diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 93bee1ce4fc..a1a429cad97 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v3 - name: Check spelling - uses: crate-ci/typos@v1.15.6 + uses: crate-ci/typos@v1.16.2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0a2c93db3c..4790f93d913 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,7 @@ jobs: - structured - p4est_part1 - p4est_part2 + - t8code_part1 - unstructured_dgmulti - parabolic - paper_self_gravitating_gas_dynamics diff --git a/AUTHORS.md b/AUTHORS.md index 973e311920b..abaa3e7e037 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -24,6 +24,7 @@ are listed in alphabetical order: * Maximilian D. Bertrand * Benjamin Bolm +* Simon Candelaresi * Jesse Chan * Lars Christmann * Christof Czernik diff --git a/NEWS.md b/NEWS.md index 35c7039b2ef..8e374d9ce99 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,7 @@ for human readability. - Experimental support for 3D parabolic diffusion terms has been added. - Capability to set truly discontinuous initial conditions in 1D. +- Wetting and drying feature and examples for 1D and 2D shallow water equations #### Changed diff --git a/Project.toml b/Project.toml index 0edba6b681c..c22d4b90642 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Trixi" uuid = "a7f1ee26-1774-49b1-8366-f1abc58fbfcb" authors = ["Michael Schlottke-Lakemper ", "Gregor Gassner ", "Hendrik Ranocha ", "Andrew R. Winters ", "Jesse Chan "] -version = "0.5.31-pre" +version = "0.5.38-pre" [deps] CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" @@ -37,6 +37,7 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StrideArrays = "d1fa6d79-ef01-42a6-86c9-f7c551f8593b" StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" SummationByPartsOperators = "9f78cca6-572e-554e-b819-917d2f1cf240" +T8code = "d0cc0030-9a40-4274-8435-baadcfd54fa1" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" Triangulate = "f7e6ffb2-c36d-4f8f-a77e-16e897189344" TriplotBase = "981d1d27-644d-49a2-9326-4793e63143c3" @@ -59,13 +60,13 @@ HDF5 = "0.14, 0.15, 0.16" IfElse = "0.1" LinearMaps = "2.7, 3.0" LoopVectorization = "0.12.118" -Makie = "0.19" MPI = "0.20" +Makie = "0.19" MuladdMacro = "0.2.2" Octavian = "0.3.5" OffsetArrays = "1.3" P4est = "0.4" -Polyester = "0.3.4, 0.5, 0.6, 0.7" +Polyester = "0.7.5" PrecompileTools = "1.1" RecipesBase = "1.1" Reexport = "1.0" @@ -79,7 +80,8 @@ StaticArrayInterface = "1.4" StaticArrays = "1" StrideArrays = "0.1.18" StructArrays = "0.6" -SummationByPartsOperators = "0.5.25" +SummationByPartsOperators = "0.5.41" +T8code = "0.4.1" TimerOutputs = "0.5" Triangulate = "2.0" TriplotBase = "0.1" diff --git a/README.md b/README.md index ccd70b6daf8..7eaee8750dd 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,16 @@

+*** +**Trixi.jl at JuliaCon 2023**
+At this year's JuliaCon, we will be present with an online contribution that involves Trixi.jl: + +* [Scaling Trixi.jl to more than 10,000 cores using MPI](https://pretalx.com/juliacon2023/talk/PC8PZ8/), + 27th July 2023, 10:30–11:30 (US/Eastern), 32-G449 (Kiva) + +We are looking forward to seeing you there ♥️ +*** + **Trixi.jl** is a numerical simulation framework for hyperbolic conservation laws written in [Julia](https://julialang.org). A key objective for the framework is to be useful to both scientists and students. Therefore, next to diff --git a/docs/literate/src/files/DGSEM_FluxDiff.jl b/docs/literate/src/files/DGSEM_FluxDiff.jl index cf3b0a1dbd4..5ec156ebbe3 100644 --- a/docs/literate/src/files/DGSEM_FluxDiff.jl +++ b/docs/literate/src/files/DGSEM_FluxDiff.jl @@ -96,13 +96,13 @@ # \begin{align*} # J \underline{\dot{u}}(t) &= - M^{-1} B (\underline{f}^* - \underline{f}) - 2D \underline{f}_{vol}(u^-, u^+)\\[5pt] # &= - M^{-1} B (\underline{f}^* - \underline{f}_{vol}(\underline{u}, \underline{u})) - 2D \underline{f}_{vol}(u^-, u^+)\\[5pt] -# &= - M^{-1} B \underline{f}_{sur}^* - (2D - M^{-1} B) \underline{f}_{vol}\\[5pt] -# &= - M^{-1} B \underline{f}_{sur}^* - D_{split} \underline{f}_{vol} +# &= - M^{-1} B \underline{f}_{surface}^* - (2D - M^{-1} B) \underline{f}_{vol}\\[5pt] +# &= - M^{-1} B \underline{f}_{surface}^* - D_{split} \underline{f}_{vol} # \end{align*} # ``` # This formulation is in a weak form type formulation and can be implemented by using the derivative # split matrix $D_{split}=(2D-M^{-1}B)$ and two different fluxes. We divide between the surface -# flux $f=f_{sur}$ used for the numerical flux $f_{sur}^*$ and the already mentioned volume +# flux $f=f_{surface}$ used for the numerical flux $f_{surface}^*$ and the already mentioned volume # flux $f_{vol}$ especially for this formulation. diff --git a/docs/literate/src/files/shock_capturing.jl b/docs/literate/src/files/shock_capturing.jl index b165f7ec8bd..afa34cbf06a 100644 --- a/docs/literate/src/files/shock_capturing.jl +++ b/docs/literate/src/files/shock_capturing.jl @@ -48,7 +48,7 @@ # with the total energy $\mathbb{E}=\max\big(\frac{m_N^2}{\sum_{j=0}^N m_j^2}, \frac{m_{N-1}^2}{\sum_{j=0}^{N-1} m_j^2}\big)$, # threshold $\mathbb{T}= 0.5 * 10^{-1.8*(N+1)^{1/4}}$ and parameter $s=ln\big(\frac{1-0.0001}{0.0001}\big)\approx 9.21024$. -# For computational efficiency, $\alpha_{min}$ is introduced und used for +# For computational efficiency, $\alpha_{min}$ is introduced and used for # ```math # \tilde{\alpha} = \begin{cases} # 0, & \text{if } \alpha<\alpha_{min}\\ diff --git a/docs/make.jl b/docs/make.jl index 5069e4dc49a..57629577ddb 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -92,6 +92,7 @@ makedocs( "Getting started" => [ "Overview" => "overview.md", "Visualization" => "visualization.md", + "Restart simulation" => "restart.md", ], "Tutorials" => tutorials, "Basic building blocks" => [ diff --git a/docs/src/restart.md b/docs/src/restart.md new file mode 100644 index 00000000000..d24d93cb297 --- /dev/null +++ b/docs/src/restart.md @@ -0,0 +1,89 @@ +# [Restart simulation](@id restart) + +You can continue running an already finished simulation by first +preparing the simulation for the restart and then performing the restart. +Here we suppose that in the first run your simulation stops at time 1.0 +and then you want it to run further to time 2.0. + +## [Prepare the simulation for a restart](@id restart_preparation) +In you original elixir you need to specify to write out restart files. +Those will later be read for the restart of your simulation. +This is done almost the same way as writing the snapshots using the +[`SaveSolutionCallback`](@ref) callback. +For the restart files it is called [`SaveRestartCallback`](@ref): +```julia +save_restart = SaveRestartCallback(interval=100, + save_final_restart=true) +``` +Make this part of your `CallbackSet`. + +An example is +[```examples/examples/structured_2d_dgsem/elixir_advection_extended.jl```](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/structured_2d_dgsem/elixir_advection_extended.jl). + + +## [Perform the simulation restart](@id restart_perform) +Since all of the information about the simulation can be obtained from the +last snapshot, the restart can be done with relatively few lines +in an extra elixir file. +However, some might prefer to keep everything in one elixir and +conditionals like ```if restart``` with a boolean variable ```restart``` that is user defined. + +First we need to define from which file we want to restart, e.g. +```julia +restart_file = "restart_000021.h5" +restart_filename = joinpath("out", restart_file) +``` + +Then we load the mesh file: +```julia +mesh = load_mesh(restart_filename) +``` + +This is then needed for the semidiscretization: +```julia +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) +``` + +We then define a new time span for the simulation that takes as starting +time the one form the snapshot: +```julia +tspan = (load_time(restart_filename), 2.0) +``` + +We now also take the last ```dt```, so that our solver does not need to first find +one to fulfill the CFL condition: +```julia +dt = load_dt(restart_filename) +``` + +The ODE that we will pass to the solver is now: +```julia +ode = semidiscretize(semi, tspan, restart_filename) +``` + +You should now define a [`SaveSolutionCallback`](@ref) similar to the +[original simulation](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/structured_2d_dgsem/elixir_advection_extended.jl), +but with ```save_initial_solution=false```, otherwise our initial snapshot will be overwritten. +If you are using one file for the original simulation and the restart +you can reuse your [`SaveSolutionCallback`](@ref), but need to set +```julia +save_solution.condition.save_initial_solution = false +``` + +Before we compute the solution using +[OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) +we need to set the integrator +and its time step number, e.g.: +```julia +integrator = init(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=dt, save_everystep=false, callback=callbacks); +integrator.iter = load_timestep(restart_filename) +integrator.stats.naccept = integrator.iter +``` + +Now we can compute the solution: +```julia +sol = solve!(integrator) +``` + +An example is in `[``examples/structured_2d_dgsem/elixir_advection_restart.jl```](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/structured_2d_dgsem/elixir_advection_restart.jl). diff --git a/examples/dgmulti_2d/elixir_euler_bilinear.jl b/examples/dgmulti_2d/elixir_euler_bilinear.jl index beb5c863971..bdd582610ea 100644 --- a/examples/dgmulti_2d/elixir_euler_bilinear.jl +++ b/examples/dgmulti_2d/elixir_euler_bilinear.jl @@ -2,7 +2,7 @@ using Trixi, OrdinaryDiffEq dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(FluxHLL()), + surface_integral = SurfaceIntegralWeakForm(flux_hll), volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) equations = CompressibleEulerEquations2D(1.4) diff --git a/examples/dgmulti_2d/elixir_euler_curved.jl b/examples/dgmulti_2d/elixir_euler_curved.jl index 4f1d613b247..39e3a0a0360 100644 --- a/examples/dgmulti_2d/elixir_euler_curved.jl +++ b/examples/dgmulti_2d/elixir_euler_curved.jl @@ -2,7 +2,7 @@ using Trixi, OrdinaryDiffEq dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(FluxHLL()), + surface_integral = SurfaceIntegralWeakForm(flux_hll), volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) equations = CompressibleEulerEquations2D(1.4) @@ -42,7 +42,8 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, +alg = RDPK3SpFSAL49() +sol = solve(ode, alg; abstol=1.0e-6, reltol=1.0e-6, ode_default_options()..., callback=callbacks); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_triangulate_pkg_mesh.jl b/examples/dgmulti_2d/elixir_euler_triangulate_pkg_mesh.jl index 1f35a11bf8e..c10b5e46a14 100644 --- a/examples/dgmulti_2d/elixir_euler_triangulate_pkg_mesh.jl +++ b/examples/dgmulti_2d/elixir_euler_triangulate_pkg_mesh.jl @@ -1,7 +1,7 @@ using Trixi, OrdinaryDiffEq dg = DGMulti(polydeg = 3, element_type = Tri(), - surface_integral = SurfaceIntegralWeakForm(FluxHLL()), + surface_integral = SurfaceIntegralWeakForm(flux_hll), volume_integral = VolumeIntegralWeakForm()) equations = CompressibleEulerEquations2D(1.4) diff --git a/examples/dgmulti_2d/elixir_euler_weakform.jl b/examples/dgmulti_2d/elixir_euler_weakform.jl index 1ecc666c8db..486a30b37f1 100644 --- a/examples/dgmulti_2d/elixir_euler_weakform.jl +++ b/examples/dgmulti_2d/elixir_euler_weakform.jl @@ -2,7 +2,7 @@ using Trixi, OrdinaryDiffEq dg = DGMulti(polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(FluxHLL()), + surface_integral = SurfaceIntegralWeakForm(flux_hll), volume_integral = VolumeIntegralWeakForm()) equations = CompressibleEulerEquations2D(1.4) diff --git a/examples/dgmulti_2d/elixir_euler_weakform_periodic.jl b/examples/dgmulti_2d/elixir_euler_weakform_periodic.jl index 48cc8070857..c4c83fff642 100644 --- a/examples/dgmulti_2d/elixir_euler_weakform_periodic.jl +++ b/examples/dgmulti_2d/elixir_euler_weakform_periodic.jl @@ -2,7 +2,7 @@ using Trixi, OrdinaryDiffEq dg = DGMulti(polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(FluxHLL()), + surface_integral = SurfaceIntegralWeakForm(flux_hll), volume_integral = VolumeIntegralWeakForm()) equations = CompressibleEulerEquations2D(1.4) diff --git a/examples/dgmulti_3d/elixir_euler_curved.jl b/examples/dgmulti_3d/elixir_euler_curved.jl index 339d6ce0186..d8c4df5dd64 100644 --- a/examples/dgmulti_3d/elixir_euler_curved.jl +++ b/examples/dgmulti_3d/elixir_euler_curved.jl @@ -2,7 +2,7 @@ using Trixi, OrdinaryDiffEq dg = DGMulti(polydeg = 3, element_type = Hex(), approximation_type=SBP(), - surface_integral = SurfaceIntegralWeakForm(FluxHLL()), + surface_integral = SurfaceIntegralWeakForm(flux_hll), volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) equations = CompressibleEulerEquations3D(1.4) diff --git a/examples/dgmulti_3d/elixir_euler_weakform.jl b/examples/dgmulti_3d/elixir_euler_weakform.jl index 4ad9f045eb6..b167377af51 100644 --- a/examples/dgmulti_3d/elixir_euler_weakform.jl +++ b/examples/dgmulti_3d/elixir_euler_weakform.jl @@ -2,7 +2,7 @@ using Trixi, OrdinaryDiffEq dg = DGMulti(polydeg = 3, element_type = Tet(), - surface_integral = SurfaceIntegralWeakForm(FluxHLL()), + surface_integral = SurfaceIntegralWeakForm(flux_hll), volume_integral = VolumeIntegralWeakForm()) equations = CompressibleEulerEquations3D(1.4) diff --git a/examples/dgmulti_3d/elixir_euler_weakform_periodic.jl b/examples/dgmulti_3d/elixir_euler_weakform_periodic.jl index f554167df90..6b17d4bba65 100644 --- a/examples/dgmulti_3d/elixir_euler_weakform_periodic.jl +++ b/examples/dgmulti_3d/elixir_euler_weakform_periodic.jl @@ -2,7 +2,7 @@ using Trixi, OrdinaryDiffEq dg = DGMulti(polydeg = 3, element_type = Tet(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(FluxHLL()), + surface_integral = SurfaceIntegralWeakForm(flux_hll), volume_integral = VolumeIntegralWeakForm()) equations = CompressibleEulerEquations3D(1.4) diff --git a/examples/p4est_2d_dgsem/elixir_advection_restart.jl b/examples/p4est_2d_dgsem/elixir_advection_restart.jl index 1906fb2896e..79a35199b83 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_restart.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_restart.jl @@ -24,13 +24,23 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, boundary_conditions=boundary_conditions) tspan = (load_time(restart_filename), 2.0) +dt = load_dt(restart_filename) ode = semidiscretize(semi, tspan, restart_filename); +# Do not overwrite the initial snapshot written by elixir_advection_extended.jl. +save_solution.condition.save_initial_solution = false + +integrator = init(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); + +# Get the last time index and work with that. +integrator.iter = load_timestep(restart_filename) +integrator.stats.naccept = integrator.iter + ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve!(integrator) summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_linearizedeuler_gaussian_source.jl b/examples/p4est_2d_dgsem/elixir_linearizedeuler_gaussian_source.jl new file mode 100644 index 00000000000..ba2ec827778 --- /dev/null +++ b/examples/p4est_2d_dgsem/elixir_linearizedeuler_gaussian_source.jl @@ -0,0 +1,89 @@ + +using OrdinaryDiffEq +using Trixi + +# Based on the TreeMesh example `elixir_acoustics_gaussian_source.jl`. +# The acoustic perturbation equations have been replaced with the linearized Euler +# equations and instead of the Cartesian `TreeMesh` a rotated `P4estMesh` is used + +# Oscillating Gaussian-shaped source terms +function source_terms_gauss(u, x, t, equations::LinearizedEulerEquations2D) + r = 0.1 + A = 1.0 + f = 2.0 + + # Velocity sources + s2 = 0.0 + s3 = 0.0 + # Density and pressure source + s1 = s4 = exp(-(x[1]^2 + x[2]^2) / (2 * r^2)) * A * sin(2 * pi * f * t) + + return SVector(s1, s2, s3, s4) +end + +initial_condition_zero(x, t, equations::LinearizedEulerEquations2D) = SVector(0.0, 0.0, 0.0, 0.0) + +############################################################################### +# semidiscretization of the linearized Euler equations + +# Create a domain that is a 30° rotated version of [-3, 3]^2 +c = cospi(2 * 30.0 / 360.0) +s = sinpi(2 * 30.0 / 360.0) +rot_mat = Trixi.SMatrix{2, 2}([c -s; s c]) +mapping(xi, eta) = rot_mat * SVector(3.0*xi, 3.0*eta) + +# Mean density and speed of sound are slightly off from 1.0 to allow proper verification of +# curved LEE implementation using this elixir (some things in the LEE cancel if both are 1.0) +equations = LinearizedEulerEquations2D(v_mean_global=Tuple(rot_mat * SVector(-0.5, 0.25)), + c_mean_global=1.02, rho_mean_global=1.01) + +initial_condition = initial_condition_zero + +# Create DG solver with polynomial degree = 3 and upwind flux as surface flux +solver = DGSEM(polydeg=3, surface_flux=flux_godunov) + +# Create a uniformly refined mesh with periodic boundaries +trees_per_dimension = (4, 4) +mesh = P4estMesh(trees_per_dimension, polydeg=1, + mapping=mapping, + periodicity=true, initial_refinement_level=2) + +# A semidiscretization collects data structures and functions for the spatial discretization +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms=source_terms_gauss) + + +############################################################################### +# ODE solvers, callbacks etc. + +# Create ODE problem with time span from 0.0 to 2.0 +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +# At the beginning of the main loop, the SummaryCallback prints a summary of the simulation setup +# and resets the timers +summary_callback = SummaryCallback() + +# The AnalysisCallback allows to analyse the solution in regular intervals and prints the results +analysis_callback = AnalysisCallback(semi, interval=100) + +# The SaveSolutionCallback allows to save the solution to a file in regular intervals +save_solution = SaveSolutionCallback(interval=100) + +# The StepsizeCallback handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl=0.5) + +# Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, stepsize_callback) + + +############################################################################### +# run the simulation + +# OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks +sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); + +# Print the timer summary +summary_callback() diff --git a/examples/p4est_3d_dgsem/elixir_advection_restart.jl b/examples/p4est_3d_dgsem/elixir_advection_restart.jl index 71b37e9f39b..b27eaab62e2 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_restart.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_restart.jl @@ -21,13 +21,23 @@ mesh = load_mesh(restart_filename) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, solver) tspan = (load_time(restart_filename), 2.0) +dt = load_dt(restart_filename) ode = semidiscretize(semi, tspan, restart_filename); +# Do not overwrite the initial snapshot written by elixir_advection_extended.jl. +save_solution.condition.save_initial_solution = false + +integrator = init(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); + +# Get the last time index and work with that. +integrator.iter = load_timestep(restart_filename) +integrator.stats.naccept = integrator.iter + ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve!(integrator) summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_navierstokes_convergence.jl b/examples/p4est_3d_dgsem/elixir_navierstokes_convergence.jl new file mode 100644 index 00000000000..c426fe95f5b --- /dev/null +++ b/examples/p4est_3d_dgsem/elixir_navierstokes_convergence.jl @@ -0,0 +1,263 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the ideal compressible Navier-Stokes equations + +prandtl_number() = 0.72 +mu() = 0.01 + +equations = CompressibleEulerEquations3D(1.4) +equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu=mu(), Prandtl=prandtl_number(), + gradient_variables=GradientVariablesPrimitive()) + +# Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux +solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs, + volume_integral=VolumeIntegralWeakForm()) + +coordinates_min = (-1.0, -1.0, -1.0) # minimum coordinates (min(x), min(y), min(z)) +coordinates_max = ( 1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) + +trees_per_dimension = (2, 2, 2) + +mesh = P4estMesh(trees_per_dimension, polydeg=3, + coordinates_min=coordinates_min, coordinates_max=coordinates_max, + periodicity=(true, false, true), initial_refinement_level=2) + +# Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion3D` +# since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion3D`) +# and by the initial condition (which passes in `CompressibleEulerEquations3D`). +# This convergence test setup was originally derived by Andrew Winters (@andrewwinters5000) +function initial_condition_navier_stokes_convergence_test(x, t, equations) + # Constants. OBS! Must match those in `source_terms_navier_stokes_convergence_test` + c = 2.0 + A1 = 0.5 + A2 = 1.0 + A3 = 0.5 + + # Convenience values for trig. functions + pi_x = pi * x[1] + pi_y = pi * x[2] + pi_z = pi * x[3] + pi_t = pi * t + + rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) + v1 = A2 * sin(pi_x) * log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) * sin(pi_z) * cos(pi_t) + v2 = v1 + v3 = v1 + p = rho^2 + + return prim2cons(SVector(rho, v1, v2, v3, p), equations) +end + +@inline function source_terms_navier_stokes_convergence_test(u, x, t, equations) + # TODO: parabolic + # we currently need to hardcode these parameters until we fix the "combined equation" issue + # see also https://github.com/trixi-framework/Trixi.jl/pull/1160 + inv_gamma_minus_one = inv(equations.gamma - 1) + Pr = prandtl_number() + mu_ = mu() + + # Constants. OBS! Must match those in `initial_condition_navier_stokes_convergence_test` + c = 2.0 + A1 = 0.5 + A2 = 1.0 + A3 = 0.5 + + # Convenience values for trig. functions + pi_x = pi * x[1] + pi_y = pi * x[2] + pi_z = pi * x[3] + pi_t = pi * t + + # Define auxiliary functions for the strange function of the y variable + # to make expressions easier to read + g = log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) + g_y = ( A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + + (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0) ) + g_yy = ( 2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) + - (1.0 - exp(-A3 * (x[2] - 1.0))) / ((x[2] + 2.0)^2) + - A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) ) + + # Density and its derivatives + rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) + rho_t = -pi * A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * sin(pi_t) + rho_x = pi * A1 * cos(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) + rho_y = -pi * A1 * sin(pi_x) * sin(pi_y) * sin(pi_z) * cos(pi_t) + rho_z = pi * A1 * sin(pi_x) * cos(pi_y) * cos(pi_z) * cos(pi_t) + rho_xx = -pi^2 * (rho - c) + rho_yy = -pi^2 * (rho - c) + rho_zz = -pi^2 * (rho - c) + + # Velocities and their derivatives + # v1 terms + v1 = A2 * sin(pi_x) * g * sin(pi_z) * cos(pi_t) + v1_t = -pi * A2 * sin(pi_x) * g * sin(pi_z) * sin(pi_t) + v1_x = pi * A2 * cos(pi_x) * g * sin(pi_z) * cos(pi_t) + v1_y = A2 * sin(pi_x) * g_y * sin(pi_z) * cos(pi_t) + v1_z = pi * A2 * sin(pi_x) * g * cos(pi_z) * cos(pi_t) + v1_xx = -pi^2 * v1 + v1_yy = A2 * sin(pi_x) * g_yy * sin(pi_z) * cos(pi_t) + v1_zz = -pi^2 * v1 + v1_xy = pi * A2 * cos(pi_x) * g_y * sin(pi_z) * cos(pi_t) + v1_xz = pi^2 * A2 * cos(pi_x) * g * cos(pi_z) * cos(pi_t) + v1_yz = pi * A2 * sin(pi_x) * g_y * cos(pi_z) * cos(pi_t) + # v2 terms (simplifies from ansatz) + v2 = v1 + v2_t = v1_t + v2_x = v1_x + v2_y = v1_y + v2_z = v1_z + v2_xx = v1_xx + v2_yy = v1_yy + v2_zz = v1_zz + v2_xy = v1_xy + v2_yz = v1_yz + # v3 terms (simplifies from ansatz) + v3 = v1 + v3_t = v1_t + v3_x = v1_x + v3_y = v1_y + v3_z = v1_z + v3_xx = v1_xx + v3_yy = v1_yy + v3_zz = v1_zz + v3_xz = v1_xz + v3_yz = v1_yz + + # Pressure and its derivatives + p = rho^2 + p_t = 2.0 * rho * rho_t + p_x = 2.0 * rho * rho_x + p_y = 2.0 * rho * rho_y + p_z = 2.0 * rho * rho_z + + # Total energy and its derivatives; simiplifies from ansatz that v2 = v1 and v3 = v1 + E = p * inv_gamma_minus_one + 1.5 * rho * v1^2 + E_t = p_t * inv_gamma_minus_one + 1.5 * rho_t * v1^2 + 3.0 * rho * v1 * v1_t + E_x = p_x * inv_gamma_minus_one + 1.5 * rho_x * v1^2 + 3.0 * rho * v1 * v1_x + E_y = p_y * inv_gamma_minus_one + 1.5 * rho_y * v1^2 + 3.0 * rho * v1 * v1_y + E_z = p_z * inv_gamma_minus_one + 1.5 * rho_z * v1^2 + 3.0 * rho * v1 * v1_z + + # Divergence of Fick's law ∇⋅∇q = kappa ∇⋅∇T; simplifies because p = rho², so T = p/rho = rho + kappa = equations.gamma * inv_gamma_minus_one / Pr + q_xx = kappa * rho_xx # kappa T_xx + q_yy = kappa * rho_yy # kappa T_yy + q_zz = kappa * rho_zz # kappa T_zz + + # Stress tensor and its derivatives (exploit symmetry) + tau11 = 4.0 / 3.0 * v1_x - 2.0 / 3.0 * (v2_y + v3_z) + tau12 = v1_y + v2_x + tau13 = v1_z + v3_x + tau22 = 4.0 / 3.0 * v2_y - 2.0 / 3.0 * (v1_x + v3_z) + tau23 = v2_z + v3_y + tau33 = 4.0 / 3.0 * v3_z - 2.0 / 3.0 * (v1_x + v2_y) + + tau11_x = 4.0 / 3.0 * v1_xx - 2.0 / 3.0 * (v2_xy + v3_xz) + tau12_x = v1_xy + v2_xx + tau13_x = v1_xz + v3_xx + + tau12_y = v1_yy + v2_xy + tau22_y = 4.0 / 3.0 * v2_yy - 2.0 / 3.0 * (v1_xy + v3_yz) + tau23_y = v2_yz + v3_yy + + tau13_z = v1_zz + v3_xz + tau23_z = v2_zz + v3_yz + tau33_z = 4.0 / 3.0 * v3_zz - 2.0 / 3.0 * (v1_xz + v2_yz) + + # Compute the source terms + # Density equation + du1 = ( rho_t + rho_x * v1 + rho * v1_x + + rho_y * v2 + rho * v2_y + + rho_z * v3 + rho * v3_z ) + # x-momentum equation + du2 = ( rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y + + rho_z * v1 * v3 + + rho * v1_z * v3 + + rho * v1 * v3_z + - mu_ * (tau11_x + tau12_y + tau13_z) ) + # y-momentum equation + du3 = ( rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y + + rho_z * v2 * v3 + + rho * v2_z * v3 + + rho * v2 * v3_z + - mu_ * (tau12_x + tau22_y + tau23_z) ) + # z-momentum equation + du4 = ( rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 + + rho * v1_x * v3 + + rho * v1 * v3_x + + rho_y * v2 * v3 + + rho * v2_y * v3 + + rho * v2 * v3_y + + rho_z * v3^2 + + 2.0 * rho * v3 * v3_z + - mu_ * (tau13_x + tau23_y + tau33_z) ) + # Total energy equation + du5 = ( E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) + + v3_z * (E + p) + v3 * (E_z + p_z) + # stress tensor and temperature gradient from x-direction + - mu_ * ( q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 + + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x) + # stress tensor and temperature gradient terms from y-direction + - mu_ * ( q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 + + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y) + # stress tensor and temperature gradient terms from z-direction + - mu_ * ( q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 + + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z) ) + + return SVector(du1, du2, du3, du4, du5) +end + +initial_condition = initial_condition_navier_stokes_convergence_test + +# BC types +velocity_bc_top_bottom = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, t, equations)[2:4]) +heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, heat_bc_top_bottom) + +# define inviscid boundary conditions +boundary_conditions = Dict( + :y_neg => boundary_condition_slip_wall, + :y_pos => boundary_condition_slip_wall + ) + +# define viscous boundary conditions +boundary_conditions_parabolic = Dict( + :y_neg => boundary_condition_top_bottom, + :y_pos => boundary_condition_top_bottom + ) + +semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), initial_condition, solver; + boundary_conditions=(boundary_conditions, boundary_conditions_parabolic), + source_terms=source_terms_navier_stokes_convergence_test) + +############################################################################### +# ODE solvers, callbacks etc. + +# Create ODE problem with time span `tspan` +tspan = (0.0, 0.2) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() +alive_callback = AliveCallback(alive_interval=10) +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval) +callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) + +############################################################################### +# run the simulation + +time_int_tol = 1e-8 +sol = solve(ode, RDPK3SpFSAL49(); abstol=time_int_tol, reltol=time_int_tol, dt = 1e-5, + ode_default_options()..., callback=callbacks) +summary_callback() # print the timer summary + diff --git a/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl b/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl new file mode 100644 index 00000000000..c5b9ccf2e38 --- /dev/null +++ b/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl @@ -0,0 +1,82 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Navier-Stokes equations + +# TODO: parabolic; unify names of these accessor functions +prandtl_number() = 0.72 +mu() = 6.25e-4 # equivalent to Re = 1600 + +equations = CompressibleEulerEquations3D(1.4) +equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu=mu(), + Prandtl=prandtl_number()) + +""" + 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 + +volume_flux = flux_ranocha +solver = DGSEM(polydeg=3, surface_flux=flux_hll, + volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) + +coordinates_min = (-1.0, -1.0, -1.0) .* pi +coordinates_max = ( 1.0, 1.0, 1.0) .* pi + +trees_per_dimension = (2, 2, 2) + +mesh = P4estMesh(trees_per_dimension, polydeg=3, + coordinates_min=coordinates_min, coordinates_max=coordinates_max, + periodicity=(true, true, true), initial_refinement_level=2) + + +semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), + initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 20.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 50 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=true, + extra_analysis_integrals=(energy_kinetic, + energy_internal, + enstrophy)) +save_solution = SaveSolutionCallback(interval=100, + save_initial_solution=true, + save_final_solution=true, + solution_variables=cons2prim) +alive_callback = AliveCallback(analysis_interval=analysis_interval,) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback,save_solution) + +############################################################################### +# run the simulation + +time_int_tol = 1e-8 +sol = solve(ode, RDPK3SpFSAL49(); abstol=time_int_tol, reltol=time_int_tol, + ode_default_options()..., callback=callbacks) +summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_advection_restart.jl b/examples/structured_2d_dgsem/elixir_advection_restart.jl index 2c2a0ef8f51..98c44fac71a 100644 --- a/examples/structured_2d_dgsem/elixir_advection_restart.jl +++ b/examples/structured_2d_dgsem/elixir_advection_restart.jl @@ -23,13 +23,22 @@ mesh = load_mesh(restart_filename) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) tspan = (load_time(restart_filename), 2.0) +dt = load_dt(restart_filename) ode = semidiscretize(semi, tspan, restart_filename); +# Do not overwrite the initial snapshot written by elixir_advection_extended.jl. +save_solution.condition.save_initial_solution = false + +integrator = init(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); + +# Get the last time index and work with that. +integrator.iter = load_timestep(restart_filename) +integrator.stats.naccept = integrator.iter ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve!(integrator) summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_shallowwater_conical_island.jl b/examples/structured_2d_dgsem/elixir_shallowwater_conical_island.jl new file mode 100644 index 00000000000..44bc7a12b35 --- /dev/null +++ b/examples/structured_2d_dgsem/elixir_shallowwater_conical_island.jl @@ -0,0 +1,113 @@ + +using OrdinaryDiffEq +using Trixi + + ############################################################################### + # Semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + +equations = ShallowWaterEquations2D(gravity_constant=9.81, H0=1.4) + +""" + initial_condition_conical_island(x, t, equations::ShallowWaterEquations2D) + +Initial condition for the [`ShallowWaterEquations2D`](@ref) to test the [`hydrostatic_reconstruction_chen_noelle`](@ref) +and its handling of discontinuous water heights at the start in combination with wetting and +drying. The bottom topography is given by a conical island in the middle of the domain. Around that +island, there is a cylindrical water column at t=0 and the rest of the domain is dry. This +discontinuous water height is smoothed by a logistic function. This simulation uses periodic +boundary conditions. +""" +function initial_condition_conical_island(x, t, equations::ShallowWaterEquations2D) + # Set the background values + + v1 = 0.0 + v2 = 0.0 + + x1, x2 = x + b = max(0.1, 1.0 - 4.0 * sqrt(x1^2 + x2^2)) + + # use a logistic function to transfer water height value smoothly + L = equations.H0 # maximum of function + x0 = 0.3 # center point of function + k = -25.0 # sharpness of transfer + + H = max(b, L/(1.0 + exp(-k*(sqrt(x1^2+x2^2) - x0)))) + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v1) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v1, v2, b), equations) +end + +initial_condition = initial_condition_conical_island + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(4) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +############################################################################### +# Get the StructuredMesh and setup a periodic mesh + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) + +cells_per_dimension = (16, 16) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +# Create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solver + +tspan = (0.0, 10.0) +ode = semidiscretize(semi, tspan) + +############################################################################### +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +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) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +############################################################################### +# run the simulation + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +sol = solve(ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback=callbacks); + +summary_callback() # print the timer summary \ No newline at end of file diff --git a/examples/structured_2d_dgsem/elixir_shallowwater_parabolic_bowl.jl b/examples/structured_2d_dgsem/elixir_shallowwater_parabolic_bowl.jl new file mode 100644 index 00000000000..15cfe6698fc --- /dev/null +++ b/examples/structured_2d_dgsem/elixir_shallowwater_parabolic_bowl.jl @@ -0,0 +1,119 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + +equations = ShallowWaterEquations2D(gravity_constant=9.81) + +""" + initial_condition_parabolic_bowl(x, t, equations:: ShallowWaterEquations2D) + +Well-known initial condition to test the [`hydrostatic_reconstruction_chen_noelle`](@ref) and its +wet-dry mechanics. This test has an analytical solution. The initial condition is defined by the +analytical solution at time t=0. The bottom topography defines a bowl and the water level is given +by an oscillating lake. + +The original test and its analytical solution were first presented in +- William C. Thacker (1981) + Some exact solutions to the nonlinear shallow-water wave equations + [DOI: 10.1017/S0022112081001882](https://doi.org/10.1017/S0022112081001882). + +The particular setup below is taken from Section 6.2 of +- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and Timothy Warburton (2018) + An entropy stable discontinuous Galerkin method for the shallow water equations on + curvilinear meshes with wet/dry fronts accelerated by GPUs + [DOI: 10.1016/j.jcp.2018.08.038](https://doi.org/10.1016/j.jcp.2018.08.038). +""" +function initial_condition_parabolic_bowl(x, t, equations:: ShallowWaterEquations2D) + a = 1.0 + h_0 = 0.1 + sigma = 0.5 + ω = sqrt(2 * equations.gravity * h_0) / a + + v1 = -sigma * ω * sin(ω * t) + v2 = sigma * ω * cos(ω * t) + + b = h_0 * ((x[1])^2 + (x[2])^2) / a^2 + + H = sigma * h_0 / a^2 * (2 * x[1] * cos(ω * t) + 2 * x[2] * sin(ω * t) - sigma) + h_0 + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v1) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v1, v2, b), equations) +end + +initial_condition = initial_condition_parabolic_bowl + + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(4) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.6, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + + +############################################################################### + +coordinates_min = (-2.0, -2.0) +coordinates_max = (2.0, 2.0) + +cells_per_dimension = (150, 150) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +# create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=false, + extra_analysis_integrals=(energy_kinetic, + energy_internal)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +save_solution = SaveSolutionCallback(interval=100, + save_initial_solution=true, + save_final_solution=true) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +############################################################################### +# run the simulation + +sol = solve(ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback=callbacks); + +summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl b/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl new file mode 100644 index 00000000000..b18b02e0b4c --- /dev/null +++ b/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl @@ -0,0 +1,200 @@ + +using OrdinaryDiffEq +using Trixi +using Printf: @printf, @sprintf + +############################################################################### +# Semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + + +equations = ShallowWaterEquations2D(gravity_constant=9.812) + +""" + initial_condition_well_balanced_chen_noelle(x, t, equations:: ShallowWaterEquations2D) + +Initial condition with a complex (discontinuous) bottom topography to test the well-balanced +property for the [`hydrostatic_reconstruction_chen_noelle`](@ref) including dry areas within the +domain. The errors from the analysis callback are not important but the error for this +lake-at-rest test case `∑|H0-(h+b)|` should be around machine roundoff. + +The initial condition is taken from Section 5.2 of the paper: +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +function initial_condition_complex_bottom_well_balanced(x, t, equations:: ShallowWaterEquations2D) + v1 = 0 + v2 = 0 + b = sin(4 * pi * x[1]) + 3 + + if x[1] >= 0.5 + b = sin(4 * pi * x[1]) + 1 + end + + H = max(b, 2.5) + + if x[1] >= 0.5 + H = max(b, 1.5) + end + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v1) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v1, v2, b), equations) +end + +initial_condition = initial_condition_complex_bottom_well_balanced + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) + +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(3) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + + +############################################################################### +# Create the StructuredMesh for the domain [0, 1]^2 + +coordinates_min = (0.0, 0.0) +coordinates_max = (1.0, 1.0) + +cells_per_dimension = (16, 16) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + + +# create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 10.0) +ode = semidiscretize(semi, tspan) + +############################################################################### +# Workaround to set a discontinuous water and bottom topography for +# debugging and testing. Essentially, this is a slight augmentation of the +# `compute_coefficients` where the `x` node value passed here is slightly +# perturbed to the left / right in order to set a true discontinuity that avoids +# the doubled value of the LGL nodes at a particular element interface. +# +# Note! The errors from the analysis callback are not important but the error +# for this lake at rest test case `∑|H0-(h+b)|` should be near machine roundoff. + +# point to the data we want to augment +u = Trixi.wrap_array(ode.u0, semi) +# reset the initial condition +for element in eachelement(semi.solver, semi.cache) + for j in eachnode(semi.solver), i in eachnode(semi.solver) + x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, semi.solver, i, j, element) + # We know that the discontinuity is a vertical line. Slightly augment the x value by a factor + # of unit roundoff to avoid the repeted value from the LGL nodes at at interface. + if i == 1 + x_node = SVector(nextfloat(x_node[1]) , x_node[2]) + elseif i == nnodes(semi.solver) + x_node = SVector(prevfloat(x_node[1]) , x_node[2]) + end + u_node = initial_condition_complex_bottom_well_balanced(x_node, first(tspan), equations) + Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) + end +end + +############################################################################### +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=false) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +save_solution = SaveSolutionCallback(interval=1000, + save_initial_solution=true, + save_final_solution=true) + +stepsize_callback = StepsizeCallback(cfl=1.0) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +############################################################################### +# run the simulation + +sol = solve(ode, SSPRK43(stage_limiter!); dt=1.0, + ode_default_options()..., callback=callbacks, adaptive=false); + +summary_callback() # print the timer summary + +############################################################################### +# Workaround to compute the well-balancedness error for this particular problem +# that has two reference water heights. One for a lake to the left of the +# discontinuous bottom topography `H0_upper = 2.5` and another for a lake to the +# right of the discontinuous bottom topography `H0_lower = 1.5`. + +# Declare a special version of the function to compute the lake-at-rest error +# OBS! The reference water height values are hardcoded for convenience. +function lake_at_rest_error_two_level(u, x, equations::ShallowWaterEquations2D) + h, _, _, b = u + + # For well-balancedness testing with possible wet/dry regions the reference + # water height `H0` accounts for the possibility that the bottom topography + # can emerge out of the water as well as for the threshold offset to avoid + # division by a "hard" zero water heights as well. + if x[1] < 0.5 + H0_wet_dry = max( 2.5 , b + equations.threshold_limiter ) + else + H0_wet_dry = max( 1.5 , b + equations.threshold_limiter ) + end + + return abs(H0_wet_dry - (h + b)) +end + +# point to the data we want to analyze +u = Trixi.wrap_array(sol[end], semi) +# Perform the actual integration of the well-balancedness error over the domain +l1_well_balance_error = Trixi.integrate_via_indices(u, mesh, equations, semi.solver, semi.cache; normalize=true) do u, i, j, element, equations, solver + x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, solver, i, j, element) + # We know that the discontinuity is a vertical line. Slightly augment the x value by a factor + # of unit roundoff to avoid the repeted value from the LGL nodes at at interface. + if i == 1 + x_node = SVector(nextfloat(x_node[1]) , x_node[2]) + elseif i == nnodes(semi.solver) + x_node = SVector(prevfloat(x_node[1]) , x_node[2]) + end + u_local = Trixi.get_node_vars(u, equations, solver, i, j, element) + return lake_at_rest_error_two_level(u_local, x_node, equations) +end + +# report the well-balancedness lake-at-rest error to the screen +println("─"^100) +println(" Lake-at-rest error for '", Trixi.get_name(equations), "' with ", summary(solver), + " at final time " * @sprintf("%10.8e", tspan[end])) + +@printf(" %-12s:", Trixi.pretty_form_utf(lake_at_rest_error)) +@printf(" % 10.8e", l1_well_balance_error) +println() +println("─"^100) diff --git a/examples/structured_3d_dgsem/elixir_advection_restart.jl b/examples/structured_3d_dgsem/elixir_advection_restart.jl index 39e1a675167..39d28848c77 100644 --- a/examples/structured_3d_dgsem/elixir_advection_restart.jl +++ b/examples/structured_3d_dgsem/elixir_advection_restart.jl @@ -21,13 +21,23 @@ mesh = load_mesh(restart_filename) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, solver) tspan = (load_time(restart_filename), 2.0) +dt = load_dt(restart_filename) ode = semidiscretize(semi, tspan, restart_filename); +# Do not overwrite the initial snapshot written by elixir_advection_extended.jl. +save_solution.condition.save_initial_solution = false + +integrator = init(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); + +# Get the last time index and work with that. +integrator.iter = load_timestep(restart_filename) +integrator.stats.naccept = integrator.iter + ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve!(integrator) summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl b/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl new file mode 100644 index 00000000000..653bab41e2d --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl @@ -0,0 +1,143 @@ +using OrdinaryDiffEq +using Trixi + +# Define new structs inside a module to allow re-evaluating the file. +module TrixiExtension + +using Trixi + +struct IndicatorSolutionIndependent{Cache <: NamedTuple} <: Trixi.AbstractIndicator + cache::Cache +end + +function IndicatorSolutionIndependent(semi) + basis = semi.solver.basis + alpha = Vector{real(basis)}() + cache = (; semi.mesh, alpha) + return IndicatorSolutionIndependent{typeof(cache)}(cache) +end + +function (indicator::IndicatorSolutionIndependent)(u::AbstractArray{<:Any, 4}, + mesh, equations, dg, cache; + t, kwargs...) + mesh = indicator.cache.mesh + alpha = indicator.cache.alpha + resize!(alpha, nelements(dg, cache)) + + # Predict the theoretical center. + advection_velocity = (0.2, -0.7) + center = t .* advection_velocity + + inner_distance = 1 + outer_distance = 1.85 + + # Iterate over all elements. + for element in 1:length(alpha) + # Calculate periodic distance between cell and center. + # This requires an uncurved mesh! + coordinates = SVector(0.5 * (cache.elements.node_coordinates[1, 1, 1, element] + + cache.elements.node_coordinates[1, end, 1, element]), + 0.5 * (cache.elements.node_coordinates[2, 1, 1, element] + + cache.elements.node_coordinates[2, 1, end, element])) + + # The geometric shape of the amr should be preserved when the base_level is increased. + # This is done by looking at the original coordinates of each cell. + cell_coordinates = original_coordinates(coordinates, 5 / 8) + cell_distance = periodic_distance_2d(cell_coordinates, center, 10) + if cell_distance < (inner_distance + outer_distance) / 2 + cell_coordinates = original_coordinates(coordinates, 5 / 16) + cell_distance = periodic_distance_2d(cell_coordinates, center, 10) + end + + # Set alpha according to cells position inside the circles. + target_level = (cell_distance < inner_distance) + (cell_distance < outer_distance) + alpha[element] = target_level / 2 + end + return alpha +end + +# For periodic domains, distance between two points must take into account +# periodic extensions of the domain. +function periodic_distance_2d(coordinates, center, domain_length) + dx = coordinates .- center + dx_shifted = abs.(dx .% domain_length) + dx_periodic = min.(dx_shifted, domain_length .- dx_shifted) + return sqrt(sum(dx_periodic .^ 2)) +end + +# This takes a cells coordinates and transforms them into the coordinates of a +# parent-cell it originally refined from. It does it so that the parent-cell +# has given cell_length. +function original_coordinates(coordinates, cell_length) + offset = coordinates .% cell_length + offset_sign = sign.(offset) + border = coordinates - offset + center = border + (offset_sign .* cell_length / 2) + return center +end + +end # module TrixiExtension + +import .TrixiExtension + +############################################################################### +# Semidiscretization of the linear advection equation. + +advection_velocity = (0.2, -0.7) +equations = LinearScalarAdvectionEquation2D(advection_velocity) + +initial_condition = initial_condition_gauss + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (-5.0, -5.0) +coordinates_max = (5.0, 5.0) + +mapping = Trixi.coordinates2mapping(coordinates_min, coordinates_max) + +trees_per_dimension = (1, 1) + +mesh = T8codeMesh(trees_per_dimension, polydeg = 3, + mapping = mapping, + initial_refinement_level = 1) + +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, + extra_analysis_integrals = (entropy,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +amr_controller = ControllerThreeLevel(semi, + TrixiExtension.IndicatorSolutionIndependent(semi), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6) + +amr_callback = AMRCallback(semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true) + +stepsize_callback = StepsizeCallback(cfl = 1.6) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + amr_callback, stepsize_callback); + +############################################################################### +# Run the simulation. + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_advection_amr_unstructured_flag.jl b/examples/t8code_2d_dgsem/elixir_advection_amr_unstructured_flag.jl new file mode 100644 index 00000000000..adf1d009a59 --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_advection_amr_unstructured_flag.jl @@ -0,0 +1,87 @@ +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the linear advection equation. + +advection_velocity = (0.2, -0.7) +equations = LinearScalarAdvectionEquation2D(advection_velocity) + +initial_condition = initial_condition_gauss + +boundary_condition = BoundaryConditionDirichlet(initial_condition) +boundary_conditions = Dict(:all => boundary_condition) + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +# Deformed rectangle that looks like a waving flag, lower and upper faces are +# sinus curves, left and right are vertical lines. +f1(s) = SVector(-5.0, 5 * s - 5.0) +f2(s) = SVector(5.0, 5 * s + 5.0) +f3(s) = SVector(5 * s, -5.0 + 5 * sin(0.5 * pi * s)) +f4(s) = SVector(5 * s, 5.0 + 5 * sin(0.5 * pi * s)) +faces = (f1, f2, f3, f4) + +# This creates a mapping that transforms [-1, 1]^2 to the domain with the faces +# defined above. It generally doesn't work for meshes loaded from mesh files +# because these can be meshes of arbitrary domains, but the mesh below is +# specifically built on the domain [-1, 1]^2. +Trixi.validate_faces(faces) +mapping_flag = Trixi.transfinite_mapping(faces) + +# Unstructured mesh with 24 cells of the square domain [-1, 1]^n +mesh_file = joinpath(@__DIR__, "square_unstructured_2.inp") +isfile(mesh_file) || + download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + mesh_file) + +# INP mesh files are only support by p4est. Hence, we +# create a p4est connecvity object first from which +# we can create a t8code mesh. +conn = Trixi.read_inp_p4est(mesh_file, Val(2)) + +mesh = T8codeMesh{2}(conn, polydeg = 3, + mapping = mapping_flag, + initial_refinement_level = 1) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# 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, + extra_analysis_integrals = (entropy,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), + base_level = 1, + med_level = 2, med_threshold = 0.1, + max_level = 3, max_threshold = 0.6) +amr_callback = AMRCallback(semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true) + +stepsize_callback = StepsizeCallback(cfl = 0.7) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + amr_callback, stepsize_callback) + +############################################################################### +# Run the simulation. + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); + +summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_advection_basic.jl b/examples/t8code_2d_dgsem/elixir_advection_basic.jl new file mode 100644 index 00000000000..efc51226586 --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_advection_basic.jl @@ -0,0 +1,59 @@ +# The same setup as tree_2d_dgsem/elixir_advection_basic.jl +# to verify the StructuredMesh implementation against TreeMesh + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linear advection equation + +advection_velocity = (0.2, -0.7) +equations = LinearScalarAdvectionEquation2D(advection_velocity) + +# Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) +coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) + +mapping = Trixi.coordinates2mapping(coordinates_min, coordinates_max) + +trees_per_dimension = (8, 8) + +mesh = T8codeMesh(trees_per_dimension, polydeg = 3, + mapping = mapping, + initial_refinement_level = 1) + +# A semidiscretization collects data structures and functions for the spatial discretization +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, + solver) + +############################################################################### +# ODE solvers, callbacks etc. + +# Create ODE problem with time span from 0.0 to 1.0 +ode = semidiscretize(semi, (0.0, 1.0)); + +# At the beginning of the main loop, the SummaryCallback prints a summary of the simulation setup +# and resets the timers +summary_callback = SummaryCallback() + +# The AnalysisCallback allows to analyse the solution in regular intervals and prints the results +analysis_callback = AnalysisCallback(semi, interval = 100) + +# The StepsizeCallback handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl = 1.6) + +# Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) + +############################################################################### +# run the simulation + +# OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); + +# Print the timer summary +summary_callback() diff --git a/examples/t8code_2d_dgsem/elixir_advection_nonconforming_flag.jl b/examples/t8code_2d_dgsem/elixir_advection_nonconforming_flag.jl new file mode 100644 index 00000000000..31a8bc93697 --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_advection_nonconforming_flag.jl @@ -0,0 +1,109 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linear advection equation + +advection_velocity = (0.2, -0.7) +equations = LinearScalarAdvectionEquation2D(advection_velocity) + +# Create DG solver with polynomial degree = 4 and (local) Lax-Friedrichs/Rusanov flux as surface flux +solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs) + +# Deformed rectangle that looks like a waving flag, +# lower and upper faces are sinus curves, left and right are vertical lines. +f1(s) = SVector(-1.0, s - 1.0) +f2(s) = SVector(1.0, s + 1.0) +f3(s) = SVector(s, -1.0 + sin(0.5 * pi * s)) +f4(s) = SVector(s, 1.0 + sin(0.5 * pi * s)) + +faces = (f1, f2, f3, f4) +mapping = Trixi.transfinite_mapping(faces) + +# Create P4estMesh with 3 x 2 trees and 6 x 4 elements, +# approximate the geometry with a smaller polydeg for testing. +trees_per_dimension = (3, 2) +mesh = T8codeMesh(trees_per_dimension, polydeg = 3, + mapping = mapping, + initial_refinement_level = 1) + +function adapt_callback(forest, + forest_from, + which_tree, + lelement_id, + ts, + is_family, + num_elements, + elements_ptr)::Cint + vertex = Vector{Cdouble}(undef, 3) + + elements = unsafe_wrap(Array, elements_ptr, num_elements) + + Trixi.t8_element_vertex_reference_coords(ts, elements[1], 0, pointer(vertex)) + + level = Trixi.t8_element_level(ts, elements[1]) + + # TODO: Make this condition more general. + if vertex[1] < 1e-8 && vertex[2] < 1e-8 && level < 4 + # return true (refine) + return 1 + else + # return false (don't refine) + return 0 + end +end + +Trixi.@T8_ASSERT(Trixi.t8_forest_is_committed(mesh.forest)!=0); + +# Init new forest. +new_forest_ref = Ref{Trixi.t8_forest_t}() +Trixi.t8_forest_init(new_forest_ref); +new_forest = new_forest_ref[] + +# Check out `examples/t8_step4_partition_balance_ghost.jl` in +# https://github.com/DLR-AMR/T8code.jl for detailed explanations. +let set_from = C_NULL, recursive = 1, set_for_coarsening = 0, no_repartition = 0 + Trixi.t8_forest_set_user_data(new_forest, C_NULL) + Trixi.t8_forest_set_adapt(new_forest, mesh.forest, + Trixi.@t8_adapt_callback(adapt_callback), recursive) + Trixi.t8_forest_set_balance(new_forest, set_from, no_repartition) + Trixi.t8_forest_set_partition(new_forest, set_from, set_for_coarsening) + Trixi.t8_forest_set_ghost(new_forest, 1, Trixi.T8_GHOST_FACES) + Trixi.t8_forest_commit(new_forest) +end + +mesh.forest = new_forest + +# A semidiscretization collects data structures and functions for the spatial discretization +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, + solver) + +############################################################################### +# ODE solvers, callbacks etc. + +# Create ODE problem with time span from 0.0 to 0.2 +ode = semidiscretize(semi, (0.0, 0.2)); + +# At the beginning of the main loop, the SummaryCallback prints a summary of the simulation setup +# and resets the timers +summary_callback = SummaryCallback() + +# The AnalysisCallback allows to analyse the solution in regular intervals and prints the results +analysis_callback = AnalysisCallback(semi, interval = 100) + +# The StepsizeCallback handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl = 1.6) + +# Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) + +############################################################################### +# run the simulation + +# OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); + +# Print the timer summary +summary_callback() diff --git a/examples/t8code_2d_dgsem/elixir_advection_unstructured_flag.jl b/examples/t8code_2d_dgsem/elixir_advection_unstructured_flag.jl new file mode 100644 index 00000000000..df9cbc26f6e --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_advection_unstructured_flag.jl @@ -0,0 +1,81 @@ +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the linear advection equation. + +advection_velocity = (0.2, -0.7) +equations = LinearScalarAdvectionEquation2D(advection_velocity) + +initial_condition = initial_condition_convergence_test + +boundary_condition = BoundaryConditionDirichlet(initial_condition) +boundary_conditions = Dict(:all => boundary_condition) + +# Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux. +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +# Deformed rectangle that looks like a waving flag, +# lower and upper faces are sinus curves, left and right are vertical lines. +f1(s) = SVector(-1.0, s - 1.0) +f2(s) = SVector(1.0, s + 1.0) +f3(s) = SVector(s, -1.0 + sin(0.5 * pi * s)) +f4(s) = SVector(s, 1.0 + sin(0.5 * pi * s)) +faces = (f1, f2, f3, f4) + +Trixi.validate_faces(faces) +mapping_flag = Trixi.transfinite_mapping(faces) + +# Unstructured mesh with 24 cells of the square domain [-1, 1]^n. +mesh_file = joinpath(@__DIR__, "square_unstructured_2.inp") +isfile(mesh_file) || + download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + mesh_file) + +# INP mesh files are only support by p4est. Hence, we +# create a p4est connecvity object first from which +# we can create a t8code mesh. +conn = Trixi.read_inp_p4est(mesh_file, Val(2)) + +mesh = T8codeMesh{2}(conn, polydeg = 3, + mapping = mapping_flag, + initial_refinement_level = 2) + +# A semidiscretization collects data structures and functions for the spatial discretization. +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +# Create ODE problem with time span from 0.0 to 0.2. +tspan = (0.0, 0.2) +ode = semidiscretize(semi, tspan) + +# At the beginning of the main loop, the SummaryCallback prints a summary of +# the simulation setup and resets the timers. +summary_callback = SummaryCallback() + +# The AnalysisCallback allows to analyse the solution in regular intervals and +# prints the results. +analysis_callback = AnalysisCallback(semi, interval = 100) + +# The StepsizeCallback handles the re-calculation of the maximum Δt after each +# time step. +stepsize_callback = StepsizeCallback(cfl = 1.4) + +# Create a CallbackSet to collect all callbacks such that they can be passed to +# the ODE solver. +callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) + +############################################################################### +# Run the simulation. + +# OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks. +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # Solve needs some value here but it will be overwritten by the stepsize_callback. + save_everystep = false, callback = callbacks); + +# Print the timer summary. +summary_callback() diff --git a/examples/t8code_2d_dgsem/elixir_euler_free_stream.jl b/examples/t8code_2d_dgsem/elixir_euler_free_stream.jl new file mode 100644 index 00000000000..01e0449c67e --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_euler_free_stream.jl @@ -0,0 +1,122 @@ +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the compressible Euler equations. + +equations = CompressibleEulerEquations2D(1.4) + +initial_condition = initial_condition_constant + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +# Mapping as described in https://arxiv.org/abs/2012.12040 but reduced to 2D +function mapping(xi_, eta_) + # Transform input variables between -1 and 1 onto [0,3] + xi = 1.5 * xi_ + 1.5 + eta = 1.5 * eta_ + 1.5 + + y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3)) + + x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3)) + + return SVector(x, y) +end + +############################################################################### +# Get the uncurved mesh from a file (downloads the file if not available locally) + +# Unstructured mesh with 48 cells of the square domain [-1, 1]^n +mesh_file = joinpath(@__DIR__, "square_unstructured_1.inp") +isfile(mesh_file) || + download("https://gist.githubusercontent.com/efaulhaber/a075f8ec39a67fa9fad8f6f84342cbca/raw/a7206a02ed3a5d3cadacd8d9694ac154f9151db7/square_unstructured_1.inp", + mesh_file) + +# INP mesh files are only support by p4est. Hence, we +# create a p4est connecvity object first from which +# we can create a t8code mesh. +conn = Trixi.read_inp_p4est(mesh_file, Val(2)) + +mesh = T8codeMesh{2}(conn, polydeg = 3, + mapping = mapping, + initial_refinement_level = 1) + +function adapt_callback(forest, + forest_from, + which_tree, + lelement_id, + ts, + is_family, + num_elements, + elements_ptr)::Cint + vertex = Vector{Cdouble}(undef, 3) + + elements = unsafe_wrap(Array, elements_ptr, num_elements) + + Trixi.t8_element_vertex_reference_coords(ts, elements[1], 0, pointer(vertex)) + + level = Trixi.t8_element_level(ts, elements[1]) + + # TODO: Make this condition more general. + if vertex[1] < 1e-8 && vertex[2] < 1e-8 && level < 3 + # return true (refine) + return 1 + else + # return false (don't refine) + return 0 + end +end + +Trixi.@T8_ASSERT(Trixi.t8_forest_is_committed(mesh.forest)!=0); + +# Init new forest. +new_forest_ref = Ref{Trixi.t8_forest_t}() +Trixi.t8_forest_init(new_forest_ref); +new_forest = new_forest_ref[] + +# Check out `examples/t8_step4_partition_balance_ghost.jl` in +# https://github.com/DLR-AMR/T8code.jl for detailed explanations. +let set_from = C_NULL, recursive = 1, set_for_coarsening = 0, no_repartition = 0 + Trixi.t8_forest_set_user_data(new_forest, C_NULL) + Trixi.t8_forest_set_adapt(new_forest, mesh.forest, + Trixi.@t8_adapt_callback(adapt_callback), recursive) + Trixi.t8_forest_set_balance(new_forest, set_from, no_repartition) + Trixi.t8_forest_set_partition(new_forest, set_from, set_for_coarsening) + Trixi.t8_forest_set_ghost(new_forest, 1, Trixi.T8_GHOST_FACES) + Trixi.t8_forest_commit(new_forest) +end + +mesh.forest = new_forest + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition))) + +############################################################################### +# 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) + +stepsize_callback = StepsizeCallback(cfl = 2.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_euler_sedov.jl b/examples/t8code_2d_dgsem/elixir_euler_sedov.jl new file mode 100644 index 00000000000..965d794f8dc --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_euler_sedov.jl @@ -0,0 +1,97 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the compressible Euler equations. + +equations = CompressibleEulerEquations2D(1.4) + +""" + initial_condition_sedov_blast_wave(x, t, equations::CompressibleEulerEquations2D) + +The Sedov blast wave setup based on Flash +- http://flash.uchicago.edu/site/flashcode/user_support/flash_ug_devel/node184.html#SECTION010114000000000000000 +""" +function initial_condition_sedov_blast_wave(x, t, equations::CompressibleEulerEquations2D) + # Set up polar coordinates + inicenter = SVector(0.0, 0.0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + + # Setup based on http://flash.uchicago.edu/site/flashcode/user_support/flash_ug_devel/node184.html#SECTION010114000000000000000 + r0 = 0.21875 # = 3.5 * smallest dx (for domain length=4 and max-ref=6) + E = 1.0 + p0_inner = 3 * (equations.gamma - 1) * E / (3 * pi * r0^2) + p0_outer = 1.0e-5 # = true Sedov setup + + # Calculate primitive variables + rho = 1.0 + v1 = 0.0 + v2 = 0.0 + p = r > r0 ? p0_outer : p0_inner + + return prim2cons(SVector(rho, v1, v2, p), equations) +end + +initial_condition = initial_condition_sedov_blast_wave + +# Get the DG approximation space +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha +polydeg = 4 +basis = LobattoLegendreBasis(polydeg) +indicator_sc = IndicatorHennemannGassner(equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) + +solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral) + +############################################################################### + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) + +mapping = Trixi.coordinates2mapping(coordinates_min, coordinates_max) + +trees_per_dimension = (4, 4) + +mesh = T8codeMesh(trees_per_dimension, polydeg = 4, + mapping = mapping, + initial_refinement_level = 2, periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 12.5) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 300 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_euler_shockcapturing_ec.jl b/examples/t8code_2d_dgsem/elixir_euler_shockcapturing_ec.jl new file mode 100644 index 00000000000..55a9063a001 --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_euler_shockcapturing_ec.jl @@ -0,0 +1,68 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the compressible Euler equations. + +equations = CompressibleEulerEquations2D(1.4) + +initial_condition = initial_condition_weak_blast_wave + +surface_flux = flux_ranocha +volume_flux = flux_ranocha +polydeg = 4 +basis = LobattoLegendreBasis(polydeg) +indicator_sc = IndicatorHennemannGassner(equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) + +solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral) + +############################################################################### + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) + +mapping = Trixi.coordinates2mapping(coordinates_min, coordinates_max) + +trees_per_dimension = (4, 4) + +mesh = T8codeMesh(trees_per_dimension, polydeg = 4, + mapping = mapping, + initial_refinement_level = 2, periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# 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) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl b/examples/t8code_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl new file mode 100644 index 00000000000..21f26d79ba8 --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl @@ -0,0 +1,122 @@ +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +initial_condition = initial_condition_convergence_test + +source_terms = source_terms_convergence_test + +# BCs must be passed as Dict +boundary_condition = BoundaryConditionDirichlet(initial_condition) +boundary_conditions = Dict(:all => boundary_condition) + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +# Deformed rectangle that looks like a waving flag, +# lower and upper faces are sinus curves, left and right are vertical lines. +f1(s) = SVector(-1.0, s - 1.0) +f2(s) = SVector(1.0, s + 1.0) +f3(s) = SVector(s, -1.0 + sin(0.5 * pi * s)) +f4(s) = SVector(s, 1.0 + sin(0.5 * pi * s)) +faces = (f1, f2, f3, f4) + +Trixi.validate_faces(faces) +mapping_flag = Trixi.transfinite_mapping(faces) + +# Get the uncurved mesh from a file (downloads the file if not available locally) +# Unstructured mesh with 24 cells of the square domain [-1, 1]^n +mesh_file = joinpath(@__DIR__, "square_unstructured_2.inp") +isfile(mesh_file) || + download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + mesh_file) + +# INP mesh files are only support by p4est. Hence, we +# create a p4est connecvity object first from which +# we can create a t8code mesh. +conn = Trixi.read_inp_p4est(mesh_file, Val(2)) + +mesh = T8codeMesh{2}(conn, polydeg = 3, + mapping = mapping_flag, + initial_refinement_level = 1) + +function adapt_callback(forest, + forest_from, + which_tree, + lelement_id, + ts, + is_family, + num_elements, + elements_ptr)::Cint + vertex = Vector{Cdouble}(undef, 3) + + elements = unsafe_wrap(Array, elements_ptr, num_elements) + + Trixi.t8_element_vertex_reference_coords(ts, elements[1], 0, pointer(vertex)) + + level = Trixi.t8_element_level(ts, elements[1]) + + # TODO: Make this condition more general. + if vertex[1] < 1e-8 && vertex[2] < 1e-8 && level < 2 + # return true (refine) + return 1 + else + # return false (don't refine) + return 0 + end +end + +@assert(Trixi.t8_forest_is_committed(mesh.forest)!=0); + +# Init new forest. +new_forest_ref = Ref{Trixi.t8_forest_t}() +Trixi.t8_forest_init(new_forest_ref); +new_forest = new_forest_ref[] + +# Check out `examples/t8_step4_partition_balance_ghost.jl` in +# https://github.com/DLR-AMR/T8code.jl for detailed explanations. +let set_from = C_NULL, recursive = 1, set_for_coarsening = 0, no_repartition = 0 + Trixi.t8_forest_set_user_data(new_forest, C_NULL) + Trixi.t8_forest_set_adapt(new_forest, mesh.forest, + Trixi.@t8_adapt_callback(adapt_callback), recursive) + Trixi.t8_forest_set_balance(new_forest, set_from, no_repartition) + Trixi.t8_forest_set_partition(new_forest, set_from, set_for_coarsening) + Trixi.t8_forest_set_ghost(new_forest, 1, Trixi.T8_GHOST_FACES) + Trixi.t8_forest_commit(new_forest) +end + +mesh.forest = new_forest + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms, + boundary_conditions = boundary_conditions) + +############################################################################### +# 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) + +stepsize_callback = StepsizeCallback(cfl = 0.8) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + stepsize_callback) +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_eulergravity_convergence.jl b/examples/t8code_2d_dgsem/elixir_eulergravity_convergence.jl new file mode 100644 index 00000000000..32649eacff4 --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_eulergravity_convergence.jl @@ -0,0 +1,77 @@ +using OrdinaryDiffEq +using Trixi + +initial_condition = initial_condition_eoc_test_coupled_euler_gravity + +############################################################################### +# semidiscretization of the compressible Euler equations +gamma = 2.0 +equations_euler = CompressibleEulerEquations2D(gamma) + +polydeg = 3 +solver_euler = DGSEM(polydeg, flux_hll) + +coordinates_min = (0.0, 0.0) +coordinates_max = (2.0, 2.0) + +trees_per_dimension = (1, 1) + +mapping = Trixi.coordinates2mapping(coordinates_min, coordinates_max) + +mesh = T8codeMesh(trees_per_dimension, polydeg = 1, + mapping = mapping, + initial_refinement_level = 2) + +semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition, solver_euler, + source_terms=source_terms_eoc_test_coupled_euler_gravity) + + +############################################################################### +# semidiscretization of the hyperbolic diffusion equations +equations_gravity = HyperbolicDiffusionEquations2D() + +solver_gravity = DGSEM(polydeg, flux_lax_friedrichs) + +semi_gravity = SemidiscretizationHyperbolic(mesh, equations_gravity, initial_condition, solver_gravity, + source_terms=source_terms_harmonic) + + +############################################################################### +# combining both semidiscretizations for Euler + self-gravity +parameters = ParametersEulerGravity(background_density=2.0, # aka rho0 + # rho0 is (ab)used to add a "+8π" term to the source terms + # for the manufactured solution + gravitational_constant=1.0, # aka G + cfl=1.1, + resid_tol=1.0e-10, + n_iterations_max=1000, + timestep_gravity=timestep_gravity_erk52_3Sstar!) + +semi = SemidiscretizationEulerGravity(semi_euler, semi_gravity, parameters) + + +############################################################################### +# ODE solvers, callbacks etc. +tspan = (0.0, 0.5) +ode = semidiscretize(semi, tspan); + +summary_callback = SummaryCallback() + +stepsize_callback = StepsizeCallback(cfl=0.8) + +analysis_interval = 100 +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +analysis_callback = AnalysisCallback(semi_euler, interval=analysis_interval, + save_analysis=true) + +callbacks = CallbackSet(summary_callback, stepsize_callback, + analysis_callback, alive_callback) + +############################################################################### +# run the simulation +sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); +summary_callback() # print the timer summary +println("Number of gravity subcycles: ", semi.gravity_counter.ncalls_since_readout) diff --git a/examples/t8code_2d_dgsem/elixir_mhd_alfven_wave.jl b/examples/t8code_2d_dgsem/elixir_mhd_alfven_wave.jl new file mode 100644 index 00000000000..463f916fa2e --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_mhd_alfven_wave.jl @@ -0,0 +1,60 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the compressible ideal GLM-MHD equations. + +gamma = 5/3 +equations = IdealGlmMhdEquations2D(gamma) + +initial_condition = initial_condition_convergence_test + +# Get the DG approximation space +volume_flux = (flux_central, flux_nonconservative_powell) +solver = DGSEM(polydeg=4, surface_flux=(flux_hll, flux_nonconservative_powell), + volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) + +coordinates_min = (0.0 , 0.0 ) +coordinates_max = (sqrt(2.0), sqrt(2.0)) + +mapping = Trixi.coordinates2mapping(coordinates_min, coordinates_max) + +trees_per_dimension = (8, 8) + +mesh = T8codeMesh(trees_per_dimension, polydeg=3, + mapping=mapping, + initial_refinement_level=0, periodicity=true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# 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) + +cfl = 0.9 +stepsize_callback = StepsizeCallback(cfl=cfl) + +glm_speed_callback = GlmSpeedCallback(glm_scale=0.5, cfl=cfl) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + stepsize_callback, + glm_speed_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); +summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_mhd_rotor.jl b/examples/t8code_2d_dgsem/elixir_mhd_rotor.jl new file mode 100644 index 00000000000..9a4bd99e444 --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_mhd_rotor.jl @@ -0,0 +1,134 @@ +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible ideal GLM-MHD equations +equations = IdealGlmMhdEquations2D(1.4) + +""" + initial_condition_rotor(x, t, equations::IdealGlmMhdEquations2D) + +The classical MHD rotor test case. Here, the setup is taken from +- Dominik Derigs, Gregor J. Gassner, Stefanie Walch & Andrew R. Winters (2018) + Entropy Stable Finite Volume Approximations for Ideal Magnetohydrodynamics + [doi: 10.1365/s13291-018-0178-9](https://doi.org/10.1365/s13291-018-0178-9) +""" +function initial_condition_rotor(x, t, equations::IdealGlmMhdEquations2D) + # setup taken from Derigs et al. DMV article (2018) + # domain must be [0, 1] x [0, 1], γ = 1.4 + dx = x[1] - 0.5 + dy = x[2] - 0.5 + r = sqrt(dx^2 + dy^2) + f = (0.115 - r) / 0.015 + if r <= 0.1 + rho = 10.0 + v1 = -20.0 * dy + v2 = 20.0 * dx + elseif r >= 0.115 + rho = 1.0 + v1 = 0.0 + v2 = 0.0 + else + rho = 1.0 + 9.0 * f + v1 = -20.0 * f * dy + v2 = 20.0 * f * dx + end + v3 = 0.0 + p = 1.0 + B1 = 5.0 / sqrt(4.0 * pi) + B2 = 0.0 + B3 = 0.0 + psi = 0.0 + return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, psi), equations) +end +initial_condition = initial_condition_rotor + +surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) +volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) +polydeg = 4 +basis = LobattoLegendreBasis(polydeg) +indicator_sc = IndicatorHennemannGassner(equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +solver = DGSEM(basis, surface_flux, volume_integral) + +# Affine type mapping to take the [-1,1]^2 domain from the mesh file +# and put it onto the rotor domain [0,1]^2 and then warp it with a mapping +# as described in https://arxiv.org/abs/2012.12040 +function mapping_twist(xi, eta) + y = 0.5 * (eta + 1.0) + + 0.05 * cos(1.5 * pi * (2.0 * xi - 1.0)) * cos(0.5 * pi * (2.0 * eta - 1.0)) + x = 0.5 * (xi + 1.0) + 0.05 * cos(0.5 * pi * (2.0 * xi - 1.0)) * cos(2.0 * pi * y) + return SVector(x, y) +end + +mesh_file = joinpath(@__DIR__, "square_unstructured_2.inp") +isfile(mesh_file) || + download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + mesh_file) + +# INP mesh files are only support by p4est. Hence, we +# create a p4est connecvity object first from which +# we can create a t8code mesh. +conn = Trixi.read_inp_p4est(mesh_file, Val(2)) + +mesh = T8codeMesh{2}(conn, polydeg = 4, + mapping = mapping_twist, + initial_refinement_level = 1) + +boundary_condition = BoundaryConditionDirichlet(initial_condition) +boundary_conditions = Dict(:all => boundary_condition) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 0.15) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +amr_indicator = IndicatorLöhner(semi, + variable = density_pressure) + +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = 1, + med_level = 3, med_threshold = 0.05, + max_level = 5, max_threshold = 0.1) +amr_callback = AMRCallback(semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true) + +cfl = 0.5 +stepsize_callback = StepsizeCallback(cfl = cfl) + +glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback, + glm_speed_callback) + +############################################################################### +# 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/t8code_2d_dgsem/elixir_shallowwater_source_terms.jl b/examples/t8code_2d_dgsem/elixir_shallowwater_source_terms.jl new file mode 100644 index 00000000000..c19f440ebc7 --- /dev/null +++ b/examples/t8code_2d_dgsem/elixir_shallowwater_source_terms.jl @@ -0,0 +1,60 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the shallow water equations. + +equations = ShallowWaterEquations2D(gravity_constant=9.81) + +initial_condition = initial_condition_convergence_test # MMS EOC test + + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +solver = DGSEM(polydeg=3, surface_flux=(flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), + volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) + +############################################################################### +# Get the P4estMesh and setup a periodic mesh + +coordinates_min = (0.0, 0.0) # minimum coordinates (min(x), min(y)) +coordinates_max = (sqrt(2.0), sqrt(2.0)) # maximum coordinates (max(x), max(y)) + +mapping = Trixi.coordinates2mapping(coordinates_min, coordinates_max) + +trees_per_dimension = (8, 8) + +mesh = T8codeMesh(trees_per_dimension, polydeg=3, + mapping=mapping, + initial_refinement_level=1) + +# A semidiscretization collects data structures and functions for the spatial discretization +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms=source_terms_convergence_test) + + +############################################################################### +# ODE solvers, callbacks etc. + +# Create ODE problem with time span from 0.0 to 1.0 +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 500 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) + +############################################################################### +# run the simulation + +# use a Runge-Kutta method with automatic (error based) time step size control +sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-8, reltol=1.0e-8, + ode_default_options()..., callback=callbacks); +summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_beach.jl b/examples/tree_1d_dgsem/elixir_shallowwater_beach.jl new file mode 100644 index 00000000000..1288bc5e66a --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_shallowwater_beach.jl @@ -0,0 +1,121 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + +equations = ShallowWaterEquations1D(gravity_constant=9.812) + +""" + initial_condition_beach(x, t, equations:: ShallowWaterEquations1D) +Initial condition to simulate a wave running towards a beach and crashing. Difficult test +including both wetting and drying in the domain using slip wall boundary conditions. +The bottom topography is altered to be differentiable on the domain [0,8] and +differs from the reference below. + +The water height and speed functions used here, are adapted from the initial condition +found in section 5.2 of the paper: + - Andreas Bollermann, Sebastian Noelle, Maria Lukáčová-Medvid’ová (2011) + Finite volume evolution Galerkin methods for the shallow water equations with dry beds\n + [DOI: 10.4208/cicp.220210.020710a](https://dx.doi.org/10.4208/cicp.220210.020710a) +""" +function initial_condition_beach(x, t, equations:: ShallowWaterEquations1D) + D = 1 + delta = 0.02 + gamma = sqrt((3 * delta) / (4 * D)) + x_a = sqrt((4 * D) / (3 * delta)) * acosh(sqrt(20)) + + f = D + 40 * delta * sech(gamma * (8 * x[1] - x_a))^2 + + # steep curved beach + b = 0.01 + 99 / 409600 * 4^x[1] + + if x[1] >= 6 + H = b + v = 0.0 + else + H = f + v = sqrt(equations.gravity / D) * H + end + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v, b), equations) +end + +initial_condition = initial_condition_beach +boundary_condition = boundary_condition_slip_wall + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(3) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +############################################################################### +# Create the TreeMesh for the domain [0, 8] + +coordinates_min = 0.0 +coordinates_max = 8.0 + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level=7, + n_cells_max=10_000, + periodicity=false) + +# create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions=boundary_condition) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 10.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=false, + extra_analysis_integrals=(energy_kinetic, + energy_internal)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +save_solution = SaveSolutionCallback(dt=0.5, + save_initial_solution=true, + save_final_solution=true) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +############################################################################### +# run the simulation + +sol = solve(ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback=callbacks); + +summary_callback() # print the timer summary \ No newline at end of file diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_parabolic_bowl.jl b/examples/tree_1d_dgsem/elixir_shallowwater_parabolic_bowl.jl new file mode 100644 index 00000000000..916bba76ece --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_shallowwater_parabolic_bowl.jl @@ -0,0 +1,117 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + +equations = ShallowWaterEquations1D(gravity_constant=9.81) + +""" + initial_condition_parabolic_bowl(x, t, equations:: ShallowWaterEquations1D) + +Well-known initial condition to test the [`hydrostatic_reconstruction_chen_noelle`](@ref) and its +wet-dry mechanics. This test has analytical solutions. The initial condition is defined by the +analytical solution at time t=0. The bottom topography defines a bowl and the water level is given +by an oscillating lake. + +The original test and its analytical solution in two dimensions were first presented in +- William C. Thacker (1981) + Some exact solutions to the nonlinear shallow-water wave equations + [DOI: 10.1017/S0022112081001882](https://doi.org/10.1017/S0022112081001882). + +The particular setup below is taken from Section 6.2 of +- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and Timothy Warburton (2018) + An entropy stable discontinuous Galerkin method for the shallow water equations on + curvilinear meshes with wet/dry fronts accelerated by GPUs + [DOI: 10.1016/j.jcp.2018.08.038](https://doi.org/10.1016/j.jcp.2018.08.038). +""" +function initial_condition_parabolic_bowl(x, t, equations:: ShallowWaterEquations1D) + a = 1 + h_0 = 0.1 + sigma = 0.5 + ω = sqrt(2 * equations.gravity * h_0) / a + + v = -sigma * ω * sin(ω * t) + + b = h_0 * x[1]^2 / a^2 + + H = sigma * h_0 / a^2 * (2 * x[1] * cos(ω * t) - sigma) + h_0 + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v, b), equations) +end + +initial_condition = initial_condition_parabolic_bowl + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(5) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +############################################################################### +# Create the TreeMesh for the domain [-2, 2] + +coordinates_min = -2.0 +coordinates_max = 2.0 + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level=6, + n_cells_max=10_000) + +# create the semi discretization object +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 = 1000 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=false, + extra_analysis_integrals=(energy_kinetic, + energy_internal)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +save_solution = SaveSolutionCallback(interval=1000, + save_initial_solution=true, + save_final_solution=true) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +############################################################################### +# run the simulation + +sol = solve(ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback=callbacks); + +summary_callback() # print the timer summary \ No newline at end of file diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl b/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl new file mode 100644 index 00000000000..8de46c61794 --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl @@ -0,0 +1,165 @@ + +using OrdinaryDiffEq +using Trixi +using Printf: @printf, @sprintf + +############################################################################### +# Semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + +equations = ShallowWaterEquations1D(gravity_constant=9.812) + +""" + initial_condition_complex_bottom_well_balanced(x, t, equations:: ShallowWaterEquations1D) + +Initial condition with a complex (discontinuous) bottom topography to test the well-balanced +property for the [`hydrostatic_reconstruction_chen_noelle`](@ref) including dry areas within the +domain. The errors from the analysis callback are not important but the error for this +lake-at-rest test case `∑|H0-(h+b)|` should be around machine roundoff. + +The initial condition is taken from Section 5.2 of the paper: +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +function initial_condition_complex_bottom_well_balanced(x, t, equations:: ShallowWaterEquations1D) + v = 0.0 + b = sin(4 * pi * x[1]) + 3 + + if x[1] >= 0.5 + b = sin(4 * pi * x[1]) + 1 + end + + H = max(b, 2.5) + + if x[1] >= 0.5 + H = max(b, 1.5) + end + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v, b), equations) +end + +initial_condition = initial_condition_complex_bottom_well_balanced + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(3) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +############################################################################### +# Create the TreeMesh for the domain [0, 1] + +coordinates_min = 0.0 +coordinates_max = 1.0 + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level=6, + n_cells_max=10_000) + +# create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 25.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 5000 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=false) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +save_solution = SaveSolutionCallback(interval=5000, + save_initial_solution=true, + save_final_solution=true) + +stepsize_callback = StepsizeCallback(cfl=1.5) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback) + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +############################################################################### +# run the simulation + +sol = solve(ode, SSPRK43(stage_limiter!); dt=1.0, + ode_default_options()..., callback=callbacks, adaptive=false); + +summary_callback() # print the timer summary + +############################################################################### +# Workaround to compute the well-balancedness error for this particular problem +# that has two reference water heights. One for a lake to the left of the +# discontinuous bottom topography `H0_upper = 2.5` and another for a lake to the +# right of the discontinuous bottom topography `H0_lower = 1.5`. + +# Declare a special version of the function to compute the lake-at-rest error +# OBS! The reference water height values are hardcoded for convenience. +function lake_at_rest_error_two_level(u, x, equations::ShallowWaterEquations1D) + h, _, b = u + + # For well-balancedness testing with possible wet/dry regions the reference + # water height `H0` accounts for the possibility that the bottom topography + # can emerge out of the water as well as for the threshold offset to avoid + # division by a "hard" zero water heights as well. + if x[1] < 0.5 + H0_wet_dry = max( 2.5 , b + equations.threshold_limiter ) + else + H0_wet_dry = max( 1.5 , b + equations.threshold_limiter ) + end + + return abs(H0_wet_dry - (h + b)) + end + +# point to the data we want to analyze +u = Trixi.wrap_array(sol[end], semi) +# Perform the actual integration of the well-balancedness error over the domain +l1_well_balance_error = Trixi.integrate_via_indices(u, mesh, equations, semi.solver, semi.cache; normalize=true) do u, i, element, equations, solver + x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, solver, i, element) + # We know that the discontinuity is a vertical line. Slightly augment the x value by a factor + # of unit roundoff to avoid the repeted value from the LGL nodes at at interface. + if i == 1 + x_node = SVector(nextfloat(x_node[1])) + elseif i == nnodes(semi.solver) + x_node = SVector(prevfloat(x_node[1])) + end + u_local = Trixi.get_node_vars(u, equations, solver, i, element) + return lake_at_rest_error_two_level(u_local, x_node, equations) +end + +# report the well-balancedness lake-at-rest error to the screen +println("─"^100) +println(" Lake-at-rest error for '", Trixi.get_name(equations), "' with ", summary(solver), + " at final time " * @sprintf("%10.8e", tspan[end])) + +@printf(" %-12s:", Trixi.pretty_form_utf(lake_at_rest_error)) +@printf(" % 10.8e", l1_well_balance_error) +println() +println("─"^100) \ No newline at end of file diff --git a/examples/tree_1d_fdsbp/elixir_advection_upwind.jl b/examples/tree_1d_fdsbp/elixir_advection_upwind.jl index 5c50e1a6c64..1f2498e0866 100644 --- a/examples/tree_1d_fdsbp/elixir_advection_upwind.jl +++ b/examples/tree_1d_fdsbp/elixir_advection_upwind.jl @@ -5,7 +5,7 @@ using OrdinaryDiffEq using Trixi ############################################################################### -# semidiscretization of the linear scalar advection equation equation +# semidiscretization of the linear scalar advection equation equations = LinearScalarAdvectionEquation1D(1.0) @@ -27,7 +27,8 @@ coordinates_min = -1.0 coordinates_max = 1.0 mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level = 4, - n_cells_max = 10_000) + n_cells_max = 10_000, + periodicity = true) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sin, solver) diff --git a/examples/tree_1d_fdsbp/elixir_advection_upwind_periodic.jl b/examples/tree_1d_fdsbp/elixir_advection_upwind_periodic.jl new file mode 100644 index 00000000000..035d3568a80 --- /dev/null +++ b/examples/tree_1d_fdsbp/elixir_advection_upwind_periodic.jl @@ -0,0 +1,57 @@ +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linear scalar advection equation + +equations = LinearScalarAdvectionEquation1D(1.0) + +function initial_condition_sin(x, t, equation::LinearScalarAdvectionEquation1D) + return SVector(sinpi(x[1] - equations.advection_velocity[1] * t)) +end + +D_upw = upwind_operators(SummationByPartsOperators.periodic_derivative_operator, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 64) +flux_splitting = splitting_lax_friedrichs +solver = FDSBP(D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting)) + +coordinates_min = -1.0 +coordinates_max = 1.0 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 0, + n_cells_max = 10_000, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sin, 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) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback) + + +############################################################################### +# run the simulation + +sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, + ode_default_options()..., callback=callbacks); +summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_diffusion.jl b/examples/tree_2d_dgsem/elixir_advection_diffusion.jl index e96e1b5a171..a716bd278b8 100644 --- a/examples/tree_2d_dgsem/elixir_advection_diffusion.jl +++ b/examples/tree_2d_dgsem/elixir_advection_diffusion.jl @@ -75,8 +75,9 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks +alg = RDPK3SpFSAL49() time_int_tol = 1.0e-11 -sol = solve(ode, RDPK3SpFSAL49(); abstol=time_int_tol, reltol=time_int_tol, +sol = solve(ode, alg; abstol=time_int_tol, reltol=time_int_tol, ode_default_options()..., callback=callbacks) # Print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_restart.jl b/examples/tree_2d_dgsem/elixir_advection_restart.jl index 2cb45c0b47e..72efb7d0c84 100644 --- a/examples/tree_2d_dgsem/elixir_advection_restart.jl +++ b/examples/tree_2d_dgsem/elixir_advection_restart.jl @@ -20,13 +20,24 @@ mesh = load_mesh(restart_filename) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) tspan = (load_time(restart_filename), 2.0) +dt = load_dt(restart_filename) ode = semidiscretize(semi, tspan, restart_filename); +# Do not overwrite the initial snapshot written by elixir_advection_extended.jl. +save_solution.condition.save_initial_solution = false + +alg = CarpenterKennedy2N54(williamson_condition=false) +integrator = init(ode, alg, + dt=dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks) + +# Get the last time index and work with that. +integrator.iter = load_timestep(restart_filename) +integrator.stats.naccept = integrator.iter ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve!(integrator) + summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_linearizedeuler_gauss_wall.jl b/examples/tree_2d_dgsem/elixir_linearizedeuler_gauss_wall.jl new file mode 100644 index 00000000000..14fe201a291 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_linearizedeuler_gauss_wall.jl @@ -0,0 +1,68 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linearized Euler equations + +equations = LinearizedEulerEquations2D(v_mean_global=(0.5, 0.0), c_mean_global=1.0, + rho_mean_global=1.0) + +# Create DG solver with polynomial degree = 5 and upwind flux as surface flux +solver = DGSEM(polydeg=5, surface_flux=flux_godunov) + +coordinates_min = (-100.0, 0.0) # minimum coordinates (min(x), min(y)) +coordinates_max = (100.0, 200.0) # maximum coordinates (max(x), max(y)) + +# Create a uniformly refined mesh with periodic boundaries +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level=4, + n_cells_max=100_000, + periodicity=false) + +function initial_condition_gauss_wall(x, t, equations::LinearizedEulerEquations2D) + v1_prime = 0.0 + v2_prime = 0.0 + rho_prime = p_prime = exp(-log(2) * (x[1]^2 + (x[2] - 25)^2) / 25) + return SVector(rho_prime, v1_prime, v2_prime, p_prime) +end +initial_condition = initial_condition_gauss_wall + +# A semidiscretization collects data structures and functions for the spatial discretization +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions=boundary_condition_wall) + + +############################################################################### +# ODE solvers, callbacks etc. + +# Create ODE problem with time span from 0.0 to 30.0 +tspan = (0.0, 30.0) +ode = semidiscretize(semi, tspan) + +# At the beginning of the main loop, the SummaryCallback prints a summary of the simulation setup +# and resets the timers +summary_callback = SummaryCallback() + +# The AnalysisCallback allows to analyse the solution in regular intervals and prints the results +analysis_callback = AnalysisCallback(semi, interval=100) + +# The SaveSolutionCallback allows to save the solution to a file in regular intervals +save_solution = SaveSolutionCallback(interval=100) + +# The StepsizeCallback handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl=0.7) + +# Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, stepsize_callback) + +############################################################################### +# run the simulation + +# OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks +sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks) + +# Print the timer summary +summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_navierstokes_convergence.jl b/examples/tree_2d_dgsem/elixir_navierstokes_convergence.jl index 36a9f52e39d..b68e9e6c97e 100644 --- a/examples/tree_2d_dgsem/elixir_navierstokes_convergence.jl +++ b/examples/tree_2d_dgsem/elixir_navierstokes_convergence.jl @@ -170,7 +170,10 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_top_bottom = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, t, equations)[2:3]) +velocity_bc_top_bottom = NoSlip() do x, t, equations + u = initial_condition_navier_stokes_convergence_test(x, t, equations) + return SVector(u[2], u[3]) +end heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, heat_bc_top_bottom) diff --git a/examples/tree_2d_dgsem/elixir_navierstokes_shear_layer.jl b/examples/tree_2d_dgsem/elixir_navierstokes_shear_layer.jl new file mode 100644 index 00000000000..dd26fd8097b --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_navierstokes_shear_layer.jl @@ -0,0 +1,73 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Navier-Stokes equations + +# TODO: parabolic; unify names of these accessor functions +prandtl_number() = 0.72 +mu() = 1.0/3.0 * 10^(-3) # equivalent to Re = 3000 + +equations = CompressibleEulerEquations2D(1.4) +equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu=mu(), + Prandtl=prandtl_number()) + +function initial_condition_shear_layer(x, t, equations::CompressibleEulerEquations2D) + # Shear layer parameters + k = 80 + delta = 0.05 + u0 = 1.0 + + Ms = 0.1 # maximum Mach number + + rho = 1.0 + v1 = x[2] <= 0.5 ? u0 * tanh(k*(x[2]*0.5 - 0.25)) : u0 * tanh(k*(0.75 -x[2]*0.5)) + v2 = u0 * delta * sin(2*pi*(x[1]*0.5 + 0.25)) + p = (u0 / Ms)^2 * rho / equations.gamma # scaling to get Ms + + return prim2cons(SVector(rho, v1, v2, p), equations) +end +initial_condition = initial_condition_shear_layer + +volume_flux = flux_ranocha +solver = DGSEM(polydeg=3, surface_flux=flux_hllc, + volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) + +coordinates_min = (0.0, 0.0) +coordinates_max = (1.0, 1.0) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level=4, + n_cells_max=100_000) + + +semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), + initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 50 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=true, + extra_analysis_integrals=(energy_kinetic, + energy_internal, + enstrophy)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval,) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback) + +############################################################################### +# run the simulation + +time_int_tol = 1e-8 +sol = solve(ode, RDPK3SpFSAL49(); abstol=time_int_tol, reltol=time_int_tol, + ode_default_options()..., callback=callbacks) +summary_callback() # print the timer summary \ No newline at end of file diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_conical_island.jl b/examples/tree_2d_dgsem/elixir_shallowwater_conical_island.jl new file mode 100644 index 00000000000..7c60e35b03e --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_shallowwater_conical_island.jl @@ -0,0 +1,116 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + +equations = ShallowWaterEquations2D(gravity_constant=9.81, H0=1.4) + +""" + initial_condition_conical_island(x, t, equations::ShallowWaterEquations2D) + +Initial condition for the [`ShallowWaterEquations2D`](@ref) to test the [`hydrostatic_reconstruction_chen_noelle`](@ref) +and its handling of discontinuous water heights at the start in combination with wetting and +drying. The bottom topography is given by a conical island in the middle of the domain. Around that +island, there is a cylindrical water column at t=0 and the rest of the domain is dry. This +discontinuous water height is smoothed by a logistic function. This simulation uses a Dirichlet +boundary condition with the initial values. Due to the dry cells at the boundary, this has the +effect of an outflow which can be seen in the simulation. +""" +function initial_condition_conical_island(x, t, equations::ShallowWaterEquations2D) + # Set the background values + + v1 = 0.0 + v2 = 0.0 + + x1, x2 = x + b = max(0.1, 1.0 - 4.0 * sqrt(x1^2 + x2^2)) + + # use a logistic function to transfer water height value smoothly + L = equations.H0 # maximum of function + x0 = 0.3 # center point of function + k = -25.0 # sharpness of transfer + + H = max(b, L/(1.0 + exp(-k*(sqrt(x1^2+x2^2) - x0)))) + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v1) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v1, v2, b), equations) +end + +initial_condition = initial_condition_conical_island +boundary_conditions = BoundaryConditionDirichlet(initial_condition) + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(4) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +############################################################################### +# Get the TreeMesh and setup a mesh + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level=4, + n_cells_max=10_000, + periodicity=false) + +# Create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions=boundary_conditions) + +############################################################################### +# ODE solver + +tspan = (0.0, 10.0) +ode = semidiscretize(semi, tspan) + +############################################################################### +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +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) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +############################################################################### +# run the simulation + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +sol = solve(ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback=callbacks); + +summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_parabolic_bowl.jl b/examples/tree_2d_dgsem/elixir_shallowwater_parabolic_bowl.jl new file mode 100644 index 00000000000..03dcf017266 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_shallowwater_parabolic_bowl.jl @@ -0,0 +1,120 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + +equations = ShallowWaterEquations2D(gravity_constant=9.81) + +""" + initial_condition_parabolic_bowl(x, t, equations:: ShallowWaterEquations2D) + +Well-known initial condition to test the [`hydrostatic_reconstruction_chen_noelle`](@ref) and its +wet-dry mechanics. This test has an analytical solution. The initial condition is defined by the +analytical solution at time t=0. The bottom topography defines a bowl and the water level is given +by an oscillating lake. + +The original test and its analytical solution were first presented in +- William C. Thacker (1981) + Some exact solutions to the nonlinear shallow-water wave equations + [DOI: 10.1017/S0022112081001882](https://doi.org/10.1017/S0022112081001882). + +The particular setup below is taken from Section 6.2 of +- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and Timothy Warburton (2018) + An entropy stable discontinuous Galerkin method for the shallow water equations on + curvilinear meshes with wet/dry fronts accelerated by GPUs + [DOI: 10.1016/j.jcp.2018.08.038](https://doi.org/10.1016/j.jcp.2018.08.038). +""" +function initial_condition_parabolic_bowl(x, t, equations:: ShallowWaterEquations2D) + a = 1.0 + h_0 = 0.1 + sigma = 0.5 + ω = sqrt(2 * equations.gravity * h_0) / a + + v1 = -sigma * ω * sin(ω * t) + v2 = sigma * ω * cos(ω * t) + + b = h_0 * ((x[1])^2 + (x[2])^2) / a^2 + + H = sigma * h_0 / a^2 * (2 * x[1] * cos(ω * t) + 2 * x[2] * sin(ω * t) - sigma) + h_0 + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v1) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v1, v2, b), equations) +end + +initial_condition = initial_condition_parabolic_bowl +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(7) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.6, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + + +############################################################################### +# Create the TreeMesh for the domain [-2, 2]^2 + +coordinates_min = (-2.0, -2.0) +coordinates_max = (2.0, 2.0) + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level=5, + n_cells_max=10_000) + +# create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=false, + extra_analysis_integrals=(energy_kinetic, + energy_internal)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +save_solution = SaveSolutionCallback(interval=100, + save_initial_solution=true, + save_final_solution=true) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +############################################################################### +# run the simulation + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +sol = solve(ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback=callbacks); + +summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl new file mode 100644 index 00000000000..6fede2fa4ea --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wet_dry.jl @@ -0,0 +1,198 @@ + +using OrdinaryDiffEq +using Trixi +using Printf: @printf, @sprintf + +############################################################################### +# Semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + +equations = ShallowWaterEquations2D(gravity_constant=9.812) + +""" + initial_condition_well_balanced_chen_noelle(x, t, equations:: ShallowWaterEquations2D) + +Initial condition with a complex (discontinuous) bottom topography to test the well-balanced +property for the [`hydrostatic_reconstruction_chen_noelle`](@ref) including dry areas within the +domain. The errors from the analysis callback are not important but the error for this +lake-at-rest test case `∑|H0-(h+b)|` should be around machine roundoff. + +The initial condition is taken from Section 5.2 of the paper: +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +function initial_condition_complex_bottom_well_balanced(x, t, equations::ShallowWaterEquations2D) + v1 = 0 + v2 = 0 + b = sin(4 * pi * x[1]) + 3 + + if x[1] >= 0.5 + b = sin(4 * pi * x[1]) + 1 + end + + H = max(b, 2.5) + if x[1] >= 0.5 + H = max(b, 1.5) + end + + # It is mandatory to shift the water level at dry areas to make sure the water height h + # stays positive. The system would not be stable for h set to a hard 0 due to division by h in + # the computation of velocity, e.g., (h v1) / h. Therefore, a small dry state threshold + # with a default value of 500*eps() ≈ 1e-13 in double precision, is set in the constructor above + # for the ShallowWaterEquations and added to the initial condition if h = 0. + # This default value can be changed within the constructor call depending on the simulation setup. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v1, v2, b), equations) +end + +initial_condition = initial_condition_complex_bottom_well_balanced + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(3) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +############################################################################### +# Create the TreeMesh for the domain [0, 1]^2 + +coordinates_min = (0.0, 0.0) +coordinates_max = (1.0, 1.0) + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level=3, + n_cells_max=10_000) + +# create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 50.0) +ode = semidiscretize(semi, tspan) + +############################################################################### +# Workaround to set a discontinuous water and bottom topography for +# debugging and testing. Essentially, this is a slight augmentation of the +# `compute_coefficients` where the `x` node value passed here is slightly +# perturbed to the left / right in order to set a true discontinuity that avoids +# the doubled value of the LGL nodes at a particular element interface. +# +# Note! The errors from the analysis callback are not important but the error +# for this lake at rest test case `∑|H0-(h+b)|` should be near machine roundoff. + +# point to the data we want to augment +u = Trixi.wrap_array(ode.u0, semi) +# reset the initial condition +for element in eachelement(semi.solver, semi.cache) + for j in eachnode(semi.solver), i in eachnode(semi.solver) + x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, semi.solver, i, j, element) + # We know that the discontinuity is a vertical line. Slightly augment the x value by a factor + # of unit roundoff to avoid the repeted value from the LGL nodes at at interface. + if i == 1 + x_node = SVector(nextfloat(x_node[1]) , x_node[2]) + elseif i == nnodes(semi.solver) + x_node = SVector(prevfloat(x_node[1]) , x_node[2]) + end + u_node = initial_condition_complex_bottom_well_balanced(x_node, first(tspan), equations) + Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) + end +end + +############################################################################### +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=false) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +save_solution = SaveSolutionCallback(interval=1000, + save_initial_solution=true, + save_final_solution=true) + +stepsize_callback = StepsizeCallback(cfl=2.0) + + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback) + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +############################################################################### +# run the simulation + +sol = solve(ode, SSPRK43(stage_limiter!); dt=1.0, + ode_default_options()..., callback=callbacks, adaptive=false); + +summary_callback() # print the timer summary + +############################################################################### +# Workaround to compute the well-balancedness error for this particular problem +# that has two reference water heights. One for a lake to the left of the +# discontinuous bottom topography `H0_upper = 2.5` and another for a lake to the +# right of the discontinuous bottom topography `H0_lower = 1.5`. + +# Declare a special version of the function to compute the lake-at-rest error +# OBS! The reference water height values are hardcoded for convenience. +function lake_at_rest_error_two_level(u, x, equations::ShallowWaterEquations2D) + h, _, _, b = u + + # For well-balancedness testing with possible wet/dry regions the reference + # water height `H0` accounts for the possibility that the bottom topography + # can emerge out of the water as well as for the threshold offset to avoid + # division by a "hard" zero water heights as well. + + if x[1] < 0.5 + H0_wet_dry = max( 2.5 , b + equations.threshold_limiter ) + else + H0_wet_dry = max( 1.5 , b + equations.threshold_limiter ) + end + + return abs(H0_wet_dry - (h + b)) +end + +# point to the data we want to analyze +u = Trixi.wrap_array(sol[end], semi) +# Perform the actual integration of the well-balancedness error over the domain +l1_well_balance_error = Trixi.integrate_via_indices(u, mesh, equations, semi.solver, semi.cache; normalize=true) do u, i, j, element, equations, solver + x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, solver, i, j, element) + # We know that the discontinuity is a vertical line. Slightly augment the x value by a factor + # of unit roundoff to avoid the repeted value from the LGL nodes at at interface. + if i == 1 + x_node = SVector(nextfloat(x_node[1]) , x_node[2]) + elseif i == nnodes(semi.solver) + x_node = SVector(prevfloat(x_node[1]) , x_node[2]) + end + u_local = Trixi.get_node_vars(u, equations, solver, i, j, element) + return lake_at_rest_error_two_level(u_local, x_node, equations) +end + +# report the well-balancedness lake-at-rest error to the screen +println("─"^100) +println(" Lake-at-rest error for '", Trixi.get_name(equations), "' with ", summary(solver), + " at final time " * @sprintf("%10.8e", tspan[end])) + +@printf(" %-12s:", Trixi.pretty_form_utf(lake_at_rest_error)) +@printf(" % 10.8e", l1_well_balance_error) +println() +println("─"^100) diff --git a/examples/tree_3d_dgsem/elixir_advection_restart.jl b/examples/tree_3d_dgsem/elixir_advection_restart.jl index 83bf4418b98..3061f165874 100644 --- a/examples/tree_3d_dgsem/elixir_advection_restart.jl +++ b/examples/tree_3d_dgsem/elixir_advection_restart.jl @@ -20,13 +20,23 @@ mesh = load_mesh(restart_filename) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) tspan = (load_time(restart_filename), 2.0) +dt = load_dt(restart_filename) ode = semidiscretize(semi, tspan, restart_filename); +# Do not overwrite the initial snapshot written by elixir_advection_extended.jl. +save_solution.condition.save_initial_solution = false + +integrator = init(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); + +# Get the last time index and work with that. +integrator.iter = load_timestep(restart_filename) +integrator.stats.naccept = integrator.iter + ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve!(integrator) summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_navierstokes_convergence.jl b/examples/tree_3d_dgsem/elixir_navierstokes_convergence.jl index b32355c48df..ebb0137a1bb 100644 --- a/examples/tree_3d_dgsem/elixir_navierstokes_convergence.jl +++ b/examples/tree_3d_dgsem/elixir_navierstokes_convergence.jl @@ -220,7 +220,10 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_top_bottom = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, t, equations)[2:4]) +velocity_bc_top_bottom = NoSlip() do x, t, equations + u = initial_condition_navier_stokes_convergence_test(x, t, equations) + return SVector(u[2], u[3], u[4]) +end heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, heat_bc_top_bottom) diff --git a/examples/unstructured_2d_dgsem/elixir_euler_restart.jl b/examples/unstructured_2d_dgsem/elixir_euler_restart.jl index 2ac67652023..b85cc2c6d70 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_restart.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_restart.jl @@ -22,14 +22,24 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, boundary_conditions=boundary_conditions) tspan = (load_time(restart_filename), 1.0) +dt = load_dt(restart_filename) ode = semidiscretize(semi, tspan, restart_filename); +# Do not overwrite the initial snapshot written by elixir_advection_extended.jl. +save_solution.condition.save_initial_solution = false + +integrator = init(ode, CarpenterKennedy2N54(williamson_condition=false), + dt=dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep=false, callback=callbacks); + +# Get the last time index and work with that. +integrator.iter = load_timestep(restart_filename) +integrator.stats.naccept = integrator.iter + ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve!(integrator) summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_three_mound_dam_break.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_three_mound_dam_break.jl new file mode 100644 index 00000000000..65b0fcae462 --- /dev/null +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_three_mound_dam_break.jl @@ -0,0 +1,139 @@ + +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the shallow water equations +# +# TODO: TrixiShallowWater: wet/dry example elixir + + +equations = ShallowWaterEquations2D(gravity_constant=9.81, H0=1.875, + threshold_limiter=1e-12, threshold_wet=1e-14) + + +""" + initial_condition_three_mounds(x, t, equations::ShallowWaterEquations2D) + +Initial condition simulating a dam break. The bottom topography is given by one large and two smaller +mounds. The mounds are flooded by the water for t > 0. To smooth the discontinuity, a logistic function +is applied. + +The initial conditions is taken from Section 6.3 of the paper: +- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and Timothy Warburton (2018) + An entropy stable discontinuous Galerkin method for the shallow water equations on + curvilinear meshes with wet/dry fronts accelerated by GPUs\n + [DOI: 10.1016/j.jcp.2018.08.038](https://doi.org/10.1016/j.jcp.2018.08.038) +""" +function initial_condition_three_mounds(x, t, equations::ShallowWaterEquations2D) + + # Set the background values + v1 = 0.0 + v2 = 0.0 + + x1, x2 = x + M_1 = 1 - 0.1 * sqrt( (x1 - 30.0)^2 + (x2 - 22.5)^2 ) + M_2 = 1 - 0.1 * sqrt( (x1 - 30.0)^2 + (x2 - 7.5)^2 ) + M_3 = 2.8 - 0.28 * sqrt( (x1 - 47.5)^2 + (x2 - 15.0)^2 ) + + b = max(0.0, M_1, M_2, M_3) + + # use a logistic function to transfer water height value smoothly + L = equations.H0 # maximum of function + x0 = 8 # center point of function + k = -75.0 # sharpness of transfer + + H = max(b, L / (1.0 + exp(-k * (x1 - x0)))) + + # Avoid division by zero by adjusting the initial condition with a small dry state threshold + # that defaults to 500*eps() ≈ 1e-13 in double precision and is set in the constructor above + # for the ShallowWaterEquations struct. + H = max(H, b + equations.threshold_limiter) + return prim2cons(SVector(H, v1, v2, b), equations) +end + +initial_condition = initial_condition_three_mounds + +function boundary_condition_outflow(u_inner, normal_direction::AbstractVector, x, t, + surface_flux_function, equations::ShallowWaterEquations2D) + # Impulse and bottom from inside, height from external state + u_outer = SVector(equations.threshold_wet, u_inner[2], u_inner[3], u_inner[4]) + + # calculate the boundary flux + flux = surface_flux_function(u_inner, u_outer, normal_direction, equations) + + return flux +end + +boundary_conditions = Dict( :Bottom => boundary_condition_slip_wall, + :Top => boundary_condition_slip_wall, + :Right => boundary_condition_outflow, + :Left => boundary_condition_slip_wall ) + +############################################################################### +# Get the DG approximation space + +volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) +surface_flux = (FluxHydrostaticReconstruction(flux_hll_chen_noelle, hydrostatic_reconstruction_chen_noelle), + flux_nonconservative_chen_noelle) + +basis = LobattoLegendreBasis(4) + +indicator_sc = IndicatorHennemannGassnerShallowWater(equations, basis, + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable=waterheight_pressure) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg=volume_flux, + volume_flux_fv=surface_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +############################################################################### +# Get the unstructured quad mesh from a file (downloads the file if not available locally) + +default_meshfile = joinpath(@__DIR__, "mesh_three_mound.mesh") + +isfile(default_meshfile) || download("https://gist.githubusercontent.com/svengoldberg/c3c87fecb3fc6e46be7f0d1c7cb35f83/raw/e817ecd9e6c4686581d63c46128f9b6468d396d3/mesh_three_mound.mesh", + default_meshfile) + +meshfile = default_meshfile + +mesh = UnstructuredMesh2D(meshfile) + +# Create the semi discretization object +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; + boundary_conditions=boundary_conditions) + +############################################################################### +# ODE solver + +tspan = (0.0, 20.0) +ode = semidiscretize(semi, tspan) + +############################################################################### +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +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) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +############################################################################### +# run the simulation + +stage_limiter! = PositivityPreservingLimiterShallowWater(variables=(Trixi.waterheight,)) + +sol = solve(ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback=callbacks); +summary_callback() # print the timer summary diff --git a/ext/TrixiMakieExt.jl b/ext/TrixiMakieExt.jl index 1eb11f6a422..8cd7576a6e5 100644 --- a/ext/TrixiMakieExt.jl +++ b/ext/TrixiMakieExt.jl @@ -335,7 +335,7 @@ end # ================== new Makie plot recipes ==================== # This initializes a Makie recipe, which creates a new type definition which Makie uses to create -# custom `trixiheatmap` plots. See also https://makie.juliaplots.org/stable/recipes.html +# custom `trixiheatmap` plots. See also https://docs.makie.org/stable/documentation/recipes/ Makie.@recipe(TrixiHeatmap, plot_data_series) do scene Makie.Theme(colormap = default_Makie_colormap()) end @@ -346,9 +346,8 @@ function Makie.plot!(myplot::TrixiHeatmap) plotting_mesh = global_plotting_triangulation_makie(pds; set_z_coordinate_zero = true) - @unpack variable_id = pds pd = pds.plot_data - solution_z = vec(StructArrays.component(pd.data, variable_id)) + solution_z = vec(StructArrays.component(pd.data, pds.variable_id)) Makie.mesh!(myplot, plotting_mesh, color = solution_z, shading = false, colormap = myplot[:colormap]) myplot.colorrange = extrema(solution_z) @@ -411,7 +410,7 @@ function Makie.plot!(fig, pd::PlotData2DTriangulated; row = row_list[variable_to_plot] col = col_list[variable_to_plot] - Makie.Colorbar(fig[row, col][1, 2], plt) + Makie.Colorbar(fig[row, col][1, 2], colormap = colormap) ax.aspect = Makie.DataAspect() # equal aspect ratio ax.title = variable_name diff --git a/src/Trixi.jl b/src/Trixi.jl index 4653e835ee7..b188401673c 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -51,9 +51,10 @@ using LoopVectorization: LoopVectorization, @turbo, indices using StaticArrayInterface: static_length # used by LoopVectorization using MuladdMacro: @muladd using Octavian: Octavian, matmul! -using Polyester: @batch # You know, the cheapest threads you can find... +using Polyester: Polyester, @batch # You know, the cheapest threads you can find... using OffsetArrays: OffsetArray, OffsetVector using P4est +using T8code using Setfield: @set using RecipesBase: RecipesBase using Requires: @require @@ -110,6 +111,7 @@ include("basic_types.jl") include("auxiliary/auxiliary.jl") include("auxiliary/mpi.jl") include("auxiliary/p4est.jl") +include("auxiliary/t8code.jl") include("equations/equations.jl") include("meshes/meshes.jl") include("solvers/solvers.jl") @@ -163,9 +165,13 @@ export flux, flux_central, flux_lax_friedrichs, flux_hll, flux_hllc, flux_hlle, flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal, flux_es_ersing_etal, flux_nonconservative_ersing_etal, hydrostatic_reconstruction_audusse_etal, flux_nonconservative_audusse_etal, +# TODO: TrixiShallowWater: move anything with "chen_noelle" to new file + hydrostatic_reconstruction_chen_noelle, flux_nonconservative_chen_noelle, + flux_hll_chen_noelle, FluxPlusDissipation, DissipationGlobalLaxFriedrichs, DissipationLocalLaxFriedrichs, FluxLaxFriedrichs, max_abs_speed_naive, - FluxHLL, min_max_speed_naive, + FluxHLL, min_max_speed_naive, min_max_speed_davis, min_max_speed_einfeldt, + min_max_speed_chen_noelle, FluxLMARS, FluxRotated, flux_shima_etal_turbo, flux_ranocha_turbo, @@ -207,7 +213,7 @@ export entropy, energy_total, energy_kinetic, energy_internal, energy_magnetic, export lake_at_rest_error export ncomponents, eachcomponent -export TreeMesh, StructuredMesh, UnstructuredMesh2D, P4estMesh +export TreeMesh, StructuredMesh, UnstructuredMesh2D, P4estMesh, T8codeMesh export DG, DGSEM, LobattoLegendreBasis, @@ -216,6 +222,8 @@ export DG, VolumeIntegralFluxDifferencing, VolumeIntegralPureLGLFiniteVolume, VolumeIntegralShockCapturingHG, IndicatorHennemannGassner, +# TODO: TrixiShallowWater: move new indicator + IndicatorHennemannGassnerShallowWater, VolumeIntegralUpwind, SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, SurfaceIntegralUpwind, @@ -242,14 +250,15 @@ export SummaryCallback, SteadyStateCallback, AnalysisCallback, AliveCallback, GlmSpeedCallback, LBMCollisionCallback, EulerAcousticsCouplingCallback, TrivialCallback, AnalysisCallbackCoupled -export load_mesh, load_time +export load_mesh, load_time, load_timestep, load_dt export ControllerThreeLevel, ControllerThreeLevelCombined, IndicatorLöhner, IndicatorLoehner, IndicatorMax, IndicatorNeuralNetwork, NeuralNetworkPerssonPeraire, NeuralNetworkRayHesthaven, NeuralNetworkCNN -export PositivityPreservingLimiterZhangShu +# TODO: TrixiShallowWater: move new limiter +export PositivityPreservingLimiterZhangShu, PositivityPreservingLimiterShallowWater export trixi_include, examples_dir, get_examples, default_example, default_example_unstructured, ode_default_options @@ -271,6 +280,7 @@ function __init__() init_mpi() init_p4est() + init_t8code() register_error_hints() diff --git a/src/auxiliary/t8code.jl b/src/auxiliary/t8code.jl new file mode 100644 index 00000000000..37cb782bb93 --- /dev/null +++ b/src/auxiliary/t8code.jl @@ -0,0 +1,486 @@ +""" + init_t8code() + +Initialize `t8code` by calling `sc_init`, `p4est_init`, and `t8_init` while +setting the log level to `SC_LP_ERROR`. This function will check if `t8code` +is already initialized and if yes, do nothing, thus it is safe to call it +multiple times. +""" +function init_t8code() + t8code_package_id = t8_get_package_id() + if t8code_package_id >= 0 + return nothing + end + + # Initialize the sc library, has to happen before we initialize t8code. + let catch_signals = 0, print_backtrace = 0, log_handler = C_NULL + T8code.Libt8.sc_init(mpi_comm(), catch_signals, print_backtrace, log_handler, + T8code.Libt8.SC_LP_ERROR) + end + + if T8code.Libt8.p4est_is_initialized() == 0 + # Initialize `p4est` with log level ERROR to prevent a lot of output in AMR simulations + T8code.Libt8.p4est_init(C_NULL, T8code.Libt8.SC_LP_ERROR) + end + + # Initialize t8code with log level ERROR to prevent a lot of output in AMR simulations. + t8_init(T8code.Libt8.SC_LP_ERROR) + + if haskey(ENV, "TRIXI_T8CODE_SC_FINALIZE") + # Normally, `sc_finalize` should always be called during shutdown of an + # application. It checks whether there is still un-freed memory by t8code + # and/or T8code.jl and throws an exception if this is the case. For + # production runs this is not mandatory, but is helpful during + # development. Hence, this option is only activated when environment + # variable TRIXI_T8CODE_SC_FINALIZE exists. + @warn "T8code.jl: sc_finalize will be called during shutdown of Trixi.jl." + MPI.add_finalize_hook!(T8code.Libt8.sc_finalize) + end + + return nothing +end + +function trixi_t8_unref_forest(forest) + t8_forest_unref(Ref(forest)) +end + +function t8_free(ptr) + T8code.Libt8.sc_free(t8_get_package_id(), ptr) +end + +function trixi_t8_count_interfaces(forest) + # Check that forest is a committed, that is valid and usable, forest. + @assert t8_forest_is_committed(forest) != 0 + + # Get the number of local elements of forest. + num_local_elements = t8_forest_get_local_num_elements(forest) + # Get the number of ghost elements of forest. + num_ghost_elements = t8_forest_get_num_ghosts(forest) + # Get the number of trees that have elements of this process. + num_local_trees = t8_forest_get_num_local_trees(forest) + + current_index = t8_locidx_t(0) + + local_num_conform = 0 + local_num_mortars = 0 + local_num_boundary = 0 + + for itree in 0:(num_local_trees - 1) + tree_class = t8_forest_get_tree_class(forest, itree) + eclass_scheme = t8_forest_get_eclass_scheme(forest, tree_class) + + # Get the number of elements of this tree. + num_elements_in_tree = t8_forest_get_tree_num_elements(forest, itree) + + for ielement in 0:(num_elements_in_tree - 1) + element = t8_forest_get_element_in_tree(forest, itree, ielement) + + level = t8_element_level(eclass_scheme, element) + + num_faces = t8_element_num_faces(eclass_scheme, element) + + for iface in 0:(num_faces - 1) + pelement_indices_ref = Ref{Ptr{t8_locidx_t}}() + pneighbor_leafs_ref = Ref{Ptr{Ptr{t8_element}}}() + pneigh_scheme_ref = Ref{Ptr{t8_eclass_scheme}}() + + dual_faces_ref = Ref{Ptr{Cint}}() + num_neighbors_ref = Ref{Cint}() + + forest_is_balanced = Cint(1) + + t8_forest_leaf_face_neighbors(forest, itree, element, + pneighbor_leafs_ref, iface, dual_faces_ref, + num_neighbors_ref, + pelement_indices_ref, pneigh_scheme_ref, + forest_is_balanced) + + num_neighbors = num_neighbors_ref[] + neighbor_ielements = unsafe_wrap(Array, pelement_indices_ref[], + num_neighbors) + neighbor_leafs = unsafe_wrap(Array, pneighbor_leafs_ref[], num_neighbors) + neighbor_scheme = pneigh_scheme_ref[] + + if num_neighbors > 0 + neighbor_level = t8_element_level(neighbor_scheme, neighbor_leafs[1]) + + # Conforming interface: The second condition ensures we only visit the interface once. + if level == neighbor_level && current_index <= neighbor_ielements[1] + local_num_conform += 1 + elseif level < neighbor_level + local_num_mortars += 1 + end + + else + local_num_boundary += 1 + end + + t8_free(dual_faces_ref[]) + t8_free(pneighbor_leafs_ref[]) + t8_free(pelement_indices_ref[]) + end # for + + current_index += 1 + end # for + end # for + + return (interfaces = local_num_conform, + mortars = local_num_mortars, + boundaries = local_num_boundary) +end + +function trixi_t8_fill_mesh_info(forest, elements, interfaces, mortars, boundaries, + boundary_names) + # Check that forest is a committed, that is valid and usable, forest. + @assert t8_forest_is_committed(forest) != 0 + + # Get the number of local elements of forest. + num_local_elements = t8_forest_get_local_num_elements(forest) + # Get the number of ghost elements of forest. + num_ghost_elements = t8_forest_get_num_ghosts(forest) + # Get the number of trees that have elements of this process. + num_local_trees = t8_forest_get_num_local_trees(forest) + + current_index = t8_locidx_t(0) + + local_num_conform = 0 + local_num_mortars = 0 + local_num_boundary = 0 + + for itree in 0:(num_local_trees - 1) + tree_class = t8_forest_get_tree_class(forest, itree) + eclass_scheme = t8_forest_get_eclass_scheme(forest, tree_class) + + # Get the number of elements of this tree. + num_elements_in_tree = t8_forest_get_tree_num_elements(forest, itree) + + for ielement in 0:(num_elements_in_tree - 1) + element = t8_forest_get_element_in_tree(forest, itree, ielement) + + level = t8_element_level(eclass_scheme, element) + + num_faces = t8_element_num_faces(eclass_scheme, element) + + for iface in 0:(num_faces - 1) + + # Compute the `orientation` of the touching faces. + if t8_element_is_root_boundary(eclass_scheme, element, iface) == 1 + cmesh = t8_forest_get_cmesh(forest) + itree_in_cmesh = t8_forest_ltreeid_to_cmesh_ltreeid(forest, itree) + iface_in_tree = t8_element_tree_face(eclass_scheme, element, iface) + orientation_ref = Ref{Cint}() + + t8_cmesh_get_face_neighbor(cmesh, itree_in_cmesh, iface_in_tree, C_NULL, + orientation_ref) + orientation = orientation_ref[] + else + orientation = zero(Cint) + end + + pelement_indices_ref = Ref{Ptr{t8_locidx_t}}() + pneighbor_leafs_ref = Ref{Ptr{Ptr{t8_element}}}() + pneigh_scheme_ref = Ref{Ptr{t8_eclass_scheme}}() + + dual_faces_ref = Ref{Ptr{Cint}}() + num_neighbors_ref = Ref{Cint}() + + forest_is_balanced = Cint(1) + + t8_forest_leaf_face_neighbors(forest, itree, element, + pneighbor_leafs_ref, iface, dual_faces_ref, + num_neighbors_ref, + pelement_indices_ref, pneigh_scheme_ref, + forest_is_balanced) + + num_neighbors = num_neighbors_ref[] + dual_faces = unsafe_wrap(Array, dual_faces_ref[], num_neighbors) + neighbor_ielements = unsafe_wrap(Array, pelement_indices_ref[], + num_neighbors) + neighbor_leafs = unsafe_wrap(Array, pneighbor_leafs_ref[], num_neighbors) + neighbor_scheme = pneigh_scheme_ref[] + + if num_neighbors > 0 + neighbor_level = t8_element_level(neighbor_scheme, neighbor_leafs[1]) + + # Conforming interface: The second condition ensures we only visit the interface once. + if level == neighbor_level && current_index <= neighbor_ielements[1] + local_num_conform += 1 + + faces = (iface, dual_faces[1]) + interface_id = local_num_conform + + # Write data to interfaces container. + interfaces.neighbor_ids[1, interface_id] = current_index + 1 + interfaces.neighbor_ids[2, interface_id] = neighbor_ielements[1] + 1 + + # Iterate over primary and secondary element. + for side in 1:2 + # Align interface in positive coordinate direction of primary element. + # For orientation == 1, the secondary element needs to be indexed backwards + # relative to the interface. + if side == 1 || orientation == 0 + # Forward indexing + indexing = :i_forward + else + # Backward indexing + indexing = :i_backward + end + + if faces[side] == 0 + # Index face in negative x-direction + interfaces.node_indices[side, interface_id] = (:begin, + indexing) + elseif faces[side] == 1 + # Index face in positive x-direction + interfaces.node_indices[side, interface_id] = (:end, + indexing) + elseif faces[side] == 2 + # Index face in negative y-direction + interfaces.node_indices[side, interface_id] = (indexing, + :begin) + else # faces[side] == 3 + # Index face in positive y-direction + interfaces.node_indices[side, interface_id] = (indexing, + :end) + end + end + + # Non-conforming interface. + elseif level < neighbor_level + local_num_mortars += 1 + + faces = (dual_faces[1], iface) + + mortar_id = local_num_mortars + + # Last entry is the large element. + mortars.neighbor_ids[end, mortar_id] = current_index + 1 + + # First `1:end-1` entries are the smaller elements. + mortars.neighbor_ids[1:(end - 1), mortar_id] .= neighbor_ielements .+ + 1 + + for side in 1:2 + # Align mortar in positive coordinate direction of small side. + # For orientation == 1, the large side needs to be indexed backwards + # relative to the mortar. + if side == 1 || orientation == 0 + # Forward indexing for small side or orientation == 0. + indexing = :i_forward + else + # Backward indexing for large side with reversed orientation. + indexing = :i_backward + # Since the orientation is reversed we have to account for this + # when filling the `neighbor_ids` array. + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[2] + + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[1] + + 1 + end + + if faces[side] == 0 + # Index face in negative x-direction + mortars.node_indices[side, mortar_id] = (:begin, indexing) + elseif faces[side] == 1 + # Index face in positive x-direction + mortars.node_indices[side, mortar_id] = (:end, indexing) + elseif faces[side] == 2 + # Index face in negative y-direction + mortars.node_indices[side, mortar_id] = (indexing, :begin) + else # faces[side] == 3 + # Index face in positive y-direction + mortars.node_indices[side, mortar_id] = (indexing, :end) + end + end + + # else: "level > neighbor_level" is skipped since we visit the mortar interface only once. + end + + # Domain boundary. + else + local_num_boundary += 1 + boundary_id = local_num_boundary + + boundaries.neighbor_ids[boundary_id] = current_index + 1 + + if iface == 0 + # Index face in negative x-direction. + boundaries.node_indices[boundary_id] = (:begin, :i_forward) + elseif iface == 1 + # Index face in positive x-direction. + boundaries.node_indices[boundary_id] = (:end, :i_forward) + elseif iface == 2 + # Index face in negative y-direction. + boundaries.node_indices[boundary_id] = (:i_forward, :begin) + else # iface == 3 + # Index face in positive y-direction. + boundaries.node_indices[boundary_id] = (:i_forward, :end) + end + + # One-based indexing. + boundaries.name[boundary_id] = boundary_names[iface + 1, itree + 1] + end + + t8_free(dual_faces_ref[]) + t8_free(pneighbor_leafs_ref[]) + t8_free(pelement_indices_ref[]) + end # for iface = ... + + current_index += 1 + end # for + end # for + + return (interfaces = local_num_conform, + mortars = local_num_mortars, + boundaries = local_num_boundary) +end + +function trixi_t8_get_local_element_levels(forest) + # Check that forest is a committed, that is valid and usable, forest. + @assert t8_forest_is_committed(forest) != 0 + + levels = Vector{Int}(undef, t8_forest_get_local_num_elements(forest)) + + # Get the number of trees that have elements of this process. + num_local_trees = t8_forest_get_num_local_trees(forest) + + current_index = 0 + + for itree in 0:(num_local_trees - 1) + tree_class = t8_forest_get_tree_class(forest, itree) + eclass_scheme = t8_forest_get_eclass_scheme(forest, tree_class) + + # Get the number of elements of this tree. + num_elements_in_tree = t8_forest_get_tree_num_elements(forest, itree) + + for ielement in 0:(num_elements_in_tree - 1) + element = t8_forest_get_element_in_tree(forest, itree, ielement) + current_index += 1 + levels[current_index] = t8_element_level(eclass_scheme, element) + end # for + end # for + + return levels +end + +# Callback function prototype to decide for refining and coarsening. +# If `is_family` equals 1, the first `num_elements` in elements +# form a family and we decide whether this family should be coarsened +# or only the first element should be refined. +# Otherwise `is_family` must equal zero and we consider the first entry +# of the element array for refinement. +# Entries of the element array beyond the first `num_elements` are undefined. +# \param [in] forest the forest to which the new elements belong +# \param [in] forest_from the forest that is adapted. +# \param [in] which_tree the local tree containing `elements` +# \param [in] lelement_id the local element id in `forest_old` in the tree of the current element +# \param [in] ts the eclass scheme of the tree +# \param [in] is_family if 1, the first `num_elements` entries in `elements` form a family. If 0, they do not. +# \param [in] num_elements the number of entries in `elements` that are defined +# \param [in] elements Pointers to a family or, if `is_family` is zero, +# pointer to one element. +# \return greater zero if the first entry in `elements` should be refined, +# smaller zero if the family `elements` shall be coarsened, +# zero else. +function adapt_callback(forest, + forest_from, + which_tree, + lelement_id, + ts, + is_family, + num_elements, + elements)::Cint + num_levels = t8_forest_get_local_num_elements(forest_from) + + indicator_ptr = Ptr{Int}(t8_forest_get_user_data(forest)) + indicators = unsafe_wrap(Array, indicator_ptr, num_levels) + + offset = t8_forest_get_tree_element_offset(forest_from, which_tree) + + # Only allow coarsening for complete families. + if indicators[offset + lelement_id + 1] < 0 && is_family == 0 + return Cint(0) + end + + return Cint(indicators[offset + lelement_id + 1]) +end + +function trixi_t8_adapt_new(old_forest, indicators) + # Check that forest is a committed, that is valid and usable, forest. + @assert t8_forest_is_committed(old_forest) != 0 + + # Init new forest. + new_forest_ref = Ref{t8_forest_t}() + t8_forest_init(new_forest_ref) + new_forest = new_forest_ref[] + + let set_from = C_NULL, recursive = 0, set_for_coarsening = 0, no_repartition = 0 + t8_forest_set_user_data(new_forest, pointer(indicators)) + t8_forest_set_adapt(new_forest, old_forest, @t8_adapt_callback(adapt_callback), + recursive) + t8_forest_set_balance(new_forest, set_from, no_repartition) + t8_forest_set_partition(new_forest, set_from, set_for_coarsening) + t8_forest_set_ghost(new_forest, 1, T8_GHOST_FACES) # Note: MPI support not available yet so it is a dummy call. + t8_forest_commit(new_forest) + end + + return new_forest +end + +function trixi_t8_get_difference(old_levels, new_levels, num_children) + old_nelems = length(old_levels) + new_nelems = length(new_levels) + + changes = Vector{Int}(undef, old_nelems) + + # Local element indices. + old_index = 1 + new_index = 1 + + while old_index <= old_nelems && new_index <= new_nelems + if old_levels[old_index] < new_levels[new_index] + # Refined. + + changes[old_index] = 1 + + old_index += 1 + new_index += num_children + + elseif old_levels[old_index] > new_levels[new_index] + # Coarsend. + + for child_index in old_index:(old_index + num_children - 1) + changes[child_index] = -1 + end + + old_index += num_children + new_index += 1 + + else + # No changes. + + changes[old_index] = 0 + + old_index += 1 + new_index += 1 + end + end + + return changes +end + +# Coarsen or refine marked cells and rebalance forest. Return a difference between +# old and new mesh. +function trixi_t8_adapt!(mesh, indicators) + old_levels = trixi_t8_get_local_element_levels(mesh.forest) + + forest_cached = trixi_t8_adapt_new(mesh.forest, indicators) + + new_levels = trixi_t8_get_local_element_levels(forest_cached) + + differences = trixi_t8_get_difference(old_levels, new_levels, 2^ndims(mesh)) + + mesh.forest = forest_cached + + return differences +end diff --git a/src/callbacks_stage/callbacks_stage.jl b/src/callbacks_stage/callbacks_stage.jl index 7609f9b341d..ab0f34efb78 100644 --- a/src/callbacks_stage/callbacks_stage.jl +++ b/src/callbacks_stage/callbacks_stage.jl @@ -6,4 +6,6 @@ #! format: noindent include("positivity_zhang_shu.jl") +# TODO: TrixiShallowWater: move specific limiter file +include("positivity_shallow_water.jl") end # @muladd diff --git a/src/callbacks_stage/positivity_shallow_water.jl b/src/callbacks_stage/positivity_shallow_water.jl new file mode 100644 index 00000000000..36276026fe9 --- /dev/null +++ b/src/callbacks_stage/positivity_shallow_water.jl @@ -0,0 +1,89 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +# TODO: TrixiShallowWater: generic wet/dry limiter + +""" + PositivityPreservingLimiterShallowWater(; variables) + +The limiter is specifically designed for the shallow water equations. +It is applied to all scalar `variables` in their given order +using the defined `threshold_limiter` from the [`ShallowWaterEquations1D`](@ref) struct +or the [`ShallowWaterEquations2D`](@ref) struct to determine the minimal acceptable values. +The order of the `variables` is important and might have a strong influence +on the robustness. + +As opposed to the standard version of the [`PositivityPreservingLimiterZhangShu`](@ref), +nodes with a water height below the `threshold_limiter` are treated in a special way. +To avoid numerical problems caused by velocities close to zero, +the velocity is cut off, such that the node can be identified as "dry". The special feature of the +`ShallowWaterEquations` used here is that the bottom topography is stored as an additional +quantity in the solution vector `u`. However, the value of the bottom topography +should not be changed. That is why, it is not limited. + +After the limiting process is applied to all degrees of freedom, for safety reasons, +the `threshold_limiter` is applied again on all the DG nodes in order to avoid water height below. +In the case where the cell mean value is below the threshold before applying the limiter, +there could still be dry nodes afterwards due to the logic of the limiter. + +This fully-discrete positivity-preserving limiter is based on the work of +- Zhang, Shu (2011) + Maximum-principle-satisfying and positivity-preserving high-order schemes + for conservation laws: survey and new developments + [doi: 10.1098/rspa.2011.0153](https://doi.org/10.1098/rspa.2011.0153) +""" +struct PositivityPreservingLimiterShallowWater{N, Variables <: NTuple{N, Any}} + variables::Variables +end + +function PositivityPreservingLimiterShallowWater(; variables) + PositivityPreservingLimiterShallowWater(variables) +end + +function (limiter!::PositivityPreservingLimiterShallowWater)(u_ode, integrator, + semi::AbstractSemidiscretization, + t) + u = wrap_array(u_ode, semi) + @trixi_timeit timer() "positivity-preserving limiter" limiter_shallow_water!(u, + limiter!.variables, + mesh_equations_solver_cache(semi)...) +end + +# Iterate over tuples in a type-stable way using "lispy tuple programming", +# similar to https://stackoverflow.com/a/55849398: +# Iterating over tuples of different functions isn't type-stable in general +# but accessing the first element of a tuple is type-stable. Hence, it's good +# to process one element at a time and replace iteration by recursion here. +# Note that you shouldn't use this with too many elements per tuple since the +# compile times can increase otherwise - but a handful of elements per tuple +# is definitely fine. +function limiter_shallow_water!(u, variables::NTuple{N, Any}, + mesh, + equations::Union{ShallowWaterEquations1D, + ShallowWaterEquations2D}, + solver, cache) where {N} + variable = first(variables) + remaining_variables = Base.tail(variables) + + limiter_shallow_water!(u, equations.threshold_limiter, variable, mesh, equations, + solver, cache) + limiter_shallow_water!(u, remaining_variables, mesh, equations, solver, cache) + return nothing +end + +# terminate the type-stable iteration over tuples +function limiter_shallow_water!(u, variables::Tuple{}, + mesh, + equations::Union{ShallowWaterEquations1D, + ShallowWaterEquations2D}, + solver, cache) + nothing +end + +include("positivity_shallow_water_dg1d.jl") +include("positivity_shallow_water_dg2d.jl") +end # @muladd diff --git a/src/callbacks_stage/positivity_shallow_water_dg1d.jl b/src/callbacks_stage/positivity_shallow_water_dg1d.jl new file mode 100644 index 00000000000..13c6866e895 --- /dev/null +++ b/src/callbacks_stage/positivity_shallow_water_dg1d.jl @@ -0,0 +1,89 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +# TODO: TrixiShallowWater: 1D wet/dry limiter should move + +function limiter_shallow_water!(u, threshold::Real, variable, + mesh::AbstractMesh{1}, + equations::ShallowWaterEquations1D, + dg::DGSEM, cache) + @unpack weights = dg.basis + + @threaded for element in eachelement(dg, cache) + # determine minimum value + value_min = typemax(eltype(u)) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + value_min = min(value_min, variable(u_node, equations)) + end + + # detect if limiting is necessary + value_min < threshold || continue + + # compute mean value + u_mean = zero(get_node_vars(u, equations, dg, 1, element)) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + u_mean += u_node * weights[i] + end + # note that the reference element is [-1,1]^ndims(dg), thus the weights sum to 2 + u_mean = u_mean / 2^ndims(mesh) + + # We compute the value directly with the mean values, as we assume that + # Jensen's inequality holds (e.g. pressure for compressible Euler equations). + value_mean = variable(u_mean, equations) + theta = (value_mean - threshold) / (value_mean - value_min) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + + # Cut off velocity in case that the waterheight is smaller than the threshold + + h_node, h_v_node, b_node = u_node + h_mean, h_v_mean, _ = u_mean # b_mean is not used as b_node must not be overwritten + + # Set them both to zero to apply linear combination correctly + if h_node <= threshold + h_v_node = zero(eltype(u)) + h_v_mean = zero(eltype(u)) + end + + u_node = SVector(h_node, h_v_node, b_node) + u_mean = SVector(h_mean, h_v_mean, b_node) + + # When velocity is cut off, the only averaged value is the waterheight, + # because the velocity is set to zero and this value is passed. + # Otherwise, the velocity is averaged, as well. + # Note that the auxiliary bottom topography variable `b` is never limited. + set_node_vars!(u, theta * u_node + (1 - theta) * u_mean, + equations, dg, i, element) + end + end + + # "Safety" application of the wet/dry thresholds over all the DG nodes + # on the current `element` after the limiting above in order to avoid dry nodes. + # If the value_mean < threshold before applying limiter, there + # could still be dry nodes afterwards due to logic of the limiting + @threaded for element in eachelement(dg, cache) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + + h, hv, b = u_node + + if h <= threshold + h = threshold + hv = zero(eltype(u)) + end + + u_node = SVector(h, hv, b) + + set_node_vars!(u, u_node, equations, dg, i, element) + end + end + + return nothing +end +end # @muladd diff --git a/src/callbacks_stage/positivity_shallow_water_dg2d.jl b/src/callbacks_stage/positivity_shallow_water_dg2d.jl new file mode 100644 index 00000000000..da3a25fdcf4 --- /dev/null +++ b/src/callbacks_stage/positivity_shallow_water_dg2d.jl @@ -0,0 +1,90 @@ +# By default, Julia/LLVM does not use fused multiply-add operations (FMAs). +# Since these FMAs can increase the performance of many numerical algorithms, +# we need to opt-in explicitly. +# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. +@muladd begin +#! format: noindent + +# TODO: TrixiShallowWater: 2D wet/dry limiter should move + +function limiter_shallow_water!(u, threshold::Real, variable, + mesh::AbstractMesh{2}, + equations::ShallowWaterEquations2D, dg::DGSEM, cache) + @unpack weights = dg.basis + + @threaded for element in eachelement(dg, cache) + # determine minimum value + value_min = typemax(eltype(u)) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + value_min = min(value_min, variable(u_node, equations)) + end + + # detect if limiting is necessary + value_min < threshold || continue + + # compute mean value + u_mean = zero(get_node_vars(u, equations, dg, 1, 1, element)) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + u_mean += u_node * weights[i] * weights[j] + end + # note that the reference element is [-1,1]^ndims(dg), thus the weights sum to 2 + u_mean = u_mean / 2^ndims(mesh) + + # We compute the value directly with the mean values, as we assume that + # Jensen's inequality holds (e.g. pressure for compressible Euler equations). + value_mean = variable(u_mean, equations) + theta = (value_mean - threshold) / (value_mean - value_min) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # Cut off velocity in case that the water height is smaller than the threshold + + h_node, h_v1_node, h_v2_node, b_node = u_node + h_mean, h_v1_mean, h_v2_mean, _ = u_mean # b_mean is not used as it must not be overwritten + + if h_node <= threshold + h_v1_node = zero(eltype(u)) + h_v2_node = zero(eltype(u)) + h_v1_mean = zero(eltype(u)) + h_v2_mean = zero(eltype(u)) + end + + u_node = SVector(h_node, h_v1_node, h_v2_node, b_node) + u_mean = SVector(h_mean, h_v1_mean, h_v2_mean, b_node) + + # When velocities are cut off, the only averaged value is the water height, + # because the velocities are set to zero and this value is passed. + # Otherwise, the velocities are averaged, as well. + # Note that the auxiliary bottom topography variable `b` is never limited. + set_node_vars!(u, theta * u_node + (1 - theta) * u_mean, + equations, dg, i, j, element) + end + end + + # "Safety" application of the wet/dry thresholds over all the DG nodes + # on the current `element` after the limiting above in order to avoid dry nodes. + # If the value_mean < threshold before applying limiter, there + # could still be dry nodes afterwards due to logic of the limiting + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + h, h_v1, h_v2, b = u_node + + if h <= threshold + h = threshold + h_v1 = zero(eltype(u)) + h_v2 = zero(eltype(u)) + end + + u_node = SVector(h, h_v1, h_v2, b) + + set_node_vars!(u, u_node, equations, dg, i, j, element) + end + end + + return nothing +end +end # @muladd diff --git a/src/callbacks_step/amr.jl b/src/callbacks_step/amr.jl index bef49b4c482..4d80e6e1139 100644 --- a/src/callbacks_step/amr.jl +++ b/src/callbacks_step/amr.jl @@ -471,6 +471,65 @@ function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::P4estMesh, return has_changed end +function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::SerialT8codeMesh, + equations, dg::DG, cache, semi, + t, iter; + only_refine = false, only_coarsen = false, + passive_args = ()) + has_changed = false + + @unpack controller, adaptor = amr_callback + + u = wrap_array(u_ode, mesh, equations, dg, cache) + indicators = @trixi_timeit timer() "indicator" controller(u, mesh, equations, dg, + cache, t = t, iter = iter) + + if only_coarsen + indicators[indicators .> 0] .= 0 + end + + if only_refine + indicators[indicators .< 0] .= 0 + end + + @boundscheck begin + @assert axes(indicators)==(Base.OneTo(ncells(mesh)),) ("Indicator array (axes = $(axes(indicators))) and mesh cells (axes = $(Base.OneTo(ncells(mesh)))) have different axes") + end + + @trixi_timeit timer() "adapt" begin + difference = @trixi_timeit timer() "mesh" trixi_t8_adapt!(mesh, indicators) + + @trixi_timeit timer() "solver" adapt!(u_ode, adaptor, mesh, equations, dg, + cache, difference) + end + + # Store whether there were any cells coarsened or refined and perform load balancing. + has_changed = any(difference .!= 0) + + # TODO: T8codeMesh for MPI not implemented yet. + # Check if mesh changed on other processes + # if mpi_isparallel() + # has_changed = MPI.Allreduce!(Ref(has_changed), |, mpi_comm())[] + # end + + if has_changed + # TODO: T8codeMesh for MPI not implemented yet. + # if mpi_isparallel() && amr_callback.dynamic_load_balancing + # @trixi_timeit timer() "dynamic load balancing" begin + # global_first_quadrant = unsafe_wrap(Array, mesh.p4est.global_first_quadrant, mpi_nranks() + 1) + # old_global_first_quadrant = copy(global_first_quadrant) + # partition!(mesh) + # rebalance_solver!(u_ode, mesh, equations, dg, cache, old_global_first_quadrant) + # end + # end + + reinitialize_boundaries!(semi.boundary_conditions, cache) + end + + # Return true if there were any cells coarsened or refined, otherwise false. + return has_changed +end + function reinitialize_boundaries!(boundary_conditions::UnstructuredSortedBoundaryTypes, cache) # Reinitialize boundary types container because boundaries may have changed. @@ -639,6 +698,10 @@ function current_element_levels(mesh::P4estMesh, solver, cache) return current_levels end +function current_element_levels(mesh::T8codeMesh, solver, cache) + return trixi_t8_get_local_element_levels(mesh.forest) +end + # TODO: Taal refactor, merge the two loops of ControllerThreeLevel and IndicatorLöhner etc.? # But that would remove the simplest possibility to write that stuff to a file... # We could of course implement some additional logic and workarounds, but is it worth the effort? diff --git a/src/callbacks_step/amr_dg2d.jl b/src/callbacks_step/amr_dg2d.jl index 400d16347d5..1d37dfce034 100644 --- a/src/callbacks_step/amr_dg2d.jl +++ b/src/callbacks_step/amr_dg2d.jl @@ -333,9 +333,79 @@ function coarsen_elements!(u::AbstractArray{<:Any, 4}, element_id, end end +# Coarsen and refine elements in the DG solver based on a difference list. +function adapt!(u_ode::AbstractVector, adaptor, mesh::T8codeMesh{2}, equations, + dg::DGSEM, cache, difference) + + # Return early if there is nothing to do. + if !any(difference .!= 0) + return nothing + end + + # Number of (local) cells/elements. + old_nelems = nelements(dg, cache) + new_nelems = ncells(mesh) + + # Local element indices. + old_index = 1 + new_index = 1 + + # Note: This is true for `quads` only. + T8_CHILDREN = 4 + + # Retain current solution data. + old_u_ode = copy(u_ode) + + GC.@preserve old_u_ode begin + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + + reinitialize_containers!(mesh, equations, dg, cache) + + resize!(u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) + u = wrap_array(u_ode, mesh, equations, dg, cache) + + while old_index <= old_nelems && new_index <= new_nelems + if difference[old_index] > 0 # Refine. + + # Refine element and store solution directly in new data structure. + refine_element!(u, new_index, old_u, old_index, adaptor, equations, dg) + + old_index += 1 + new_index += T8_CHILDREN + + elseif difference[old_index] < 0 # Coarsen. + + # If an element is to be removed, sanity check if the following elements + # are also marked - otherwise there would be an error in the way the + # cells/elements are sorted. + @assert all(difference[old_index:(old_index + T8_CHILDREN - 1)] .< 0) "bad cell/element order" + + # Coarsen elements and store solution directly in new data structure. + coarsen_elements!(u, new_index, old_u, old_index, adaptor, equations, + dg) + + old_index += T8_CHILDREN + new_index += 1 + + else # No changes. + + # Copy old element data to new element container. + @views u[:, .., new_index] .= old_u[:, .., old_index] + + old_index += 1 + new_index += 1 + end + end # while + end # GC.@preserve old_u_ode + + return nothing +end + # this method is called when an `ControllerThreeLevel` is constructed function create_cache(::Type{ControllerThreeLevel}, - mesh::Union{TreeMesh{2}, P4estMesh{2}}, equations, dg::DG, cache) + mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, equations, + dg::DG, cache) controller_value = Vector{Int}(undef, nelements(dg, cache)) return (; controller_value) end diff --git a/src/callbacks_step/analysis.jl b/src/callbacks_step/analysis.jl index 8cf43a1d15e..fad42b11098 100644 --- a/src/callbacks_step/analysis.jl +++ b/src/callbacks_step/analysis.jl @@ -267,7 +267,7 @@ function (analysis_callback::AnalysisCallback)(u_ode, du_ode, integrator, semi) gc_time_absolute = 1.0e-9 * (Base.gc_time_ns() - analysis_callback.start_gc_time) # Compute the percentage of total time that was spent in garbage collection - gc_time_percentage = gc_time_absolute / runtime_absolute + gc_time_percentage = gc_time_absolute / runtime_absolute * 100 # Obtain the current memory usage of the Julia garbage collector, in MiB, i.e., the total size of # objects in memory that have been allocated by the JIT compiler or the user code. @@ -534,6 +534,36 @@ function print_amr_information(callbacks, mesh::P4estMesh, solver, cache) return nothing end +# Print level information only if AMR is enabled +function print_amr_information(callbacks, mesh::T8codeMesh, solver, cache) + + # Return early if there is nothing to print + uses_amr(callbacks) || return nothing + + # TODO: Switch to global element levels array when MPI supported or find + # another solution. + levels = trixi_t8_get_local_element_levels(mesh.forest) + + min_level = minimum(levels) + max_level = maximum(levels) + + mpi_println(" minlevel = $min_level") + mpi_println(" maxlevel = $max_level") + + if min_level > 0 + elements_per_level = [count(==(l), levels) for l in 1:max_level] + + for level in max_level:-1:(min_level + 1) + mpi_println(" ├── level $level: " * + @sprintf("% 14d", elements_per_level[level])) + end + mpi_println(" └── level $min_level: " * + @sprintf("% 14d", elements_per_level[min_level])) + end + + return nothing +end + # Iterate over tuples of analysis integrals in a type-stable way using "lispy tuple programming". function analyze_integrals(analysis_integrals::NTuple{N, Any}, io, du, u, t, semi) where {N} diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index 6c74e172e46..aecabf0e4b7 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -31,7 +31,7 @@ end function create_cache_analysis(analyzer, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, + P4estMesh{2}, T8codeMesh{2}}, equations, dg::DG, cache, RealT, uEltype) @@ -108,7 +108,7 @@ end function calc_error_norms(func, u, t, analyzer, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, equations, + P4estMesh{2}, T8codeMesh{2}}, equations, initial_condition, dg::DGSEM, cache, cache_analysis) @unpack vandermonde, weights = analyzer @unpack node_coordinates, inverse_jacobian = cache.elements @@ -176,7 +176,7 @@ end function integrate_via_indices(func::Func, u, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, equations, + P4estMesh{2}, T8codeMesh{2}}, equations, dg::DGSEM, cache, args...; normalize = true) where {Func} @unpack weights = dg.basis @@ -204,7 +204,7 @@ end function integrate(func::Func, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, + P4estMesh{2}, T8codeMesh{2}}, equations, dg::DG, cache; normalize = true) where {Func} integrate_via_indices(u, mesh, equations, dg, cache; normalize = normalize) do u, i, j, element, equations, dg @@ -213,9 +213,27 @@ function integrate(func::Func, u, end end +function integrate(func::Func, u, + mesh::Union{TreeMesh{2}, P4estMesh{2}}, + equations, equations_parabolic, + dg::DGSEM, + cache, cache_parabolic; normalize = true) where {Func} + gradients_x, gradients_y = cache_parabolic.gradients + integrate_via_indices(u, mesh, equations, dg, cache; + normalize = normalize) do u, i, j, element, equations, dg + u_local = get_node_vars(u, equations, dg, i, j, element) + gradients_1_local = get_node_vars(gradients_x, equations_parabolic, dg, i, j, + element) + gradients_2_local = get_node_vars(gradients_y, equations_parabolic, dg, i, j, + element) + return func(u_local, (gradients_1_local, gradients_2_local), + equations_parabolic) + end +end + function analyze(::typeof(entropy_timederivative), du, u, t, mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, + P4estMesh{2}, T8codeMesh{2}}, equations, dg::DG, cache) # Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ integrate_via_indices(u, mesh, equations, dg, cache, @@ -259,7 +277,8 @@ function analyze(::Val{:l2_divb}, du, u, t, end function analyze(::Val{:l2_divb}, du, u, t, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}}, + mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, equations::IdealGlmMhdEquations2D, dg::DGSEM, cache) @unpack contravariant_vectors = cache.elements integrate_via_indices(u, mesh, equations, dg, cache, cache, @@ -326,7 +345,8 @@ function analyze(::Val{:linf_divb}, du, u, t, end function analyze(::Val{:linf_divb}, du, u, t, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}}, + mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, equations::IdealGlmMhdEquations2D, dg::DGSEM, cache) @unpack derivative_matrix, weights = dg.basis @unpack contravariant_vectors = cache.elements diff --git a/src/callbacks_step/analysis_dg3d.jl b/src/callbacks_step/analysis_dg3d.jl index 76aba813fab..3d9b38fd2a5 100644 --- a/src/callbacks_step/analysis_dg3d.jl +++ b/src/callbacks_step/analysis_dg3d.jl @@ -228,7 +228,7 @@ function integrate(func::Func, u, end function integrate(func::Func, u, - mesh::TreeMesh{3}, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, equations, equations_parabolic, dg::DGSEM, cache, cache_parabolic; normalize = true) where {Func} diff --git a/src/callbacks_step/save_restart.jl b/src/callbacks_step/save_restart.jl index e23f58f26ea..f567a5c7fda 100644 --- a/src/callbacks_step/save_restart.jl +++ b/src/callbacks_step/save_restart.jl @@ -130,6 +130,28 @@ function load_time(restart_file::AbstractString) end end +""" + load_timestep(restart_file::AbstractString) + +Load the time step number (`iter` in OrdinaryDiffEq.jl) saved in a `restart_file`. +""" +function load_timestep(restart_file::AbstractString) + h5open(restart_file, "r") do file + read(attributes(file)["timestep"]) + end +end + +""" + load_dt(restart_file::AbstractString) + +Load the time step size (`dt` in OrdinaryDiffEq.jl) saved in a `restart_file`. +""" +function load_dt(restart_file::AbstractString) + h5open(restart_file, "r") do file + read(attributes(file)["dt"]) + end +end + function load_restart_file(semi::AbstractSemidiscretization, restart_file) load_restart_file(mesh_equations_solver_cache(semi)..., restart_file) end diff --git a/src/callbacks_step/save_restart_dg.jl b/src/callbacks_step/save_restart_dg.jl index 5695eb8bede..8db6db2d2b8 100644 --- a/src/callbacks_step/save_restart_dg.jl +++ b/src/callbacks_step/save_restart_dg.jl @@ -7,7 +7,8 @@ function save_restart_file(u, time, dt, timestep, mesh::Union{SerialTreeMesh, StructuredMesh, - UnstructuredMesh2D, SerialP4estMesh}, + UnstructuredMesh2D, SerialP4estMesh, + SerialT8codeMesh}, equations, dg::DG, cache, restart_callback) @unpack output_directory = restart_callback diff --git a/src/callbacks_step/save_solution.jl b/src/callbacks_step/save_solution.jl index 1fe0d6b1e15..14ea33368f8 100644 --- a/src/callbacks_step/save_solution.jl +++ b/src/callbacks_step/save_solution.jl @@ -155,7 +155,14 @@ function save_mesh(semi::AbstractSemidiscretization, output_directory, timestep mesh, _, _, _ = mesh_equations_solver_cache(semi) if mesh.unsaved_changes - mesh.current_filename = save_mesh_file(mesh, output_directory) + # We only append the time step number to the mesh file name if it has + # changed during the simulation due to AMR. We do not append it for + # the first time step. + if timestep == 0 + mesh.current_filename = save_mesh_file(mesh, output_directory) + else + mesh.current_filename = save_mesh_file(mesh, output_directory, timestep) + end mesh.unsaved_changes = false end end diff --git a/src/callbacks_step/save_solution_dg.jl b/src/callbacks_step/save_solution_dg.jl index 6cd4a0ec9c1..6d5004ff65f 100644 --- a/src/callbacks_step/save_solution_dg.jl +++ b/src/callbacks_step/save_solution_dg.jl @@ -7,7 +7,8 @@ function save_solution_file(u, time, dt, timestep, mesh::Union{SerialTreeMesh, StructuredMesh, - UnstructuredMesh2D, SerialP4estMesh}, + UnstructuredMesh2D, SerialP4estMesh, + SerialT8codeMesh}, equations, dg::DG, cache, solution_callback, element_variables = Dict{Symbol, Any}(); system = "") diff --git a/src/callbacks_step/stepsize_dg2d.jl b/src/callbacks_step/stepsize_dg2d.jl index 89a2b2b8350..673c3ba6aa6 100644 --- a/src/callbacks_step/stepsize_dg2d.jl +++ b/src/callbacks_step/stepsize_dg2d.jl @@ -75,7 +75,9 @@ function max_dt(u, t, mesh::ParallelTreeMesh{2}, return dt end -function max_dt(u, t, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}}, +function max_dt(u, t, + mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, constant_speed::False, equations, dg::DG, cache) # to avoid a division by zero if the speed vanishes everywhere, # e.g. for steady-state linear advection @@ -109,7 +111,9 @@ function max_dt(u, t, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMe return 2 / (nnodes(dg) * max_scaled_speed) end -function max_dt(u, t, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}}, +function max_dt(u, t, + mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, constant_speed::True, equations, dg::DG, cache) @unpack contravariant_vectors, inverse_jacobian = cache.elements diff --git a/src/callbacks_step/summary.jl b/src/callbacks_step/summary.jl index 08e13d0b98d..26981a58b73 100644 --- a/src/callbacks_step/summary.jl +++ b/src/callbacks_step/summary.jl @@ -15,10 +15,14 @@ Create and return a callback that prints a human-readable summary of the simulat beginning of a simulation and then resets the timer. When the returned callback is executed directly, the current timer values are shown. """ -function SummaryCallback() +function SummaryCallback(reset_threads = true) + function initialize(cb, u, t, integrator) + initialize_summary_callback(cb, u, t, integrator; + reset_threads) + end DiscreteCallback(summary_callback, summary_callback, save_positions = (false, false), - initialize = initialize_summary_callback) + initialize = initialize) end function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:typeof(summary_callback)}) @@ -139,7 +143,15 @@ end # Print information about the current simulation setup # Note: This is called *after* all initialization is done, but *before* the first time step -function initialize_summary_callback(cb::DiscreteCallback, u, t, integrator) +function initialize_summary_callback(cb::DiscreteCallback, u, t, integrator; + reset_threads = true) + # Optionally reset Polyester.jl threads. See + # https://github.com/trixi-framework/Trixi.jl/issues/1583 + # https://github.com/JuliaSIMD/Polyester.jl/issues/30 + if reset_threads + Polyester.reset_threads!() + end + mpi_isroot() || return nothing print_startup_message() diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 15f7a2cb4c4..e4fd0997eae 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -628,7 +628,7 @@ end return SVector(f1m, f2m, f3m) end -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the +# Calculate estimates for maximum wave speed for local Lax-Friedrichs-type dissipation as the # maximum velocity magnitude plus the maximum speed of sound @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations::CompressibleEulerEquations1D) @@ -648,7 +648,7 @@ end λ_max = max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) end -# Calculate minimum and maximum wave speeds for HLL-type fluxes +# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::CompressibleEulerEquations1D) rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) @@ -660,6 +660,21 @@ end return λ_min, λ_max end +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D) + rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + + return λ_min, λ_max +end + """ flux_hllc(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index 05987c510b8..27b92f41953 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -1032,7 +1032,7 @@ end return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) end -# Calculate minimum and maximum wave speeds for HLL-type fluxes +# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::CompressibleEulerEquations2D) rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) @@ -1065,6 +1065,47 @@ end return λ_min, λ_max end +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + else # y-direction + λ_min = min(v2_ll - c_ll, v2_rr - c_rr) + λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + end + + return λ_min, λ_max +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + norm_ = norm(normal_direction) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # The v_normals are already scaled by the norm + λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) + λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) + + return λ_min, λ_max +end + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction # has been normalized prior to this rotation of the state vector @inline function rotate_to_x(u, normal_vector, equations::CompressibleEulerEquations2D) diff --git a/src/equations/compressible_euler_3d.jl b/src/equations/compressible_euler_3d.jl index 2085811f832..7f25bde31fd 100644 --- a/src/equations/compressible_euler_3d.jl +++ b/src/equations/compressible_euler_3d.jl @@ -1070,7 +1070,7 @@ end return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) end -# Calculate minimum and maximum wave speeds for HLL-type fluxes +# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::CompressibleEulerEquations3D) rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) @@ -1108,6 +1108,54 @@ end return λ_min, λ_max end +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + elseif orientation == 2 # y-direction + λ_min = min(v2_ll - c_ll, v2_rr - c_rr) + λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + else # z-direction + λ_min = min(v3_ll - c_ll, v3_rr - c_rr) + λ_max = max(v3_ll + c_ll, v3_rr + c_rr) + end + + return λ_min, λ_max +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + norm_ = norm(normal_direction) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + v_normal_ll = v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_normal_rr = v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + # The v_normals are already scaled by the norm + λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) + λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) + + return λ_min, λ_max +end + # Rotate normal vector to x-axis; normal, tangent1 and tangent2 need to be orthonormal # Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions # has been normalized prior to this rotation of the state vector diff --git a/src/equations/compressible_navier_stokes_2d.jl b/src/equations/compressible_navier_stokes_2d.jl index 9b06e0b5abf..a1f11717e69 100644 --- a/src/equations/compressible_navier_stokes_2d.jl +++ b/src/equations/compressible_navier_stokes_2d.jl @@ -300,6 +300,21 @@ end return T end +@inline function enstrophy(u, gradients, equations::CompressibleNavierStokesDiffusion2D) + # Enstrophy is 0.5 rho ω⋅ω where ω = ∇ × v + + omega = vorticity(u, gradients, equations) + return 0.5 * u[1] * omega^2 +end + +@inline function vorticity(u, gradients, equations::CompressibleNavierStokesDiffusion2D) + # Ensure that we have velocity `gradients` by way of the `convert_gradient_variables` function. + _, dv1dx, dv2dx, _ = convert_derivative_to_primitive(u, gradients[1], equations) + _, dv1dy, dv2dy, _ = convert_derivative_to_primitive(u, gradients[2], equations) + + return dv2dx - dv1dy +end + # TODO: can we generalize this to MHD? """ struct BoundaryConditionNavierStokesWall diff --git a/src/equations/ideal_glm_mhd_1d.jl b/src/equations/ideal_glm_mhd_1d.jl index 4ef593cda53..7e5c94c7bc3 100644 --- a/src/equations/ideal_glm_mhd_1d.jl +++ b/src/equations/ideal_glm_mhd_1d.jl @@ -277,13 +277,33 @@ end λ_max = max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) end +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations1D) + rho_ll, rho_v1_ll, _ = u_ll + rho_rr, rho_v1_rr, _ = u_rr + + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) + λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) + + return λ_min, λ_max +end + """ - min_max_speed_naive(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations1D) + min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::IdealGlmMhdEquations1D) Calculate minimum and maximum wave speeds for HLL-type fluxes as in - Li (2005) An HLLC Riemann solver for magneto-hydrodynamics - [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020) + [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020). """ @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::IdealGlmMhdEquations1D) diff --git a/src/equations/ideal_glm_mhd_2d.jl b/src/equations/ideal_glm_mhd_2d.jl index fb3048fe883..8fef1ee22c9 100644 --- a/src/equations/ideal_glm_mhd_2d.jl +++ b/src/equations/ideal_glm_mhd_2d.jl @@ -585,13 +585,70 @@ end return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) end +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + if orientation == 1 # x-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) + λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) + else # y-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v2_ll - c_f_ll, v2_rr - c_f_rr) + λ_max = max(v2_ll + c_f_ll, v1_rr + c_f_rr) + end + + return λ_min, λ_max +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + + v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) + + c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + + # Estimate the min/max eigenvalues in the normal direction + λ_min = min(v_normal_ll - c_f_ll, v_normal_rr - c_f_rr) + λ_max = max(v_normal_ll + c_f_ll, v_normal_rr + c_f_rr) + + return λ_min, λ_max +end + """ - min_max_speed_naive(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations2D) + min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::IdealGlmMhdEquations2D) Calculate minimum and maximum wave speeds for HLL-type fluxes as in - Li (2005) An HLLC Riemann solver for magneto-hydrodynamics - [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020) + [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020). """ @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::IdealGlmMhdEquations2D) @@ -635,10 +692,8 @@ end v1_rr = rho_v1_rr / rho_rr v2_rr = rho_v2_rr / rho_rr - v_normal_ll = (v1_ll * normal_direction[1] + - v2_ll * normal_direction[2]) - v_normal_rr = (v1_rr * normal_direction[1] + - v2_rr * normal_direction[2]) + v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) diff --git a/src/equations/ideal_glm_mhd_3d.jl b/src/equations/ideal_glm_mhd_3d.jl index 2e149d2849f..09990837706 100644 --- a/src/equations/ideal_glm_mhd_3d.jl +++ b/src/equations/ideal_glm_mhd_3d.jl @@ -670,6 +670,77 @@ end return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) end +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations3D) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate primitive variables and speed of sound + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + if orientation == 1 # x-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) + λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) + elseif orientation == 2 # y-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v2_ll - c_f_ll, v2_rr - c_f_rr) + λ_max = max(v2_ll + c_f_ll, v2_rr + c_f_rr) + else # z-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v3_ll - c_f_ll, v3_rr - c_f_rr) + λ_max = max(v3_ll + c_f_ll, v3_rr + c_f_rr) + end + + return λ_min, λ_max +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations3D) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + v_normal_ll = (v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3]) + v_normal_rr = (v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3]) + + c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + + # Estimate the min/max eigenvalues in the normal direction + λ_min = min(v_normal_ll - c_f_ll, v_normal_rr - c_f_rr) + λ_max = max(v_normal_ll + c_f_ll, v_normal_rr + c_f_rr) + + return λ_min, λ_max +end + """ min_max_speed_naive(u_ll, u_rr, orientation_or_normal_direction, equations::IdealGlmMhdEquations3D) diff --git a/src/equations/linearized_euler_2d.jl b/src/equations/linearized_euler_2d.jl index cd681365cae..d497762bf62 100644 --- a/src/equations/linearized_euler_2d.jl +++ b/src/equations/linearized_euler_2d.jl @@ -53,7 +53,7 @@ end function LinearizedEulerEquations2D(; v_mean_global::NTuple{2, <:Real}, c_mean_global::Real, rho_mean_global::Real) - return LinearizedEulerEquations2D(SVector(v_mean_global), c_mean_global, + return LinearizedEulerEquations2D(v_mean_global, c_mean_global, rho_mean_global) end @@ -126,6 +126,24 @@ end return SVector(f1, f2, f3, f4) end +# Calculate 1D flux for a single point +@inline function flux(u, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, v2_prime, p_prime = u + + v_mean_normal = v_mean_global[1] * normal_direction[1] + + v_mean_global[2] * normal_direction[2] + v_prime_normal = v1_prime * normal_direction[1] + v2_prime * normal_direction[2] + + f1 = v_mean_normal * rho_prime + rho_mean_global * v_prime_normal + f2 = v_mean_normal * v1_prime + normal_direction[1] * p_prime / rho_mean_global + f3 = v_mean_normal * v2_prime + normal_direction[2] * p_prime / rho_mean_global + f4 = v_mean_normal * p_prime + c_mean_global^2 * rho_mean_global * v_prime_normal + + return SVector(f1, f2, f3, f4) +end + @inline have_constant_speed(::LinearizedEulerEquations2D) = True() @inline function max_abs_speeds(equations::LinearizedEulerEquations2D) @@ -143,6 +161,236 @@ end end end +@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D) + @unpack v_mean_global, c_mean_global = equations + v_mean_normal = normal_direction[1] * v_mean_global[1] + + normal_direction[2] * v_mean_global[2] + return abs(v_mean_normal) + c_mean_global * norm(normal_direction) +end + +@doc raw""" + flux_godunov(u_ll, u_rr, orientation_or_normal_direction, + equations::LinearizedEulerEquations2D) + +An upwind flux for the linearized Euler equations based on diagonalization of the physical +flux matrix. Given the physical flux ``Au``, ``A=T \Lambda T^{-1}`` with +``\Lambda`` being a diagonal matrix that holds the eigenvalues of ``A``, decompose +``\Lambda = \Lambda^+ + \Lambda^-`` where ``\Lambda^+`` and ``\Lambda^-`` are diagonal +matrices holding the positive and negative eigenvalues of ``A``, respectively. Then for +left and right states ``u_L, u_R``, the numerical flux calculated by this function is given +by ``A^+ u_L + A^- u_R`` where ``A^{\pm} = T \Lambda^{\pm} T^{-1}``. + +The diagonalization of the flux matrix can be found in +- R. F. Warming, Richard M. Beam and B. J. Hyett (1975) + Diagonalization and simultaneous symmetrization of the gas-dynamic matrices + [DOI: 10.1090/S0025-5718-1975-0388967-5](https://doi.org/10.1090/S0025-5718-1975-0388967-5) +""" +@inline function flux_godunov(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations2D) + @unpack v_mean_global, rho_mean_global, c_mean_global = equations + v1_mean = v_mean_global[1] + v2_mean = v_mean_global[2] + + rho_prime_ll, v1_prime_ll, v2_prime_ll, p_prime_ll = u_ll + rho_prime_rr, v1_prime_rr, v2_prime_rr, p_prime_rr = u_rr + + if orientation == 1 + # Eigenvalues of the flux matrix + lambda1 = v1_mean + lambda2 = v1_mean - c_mean_global + lambda3 = v1_mean + c_mean_global + + lambda1_p = positive_part(lambda1) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + lambda2p3_half_p = 0.5 * (lambda2_p + lambda3_p) + lambda3m2_half_p = 0.5 * (lambda3_p - lambda2_p) + + lambda1_m = negative_part(lambda1) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + lambda2p3_half_m = 0.5 * (lambda2_m + lambda3_m) + lambda3m2_half_m = 0.5 * (lambda3_m - lambda2_m) + + f1p = (lambda1_p * rho_prime_ll + + lambda3m2_half_p / c_mean_global * rho_mean_global * v1_prime_ll + + (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll) + f2p = (lambda2p3_half_p * v1_prime_ll + + lambda3m2_half_p / c_mean_global * p_prime_ll / rho_mean_global) + f3p = lambda1_p * v2_prime_ll + f4p = (lambda3m2_half_p * c_mean_global * rho_mean_global * v1_prime_ll + + lambda2p3_half_p * p_prime_ll) + + f1m = (lambda1_m * rho_prime_rr + + lambda3m2_half_m / c_mean_global * rho_mean_global * v1_prime_rr + + (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr) + f2m = (lambda2p3_half_m * v1_prime_rr + + lambda3m2_half_m / c_mean_global * p_prime_rr / rho_mean_global) + f3m = lambda1_m * v2_prime_rr + f4m = (lambda3m2_half_m * c_mean_global * rho_mean_global * v1_prime_rr + + lambda2p3_half_m * p_prime_rr) + + f1 = f1p + f1m + f2 = f2p + f2m + f3 = f3p + f3m + f4 = f4p + f4m + else # orientation == 2 + # Eigenvalues of the flux matrix + lambda1 = v2_mean + lambda2 = v2_mean - c_mean_global + lambda3 = v2_mean + c_mean_global + + lambda1_p = positive_part(lambda1) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + lambda2p3_half_p = 0.5 * (lambda2_p + lambda3_p) + lambda3m2_half_p = 0.5 * (lambda3_p - lambda2_p) + + lambda1_m = negative_part(lambda1) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + lambda2p3_half_m = 0.5 * (lambda2_m + lambda3_m) + lambda3m2_half_m = 0.5 * (lambda3_m - lambda2_m) + + f1p = (lambda1_p * rho_prime_ll + + lambda3m2_half_p / c_mean_global * rho_mean_global * v2_prime_ll + + (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll) + f2p = lambda1_p * v1_prime_ll + f3p = (lambda2p3_half_p * v2_prime_ll + + lambda3m2_half_p / c_mean_global * p_prime_ll / rho_mean_global) + f4p = (lambda3m2_half_p * c_mean_global * rho_mean_global * v2_prime_ll + + lambda2p3_half_p * p_prime_ll) + + f1m = (lambda1_m * rho_prime_rr + + lambda3m2_half_m / c_mean_global * rho_mean_global * v2_prime_rr + + (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr) + f2m = lambda1_m * v1_prime_rr + f3m = (lambda2p3_half_m * v2_prime_rr + + lambda3m2_half_m / c_mean_global * p_prime_rr / rho_mean_global) + f4m = (lambda3m2_half_m * c_mean_global * rho_mean_global * v2_prime_rr + + lambda2p3_half_m * p_prime_rr) + + f1 = f1p + f1m + f2 = f2p + f2m + f3 = f3p + f3m + f4 = f4p + f4m + end + + return SVector(f1, f2, f3, f4) +end + +@inline function flux_godunov(u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D) + @unpack v_mean_global, rho_mean_global, c_mean_global = equations + rho_prime_ll, v1_prime_ll, v2_prime_ll, p_prime_ll = u_ll + rho_prime_rr, v1_prime_rr, v2_prime_rr, p_prime_rr = u_rr + + # Do not use `normalize` since we use `norm_` later to scale the eigenvalues + norm_ = norm(normal_direction) + normal_vector = normal_direction / norm_ + + # Use normalized vector here, scaling is applied via eigenvalues of the flux matrix + v_mean_normal = v_mean_global[1] * normal_vector[1] + + v_mean_global[2] * normal_vector[2] + v_prime_normal_ll = v1_prime_ll * normal_vector[1] + v2_prime_ll * normal_vector[2] + v_prime_normal_rr = v1_prime_rr * normal_vector[1] + v2_prime_rr * normal_vector[2] + + # Eigenvalues of the flux matrix + lambda1 = v_mean_normal * norm_ + lambda2 = (v_mean_normal - c_mean_global) * norm_ + lambda3 = (v_mean_normal + c_mean_global) * norm_ + + lambda1_p = positive_part(lambda1) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + lambda2p3_half_p = 0.5 * (lambda2_p + lambda3_p) + lambda3m2_half_p = 0.5 * (lambda3_p - lambda2_p) + + lambda1_m = negative_part(lambda1) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + lambda2p3_half_m = 0.5 * (lambda2_m + lambda3_m) + lambda3m2_half_m = 0.5 * (lambda3_m - lambda2_m) + + f1p = (lambda1_p * rho_prime_ll + + lambda3m2_half_p / c_mean_global * rho_mean_global * v_prime_normal_ll + + (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll) + f2p = (((lambda1_p * normal_vector[2]^2 + + lambda2p3_half_p * normal_vector[1]^2) * v1_prime_ll + + (lambda2p3_half_p - lambda1_p) * prod(normal_vector) * v2_prime_ll) + + lambda3m2_half_p / c_mean_global * normal_vector[1] * p_prime_ll / + rho_mean_global) + f3p = (((lambda1_p * normal_vector[1]^2 + + lambda2p3_half_p * normal_vector[2]^2) * v2_prime_ll + + (lambda2p3_half_p - lambda1_p) * prod(normal_vector) * v1_prime_ll) + + lambda3m2_half_p / c_mean_global * normal_vector[2] * p_prime_ll / + rho_mean_global) + f4p = (lambda3m2_half_p * c_mean_global * rho_mean_global * v_prime_normal_ll + + lambda2p3_half_p * p_prime_ll) + + f1m = (lambda1_m * rho_prime_rr + + lambda3m2_half_m / c_mean_global * rho_mean_global * v_prime_normal_rr + + (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr) + f2m = (((lambda1_m * normal_vector[2]^2 + + lambda2p3_half_m * normal_vector[1]^2) * v1_prime_rr + + (lambda2p3_half_m - lambda1_m) * prod(normal_vector) * v2_prime_rr) + + lambda3m2_half_m / c_mean_global * normal_vector[1] * p_prime_rr / + rho_mean_global) + f3m = (((lambda1_m * normal_vector[1]^2 + + lambda2p3_half_m * normal_vector[2]^2) * v2_prime_rr + + (lambda2p3_half_m - lambda1_m) * prod(normal_vector) * v1_prime_rr) + + lambda3m2_half_m / c_mean_global * normal_vector[2] * p_prime_rr / + rho_mean_global) + f4m = (lambda3m2_half_m * c_mean_global * rho_mean_global * v_prime_normal_rr + + lambda2p3_half_m * p_prime_rr) + + f1 = f1p + f1m + f2 = f2p + f2m + f3 = f3p + f3m + f4 = f4p + f4m + + return SVector(f1, f2, f3, f4) +end + +# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations2D) + min_max_speed_davis(u_ll, u_rr, orientation, equations) +end + +@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D) + min_max_speed_davis(u_ll, u_rr, normal_direction, equations) +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations2D) + @unpack v_mean_global, c_mean_global = equations + + λ_min = v_mean_global[orientation] - c_mean_global + λ_max = v_mean_global[orientation] + c_mean_global + + return λ_min, λ_max +end + +@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D) + @unpack v_mean_global, c_mean_global = equations + + norm_ = norm(normal_direction) + + v_normal = v_mean_global[1] * normal_direction[1] + + v_mean_global[2] * normal_direction[2] + + # The v_normals are already scaled by the norm + λ_min = v_normal - c_mean_global * norm_ + λ_max = v_normal + c_mean_global * norm_ + + return λ_min, λ_max +end + # Convert conservative variables to primitive @inline cons2prim(u, equations::LinearizedEulerEquations2D) = u @inline cons2entropy(u, ::LinearizedEulerEquations2D) = u diff --git a/src/equations/numerical_fluxes.jl b/src/equations/numerical_fluxes.jl index 16a83124d14..87010275f2c 100644 --- a/src/equations/numerical_fluxes.jl +++ b/src/equations/numerical_fluxes.jl @@ -214,6 +214,10 @@ Create an HLL (Harten, Lax, van Leer) numerical flux where the minimum and maxim wave speeds are estimated as `λ_min, λ_max = min_max_speed(u_ll, u_rr, orientation_or_normal_direction, equations)`, defaulting to [`min_max_speed_naive`](@ref). +Original paper: +- Amiram Harten, Peter D. Lax, Bram van Leer (1983) + On Upstream Differencing and Godunov-Type Schemes for Hyperbolic Conservation Laws + [DOI: 10.1137/1025002](https://doi.org/10.1137/1025002) """ struct FluxHLL{MinMaxSpeed} min_max_speed::MinMaxSpeed @@ -222,18 +226,55 @@ end FluxHLL() = FluxHLL(min_max_speed_naive) """ - min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations) + min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations) min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, equations) -Simple and fast estimate of the minimal and maximal wave speed of the Riemann problem with +Simple and fast estimate(!) of the minimal and maximal wave speed of the Riemann problem with left and right states `u_ll, u_rr`, usually based only on the local wave speeds associated to `u_ll` and `u_rr`. - Amiram Harten, Peter D. Lax, Bram van Leer (1983) On Upstream Differencing and Godunov-Type Schemes for Hyperbolic Conservation Laws [DOI: 10.1137/1025002](https://doi.org/10.1137/1025002) + +See also [`FluxHLL`](@ref), [`min_max_speed_davis`](@ref), [`min_max_speed_einfeldt`](@ref). """ function min_max_speed_naive end +""" + min_max_speed_davis(u_ll, u_rr, orientation::Integer, equations) + min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, equations) + +Simple and fast estimates of the minimal and maximal wave speed of the Riemann problem with +left and right states `u_ll, u_rr`, usually based only on the local wave speeds associated to +`u_ll` and `u_rr`. + +- S.F. Davis (1988) + Simplified Second-Order Godunov-Type Methods + [DOI: 10.1137/0909030](https://doi.org/10.1137/0909030) + +See also [`FluxHLL`](@ref), [`min_max_speed_naive`](@ref), [`min_max_speed_einfeldt`](@ref). +""" +function min_max_speed_davis end + +""" + min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, equations) + min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, equations) + +More advanced mininmal and maximal wave speed computation based on +- Bernd Einfeldt (1988) + On Godunov-type methods for gas dynamics. + [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) +- Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) + On Godunov-type methods near low densities. + [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) + +originally developed for the compressible Euler equations. +A compact representation can be found in [this lecture notes, eq. (9.28)](https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf). + +See also [`FluxHLL`](@ref), [`min_max_speed_naive`](@ref), [`min_max_speed_davis`](@ref). +""" +function min_max_speed_einfeldt end + @inline function (numflux::FluxHLL)(u_ll, u_rr, orientation_or_normal_direction, equations) λ_min, λ_max = numflux.min_max_speed(u_ll, u_rr, orientation_or_normal_direction, @@ -263,6 +304,29 @@ See [`FluxHLL`](@ref). """ const flux_hll = FluxHLL() +# TODO: TrixiShallowWater: move the chen_noelle flux structure to the new package + +# An empty version of the `min_max_speed_chen_noelle` function is declared here +# in order to create a dimension agnostic version of `flux_hll_chen_noelle`. +# The full description of this wave speed estimate can be found in the docstrings +# for `min_max_speed_chen_noelle` in `shallow_water_1d.jl` or `shallow_water_2d.jl`. +function min_max_speed_chen_noelle end + +""" + flux_hll_chen_noelle = FluxHLL(min_max_speed_chen_noelle) + +An instance of [`FluxHLL`](@ref) specific to the shallow water equations that +uses the wave speed estimates from [`min_max_speed_chen_noelle`](@ref). +This HLL flux is guaranteed to have zero numerical mass flux out of a "dry" element, +maintain positivity of the water height, and satisfy an entropy inequality. + +For complete details see Section 2.4 of the following reference +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI: 10.1137/15M1053074](https://doi.org/10.1137/15M1053074) +""" +const flux_hll_chen_noelle = FluxHLL(min_max_speed_chen_noelle) + """ flux_shima_etal_turbo(u_ll, u_rr, orientation_or_normal_direction, equations) diff --git a/src/equations/shallow_water_1d.jl b/src/equations/shallow_water_1d.jl index 3071d96943e..bcde6c4b4f2 100644 --- a/src/equations/shallow_water_1d.jl +++ b/src/equations/shallow_water_1d.jl @@ -6,7 +6,7 @@ #! format: noindent @doc raw""" - ShallowWaterEquations1D(gravity, H0) + ShallowWaterEquations1D(; gravity, H0 = 0, threshold_limiter = nothing threshold_wet = nothing) Shallow water equations (SWE) in one space dimension. The equations are given by ```math @@ -24,6 +24,12 @@ also defines the total water height as ``H = h + b``. The additional quantity ``H_0`` is also available to store a reference value for the total water height that is useful to set initial conditions or test the "lake-at-rest" well-balancedness. +Also, there are two thresholds which prevent numerical problems as well as instabilities. Both of them do not +have to be passed, as default values are defined within the struct. The first one, `threshold_limiter`, is +used in [`PositivityPreservingLimiterShallowWater`](@ref) on the water height, as a (small) shift on the initial +condition and cutoff before the next time step. The second one, `threshold_wet`, is applied on the water height to +define when the flow is "wet" before calculating the numerical flux. + The bottom topography function ``b(x)`` is set inside the initial condition routine for a particular problem setup. To test the conservative form of the SWE one can set the bottom topography variable `b` to zero. @@ -45,16 +51,35 @@ References for the SWE are many but a good introduction is available in Chapter [DOI: 10.1017/CBO9780511791253](https://doi.org/10.1017/CBO9780511791253) """ struct ShallowWaterEquations1D{RealT <: Real} <: AbstractShallowWaterEquations{1, 3} + # TODO: TrixiShallowWater: where should the `threshold_limiter` and `threshold_wet` live? + # how to "properly" export these constants across the two packages? gravity::RealT # gravitational constant H0::RealT # constant "lake-at-rest" total water height + # `threshold_limiter` used in `PositivityPreservingLimiterShallowWater` on water height, + # as a (small) shift on the initial condition and cutoff before the next time step. + # Default is 500*eps() which in double precision is ≈1e-13. + threshold_limiter::RealT + # `threshold_wet` applied on water height to define when the flow is "wet" + # before calculating the numerical flux. + # Default is 5*eps() which in double precision is ≈1e-15. + threshold_wet::RealT end # Allow for flexibility to set the gravitational constant within an elixir depending on the # application where `gravity_constant=1.0` or `gravity_constant=9.81` are common values. # The reference total water height H0 defaults to 0.0 but is used for the "lake-at-rest" -# well-balancedness test cases -function ShallowWaterEquations1D(; gravity_constant, H0 = 0.0) - ShallowWaterEquations1D(gravity_constant, H0) +# well-balancedness test cases. +# Strict default values for thresholds that performed well in many numerical experiments +function ShallowWaterEquations1D(; gravity_constant, H0 = zero(gravity_constant), + threshold_limiter = nothing, threshold_wet = nothing) + T = promote_type(typeof(gravity_constant), typeof(H0)) + if threshold_limiter === nothing + threshold_limiter = 500 * eps(T) + end + if threshold_wet === nothing + threshold_wet = 5 * eps(T) + end + ShallowWaterEquations1D(gravity_constant, H0, threshold_limiter, threshold_wet) end have_nonconservative_terms(::ShallowWaterEquations1D) = True() @@ -307,6 +332,54 @@ Further details on the hydrostatic reconstruction and its motivation can be foun z) end +# TODO: TrixiShallowWater: move wet/dry specific routine +""" + flux_nonconservative_chen_noelle(u_ll, u_rr, + orientation::Integer, + equations::ShallowWaterEquations1D) + +Non-symmetric two-point surface flux that discretizes the nonconservative (source) term. +The discretization uses the `hydrostatic_reconstruction_chen_noelle` on the conservative +variables. + +Should be used together with [`FluxHydrostaticReconstruction`](@ref) and +[`hydrostatic_reconstruction_chen_noelle`](@ref) in the surface flux to ensure consistency. + +Further details on the hydrostatic reconstruction and its motivation can be found in +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +@inline function flux_nonconservative_chen_noelle(u_ll, u_rr, + orientation::Integer, + equations::ShallowWaterEquations1D) + + # Pull the water height and bottom topography on the left + h_ll, _, b_ll = u_ll + h_rr, _, b_rr = u_rr + + H_ll = h_ll + b_ll + H_rr = h_rr + b_rr + + b_star = min(max(b_ll, b_rr), min(H_ll, H_rr)) + + # Create the hydrostatic reconstruction for the left solution state + u_ll_star, _ = hydrostatic_reconstruction_chen_noelle(u_ll, u_rr, equations) + + # Copy the reconstructed water height for easier to read code + h_ll_star = u_ll_star[1] + + z = zero(eltype(u_ll)) + # Includes two parts: + # (i) Diagonal (consistent) term from the volume flux that uses `b_ll` to avoid + # cross-averaging across a discontinuous bottom topography + # (ii) True surface part that uses `h_ll` and `h_ll_star` to handle discontinuous bathymetry + return SVector(z, + equations.gravity * h_ll * b_ll - + equations.gravity * (h_ll_star + h_ll) * (b_ll - b_star), + z) +end + """ flux_nonconservative_ersing_etal(u_ll, u_rr, orientation::Integer, equations::ShallowWaterEquations1D) @@ -419,7 +492,7 @@ end A particular type of hydrostatic reconstruction on the water height to guarantee well-balancedness for a general bottom topography [`ShallowWaterEquations1D`](@ref). The reconstructed solution states -`u_ll_star` and `u_rr_star` variables are used to evaluate the surface numerical flux at the interface. +`u_ll_star` and `u_rr_star` variables are then used to evaluate the surface numerical flux at the interface. Use in combination with the generic numerical flux routine [`FluxHydrostaticReconstruction`](@ref). Further details on this hydrostatic reconstruction and its motivation can be found in @@ -448,6 +521,67 @@ Further details on this hydrostatic reconstruction and its motivation can be fou return u_ll_star, u_rr_star end +# TODO: TrixiShallowWater: move wet/dry specific routine +""" + hydrostatic_reconstruction_chen_noelle(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D) + +A particular type of hydrostatic reconstruction of the water height to guarantee well-balancedness +for a general bottom topography of the [`ShallowWaterEquations1D`](@ref). The reconstructed solution states +`u_ll_star` and `u_rr_star` variables are used to evaluate the surface numerical flux at the interface. +The key idea is a linear reconstruction of the bottom and water height at the interfaces using subcells. +Use in combination with the generic numerical flux routine [`FluxHydrostaticReconstruction`](@ref). + +Further details on this hydrostatic reconstruction and its motivation can be found in +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +@inline function hydrostatic_reconstruction_chen_noelle(u_ll, u_rr, + equations::ShallowWaterEquations1D) + # Unpack left and right water heights and bottom topographies + h_ll, _, b_ll = u_ll + h_rr, _, b_rr = u_rr + + # Get the velocities on either side + v_ll = velocity(u_ll, equations) + v_rr = velocity(u_rr, equations) + + H_ll = b_ll + h_ll + H_rr = b_rr + h_rr + + b_star = min(max(b_ll, b_rr), min(H_ll, H_rr)) + + # Compute the reconstructed water heights + h_ll_star = min(H_ll - b_star, h_ll) + h_rr_star = min(H_rr - b_star, h_rr) + + # Set the water height to be at least the value stored in the variable threshold after + # the hydrostatic reconstruction is applied and before the numerical flux is calculated + # to avoid numerical problem with arbitrary small values. Interfaces with a water height + # lower or equal to the threshold can be declared as dry. + # The default value for `threshold_wet` is ≈ 5*eps(), or 1e-15 in double precision, is set + # in the `ShallowWaterEquations1D` struct. This threshold value can be changed in the constructor + # call of this equation struct in an elixir. + threshold = equations.threshold_wet + + if (h_ll_star <= threshold) + h_ll_star = threshold + v_ll = zero(v_ll) + end + + if (h_rr_star <= threshold) + h_rr_star = threshold + v_rr = zero(v_rr) + end + + # Create the conservative variables using the reconstruted water heights + u_ll_star = SVector(h_ll_star, h_ll_star * v_ll, b_ll) + u_rr_star = SVector(h_rr_star, h_rr_star * v_rr, b_rr) + + return u_ll_star, u_rr_star +end + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the # maximum velocity magnitude plus the maximum speed of sound @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, @@ -498,7 +632,7 @@ end end end -# Calculate minimum and maximum wave speeds for HLL-type fluxes +# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::ShallowWaterEquations1D) h_ll = waterheight(u_ll, equations) @@ -512,6 +646,74 @@ end return λ_min, λ_max end +# TODO: TrixiShallowWater: move wet/dry specific routine +""" + min_max_speed_chen_noelle(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D) + +The approximated speeds for the HLL type numerical flux used by Chen and Noelle for their +hydrostatic reconstruction. As they state in the paper, these speeds are chosen for the numerical +flux to ensure positivity and to satisfy an entropy inequality. + +Further details on this hydrostatic reconstruction and its motivation can be found in +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +@inline function min_max_speed_chen_noelle(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D) + # Get the velocity quantities + v_ll = velocity(u_ll, equations) + v_rr = velocity(u_rr, equations) + + # Calculate the wave celerity on the left and right + h_ll = waterheight(u_ll, equations) + h_rr = waterheight(u_rr, equations) + + a_ll = sqrt(equations.gravity * h_ll) + a_rr = sqrt(equations.gravity * h_rr) + + λ_min = min(v_ll - a_ll, v_rr - a_rr, zero(eltype(u_ll))) + λ_max = max(v_ll + a_ll, v_rr + a_rr, zero(eltype(u_ll))) + + return λ_min, λ_max +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D) + h_ll = waterheight(u_ll, equations) + v_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v_rr = velocity(u_rr, equations) + + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + λ_min = min(v_ll - c_ll, v_rr - c_rr) + λ_max = max(v_rr + c_rr, v_rr + c_rr) + + return λ_min, λ_max +end + +@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D) + h_ll = waterheight(u_ll, equations) + v_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v_rr = velocity(u_rr, equations) + + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) + + λ_min = min(v_ll - c_ll, v_roe - c_roe) + λ_max = max(v_rr + c_rr, v_roe + c_roe) + + return λ_min, λ_max +end + @inline function max_abs_speeds(u, equations::ShallowWaterEquations1D) h = waterheight(u, equations) v = velocity(u, equations) @@ -585,6 +787,35 @@ end return waterheight(u, equations) * pressure(u, equations) end +""" + calc_wavespeed_roe(u_ll, u_rr, direction::Integer, + equations::ShallowWaterEquations1D) + +Calculate Roe-averaged velocity `v_roe` and wavespeed `c_roe = sqrt{g * h_roe}` +See for instance equation (62) in +- Paul A. Ullrich, Christiane Jablonowski, and Bram van Leer (2010) + High-order finite-volume methods for the shallow-water equations on the sphere + [DOI: 10.1016/j.jcp.2010.04.044](https://doi.org/10.1016/j.jcp.2010.04.044) +Or equation (9.17) in [this lecture notes](https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf). +""" +@inline function calc_wavespeed_roe(u_ll, u_rr, direction::Integer, + equations::ShallowWaterEquations1D) + h_ll = waterheight(u_ll, equations) + v_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v_rr = velocity(u_rr, equations) + + h_roe = 0.5 * (h_ll + h_rr) + c_roe = sqrt(equations.gravity * h_roe) + + h_ll_sqrt = sqrt(h_ll) + h_rr_sqrt = sqrt(h_rr) + + v_roe = (h_ll_sqrt * v_ll + h_rr_sqrt * v_rr) / (h_ll_sqrt + h_rr_sqrt) + + return v_roe, c_roe +end + # Entropy function for the shallow water equations is the total energy @inline function entropy(cons, equations::ShallowWaterEquations1D) energy_total(cons, equations) @@ -610,9 +841,20 @@ end end # Calculate the error for the "lake-at-rest" test case where H = h+b should -# be a constant value over time +# be a constant value over time. Note, assumes there is a single reference +# water height `H0` with which to compare. +# +# TODO: TrixiShallowWater: where should `threshold_limiter` live? May need +# to modify or have different versions of the `lake_at_rest_error` function @inline function lake_at_rest_error(u, equations::ShallowWaterEquations1D) h, _, b = u - return abs(equations.H0 - (h + b)) + + # For well-balancedness testing with possible wet/dry regions the reference + # water height `H0` accounts for the possibility that the bottom topography + # can emerge out of the water as well as for the threshold offset to avoid + # division by a "hard" zero water heights as well. + H0_wet_dry = max(equations.H0, b + equations.threshold_limiter) + + return abs(H0_wet_dry - (h + b)) end end # @muladd diff --git a/src/equations/shallow_water_2d.jl b/src/equations/shallow_water_2d.jl index 283cc3e2467..e75c92a27d0 100644 --- a/src/equations/shallow_water_2d.jl +++ b/src/equations/shallow_water_2d.jl @@ -6,7 +6,7 @@ #! format: noindent @doc raw""" - ShallowWaterEquations2D(gravity, H0) + ShallowWaterEquations2D(; gravity, H0 = 0, threshold_limiter = nothing, threshold_wet = nothing) Shallow water equations (SWE) in two space dimensions. The equations are given by ```math @@ -27,6 +27,12 @@ also defines the total water height as ``H = h + b``. The additional quantity ``H_0`` is also available to store a reference value for the total water height that is useful to set initial conditions or test the "lake-at-rest" well-balancedness. +Also, there are two thresholds which prevent numerical problems as well as instabilities. Both of them do not +have to be passed, as default values are defined within the struct. The first one, `threshold_limiter`, is +used in [`PositivityPreservingLimiterShallowWater`](@ref) on the water height, as a (small) shift on the initial +condition and cutoff before the next time step. The second one, `threshold_wet`, is applied on the water height to +define when the flow is "wet" before calculating the numerical flux. + The bottom topography function ``b(x,y)`` is set inside the initial condition routine for a particular problem setup. To test the conservative form of the SWE one can set the bottom topography variable `b` to zero. @@ -48,16 +54,35 @@ References for the SWE are many but a good introduction is available in Chapter [DOI: 10.1017/CBO9780511791253](https://doi.org/10.1017/CBO9780511791253) """ struct ShallowWaterEquations2D{RealT <: Real} <: AbstractShallowWaterEquations{2, 4} + # TODO: TrixiShallowWater: where should the `threshold_limiter` and `threshold_wet` live? + # how to "properly" export these constants across the two packages? gravity::RealT # gravitational constant H0::RealT # constant "lake-at-rest" total water height + # `threshold_limiter` used in `PositivityPreservingLimiterShallowWater` on water height, + # as a (small) shift on the initial condition and cutoff before the next time step. + # Default is 500*eps() which in double precision is ≈1e-13. + threshold_limiter::RealT + # `threshold_wet` applied on water height to define when the flow is "wet" + # before calculating the numerical flux. + # Default is 5*eps() which in double precision is ≈1e-15. + threshold_wet::RealT end # Allow for flexibility to set the gravitational constant within an elixir depending on the # application where `gravity_constant=1.0` or `gravity_constant=9.81` are common values. # The reference total water height H0 defaults to 0.0 but is used for the "lake-at-rest" -# well-balancedness test cases -function ShallowWaterEquations2D(; gravity_constant, H0 = 0.0) - ShallowWaterEquations2D(gravity_constant, H0) +# well-balancedness test cases. +# Strict default values for thresholds that performed well in many numerical experiments +function ShallowWaterEquations2D(; gravity_constant, H0 = zero(gravity_constant), + threshold_limiter = nothing, threshold_wet = nothing) + T = promote_type(typeof(gravity_constant), typeof(H0)) + if threshold_limiter === nothing + threshold_limiter = 500 * eps(T) + end + if threshold_wet === nothing + threshold_wet = 5 * eps(T) + end + ShallowWaterEquations2D(gravity_constant, H0, threshold_limiter, threshold_wet) end have_nonconservative_terms(::ShallowWaterEquations2D) = True() @@ -431,6 +456,69 @@ Further details for the hydrostatic reconstruction and its motivation can be fou return u_ll_star, u_rr_star end +# TODO: TrixiShallowWater: move wet/dry specific routine +""" + hydrostatic_reconstruction_chen_noelle(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + +A particular type of hydrostatic reconstruction of the water height to guarantee well-balancedness +for a general bottom topography of the [`ShallowWaterEquations2D`](@ref). The reconstructed solution states +`u_ll_star` and `u_rr_star` variables are then used to evaluate the surface numerical flux at the interface. +The key idea is a linear reconstruction of the bottom and water height at the interfaces using subcells. +Use in combination with the generic numerical flux routine [`FluxHydrostaticReconstruction`](@ref). + +Further details on this hydrostatic reconstruction and its motivation can be found in +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +@inline function hydrostatic_reconstruction_chen_noelle(u_ll, u_rr, + equations::ShallowWaterEquations2D) + # Unpack left and right water heights and bottom topographies + h_ll, _, _, b_ll = u_ll + h_rr, _, _, b_rr = u_rr + + # Get the velocities on either side + v1_ll, v2_ll = velocity(u_ll, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + H_ll = b_ll + h_ll + H_rr = b_rr + h_rr + + b_star = min(max(b_ll, b_rr), min(H_ll, H_rr)) + + # Compute the reconstructed water heights + h_ll_star = min(H_ll - b_star, h_ll) + h_rr_star = min(H_rr - b_star, h_rr) + + # Set the water height to be at least the value stored in the variable threshold after + # the hydrostatic reconstruction is applied and before the numerical flux is calculated + # to avoid numerical problem with arbitrary small values. Interfaces with a water height + # lower or equal to the threshold can be declared as dry. + # The default value for `threshold_wet` is ≈5*eps(), or 1e-15 in double precision, is set + # in the `ShallowWaterEquations2D` struct. This threshold value can be changed in the constructor + # call of this equation struct in an elixir. + threshold = equations.threshold_wet + + if (h_ll_star <= threshold) + h_ll_star = threshold + v1_ll = zero(v1_ll) + v2_ll = zero(v2_ll) + end + + if (h_rr_star <= threshold) + h_rr_star = threshold + v1_rr = zero(v1_rr) + v2_rr = zero(v2_rr) + end + + # Create the conservative variables using the reconstruted water heights + u_ll_star = SVector(h_ll_star, h_ll_star * v1_ll, h_ll_star * v2_ll, b_ll) + u_rr_star = SVector(h_rr_star, h_rr_star * v1_rr, h_rr_star * v2_rr, b_rr) + + return u_ll_star, u_rr_star +end + """ flux_nonconservative_audusse_etal(u_ll, u_rr, orientation::Integer, equations::ShallowWaterEquations2D) @@ -516,6 +604,104 @@ end return SVector(f1, f2, f3, f4) end +# TODO: TrixiShallowWater: move wet/dry specific routine +""" + flux_nonconservative_chen_noelle(u_ll, u_rr, + orientation::Integer, + equations::ShallowWaterEquations2D) + flux_nonconservative_chen_noelle(u_ll, u_rr, + normal_direction_ll ::AbstractVector, + normal_direction_average ::AbstractVector, + equations::ShallowWaterEquations2D) + +Non-symmetric two-point surface flux that discretizes the nonconservative (source) term. +The discretization uses the [`hydrostatic_reconstruction_chen_noelle`](@ref) on the conservative +variables. + +Should be used together with [`FluxHydrostaticReconstruction`](@ref) and +[`hydrostatic_reconstruction_chen_noelle`](@ref) in the surface flux to ensure consistency. + +Further details on the hydrostatic reconstruction and its motivation can be found in +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +@inline function flux_nonconservative_chen_noelle(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + # Pull the water height and bottom topography on the left + h_ll, _, _, b_ll = u_ll + h_rr, _, _, b_rr = u_rr + + H_ll = h_ll + b_ll + H_rr = h_rr + b_rr + + b_star = min(max(b_ll, b_rr), min(H_ll, H_rr)) + + # Create the hydrostatic reconstruction for the left solution state + u_ll_star, _ = hydrostatic_reconstruction_chen_noelle(u_ll, u_rr, equations) + + # Copy the reconstructed water height for easier to read code + h_ll_star = u_ll_star[1] + + z = zero(eltype(u_ll)) + # Includes two parts: + # (i) Diagonal (consistent) term from the volume flux that uses `b_ll` to avoid + # cross-averaging across a discontinuous bottom topography + # (ii) True surface part that uses `h_ll` and `h_ll_star` to handle discontinuous bathymetry + g = equations.gravity + if orientation == 1 + f = SVector(z, + g * h_ll * b_ll - g * (h_ll_star + h_ll) * (b_ll - b_star), + z, z) + else # orientation == 2 + f = SVector(z, z, + g * h_ll * b_ll - g * (h_ll_star + h_ll) * (b_ll - b_star), + z) + end + + return f +end + +@inline function flux_nonconservative_chen_noelle(u_ll, u_rr, + normal_direction_ll::AbstractVector, + normal_direction_average::AbstractVector, + equations::ShallowWaterEquations2D) + # Pull the water height and bottom topography on the left + h_ll, _, _, b_ll = u_ll + h_rr, _, _, b_rr = u_rr + + H_ll = h_ll + b_ll + H_rr = h_rr + b_rr + + b_star = min(max(b_ll, b_rr), min(H_ll, H_rr)) + + # Create the hydrostatic reconstruction for the left solution state + u_ll_star, _ = hydrostatic_reconstruction_chen_noelle(u_ll, u_rr, equations) + + # Copy the reconstructed water height for easier to read code + h_ll_star = u_ll_star[1] + + # Comes in two parts: + # (i) Diagonal (consistent) term from the volume flux that uses `normal_direction_average` + # but we use `b_ll` to avoid cross-averaging across a discontinuous bottom topography + + f2 = normal_direction_average[1] * equations.gravity * h_ll * b_ll + f3 = normal_direction_average[2] * equations.gravity * h_ll * b_ll + + # (ii) True surface part that uses `normal_direction_ll`, `h_ll` and `h_ll_star` + # to handle discontinuous bathymetry + + f2 -= normal_direction_ll[1] * equations.gravity * (h_ll_star + h_ll) * + (b_ll - b_star) + f3 -= normal_direction_ll[2] * equations.gravity * (h_ll_star + h_ll) * + (b_ll - b_star) + + # First and last equations do not have a nonconservative flux + f1 = f4 = zero(eltype(u_ll)) + + return SVector(f1, f2, f3, f4) +end + """ flux_nonconservative_ersing_etal(u_ll, u_rr, orientation::Integer, equations::ShallowWaterEquations2D) @@ -793,7 +979,7 @@ end end end -# Calculate minimum and maximum wave speeds for HLL-type fluxes +# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes @inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations::ShallowWaterEquations2D) h_ll = waterheight(u_ll, equations) @@ -830,6 +1016,155 @@ end return λ_min, λ_max end +# TODO: TrixiShallowWater: move wet/dry specific routine +""" + min_max_speed_chen_noelle(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + min_max_speed_chen_noelle(u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D) + +Special estimate of the minimal and maximal wave speed of the shallow water equations for +the left and right states `u_ll, u_rr`. These approximate speeds are used for the HLL-type +numerical flux [`flux_hll_chen_noelle`](@ref). These wave speed estimates +together with a particular hydrostatic reconstruction technique guarantee +that the numerical flux is positive and satisfies an entropy inequality. + +Further details on this hydrostatic reconstruction and its motivation can be found in +the reference below. The definition of the wave speeds are given in Equation (2.20). +- Guoxian Chen and Sebastian Noelle (2017) + A new hydrostatic reconstruction scheme based on subcell reconstructions + [DOI:10.1137/15M1053074](https://dx.doi.org/10.1137/15M1053074) +""" +@inline function min_max_speed_chen_noelle(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + a_ll = sqrt(equations.gravity * h_ll) + a_rr = sqrt(equations.gravity * h_rr) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - a_ll, v1_rr - a_rr, zero(eltype(u_ll))) + λ_max = max(v1_ll + a_ll, v1_rr + a_rr, zero(eltype(u_ll))) + else # y-direction + λ_min = min(v2_ll - a_ll, v2_rr - a_rr, zero(eltype(u_ll))) + λ_max = max(v2_ll + a_ll, v2_rr + a_rr, zero(eltype(u_ll))) + end + + return λ_min, λ_max +end + +@inline function min_max_speed_chen_noelle(u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + norm_ = norm(normal_direction) + + a_ll = sqrt(equations.gravity * h_ll) * norm_ + a_rr = sqrt(equations.gravity * h_rr) * norm_ + + λ_min = min(v_normal_ll - a_ll, v_normal_rr - a_rr, zero(eltype(u_ll))) + λ_max = max(v_normal_ll + a_ll, v_normal_rr + a_rr, zero(eltype(u_ll))) + + return λ_min, λ_max +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + else # y-direction + λ_min = min(v2_ll - c_ll, v2_rr - c_rr) + λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + end + + return λ_min, λ_max +end + +@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + norm_ = norm(normal_direction) + c_ll = sqrt(equations.gravity * h_ll) * norm_ + c_rr = sqrt(equations.gravity * h_rr) * norm_ + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # The v_normals are already scaled by the norm + λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) + λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) + + return λ_min, λ_max +end + +@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + if orientation == 1 # x-direction + v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v1_ll - c_ll, v_roe - c_roe) + λ_max = max(v1_rr + c_rr, v_roe + c_roe) + else # y-direction + v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v2_ll - c_ll, v_roe - c_roe) + λ_max = max(v2_rr + c_rr, v_roe + c_roe) + end + + return λ_min, λ_max +end + +@inline function min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + norm_ = norm(normal_direction) + + c_ll = sqrt(equations.gravity * h_ll) * norm_ + c_rr = sqrt(equations.gravity * h_rr) * norm_ + + v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) + + v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, normal_direction, equations) + λ_min = min(v_normal_ll - c_ll, v_roe - c_roe) + λ_max = max(v_normal_rr + c_rr, v_roe + c_roe) + + return λ_min, λ_max +end + @inline function max_abs_speeds(u, equations::ShallowWaterEquations2D) h = waterheight(u, equations) v1, v2 = velocity(u, equations) @@ -905,6 +1240,63 @@ end return waterheight(u, equations) * pressure(u, equations) end +""" + calc_wavespeed_roe(u_ll, u_rr, direction::Integer, + equations::ShallowWaterEquations2D) + +Calculate Roe-averaged velocity `v_roe` and wavespeed `c_roe = sqrt{g * h_roe}` depending on direction. +See for instance equation (62) in +- Paul A. Ullrich, Christiane Jablonowski, and Bram van Leer (2010) + High-order finite-volume methods for the shallow-water equations on the sphere + [DOI: 10.1016/j.jcp.2010.04.044](https://doi.org/10.1016/j.jcp.2010.04.044) +Or [this slides](https://faculty.washington.edu/rjl/classes/am574w2011/slides/am574lecture20nup3.pdf), +slides 8 and 9. +""" +@inline function calc_wavespeed_roe(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + h_roe = 0.5 * (h_ll + h_rr) + c_roe = sqrt(equations.gravity * h_roe) + + h_ll_sqrt = sqrt(h_ll) + h_rr_sqrt = sqrt(h_rr) + + if orientation == 1 # x-direction + v_roe = (h_ll_sqrt * v1_ll + h_rr_sqrt * v1_rr) / (h_ll_sqrt + h_rr_sqrt) + else # y-direction + v_roe = (h_ll_sqrt * v2_ll + h_rr_sqrt * v2_rr) / (h_ll_sqrt + h_rr_sqrt) + end + + return v_roe, c_roe +end + +@inline function calc_wavespeed_roe(u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + norm_ = norm(normal_direction) + + h_roe = 0.5 * (h_ll + h_rr) + c_roe = sqrt(equations.gravity * h_roe) * norm_ + + h_ll_sqrt = sqrt(h_ll) + h_rr_sqrt = sqrt(h_rr) + + v1_roe = (h_ll_sqrt * v1_ll + h_rr_sqrt * v1_rr) / (h_ll_sqrt + h_rr_sqrt) + v2_roe = (h_ll_sqrt * v2_ll + h_rr_sqrt * v2_rr) / (h_ll_sqrt + h_rr_sqrt) + + v_roe = (v1_roe * normal_direction[1] + v2_roe * normal_direction[2]) + + return v_roe, c_roe +end + # Entropy function for the shallow water equations is the total energy @inline function entropy(cons, equations::ShallowWaterEquations2D) energy_total(cons, equations) @@ -931,9 +1323,20 @@ end end # Calculate the error for the "lake-at-rest" test case where H = h+b should -# be a constant value over time +# be a constant value over time. Note, assumes there is a single reference +# water height `H0` with which to compare. +# +# TODO: TrixiShallowWater: where should `threshold_limiter` live? May need +# to modify or have different versions of the `lake_at_rest_error` function @inline function lake_at_rest_error(u, equations::ShallowWaterEquations2D) h, _, _, b = u - return abs(equations.H0 - (h + b)) + + # For well-balancedness testing with possible wet/dry regions the reference + # water height `H0` accounts for the possibility that the bottom topography + # can emerge out of the water as well as for the threshold offset to avoid + # division by a "hard" zero water heights as well. + H0_wet_dry = max(equations.H0, b + equations.threshold_limiter) + + return abs(H0_wet_dry - (h + b)) end end # @muladd diff --git a/src/equations/shallow_water_two_layer_1d.jl b/src/equations/shallow_water_two_layer_1d.jl index a3ef3f82322..79f691b6985 100644 --- a/src/equations/shallow_water_two_layer_1d.jl +++ b/src/equations/shallow_water_two_layer_1d.jl @@ -5,6 +5,8 @@ @muladd begin #! format: noindent +# TODO: TrixiShallowWater: 1D two layer equations should move to new package + @doc raw""" ShallowWaterTwoLayerEquations1D(gravity, H0, rho_upper, rho_lower) diff --git a/src/equations/shallow_water_two_layer_2d.jl b/src/equations/shallow_water_two_layer_2d.jl index 365c3df6bc9..06f3828967f 100644 --- a/src/equations/shallow_water_two_layer_2d.jl +++ b/src/equations/shallow_water_two_layer_2d.jl @@ -5,48 +5,50 @@ @muladd begin #! format: noindent +# TODO: TrixiShallowWater: 2D two layer equations should move to new package + @doc raw""" ShallowWaterTwoLayerEquations2D(gravity, H0, rho_upper, rho_lower) Two-Layer Shallow water equations (2LSWE) in two space dimension. The equations are given by ```math \begin{alignat*}{8} -&\frac{\partial}{\partial t}h_{upper} +&\frac{\partial}{\partial t}h_{upper} &&+ \frac{\partial}{\partial x}\left(h_{upper} v_{1,upper}\right) -&&+ \frac{\partial}{\partial y}\left(h_{upper} v_{2,upper}\right) \quad +&&+ \frac{\partial}{\partial y}\left(h_{upper} v_{2,upper}\right) \quad &&= \quad 0 \\ -&\frac{\partial}{\partial t}\left(h_{upper} v_{1,upper}\right) -&&+ \frac{\partial}{\partial x}\left(h_{upper} v_{1,upper}^2 + \frac{gh_{upper}^2}{2}\right) -&&+ \frac{\partial}{\partial y}\left(h_{upper} v_{1,upper} v_{2,upper}\right) \quad +&\frac{\partial}{\partial t}\left(h_{upper} v_{1,upper}\right) +&&+ \frac{\partial}{\partial x}\left(h_{upper} v_{1,upper}^2 + \frac{gh_{upper}^2}{2}\right) +&&+ \frac{\partial}{\partial y}\left(h_{upper} v_{1,upper} v_{2,upper}\right) \quad &&= -gh_{upper}\frac{\partial}{\partial x}\left(b+h_{lower}\right) \\ -&\frac{\partial}{\partial t}\left(h_{upper} v_{2,upper}\right) -&&+ \frac{\partial}{\partial x}\left(h_{upper} v_{1,upper} v_{2,upper}\right) -&&+ \frac{\partial}{\partial y}\left(h_{upper} v_{2,upper}^2 + \frac{gh_{upper}^2}{2}\right) +&\frac{\partial}{\partial t}\left(h_{upper} v_{2,upper}\right) +&&+ \frac{\partial}{\partial x}\left(h_{upper} v_{1,upper} v_{2,upper}\right) +&&+ \frac{\partial}{\partial y}\left(h_{upper} v_{2,upper}^2 + \frac{gh_{upper}^2}{2}\right) &&= -gh_{upper}\frac{\partial}{\partial y}\left(b+h_{lower}\right)\\ -&\frac{\partial}{\partial t}h_{lower} -&&+ \frac{\partial}{\partial x}\left(h_{lower} v_{1,lower}\right) -&&+ \frac{\partial}{\partial y}\left(h_{lower} v_{2,lower}\right) +&\frac{\partial}{\partial t}h_{lower} +&&+ \frac{\partial}{\partial x}\left(h_{lower} v_{1,lower}\right) +&&+ \frac{\partial}{\partial y}\left(h_{lower} v_{2,lower}\right) &&= \quad 0 \\ -&\frac{\partial}{\partial t}\left(h_{lower} v_{1,lower}\right) -&&+ \frac{\partial}{\partial x}\left(h_{lower} v_{1,lower}^2 + \frac{gh_{lower}^2}{2}\right) -&&+ \frac{\partial}{\partial y}\left(h_{lower} v_{1,lower} v_{2,lower}\right) +&\frac{\partial}{\partial t}\left(h_{lower} v_{1,lower}\right) +&&+ \frac{\partial}{\partial x}\left(h_{lower} v_{1,lower}^2 + \frac{gh_{lower}^2}{2}\right) +&&+ \frac{\partial}{\partial y}\left(h_{lower} v_{1,lower} v_{2,lower}\right) &&= -gh_{lower}\frac{\partial}{\partial x}\left(b+\frac{\rho_{upper}}{\rho_{lower}} h_{upper}\right)\\ -&\frac{\partial}{\partial t}\left(h_{lower} v_{2,lower}\right) -&&+ \frac{\partial}{\partial x}\left(h_{lower} v_{1,lower} v_{2,lower}\right) -&&+ \frac{\partial}{\partial y}\left(h_{lower} v_{2,lower}^2 + \frac{gh_{lower}^2}{2}\right) +&\frac{\partial}{\partial t}\left(h_{lower} v_{2,lower}\right) +&&+ \frac{\partial}{\partial x}\left(h_{lower} v_{1,lower} v_{2,lower}\right) +&&+ \frac{\partial}{\partial y}\left(h_{lower} v_{2,lower}^2 + \frac{gh_{lower}^2}{2}\right) &&= -gh_{lower}\frac{\partial}{\partial y}\left(b+\frac{\rho_{upper}}{\rho_{lower}} h_{upper}\right) \end{alignat*} ``` -The unknown quantities of the 2LSWE are the water heights of the lower layer ``h_{lower}`` and the -upper +The unknown quantities of the 2LSWE are the water heights of the lower layer ``h_{lower}`` and the +upper layer ``h_{upper}`` and the respective velocities in x-direction ``v_{1,lower}`` and ``v_{1,upper}`` and in y-direction -``v_{2,lower}`` and ``v_{2,upper}``. The gravitational constant is denoted by `g`, the layer densitites by -``\rho_{upper}``and ``\rho_{lower}`` and the (possibly) variable bottom topography function by ``b(x)``. -Conservative variable water height ``h_{lower}`` is measured from the bottom topography ``b`` and ``h_{upper}`` -relative to ``h_{lower}``, therefore one also defines the total water heights as ``H_{lower} = h_{lower} + b`` and +``v_{2,lower}`` and ``v_{2,upper}``. The gravitational constant is denoted by `g`, the layer densitites by +``\rho_{upper}``and ``\rho_{lower}`` and the (possibly) variable bottom topography function by ``b(x)``. +Conservative variable water height ``h_{lower}`` is measured from the bottom topography ``b`` and ``h_{upper}`` +relative to ``h_{lower}``, therefore one also defines the total water heights as ``H_{lower} = h_{lower} + b`` and ``H_{upper} = h_{upper} + h_{lower} + b``. -The densities must be chosen such that ``\rho_{upper} < \rho_{lower}``, to make sure that the heavier fluid +The densities must be chosen such that ``\rho_{upper} < \rho_{lower}``, to make sure that the heavier fluid ``\rho_{lower}`` is in the bottom layer and the lighter fluid ``\rho_{upper}`` in the upper layer. The additional quantity ``H_0`` is also available to store a reference value for the total water @@ -55,13 +57,13 @@ height that is useful to set initial conditions or test the "lake-at-rest" well- The bottom topography function ``b(x)`` is set inside the initial condition routine for a particular problem setup. -In addition to the unknowns, Trixi currently stores the bottom topography values at the -approximation points despite being fixed in time. This is done for convenience of computing the -bottom topography gradients on the fly during the approximation as well as computing auxiliary +In addition to the unknowns, Trixi currently stores the bottom topography values at the +approximation points despite being fixed in time. This is done for convenience of computing the +bottom topography gradients on the fly during the approximation as well as computing auxiliary quantities like the total water height ``H`` or the entropy variables. This affects the implementation and use of these equations in various ways: * The flux values corresponding to the bottom topography must be zero. -* The bottom topography values must be included when defining initial conditions, boundary +* The bottom topography values must be included when defining initial conditions, boundary conditions or source terms. * [`AnalysisCallback`](@ref) analyzes this variable. * Trixi's visualization tools will visualize the bottom topography by default. @@ -113,7 +115,7 @@ end initial_condition_convergence_test(x, t, equations::ShallowWaterTwoLayerEquations2D) A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref). Constants must be set to ``rho_{upper} = 0.9``, +[`source_terms_convergence_test`](@ref). Constants must be set to ``rho_{upper} = 0.9``, ``rho_{lower} = 1.0``, ``g = 10.0``. """ function initial_condition_convergence_test(x, t, @@ -141,7 +143,7 @@ Source terms used for convergence tests in combination with """ @inline function source_terms_convergence_test(u, x, t, equations::ShallowWaterTwoLayerEquations2D) - # Same settings as in `initial_condition_convergence_test`. + # Same settings as in `initial_condition_convergence_test`. # some constants are chosen such that the function is periodic on the domain [0,sqrt(2)]^2] ω = 2.0 * pi * sqrt(2.0) @@ -354,7 +356,7 @@ For further details see: z = zero(eltype(u_ll)) # Bottom gradient nonconservative term: (0, g*h_upper*(b + h_lower)_x, g*h_upper*(b + h_lower)_y , - # 0, g*h_lower*(b + r*h_upper)_x, + # 0, g*h_lower*(b + r*h_upper)_x, # g*h_lower*(b + r*h_upper)_y, 0) if orientation == 1 f = SVector(z, @@ -408,7 +410,7 @@ end """ flux_wintermeyer_etal(u_ll, u_rr, orientation, equations::ShallowWaterTwoLayerEquations2D) - + Total energy conservative (mathematical entropy for two-layer shallow water equations) split form. When the bottom topography is nonzero this scheme will be well-balanced when used with the nonconservative [`flux_nonconservative_ersing_etal`](@ref). To obtain the flux for the @@ -523,7 +525,7 @@ For further details see: q_rr = cons2entropy(u_rr, equations) q_ll = cons2entropy(u_ll, equations) - # Average values from left and right + # Average values from left and right u_avg = (u_ll + u_rr) / 2 # Introduce variables for better readability @@ -591,10 +593,10 @@ For further details see: end # Calculate approximation for maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound. This function uses approximate -# eigenvalues using the speed of the barotropic mode as there is no simple way to calculate them -# analytically. -# +# maximum velocity magnitude plus the maximum speed of sound. This function uses approximate +# eigenvalues using the speed of the barotropic mode as there is no simple way to calculate them +# analytically. +# # A good overview of the derivation is given in: # - Jonas Nycander, Andrew McC. Hogg, Leela M. Frankcombe (2008) # Open boundary conditions for nonlinear channel Flows @@ -714,7 +716,7 @@ end # Convert conservative variables to entropy variables # Note, only the first four are the entropy variables, the fifth entry still just carries the bottom -# topography values for convenience. +# topography values for convenience. # In contrast to general usage the entropy variables are denoted with q instead of w, because w is # already used for velocity in y-Direction @inline function cons2entropy(u, equations::ShallowWaterTwoLayerEquations2D) diff --git a/src/meshes/mesh_io.jl b/src/meshes/mesh_io.jl index ede85d80106..b9895e7d454 100644 --- a/src/meshes/mesh_io.jl +++ b/src/meshes/mesh_io.jl @@ -6,7 +6,7 @@ #! format: noindent # Save current mesh with some context information as an HDF5 file. -function save_mesh_file(mesh::Union{TreeMesh, P4estMesh}, output_directory, +function save_mesh_file(mesh::Union{TreeMesh, P4estMesh, T8codeMesh}, output_directory, timestep = 0) save_mesh_file(mesh, output_directory, timestep, mpi_parallel(mesh)) end @@ -220,6 +220,13 @@ function save_mesh_file(mesh::P4estMesh, output_directory, timestep, mpi_paralle return filename end +# TODO: Implement this function as soon as there is support for this in `t8code`. +function save_mesh_file(mesh::T8codeMesh, output_directory, timestep, mpi_parallel) + error("Mesh file output not supported yet for `T8codeMesh`.") + + return joinpath(output_directory, "dummy_mesh.h5") +end + """ load_mesh(restart_file::AbstractString; n_cells_max) @@ -286,6 +293,7 @@ function load_mesh_serial(mesh_file::AbstractString; n_cells_max, RealT) mesh = StructuredMesh(size, mapping; RealT = RealT, unsaved_changes = false, mapping_as_string = mapping_as_string) + mesh.current_filename = mesh_file elseif mesh_type == "UnstructuredMesh2D" mesh_filename, periodicity_ = h5open(mesh_file, "r") do file return read(attributes(file)["mesh_filename"]), diff --git a/src/meshes/meshes.jl b/src/meshes/meshes.jl index 2716aa2007b..ed2158b169a 100644 --- a/src/meshes/meshes.jl +++ b/src/meshes/meshes.jl @@ -12,6 +12,7 @@ include("unstructured_mesh.jl") include("face_interpolant.jl") include("transfinite_mappings_3d.jl") include("p4est_mesh.jl") +include("t8code_mesh.jl") include("mesh_io.jl") include("dgmulti_meshes.jl") end # @muladd diff --git a/src/meshes/t8code_mesh.jl b/src/meshes/t8code_mesh.jl new file mode 100644 index 00000000000..13edcc29711 --- /dev/null +++ b/src/meshes/t8code_mesh.jl @@ -0,0 +1,345 @@ +""" + T8codeMesh{NDIMS} <: AbstractMesh{NDIMS} + +An unstructured curved mesh based on trees that uses the C library +['t8code'](https://github.com/DLR-AMR/t8code) +to manage trees and mesh refinement. +""" +mutable struct T8codeMesh{NDIMS, RealT <: Real, IsParallel, NDIMSP2, NNODES} <: + AbstractMesh{NDIMS} + cmesh :: Ptr{t8_cmesh} # cpointer to coarse mesh + scheme :: Ptr{t8_eclass_scheme} # cpointer to element scheme + forest :: Ptr{t8_forest} # cpointer to forest + is_parallel :: IsParallel + + # This specifies the geometry interpolation for each tree. + tree_node_coordinates::Array{RealT, NDIMSP2} # [dimension, i, j, k, tree] + + # Stores the quadrature nodes. + nodes::SVector{NNODES, RealT} + + boundary_names :: Array{Symbol, 2} # [face direction, tree] + current_filename :: String + + ninterfaces :: Int + nmortars :: Int + nboundaries :: Int + + function T8codeMesh{NDIMS}(cmesh, scheme, forest, tree_node_coordinates, nodes, + boundary_names, + current_filename) where {NDIMS} + is_parallel = False() + + mesh = new{NDIMS, Float64, typeof(is_parallel), NDIMS + 2, length(nodes)}(cmesh, + scheme, + forest, + is_parallel) + + mesh.nodes = nodes + mesh.boundary_names = boundary_names + mesh.current_filename = current_filename + mesh.tree_node_coordinates = tree_node_coordinates + + finalizer(mesh) do mesh + # When finalizing `mesh.forest`, `mesh.scheme` and `mesh.cmesh` are + # also cleaned up from within `t8code`. The cleanup code for + # `cmesh` does some MPI calls for deallocating shared memory + # arrays. Due to garbage collection in Julia the order of shutdown + # is not deterministic. The following code might happen after MPI + # is already in finalized state. + # If the environment variable `TRIXI_T8CODE_SC_FINALIZE` is set the + # `finalize_hook` of the MPI module takes care of the cleanup. See + # further down. However, this might cause a pile-up of `mesh` + # objects during long-running sessions. + if !MPI.Finalized() + trixi_t8_unref_forest(mesh.forest) + end + end + + # This finalizer call is only recommended during development and not for + # production runs, especially long-running sessions since a reference to + # the `mesh` object will be kept throughout the lifetime of the session. + # See comments in `init_t8code()` in file `src/auxiliary/t8code.jl` for + # more information. + if haskey(ENV, "TRIXI_T8CODE_SC_FINALIZE") + MPI.add_finalize_hook!() do + trixi_t8_unref_forest(mesh.forest) + end + end + + return mesh + end +end + +const SerialT8codeMesh{NDIMS} = T8codeMesh{NDIMS, <:Real, <:False} +@inline mpi_parallel(mesh::SerialT8codeMesh) = False() + +@inline Base.ndims(::T8codeMesh{NDIMS}) where {NDIMS} = NDIMS +@inline Base.real(::T8codeMesh{NDIMS, RealT}) where {NDIMS, RealT} = RealT + +@inline ntrees(mesh::T8codeMesh) = Int(t8_forest_get_num_local_trees(mesh.forest)) +@inline ncells(mesh::T8codeMesh) = Int(t8_forest_get_local_num_elements(mesh.forest)) +@inline ninterfaces(mesh::T8codeMesh) = mesh.ninterfaces +@inline nmortars(mesh::T8codeMesh) = mesh.nmortars +@inline nboundaries(mesh::T8codeMesh) = mesh.nboundaries + +function Base.show(io::IO, mesh::T8codeMesh) + print(io, "T8codeMesh{", ndims(mesh), ", ", real(mesh), "}") +end + +function Base.show(io::IO, ::MIME"text/plain", mesh::T8codeMesh) + if get(io, :compact, false) + show(io, mesh) + else + setup = [ + "#trees" => ntrees(mesh), + "current #cells" => ncells(mesh), + "polydeg" => length(mesh.nodes) - 1, + ] + summary_box(io, + "T8codeMesh{" * string(ndims(mesh)) * ", " * string(real(mesh)) * "}", + setup) + end +end + +""" + T8codeMesh(trees_per_dimension; polydeg, mapping=identity, + RealT=Float64, initial_refinement_level=0, periodicity=true) + +Create a structured potentially curved 'T8codeMesh' of the specified size. + +Non-periodic boundaries will be called ':x_neg', ':x_pos', ':y_neg', ':y_pos', ':z_neg', ':z_pos'. + +# Arguments +- 'trees_per_dimension::NTupleE{NDIMS, Int}': the number of trees in each dimension. +- 'polydeg::Integer': polynomial degree used to store the geometry of the mesh. + The mapping will be approximated by an interpolation polynomial + of the specified degree for each tree. +- 'mapping': a function of 'NDIMS' variables to describe the mapping that transforms + the reference mesh ('[-1, 1]^n') to the physical domain. +- 'RealT::Type': the type that should be used for coordinates. +- 'initial_refinement_level::Integer': refine the mesh uniformly to this level before the simulation starts. +- 'periodicity': either a 'Bool' deciding if all of the boundaries are periodic or an 'NTuple{NDIMS, Bool}' + deciding for each dimension if the boundaries in this dimension are periodic. +""" +function T8codeMesh(trees_per_dimension; polydeg, + mapping = coordinates2mapping((-1.0, -1.0), (1.0, 1.0)), + RealT = Float64, initial_refinement_level = 0, periodicity = true) + NDIMS = length(trees_per_dimension) + + @assert NDIMS == 2 # Only support for NDIMS = 2 yet. + + # Convert periodicity to a Tuple of a Bool for every dimension + if all(periodicity) + # Also catches case where periodicity = true + periodicity = ntuple(_ -> true, NDIMS) + elseif !any(periodicity) + # Also catches case where periodicity = false + periodicity = ntuple(_ -> false, NDIMS) + else + # Default case if periodicity is an iterable + periodicity = Tuple(periodicity) + end + + conn = T8code.Libt8.p4est_connectivity_new_brick(trees_per_dimension..., periodicity...) + do_partition = 0 + cmesh = t8_cmesh_new_from_p4est(conn, mpi_comm(), do_partition) + T8code.Libt8.p4est_connectivity_destroy(conn) + + scheme = t8_scheme_new_default_cxx() + forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, 0, mpi_comm()) + + basis = LobattoLegendreBasis(RealT, polydeg) + nodes = basis.nodes + + tree_node_coordinates = Array{RealT, NDIMS + 2}(undef, NDIMS, + ntuple(_ -> length(nodes), NDIMS)..., + prod(trees_per_dimension)) + + # Get cell length in reference mesh: Omega_ref = [-1,1]^2. + dx = 2 / trees_per_dimension[1] + dy = 2 / trees_per_dimension[2] + + num_local_trees = t8_cmesh_get_num_local_trees(cmesh) + + # Non-periodic boundaries. + boundary_names = fill(Symbol("---"), 2 * NDIMS, prod(trees_per_dimension)) + + for itree in 1:num_local_trees + veptr = t8_cmesh_get_tree_vertices(cmesh, itree - 1) + verts = unsafe_wrap(Array, veptr, (3, 1 << NDIMS)) + + # Calculate node coordinates of reference mesh. + cell_x_offset = (verts[1, 1] - 1 / 2 * (trees_per_dimension[1] - 1)) * dx + cell_y_offset = (verts[2, 1] - 1 / 2 * (trees_per_dimension[2] - 1)) * dy + + for j in eachindex(nodes), i in eachindex(nodes) + tree_node_coordinates[:, i, j, itree] .= mapping(cell_x_offset + + dx * nodes[i] / 2, + cell_y_offset + + dy * nodes[j] / 2) + end + + if !periodicity[1] + boundary_names[1, itree] = :x_neg + boundary_names[2, itree] = :x_pos + end + + if !periodicity[2] + boundary_names[3, itree] = :y_neg + boundary_names[4, itree] = :y_pos + end + end + + return T8codeMesh{NDIMS}(cmesh, scheme, forest, tree_node_coordinates, nodes, + boundary_names, "") +end + +""" + T8codeMesh{NDIMS}(cmesh::Ptr{t8_cmesh}, + mapping=nothing, polydeg=1, RealT=Float64, + initial_refinement_level=0) + +Main mesh constructor for the `T8codeMesh` that imports an unstructured, +conforming mesh from a `t8_cmesh` data structure. + +# Arguments +- `cmesh::Ptr{t8_cmesh}`: Pointer to a cmesh object. +- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms + the imported mesh to the physical domain. Use `nothing` for the identity map. +- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. + The mapping will be approximated by an interpolation polynomial + of the specified degree for each tree. + The default of `1` creates an uncurved geometry. Use a higher value if the mapping + will curve the imported uncurved mesh. +- `RealT::Type`: the type that should be used for coordinates. +- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. +""" +function T8codeMesh{NDIMS}(cmesh::Ptr{t8_cmesh}; + mapping = nothing, polydeg = 1, RealT = Float64, + initial_refinement_level = 0) where {NDIMS} + @assert NDIMS == 2 # Only support for NDIMS = 2 yet. + + scheme = t8_scheme_new_default_cxx() + forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, 0, mpi_comm()) + + basis = LobattoLegendreBasis(RealT, polydeg) + nodes = basis.nodes + + num_local_trees = t8_cmesh_get_num_local_trees(cmesh) + + tree_node_coordinates = Array{RealT, NDIMS + 2}(undef, NDIMS, + ntuple(_ -> length(nodes), NDIMS)..., + num_local_trees) + + nodes_in = [-1.0, 1.0] + matrix = polynomial_interpolation_matrix(nodes_in, nodes) + data_in = Array{RealT, 3}(undef, 2, 2, 2) + tmp1 = zeros(RealT, 2, length(nodes), length(nodes_in)) + + for itree in 0:(num_local_trees - 1) + veptr = t8_cmesh_get_tree_vertices(cmesh, itree) + verts = unsafe_wrap(Array, veptr, (3, 1 << NDIMS)) + + u = verts[:, 2] - verts[:, 1] + v = verts[:, 3] - verts[:, 1] + w = [0.0, 0.0, 1.0] + + vol = dot(cross(u, v), w) + + if vol < 0.0 + @warn "Discovered negative volumes in `cmesh`: vol = $vol" + end + + # Tree vertices are stored in z-order. + @views data_in[:, 1, 1] .= verts[1:2, 1] + @views data_in[:, 2, 1] .= verts[1:2, 2] + @views data_in[:, 1, 2] .= verts[1:2, 3] + @views data_in[:, 2, 2] .= verts[1:2, 4] + + # Interpolate corner coordinates to specified nodes. + multiply_dimensionwise!(view(tree_node_coordinates, :, :, :, itree + 1), + matrix, matrix, + data_in, + tmp1) + end + + map_node_coordinates!(tree_node_coordinates, mapping) + + # There's no simple and generic way to distinguish boundaries. Name all of them :all. + boundary_names = fill(:all, 2 * NDIMS, num_local_trees) + + return T8codeMesh{NDIMS}(cmesh, scheme, forest, tree_node_coordinates, nodes, + boundary_names, "") +end + +""" + T8codeMesh{NDIMS}(conn::Ptr{p4est_connectivity}, + mapping=nothing, polydeg=1, RealT=Float64, + initial_refinement_level=0) + +Main mesh constructor for the `T8codeMesh` that imports an unstructured, +conforming mesh from a `p4est_connectivity` data structure. + +# Arguments +- `conn::Ptr{p4est_connectivity}`: Pointer to a P4est connectivity object. +- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms + the imported mesh to the physical domain. Use `nothing` for the identity map. +- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. + The mapping will be approximated by an interpolation polynomial + of the specified degree for each tree. + The default of `1` creates an uncurved geometry. Use a higher value if the mapping + will curve the imported uncurved mesh. +- `RealT::Type`: the type that should be used for coordinates. +- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. +""" +function T8codeMesh{NDIMS}(conn::Ptr{p4est_connectivity}; kwargs...) where {NDIMS} + @assert NDIMS == 2 # Only support for NDIMS = 2 yet. + + cmesh = t8_cmesh_new_from_p4est(conn, mpi_comm(), 0) + + return T8codeMesh{NDIMS}(cmesh; kwargs...) +end + +""" + T8codeMesh{NDIMS}(meshfile::String; + mapping=nothing, polydeg=1, RealT=Float64, + initial_refinement_level=0) + +Main mesh constructor for the `T8codeMesh` that imports an unstructured, conforming +mesh from a Gmsh mesh file (`.msh`). + +# Arguments +- `meshfile::String`: path to a Gmsh mesh file. +- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms + the imported mesh to the physical domain. Use `nothing` for the identity map. +- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. + The mapping will be approximated by an interpolation polynomial + of the specified degree for each tree. + The default of `1` creates an uncurved geometry. Use a higher value if the mapping + will curve the imported uncurved mesh. +- `RealT::Type`: the type that should be used for coordinates. +- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. +""" +function T8codeMesh{NDIMS}(meshfile::String; kwargs...) where {NDIMS} + @assert NDIMS == 2 # Only support for NDIMS = 2 yet. + + # Prevent `t8code` from crashing Julia if the file doesn't exist. + @assert isfile(meshfile) + + meshfile_prefix, meshfile_suffix = splitext(meshfile) + + cmesh = t8_cmesh_from_msh_file(meshfile_prefix, 0, mpi_comm(), NDIMS, 0, 0) + + return T8codeMesh{NDIMS}(cmesh; kwargs...) +end + +# TODO: Just a placeholder. Will be implemented later when MPI is supported. +function balance!(mesh::T8codeMesh, init_fn = C_NULL) + return nothing +end + +# TODO: Just a placeholder. Will be implemented later when MPI is supported. +function partition!(mesh::T8codeMesh; allow_coarsening = true, weight_fn = C_NULL) + return nothing +end diff --git a/src/meshes/tree_mesh.jl b/src/meshes/tree_mesh.jl index 34794ded852..93ba982bce9 100644 --- a/src/meshes/tree_mesh.jl +++ b/src/meshes/tree_mesh.jl @@ -125,9 +125,9 @@ function TreeMesh(coordinates_min::NTuple{NDIMS, Real}, # TODO: MPI, create nice interface for a parallel tree/mesh if mpi_isparallel() - if mpi_isroot() && NDIMS == 3 + if mpi_isroot() && NDIMS != 2 println(stderr, - "ERROR: TreeMesh3D does not support parallel execution with MPI") + "ERROR: The TreeMesh supports parallel execution with MPI only in 2 dimensions") MPI.Abort(mpi_comm(), 1) end TreeType = ParallelTree{NDIMS} diff --git a/src/semidiscretization/semidiscretization.jl b/src/semidiscretization/semidiscretization.jl index ac312c57c89..fbdcd73e2a8 100644 --- a/src/semidiscretization/semidiscretization.jl +++ b/src/semidiscretization/semidiscretization.jl @@ -70,7 +70,15 @@ end Wrap the semidiscretization `semi` as an ODE problem in the time interval `tspan` that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/). """ -function semidiscretize(semi::AbstractSemidiscretization, tspan) +function semidiscretize(semi::AbstractSemidiscretization, tspan; + reset_threads = true) + # Optionally reset Polyester.jl threads. See + # https://github.com/trixi-framework/Trixi.jl/issues/1583 + # https://github.com/JuliaSIMD/Polyester.jl/issues/30 + if reset_threads + Polyester.reset_threads!() + end + u0_ode = compute_coefficients(first(tspan), semi) # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using # mpi_isparallel() && MPI.Barrier(mpi_comm()) @@ -88,7 +96,15 @@ that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai The initial condition etc. is taken from the `restart_file`. """ function semidiscretize(semi::AbstractSemidiscretization, tspan, - restart_file::AbstractString) + restart_file::AbstractString; + reset_threads = true) + # Optionally reset Polyester.jl threads. See + # https://github.com/trixi-framework/Trixi.jl/issues/1583 + # https://github.com/JuliaSIMD/Polyester.jl/issues/30 + if reset_threads + Polyester.reset_threads!() + end + u0_ode = load_restart_file(semi, restart_file) # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using # mpi_isparallel() && MPI.Barrier(mpi_comm()) diff --git a/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl b/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl index f54bc744164..8f1e38c891b 100644 --- a/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl +++ b/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl @@ -274,7 +274,15 @@ The parabolic right-hand side is the first function of the split ODE problem and will be used by default by the implicit part of IMEX methods from the SciML ecosystem. """ -function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan) +function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan; + reset_threads = true) + # Optionally reset Polyester.jl threads. See + # https://github.com/trixi-framework/Trixi.jl/issues/1583 + # https://github.com/JuliaSIMD/Polyester.jl/issues/30 + if reset_threads + Polyester.reset_threads!() + end + u0_ode = compute_coefficients(first(tspan), semi) # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using # mpi_isparallel() && MPI.Barrier(mpi_comm()) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 2536cfe0bf2..495e0ffc4a4 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -363,7 +363,8 @@ function get_element_variables!(element_variables, u, mesh, equations, dg::DG, c dg, cache) end -const MeshesDGSEM = Union{TreeMesh, StructuredMesh, UnstructuredMesh2D, P4estMesh} +const MeshesDGSEM = Union{TreeMesh, StructuredMesh, UnstructuredMesh2D, P4estMesh, + T8codeMesh} @inline function ndofs(mesh::MeshesDGSEM, dg::DG, cache) nelements(cache.elements) * nnodes(dg)^ndims(mesh) @@ -679,4 +680,5 @@ include("dgsem_tree/dg.jl") include("dgsem_structured/dg.jl") include("dgsem_unstructured/dg.jl") include("dgsem_p4est/dg.jl") +include("dgsem_t8code/dg.jl") end # @muladd diff --git a/src/solvers/dgsem_p4est/containers.jl b/src/solvers/dgsem_p4est/containers.jl index 2b9c6987d24..0176f5c6346 100644 --- a/src/solvers/dgsem_p4est/containers.jl +++ b/src/solvers/dgsem_p4est/containers.jl @@ -81,7 +81,8 @@ function Base.resize!(elements::P4estElementContainer, capacity) end # Create element container and initialize element data -function init_elements(mesh::P4estMesh{NDIMS, RealT}, equations, +function init_elements(mesh::Union{P4estMesh{NDIMS, RealT}, T8codeMesh{NDIMS, RealT}}, + equations, basis, ::Type{uEltype}) where {NDIMS, RealT <: Real, uEltype <: Real} nelements = ncells(mesh) @@ -165,7 +166,7 @@ function Base.resize!(interfaces::P4estInterfaceContainer, capacity) end # Create interface container and initialize interface data. -function init_interfaces(mesh::P4estMesh, equations, basis, elements) +function init_interfaces(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) NDIMS = ndims(elements) uEltype = eltype(elements) @@ -240,7 +241,7 @@ function Base.resize!(boundaries::P4estBoundaryContainer, capacity) end # Create interface container and initialize interface data in `elements`. -function init_boundaries(mesh::P4estMesh, equations, basis, elements) +function init_boundaries(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) NDIMS = ndims(elements) uEltype = eltype(elements) @@ -371,7 +372,7 @@ function Base.resize!(mortars::P4estMortarContainer, capacity) end # Create mortar container and initialize mortar data. -function init_mortars(mesh::P4estMesh, equations, basis, elements) +function init_mortars(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) NDIMS = ndims(elements) uEltype = eltype(elements) diff --git a/src/solvers/dgsem_p4est/containers_2d.jl b/src/solvers/dgsem_p4est/containers_2d.jl index 11747f1f175..236d7d24c06 100644 --- a/src/solvers/dgsem_p4est/containers_2d.jl +++ b/src/solvers/dgsem_p4est/containers_2d.jl @@ -6,7 +6,8 @@ #! format: noindent # Initialize data structures in element container -function init_elements!(elements, mesh::P4estMesh{2}, basis::LobattoLegendreBasis) +function init_elements!(elements, mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + basis::LobattoLegendreBasis) @unpack node_coordinates, jacobian_matrix, contravariant_vectors, inverse_jacobian = elements @@ -25,7 +26,7 @@ end # Interpolate tree_node_coordinates to each quadrant at the nodes of the specified basis function calc_node_coordinates!(node_coordinates, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, basis::LobattoLegendreBasis) # Hanging nodes will cause holes in the mesh if its polydeg is higher # than the polydeg of the solver. diff --git a/src/solvers/dgsem_p4est/dg.jl b/src/solvers/dgsem_p4est/dg.jl index a7cc1eee04d..ec50627d3ef 100644 --- a/src/solvers/dgsem_p4est/dg.jl +++ b/src/solvers/dgsem_p4est/dg.jl @@ -50,5 +50,6 @@ include("dg_2d.jl") include("dg_2d_parabolic.jl") include("dg_3d.jl") +include("dg_3d_parabolic.jl") include("dg_parallel.jl") end # @muladd diff --git a/src/solvers/dgsem_p4est/dg_2d.jl b/src/solvers/dgsem_p4est/dg_2d.jl index bc7d9edb6ef..97b931fa325 100644 --- a/src/solvers/dgsem_p4est/dg_2d.jl +++ b/src/solvers/dgsem_p4est/dg_2d.jl @@ -7,8 +7,8 @@ # The methods below are specialized on the mortar type # and called from the basic `create_cache` method at the top. -function create_cache(mesh::P4estMesh{2}, equations, mortar_l2::LobattoLegendreMortarL2, - uEltype) +function create_cache(mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, + mortar_l2::LobattoLegendreMortarL2, uEltype) # TODO: Taal performance using different types MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)}, uEltype, 2, @@ -58,7 +58,7 @@ end # We pass the `surface_integral` argument solely for dispatch function prolong2interfaces!(cache, u, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, surface_integral, dg::DG) @unpack interfaces = cache index_range = eachnode(dg) @@ -114,7 +114,7 @@ function prolong2interfaces!(cache, u, end function calc_interface_flux!(surface_flux_values, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms, equations, surface_integral, dg::DG, cache) @unpack neighbor_ids, node_indices = cache.interfaces @@ -182,7 +182,7 @@ end # Inlined version of the interface flux computation for conservation laws @inline function calc_interface_flux!(surface_flux_values, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::False, equations, surface_integral, dg::DG, cache, interface_index, normal_direction, @@ -206,7 +206,7 @@ end # Inlined version of the interface flux computation for equations with conservative and nonconservative terms @inline function calc_interface_flux!(surface_flux_values, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::True, equations, surface_integral, dg::DG, cache, interface_index, normal_direction, @@ -247,7 +247,7 @@ end end function prolong2boundaries!(cache, u, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, surface_integral, dg::DG) @unpack boundaries = cache index_range = eachnode(dg) @@ -276,7 +276,7 @@ function prolong2boundaries!(cache, u, end function calc_boundary_flux!(cache, t, boundary_condition, boundary_indexing, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, surface_integral, dg::DG) @unpack boundaries = cache @unpack surface_flux_values = cache.elements @@ -312,7 +312,7 @@ end # inlined version of the boundary flux calculation along a physical interface @inline function calc_boundary_flux!(surface_flux_values, t, boundary_condition, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::False, equations, surface_integral, dg::DG, cache, i_index, j_index, @@ -343,7 +343,7 @@ end # inlined version of the boundary flux with nonconservative terms calculation along a physical interface @inline function calc_boundary_flux!(surface_flux_values, t, boundary_condition, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::True, equations, surface_integral, dg::DG, cache, i_index, j_index, @@ -385,7 +385,7 @@ end end function prolong2mortars!(cache, u, - mesh::P4estMesh{2}, equations, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, mortar_l2::LobattoLegendreMortarL2, surface_integral, dg::DGSEM) @unpack neighbor_ids, node_indices = cache.mortars @@ -452,7 +452,7 @@ function prolong2mortars!(cache, u, end function calc_mortar_flux!(surface_flux_values, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms, equations, mortar_l2::LobattoLegendreMortarL2, surface_integral, dg::DG, cache) @@ -511,7 +511,7 @@ end # Inlined version of the mortar flux computation on small elements for conservation laws @inline function calc_mortar_flux!(fstar, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::False, equations, surface_integral, dg::DG, cache, mortar_index, position_index, normal_direction, @@ -531,7 +531,7 @@ end # Inlined version of the mortar flux computation on small elements for equations with conservative and # nonconservative terms @inline function calc_mortar_flux!(fstar, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::True, equations, surface_integral, dg::DG, cache, mortar_index, position_index, normal_direction, @@ -559,7 +559,8 @@ end end @inline function mortar_fluxes_to_elements!(surface_flux_values, - mesh::P4estMesh{2}, equations, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations, mortar_l2::LobattoLegendreMortarL2, dg::DGSEM, cache, mortar, fstar, u_buffer) @unpack neighbor_ids, node_indices = cache.mortars @@ -620,7 +621,7 @@ end end function calc_surface_integral!(du, u, - mesh::P4estMesh{2}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, surface_integral::SurfaceIntegralWeakForm, dg::DGSEM, cache) diff --git a/src/solvers/dgsem_p4est/dg_2d_parabolic.jl b/src/solvers/dgsem_p4est/dg_2d_parabolic.jl index 73ac47ed1e3..7e90a83a9ca 100644 --- a/src/solvers/dgsem_p4est/dg_2d_parabolic.jl +++ b/src/solvers/dgsem_p4est/dg_2d_parabolic.jl @@ -1,7 +1,7 @@ # This method is called when a SemidiscretizationHyperbolicParabolic is constructed. # It constructs the basic `cache` used throughout the simulation to compute # the RHS etc. -function create_cache_parabolic(mesh::P4estMesh, equations_hyperbolic::AbstractEquations, +function create_cache_parabolic(mesh::P4estMesh{2}, equations_hyperbolic::AbstractEquations, equations_parabolic::AbstractEquationsParabolic, dg::DG, parabolic_scheme, RealT, uEltype) balance!(mesh) diff --git a/src/solvers/dgsem_p4est/dg_3d_parabolic.jl b/src/solvers/dgsem_p4est/dg_3d_parabolic.jl new file mode 100644 index 00000000000..6439cad69bb --- /dev/null +++ b/src/solvers/dgsem_p4est/dg_3d_parabolic.jl @@ -0,0 +1,637 @@ +# This method is called when a SemidiscretizationHyperbolicParabolic is constructed. +# It constructs the basic `cache` used throughout the simulation to compute +# the RHS etc. +function create_cache_parabolic(mesh::P4estMesh{3}, equations_hyperbolic::AbstractEquations, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, RealT, uEltype) + balance!(mesh) + + elements = init_elements(mesh, equations_hyperbolic, dg.basis, uEltype) + interfaces = init_interfaces(mesh, equations_hyperbolic, dg.basis, elements) + boundaries = init_boundaries(mesh, equations_hyperbolic, dg.basis, elements) + + n_vars = nvariables(equations_hyperbolic) + n_elements = nelements(elements) + n_nodes = nnodes(dg.basis) # nodes in one direction + u_transformed = Array{uEltype}(undef, n_vars, n_nodes, n_nodes, n_nodes, n_elements) + gradients = ntuple(_ -> similar(u_transformed), ndims(mesh)) + flux_viscous = ntuple(_ -> similar(u_transformed), ndims(mesh)) + + cache = (; elements, interfaces, boundaries, gradients, flux_viscous, u_transformed) + + return cache +end + +function calc_gradient!(gradients, u_transformed, t, + mesh::P4estMesh{3}, equations_parabolic, + boundary_conditions_parabolic, dg::DG, + cache, cache_parabolic) + gradients_x, gradients_y, gradients_z = gradients + + # Reset du + @trixi_timeit timer() "reset gradients" begin + reset_du!(gradients_x, dg, cache) + reset_du!(gradients_y, dg, cache) + reset_du!(gradients_z, dg, cache) + end + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + (; derivative_dhat) = dg.basis + (; contravariant_vectors) = cache.elements + + @threaded for element in eachelement(dg, cache) + + # Calculate gradients with respect to reference coordinates in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, k, + element) + + for ii in eachnode(dg) + multiply_add_to_node_vars!(gradients_x, derivative_dhat[ii, i], + u_node, equations_parabolic, dg, ii, j, + k, element) + end + + for jj in eachnode(dg) + multiply_add_to_node_vars!(gradients_y, derivative_dhat[jj, j], + u_node, equations_parabolic, dg, i, jj, + k, element) + end + + for kk in eachnode(dg) + multiply_add_to_node_vars!(gradients_z, derivative_dhat[kk, k], + u_node, equations_parabolic, dg, i, j, + kk, element) + end + end + + # now that the reference coordinate gradients are computed, transform them node-by-node to physical gradients + # using the contravariant vectors + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, + i, j, k, element) + Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, + i, j, k, element) + Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, + i, j, k, element) + + gradients_reference_1 = get_node_vars(gradients_x, equations_parabolic, dg, + i, j, k, element) + gradients_reference_2 = get_node_vars(gradients_y, equations_parabolic, dg, + i, j, k, element) + gradients_reference_3 = get_node_vars(gradients_z, equations_parabolic, dg, + i, j, k, element) + + # note that the contravariant vectors are transposed compared with computations of flux + # divergences in `calc_volume_integral!`. See + # https://github.com/trixi-framework/Trixi.jl/pull/1490#discussion_r1213345190 + # for a more detailed discussion. + gradient_x_node = Ja11 * gradients_reference_1 + + Ja21 * gradients_reference_2 + + Ja31 * gradients_reference_3 + gradient_y_node = Ja12 * gradients_reference_1 + + Ja22 * gradients_reference_2 + + Ja32 * gradients_reference_3 + gradient_z_node = Ja13 * gradients_reference_1 + + Ja23 * gradients_reference_2 + + Ja33 * gradients_reference_3 + + set_node_vars!(gradients_x, gradient_x_node, equations_parabolic, dg, + i, j, k, element) + set_node_vars!(gradients_y, gradient_y_node, equations_parabolic, dg, + i, j, k, element) + set_node_vars!(gradients_z, gradient_z_node, equations_parabolic, dg, + i, j, k, element) + end + end + end + + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!(cache_parabolic, u_transformed, mesh, + equations_parabolic, dg.surface_integral, dg) + end + + # Calculate interface fluxes for the gradient. This reuses P4est `calc_interface_flux!` along with a + # specialization for AbstractEquationsParabolic. + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!(cache_parabolic.elements.surface_flux_values, + mesh, False(), # False() = no nonconservative terms + equations_parabolic, dg.surface_integral, dg, cache_parabolic) + end + + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!(cache_parabolic, u_transformed, mesh, + equations_parabolic, dg.surface_integral, dg) + end + + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux_gradients!(cache_parabolic, t, boundary_conditions_parabolic, + mesh, equations_parabolic, dg.surface_integral, dg) + end + + # TODO: parabolic; mortars + @assert nmortars(dg, cache) == 0 + + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + (; boundary_interpolation) = dg.basis + (; surface_flux_values) = cache_parabolic.elements + (; contravariant_vectors) = cache.elements + + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg), m in eachnode(dg) + for v in eachvariable(equations_parabolic) + for dim in 1:3 + grad = gradients[dim] + # surface at -x + normal_direction = get_normal_direction(1, contravariant_vectors, + 1, l, m, element) + grad[v, 1, l, m, element] = (grad[v, 1, l, m, element] + + surface_flux_values[v, l, m, 1, + element] * + factor_1 * normal_direction[dim]) + + # surface at +x + normal_direction = get_normal_direction(2, contravariant_vectors, + nnodes(dg), l, m, element) + grad[v, nnodes(dg), l, m, element] = (grad[v, nnodes(dg), l, m, + element] + + surface_flux_values[v, l, m, + 2, + element] * + factor_2 * + normal_direction[dim]) + + # surface at -y + normal_direction = get_normal_direction(3, contravariant_vectors, + l, m, 1, element) + grad[v, l, 1, m, element] = (grad[v, l, 1, m, element] + + surface_flux_values[v, l, m, 3, + element] * + factor_1 * normal_direction[dim]) + + # surface at +y + normal_direction = get_normal_direction(4, contravariant_vectors, + l, nnodes(dg), m, element) + grad[v, l, nnodes(dg), m, element] = (grad[v, l, nnodes(dg), m, + element] + + surface_flux_values[v, l, m, + 4, + element] * + factor_2 * + normal_direction[dim]) + + # surface at -z + normal_direction = get_normal_direction(5, contravariant_vectors, + l, m, 1, element) + grad[v, l, m, 1, element] = (grad[v, l, m, 1, element] + + surface_flux_values[v, l, m, 5, + element] * + factor_1 * normal_direction[dim]) + + # surface at +z + normal_direction = get_normal_direction(6, contravariant_vectors, + l, m, nnodes(dg), element) + grad[v, l, m, nnodes(dg), element] = (grad[v, l, m, nnodes(dg), + element] + + surface_flux_values[v, l, m, + 6, + element] * + factor_2 * + normal_direction[dim]) + end + end + end + end + end + + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!(gradients_x, mesh, equations_parabolic, dg, + cache_parabolic) + apply_jacobian_parabolic!(gradients_y, mesh, equations_parabolic, dg, + cache_parabolic) + apply_jacobian_parabolic!(gradients_z, mesh, equations_parabolic, dg, + cache_parabolic) + end + + return nothing +end + +# This version is used for parabolic gradient computations +@inline function calc_interface_flux!(surface_flux_values, mesh::P4estMesh{3}, + nonconservative_terms::False, + equations::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + primary_i_node_index, primary_j_node_index, + primary_direction_index, primary_element_index, + secondary_i_node_index, secondary_j_node_index, + secondary_direction_index, + secondary_element_index) + @unpack u = cache.interfaces + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_i_node_index, + primary_j_node_index, + interface_index) + + flux_ = 0.5 * (u_ll + u_rr) # we assume that the gradient computations utilize a central flux + + # Note that we don't flip the sign on the secondondary flux. This is because for parabolic terms, + # the normals are not embedded in `flux_` for the parabolic gradient computations. + for v in eachvariable(equations) + surface_flux_values[v, primary_i_node_index, primary_j_node_index, primary_direction_index, primary_element_index] = flux_[v] + surface_flux_values[v, secondary_i_node_index, secondary_j_node_index, secondary_direction_index, secondary_element_index] = flux_[v] + end +end + +# This is the version used when calculating the divergence of the viscous fluxes +function calc_volume_integral!(du, flux_viscous, + mesh::P4estMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + dg::DGSEM, cache) + (; derivative_dhat) = dg.basis + (; contravariant_vectors) = cache.elements + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for element in eachelement(dg, cache) + # Calculate volume terms in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + flux1 = get_node_vars(flux_viscous_x, equations_parabolic, dg, i, j, k, element) + flux2 = get_node_vars(flux_viscous_y, equations_parabolic, dg, i, j, k, element) + flux3 = get_node_vars(flux_viscous_z, equations_parabolic, dg, i, j, k, element) + + # Compute the contravariant flux by taking the scalar product of the + # first contravariant vector Ja^1 and the flux vector + Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, i, j, k, + element) + contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + Ja13 * flux3 + for ii in eachnode(dg) + multiply_add_to_node_vars!(du, derivative_dhat[ii, i], contravariant_flux1, + equations_parabolic, dg, ii, j, k, element) + end + + # Compute the contravariant flux by taking the scalar product of the + # second contravariant vector Ja^2 and the flux vector + Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, i, j, k, + element) + contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + Ja23 * flux3 + for jj in eachnode(dg) + multiply_add_to_node_vars!(du, derivative_dhat[jj, j], contravariant_flux2, + equations_parabolic, dg, i, jj, k, element) + end + + # Compute the contravariant flux by taking the scalar product of the + # second contravariant vector Ja^2 and the flux vector + Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, i, j, k, + element) + contravariant_flux3 = Ja31 * flux1 + Ja32 * flux2 + Ja33 * flux3 + for kk in eachnode(dg) + multiply_add_to_node_vars!(du, derivative_dhat[kk, k], contravariant_flux3, + equations_parabolic, dg, i, j, kk, element) + end + end + end + + return nothing +end + +# This is the version used when calculating the divergence of the viscous fluxes +# We pass the `surface_integral` argument solely for dispatch +function prolong2interfaces!(cache_parabolic, flux_viscous, + mesh::P4estMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache) + (; interfaces) = cache_parabolic + (; contravariant_vectors) = cache_parabolic.elements + index_range = eachnode(dg) + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for interface in eachinterface(dg, cache) + # Copy solution data from the primary element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + # Note that in the current implementation, the interface will be + # "aligned at the primary element", i.e., the index of the primary side + # will always run forwards. + primary_element = interfaces.neighbor_ids[1, interface] + primary_indices = interfaces.node_indices[1, interface] + primary_direction = indices2direction(primary_indices) + + i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1], + index_range) + j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2], + index_range) + k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3], + index_range) + + i_primary = i_primary_start + j_primary = j_primary_start + k_primary = k_primary_start + + for j in eachnode(dg) + for i in eachnode(dg) + # this is the outward normal direction on the primary element + normal_direction = get_normal_direction(primary_direction, + contravariant_vectors, + i_primary, j_primary, k_primary, + primary_element) + + for v in eachvariable(equations_parabolic) + # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! + flux_viscous = SVector(flux_viscous_x[v, i_primary, j_primary, + k_primary, + primary_element], + flux_viscous_y[v, i_primary, j_primary, + k_primary, + primary_element], + flux_viscous_z[v, i_primary, j_primary, + k_primary, + primary_element]) + + interfaces.u[1, v, i, j, interface] = dot(flux_viscous, + normal_direction) + end + i_primary += i_primary_step_i + j_primary += j_primary_step_i + k_primary += k_primary_step_i + end + i_primary += i_primary_step_j + j_primary += j_primary_step_j + k_primary += k_primary_step_j + end + + # Copy solution data from the secondary element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + secondary_element = interfaces.neighbor_ids[2, interface] + secondary_indices = interfaces.node_indices[2, interface] + secondary_direction = indices2direction(secondary_indices) + + i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_indices[1], + index_range) + j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_indices[2], + index_range) + k_secondary_start, k_secondary_step_i, k_secondary_step_j = index_to_start_step_3d(secondary_indices[3], + index_range) + + i_secondary = i_secondary_start + j_secondary = j_secondary_start + k_secondary = k_secondary_start + for j in eachnode(dg) + for i in eachnode(dg) + # This is the outward normal direction on the secondary element. + # Here, we assume that normal_direction on the secondary element is + # the negative of normal_direction on the primary element. + normal_direction = get_normal_direction(secondary_direction, + contravariant_vectors, + i_secondary, j_secondary, + k_secondary, + secondary_element) + + for v in eachvariable(equations_parabolic) + # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! + flux_viscous = SVector(flux_viscous_x[v, i_secondary, j_secondary, + k_secondary, + secondary_element], + flux_viscous_y[v, i_secondary, j_secondary, + k_secondary, + secondary_element], + flux_viscous_z[v, i_secondary, j_secondary, + k_secondary, + secondary_element]) + # store the normal flux with respect to the primary normal direction + interfaces.u[2, v, i, j, interface] = -dot(flux_viscous, + normal_direction) + end + i_secondary += i_secondary_step_i + j_secondary += j_secondary_step_i + k_secondary += k_secondary_step_i + end + i_secondary += i_secondary_step_j + j_secondary += j_secondary_step_j + k_secondary += k_secondary_step_j + end + end + + return nothing +end + +# This version is used for divergence flux computations +function calc_interface_flux!(surface_flux_values, + mesh::P4estMesh{3}, equations_parabolic, + dg::DG, cache_parabolic) + (; neighbor_ids, node_indices) = cache_parabolic.interfaces + index_range = eachnode(dg) + + @threaded for interface in eachinterface(dg, cache_parabolic) + # Get element and side index information on the primary element + primary_element = neighbor_ids[1, interface] + primary_indices = node_indices[1, interface] + primary_direction_index = indices2direction(primary_indices) + + i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1], + index_range) + j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2], + index_range) + k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3], + index_range) + + i_primary = i_primary_start + j_primary = j_primary_start + k_primary = k_primary_start + + # Get element and side index information on the secondary element + secondary_element = neighbor_ids[2, interface] + secondary_indices = node_indices[2, interface] + secondary_direction_index = indices2direction(secondary_indices) + secondary_surface_indices = surface_indices(secondary_indices) + + # Initiate the secondary index to be used in the surface for loop. + # This index on the primary side will always run forward but + # the secondary index might need to run backwards for flipped sides. + # Get the surface indexing on the secondary element. + # Note that the indices of the primary side will always run forward but + # the secondary indices might need to run backwards for flipped sides. + i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[1], + index_range) + j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[2], + index_range) + i_secondary = i_secondary_start + j_secondary = j_secondary_start + + for j in eachnode(dg) + for i in eachnode(dg) + # We prolong the viscous flux dotted with respect the outward normal on the + # primary element. We assume a BR-1 type of flux. + viscous_flux_normal_ll, viscous_flux_normal_rr = get_surface_node_vars(cache_parabolic.interfaces.u, + equations_parabolic, + dg, + i, j, + interface) + + flux = 0.5 * (viscous_flux_normal_ll + viscous_flux_normal_rr) + + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, j, primary_direction_index, primary_element] = flux[v] + surface_flux_values[v, i_secondary, j_secondary, secondary_direction_index, secondary_element] = -flux[v] + end + + # Increment the primary element indices + i_primary += i_primary_step_i + j_primary += j_primary_step_i + k_primary += k_primary_step_i + # Increment the secondary element surface indices + i_secondary += i_secondary_step_i + j_secondary += j_secondary_step_i + end + # Increment the primary element indices + i_primary += i_primary_step_j + j_primary += j_primary_step_j + k_primary += k_primary_step_j + # Increment the secondary element surface indices + i_secondary += i_secondary_step_j + j_secondary += j_secondary_step_j + end + end + + return nothing +end + +# TODO: parabolic, finish implementing `calc_boundary_flux_gradients!` and `calc_boundary_flux_divergence!` +function prolong2boundaries!(cache_parabolic, flux_viscous, + mesh::P4estMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache) + (; boundaries) = cache_parabolic + (; contravariant_vectors) = cache_parabolic.elements + index_range = eachnode(dg) + + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for boundary in eachboundary(dg, cache_parabolic) + # Copy solution data from the element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1], + index_range) + j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2], + index_range) + k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3], + index_range) + + i_node = i_node_start + j_node = j_node_start + k_node = k_node_start + + for j in eachnode(dg) + for i in eachnode(dg) + # this is the outward normal direction on the primary element + normal_direction = get_normal_direction(direction, contravariant_vectors, + i_node, j_node, k_node, element) + + for v in eachvariable(equations_parabolic) + flux_viscous = SVector(flux_viscous_x[v, i_node, j_node, k_node, + element], + flux_viscous_y[v, i_node, j_node, k_node, + element], + flux_viscous_z[v, i_node, j_node, k_node, + element]) + + boundaries.u[v, i, j, boundary] = dot(flux_viscous, normal_direction) + end + i_node += i_node_step_i + j_node += j_node_step_i + k_node += k_node_step_i + end + i_node += i_node_step_j + j_node += j_node_step_j + k_node += k_node_step_j + end + end + return nothing +end + +function calc_boundary_flux!(cache, t, + boundary_condition_parabolic, # works with Dict types + boundary_condition_indices, + operator_type, mesh::P4estMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG) + (; boundaries) = cache + (; node_coordinates, surface_flux_values) = cache.elements + (; contravariant_vectors) = cache.elements + index_range = eachnode(dg) + + @threaded for local_index in eachindex(boundary_condition_indices) + # Use the local index to get the global boundary index from the pre-sorted list + boundary_index = boundary_condition_indices[local_index] + + # Get information on the adjacent element, compute the surface fluxes, + # and store them + element = boundaries.neighbor_ids[boundary_index] + node_indices = boundaries.node_indices[boundary_index] + direction_index = indices2direction(node_indices) + + i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1], + index_range) + j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2], + index_range) + k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3], + index_range) + + i_node = i_node_start + j_node = j_node_start + k_node = k_node_start + + for j in eachnode(dg) + for i in eachnode(dg) + # Extract solution data from boundary container + u_inner = get_node_vars(boundaries.u, equations_parabolic, dg, i, j, + boundary_index) + + # Outward-pointing normal direction (not normalized) + normal_direction = get_normal_direction(direction_index, + contravariant_vectors, + i_node, j_node, k_node, element) + + # TODO: revisit if we want more general boundary treatments. + # This assumes the gradient numerical flux at the boundary is the gradient variable, + # which is consistent with BR1, LDG. + flux_inner = u_inner + + # Coordinates at boundary node + x = get_node_coords(node_coordinates, equations_parabolic, dg, i_node, + j_node, k_node, + element) + + flux_ = boundary_condition_parabolic(flux_inner, u_inner, normal_direction, + x, t, operator_type, + equations_parabolic) + + # Copy flux to element storage in the correct orientation + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, j, direction_index, element] = flux_[v] + end + + i_node += i_node_step_i + j_node += j_node_step_i + k_node += k_node_step_i + end + i_node += i_node_step_j + j_node += j_node_step_j + k_node += k_node_step_j + end + end +end diff --git a/src/solvers/dgsem_structured/dg_2d.jl b/src/solvers/dgsem_structured/dg_2d.jl index c013bf62d98..3e8ce759b30 100644 --- a/src/solvers/dgsem_structured/dg_2d.jl +++ b/src/solvers/dgsem_structured/dg_2d.jl @@ -52,7 +52,7 @@ end @inline function weak_form_kernel!(du, u, element, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, + P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::False, equations, dg::DGSEM, cache, alpha = true) # true * [some floating point value] == [exactly the same floating point value] @@ -93,8 +93,8 @@ end @inline function flux_differencing_kernel!(du, u, element, mesh::Union{StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2} - }, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, nonconservative_terms::False, equations, volume_flux, dg::DGSEM, cache, alpha = true) @unpack derivative_split = dg.basis @@ -150,8 +150,8 @@ end @inline function flux_differencing_kernel!(du, u, element, mesh::Union{StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2} - }, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, nonconservative_terms::True, equations, volume_flux, dg::DGSEM, cache, alpha = true) @unpack derivative_split = dg.basis @@ -219,7 +219,7 @@ end # [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044) @inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, + P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::False, equations, volume_flux_fv, dg::DGSEM, element, cache) @unpack contravariant_vectors = cache.elements @@ -289,7 +289,7 @@ end @inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u::AbstractArray{<:Any, 4}, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, + P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms::True, equations, volume_flux_fv, dg::DGSEM, element, cache) @unpack contravariant_vectors = cache.elements @@ -609,9 +609,8 @@ function calc_boundary_flux!(cache, u, t, boundary_conditions::NamedTuple, end function apply_jacobian!(du, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2 - } - }, + mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}}, equations, dg::DG, cache) @unpack inverse_jacobian = cache.elements diff --git a/src/solvers/dgsem_t8code/containers.jl b/src/solvers/dgsem_t8code/containers.jl new file mode 100644 index 00000000000..093feb2985a --- /dev/null +++ b/src/solvers/dgsem_t8code/containers.jl @@ -0,0 +1,60 @@ +function reinitialize_containers!(mesh::T8codeMesh, equations, dg::DGSEM, cache) + # Re-initialize elements container. + @unpack elements = cache + resize!(elements, ncells(mesh)) + init_elements!(elements, mesh, dg.basis) + + count_required_surfaces!(mesh) + + # Resize interfaces container. + @unpack interfaces = cache + resize!(interfaces, mesh.ninterfaces) + + # Resize mortars container. + @unpack mortars = cache + resize!(mortars, mesh.nmortars) + + # Resize boundaries container. + @unpack boundaries = cache + resize!(boundaries, mesh.nboundaries) + + trixi_t8_fill_mesh_info(mesh.forest, elements, interfaces, mortars, boundaries, + mesh.boundary_names) + + return nothing +end + +function count_required_surfaces!(mesh::T8codeMesh) + counts = trixi_t8_count_interfaces(mesh.forest) + + mesh.nmortars = counts.mortars + mesh.ninterfaces = counts.interfaces + mesh.nboundaries = counts.boundaries + + return counts +end + +# Compatibility to `dgsem_p4est/containers.jl`. +function count_required_surfaces(mesh::T8codeMesh) + return (interfaces = mesh.ninterfaces, + mortars = mesh.nmortars, + boundaries = mesh.nboundaries) +end + +# Compatibility to `dgsem_p4est/containers.jl`. +function init_interfaces!(interfaces, mesh::T8codeMesh) + # Already computed. Do nothing. + return nothing +end + +# Compatibility to `dgsem_p4est/containers.jl`. +function init_mortars!(mortars, mesh::T8codeMesh) + # Already computed. Do nothing. + return nothing +end + +# Compatibility to `dgsem_p4est/containers.jl`. +function init_boundaries!(boundaries, mesh::T8codeMesh) + # Already computed. Do nothing. + return nothing +end diff --git a/src/solvers/dgsem_t8code/containers_2d.jl b/src/solvers/dgsem_t8code/containers_2d.jl new file mode 100644 index 00000000000..029e6674afb --- /dev/null +++ b/src/solvers/dgsem_t8code/containers_2d.jl @@ -0,0 +1,58 @@ +@muladd begin +#! format: noindent + +# Interpolate tree_node_coordinates to each quadrant at the specified nodes. +function calc_node_coordinates!(node_coordinates, + mesh::T8codeMesh{2}, + nodes::AbstractVector) + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + tmp1 = StrideArray(undef, real(mesh), + StaticInt(2), static_length(nodes), static_length(mesh.nodes)) + matrix1 = StrideArray(undef, real(mesh), + static_length(nodes), static_length(mesh.nodes)) + matrix2 = similar(matrix1) + baryweights_in = barycentric_weights(mesh.nodes) + + num_local_trees = t8_forest_get_num_local_trees(mesh.forest) + + current_index = 0 + for itree in 0:(num_local_trees - 1) + tree_class = t8_forest_get_tree_class(mesh.forest, itree) + eclass_scheme = t8_forest_get_eclass_scheme(mesh.forest, tree_class) + num_elements_in_tree = t8_forest_get_tree_num_elements(mesh.forest, itree) + + for ielement in 0:(num_elements_in_tree - 1) + element = t8_forest_get_element_in_tree(mesh.forest, itree, ielement) + element_level = t8_element_level(eclass_scheme, element) + + element_length = t8_quad_len(element_level) / t8_quad_root_len + + element_coords = Array{Float64}(undef, 3) + t8_element_vertex_reference_coords(eclass_scheme, element, 0, + pointer(element_coords)) + + nodes_out_x = 2 * + (element_length * 1 / 2 * (nodes .+ 1) .+ element_coords[1]) .- + 1 + nodes_out_y = 2 * + (element_length * 1 / 2 * (nodes .+ 1) .+ element_coords[2]) .- + 1 + + polynomial_interpolation_matrix!(matrix1, mesh.nodes, nodes_out_x, + baryweights_in) + polynomial_interpolation_matrix!(matrix2, mesh.nodes, nodes_out_y, + baryweights_in) + + multiply_dimensionwise!(view(node_coordinates, :, :, :, current_index += 1), + matrix1, matrix2, + view(mesh.tree_node_coordinates, :, :, :, + itree + 1), + tmp1) + end + end + + return node_coordinates +end +end # @muladd diff --git a/src/solvers/dgsem_t8code/dg.jl b/src/solvers/dgsem_t8code/dg.jl new file mode 100644 index 00000000000..16a9d7d35b1 --- /dev/null +++ b/src/solvers/dgsem_t8code/dg.jl @@ -0,0 +1,31 @@ +@muladd begin +#! format: noindent + +# This method is called when a SemidiscretizationHyperbolic is constructed. +# It constructs the basic `cache` used throughout the simulation to compute +# the RHS etc. +function create_cache(mesh::T8codeMesh, equations::AbstractEquations, dg::DG, ::Any, + ::Type{uEltype}) where {uEltype <: Real} + count_required_surfaces!(mesh) + + elements = init_elements(mesh, equations, dg.basis, uEltype) + interfaces = init_interfaces(mesh, equations, dg.basis, elements) + boundaries = init_boundaries(mesh, equations, dg.basis, elements) + mortars = init_mortars(mesh, equations, dg.basis, elements) + + trixi_t8_fill_mesh_info(mesh.forest, elements, interfaces, mortars, boundaries, + mesh.boundary_names) + + cache = (; elements, interfaces, boundaries, mortars) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) + cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) + + return cache +end + +include("containers.jl") +include("containers_2d.jl") +end # @muladd diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index 5cf256d3499..d80522d42fd 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -764,10 +764,10 @@ end # Container data structure (structure-of-arrays style) for DG MPI interfaces mutable struct MPIInterfaceContainer2D{uEltype <: Real} <: AbstractContainer - u::Array{uEltype, 4} # [leftright, variables, i, interfaces] + u::Array{uEltype, 4} # [leftright, variables, i, interfaces] local_neighbor_ids::Vector{Int} # [interfaces] - orientations::Vector{Int} # [interfaces] - remote_sides::Vector{Int} # [interfaces] + orientations::Vector{Int} # [interfaces] + remote_sides::Vector{Int} # [interfaces] # internal `resize!`able storage _u::Vector{uEltype} end diff --git a/src/solvers/dgsem_tree/containers_3d.jl b/src/solvers/dgsem_tree/containers_3d.jl index 0318946e34d..5fc027ad001 100644 --- a/src/solvers/dgsem_tree/containers_3d.jl +++ b/src/solvers/dgsem_tree/containers_3d.jl @@ -520,14 +520,14 @@ end # Left and right are used *both* for the numbering of the mortar faces *and* for the position of the # elements with respect to the axis orthogonal to the mortar. mutable struct L2MortarContainer3D{uEltype <: Real} <: AbstractContainer - u_upper_left::Array{uEltype, 5} # [leftright, variables, i, j, mortars] + u_upper_left::Array{uEltype, 5} # [leftright, variables, i, j, mortars] u_upper_right::Array{uEltype, 5} # [leftright, variables, i, j, mortars] - u_lower_left::Array{uEltype, 5} # [leftright, variables, i, j, mortars] + u_lower_left::Array{uEltype, 5} # [leftright, variables, i, j, mortars] u_lower_right::Array{uEltype, 5} # [leftright, variables, i, j, mortars] - neighbor_ids::Array{Int, 2} # [position, mortars] + neighbor_ids::Array{Int, 2} # [position, mortars] # Large sides: left -> 1, right -> 2 large_sides::Vector{Int} # [mortars] - orientations::Vector{Int} # [mortars] + orientations::Vector{Int} # [mortars] # internal `resize!`able storage _u_upper_left::Vector{uEltype} _u_upper_right::Vector{uEltype} diff --git a/src/solvers/dgsem_tree/dg_1d.jl b/src/solvers/dgsem_tree/dg_1d.jl index c66f427cce3..b5bb076f3b7 100644 --- a/src/solvers/dgsem_tree/dg_1d.jl +++ b/src/solvers/dgsem_tree/dg_1d.jl @@ -385,15 +385,17 @@ end function prolong2interfaces!(cache, u, mesh::TreeMesh{1}, equations, surface_integral, dg::DG) @unpack interfaces = cache + @unpack neighbor_ids = interfaces + interfaces_u = interfaces.u @threaded for interface in eachinterface(dg, cache) - left_element = interfaces.neighbor_ids[1, interface] - right_element = interfaces.neighbor_ids[2, interface] + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] # interface in x-direction for v in eachvariable(equations) - interfaces.u[1, v, interface] = u[v, nnodes(dg), left_element] - interfaces.u[2, v, interface] = u[v, 1, right_element] + interfaces_u[1, v, interface] = u[v, nnodes(dg), left_element] + interfaces_u[2, v, interface] = u[v, 1, right_element] end end @@ -621,8 +623,10 @@ end function apply_jacobian!(du, mesh::Union{TreeMesh{1}, StructuredMesh{1}}, equations, dg::DG, cache) + @unpack inverse_jacobian = cache.elements + @threaded for element in eachelement(dg, cache) - factor = -cache.elements.inverse_jacobian[element] + factor = -inverse_jacobian[element] for i in eachnode(dg) for v in eachvariable(equations) @@ -642,11 +646,13 @@ end function calc_sources!(du, u, t, source_terms, equations::AbstractEquations{1}, dg::DG, cache) + @unpack node_coordinates = cache.elements + @threaded for element in eachelement(dg, cache) for i in eachnode(dg) u_local = get_node_vars(u, equations, dg, i, element) - x_local = get_node_coords(cache.elements.node_coordinates, equations, dg, i, - element) + x_local = get_node_coords(node_coordinates, equations, dg, + i, element) du_local = source_terms(u_local, x_local, t, equations) add_to_node_vars!(du, du_local, equations, dg, i, element) end diff --git a/src/solvers/dgsem_tree/dg_2d.jl b/src/solvers/dgsem_tree/dg_2d.jl index d3227710686..c30d0a8e01a 100644 --- a/src/solvers/dgsem_tree/dg_2d.jl +++ b/src/solvers/dgsem_tree/dg_2d.jl @@ -37,14 +37,14 @@ end # The methods below are specialized on the volume integral type # and called from the basic `create_cache` method at the top. function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, + P4estMesh{2}, T8codeMesh{2}}, equations, volume_integral::VolumeIntegralFluxDifferencing, dg::DG, uEltype) NamedTuple() end function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, equations, + P4estMesh{2}, T8codeMesh{2}}, equations, volume_integral::VolumeIntegralShockCapturingHG, dg::DG, uEltype) element_ids_dg = Int[] element_ids_dgfv = Int[] @@ -70,7 +70,7 @@ function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMe end function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, equations, + P4estMesh{2}, T8codeMesh{2}}, equations, volume_integral::VolumeIntegralPureLGLFiniteVolume, dg::DG, uEltype) A3dp1_x = Array{uEltype, 3} @@ -92,7 +92,7 @@ end # The methods below are specialized on the mortar type # and called from the basic `create_cache` method at the top. function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}}, + P4estMesh{2}, T8codeMesh{2}}, equations, mortar_l2::LobattoLegendreMortarL2, uEltype) # TODO: Taal performance using different types MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)}, uEltype, 2, @@ -110,7 +110,7 @@ end # TODO: Taal discuss/refactor timer, allowing users to pass a custom timer? function rhs!(du, u, t, - mesh::Union{TreeMesh{2}, P4estMesh{2}}, equations, + mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, equations, initial_condition, boundary_conditions, source_terms::Source, dg::DG, cache) where {Source} # Reset du @@ -180,7 +180,8 @@ end function calc_volume_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, nonconservative_terms, equations, volume_integral::VolumeIntegralWeakForm, dg::DGSEM, cache) @@ -226,7 +227,8 @@ end # from the evaluation of the physical fluxes in each Cartesian direction function calc_volume_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, nonconservative_terms, equations, volume_integral::VolumeIntegralFluxDifferencing, dg::DGSEM, cache) @@ -322,7 +324,8 @@ end # TODO: Taal dimension agnostic function calc_volume_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, nonconservative_terms, equations, volume_integral::VolumeIntegralShockCapturingHG, dg::DGSEM, cache) @@ -381,7 +384,8 @@ end @inline function fv_kernel!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2} + }, nonconservative_terms, equations, volume_flux_fv, dg::DGSEM, cache, element, alpha = true) @unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache @@ -529,23 +533,24 @@ end function prolong2interfaces!(cache, u, mesh::TreeMesh{2}, equations, surface_integral, dg::DG) @unpack interfaces = cache - @unpack orientations = interfaces + @unpack orientations, neighbor_ids = interfaces + interfaces_u = interfaces.u @threaded for interface in eachinterface(dg, cache) - left_element = interfaces.neighbor_ids[1, interface] - right_element = interfaces.neighbor_ids[2, interface] + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] if orientations[interface] == 1 # interface in x-direction for j in eachnode(dg), v in eachvariable(equations) - interfaces.u[1, v, j, interface] = u[v, nnodes(dg), j, left_element] - interfaces.u[2, v, j, interface] = u[v, 1, j, right_element] + interfaces_u[1, v, j, interface] = u[v, nnodes(dg), j, left_element] + interfaces_u[2, v, j, interface] = u[v, 1, j, right_element] end else # if orientations[interface] == 2 # interface in y-direction for i in eachnode(dg), v in eachvariable(equations) - interfaces.u[1, v, i, interface] = u[v, i, nnodes(dg), left_element] - interfaces.u[2, v, i, interface] = u[v, i, 1, right_element] + interfaces_u[1, v, i, interface] = u[v, i, nnodes(dg), left_element] + interfaces_u[2, v, i, interface] = u[v, i, 1, right_element] end end end @@ -1116,8 +1121,10 @@ end function apply_jacobian!(du, mesh::TreeMesh{2}, equations, dg::DG, cache) + @unpack inverse_jacobian = cache.elements + @threaded for element in eachelement(dg, cache) - factor = -cache.elements.inverse_jacobian[element] + factor = -inverse_jacobian[element] for j in eachnode(dg), i in eachnode(dg) for v in eachvariable(equations) @@ -1137,11 +1144,13 @@ end function calc_sources!(du, u, t, source_terms, equations::AbstractEquations{2}, dg::DG, cache) + @unpack node_coordinates = cache.elements + @threaded for element in eachelement(dg, cache) for j in eachnode(dg), i in eachnode(dg) u_local = get_node_vars(u, equations, dg, i, j, element) - x_local = get_node_coords(cache.elements.node_coordinates, equations, dg, i, - j, element) + x_local = get_node_coords(node_coordinates, equations, dg, + i, j, element) du_local = source_terms(u_local, x_local, t, equations) add_to_node_vars!(du, du_local, equations, dg, i, j, element) end diff --git a/src/solvers/dgsem_tree/dg_3d.jl b/src/solvers/dgsem_tree/dg_3d.jl index 95abb2595e5..acdab900cd1 100644 --- a/src/solvers/dgsem_tree/dg_3d.jl +++ b/src/solvers/dgsem_tree/dg_3d.jl @@ -598,32 +598,33 @@ end function prolong2interfaces!(cache, u, mesh::TreeMesh{3}, equations, surface_integral, dg::DG) @unpack interfaces = cache - @unpack orientations = interfaces + @unpack orientations, neighbor_ids = interfaces + interfaces_u = interfaces.u @threaded for interface in eachinterface(dg, cache) - left_element = interfaces.neighbor_ids[1, interface] - right_element = interfaces.neighbor_ids[2, interface] + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] if orientations[interface] == 1 # interface in x-direction for k in eachnode(dg), j in eachnode(dg), v in eachvariable(equations) - interfaces.u[1, v, j, k, interface] = u[v, nnodes(dg), j, k, + interfaces_u[1, v, j, k, interface] = u[v, nnodes(dg), j, k, left_element] - interfaces.u[2, v, j, k, interface] = u[v, 1, j, k, right_element] + interfaces_u[2, v, j, k, interface] = u[v, 1, j, k, right_element] end elseif orientations[interface] == 2 # interface in y-direction for k in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) - interfaces.u[1, v, i, k, interface] = u[v, i, nnodes(dg), k, + interfaces_u[1, v, i, k, interface] = u[v, i, nnodes(dg), k, left_element] - interfaces.u[2, v, i, k, interface] = u[v, i, 1, k, right_element] + interfaces_u[2, v, i, k, interface] = u[v, i, 1, k, right_element] end else # if orientations[interface] == 3 # interface in z-direction for j in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) - interfaces.u[1, v, i, j, interface] = u[v, i, j, nnodes(dg), + interfaces_u[1, v, i, j, interface] = u[v, i, j, nnodes(dg), left_element] - interfaces.u[2, v, i, j, interface] = u[v, i, j, 1, right_element] + interfaces_u[2, v, i, j, interface] = u[v, i, j, 1, right_element] end end end @@ -1350,8 +1351,10 @@ end function apply_jacobian!(du, mesh::TreeMesh{3}, equations, dg::DG, cache) + @unpack inverse_jacobian = cache.elements + @threaded for element in eachelement(dg, cache) - factor = -cache.elements.inverse_jacobian[element] + factor = -inverse_jacobian[element] for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) for v in eachvariable(equations) @@ -1371,11 +1374,13 @@ end function calc_sources!(du, u, t, source_terms, equations::AbstractEquations{3}, dg::DG, cache) + @unpack node_coordinates = cache.elements + @threaded for element in eachelement(dg, cache) for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) u_local = get_node_vars(u, equations, dg, i, j, k, element) - x_local = get_node_coords(cache.elements.node_coordinates, equations, dg, i, - j, k, element) + x_local = get_node_coords(node_coordinates, equations, dg, + i, j, k, element) du_local = source_terms(u_local, x_local, t, equations) add_to_node_vars!(du, du_local, equations, dg, i, j, k, element) end diff --git a/src/solvers/dgsem_tree/dg_3d_parabolic.jl b/src/solvers/dgsem_tree/dg_3d_parabolic.jl index d6d74637021..5b63b971cd8 100644 --- a/src/solvers/dgsem_tree/dg_3d_parabolic.jl +++ b/src/solvers/dgsem_tree/dg_3d_parabolic.jl @@ -13,7 +13,7 @@ # 2. compute f(u, grad(u)) # 3. compute div(f(u, grad(u))) (i.e., the "regular" rhs! call) # boundary conditions will be applied to both grad(u) and div(f(u, grad(u))). -function rhs_parabolic!(du, u, t, mesh::TreeMesh{3}, +function rhs_parabolic!(du, u, t, mesh::Union{TreeMesh{3}, P4estMesh{3}}, equations_parabolic::AbstractEquationsParabolic, initial_condition, boundary_conditions_parabolic, source_terms, dg::DG, parabolic_scheme, cache, cache_parabolic) @@ -105,7 +105,7 @@ end # Transform solution variables prior to taking the gradient # (e.g., conservative to primitive variables). Defaults to doing nothing. # TODO: can we avoid copying data? -function transform_variables!(u_transformed, u, mesh::TreeMesh{3}, +function transform_variables!(u_transformed, u, mesh::Union{TreeMesh{3}, P4estMesh{3}}, equations_parabolic::AbstractEquationsParabolic, dg::DG, parabolic_scheme, cache, cache_parabolic) @threaded for element in eachelement(dg, cache) @@ -325,7 +325,8 @@ function prolong2boundaries!(cache_parabolic, flux_viscous, return nothing end -function calc_viscous_fluxes!(flux_viscous, gradients, u_transformed, mesh::TreeMesh{3}, +function calc_viscous_fluxes!(flux_viscous, gradients, u_transformed, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, equations_parabolic::AbstractEquationsParabolic, dg::DG, cache, cache_parabolic) gradients_x, gradients_y, gradients_z = gradients @@ -379,7 +380,7 @@ end function calc_boundary_flux_gradients!(cache, t, boundary_conditions_parabolic::BoundaryConditionPeriodic, - mesh::TreeMesh{3}, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, equations_parabolic::AbstractEquationsParabolic, surface_integral, dg::DG) return nothing @@ -387,7 +388,7 @@ end function calc_boundary_flux_divergence!(cache, t, boundary_conditions_parabolic::BoundaryConditionPeriodic, - mesh::TreeMesh{3}, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, equations_parabolic::AbstractEquationsParabolic, surface_integral, dg::DG) return nothing @@ -806,7 +807,7 @@ end # This is because the parabolic fluxes are assumed to be of the form # `du/dt + df/dx = dg/dx + source(x,t)`, # where f(u) is the inviscid flux and g(u) is the viscous flux. -function apply_jacobian_parabolic!(du, mesh::TreeMesh{3}, +function apply_jacobian_parabolic!(du, mesh::Union{TreeMesh{3}, P4estMesh{3}}, equations::AbstractEquationsParabolic, dg::DG, cache) @threaded for element in eachelement(dg, cache) factor = cache.elements.inverse_jacobian[element] diff --git a/src/solvers/dgsem_tree/indicators.jl b/src/solvers/dgsem_tree/indicators.jl index b8f8a796f2b..4b83e9c1a9e 100644 --- a/src/solvers/dgsem_tree/indicators.jl +++ b/src/solvers/dgsem_tree/indicators.jl @@ -92,6 +92,77 @@ end function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorHennemannGassner) @nospecialize indicator # reduce precompilation time + setup = [ + "indicator variable" => indicator.variable, + "max. α" => indicator.alpha_max, + "min. α" => indicator.alpha_min, + "smooth α" => (indicator.alpha_smooth ? "yes" : "no"), + ] + summary_box(io, "IndicatorHennemannGassner", setup) +end + +# TODO: TrixiShallowWater: move the new indicator and all associated routines to the new package +""" + IndicatorHennemannGassnerShallowWater(equations::AbstractEquations, basis; + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable) + +Modified version of the [`IndicatorHennemannGassner`](@ref) +indicator used for shock-capturing for shallow water equations. After +the element-wise values for the blending factors are computed an additional check +is made to see if the element is partially wet. In this case, partially wet elements +are set to use the pure finite volume scheme that is guaranteed to be well-balanced +for this wet/dry transition state of the flow regime. + +See also [`VolumeIntegralShockCapturingHG`](@ref). + +## References + +- Hennemann, Gassner (2020) + "A provably entropy stable subcell shock capturing approach for high order split form DG" + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) +""" +struct IndicatorHennemannGassnerShallowWater{RealT <: Real, Variable, Cache} <: + AbstractIndicator + alpha_max::RealT + alpha_min::RealT + alpha_smooth::Bool + variable::Variable + cache::Cache +end + +# this method is used when the indicator is constructed as for shock-capturing volume integrals +# of the shallow water equations +# It modifies the shock-capturing indicator to use full FV method in dry cells +function IndicatorHennemannGassnerShallowWater(equations::AbstractShallowWaterEquations, + basis; + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable) + alpha_max, alpha_min = promote(alpha_max, alpha_min) + cache = create_cache(IndicatorHennemannGassner, equations, basis) + IndicatorHennemannGassnerShallowWater{typeof(alpha_max), typeof(variable), + typeof(cache)}(alpha_max, alpha_min, + alpha_smooth, variable, cache) +end + +function Base.show(io::IO, indicator::IndicatorHennemannGassnerShallowWater) + @nospecialize indicator # reduce precompilation time + + print(io, "IndicatorHennemannGassnerShallowWater(") + print(io, indicator.variable) + print(io, ", alpha_max=", indicator.alpha_max) + print(io, ", alpha_min=", indicator.alpha_min) + print(io, ", alpha_smooth=", indicator.alpha_smooth) + print(io, ")") +end + +function Base.show(io::IO, ::MIME"text/plain", + indicator::IndicatorHennemannGassnerShallowWater) + @nospecialize indicator # reduce precompilation time if get(io, :compact, false) show(io, indicator) @@ -102,7 +173,7 @@ function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorHennemannGass "min. α" => indicator.alpha_min, "smooth α" => (indicator.alpha_smooth ? "yes" : "no"), ] - summary_box(io, "IndicatorHennemannGassner", setup) + summary_box(io, "IndicatorHennemannGassnerShallowWater", setup) end end diff --git a/src/solvers/dgsem_tree/indicators_1d.jl b/src/solvers/dgsem_tree/indicators_1d.jl index e722584bb2e..8b57348861c 100644 --- a/src/solvers/dgsem_tree/indicators_1d.jl +++ b/src/solvers/dgsem_tree/indicators_1d.jl @@ -24,6 +24,115 @@ function create_cache(typ::Type{IndicatorHennemannGassner}, mesh, create_cache(typ, equations, dg.basis) end +# Modified indicator for ShallowWaterEquations1D to apply full FV method on cells +# containing some "dry" LGL nodes. That is, if an element is partially "wet" then it becomes a +# full FV element. +# +# TODO: TrixiShallowWater: move new indicator type +function (indicator_hg::IndicatorHennemannGassnerShallowWater)(u::AbstractArray{<:Any, 3 + }, + mesh, + equations::ShallowWaterEquations1D, + dg::DGSEM, cache; + kwargs...) + @unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg + @unpack alpha, alpha_tmp, indicator_threaded, modal_threaded = indicator_hg.cache + # TODO: Taal refactor, when to `resize!` stuff changed possibly by AMR? + # Shall we implement `resize!(semi::AbstractSemidiscretization, new_size)` + # or just `resize!` whenever we call the relevant methods as we do now? + resize!(alpha, nelements(dg, cache)) + if alpha_smooth + resize!(alpha_tmp, nelements(dg, cache)) + end + + # magic parameters + threshold = 0.5 * 10^(-1.8 * (nnodes(dg))^0.25) + parameter_s = log((1 - 0.0001) / 0.0001) + + # If the water height `h` at one LGL node is lower than `threshold_partially_wet` + # the indicator sets the element-wise blending factor alpha[element] = 1 + # via the local variable `indicator_wet`. In turn, this ensures that a pure + # FV method is used in partially wet cells and guarantees the well-balanced property. + # + # Hard-coded cut-off value of `threshold_partially_wet = 1e-4` was determined through many numerical experiments. + # Overall idea is to increase robustness when computing the velocity on (nearly) dry cells which + # could be "dangerous" due to division of conservative variables, e.g., v = hv / h. + # Here, the impact of the threshold on the number of cells being updated with FV is not that + # significant. However, its impact on the robustness is very significant. + # The value can be seen as a trade-off between accuracy and stability. + # Well-balancedness of the scheme on partially wet cells with hydrostatic reconstruction + # can only be proven for the FV method (see Chen and Noelle). + # Therefore we set alpha to one regardless of its given maximum value. + threshold_partially_wet = 1e-4 + + @threaded for element in eachelement(dg, cache) + indicator = indicator_threaded[Threads.threadid()] + modal = modal_threaded[Threads.threadid()] + + # (Re-)set dummy variable for alpha_dry + indicator_wet = 1 + + # Calculate indicator variables at Gauss-Lobatto nodes + for i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, element) + h, _, _ = u_local + + if h <= threshold_partially_wet + indicator_wet = 0 + end + + indicator[i] = indicator_hg.variable(u_local, equations) + end + + # Convert to modal representation + multiply_scalar_dimensionwise!(modal, dg.basis.inverse_vandermonde_legendre, + indicator) + + # Calculate total energies for all modes, without highest, without two highest + total_energy = zero(eltype(modal)) + for i in 1:nnodes(dg) + total_energy += modal[i]^2 + end + total_energy_clip1 = zero(eltype(modal)) + for i in 1:(nnodes(dg) - 1) + total_energy_clip1 += modal[i]^2 + end + total_energy_clip2 = zero(eltype(modal)) + for i in 1:(nnodes(dg) - 2) + total_energy_clip2 += modal[i]^2 + end + + # Calculate energy in higher modes + energy = max((total_energy - total_energy_clip1) / total_energy, + (total_energy_clip1 - total_energy_clip2) / total_energy_clip1) + + alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold))) + + # Take care of the case close to pure DG + if alpha_element < alpha_min + alpha_element = zero(alpha_element) + end + + # Take care of the case close to pure FV + if alpha_element > 1 - alpha_min + alpha_element = one(alpha_element) + end + + # Clip the maximum amount of FV allowed or set to one depending on indicator_wet + if indicator_wet == 0 + alpha[element] = 1 + else # Element is not defined as dry but wet + alpha[element] = min(alpha_max, alpha_element) + end + end + + if alpha_smooth + apply_smoothing!(mesh, alpha, alpha_tmp, dg, cache) + end + + return alpha +end + # Use this function barrier and unpack inside to avoid passing closures to Polyester.jl # with @batch (@threaded). # Otherwise, @threaded does not work here with Julia ARM on macOS. diff --git a/src/solvers/dgsem_tree/indicators_2d.jl b/src/solvers/dgsem_tree/indicators_2d.jl index 085cb71ad0c..2f34e0eb661 100644 --- a/src/solvers/dgsem_tree/indicators_2d.jl +++ b/src/solvers/dgsem_tree/indicators_2d.jl @@ -28,6 +28,116 @@ function create_cache(typ::Type{IndicatorHennemannGassner}, mesh, create_cache(typ, equations, dg.basis) end +# Modified indicator for ShallowWaterEquations2D to apply full FV method on cells +# containing some "dry" LGL nodes. That is, if an element is partially "wet" then it becomes a +# full FV element. +# +# TODO: TrixiShallowWater: move new indicator type +function (indicator_hg::IndicatorHennemannGassnerShallowWater)(u::AbstractArray{<:Any, 4 + }, + mesh, + equations::ShallowWaterEquations2D, + dg::DGSEM, cache; + kwargs...) + @unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg + @unpack alpha, alpha_tmp, indicator_threaded, modal_threaded, modal_tmp1_threaded = indicator_hg.cache + # TODO: Taal refactor, when to `resize!` stuff changed possibly by AMR? + # Shall we implement `resize!(semi::AbstractSemidiscretization, new_size)` + # or just `resize!` whenever we call the relevant methods as we do now? + resize!(alpha, nelements(dg, cache)) + if alpha_smooth + resize!(alpha_tmp, nelements(dg, cache)) + end + + # magic parameters + threshold = 0.5 * 10^(-1.8 * (nnodes(dg))^0.25) + parameter_s = log((1 - 0.0001) / 0.0001) + + # If the water height `h` at one LGL node is lower than `threshold_partially_wet` + # the indicator sets the element-wise blending factor alpha[element] = 1 + # via the local variable `indicator_wet`. In turn, this ensures that a pure + # FV method is used in partially wet cells and guarantees the well-balanced property. + # + # Hard-coded cut-off value of `threshold_partially_wet = 1e-4` was determined through many numerical experiments. + # Overall idea is to increase robustness when computing the velocity on (nearly) dry cells which + # could be "dangerous" due to division of conservative variables, e.g., v1 = hv1 / h. + # Here, the impact of the threshold on the number of cells being updated with FV is not that + # significant. However, its impact on the robustness is very significant. + # The value can be seen as a trade-off between accuracy and stability. + # Well-balancedness of the scheme on partially wet cells with hydrostatic reconstruction + # can only be proven for the FV method (see Chen and Noelle). + # Therefore we set alpha to be one regardless of its given value from the modal indicator. + threshold_partially_wet = 1e-4 + + @threaded for element in eachelement(dg, cache) + indicator = indicator_threaded[Threads.threadid()] + modal = modal_threaded[Threads.threadid()] + modal_tmp1 = modal_tmp1_threaded[Threads.threadid()] + + # (Re-)set dummy variable for alpha_dry + indicator_wet = 1 + + # Calculate indicator variables at Gauss-Lobatto nodes + for j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, element) + h, _, _, _ = u_local + + if h <= threshold_partially_wet + indicator_wet = 0 + end + + indicator[i, j] = indicator_hg.variable(u_local, equations) + end + + # Convert to modal representation + multiply_scalar_dimensionwise!(modal, dg.basis.inverse_vandermonde_legendre, + indicator, modal_tmp1) + + # Calculate total energies for all modes, without highest, without two highest + total_energy = zero(eltype(modal)) + for j in 1:nnodes(dg), i in 1:nnodes(dg) + total_energy += modal[i, j]^2 + end + total_energy_clip1 = zero(eltype(modal)) + for j in 1:(nnodes(dg) - 1), i in 1:(nnodes(dg) - 1) + total_energy_clip1 += modal[i, j]^2 + end + total_energy_clip2 = zero(eltype(modal)) + for j in 1:(nnodes(dg) - 2), i in 1:(nnodes(dg) - 2) + total_energy_clip2 += modal[i, j]^2 + end + + # Calculate energy in higher modes + energy = max((total_energy - total_energy_clip1) / total_energy, + (total_energy_clip1 - total_energy_clip2) / total_energy_clip1) + + alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold))) + + # Take care of the case close to pure DG + if alpha_element < alpha_min + alpha_element = zero(alpha_element) + end + + # Take care of the case close to pure FV + if alpha_element > 1 - alpha_min + alpha_element = one(alpha_element) + end + + # Clip the maximum amount of FV allowed or set to 1 depending on indicator_wet + if indicator_wet == 0 + alpha[element] = 1 + else # Element is not defined as dry but wet + alpha[element] = min(alpha_max, alpha_element) + end + end + + if alpha_smooth + apply_smoothing!(mesh, alpha, alpha_tmp, dg, cache) + end + + return alpha +end + # Use this function barrier and unpack inside to avoid passing closures to Polyester.jl # with @batch (@threaded). # Otherwise, @threaded does not work here with Julia ARM on macOS. @@ -98,7 +208,8 @@ end end # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha -function apply_smoothing!(mesh::Union{TreeMesh{2}, P4estMesh{2}}, alpha, alpha_tmp, dg, +function apply_smoothing!(mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, alpha, + alpha_tmp, dg, cache) # Copy alpha values such that smoothing is indpedenent of the element access order alpha_tmp .= alpha diff --git a/src/solvers/dgsem_unstructured/dg_2d.jl b/src/solvers/dgsem_unstructured/dg_2d.jl index 95dec027a82..7b8dafdddd2 100644 --- a/src/solvers/dgsem_unstructured/dg_2d.jl +++ b/src/solvers/dgsem_unstructured/dg_2d.jl @@ -307,14 +307,14 @@ end # TODO: Taal dimension agnostic function calc_boundary_flux!(cache, t, boundary_condition::BoundaryConditionPeriodic, - mesh::Union{UnstructuredMesh2D, P4estMesh}, + mesh::Union{UnstructuredMesh2D, P4estMesh, T8codeMesh}, equations, surface_integral, dg::DG) @assert isempty(eachboundary(dg, cache)) end # Function barrier for type stability function calc_boundary_flux!(cache, t, boundary_conditions, - mesh::Union{UnstructuredMesh2D, P4estMesh}, + mesh::Union{UnstructuredMesh2D, P4estMesh, T8codeMesh}, equations, surface_integral, dg::DG) @unpack boundary_condition_types, boundary_indices = boundary_conditions @@ -327,7 +327,8 @@ end # in a type-stable way using "lispy tuple programming". function calc_boundary_flux_by_type!(cache, t, BCs::NTuple{N, Any}, BC_indices::NTuple{N, Vector{Int}}, - mesh::Union{UnstructuredMesh2D, P4estMesh}, + mesh::Union{UnstructuredMesh2D, P4estMesh, + T8codeMesh}, equations, surface_integral, dg::DG) where {N} # Extract the boundary condition type and index vector boundary_condition = first(BCs) @@ -350,7 +351,8 @@ end # terminate the type-stable iteration over tuples function calc_boundary_flux_by_type!(cache, t, BCs::Tuple{}, BC_indices::Tuple{}, - mesh::Union{UnstructuredMesh2D, P4estMesh}, + mesh::Union{UnstructuredMesh2D, P4estMesh, + T8codeMesh}, equations, surface_integral, dg::DG) nothing end diff --git a/src/solvers/fdsbp_tree/fdsbp.jl b/src/solvers/fdsbp_tree/fdsbp.jl index cbb6fd16243..11b09c6df9c 100644 --- a/src/solvers/fdsbp_tree/fdsbp.jl +++ b/src/solvers/fdsbp_tree/fdsbp.jl @@ -27,6 +27,9 @@ The other arguments have the same meaning as in [`DG`](@ref) or [`DGSEM`](@ref). """ const FDSBP = DG{Basis} where {Basis <: AbstractDerivativeOperator} +# Internal abbreviation for easier-to-read dispatch (not exported) +const PeriodicFDSBP = FDSBP{Basis} where {Basis <: AbstractPeriodicDerivativeOperator} + function FDSBP(D_SBP::AbstractDerivativeOperator; surface_integral, volume_integral) # `nothing` is passed as `mortar` return DG(D_SBP, nothing, surface_integral, volume_integral) diff --git a/src/solvers/fdsbp_tree/fdsbp_1d.jl b/src/solvers/fdsbp_tree/fdsbp_1d.jl index c7712074940..0de0cff4851 100644 --- a/src/solvers/fdsbp_tree/fdsbp_1d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_1d.jl @@ -165,6 +165,14 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{1}, return nothing end +# Periodic FDSBP operators need to use a single element without boundaries +function calc_surface_integral!(du, u, mesh::TreeMesh1D, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::PeriodicFDSBP, cache) + @assert nelements(dg, cache) == 1 + return nothing +end + # Specialized interface flux computation because the upwind solver does # not require a standard numerical flux (Riemann solver). The flux splitting # already separates the solution information into right-traveling and @@ -239,13 +247,25 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{1}, return nothing end +# Periodic FDSBP operators need to use a single element without boundaries +function calc_surface_integral!(du, u, mesh::TreeMesh1D, + equations, surface_integral::SurfaceIntegralUpwind, + dg::PeriodicFDSBP, cache) + @assert nelements(dg, cache) == 1 + return nothing +end + # AnalysisCallback function integrate_via_indices(func::Func, u, mesh::TreeMesh{1}, equations, dg::FDSBP, cache, args...; normalize = true) where {Func} # TODO: FD. This is rather inefficient right now and allocates... - weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) # Initialize integral with zeros of the right shape integral = zero(func(u, 1, 1, equations, dg, args...)) @@ -271,7 +291,11 @@ 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)) + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) @unpack node_coordinates = cache.elements # Set up data structures diff --git a/src/solvers/fdsbp_tree/fdsbp_2d.jl b/src/solvers/fdsbp_tree/fdsbp_2d.jl index 241e0d95342..beff605629a 100644 --- a/src/solvers/fdsbp_tree/fdsbp_2d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_2d.jl @@ -201,6 +201,14 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{2}, return nothing end +# Periodic FDSBP operators need to use a single element without boundaries +function calc_surface_integral!(du, u, mesh::TreeMesh2D, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::PeriodicFDSBP, cache) + @assert nelements(dg, cache) == 1 + return nothing +end + # Specialized interface flux computation because the upwind solver does # not require a standard numerical flux (Riemann solver). The flux splitting # already separates the solution information into right-traveling and @@ -295,12 +303,24 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{2}, return nothing end +# Periodic FDSBP operators need to use a single element without boundaries +function calc_surface_integral!(du, u, mesh::TreeMesh2D, + equations, surface_integral::SurfaceIntegralUpwind, + dg::PeriodicFDSBP, cache) + @assert nelements(dg, cache) == 1 + return nothing +end + # AnalysisCallback function integrate_via_indices(func::Func, u, mesh::TreeMesh{2}, equations, dg::FDSBP, cache, args...; normalize = true) where {Func} # TODO: FD. This is rather inefficient right now and allocates... - weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) # Initialize integral with zeros of the right shape integral = zero(func(u, 1, 1, 1, equations, dg, args...)) @@ -326,7 +346,11 @@ function calc_error_norms(func, u, t, analyzer, mesh::TreeMesh{2}, equations, initial_condition, dg::FDSBP, cache, cache_analysis) # TODO: FD. This is rather inefficient right now and allocates... - weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) @unpack node_coordinates = cache.elements # Set up data structures diff --git a/src/solvers/fdsbp_tree/fdsbp_3d.jl b/src/solvers/fdsbp_tree/fdsbp_3d.jl index a4f69d3d481..0c3f18b6d6e 100644 --- a/src/solvers/fdsbp_tree/fdsbp_3d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_3d.jl @@ -237,6 +237,14 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{3}, return nothing end +# Periodic FDSBP operators need to use a single element without boundaries +function calc_surface_integral!(du, u, mesh::TreeMesh3D, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::PeriodicFDSBP, cache) + @assert nelements(dg, cache) == 1 + return nothing +end + # Specialized interface flux computation because the upwind solver does # not require a standard numerical flux (Riemann solver). The flux splitting # already separates the solution information into right-traveling and @@ -346,13 +354,25 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{3}, return nothing end +# Periodic FDSBP operators need to use a single element without boundaries +function calc_surface_integral!(du, u, mesh::TreeMesh3D, + equations, surface_integral::SurfaceIntegralUpwind, + dg::PeriodicFDSBP, cache) + @assert nelements(dg, cache) == 1 + return nothing +end + # AnalysisCallback function integrate_via_indices(func::Func, u, mesh::TreeMesh{3}, equations, dg::FDSBP, cache, args...; normalize = true) where {Func} # TODO: FD. This is rather inefficient right now and allocates... - weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) # Initialize integral with zeros of the right shape integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...)) @@ -378,7 +398,11 @@ 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)) + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) @unpack node_coordinates = cache.elements # Set up data structures diff --git a/test/Project.toml b/test/Project.toml index 7d386415227..7115a19b441 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -17,15 +17,16 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [preferences.OrdinaryDiffEq] PrecompileAutoSpecialize = false PrecompileAutoSwitch = false -PrecompileDefaultSpecialize = true +PrecompileDefaultSpecialize = false PrecompileFunctionWrapperSpecialize = false -PrecompileLowStorage = true +PrecompileLowStorage = false PrecompileNoSpecialize = false -PrecompileNonStiff = true +PrecompileNonStiff = false PrecompileStiff = false diff --git a/test/runtests.jl b/test/runtests.jl index f76811dddbf..1b0c745dbfd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,116 +1,125 @@ using Test using MPI: mpiexec -# run tests on Travis CI in parallel -const TRIXI_TEST = get(ENV, "TRIXI_TEST", "all") +# We run tests in parallel with CI jobs setting the `TRIXI_TEST` environment +# variable to determine the subset of tests to execute. +# By default, we just run the threaded tests since they are relatively cheap +# and test a good amount of different functionality. +const TRIXI_TEST = get(ENV, "TRIXI_TEST", "threaded") const TRIXI_MPI_NPROCS = clamp(Sys.CPU_THREADS, 2, 3) -const TRIXI_NTHREADS = clamp(Sys.CPU_THREADS, 2, 3) +const TRIXI_NTHREADS = clamp(Sys.CPU_THREADS, 2, 3) @time @testset "Trixi.jl tests" begin - # This is placed first since tests error out otherwise if `TRIXI_TEST == "all"`, - # at least on some systems. - @time if TRIXI_TEST == "all" || TRIXI_TEST == "mpi" - # Do a dummy `@test true`: - # If the process errors out the testset would error out as well, - # cf. https://github.com/JuliaParallel/MPI.jl/pull/391 - @test true - - # There are spurious test failures of Trixi.jl with MPI on Windows, see - # https://github.com/trixi-framework/Trixi.jl/issues/901 - # To reduce their impact, we do not test MPI with coverage on Windows. - # This reduces the chance to hit a spurious test failure by one half. - # In addition, it looks like the Linux GitHub runners run out of memory during the 3D tests - # with coverage, so we currently do not test MPI with coverage on Linux. For more details, - # see the discussion at https://github.com/trixi-framework/Trixi.jl/pull/1062#issuecomment-1035901020 - cmd = string(Base.julia_cmd()) - coverage = occursin("--code-coverage", cmd) && !occursin("--code-coverage=none", cmd) - if !(coverage && Sys.iswindows()) && !(coverage && Sys.islinux()) - # We provide a `--heap-size-hint` to avoid/reduce out-of-memory errors during CI testing - mpiexec() do cmd - run(`$cmd -n $TRIXI_MPI_NPROCS $(Base.julia_cmd()) --threads=1 --check-bounds=yes --heap-size-hint=1G $(abspath("test_mpi.jl"))`) - end - end - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "threaded" || TRIXI_TEST == "threaded_legacy" - # Do a dummy `@test true`: - # If the process errors out the testset would error out as well, - # cf. https://github.com/JuliaParallel/MPI.jl/pull/391 - @test true - - run(`$(Base.julia_cmd()) --threads=$TRIXI_NTHREADS --check-bounds=yes --code-coverage=none $(abspath("test_threaded.jl"))`) - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part1" - include("test_tree_1d.jl") - include("test_tree_2d_part1.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part2" - include("test_tree_2d_part2.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part3" - include("test_tree_2d_part3.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part4" - include("test_tree_3d_part1.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part5" - include("test_tree_3d_part2.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part6" - include("test_tree_3d_part3.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "structured" - include("test_structured_1d.jl") - include("test_structured_2d.jl") - include("test_structured_3d.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "p4est_part1" - include("test_p4est_2d.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "p4est_part2" - include("test_p4est_3d.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "unstructured_dgmulti" - include("test_unstructured_2d.jl") - include("test_dgmulti_1d.jl") - include("test_dgmulti_2d.jl") - include("test_dgmulti_3d.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "parabolic" - include("test_parabolic_1d.jl") - include("test_parabolic_2d.jl") - include("test_parabolic_3d.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "misc_part1" - include("test_unit.jl") - include("test_visualization.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "misc_part2" - include("test_special_elixirs.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "performance_specializations_part1" - include("test_performance_specializations_2d.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "performance_specializations_part2" - include("test_performance_specializations_3d.jl") - end - - @time if TRIXI_TEST == "all" || TRIXI_TEST == "paper_self_gravitating_gas_dynamics" - include("test_paper_self_gravitating_gas_dynamics.jl") - end + # This is placed first since tests error out otherwise if `TRIXI_TEST == "all"`, + # at least on some systems. + @time if TRIXI_TEST == "all" || TRIXI_TEST == "mpi" + # Do a dummy `@test true`: + # If the process errors out the testset would error out as well, + # cf. https://github.com/JuliaParallel/MPI.jl/pull/391 + @test true + + # There are spurious test failures of Trixi.jl with MPI on Windows, see + # https://github.com/trixi-framework/Trixi.jl/issues/901 + # To reduce their impact, we do not test MPI with coverage on Windows. + # This reduces the chance to hit a spurious test failure by one half. + # In addition, it looks like the Linux GitHub runners run out of memory during the 3D tests + # with coverage, so we currently do not test MPI with coverage on Linux. For more details, + # see the discussion at https://github.com/trixi-framework/Trixi.jl/pull/1062#issuecomment-1035901020 + cmd = string(Base.julia_cmd()) + coverage = occursin("--code-coverage", cmd) && + !occursin("--code-coverage=none", cmd) + if !(coverage && Sys.iswindows()) && !(coverage && Sys.islinux()) + # We provide a `--heap-size-hint` to avoid/reduce out-of-memory errors during CI testing + mpiexec() do cmd + run(`$cmd -n $TRIXI_MPI_NPROCS $(Base.julia_cmd()) --threads=1 --check-bounds=yes --heap-size-hint=1G $(abspath("test_mpi.jl"))`) + end + end + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "threaded" || + TRIXI_TEST == "threaded_legacy" + # Do a dummy `@test true`: + # If the process errors out the testset would error out as well, + # cf. https://github.com/JuliaParallel/MPI.jl/pull/391 + @test true + + run(`$(Base.julia_cmd()) --threads=$TRIXI_NTHREADS --check-bounds=yes --code-coverage=none $(abspath("test_threaded.jl"))`) + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part1" + include("test_tree_1d.jl") + include("test_tree_2d_part1.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part2" + include("test_tree_2d_part2.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part3" + include("test_tree_2d_part3.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part4" + include("test_tree_3d_part1.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part5" + include("test_tree_3d_part2.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "tree_part6" + include("test_tree_3d_part3.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "structured" + include("test_structured_1d.jl") + include("test_structured_2d.jl") + include("test_structured_3d.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "p4est_part1" + include("test_p4est_2d.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "p4est_part2" + include("test_p4est_3d.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "t8code_part1" + include("test_t8code_2d.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "unstructured_dgmulti" + include("test_unstructured_2d.jl") + include("test_dgmulti_1d.jl") + include("test_dgmulti_2d.jl") + include("test_dgmulti_3d.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "parabolic" + include("test_parabolic_1d.jl") + include("test_parabolic_2d.jl") + include("test_parabolic_3d.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "misc_part1" + include("test_unit.jl") + include("test_visualization.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "misc_part2" + include("test_special_elixirs.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "performance_specializations_part1" + include("test_performance_specializations_2d.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "performance_specializations_part2" + include("test_performance_specializations_3d.jl") + end + + @time if TRIXI_TEST == "all" || TRIXI_TEST == "paper_self_gravitating_gas_dynamics" + include("test_paper_self_gravitating_gas_dynamics.jl") + end end diff --git a/test/test_mpi_tree.jl b/test/test_mpi_tree.jl index 84d2609cbb1..8403fcf1b04 100644 --- a/test/test_mpi_tree.jl +++ b/test/test_mpi_tree.jl @@ -55,10 +55,16 @@ CI_ON_WINDOWS = (get(ENV, "GITHUB_ACTIONS", false) == "true") && Sys.iswindows() # Linear scalar advection with AMR # These example files are only for testing purposes and have no practical use @trixi_testset "elixir_advection_amr_refine_twice.jl" begin + # Here, we also test that SaveSolutionCallback prints multiple mesh files with AMR + # Start with a clean environment: remove Trixi.jl output directory if it exists + outdir = "out" + isdir(outdir) && rm(outdir, recursive=true) @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr_refine_twice.jl"), l2 = [0.00020547512522578292], linf = [0.007831753383083506], coverage_override = (maxiters=6,)) + meshfiles = filter(file -> endswith(file,".h5") && startswith(file,"mesh"), readdir(outdir)) + @test length(meshfiles) > 1 end @trixi_testset "elixir_advection_amr_coarsen_twice.jl" begin diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index f66664c7a89..c4ce2619e15 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -164,6 +164,11 @@ isdir(outdir) && rm(outdir, recursive=true) tspan = (0.0, 0.02)) end + @trixi_testset "elixir_linearizedeuler_gaussian_source.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gaussian_source.jl"), + l2 = [0.006047938590548741, 0.0040953286019907035, 0.004222698522497298, 0.006269492499336128], + linf = [0.06386175207349379, 0.0378926444850457, 0.041759728067967065, 0.06430136016259067]) + end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_parabolic_2d.jl b/test/test_parabolic_2d.jl index 471b976e990..57f296b55fe 100644 --- a/test/test_parabolic_2d.jl +++ b/test/test_parabolic_2d.jl @@ -136,6 +136,10 @@ isdir(outdir) && rm(outdir, recursive=true) @trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl" begin @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", "elixir_navierstokes_convergence.jl"), initial_refinement_level = 2, tspan=(0.0, 0.1), + analysis_callback = AnalysisCallback(semi, interval=analysis_interval, + extra_analysis_integrals=(energy_kinetic, + energy_internal, + enstrophy)), l2 = [0.002111672530658797, 0.0034322351490857846, 0.0038742528195910416, 0.012469246082568561], linf = [0.012006418939223495, 0.035520871209746126, 0.024512747492231427, 0.11191122588756564] ) diff --git a/test/test_parabolic_3d.jl b/test/test_parabolic_3d.jl index 1ae5eed44ae..67a27238969 100644 --- a/test/test_parabolic_3d.jl +++ b/test/test_parabolic_3d.jl @@ -86,9 +86,24 @@ isdir(outdir) && rm(outdir, recursive=true) ) end -end + @trixi_testset "P4estMesh3D: elixir_navierstokes_convergence.jl" begin + @test_trixi_include(joinpath(examples_dir(), "p4est_3d_dgsem", "elixir_navierstokes_convergence.jl"), + initial_refinement_level = 2, tspan=(0.0, 0.1), + l2 = [0.00026599105554982194, 0.000461877794472316, 0.0005424899076052261, 0.0004618777944723191, 0.0015846392581126832], + linf = [0.0025241668929956163, 0.006308461681816373, 0.004334939663169113, 0.006308461681804009, 0.03176343480493493] + ) + end + @trixi_testset "P4estMesh3D: elixir_navierstokes_taylor_green_vortex.jl" begin + @test_trixi_include(joinpath(examples_dir(), "p4est_3d_dgsem", "elixir_navierstokes_taylor_green_vortex.jl"), + initial_refinement_level = 2, tspan=(0.0, 0.25), + l2 = [0.0001547509861140407, 0.015637861347119624, 0.015637861347119687, 0.022024699158522523, 0.009711013505930812], + linf = [0.0006696415247340326, 0.03442565722527785, 0.03442565722577423, 0.06295407168705314, 0.032857472756916195] + ) + end + +end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn isdir(outdir) && rm(outdir, recursive=true) -end # module +end # module \ No newline at end of file diff --git a/test/test_structured_1d.jl b/test/test_structured_1d.jl index ec8c7a138d5..d280e2a5e01 100644 --- a/test/test_structured_1d.jl +++ b/test/test_structured_1d.jl @@ -39,6 +39,14 @@ isdir(outdir) && rm(outdir, recursive=true) tspan = (0.0, 0.3)) end + @trixi_testset "elixir_euler_sedov_hll_davis.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [1.278661029299215, 0.0663853410742763, 0.9585741943783386], + linf = [3.1661064228547255, 0.16256363944708607, 2.667676158812806], + tspan = (0.0, 12.5), + surface_flux = FluxHLL(min_max_speed_davis)) + end + @trixi_testset "elixir_euler_source_terms.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), # Expected errors are exactly the same as with TreeMesh! diff --git a/test/test_structured_2d.jl b/test/test_structured_2d.jl index 16fc72f0a46..75937ba82ad 100644 --- a/test/test_structured_2d.jl +++ b/test/test_structured_2d.jl @@ -1,5 +1,7 @@ module TestExamplesStructuredMesh2D +# TODO: TrixiShallowWater: move any wet/dry tests to new package + using Test using Trixi @@ -20,7 +22,7 @@ isdir(outdir) && rm(outdir, recursive=true) end @trixi_testset "elixir_advection_coupled.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_coupled.jl"), + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_coupled.jl"), l2 = [7.816742843181738e-6, 7.816742843196112e-6], linf = [6.314906965543265e-5, 6.314906965410039e-5], coverage_override = (maxiters=10^5,)) @@ -270,6 +272,27 @@ isdir(outdir) && rm(outdir, recursive=true) tspan = (0.0, 0.25)) end + @trixi_testset "elixir_shallowwater_well_balanced_wet_dry.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced_wet_dry.jl"), + l2 = [0.019731646454942086, 1.0694532773278277e-14, 1.1969913383405568e-14, 0.0771517260037954], + linf = [0.4999999999998892, 6.067153702623552e-14, 4.4849667259339357e-14, 1.9999999999999993], + tspan = (0.0, 0.25)) + end + + @trixi_testset "elixir_shallowwater_conical_island.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_conical_island.jl"), + l2 = [0.04593154164306353, 0.1644534881916908, 0.16445348819169076, 0.0011537702354532122], + linf = [0.21100717610846442, 0.9501592344310412, 0.950159234431041, 0.021790250683516296], + tspan = (0.0, 0.025)) + end + + @trixi_testset "elixir_shallowwater_parabolic_bowl.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_parabolic_bowl.jl"), + l2 = [0.00015285369980313484, 1.9536806395943226e-5, 9.936906607758672e-5, 5.0686313334616055e-15], + linf = [0.003316119030459211, 0.0005075409427972817, 0.001986721761060583, 4.701794509287538e-14], + tspan = (0.0, 0.025), cells_per_dimension = (40, 40)) + end + @trixi_testset "elixir_mhd_ec_shockcapturing.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec_shockcapturing.jl"), l2 = [0.0364192725149364, 0.0426667193422069, 0.04261673001449095, 0.025884071405646924, diff --git a/test/test_t8code_2d.jl b/test/test_t8code_2d.jl new file mode 100644 index 00000000000..a424c9df84b --- /dev/null +++ b/test/test_t8code_2d.jl @@ -0,0 +1,182 @@ +module TestExamplesT8codeMesh2D + +using Test +using Trixi + +include("test_trixi.jl") + +EXAMPLES_DIR = joinpath(examples_dir(), "t8code_2d_dgsem") + +# Start with a clean environment: remove Trixi.jl output directory if it exists +outdir = "out" +isdir(outdir) && rm(outdir, recursive = true) +mkdir(outdir) + +@testset "T8codeMesh2D" begin + + @trixi_testset "test save_mesh_file" begin + @test_throws Exception begin + # Save mesh file support will be added in the future. The following + # lines of code are here for satisfying code coverage. + + # Create dummy mesh. + mesh = T8codeMesh((1, 1), polydeg = 1, + mapping = Trixi.coordinates2mapping((-1.0, -1.0), ( 1.0, 1.0)), + initial_refinement_level = 1) + + # This call throws an error. + Trixi.save_mesh_file(mesh, "dummy") + end + end + + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2=[8.311947673061856e-6], + linf=[6.627000273229378e-5]) + end + + @trixi_testset "elixir_advection_nonconforming_flag.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_advection_nonconforming_flag.jl"), + l2=[3.198940059144588e-5], + linf=[0.00030636069494005547]) + end + + @trixi_testset "elixir_advection_unstructured_flag.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_unstructured_flag.jl"), + l2=[0.0005379687442422346], + linf=[0.007438525029884735]) + end + + @trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_advection_amr_unstructured_flag.jl"), + l2=[0.001993165013217687], + linf=[0.032891018571625796], + coverage_override=(maxiters = 6,)) + end + + @trixi_testset "elixir_advection_amr_solution_independent.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_advection_amr_solution_independent.jl"), + # Expected errors are exactly the same as with StructuredMesh! + l2=[4.949660644033807e-5], + linf=[0.0004867846262313763], + coverage_override=(maxiters = 6,)) + end + + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), + l2=[ + 0.0034516244508588046, + 0.0023420334036925493, + 0.0024261923964557187, + 0.004731710454271893, + ], + linf=[ + 0.04155789011775046, + 0.024772109862748914, + 0.03759938693042297, + 0.08039824959535657, + ]) + end + + @trixi_testset "elixir_euler_free_stream.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + l2=[ + 2.063350241405049e-15, + 1.8571016296925367e-14, + 3.1769447886391905e-14, + 1.4104095258528071e-14, + ], + linf=[1.9539925233402755e-14, 2e-12, 4.8e-12, 4e-12], + atol=2.0e-12,) + end + + @trixi_testset "elixir_euler_shockcapturing_ec.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_ec.jl"), + l2=[ + 9.53984675e-02, + 1.05633455e-01, + 1.05636158e-01, + 3.50747237e-01, + ], + linf=[ + 2.94357464e-01, + 4.07893014e-01, + 3.97334516e-01, + 1.08142520e+00, + ], + tspan=(0.0, 1.0)) + end + + @trixi_testset "elixir_euler_sedov.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2=[ + 3.76149952e-01, + 2.46970327e-01, + 2.46970327e-01, + 1.28889042e+00, + ], + linf=[ + 1.22139001e+00, + 1.17742626e+00, + 1.17742626e+00, + 6.20638482e+00, + ], + tspan=(0.0, 0.3)) + end + + @trixi_testset "elixir_shallowwater_source_terms.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2=[ + 9.168126407325352e-5, + 0.0009795410115453788, + 0.002546408320320785, + 3.941189812642317e-6, + ], + linf=[ + 0.0009903782521019089, + 0.0059752684687262025, + 0.010941106525454103, + 1.2129488214718265e-5, + ], + tspan=(0.0, 0.1)) + end + + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2=[1.0513414461545583e-5, 1.0517900957166411e-6, + 1.0517900957304043e-6, 1.511816606372376e-6, + 1.0443997728645063e-6, 7.879639064990798e-7, + 7.879639065049896e-7, 1.0628631669056271e-6, + 4.3382328912336153e-7], + linf=[4.255466285174592e-5, 1.0029706745823264e-5, + 1.0029706747467781e-5, 1.2122265939010224e-5, + 5.4791097160444835e-6, 5.18922042269665e-6, + 5.189220422141538e-6, 9.552667261422676e-6, + 1.4237578427628152e-6]) + end + + @trixi_testset "elixir_mhd_rotor.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_rotor.jl"), + l2=[0.44211360369891683, 0.8805178316216257, 0.8262710688468049, + 0.0, + 0.9616090460973586, 0.10386643568745411, + 0.15403457366543802, 0.0, + 2.8399715649715473e-5], + linf=[10.04369305341599, 17.995640564998403, 9.576041548174265, + 0.0, + 19.429658884314534, 1.3821395681242314, 1.818559351543182, + 0.0, + 0.002261930217575465], + tspan=(0.0, 0.02)) + end +end + +# Clean up afterwards: delete Trixi.jl output directory +@test_nowarn rm(outdir, recursive = true) + +end # module diff --git a/test/test_threaded.jl b/test/test_threaded.jl index 1e750707981..9b30836d0ed 100644 --- a/test/test_threaded.jl +++ b/test/test_threaded.jl @@ -16,18 +16,53 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) # Expected errors are exactly the same as in the serial test! l2 = [7.81674284320524e-6], linf = [6.314906965243505e-5]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end + end + + @trixi_testset "elixir_advection_restart.jl with threaded time integration" begin + @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_restart.jl"), + alg = CarpenterKennedy2N54(williamson_condition = false, thread = OrdinaryDiffEq.True()), + # Expected errors are exactly the same as in the serial test! + l2 = [7.81674284320524e-6], + linf = [6.314906965243505e-5]) end @trixi_testset "elixir_advection_amr_refine_twice.jl" begin @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_amr_refine_twice.jl"), l2 = [0.00020547512522578292], linf = [0.007831753383083506]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end @trixi_testset "elixir_advection_amr_coarsen_twice.jl" begin @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_amr_coarsen_twice.jl"), l2 = [0.0014321062757891826], linf = [0.0253454486893413]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin @@ -35,12 +70,80 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) l2 = [2.259440511766445e-6, 2.318888155713922e-6, 2.3188881557894307e-6, 6.3327863238858925e-6], linf = [1.498738264560373e-5, 1.9182011928187137e-5, 1.918201192685487e-5, 6.0526717141407005e-5], rtol = 0.001) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end @trixi_testset "elixir_euler_ec.jl" begin @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl"), l2 = [0.061751715597716854, 0.05018223615408711, 0.05018989446443463, 0.225871559730513], linf = [0.29347582879608825, 0.31081249232844693, 0.3107380389947736, 1.0540358049885143]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end + end + + @trixi_testset "elixir_advection_diffusion.jl" begin + @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_diffusion.jl"), + initial_refinement_level = 2, tspan = (0.0, 0.4), polydeg = 5, + alg = RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()), + l2 = [4.0915532997994255e-6], + linf = [2.3040850347877395e-5] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end + end + + @trixi_testset "FDSBP, elixir_advection_extended.jl" begin + @test_trixi_include(joinpath(examples_dir(), "tree_2d_fdsbp", "elixir_advection_extended.jl"), + l2 = [2.898644263922225e-6], + linf = [8.491517930142578e-6], + rtol = 1.0e-7) # These results change a little bit and depend on the CI system + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end + end + + @trixi_testset "FDSBP, elixir_euler_convergence.jl" begin + @test_trixi_include(joinpath(examples_dir(), "tree_2d_fdsbp", "elixir_euler_convergence.jl"), + l2 = [1.7088389997042244e-6, 1.7437997855125774e-6, 1.7437997855350776e-6, 5.457223460127621e-6], + linf = [9.796504903736292e-6, 9.614745892783105e-6, 9.614745892783105e-6, 4.026107182575345e-5], + tspan = (0.0, 0.1)) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end end @@ -53,6 +156,15 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) rtol = 5.0e-5, # Higher tolerance to make tests pass in CI (in particular with macOS) elixir_file="elixir_advection_waving_flag.jl", restart_file="restart_000021.h5") + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end @trixi_testset "elixir_mhd_ec.jl" begin @@ -64,6 +176,15 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) 0.9757376320946505, 0.12123736788315098, 0.12837436699267113, 0.17793825293524734, 0.03460761690059514], tspan = (0.0, 0.3)) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end end @@ -76,6 +197,15 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) linf = [0.36236334472179443, 0.3690785638275256, 0.8475748723784078, 0.0, 8.881784197001252e-16, 1.7763568394002505e-15, 1.7763568394002505e-15], tspan = (0.0, 5.0)) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end end @@ -85,6 +215,15 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), l2 = [0.0034516244508588046, 0.0023420334036925493, 0.0024261923964557187, 0.004731710454271893], linf = [0.04155789011775046, 0.024772109862748914, 0.03759938693042297, 0.08039824959535657]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end @trixi_testset "elixir_eulergravity_convergence.jl" begin @@ -96,6 +235,22 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) end + @testset "T8codeMesh" begin + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin + @test_trixi_include(joinpath(examples_dir(), "t8code_2d_dgsem", "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), + l2 = [0.0034516244508588046, 0.0023420334036925493, 0.0024261923964557187, 0.004731710454271893], + linf = [0.04155789011775046, 0.024772109862748914, 0.03759938693042297, 0.08039824959535657]) + end + + @trixi_testset "elixir_eulergravity_convergence.jl" begin + @test_trixi_include(joinpath(examples_dir(), "t8code_2d_dgsem", "elixir_eulergravity_convergence.jl"), + l2 = [0.00024871265138964204, 0.0003370077102132591, 0.0003370077102131964, 0.0007231525513793697], + linf = [0.0015813032944647087, 0.0020494288423820173, 0.0020494288423824614, 0.004793821195083758], + tspan = (0.0, 0.1)) + end + end + + @testset "DGMulti" begin @trixi_testset "elixir_euler_weakform.jl (SBP, EC)" begin @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", "elixir_euler_weakform.jl"), @@ -106,6 +261,32 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) l2 = [0.006400337855843578, 0.005303799804137764, 0.005303799804119745, 0.013204169007030144], linf = [0.03798302318566282, 0.05321027922532284, 0.05321027922605448, 0.13392025411839015], ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end + end + + @trixi_testset "elixir_euler_curved.jl with threaded time integration" begin + @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", "elixir_euler_curved.jl"), + alg = RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()), + l2 = [1.720476068165337e-5, 1.592168205710526e-5, 1.592168205812963e-5, 4.894094865697305e-5], + linf = [0.00010525416930584619, 0.00010003778091061122, 0.00010003778085621029, 0.00036426282101720275] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end @trixi_testset "elixir_euler_triangulate_pkg_mesh.jl" begin @@ -113,6 +294,19 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) l2 = [2.344080455438114e-6, 1.8610038753097983e-6, 2.4095165666095305e-6, 6.373308158814308e-6], linf = [2.5099852761334418e-5, 2.2683684021362893e-5, 2.6180448559287584e-5, 5.5752932611508044e-5] ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + if (Threads.nthreads() < 2) || (VERSION < v"1.9") + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + else + @test_broken (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end + end end @trixi_testset "elixir_euler_fdsbp_periodic.jl" begin @@ -120,6 +314,15 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive=true) l2 = [1.3333320340010056e-6, 2.044834627970641e-6, 2.044834627855601e-6, 5.282189803559564e-6], linf = [2.7000151718858945e-6, 3.988595028259212e-6, 3.9885950273710336e-6, 8.848583042286862e-6] ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end end end diff --git a/test/test_tree_1d_fdsbp.jl b/test/test_tree_1d_fdsbp.jl index 118385c34b3..ce0ca660d35 100644 --- a/test/test_tree_1d_fdsbp.jl +++ b/test/test_tree_1d_fdsbp.jl @@ -23,6 +23,21 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_fdsbp") @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end + + @trixi_testset "elixir_advection_upwind_periodic.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_upwind_periodic.jl"), + l2 = [1.1672962783692568e-5], + linf = [1.650514414558435e-5]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end end @testset "Inviscid Burgers" begin diff --git a/test/test_tree_1d_shallowwater.jl b/test/test_tree_1d_shallowwater.jl index 6fedac905f8..fe938f52721 100644 --- a/test/test_tree_1d_shallowwater.jl +++ b/test/test_tree_1d_shallowwater.jl @@ -1,5 +1,7 @@ module TestExamples1DShallowWater +# TODO: TrixiShallowWater: move any wet/dry tests to new package + using Test using Trixi @@ -47,6 +49,13 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") tspan = (0.0, 0.25)) end + @trixi_testset "elixir_shallowwater_well_balanced_wet_dry.jl with FluxHydrostaticReconstruction" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced_wet_dry.jl"), + l2 = [0.00965787167169024, 5.345454081916856e-14, 0.03857583749209928], + linf = [0.4999999999998892, 2.2447689894899726e-13, 1.9999999999999714], + tspan = (0.0, 0.25)) + end + @trixi_testset "elixir_shallowwater_source_terms.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), l2 = [0.0022363707373868713, 0.01576799981934617, 4.436491725585346e-5], @@ -106,6 +115,20 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") linf = [1.1209754279344226, 1.3230788645853582, 0.8646939843534251], tspan = (0.0, 0.05)) end + + @trixi_testset "elixir_shallowwater_beach.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_beach.jl"), + l2 = [0.17979210479598923, 1.2377495706611434, 6.289818963361573e-8], + linf = [0.845938394800688, 3.3740800777086575, 4.4541473087633676e-7], + tspan = (0.0, 0.05)) + end + + @trixi_testset "elixir_shallowwater_parabolic_bowl.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_parabolic_bowl.jl"), + l2 = [8.965981683033589e-5, 1.8565707397810857e-5, 4.1043039226164336e-17], + linf = [0.00041080213807871235, 0.00014823261488938177, 2.220446049250313e-16], + tspan = (0.0, 0.05)) + end end end # module diff --git a/test/test_tree_1d_shallowwater_twolayer.jl b/test/test_tree_1d_shallowwater_twolayer.jl index f09eb0f05ca..b3b5348e6d5 100644 --- a/test/test_tree_1d_shallowwater_twolayer.jl +++ b/test/test_tree_1d_shallowwater_twolayer.jl @@ -1,5 +1,7 @@ module TestExamples1DShallowWaterTwoLayer +# TODO: TrixiShallowWater: move two layer tests to new package + using Test using Trixi diff --git a/test/test_tree_2d_fdsbp.jl b/test/test_tree_2d_fdsbp.jl index 7c58ef89a6c..e81c82f3f34 100644 --- a/test/test_tree_2d_fdsbp.jl +++ b/test/test_tree_2d_fdsbp.jl @@ -23,6 +23,24 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_fdsbp") @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end + + @trixi_testset "elixir_advection_extended.jl with periodic operators" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [1.1239649404463432e-5], + linf = [1.5895264629195438e-5], + D_SBP = SummationByPartsOperators.periodic_derivative_operator( + derivative_order = 1, accuracy_order = 4, xmin = 0.0, xmax = 1.0, N = 40), + initial_refinement_level = 0) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end end @testset "Compressible Euler" begin diff --git a/test/test_tree_2d_linearizedeuler.jl b/test/test_tree_2d_linearizedeuler.jl index 540b3951212..2c5f6dc2cd1 100644 --- a/test/test_tree_2d_linearizedeuler.jl +++ b/test/test_tree_2d_linearizedeuler.jl @@ -13,4 +13,10 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") linf = [0.0011006084408365924, 0.0005788678074691855, 0.0005788678074701847, 0.0011006084408365924] ) end + + @trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), + l2 = [0.048185623945503485, 0.01941899333212175, 0.019510224816991825, 0.048185623945503485], + linf = [1.0392165942153189, 0.18188777290819994, 0.1877028372108587, 1.0392165942153189]) + end end diff --git a/test/test_tree_2d_shallowwater.jl b/test/test_tree_2d_shallowwater.jl index d75ea720ab7..239c41f86ed 100644 --- a/test/test_tree_2d_shallowwater.jl +++ b/test/test_tree_2d_shallowwater.jl @@ -1,5 +1,7 @@ module TestExamples2DShallowWater +# TODO: TrixiShallowWater: move any wet/dry tests to new package + using Test using Trixi @@ -46,6 +48,13 @@ EXAMPLES_DIR = joinpath(examples_dir(), "tree_2d_dgsem") tspan = (0.0, 0.25)) end + @trixi_testset "elixir_shallowwater_well_balanced_wet_dry.jl with FluxHydrostaticReconstruction" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced_wet_dry.jl"), + l2 = [0.030186039395610056, 2.513287752536758e-14, 1.3631397744897607e-16, 0.10911781485920438], + linf = [0.49999999999993505, 5.5278950497971455e-14, 7.462550826772548e-16, 2.0], + tspan = (0.0, 0.25)) + end + @trixi_testset "elixir_shallowwater_source_terms.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), l2 = [0.001868474306068482, 0.01731687445878443, 0.017649083171490863, 6.274146767717023e-5], @@ -75,6 +84,21 @@ EXAMPLES_DIR = joinpath(examples_dir(), "tree_2d_dgsem") volume_flux = (flux_wintermeyer_etal, flux_nonconservative_ersing_etal), tspan = (0.0, 0.25)) end + + @trixi_testset "elixir_shallowwater_conical_island.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_conical_island.jl"), + l2 = [0.0459315416430658, 0.1644534881916991, 0.16445348819169914, 0.0011537702354532694], + linf = [0.21100717610846464, 0.9501592344310412, 0.9501592344310417, 0.021790250683516282], + tspan = (0.0, 0.025)) + end + + @trixi_testset "elixir_shallowwater_parabolic_bowl.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_parabolic_bowl.jl"), + l2 = [0.00025345501281482687, 4.4525120338817177e-5, 0.00015991819160294247, 7.750412064917294e-15], + linf = [0.004664246019836723, 0.0004972780116736669, 0.0028735707270457628, 6.866729407306593e-14], + tspan = (0.0, 0.025), + basis = LobattoLegendreBasis(3)) + end end end # module diff --git a/test/test_tree_2d_shallowwater_twolayer.jl b/test/test_tree_2d_shallowwater_twolayer.jl index 0d15a709871..7dc539061c3 100644 --- a/test/test_tree_2d_shallowwater_twolayer.jl +++ b/test/test_tree_2d_shallowwater_twolayer.jl @@ -1,5 +1,7 @@ module TestExamples2DShallowWaterTwoLayer +# TODO: TrixiShallowWater: move two layer tests to new package + using Test using Trixi @@ -32,8 +34,8 @@ EXAMPLES_DIR = joinpath(examples_dir(), "tree_2d_dgsem") @trixi_testset "elixir_shallowwater_twolayer_well_balanced with flux_lax_friedrichs.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_twolayer_well_balanced.jl"), - l2 = [2.0525741072929735e-16, 6.000589392730905e-17, 6.102759428478984e-17, - 0.0030769233188014905, 1.8421386173122792e-16, 1.8473184927121752e-16, + l2 = [2.0525741072929735e-16, 6.000589392730905e-17, 6.102759428478984e-17, + 0.0030769233188014905, 1.8421386173122792e-16, 1.8473184927121752e-16, 0.0030769233188014935], linf = [7.355227538141662e-16, 2.960836949170518e-16, 4.2726562436938764e-16, 0.02647405113891016, 1.038795478061861e-15, 1.0401789378532516e-15, diff --git a/test/test_tree_3d_fdsbp.jl b/test/test_tree_3d_fdsbp.jl index 9dceab38031..106dd007b09 100644 --- a/test/test_tree_3d_fdsbp.jl +++ b/test/test_tree_3d_fdsbp.jl @@ -7,7 +7,7 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_fdsbp") -@testset "Compressible Euler" begin +@testset "Linear scalar advection" begin @trixi_testset "elixir_advection_extended.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), l2 = [0.005355755365412444], @@ -23,6 +23,27 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_fdsbp") end end + @trixi_testset "elixir_advection_extended.jl with periodic operators" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [1.3819894522373702e-8], + linf = [3.381866298113323e-8], + D_SBP = SummationByPartsOperators.periodic_derivative_operator( + derivative_order = 1, accuracy_order = 4, xmin = 0.0, xmax = 1.0, N = 10), + initial_refinement_level = 0, + tspan = (0.0, 5.0)) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end +end + +@testset "Compressible Euler" begin @trixi_testset "elixir_euler_convergence.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), l2 = [2.247522803543667e-5, 2.2499169224681058e-5, 2.24991692246826e-5, 2.2499169224684707e-5, 5.814121361417382e-5], diff --git a/test/test_unit.jl b/test/test_unit.jl index 2156e9bac32..e70a9be6a4a 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -382,7 +382,7 @@ isdir(outdir) && rm(outdir, recursive=true) @timed_testset "HLL flux with vanishing wave speed estimates (#502)" begin equations = CompressibleEulerEquations1D(1.4) u = SVector(1.0, 0.0, 0.0) - @test !any(isnan, FluxHLL()(u, u, 1, equations)) + @test !any(isnan, flux_hll(u, u, 1, equations)) end @timed_testset "DG L2 mortar container debug output" begin @@ -402,6 +402,10 @@ isdir(outdir) && rm(outdir, recursive=true) indicator_hg = IndicatorHennemannGassner(1.0, 0.0, true, "variable", "cache") @test_nowarn show(stdout, indicator_hg) + # TODO: TrixiShallowWater: move unit test + indicator_hg_swe = IndicatorHennemannGassnerShallowWater(1.0, 0.0, true, "variable", "cache") + @test_nowarn show(stdout, indicator_hg_swe) + indicator_loehner = IndicatorLöhner(1.0, "variable", (; cache=nothing)) @test_nowarn show(stdout, indicator_loehner) @@ -586,7 +590,265 @@ isdir(outdir) && rm(outdir, recursive=true) @test_throws ArgumentError TimeSeriesCallback(semi, [1.0 1.0 1.0; 2.0 2.0 2.0]) end - @timed_testset "Consistency check for HLLE flux" begin + @timed_testset "Consistency check for HLL flux (naive): CEE" begin + flux_hll = FluxHLL(min_max_speed_naive) + + # Set up equations and dummy conservative variables state + equations = CompressibleEulerEquations1D(1.4) + u = SVector(1.1, 2.34, 5.5) + + orientations = [1] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + equations = CompressibleEulerEquations2D(1.4) + u = SVector(1.1, -0.5, 2.34, 5.5) + + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + equations = CompressibleEulerEquations3D(1.4) + u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) + + orientations = [1, 2, 3] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + end + + @timed_testset "Consistency check for HLL flux (naive): LEE" begin + flux_hll = FluxHLL(min_max_speed_naive) + + equations = LinearizedEulerEquations2D(SVector(1.0, 1.0), 1.0, 1.0) + u = SVector(1.1, -0.5, 2.34, 5.5) + + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + end + + @timed_testset "Consistency check for HLL flux (naive): SWE" begin + flux_hll = FluxHLL(min_max_speed_naive) + + equations = ShallowWaterEquations1D(gravity_constant=9.81) + u = SVector(1, 0.5, 0.0) + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + + equations = ShallowWaterEquations2D(gravity_constant=9.81) + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + u = SVector(1, 0.5, 0.5, 0.0) + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + end + + @timed_testset "Consistency check for HLL flux (naive): MHD" begin + flux_hll = FluxHLL(min_max_speed_naive) + + equations = IdealGlmMhdEquations1D(1.4) + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2),] + + for u in u_values + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + end + + equations = IdealGlmMhdEquations2D(1.4, 5.0 #= c_h =#) + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + orientations = [1, 2] + + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2),] + + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + + equations = IdealGlmMhdEquations3D(1.4, 5.0 #= c_h =#) + normal_directions = [SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4)] + orientations = [1, 2, 3] + + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2),] + + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + end + + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: CEE" begin + flux_hll = FluxHLL(min_max_speed_davis) + + # Set up equations and dummy conservative variables state + equations = CompressibleEulerEquations1D(1.4) + u = SVector(1.1, 2.34, 5.5) + + orientations = [1] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + equations = CompressibleEulerEquations2D(1.4) + u = SVector(1.1, -0.5, 2.34, 5.5) + + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + + equations = CompressibleEulerEquations3D(1.4) + u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) + + orientations = [1, 2, 3] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + normal_directions = [SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4)] + + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + end + + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: LEE" begin + flux_hll = FluxHLL(min_max_speed_davis) + + equations = LinearizedEulerEquations2D(SVector(1.0, 1.0), 1.0, 1.0) + u = SVector(1.1, -0.5, 2.34, 5.5) + + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + end + + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: SWE" begin + flux_hll = FluxHLL(min_max_speed_davis) + + equations = ShallowWaterEquations1D(gravity_constant=9.81) + u = SVector(1, 0.5, 0.0) + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + + equations = ShallowWaterEquations2D(gravity_constant=9.81) + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + u = SVector(1, 0.5, 0.5, 0.0) + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + end + + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: MHD" begin + flux_hll = FluxHLL(min_max_speed_davis) + + equations = IdealGlmMhdEquations1D(1.4) + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2),] + + for u in u_values + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + end + + equations = IdealGlmMhdEquations2D(1.4, 5.0 #= c_h =#) + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + orientations = [1, 2] + + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2),] + + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + + equations = IdealGlmMhdEquations3D(1.4, 5.0 #= c_h =#) + normal_directions = [SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4)] + orientations = [1, 2, 3] + + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2),] + + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + end + + @timed_testset "Consistency check for HLLE flux: CEE" begin # Set up equations and dummy conservative variables state equations = CompressibleEulerEquations1D(1.4) u = SVector(1.1, 2.34, 5.5) @@ -604,6 +866,15 @@ isdir(outdir) && rm(outdir, recursive=true) @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) end + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + equations = CompressibleEulerEquations3D(1.4) u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) @@ -611,6 +882,92 @@ isdir(outdir) && rm(outdir, recursive=true) for orientation in orientations @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) end + + normal_directions = [SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4)] + + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + end + + @timed_testset "Consistency check for HLLE flux: SWE" begin + # Test HLL flux with min_max_speed_einfeldt + flux_hll = FluxHLL(min_max_speed_einfeldt) + + equations = ShallowWaterEquations1D(gravity_constant=9.81) + u = SVector(1, 0.5, 0.0) + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + + equations = ShallowWaterEquations2D(gravity_constant=9.81) + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + orientations = [1, 2] + + u = SVector(1, 0.5, 0.5, 0.0) + + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + end + + @timed_testset "Consistency check for HLLE flux: MHD" begin + # Test HLL flux with min_max_speed_einfeldt + flux_hll = FluxHLL(min_max_speed_naive) + + equations = IdealGlmMhdEquations1D(1.4) + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2),] + + for u in u_values + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + end + + equations = IdealGlmMhdEquations2D(1.4, 5.0 #= c_h =#) + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + orientations = [1, 2] + + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2),] + + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end + + equations = IdealGlmMhdEquations3D(1.4, 5.0 #= c_h =#) + normal_directions = [SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4)] + orientations = [1, 2, 3] + + u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2),] + + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ flux(u, normal_direction, equations) + end end @timed_testset "Consistency check for Godunov flux" begin @@ -670,6 +1027,26 @@ isdir(outdir) && rm(outdir, recursive=true) for normal_direction in normal_directions @test flux_godunov(u, u, normal_direction, equation) ≈ flux(u, normal_direction, equation) end + + # Linearized Euler 2D + equation = LinearizedEulerEquations2D(v_mean_global=(0.5, -0.7), c_mean_global=1.1, + rho_mean_global=1.2) + u_values = [SVector(1.0, 0.5, -0.7, 1.0), + SVector(1.5, -0.2, 0.1, 5.0),] + + orientations = [1, 2] + for orientation in orientations, u in u_values + @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) + end + + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + + for normal_direction in normal_directions, u in u_values + @test flux_godunov(u, u, normal_direction, equation) ≈ flux(u, normal_direction, equation) + end end @timed_testset "Consistency check for Engquist-Osher flux" begin @@ -760,7 +1137,8 @@ isdir(outdir) && rm(outdir, recursive=true) SVector(-1.2, 0.3)] u_values = [SVector(1.0, 0.5, -0.7, 1.0), SVector(1.5, -0.2, 0.1, 5.0),] - fluxes = [flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber] + fluxes = [flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, + flux_hll, FluxHLL(min_max_speed_davis)] for f_std in fluxes f_rot = FluxRotated(f_std) @@ -779,7 +1157,8 @@ isdir(outdir) && rm(outdir, recursive=true) SVector(-1.2, 0.3, 1.4)] u_values = [SVector(1.0, 0.5, -0.7, 0.1, 1.0), SVector(1.5, -0.2, 0.1, 0.2, 5.0),] - fluxes = [flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, FluxLMARS(340)] + fluxes = [flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, FluxLMARS(340), + flux_hll, FluxHLL(min_max_speed_davis)] for f_std in fluxes f_rot = FluxRotated(f_std) @@ -789,6 +1168,20 @@ isdir(outdir) && rm(outdir, recursive=true) end end + @timed_testset "ShallowWaterEquations2D" begin + equations = ShallowWaterEquations2D(gravity_constant=9.81) + normal_directions = [SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3)] + + u = SVector(1, 0.5, 0.5, 0.0) + + fluxes = [flux_central, flux_fjordholm_etal, flux_wintermeyer_etal, + flux_hll, FluxHLL(min_max_speed_davis), FluxHLL(min_max_speed_einfeldt)] + + end + @timed_testset "IdealGlmMhdEquations2D" begin equations = IdealGlmMhdEquations2D(1.4, 5.0 #= c_h =#) normal_directions = [SVector(1.0, 0.0), @@ -797,7 +1190,7 @@ isdir(outdir) && rm(outdir, recursive=true) SVector(-1.2, 0.3)] u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2),] - fluxes = [flux_central, flux_hindenlang_gassner] + fluxes = [flux_central, flux_hindenlang_gassner, flux_hll, FluxHLL(min_max_speed_davis)] for f_std in fluxes f_rot = FluxRotated(f_std) @@ -816,7 +1209,7 @@ isdir(outdir) && rm(outdir, recursive=true) SVector(-1.2, 0.3, 1.4)] u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2),] - fluxes = [flux_central, flux_hindenlang_gassner] + fluxes = [flux_central, flux_hindenlang_gassner, flux_hll, FluxHLL(min_max_speed_davis)] for f_std in fluxes f_rot = FluxRotated(f_std) diff --git a/test/test_unstructured_2d.jl b/test/test_unstructured_2d.jl index b7b60e5c54d..52f3845a289 100644 --- a/test/test_unstructured_2d.jl +++ b/test/test_unstructured_2d.jl @@ -1,5 +1,7 @@ module TestExamplesUnstructuredMesh2D +# TODO: TrixiShallowWater: move any wet/dry and two layer tests + using Test using Trixi @@ -152,6 +154,14 @@ isdir(outdir) && rm(outdir, recursive=true) tspan = (0.0, 0.025)) end + @trixi_testset "elixir_shallowwater_source_terms.jl with flux_hll" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [0.0011197139793938727, 0.015430259691311309, 0.017081031802719554, 5.089218476759981e-6], + linf = [0.014300809338967824, 0.12783372461224918, 0.17625472321993918, 2.6407324614341476e-5], + surface_flux=(flux_hll, flux_nonconservative_fjordholm_etal), + tspan = (0.0, 0.025)) + end + @trixi_testset "elixir_shallowwater_dirichlet.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_dirichlet.jl"), l2 = [1.1577518608940115e-5, 4.867189932537344e-13, 4.647273240470541e-13, 1.1577518608933468e-5], @@ -173,6 +183,14 @@ isdir(outdir) && rm(outdir, recursive=true) tspan = (0.0, 0.25)) end + @trixi_testset "elixir_shallowwater_three_mound_dam_break.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_three_mound_dam_break.jl"), + l2 = [0.0892957892027502, 0.30648836484407915, 2.28712547616214e-15, 0.0008778654298684622], + linf = [0.850329472915091, 2.330631694956507, 5.783660020252348e-14, 0.04326237921249021], + basis = LobattoLegendreBasis(3), + tspan = (0.0, 0.25)) + end + @trixi_testset "elixir_shallowwater_twolayer_convergence.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_twolayer_convergence.jl"), l2 = [0.0007935561625451243, 0.008825315509943844, 0.002429969315645897,