diff --git a/.github/review-checklist.md b/.github/review-checklist.md index 2d8a24f1971..69410a07011 100644 --- a/.github/review-checklist.md +++ b/.github/review-checklist.md @@ -19,7 +19,7 @@ This checklist is meant to assist creators of PRs (to let them know what reviewe - [ ] Relevant publications are referenced in docstrings (see [example](https://github.com/trixi-framework/Trixi.jl/blob/7f83a1a938eecd9b841efe215a6e482e67cfdcc1/src/equations/compressible_euler_2d.jl#L601-L615) for formatting). - [ ] Inline comments are used to document longer or unusual code sections. - [ ] Comments describe intent ("why?") and not just functionality ("what?"). -- [ ] If the PR introduces a significant change or new feature, it is documented in `NEWS.md`. +- [ ] If the PR introduces a significant change or new feature, it is documented in `NEWS.md` with its PR number. #### Testing - [ ] The PR passes all tests. @@ -35,4 +35,4 @@ This checklist is meant to assist creators of PRs (to let them know what reviewe - [ ] If new equations/methods are added, a convergence test has been run and the results are posted in the PR. -*Created with :heart: by the Trixi.jl community.* \ No newline at end of file +*Created with :heart: by the Trixi.jl community.* diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 8f8d674ffa9..df6623839d7 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -34,9 +34,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: - version: '1.9' + version: '1.10' show-versioninfo: true - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index dd5d8ee7e32..234939a8017 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -64,7 +64,7 @@ jobs: - threaded steps: - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} diff --git a/.github/workflows/Invalidations.yml b/.github/workflows/Invalidations.yml index 18048d26be8..b2d34cbc856 100644 --- a/.github/workflows/Invalidations.yml +++ b/.github/workflows/Invalidations.yml @@ -16,7 +16,7 @@ jobs: if: github.base_ref == github.event.repository.default_branch runs-on: ubuntu-latest steps: - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: version: '1' - uses: actions/checkout@v4 diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 87e34cb50f3..10bcc22786a 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.18.2 + uses: crate-ci/typos@v1.21.0 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 4531c3aee0a..5eba2e70f4f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -11,7 +11,7 @@ jobs: os: - ubuntu-latest version: - - '1.9' + - '1.10' arch: - x64 steps: @@ -21,7 +21,7 @@ jobs: - run: | git fetch --tags git branch --create-reflog main origin/main - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7dfe033a90..b4b3cfa1487 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,8 +52,8 @@ jobs: fail-fast: false matrix: version: - - '1.9' - # - '~1.9.0-0' # including development versions + - '1.10' + # - '~1.10.0-0' # including development versions # - 'nightly' os: - ubuntu-latest @@ -86,28 +86,28 @@ jobs: arch: x64 trixi_test: threaded_legacy - version: '1.9' - os: macOS-latest + os: ubuntu-latest arch: x64 - trixi_test: mpi - - version: '1.9' - os: macOS-latest + trixi_test: threaded_legacy + - version: '1.10' + os: macos-13 arch: x64 + trixi_test: mpi + - version: '1.10' + os: macos-latest + arch: aarch64 trixi_test: threaded - - version: '1.9' + - version: '1.10' os: windows-latest arch: x64 trixi_test: mpi - - version: '1.9' + - version: '1.10' os: windows-latest arch: x64 trixi_test: threaded - - version: '1.9' - os: macos-14 - arch: arm64 - trixi_test: threaded steps: - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index b40d5d365c0..83ed6cc79c7 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -64,7 +64,7 @@ jobs: - TrixiShallowWater.jl steps: - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} diff --git a/NEWS.md b/NEWS.md index 022252e61a9..e2902229f71 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,8 +8,11 @@ for human readability. #### Added - Implementation of `TimeSeriesCallback` for curvilinear meshes on `UnstructuredMesh2D` and extension - to 1D and 3D on `TreeMesh`. - + to 1D and 3D on `TreeMesh` ([#1855], [#1873]). +- Implementation of 1D Linearized Euler Equations ([#1867]). +- New analysis callback for 2D `P4estMesh` to compute integrated quantities along a boundary surface, e.g., pressure lift and drag coefficients ([#1812]). +- Optional tuple parameter for `GlmSpeedCallback` called `semi_indices` to specify for which semidiscretization of a `SemidiscretizationCoupled` we need to update the GLM speed ([#1835]). +- Subcell local one-sided limiting support for nonlinear variables in 2D for `TreeMesh` ([#1792]). ## Changes when updating to v0.7 from v0.6.x @@ -187,9 +190,8 @@ for human readability. #### Added - Experimental support for artificial neural network-based indicators for shock capturing and - adaptive mesh refinement ([#632](https://github.com/trixi-framework/Trixi.jl/pull/632)) -- Experimental support for direct-hybrid aeroacoustics simulations - ([#712](https://github.com/trixi-framework/Trixi.jl/pull/712)) + adaptive mesh refinement ([#632]) +- Experimental support for direct-hybrid aeroacoustics simulations ([#712]) - Implementation of shallow water equations in 2D - Experimental support for interactive visualization with [Makie.jl](https://makie.juliaplots.org/) @@ -225,7 +227,7 @@ for human readability. - acoustic perturbation equations - Lattice-Boltzmann equations - Composable `FluxPlusDissipation` and `FluxLaxFriedrichs()`, `FluxHLL()` with adaptable - wave speed estimates were added in [#493](https://github.com/trixi-framework/Trixi.jl/pull/493) + wave speed estimates were added in [#493] - New structured, curvilinear, conforming mesh type `StructuredMesh` - New unstructured, curvilinear, conforming mesh type `UnstructuredMesh2D` in 2D - New unstructured, curvilinear, adaptive (non-conforming) mesh type `P4estMesh` in 2D and 3D @@ -238,13 +240,13 @@ for human readability. - `flux_lax_friedrichs(u_ll, u_rr, orientation, equations::LatticeBoltzmannEquations2D)` and `flux_lax_friedrichs(u_ll, u_rr, orientation, equations::LatticeBoltzmannEquations3D)` were actually using the logic of `flux_godunov`. Thus, they were renamed accordingly - in [#493](https://github.com/trixi-framework/Trixi.jl/pull/493). This is considered a bugfix + in [#493]. This is considered a bugfix (released in Trixi.jl v0.3.22). - The required Julia version is updated to v1.6. #### Deprecated -- `calcflux` → `flux` ([#463](https://github.com/trixi-framework/Trixi.jl/pull/463)) +- `calcflux` → `flux` ([#463]) - `flux_upwind` → `flux_godunov` - `flux_hindenlang` → `flux_hindenlang_gassner` - Providing the keyword argument `solution_variables` of `SaveSolutionCallback` @@ -256,6 +258,6 @@ for human readability. only a single two-point numerical flux for nonconservative is deprecated. The new interface is described in a tutorial. Now, a tuple of two numerical fluxes of the form `(conservative_flux, nonconservative_flux)` needs to be passed for - nonconservative equations, see [#657](https://github.com/trixi-framework/Trixi.jl/pull/657). + nonconservative equations, see [#657]. #### Removed diff --git a/Project.toml b/Project.toml index 6ff7f29686d..68f9060b9d3 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.7.6-pre" +version = "0.7.12-pre" [deps] CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" diff --git a/docs/.gitignore b/docs/.gitignore index c8a9e842246..cc6f90fae09 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,3 +1,4 @@ +src/changelog.md src/code_of_conduct.md src/contributing.md diff --git a/docs/Project.toml b/docs/Project.toml index 3b8d169fdb8..9f9bb956274 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,6 @@ [deps] CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Changelog = "5217a498-cd5d-4ec6-b8c2-9b85a09b6e3e" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" HOHQMesh = "e4f4c7b8-17cb-445a-93c5-f69190ed6c8c" @@ -14,6 +15,7 @@ TrixiBase = "9a0f1c46-06d5-4909-a5a3-ce25d3fa3284" [compat] CairoMakie = "0.6, 0.7, 0.8, 0.9, 0.10, 0.11" +Changelog = "1.1" Documenter = "1" ForwardDiff = "0.10" HOHQMesh = "0.1, 0.2" diff --git a/docs/literate/make.jl b/docs/literate/make.jl index 84e4fbdced6..262a236971c 100644 --- a/docs/literate/make.jl +++ b/docs/literate/make.jl @@ -75,7 +75,17 @@ function create_tutorials(files) end # Generate markdown file for introduction page - Literate.markdown(joinpath(repo_src, "index.jl"), pages_dir; name="introduction") + # Preprocessing introduction file: Generate consecutive tutorial numbers by replacing + # each occurrence of `{index}` with an integer incremented by 1, starting at 1. + function preprocess_introduction(content) + counter = 1 + while occursin("{index}", content) + content = replace(content, "{index}" => "$counter", count = 1) + counter += 1 + end + return content + end + Literate.markdown(joinpath(repo_src, "index.jl"), pages_dir; name="introduction", preprocess=preprocess_introduction) # Navigation system for makedocs pages = Any["Introduction" => "tutorials/introduction.md",] diff --git a/docs/literate/src/files/first_steps/changing_trixi.jl b/docs/literate/src/files/first_steps/changing_trixi.jl index 551377a6a71..b8f1fff6de8 100644 --- a/docs/literate/src/files/first_steps/changing_trixi.jl +++ b/docs/literate/src/files/first_steps/changing_trixi.jl @@ -4,6 +4,14 @@ # the cloned directory. +# ## Forking Trixi.jl + +# To create your own fork of Trixi.jl, log in to your GitHub account, visit the +# [Trixi.jl GitHub repository](https://github.com/trixi-framework/Trixi.jl) and click the `Fork` +# button located in the upper-right corner of the page. Then, click on `Create fork` in the opened +# window to complete the forking process. + + # ## Cloning Trixi.jl @@ -15,8 +23,10 @@ # - Download and install [GitHub Desktop](https://desktop.github.com/) and then log in to # your account. # - Open GitHub Desktop, press `Ctrl+Shift+O`. -# - In the opened window, paste `trixi-framework/Trixi.jl` and choose the path to the folder where -# you want to save Trixi.jl. Then click `Clone` and Trixi.jl will be cloned to your computer. +# - In the opened window, navigate to the `URL` tab and paste `trixi-framework/Trixi.jl` or +# `YourGitHubUserName/Trixi.jl` to clone your own fork of Trixi.jl, and choose the +# path to the folder where you want to save Trixi.jl. Then click `Clone` and Trixi.jl will be +# cloned to your computer. # Now you cloned Trixi.jl and only need to tell Julia to use the local clone as the package sources: # - Open a terminal using `Win+r` and `cmd`. Navigate to the folder with the cloned Trixi.jl using `cd`. @@ -54,6 +64,9 @@ # julia --project=. -e 'using Pkg; Pkg.develop(PackageSpec(path=".."))' # Tell Julia to use the local Trixi.jl clone # julia --project=. -e 'using Pkg; Pkg.add(["OrdinaryDiffEq", "Plots"])' # Install additional packages # ``` +# Alternatively, you can clone your own fork of Trixi.jl by replacing the link +# `git@github.com:trixi-framework/Trixi.jl.git` with `git@github.com:YourGitHubUserName/Trixi.jl.git`. + # Note that if you installed Trixi.jl this way, # you always have to start Julia with the `--project` flag set to your `run` directory, e.g., # ```shell @@ -62,9 +75,32 @@ # if already inside the `run` directory. +# ## Developing Trixi.jl + +# If you've created and cloned your own fork of Trixi.jl, you can make local changes to Trixi.jl +# and propose them as a Pull Request (PR) to be merged into `trixi-framework/Trixi.jl`. + +# Linux and MacOS utilize the `git` version control system to manage changes between your local and +# remote repositories. The most commonly used commands include `add`, `commit`, `push` and `pull`. +# You can find detailed information about these functions in the +# [Git documentation](https://git-scm.com/docs). + +# For Windows and GitHub Desktop users, refer to the +# [documentation of GitHub Desktop](https://docs.github.com/en/desktop/overview/getting-started-with-github-desktop#making-changes-in-a-branch). + +# After making local changes to Trixi.jl and pushing them to the remote repository, you can open a +# Pull Request (PR) from your branch to the main branch of `trixi-framework/Trixi.jl`. Then, follow +# the Review checklist provided in the Pull Request to streamline the review process. + + # ## Additional reading # To further delve into Trixi.jl, you may have a look at the following introductory tutorials. +# - [Behind the scenes of a simulation setup](@ref behind_the_scenes_simulation_setup) will guide +# you through a simple Trixi.jl setup ("elixir"), giving an overview of what happens in the +# background during the initialization of a simulation. It clarifies some of the more +# fundamental, technical concepts that are applicable to a variety of (also more complex) +# configurations. # - [Introduction to DG methods](@ref scalar_linear_advection_1d) will teach you how to set up a # simple way to approximate the solution of a hyperbolic partial differential equation. It will # be especially useful to learn about the diff --git a/docs/literate/src/files/first_steps/create_first_setup.jl b/docs/literate/src/files/first_steps/create_first_setup.jl index 906a6f93461..ae78a6a1546 100644 --- a/docs/literate/src/files/first_steps/create_first_setup.jl +++ b/docs/literate/src/files/first_steps/create_first_setup.jl @@ -1,4 +1,4 @@ -#src # Create first setup +#src # Create your first setup # In this part of the introductory guide, we will create a first Trixi.jl setup as an extension of # [`elixir_advection_basic.jl`](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/tree_2d_dgsem/elixir_advection_basic.jl). @@ -19,7 +19,10 @@ # The first step is to create and open a file with the .jl extension. You can do this with your # favorite text editor (if you do not have one, we recommend [VS Code](https://code.visualstudio.com/)). -# In this file you will create your setup. +# In this file, you will create your setup. The file can then be executed in Julia using, for example, `trixi_include()`. +# Alternatively, you can execute each line of the following code one by one in the +# Julia REPL. This will generate useful output for nearly every +# command and improve your comprehension of the process. # To be able to use functionalities of Trixi.jl, you always need to load Trixi.jl itself # and the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package. @@ -65,7 +68,9 @@ mesh = TreeMesh(coordinates_min, coordinates_max, # To approximate the solution of the defined model, we create a [`DGSEM`](@ref) solver. # The solution in each of the recently defined mesh elements will be approximated by a polynomial # of degree `polydeg`. For more information about discontinuous Galerkin methods, -# check out the [Introduction to DG methods](@ref scalar_linear_advection_1d) tutorial. +# check out the [Introduction to DG methods](@ref scalar_linear_advection_1d) tutorial. By default, +# in the weak formulation `DGSEM` initializes the surface flux as `flux_central` and uses the physical flux for +# the volume integral. solver = DGSEM(polydeg=3) @@ -90,8 +95,8 @@ solver = DGSEM(polydeg=3) # [section about analyzing the solution](https://trixi-framework.github.io/Trixi.jl/stable/callbacks/#Analyzing-the-numerical-solution). function initial_condition_sinpi(x, t, equations::LinearScalarAdvectionEquation2D) - scalar = sinpi(x[1]) * sinpi(x[2]) - return SVector(scalar) + u = sinpi(x[1]) * sinpi(x[2]) + return SVector(u) end initial_condition = initial_condition_sinpi @@ -103,18 +108,20 @@ initial_condition = initial_condition_sinpi # equation itself as arguments and returns the source term as a static vector `SVector`. function source_term_exp_sinpi(u, x, t, equations::LinearScalarAdvectionEquation2D) - scalar = - 2 * exp(-t) * sinpi(2*(x[1] - t)) * sinpi(2*(x[2] - t)) - return SVector(scalar) + u = - 2 * exp(-t) * sinpi(2*(x[1] - t)) * sinpi(2*(x[2] - t)) + return SVector(u) end # Now we collect all the information that is necessary to define a spatial discretization, -# which leaves us with an ODE problem in time with a span from 0.0 to 1.0. -# This approach is commonly referred to as the method of lines. semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; source_terms = source_term_exp_sinpi) + +# which leaves us with an ODE problem in time with a span from `0.0` to `1.0`. +# This approach is commonly referred to as the method of lines. + tspan = (0.0, 1.0) -ode = semidiscretize(semi, tspan); +ode = semidiscretize(semi, tspan) # At this point, our problem is defined. We will use the `solve` function defined in # [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) to get the solution. @@ -126,22 +133,27 @@ ode = semidiscretize(semi, tspan); # We will show you how to use some of the common callbacks. # To print a summary of the simulation setup at the beginning -# and to reset timers we use the [`SummaryCallback`](@ref). -# When the returned callback is executed directly, the current timer values are shown. +# and to reset timers to zero, we use the [`SummaryCallback`](@ref). summary_callback = SummaryCallback() # We also want to analyze the current state of the solution in regular intervals. -# The [`AnalysisCallback`](@ref) outputs some useful statistical information during the solving process +# The [`AnalysisCallback`](@ref) outputs some useful statistical information during the simulation # every `interval` time steps. -analysis_callback = AnalysisCallback(semi, interval = 5) +analysis_callback = AnalysisCallback(semi, interval = 20) + +# To indicate that a simulation is still running, we utilize the inexpensive [`AliveCallback`](@ref) +# to periodically print information to the screen, such as the +# current time, every `alive_interval` time steps. + +alive_callback = AliveCallback(alive_interval = 10) # It is also possible to control the time step size using the [`StepsizeCallback`](@ref) if the time # integration method isn't adaptive itself. To get more details, look at # [CFL based step size control](@ref CFL-based-step-size-control). -stepsize_callback = StepsizeCallback(cfl = 1.6) +stepsize_callback = StepsizeCallback(cfl = 0.9) # To save the current solution in regular intervals we use the [`SaveSolutionCallback`](@ref). # We would like to save the initial and final solutions as well. The data @@ -149,7 +161,7 @@ stepsize_callback = StepsizeCallback(cfl = 1.6) # a solution from saved files using Trixi2Vtk.jl and ParaView, which is described below in the # section [Visualize the solution](@ref Visualize-the-solution). -save_solution = SaveSolutionCallback(interval = 5, +save_solution = SaveSolutionCallback(interval = 20, save_initial_solution = true, save_final_solution = true) @@ -170,19 +182,19 @@ save_restart = SaveRestartCallback(interval = 100, save_final_restart = true) # Create a `CallbackSet` to collect all callbacks so that they can be passed to the `solve` # function. -callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback, save_solution, - save_restart) +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, stepsize_callback, + save_solution, save_restart); # The last step is to choose the time integration method. OrdinaryDiffEq.jl defines a wide range of -# [ODE solvers](https://docs.sciml.ai/DiffEqDocs/latest/solvers/ode_solve/), e.g. -# `CarpenterKennedy2N54(williamson_condition = false)`. We will pass the ODE -# problem, the ODE solver and the callbacks to the `solve` function. Also, to use +# [ODE solvers](https://docs.sciml.ai/DiffEqDocs/latest/solvers/ode_solve/), including the +# three-stage, third-order strong stability preserving Runge-Kutta method `SSPRK33`. We will pass +# the ODE problem, the ODE solver and the callbacks to the `solve` function. Also, to use # `StepsizeCallback`, we must explicitly specify the initial trial time step `dt`, the selected # value is not important, because it will be overwritten by the `StepsizeCallback`. And there is no -# need to save every step of the solution, we are only interested in the final result. +# need to save every step of the solution, as we are only interested the output provided by +# our callback [`SaveSolutionCallback`](@ref). -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), dt = 1.0, - save_everystep = false, callback = callbacks); +sol = solve(ode, SSPRK33(); dt = 1.0, save_everystep = false, callback = callbacks); # Finally, we print the timer summary. @@ -202,17 +214,33 @@ summary_callback() # ### Using Plots.jl # The first option is to use the [Plots.jl](https://github.com/JuliaPlots/Plots.jl) package -# directly after calculations, when the solution is saved in the `sol` variable. We load the -# package and use the `plot` function. +# directly after calculations, when the solution is saved in the `sol` variable. using Plots -plot(sol) -# To show the mesh on the plot, we need to extract the visualization data from the solution as -# a [`PlotData2D`](@ref) object. Mesh extraction is possible using the [`getmesh`](@ref) function. +# As was shown in the [Getting started](@ref getting_started) section, you can plot all +# variables from the system of equations by executing the following. +# ```julia +# plot(sol) +# ``` +# Alternatively, you can configure the plot more precisely. Trixi.jl provides a special data type, +# [`PlotData2D`](@ref), to extract the visualization data from the solution. + +pd = PlotData2D(sol); + +# You can plot specific variables from the system of equations by referring to their names. +# To obtain the names of all variables, execute the following. + +@show pd.variable_names; + +# Plot the variable named "scalar" (which is the name of the variable for the +# linear advection equation in Trixi.jl). + +plot(pd["scalar"]) + +# Mesh extraction is possible using the [`getmesh`](@ref) function. # Plots.jl has the `plot!` function that allows you to modify an already built graph. -pd = PlotData2D(sol) plot!(getmesh(pd)) @@ -222,38 +250,38 @@ plot!(getmesh(pd)) # `solve` function with [`SaveSolutionCallback`](@ref) there is a file with the final solution. # It is located in the `out` folder and is named as follows: `solution_index.h5`. The `index` # is the final time step of the solution that is padded to 6 digits with zeros from the beginning. -# With [Trixi2Vtk](@ref) you can convert the HDF5 output file generated by Trixi.jl into a VTK file. -# This can be used in visualization tools such as [ParaView](https://www.paraview.org) or -# [VisIt](https://visit.llnl.gov) to plot the solution. The important thing is that currently -# Trixi2Vtk.jl supports conversion only for solutions in 2D and 3D spatial domains. +# With [Trixi2Vtk](@ref) you can convert the HDF5 output file generated by Trixi.jl into a VTK/VTU +# files. VTK/VTU are specialized formats designed to store structured data required for +# visualization purposes. This can be used in visualization tools such as +# [ParaView](https://www.paraview.org) or [VisIt](https://visit.llnl.gov) to plot the solution. # If you haven't added Trixi2Vtk.jl to your project yet, you can add it as follows. # ```julia # import Pkg # Pkg.add(["Trixi2Vtk"]) # ``` -# Now we load the Trixi2Vtk.jl package and convert the file `out/solution_000018.h5` with +# Now we load the Trixi2Vtk.jl package and convert the file `out/solution_000032.h5` with # the final solution using the [`trixi2vtk`](@ref) function saving the resulting file in the # `out` folder. using Trixi2Vtk -trixi2vtk(joinpath("out", "solution_000018.h5"), output_directory="out") +trixi2vtk(joinpath("out", "solution_000032.h5"), output_directory="out") -# Now two files `solution_000018.vtu` and `solution_000018_celldata.vtu` have been generated in the +# Now two files `solution_000032.vtu` and `solution_000032_celldata.vtu` have been generated in the # `out` folder. The first one contains all the information for visualizing the solution, the # second one contains all the cell-based or discretization-based information. # Now let's visualize the solution from the generated files in ParaView. Follow this short # instruction to get the visualization. # - Download, install and open [ParaView](https://www.paraview.org/download/). -# - Press `Ctrl+O` and select the generated files `solution_000018.vtu` and -# `solution_000018_celldata.vtu` from the `out` folder. +# - Press `Ctrl+O` and select the generated files `solution_000032.vtu` and +# `solution_000032_celldata.vtu` from the `out` folder. # - In the upper-left corner in the Pipeline Browser window, left-click on the eye-icon near -# `solution_000018.vtu`. +# `solution_000032.vtu`. # - In the lower-left corner in the Properties window, change the Coloring from Solid Color to # scalar. This already generates the visualization of the final solution. # - Now let's add the mesh to the visualization. In the upper-left corner in the -# Pipeline Browser window, left-click on the eye-icon near `solution_000018_celldata.vtu`. +# Pipeline Browser window, left-click on the eye-icon near `solution_000032_celldata.vtu`. # - In the lower-left corner in the Properties window, change the Representation from Surface # to Wireframe. Then a white grid should appear on the visualization. # Now, if you followed the instructions exactly, you should get a similar image as shown in the diff --git a/docs/literate/src/files/first_steps/getting_started.jl b/docs/literate/src/files/first_steps/getting_started.jl index 2bfaf33b5fc..80ea78b51f4 100644 --- a/docs/literate/src/files/first_steps/getting_started.jl +++ b/docs/literate/src/files/first_steps/getting_started.jl @@ -19,10 +19,10 @@ # Trixi.jl is compatible with the latest stable release of Julia. Additional details regarding Julia # support can be found in the [`README.md`](https://github.com/trixi-framework/Trixi.jl#installation) -# file. The current default Julia installation is managed through `juliaup`. You may follow our -# concise installation guidelines for Windows, Linux, and MacOS provided below. In the event of any -# issues during the installation process, please consult the official -# [Julia installation instruction](https://julialang.org/downloads/). +# file. After installation, the current default Julia version can be managed through the command +# line tool `juliaup`. You may follow our concise installation guidelines for Windows, Linux, and +# MacOS provided below. In the event of any issues during the installation process, please consult +# the official [Julia installation instruction](https://julialang.org/downloads/). # ### Windows @@ -32,6 +32,7 @@ # ```shell # winget install julia -s msstore # ``` +# Note: For this installation an MS Store account is necessary to proceed. # - Verify the successful installation of Julia by executing the following command in the terminal: # ```shell # julia @@ -69,14 +70,17 @@ # [Plots.jl](https://github.com/JuliaPlots/Plots.jl). # - Open a terminal and start Julia. -# - Execute following commands: +# - Execute the following commands to install all mentioned packages. Please note that the +# installation process involves downloading and precompiling the source code, which may take +# some time depending on your machine. # ```julia # import Pkg # Pkg.add(["OrdinaryDiffEq", "Plots", "Trixi"]) # ``` +# - On Windows, the firewall may request permission to install packages. -# Now you have installed all these -# packages. [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) provides time +# Besides Trixi.jl you have now installed two additional +# packages: [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) provides time # integration schemes used by Trixi.jl and [Plots.jl](https://github.com/JuliaPlots/Plots.jl) # can be used to directly visualize Trixi.jl results from the Julia REPL. @@ -102,7 +106,39 @@ # Let's execute a short two-dimensional problem setup. It approximates the solution of # the compressible Euler equations in 2D for an ideal gas ([`CompressibleEulerEquations2D`](@ref)) -# with a weak blast wave as the initial condition. +# with a weak blast wave as the initial condition and periodic boundary conditions. + +# The compressible Euler equations in two spatial dimensions are given by +# ```math +# \frac{\partial}{\partial t} +# \begin{pmatrix} +# \rho \\ \rho v_1 \\ \rho v_2 \\ \rho e +# \end{pmatrix} +# + +# \frac{\partial}{\partial x} +# \begin{pmatrix} +# \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ (\rho e + p) v_1 +# \end{pmatrix} +# + +# \frac{\partial}{\partial y} +# \begin{pmatrix} +# \rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ (\rho e + p) v_2 +# \end{pmatrix} +# = +# \begin{pmatrix} +# 0 \\ 0 \\ 0 \\ 0 +# \end{pmatrix}, +# ``` +# for an ideal gas with the specific heat ratio ``\gamma``. +# Here, ``\rho`` is the density, ``v_1`` and ``v_2`` are the velocities, ``e`` is the specific +# total energy, and +# ```math +# p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2 + v_2^2) \right) +# ``` +# is the pressure. + +# The [`initial_condition_weak_blast_wave`](@ref) is specified in +# [`compressible_euler_2d.jl`](https://github.com/trixi-framework/Trixi.jl/blob/main/src/equations/compressible_euler_2d.jl) # Start Julia in a terminal and execute the following code: @@ -113,13 +149,26 @@ using Trixi, OrdinaryDiffEq #hide #md trixi_include(@__MODULE__,joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl")) #hide #md +# The output contains a recap of the setup and various information about the course of the simulation. +# For instance, the solution was approximated over the [`TreeMesh`](@ref) with 1024 effective cells using +# the `CarpenterKennedy2N54` ODE +# solver. Further details about the ODE solver can be found in the +# [documentation of OrdinaryDiffEq.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Low-Storage-Methods) + # To analyze the result of the computation, we can use the Plots.jl package and the function # `plot(...)`, which creates a graphical representation of the solution. `sol` is a variable -# defined in the executed example and it contains the solution at the final moment of the simulation. +# defined in the executed example and it contains the solution after the simulation +# finishes. `sol.u` holds the vector of values at each saved timestep, while `sol.t` holds the +# corresponding times for each saved timestep. In this instance, only two timesteps were saved: the +# initial and final ones. The plot depicts the distribution of the weak blast wave at the final moment +# of time, showing the density, velocities, and pressure of the ideal gas across a 2D domain. using Plots plot(sol) + +# ### Getting an existing setup file + # To obtain a list of all Trixi.jl elixirs execute # [`get_examples`](@ref). It returns the paths to all example setups. @@ -127,9 +176,6 @@ get_examples() # Editing an existing elixir is the best way to start your first own investigation using Trixi.jl. - -# ### Getting an existing setup file - # To edit an existing elixir, you first have to find a suitable one and then copy it to a local # folder. Let's have a look at how to download the `elixir_euler_ec.jl` elixir used in the previous # section from the [Trixi.jl GitHub repository](https://github.com/trixi-framework/Trixi.jl). @@ -146,39 +192,9 @@ get_examples() # ### Modifying an existing setup # As an example, we will change the initial condition for calculations that occur in -# `elixir_euler_ec.jl`. In this example we consider the compressible Euler equations in two spatial -# dimensions, -# ```math -# \frac{\partial}{\partial t} -# \begin{pmatrix} -# \rho \\ \rho v_1 \\ \rho v_2 \\ \rho e -# \end{pmatrix} -# + -# \frac{\partial}{\partial x} -# \begin{pmatrix} -# \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ (\rho e + p) v_1 -# \end{pmatrix} -# + -# \frac{\partial}{\partial y} -# \begin{pmatrix} -# \rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ (\rho e + p) v_2 -# \end{pmatrix} -# = -# \begin{pmatrix} -# 0 \\ 0 \\ 0 \\ 0 -# \end{pmatrix}, -# ``` -# for an ideal gas with the specific heat ratio ``\gamma``. -# Here, ``\rho`` is the density, ``v_1`` and ``v_2`` are the velocities, ``e`` is the specific -# total energy, and -# ```math -# p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2 + v_2^2) \right) -# ``` -# is the pressure. -# Initial conditions consist of initial values for ``\rho``, ``\rho v_1``, -# ``\rho v_2`` and ``\rho e``. -# One of the common initial conditions for the compressible Euler equations is a simple density -# wave. Let's implement it. +# `elixir_euler_ec.jl`. Initial conditions for [`CompressibleEulerEquations2D`](@ref) consist of +# initial values for ``\rho``, ``\rho v_1``, ``\rho v_2`` and ``\rho e``. One of the common initial +# conditions for the compressible Euler equations is a simple density wave. Let's implement it. # - Open the downloaded file `elixir_euler_ec.jl` with a text editor. # - Go to the line with the following code: @@ -202,6 +218,7 @@ function initial_condition_density_waves(x, t, equations::CompressibleEulerEquat return SVector(rho, rho*v1, rho*v2, rho_e) end initial_condition = initial_condition_density_waves +nothing; #hide #md # - Execute the following code one more time, but instead of `path/to/file` paste the path to the # `elixir_euler_ec.jl` file that you just edited. @@ -237,6 +254,6 @@ plot(p1, p2, p3, p4) #hide #md # Now you are able to download, modify and execute simulation setups for Trixi.jl. To explore # further details on setting up a new simulation with Trixi.jl, refer to the second part of -# the introduction titled [Create first setup](@ref create_first_setup). +# the introduction titled [Create your first setup](@ref create_first_setup). Sys.rm("out"; recursive=true, force=true) #hide #md \ No newline at end of file diff --git a/docs/literate/src/files/hohqmesh_tutorial.jl b/docs/literate/src/files/hohqmesh_tutorial.jl index b19d363c4bf..dd81f47951e 100644 --- a/docs/literate/src/files/hohqmesh_tutorial.jl +++ b/docs/literate/src/files/hohqmesh_tutorial.jl @@ -35,6 +35,7 @@ # There is a default example for this mesh type that can be executed by using Trixi +rm("out", force = true, recursive = true) #hide #md redirect_stdio(stdout=devnull, stderr=devnull) do # code that prints annoying stuff we don't want to see here #hide #md trixi_include(default_example_unstructured()) end #hide #md diff --git a/docs/literate/src/files/index.jl b/docs/literate/src/files/index.jl index 6d5800c3b66..fd1d884d47d 100644 --- a/docs/literate/src/files/index.jl +++ b/docs/literate/src/files/index.jl @@ -14,14 +14,16 @@ # There are tutorials for the following topics: -# ### [1 First steps in Trixi.jl](@ref getting_started) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} First steps in Trixi.jl](@ref getting_started) #- # This tutorial provides guidance for getting started with Trixi.jl, and Julia as well. It outlines # the installation procedures for both Julia and Trixi.jl, the execution of Trixi.jl elixirs, the # fundamental structure of a Trixi.jl setup, the visualization of results, and the development # process for Trixi.jl. -# ### [2 Behind the scenes of a simulation setup](@ref behind_the_scenes_simulation_setup) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Behind the scenes of a simulation setup](@ref behind_the_scenes_simulation_setup) #- # This tutorial will guide you through a simple Trixi.jl setup ("elixir"), giving an overview of # what happens in the background during the initialization of a simulation. While the setup @@ -30,20 +32,23 @@ # the more fundamental, *technical* concepts that are applicable to a variety of # (also more complex) configurations.s -# ### [3 Introduction to DG methods](@ref scalar_linear_advection_1d) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Introduction to DG methods](@ref scalar_linear_advection_1d) #- # This tutorial gives an introduction to discontinuous Galerkin (DG) methods with the example of the # scalar linear advection equation in 1D. Starting with some theoretical explanations, we first implement # a raw version of a discontinuous Galerkin spectral element method (DGSEM). Then, we will show how # to use features of Trixi.jl to achieve the same result. -# ### [4 DGSEM with flux differencing](@ref DGSEM_FluxDiff) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} DGSEM with flux differencing](@ref DGSEM_FluxDiff) #- # To improve stability often the flux differencing formulation of the DGSEM (split form) is used. # We want to present the idea and formulation on a basic 1D level. Then, we show how this formulation # can be implemented in Trixi.jl and analyse entropy conservation for two different flux combinations. -# ### [5 Shock capturing with flux differencing and stage limiter](@ref shock_capturing) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Shock capturing with flux differencing and stage limiter](@ref shock_capturing) #- # Using the flux differencing formulation, a simple procedure to capture shocks is a hybrid blending # of a high-order DG method and a low-order subcell finite volume (FV) method. We present the idea on a @@ -51,20 +56,35 @@ # explained and added to an exemplary simulation of the Sedov blast wave with the 2D compressible Euler # equations. -# ### [6 Non-periodic boundary conditions](@ref non_periodic_boundaries) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Subcell limiting with the IDP Limiter](@ref subcell_shock_capturing) +#- +# Trixi.jl features a subcell-wise limiting strategy utilizing an Invariant Domain-Preserving (IDP) +# approach. This IDP approach computes a blending factor that balances the high-order +# discontinuous Galerkin (DG) method with a low-order subcell finite volume (FV) method for each +# node within an element. This localized approach minimizes the application of dissipation, +# resulting in less limiting compared to the element-wise strategy. Additionally, the framework +# supports both local bounds, which are primarily used for shock capturing, and global bounds. +# The application of global bounds ensures the minimal necessary limiting to meet physical +# admissibility conditions, such as ensuring the non-negativity of variables. + +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Non-periodic boundary conditions](@ref non_periodic_boundaries) #- # Thus far, all examples used periodic boundaries. In Trixi.jl, you can also set up a simulation with # non-periodic boundaries. This tutorial presents the implementation of the classical Dirichlet # boundary condition with a following example. Then, other non-periodic boundaries are mentioned. -# ### [7 DG schemes via `DGMulti` solver](@ref DGMulti_1) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} DG schemes via `DGMulti` solver](@ref DGMulti_1) #- # This tutorial is about the more general DG solver [`DGMulti`](@ref), introduced [here](@ref DGMulti). # We are showing some examples for this solver, for instance with discretization nodes by Gauss or # triangular elements. Moreover, we present a simple way to include pre-defined triangulate meshes for # non-Cartesian domains using the package [StartUpDG.jl](https://github.com/jlchan/StartUpDG.jl). -# ### [8 Other SBP schemes (FD, CGSEM) via `DGMulti` solver](@ref DGMulti_2) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Other SBP schemes (FD, CGSEM) via `DGMulti` solver](@ref DGMulti_2) #- # Supplementary to the previous tutorial about DG schemes via the `DGMulti` solver we now present # the possibility for `DGMulti` to use other SBP schemes via the package @@ -72,7 +92,8 @@ # For instance, we show how to set up a finite differences (FD) scheme and a continuous Galerkin # (CGSEM) method. -# ### [9 Upwind FD SBP schemes](@ref upwind_fdsbp) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Upwind FD SBP schemes](@ref upwind_fdsbp) #- # General SBP schemes can not only be used via the [`DGMulti`](@ref) solver but # also with a general `DG` solver. In particular, upwind finite difference SBP @@ -80,42 +101,49 @@ # schemes in the `DGMulti` framework, the interface is based on the package # [SummationByPartsOperators.jl](https://github.com/ranocha/SummationByPartsOperators.jl). -# ### [10 Adding a new scalar conservation law](@ref adding_new_scalar_equations) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Adding a new scalar conservation law](@ref adding_new_scalar_equations) #- # This tutorial explains how to add a new physics model using the example of the cubic conservation # law. First, we define the equation using a `struct` `CubicEquation` and the physical flux. Then, # the corresponding standard setup in Trixi.jl (`mesh`, `solver`, `semi` and `ode`) is implemented # and the ODE problem is solved by OrdinaryDiffEq's `solve` method. -# ### [11 Adding a non-conservative equation](@ref adding_nonconservative_equation) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Adding a non-conservative equation](@ref adding_nonconservative_equation) #- # In this part, another physics model is implemented, the nonconservative linear advection equation. # We run two different simulations with different levels of refinement and compare the resulting errors. -# ### [12 Parabolic terms](@ref parabolic_terms) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Parabolic terms](@ref parabolic_terms) #- # This tutorial describes how parabolic terms are implemented in Trixi.jl, e.g., # to solve the advection-diffusion equation. -# ### [13 Adding new parabolic terms](@ref adding_new_parabolic_terms) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Adding new parabolic terms](@ref adding_new_parabolic_terms) #- # This tutorial describes how new parabolic terms can be implemented using Trixi.jl. -# ### [14 Adaptive mesh refinement](@ref adaptive_mesh_refinement) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Adaptive mesh refinement](@ref adaptive_mesh_refinement) #- # Adaptive mesh refinement (AMR) helps to increase the accuracy in sensitive or turbolent regions while # not wasting resources for less interesting parts of the domain. This leads to much more efficient # simulations. This tutorial presents the implementation strategy of AMR in Trixi.jl, including the use of # different indicators and controllers. -# ### [15 Structured mesh with curvilinear mapping](@ref structured_mesh_mapping) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Structured mesh with curvilinear mapping](@ref structured_mesh_mapping) #- # In this tutorial, the use of Trixi.jl's structured curved mesh type [`StructuredMesh`](@ref) is explained. # We present the two basic option to initialize such a mesh. First, the curved domain boundaries # of a circular cylinder are set by explicit boundary functions. Then, a fully curved mesh is # defined by passing the transformation mapping. -# ### [16 Unstructured meshes with HOHQMesh.jl](@ref hohqmesh_tutorial) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Unstructured meshes with HOHQMesh.jl](@ref hohqmesh_tutorial) #- # The purpose of this tutorial is to demonstrate how to use the [`UnstructuredMesh2D`](@ref) # functionality of Trixi.jl. This begins by running and visualizing an available unstructured @@ -124,26 +152,30 @@ # software in the Trixi.jl ecosystem, and then run a simulation using Trixi.jl on said mesh. # In the end, the tutorial briefly explains how to simulate an example using AMR via `P4estMesh`. -# ### [17 P4est mesh from gmsh](@ref p4est_from_gmsh) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} P4est mesh from gmsh](@ref p4est_from_gmsh) #- # This tutorial describes how to obtain a [`P4estMesh`](@ref) from an existing mesh generated # by [`gmsh`](https://gmsh.info/) or any other meshing software that can export to the Abaqus # input `.inp` format. The tutorial demonstrates how edges/faces can be associated with boundary conditions based on the physical nodesets. -# ### [18 Explicit time stepping](@ref time_stepping) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Explicit time stepping](@ref time_stepping) #- # This tutorial is about time integration using [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl). # It explains how to use their algorithms and presents two types of time step choices - with error-based # and CFL-based adaptive step size control. -# ### [19 Differentiable programming](@ref differentiable_programming) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Differentiable programming](@ref differentiable_programming) #- # This part deals with some basic differentiable programming topics. For example, a Jacobian, its # eigenvalues and a curve of total energy (through the simulation) are calculated and plotted for # a few semidiscretizations. Moreover, we calculate an example for propagating errors with Measurement.jl # at the end. -# ### [20 Custom semidiscretization](@ref custom_semidiscretization) +#src Note to developers: Use "{ index }" (but without spaces, see next line) to enable automatic indexing +# ### [{index} Custom semidiscretization](@ref custom_semidiscretization) #- # This tutorial describes the [semidiscretiations](@ref overview-semidiscretizations) of Trixi.jl # and explains how to extend them for custom tasks. diff --git a/docs/literate/src/files/non_periodic_boundaries.jl b/docs/literate/src/files/non_periodic_boundaries.jl index 3b238ad4533..8f0e320dfdc 100644 --- a/docs/literate/src/files/non_periodic_boundaries.jl +++ b/docs/literate/src/files/non_periodic_boundaries.jl @@ -99,7 +99,7 @@ sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), save_everystep=false, saveat=visnodes, callback=callbacks); using Plots -@gif for step in 1:length(sol.u) +@gif for step in eachindex(sol.u) plot(sol.u[step], semi, ylim=(-1.5, 1.5), legend=true, label="approximation", title="time t=$(round(sol.t[step], digits=5))") scatter!([0.0], [sum(boundary_condition(SVector(0.0), sol.t[step], equations))], label="boundary condition") end diff --git a/docs/literate/src/files/scalar_linear_advection_1d.jl b/docs/literate/src/files/scalar_linear_advection_1d.jl index 9b48f29d341..3e2c7e6d0dc 100644 --- a/docs/literate/src/files/scalar_linear_advection_1d.jl +++ b/docs/literate/src/files/scalar_linear_advection_1d.jl @@ -120,7 +120,7 @@ integral = sum(nodes.^3 .* weights) x = Matrix{Float64}(undef, length(nodes), n_elements) for element in 1:n_elements x_l = coordinates_min + (element - 1) * dx + dx/2 - for i in 1:length(nodes) + for i in eachindex(nodes) ξ = nodes[i] # nodes in [-1, 1] x[i, element] = x_l + dx/2 * ξ end @@ -184,7 +184,7 @@ M = diagm(weights) # ``` # With an exact integration the mass matrix would be dense. Choosing numerical integrating and quadrature # with the exact same nodes (collocation) leads to the sparse and diagonal mass matrix $M$. This -# is called mass lumping and has the big advantage of an easy invertation of the matrix. +# is called mass lumping and has the big advantage of an easy inversion of the matrix. # #### Term II: # We use spatial partial integration for the second term: @@ -417,7 +417,7 @@ dx = (coordinates_max - coordinates_min) / n_elements # length of one element x = Matrix{Float64}(undef, length(nodes), n_elements) for element in 1:n_elements x_l = -1 + (element - 1) * dx + dx/2 - for i in 1:length(nodes) # basis points in [-1, 1] + for i in eachindex(nodes) # basis points in [-1, 1] ξ = nodes[i] x[i, element] = x_l + dx/2 * ξ end diff --git a/docs/literate/src/files/subcell_shock_capturing.jl b/docs/literate/src/files/subcell_shock_capturing.jl new file mode 100644 index 00000000000..8a98fdae283 --- /dev/null +++ b/docs/literate/src/files/subcell_shock_capturing.jl @@ -0,0 +1,287 @@ +#src # Subcell limiting with the IDP Limiter + +# In the previous tutorial, the element-wise limiting with [`IndicatorHennemannGassner`](@ref) +# and [`VolumeIntegralShockCapturingHG`](@ref) was explained. This tutorial contains a short +# introduction to the idea and implementation of subcell shock capturing approaches in Trixi.jl, +# which is also based on the DGSEM scheme in flux differencing formulation. +# Trixi.jl contains the a-posteriori invariant domain-preserving (IDP) limiter which was +# introduced by [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) and +# [Rueda-Ramírez, Pazner, Gassner (2022)](https://doi.org/10.1016/j.compfluid.2022.105627). +# It is a flux-corrected transport-type (FCT) limiter and is implemented using [`SubcellLimiterIDP`](@ref) +# and [`VolumeIntegralSubcellLimiting`](@ref). +# Since it is an a-posteriori limiter you have to apply a correction stage after each Runge-Kutta +# stage. This is done by passing the stage callback [`SubcellLimiterIDPCorrection`](@ref) to the +# time integration method. + +# ## Time integration method +# As mentioned before, the IDP limiting is an a-posteriori limiter. Its limiting process +# guarantees the target bounds for an explicit (forward) Euler time step. To still achieve a +# high-order approximation, the implementation uses strong-stability preserving (SSP) Runge-Kutta +# methods, which can be written as convex combinations of forward Euler steps. +# As such, they preserve the convexity of convex functions and functionals, such as the TVD +# semi-norm and the maximum principle in 1D, for instance. +#- +# Since IDP/FCT limiting procedure operates on independent forward Euler steps, its +# a-posteriori correction stage is implemented as a stage callback that is triggered after each +# forward Euler step in an SSP Runge-Kutta method. Unfortunately, the `solve(...)` routines in +# [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl), typically employed for time +# integration in Trixi.jl, do not support this type of stage callback. +#- +# Therefore, subcell limiting with the IDP limiter requires the use of a Trixi-intern +# time integration SSPRK method called with +# ````julia +# Trixi.solve(ode, method(stage_callbacks = stage_callbacks); ...) +# ```` +#- +# Right now, only the canonical three-stage, third-order SSPRK method (Shu-Osher) +# [`Trixi.SimpleSSPRK33`](@ref) is implemented. + +# # [IDP Limiting](@id IDPLimiter) +# The implementation of the invariant domain preserving (IDP) limiting approach ([`SubcellLimiterIDP`](@ref)) +# is based on [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) and +# [Rueda-Ramírez, Pazner, Gassner (2022)](https://doi.org/10.101/j.compfluid.2022.105627). +# It supports several types of limiting which are enabled by passing parameters individually. + +# ### [Global bounds](@id global_bounds) +# If enabled, the global bounds enforce physical admissibility conditions, such as non-negativity +# of variables. This can be done for conservative variables, where the limiter is of a one-sided +# Zalesak-type ([Zalesak, 1979](https://doi.org/10.1016/0021-9991(79)90051-2)), and general +# non-linear variables, where a Newton-bisection algorithm is used to enforce the bounds. + +# The Newton-bisection algorithm is an iterative method and requires some parameters. +# It uses a fixed maximum number of iteration steps (`max_iterations_newton = 10`) and +# relative/absolute tolerances (`newton_tolerances = (1.0e-12, 1.0e-14)`). The given values are +# sufficient in most cases and therefore used as default. Additionally, there is the parameter +# `gamma_constant_newton`, which can be used to scale the antidiffusive flux for the computation +# of the blending coefficients of nonlinear variables. The default value is `2 * ndims(equations)`, +# as it was shown by [Pazner (2020)](https://doi.org/10.1016/j.cma.2021.113876) [Section 4.2.2.] +# that this value guarantees the fulfillment of bounds for a forward-Euler increment. + +# Very small non-negative values can be an issue as well. That's why we use an additional +# correction factor in the calculation of the global bounds, +# ```math +# u^{new} \geq \beta * u^{FV}. +# ``` +# By default, $\beta$ (named `positivity_correction_factor`) is set to `0.1` which works properly +# in most of the tested setups. + +# #### Conservative variables +# The procedure to enforce global bounds for a conservative variables is as follows: +# If you want to guarantee non-negativity for the density of the compressible Euler equations, +# you pass the specific quantity name of the conservative variable. +using Trixi +equations = CompressibleEulerEquations2D(1.4) + +# The quantity name of the density is `rho` which is how we enable its limiting. +positivity_variables_cons = ["rho"] + +# The quantity names are passed as a vector to allow several quantities. +# This is used, for instance, if you want to limit the density of two different components using +# the multicomponent compressible Euler equations. +equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.648), + gas_constants = (0.287, 1.578)) + +# Then, we just pass both quantity names. +positivity_variables_cons = ["rho1", "rho2"] + +# Alternatively, it is possible to all limit all density variables with a general command using +positivity_variables_cons = ["rho" * string(i) for i in eachcomponent(equations)] + +# #### Non-linear variables +# To allow limitation for all possible non-linear variables, including variables defined +# on-the-fly, you can directly pass the function that computes the quantity for which you want +# to enforce positivity. For instance, if you want to enforce non-negativity for the pressure, +# do as follows. +positivity_variables_nonlinear = [pressure] + +# ### Local bounds +# Second, Trixi.jl supports the limiting with local bounds for conservative variables using a +# two-sided Zalesak-type limiter ([Zalesak, 1979](https://doi.org/10.1016/0021-9991(79)90051-2)). +# They allow to avoid spurious oscillations within the global bounds and to improve the +# shock-capturing capabilities of the method. The corresponding numerical admissibility conditions +# are frequently formulated as local maximum or minimum principles. The local bounds are computed +# using the maximum and minimum values of all local neighboring nodes. Within this calculation we +# use the low-order FV solution values for each node. + +# As for the limiting with global bounds you are passing the quantity names of the conservative +# variables you want to limit. So, to limit the density with lower and upper local bounds pass +# the following. +local_twosided_variables_cons = ["rho"] + +# ## Exemplary simulation +# How to set up a simulation using the IDP limiting becomes clearer when looking at an exemplary +# setup. This will be a simplified version of `tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell.jl`. +# Since the setup is mostly very similar to a pure DGSEM setup as in +# `tree_2d_dgsem/elixir_euler_blast_wave.jl`, the equivalent parts are used without any explanation +# here. +using OrdinaryDiffEq +using Trixi + +equations = CompressibleEulerEquations2D(1.4) + +function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) + ## Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave" + ## 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) + phi = atan(y_norm, x_norm) + sin_phi, cos_phi = sincos(phi) + + ## Calculate primitive variables + rho = r > 0.5 ? 1.0 : 1.1691 + v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi + v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi + p = r > 0.5 ? 1.0E-3 : 1.245 + + return prim2cons(SVector(rho, v1, v2, p), equations) +end +initial_condition = initial_condition_blast_wave; + +# Since the surface integral is equal for both the DG and the subcell FV method, the limiting is +# applied only in the volume integral. +#- +# Note, that the DG method is based on the flux differencing formulation. Hence, you have to use a +# two-point flux, such as [`flux_ranocha`](@ref), [`flux_shima_etal`](@ref), [`flux_chandrashekar`](@ref) +# or [`flux_kennedy_gruber`](@ref), for the DG volume flux. +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha + +# The limiter is implemented within [`SubcellLimiterIDP`](@ref). It always requires the +# parameters `equations` and `basis`. With additional parameters (described [above](@ref IDPLimiter) +# or listed in the docstring) you can specify and enable additional limiting options. +# Here, the simulation should contain local limiting for the density using lower and upper bounds. +basis = LobattoLegendreBasis(3) +limiter_idp = SubcellLimiterIDP(equations, basis; + local_twosided_variables_cons = ["rho"]) + +# The initialized limiter is passed to `VolumeIntegralSubcellLimiting` in addition to the volume +# fluxes of the low-order and high-order scheme. +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) + +# Then, the volume integral is passed to `solver` as it is done for the standard flux-differencing +# DG scheme or the element-wise limiting. +solver = DGSEM(basis, surface_flux, volume_integral) +#- +coordinates_min = (-2.0, -2.0) +coordinates_max = (2.0, 2.0) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +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) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.3) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback); + +# As explained above, the IDP limiter works a-posteriori and requires the additional use of a +# correction stage implemented with the stage callback [`SubcellLimiterIDPCorrection`](@ref). +# This callback is passed within a tuple to the time integration method. +stage_callbacks = (SubcellLimiterIDPCorrection(),) + +# Moreover, as mentioned before as well, simulations with subcell limiting require a Trixi-intern +# SSPRK time integration methods with passed stage callbacks and a Trixi-intern `Trixi.solve(...)` +# routine. +sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + callback = callbacks); +summary_callback() # print the timer summary + + +# ## Visualization +# As for a standard simulation in Trixi.jl, it is possible to visualize the solution using the +# `plot` routine from Plots.jl. +using Plots +plot(sol) + +# To get an additional look at the amount of limiting that is used, you can use the visualization +# approach using the [`SaveSolutionCallback`](@ref), [`Trixi2Vtk`](https://github.com/trixi-framework/Trixi2Vtk.jl) +# and [ParaView](https://www.paraview.org/download/). More details about this procedure +# can be found in the [visualization documentation](@ref visualization). +# Unfortunately, the support for subcell limiting data is not yet merged into the main branch +# of Trixi2Vtk but lies in the branch [`bennibolm/node-variables`](https://github.com/bennibolm/Trixi2Vtk.jl/tree/node-variables). +#- +# With that implementation and the standard procedure used for Trixi2Vtk you get the following +# dropdown menu in ParaView. +#- +# ![ParaView_Dropdownmenu](https://github.com/trixi-framework/Trixi.jl/assets/74359358/70d15f6a-059b-4349-8291-68d9ab3af43e) + +# The resulting visualization of the density and the limiting parameter then looks like this. +# ![blast_wave_paraview](https://github.com/trixi-framework/Trixi.jl/assets/74359358/e5808bed-c8ab-43bf-af7a-050fe43dd630) + +# You can see that the limiting coefficient does not lie in the interval [0,1] because Trixi2Vtk +# interpolates all quantities to regular nodes by default. +# You can disable this functionality with `reinterpolate=false` within the call of `trixi2vtk(...)` +# and get the following visualization. +# ![blast_wave_paraview_reinterpolate=false](https://github.com/trixi-framework/Trixi.jl/assets/74359358/39274f18-0064-469c-b4da-bac4b843e116) + + +# ## Bounds checking +# Subcell limiting is based on the fulfillment of target bounds - either global or local. +# Although the implementation works and has been thoroughly tested, there are some cases where +# these bounds are not met. +# For instance, the deviations could be in machine precision, which is not problematic. +# Larger deviations can be cause by too large time-step sizes (which can be easily fixed by +# reducing the CFL number), specific boundary conditions or source terms. Insufficient parameters +# for the Newton-bisection algorithm can also be a reason when limiting non-linear variables. +# There are described [above](@ref global_bounds). +#- +# In many cases, it is reasonable to monitor the bounds deviations. +# Because of that, Trixi.jl supports a bounds checking routine implemented using the stage +# callback [`BoundsCheckCallback`](@ref). It checks all target bounds for fulfillment +# in every RK stage. If added to the tuple of stage callbacks like +# ````julia +# stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) +# ```` +# and passed to the time integration method, a summary is added to the final console output. +# For the given example, this summary shows that all bounds are met at all times. +# ```` +# ──────────────────────────────────────────────────────────────────────────────────────────────────── +# Maximum deviation from bounds: +# ──────────────────────────────────────────────────────────────────────────────────────────────────── +# rho: +# - lower bound: 0.0 +# - upper bound: 0.0 +# ──────────────────────────────────────────────────────────────────────────────────────────────────── +# ```` + +# Moreover, it is also possible to monitor the bounds deviations incurred during the simulations. +# To do that use the parameter `save_errors = true`, such that the instant deviations are written +# to `deviations.txt` in `output_directory` every `interval` time steps. +# ````julia +# BoundsCheckCallback(save_errors = true, output_directory = "out", interval = 100) +# ```` +# Then, for the given example the deviations file contains all daviations for the current +# timestep and simulation time. +# ```` +# iter, simu_time, rho_min, rho_max +# 1, 0.0, 0.0, 0.0 +# 101, 0.29394033217556337, 0.0, 0.0 +# 201, 0.6012597465597065, 0.0, 0.0 +# 301, 0.9559096690030839, 0.0, 0.0 +# 401, 1.3674274981949077, 0.0, 0.0 +# 501, 1.8395301696603052, 0.0, 0.0 +# 532, 1.9974179806990118, 0.0, 0.0 +# ```` diff --git a/docs/make.jl b/docs/make.jl index f752a7b0ee6..73ee86abd8d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,5 +1,6 @@ using Documenter import Pkg +using Changelog: Changelog # Fix for https://github.com/trixi-framework/Trixi.jl/issues/668 if (get(ENV, "CI", nothing) != "true") && (get(ENV, "TRIXI_DOC_DEFAULT_ENVIRONMENT", nothing) != "true") @@ -69,7 +70,7 @@ files = [ # Topic: introduction "First steps in Trixi.jl" => [ "Getting started" => ("first_steps", "getting_started.jl"), - "Create first setup" => ("first_steps", "create_first_setup.jl"), + "Create your first setup" => ("first_steps", "create_first_setup.jl"), "Changing Trixi.jl itself" => ("first_steps", "changing_trixi.jl"), ], "Behind the scenes of a simulation setup" => "behind_the_scenes_simulation_setup.jl", @@ -77,6 +78,7 @@ files = [ "Introduction to DG methods" => "scalar_linear_advection_1d.jl", "DGSEM with flux differencing" => "DGSEM_FluxDiff.jl", "Shock capturing with flux differencing and stage limiter" => "shock_capturing.jl", + "Subcell limiting with the IDP Limiter" => "subcell_shock_capturing.jl", "Non-periodic boundaries" => "non_periodic_boundaries.jl", "DG schemes via `DGMulti` solver" => "DGMulti_1.jl", "Other SBP schemes (FD, CGSEM) via `DGMulti` solver" => "DGMulti_2.jl", @@ -98,6 +100,14 @@ files = [ ] tutorials = create_tutorials(files) +# Create changelog +Changelog.generate( + Changelog.Documenter(), # output type + joinpath(@__DIR__, "..", "NEWS.md"), # input file + joinpath(@__DIR__, "src", "changelog.md"); # output file + repo = "trixi-framework/Trixi.jl", # default repository for links +) + # Make documentation makedocs( # Specify modules for which docstrings should be shown @@ -150,6 +160,7 @@ makedocs( "TrixiBase.jl" => "reference-trixibase.md", "Trixi2Vtk.jl" => "reference-trixi2vtk.md" ], + "Changelog" => "changelog.md", "Authors" => "authors.md", "Contributing" => "contributing.md", "Code of Conduct" => "code_of_conduct.md", diff --git a/docs/src/meshes/structured_mesh.md b/docs/src/meshes/structured_mesh.md index 7c25c96be63..98f7beb3db9 100644 --- a/docs/src/meshes/structured_mesh.md +++ b/docs/src/meshes/structured_mesh.md @@ -2,6 +2,8 @@ The [`StructuredMesh`](@ref) is a structured, curvilinear, conforming mesh type available for one-, two-, and three-dimensional simulations. +An application of the [`StructuredMesh`](@ref) using a user-defined mapping +is provided by [one of the tutorials](https://trixi-framework.github.io/Trixi.jl/stable/tutorials/structured_mesh_mapping/). Due to its curvilinear nature, (numerical) fluxes need to implement methods dispatching on the `normal::AbstractVector`. Rotationally invariant equations diff --git a/docs/src/meshes/tree_mesh.md b/docs/src/meshes/tree_mesh.md index a172a9b9a5b..9a88919a40c 100644 --- a/docs/src/meshes/tree_mesh.md +++ b/docs/src/meshes/tree_mesh.md @@ -5,7 +5,7 @@ used in many parts of Trixi.jl. Often, the support for this mesh type is developed best since it was the first mesh type in Trixi.jl, and it is available in one, two, and three space dimensions. -It is limited to hypercube domains but supports AMR via the [`AMRCallback`](@ref). +It is limited to hypercube domains (that is, lines in 1D, squares in 2D and cubes in 3D) but supports AMR via the [`AMRCallback`](@ref). Due to its Cartesian nature, (numerical) fluxes need to implement methods dispatching on the `orientation::Integer` as described in the [conventions](@ref conventions). diff --git a/docs/src/multi-physics_coupling.md b/docs/src/multi-physics_coupling.md index eec92bc21de..2c82c1b19e5 100644 --- a/docs/src/multi-physics_coupling.md +++ b/docs/src/multi-physics_coupling.md @@ -36,6 +36,17 @@ By passing the equations we can make use of their parameters, if they are requir Examples can be seen in `examples/structured_2d_dgsem/elixir_advection_coupled.jl`. +## GlmSpeedCallback for coupled MHD simulations + +When simulating an MHD system and the [`GlmSpeedCallback`](@ref) is required, +we need to specify for which semidiscretization we need the GLM speed updated. +This can be done with an additional parameter called `semi_indices`, which +is a tuple containing the semidiscretization indices for all systems +that require the GLM speed updated. + +An example elixir can be found at [`examples/structured_2d_dgsem/elixir_mhd_coupled.jl`](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/structured_2d_dgsem/elixir_mhd_coupled.jl). + + ## Warning about binary compatibility Currently the coordinate values on the nodes can differ by machine precision when simulating the mesh and when splitting the mesh in multiple domains. diff --git a/docs/src/overview.md b/docs/src/overview.md index 9bc523ca297..72a341de7eb 100644 --- a/docs/src/overview.md +++ b/docs/src/overview.md @@ -64,6 +64,14 @@ different features on different mesh types. ᵃ: quad = quadrilateral, hex = hexahedron +Note that except for [`TreeMesh`](@ref) all meshes are of *curvilinear* type, +which means that a (unit) vector normal to the interface (`normal_direction`) needs to be supplied to the +numerical flux function. +You can check the [reference](https://trixi-framework.github.io/Trixi.jl/stable/reference-trixi/) if a certain +numerical flux is implemented with a `normal_direction` +or if currently only the *Cartesian* version (for [`TreeMesh`](@ref)) exists. +In this case, you can still use this flux on curvilinear meshes by rotating it, see [`FluxRotated`](@ref). + ## Time integration methods Trixi.jl is compatible with the [SciML ecosystem for ordinary differential equations](https://diffeq.sciml.ai/latest/). diff --git a/examples/dgmulti_1d/elixir_burgers_gauss_shock_capturing.jl b/examples/dgmulti_1d/elixir_burgers_gauss_shock_capturing.jl new file mode 100644 index 00000000000..b0632b1f978 --- /dev/null +++ b/examples/dgmulti_1d/elixir_burgers_gauss_shock_capturing.jl @@ -0,0 +1,68 @@ +using Trixi +using OrdinaryDiffEq + +equations = InviscidBurgersEquation1D() + +############################################################################### +# setup the GSBP DG discretization that uses the Gauss operators from +# Chan, Del Rey Fernandez, Carpenter (2019). +# [https://doi.org/10.1137/18M1209234](https://doi.org/10.1137/18M1209234) + +surface_flux = flux_lax_friedrichs +volume_flux = flux_ec + +polydeg = 3 +basis = DGMultiBasis(Line(), polydeg, approximation_type = GaussSBP()) + +indicator_sc = IndicatorHennemannGassner(equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = first) +volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) + +dg = DGMulti(basis, + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = volume_integral) + +############################################################################### +# setup the 1D mesh + +cells_per_dimension = (32,) +mesh = DGMultiMesh(dg, cells_per_dimension, + coordinates_min = (-1.0,), coordinates_max = (1.0,), + periodicity = true) + +############################################################################### +# setup the semidiscretization and ODE problem + +semi = SemidiscretizationHyperbolic(mesh, + equations, + initial_condition_convergence_test, + dg) + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +############################################################################### +# setup the callbacks + +# prints a summary of the simulation setup and resets the timers +summary_callback = SummaryCallback() + +# analyse the solution in regular intervals and prints the results +analysis_callback = AnalysisCallback(semi, interval = 100, uEltype = real(dg)) + +# handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl = 0.5) + +# collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) + +# ############################################################################### +# # run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, save_everystep = false, callback = callbacks); diff --git a/examples/p4est_2d_dgsem/elixir_advection_amr_solution_independent.jl b/examples/p4est_2d_dgsem/elixir_advection_amr_solution_independent.jl index 5a2537be4e6..a1ddc6a314f 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_amr_solution_independent.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_amr_solution_independent.jl @@ -33,7 +33,7 @@ function (indicator::IndicatorSolutionIndependent)(u::AbstractArray{<:Any, 4}, outer_distance = 1.85 #Iterate over all elements - for element in 1:length(alpha) + for element in eachindex(alpha) # Calculate periodic distance between cell and center. # This requires an uncurved mesh! coordinates = SVector(0.5 * (cache.elements.node_coordinates[1, 1, 1, element] + diff --git a/examples/p4est_2d_dgsem/elixir_euler_NACA0012airfoil_mach085.jl b/examples/p4est_2d_dgsem/elixir_euler_NACA0012airfoil_mach085.jl new file mode 100644 index 00000000000..fb5f29bd038 --- /dev/null +++ b/examples/p4est_2d_dgsem/elixir_euler_NACA0012airfoil_mach085.jl @@ -0,0 +1,132 @@ +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +p_inf() = 1.0 +rho_inf() = p_inf() / (1.0 * 287.87) # p_inf = 1.0, T = 1, R = 287.87 +mach_inf() = 0.85 +aoa() = pi / 180.0 # 1 Degree angle of attack +c_inf(equations) = sqrt(equations.gamma * p_inf() / rho_inf()) +u_inf(equations) = mach_inf() * c_inf(equations) + +@inline function initial_condition_mach085_flow(x, t, + equations::CompressibleEulerEquations2D) + v1 = u_inf(equations) * cos(aoa()) + v2 = u_inf(equations) * sin(aoa()) + + prim = SVector(rho_inf(), v1, v2, p_inf()) + return prim2cons(prim, equations) +end + +initial_condition = initial_condition_mach085_flow + +volume_flux = flux_ranocha_turbo +surface_flux = flux_lax_friedrichs + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) +shock_indicator = IndicatorHennemannGassner(equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure) +volume_integral = VolumeIntegralShockCapturingHG(shock_indicator; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral) + +mesh_file = Trixi.download("https://gist.githubusercontent.com/Arpit-Babbar/339662b4b46164a016e35c81c66383bb/raw/8bf94f5b426ba907ace87405cfcc1dcc2ef7cbda/NACA0012.inp", + joinpath(@__DIR__, "NACA0012.inp")) + +mesh = P4estMesh{2}(mesh_file) + +# The outer boundary is constant but subsonic, so we cannot compute the +# boundary flux for the external information alone. Thus, we use the numerical flux to distinguish +# between inflow and outflow characteristics +@inline function boundary_condition_subsonic_constant(u_inner, + normal_direction::AbstractVector, x, + t, + surface_flux_function, + equations::CompressibleEulerEquations2D) + u_boundary = initial_condition_mach085_flow(x, t, equations) + + return Trixi.flux_hll(u_inner, u_boundary, normal_direction, equations) +end + +boundary_conditions = Dict(:Left => boundary_condition_subsonic_constant, + :Right => boundary_condition_subsonic_constant, + :Top => boundary_condition_subsonic_constant, + :Bottom => boundary_condition_subsonic_constant, + :AirfoilBottom => boundary_condition_slip_wall, + :AirfoilTop => boundary_condition_slip_wall) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers + +# Run for a long time to reach a steady state +tspan = (0.0, 20.0) +ode = semidiscretize(semi, tspan) + +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 2000 + +l_inf = 1.0 # Length of airfoil + +force_boundary_names = [:AirfoilBottom, :AirfoilTop] +drag_coefficient = AnalysisSurfaceIntegral(semi, force_boundary_names, + DragCoefficientPressure(aoa(), rho_inf(), + u_inf(equations), l_inf)) + +lift_coefficient = AnalysisSurfaceIntegral(semi, force_boundary_names, + LiftCoefficientPressure(aoa(), rho_inf(), + u_inf(equations), l_inf)) + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + output_directory = "out", + save_analysis = true, + analysis_integrals = (drag_coefficient, + lift_coefficient)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 500, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) + +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = 1, + med_level = 3, med_threshold = 0.05, + max_level = 4, max_threshold = 0.1) + +amr_interval = 100 +amr_callback = AMRCallback(semi, amr_controller, + interval = amr_interval, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback, amr_callback) + +############################################################################### +# run the simulation +sol = solve(ode, SSPRK54(thread = OrdinaryDiffEq.True()), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_double_mach.jl b/examples/p4est_2d_dgsem/elixir_euler_double_mach.jl index 949bc40e641..d5d7338ba4e 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_double_mach.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_double_mach.jl @@ -108,8 +108,9 @@ polydeg = 4 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], - spec_entropy = true, + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, + min)], positivity_correction_factor = 0.1, max_iterations_newton = 100, bar_states = true) diff --git a/examples/p4est_2d_dgsem/elixir_euler_free_stream_sc_subcell.jl b/examples/p4est_2d_dgsem/elixir_euler_free_stream_sc_subcell.jl index 9d67ad76ce0..5e5a0847fe7 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_free_stream_sc_subcell.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_free_stream_sc_subcell.jl @@ -17,7 +17,6 @@ limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], positivity_variables_nonlinear = [pressure], positivity_correction_factor = 0.1, - spec_entropy = false, bar_states = true) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/p4est_2d_dgsem/elixir_euler_subsonic_cylinder.jl b/examples/p4est_2d_dgsem/elixir_euler_subsonic_cylinder.jl new file mode 100644 index 00000000000..dc23e192de8 --- /dev/null +++ b/examples/p4est_2d_dgsem/elixir_euler_subsonic_cylinder.jl @@ -0,0 +1,125 @@ +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +@inline function initial_condition_mach038_flow(x, t, + equations::CompressibleEulerEquations2D) + # set the freestream flow parameters + rho_freestream = 1.4 + v1 = 0.38 + v2 = 0.0 + p_freestream = 1.0 + + prim = SVector(rho_freestream, v1, v2, p_freestream) + return prim2cons(prim, equations) +end + +initial_condition = initial_condition_mach038_flow + +volume_flux = flux_ranocha_turbo # FluxRotated(flux_chandrashekar) can also be used +surface_flux = flux_lax_friedrichs + +polydeg = 3 +solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) + +function mapping2cylinder(xi, eta) + xi_, eta_ = 0.5 * (xi + 1), 0.5 * (eta + 1.0) # Map from [-1,1] to [0,1] for simplicity + + R2 = 50.0 # Bigger circle + R1 = 0.5 # Smaller circle + + # Ensure an isotropic mesh by using elements with smaller radial length near the inner circle + + r = R1 * exp(xi_ * log(R2 / R1)) + theta = 2.0 * pi * eta_ + + x = r * cos(theta) + y = r * sin(theta) + return (x, y) +end + +cells_per_dimension = (64, 64) +# xi = -1 maps to the inner circle and xi = +1 maps to the outer circle and we can specify boundary +# conditions there. However, the image of eta = -1, +1 coincides at the line y = 0. There is no +# physical boundary there so we specify `periodicity = true` there and the solver treats the +# element across eta = -1, +1 as neighbours which is what we want +mesh = P4estMesh(cells_per_dimension, mapping = mapping2cylinder, polydeg = polydeg, + periodicity = (false, true)) + +# The boundary condition on the outer cylinder is constant but subsonic, so we cannot compute the +# boundary flux from the external information alone. Thus, we use the numerical flux to distinguish +# between inflow and outflow characteristics +@inline function boundary_condition_subsonic_constant(u_inner, + normal_direction::AbstractVector, x, + t, + surface_flux_function, + equations::CompressibleEulerEquations2D) + u_boundary = initial_condition_mach038_flow(x, t, equations) + + return surface_flux_function(u_inner, u_boundary, normal_direction, equations) +end + +boundary_conditions = Dict(:x_neg => boundary_condition_slip_wall, + :x_pos => boundary_condition_subsonic_constant) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers + +# Run for a long time to reach a steady state +tspan = (0.0, 100.0) +ode = semidiscretize(semi, tspan) + +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 2000 + +aoa = 0.0 +rho_inf = 1.4 +u_inf = 0.38 +l_inf = 1.0 # Diameter of circle + +drag_coefficient = AnalysisSurfaceIntegral(semi, :x_neg, + DragCoefficientPressure(aoa, rho_inf, u_inf, + l_inf)) + +lift_coefficient = AnalysisSurfaceIntegral(semi, :x_neg, + LiftCoefficientPressure(aoa, rho_inf, u_inf, + l_inf)) + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + output_directory = "out", + save_analysis = true, + analysis_integrals = (drag_coefficient, + lift_coefficient)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 500, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 2.0) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback) + +############################################################################### +# run the simulation +sol = solve(ode, + CarpenterKennedy2N54(williamson_condition = false; + thread = OrdinaryDiffEq.True()), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl index de0a0116ec7..5157480894c 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl @@ -98,7 +98,8 @@ basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], positivity_variables_nonlinear = [pressure], - spec_entropy = true, + local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, + min)], max_iterations_newton = 100, bar_states = false) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/p4est_2d_dgsem/elixir_navierstokes_NACA0012airfoil_mach08.jl b/examples/p4est_2d_dgsem/elixir_navierstokes_NACA0012airfoil_mach08.jl new file mode 100644 index 00000000000..1b485913ab2 --- /dev/null +++ b/examples/p4est_2d_dgsem/elixir_navierstokes_NACA0012airfoil_mach08.jl @@ -0,0 +1,169 @@ +using Downloads: download +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +# Laminar transonic flow around a NACA0012 airfoil. + +# This test is taken from the paper of Swanson and Langer. The values for the drag and lift coefficients +# from Case 5 in Table 3 are used to validate the scheme and computation of surface forces. + +# References: +# - Roy Charles Swanson, Stefan Langer (2016) +# Structured and Unstructured Grid Methods (2016) +# [https://ntrs.nasa.gov/citations/20160003623] (https://ntrs.nasa.gov/citations/20160003623) +# - Deep Ray, Praveen Chandrashekar (2017) +# An entropy stable finite volume scheme for the +# two dimensional Navier–Stokes equations on triangular grids +# [DOI:10.1016/j.amc.2017.07.020](https://doi.org/10.1016/j.amc.2017.07.020) + +equations = CompressibleEulerEquations2D(1.4) + +prandtl_number() = 0.72 +mu() = 0.0031959974968701088 +equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), + Prandtl = prandtl_number()) + +rho_inf() = 1.0 +p_inf() = 2.85 +aoa() = 10.0 * pi / 180.0 # 10 degree angle of attack +l_inf() = 1.0 +mach_inf() = 0.8 +u_inf(equations) = mach_inf() * sqrt(equations.gamma * p_inf() / rho_inf()) +@inline function initial_condition_mach08_flow(x, t, equations) + # set the freestream flow parameters + gamma = equations.gamma + u_inf = mach_inf() * sqrt(gamma * p_inf() / rho_inf()) + + v1 = u_inf * cos(aoa()) + v2 = u_inf * sin(aoa()) + + prim = SVector(rho_inf(), v1, v2, p_inf()) + return prim2cons(prim, equations) +end + +initial_condition = initial_condition_mach08_flow + +surface_flux = flux_lax_friedrichs + +polydeg = 3 +solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux) + +mesh_file = Trixi.download("https://gist.githubusercontent.com/Arpit-Babbar/339662b4b46164a016e35c81c66383bb/raw/8bf94f5b426ba907ace87405cfcc1dcc2ef7cbda/NACA0012.inp", + joinpath(@__DIR__, "NACA0012.inp")) + +mesh = P4estMesh{2}(mesh_file, initial_refinement_level = 1) + +# The boundary values across outer boundary are constant but subsonic, so we cannot compute the +# boundary flux from the external information alone. Thus, we use the numerical flux to distinguish +# between inflow and outflow characteristics +@inline function boundary_condition_subsonic_constant(u_inner, + normal_direction::AbstractVector, x, + t, + surface_flux_function, + equations::CompressibleEulerEquations2D) + u_boundary = initial_condition_mach08_flow(x, t, equations) + + return Trixi.flux_hll(u_inner, u_boundary, normal_direction, equations) +end + +boundary_conditions = Dict(:Left => boundary_condition_subsonic_constant, + :Right => boundary_condition_subsonic_constant, + :Top => boundary_condition_subsonic_constant, + :Bottom => boundary_condition_subsonic_constant, + :AirfoilBottom => boundary_condition_slip_wall, + :AirfoilTop => boundary_condition_slip_wall) + +velocity_airfoil = NoSlip((x, t, equations) -> SVector(0.0, 0.0)) + +heat_airfoil = Adiabatic((x, t, equations) -> 0.0) + +boundary_conditions_airfoil = BoundaryConditionNavierStokesWall(velocity_airfoil, + heat_airfoil) + +function momenta_initial_condition_mach08_flow(x, t, equations) + u = initial_condition_mach08_flow(x, t, equations) + momenta = SVector(u[2], u[3]) +end +velocity_bc_square = NoSlip((x, t, equations) -> momenta_initial_condition_mach08_flow(x, t, + equations)) + +heat_bc_square = Adiabatic((x, t, equations) -> 0.0) +boundary_condition_square = BoundaryConditionNavierStokesWall(velocity_bc_square, + heat_bc_square) + +boundary_conditions_parabolic = Dict(:Left => boundary_condition_square, + :Right => boundary_condition_square, + :Top => boundary_condition_square, + :Bottom => boundary_condition_square, + :AirfoilBottom => boundary_conditions_airfoil, + :AirfoilTop => boundary_conditions_airfoil) + +semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = (boundary_conditions, + boundary_conditions_parabolic)) + +############################################################################### +# ODE solvers + +# Run for a long time to reach a state where forces stabilize up to 3 digits +tspan = (0.0, 10.0) +ode = semidiscretize(semi, tspan) + +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 2000 + +force_boundary_names = [:AirfoilBottom, :AirfoilTop] +drag_coefficient = AnalysisSurfaceIntegral(semi, force_boundary_names, + DragCoefficientPressure(aoa(), rho_inf(), + u_inf(equations), + l_inf())) + +lift_coefficient = AnalysisSurfaceIntegral(semi, force_boundary_names, + LiftCoefficientPressure(aoa(), rho_inf(), + u_inf(equations), + l_inf())) + +drag_coefficient_shear_force = AnalysisSurfaceIntegral(semi, force_boundary_names, + DragCoefficientShearStress(aoa(), + rho_inf(), + u_inf(equations), + l_inf())) + +lift_coefficient_shear_force = AnalysisSurfaceIntegral(semi, force_boundary_names, + LiftCoefficientShearStress(aoa(), + rho_inf(), + u_inf(equations), + l_inf())) + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + output_directory = "out", + save_analysis = true, + analysis_errors = Symbol[], + analysis_integrals = (drag_coefficient, + lift_coefficient, + drag_coefficient_shear_force, + lift_coefficient_shear_force)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 500, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + +############################################################################### +# run the simulation + +sol = solve(ode, RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()); abstol = 1e-8, + reltol = 1e-8, + ode_default_options()..., callback = callbacks) +summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_linearizedeuler_convergence.jl b/examples/p4est_3d_dgsem/elixir_linearizedeuler_convergence.jl new file mode 100644 index 00000000000..fd066ef8955 --- /dev/null +++ b/examples/p4est_3d_dgsem/elixir_linearizedeuler_convergence.jl @@ -0,0 +1,64 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linearized Euler equations + +equations = LinearizedEulerEquations3D(v_mean_global = (0.0, 0.0, 0.0), c_mean_global = 1.0, + rho_mean_global = 1.0) + +initial_condition = initial_condition_convergence_test + +solver = DGSEM(polydeg = 3, surface_flux = flux_hll) + +coordinates_min = (-1.0, -1.0, -1.0) # minimum coordinates (min(x), min(y), min(z)) +coordinates_max = (1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) + +# `initial_refinement_level` is provided here to allow for a +# convenient convergence test, see +# https://trixi-framework.github.io/Trixi.jl/stable/#Performing-a-convergence-analysis +trees_per_dimension = (4, 4, 4) +mesh = P4estMesh(trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + initial_refinement_level = 0) + +# A semidiscretization collects data structures and functions for the spatial discretization +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# 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() + +analysis_interval = 100 + +# The AnalysisCallback allows to analyse the solution in regular intervals and prints the results +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +# The AliveCallback prints short status information in regular intervals +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +# The StepsizeCallback handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl = 0.8) + +# Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, + stepsize_callback) + +############################################################################### +# 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() # print the timer summary diff --git a/examples/structured_1d_dgsem/elixir_linearizedeuler_characteristic_system.jl b/examples/structured_1d_dgsem/elixir_linearizedeuler_characteristic_system.jl new file mode 100644 index 00000000000..663b25b18c0 --- /dev/null +++ b/examples/structured_1d_dgsem/elixir_linearizedeuler_characteristic_system.jl @@ -0,0 +1,112 @@ + +using OrdinaryDiffEq +using LinearAlgebra: dot +using Trixi + +############################################################################### +# semidiscretization of the linearized Euler equations + +rho_0 = 1.0 +v_0 = 1.0 +c_0 = 1.0 +equations = LinearizedEulerEquations1D(rho_0, v_0, c_0) + +solver = DGSEM(polydeg = 3, surface_flux = flux_hll) + +coordinates_min = (0.0,) # minimum coordinate +coordinates_max = (1.0,) # maximum coordinate +cells_per_dimension = (64,) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +# For eigensystem of the linearized Euler equations see e.g. +# https://www.nas.nasa.gov/assets/nas/pdf/ams/2018/introtocfd/Intro2CFD_Lecture1_Pulliam_Euler_WaveEQ.pdf +# Linearized Euler: Eigensystem +lin_euler_eigvals = [v_0 - c_0; v_0; v_0 + c_0] +lin_euler_eigvecs = [-rho_0/c_0 1 rho_0/c_0; + 1 0 1; + -rho_0*c_0 0 rho_0*c_0] +lin_euler_eigvecs_inv = inv(lin_euler_eigvecs) + +# Trace back characteristics. +# See https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf, p.95 +function compute_char_initial_pos(x, t) + return SVector(x[1], x[1], x[1]) .- t * lin_euler_eigvals +end + +function compute_primal_sol(char_vars) + return lin_euler_eigvecs * char_vars +end + +# Initial condition is in principle arbitrary, only periodicity is required +function initial_condition_entropy_wave(x, t, equations::LinearizedEulerEquations1D) + # Parameters + alpha = 1.0 + beta = 150.0 + center = 0.5 + + rho_prime = alpha * exp(-beta * (x[1] - center)^2) + v_prime = 0.0 + p_prime = 0.0 + + return SVector(rho_prime, v_prime, p_prime) +end + +function initial_condition_char_vars(x, t, equations::LinearizedEulerEquations1D) + # Trace back characteristics + x_char = compute_char_initial_pos(x, t) + + # Employ periodicity + for p in 1:3 + while x_char[p] < coordinates_min[1] + x_char[p] += coordinates_max[1] - coordinates_min[1] + end + while x_char[p] > coordinates_max[1] + x_char[p] -= coordinates_max[1] - coordinates_min[1] + end + end + + # Set up characteristic variables + w = zeros(3) + t_0 = 0 # Assumes t_0 = 0 + for p in 1:3 + u_char = initial_condition_entropy_wave(x_char[p], t_0, equations) + w[p] = dot(lin_euler_eigvecs_inv[p, :], u_char) + end + + return compute_primal_sol(w) +end + +initial_condition = initial_condition_char_vars + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 0.3) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +# 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 + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); + +summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_advection_meshview.jl b/examples/structured_2d_dgsem/elixir_advection_meshview.jl new file mode 100644 index 00000000000..d8d27031090 --- /dev/null +++ b/examples/structured_2d_dgsem/elixir_advection_meshview.jl @@ -0,0 +1,122 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Coupled semidiscretization of two linear advection systems using converter functions +# and mesh views for the semidiscretizations. First we define a parent mesh +# for the entire physical domain, then we define the two mesh views on this parent. +# +# In this elixir, we have a square domain that is divided into left and right subdomains. +# On each half of the domain, a completely independent `SemidiscretizationHyperbolic` +# is created for the linear advection equations. The two systems are coupled in the +# x-direction. +# For a high-level overview, see also the figure below: +# +# (-1, 1) ( 1, 1) +# ┌────────────────────┬────────────────────┐ +# │ ↑ periodic ↑ │ ↑ periodic ↑ │ +# │ │ │ +# │ ========= │ ========= │ +# │ system #1 │ system #2 │ +# │ ========= │ ========= │ +# │ │ │ +# │<-- coupled │<-- coupled │ +# │ coupled -->│ coupled -->│ +# │ │ │ +# │ ↓ periodic ↓ │ ↓ periodic ↓ │ +# └────────────────────┴────────────────────┘ +# (-1, -1) ( 1, -1) + +advection_velocity = (0.2, -0.7) +equations = LinearScalarAdvectionEquation2D(advection_velocity) + +# Create DG solver with polynomial degree = 3 +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +# Domain size of the parent mesh. +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) + +# Cell dimensions of the parent mesh. +cells_per_dimension = (16, 16) + +# Create parent mesh with 16 x 16 elements +parent_mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +# Create the two mesh views, each of which takes half of the parent mesh. +mesh1 = StructuredMeshView(parent_mesh; indices_min = (1, 1), indices_max = (8, 16)) +mesh2 = StructuredMeshView(parent_mesh; indices_min = (9, 1), indices_max = (16, 16)) + +# The coupling function is simply the identity, as we are dealing with two identical systems. +coupling_function = (x, u, equations_other, equations_own) -> u + +# Define the coupled boundary conditions +# The indices (:end, :i_forward) and (:begin, :i_forward) denote the interface indexing. +# For a system with coupling in x and y see examples/structured_2d_dgsem/elixir_advection_coupled.jl. +boundary_conditions1 = ( + # Connect left boundary with right boundary of left mesh + x_neg = BoundaryConditionCoupled(2, (:end, :i_forward), Float64, + coupling_function), + x_pos = BoundaryConditionCoupled(2, (:begin, :i_forward), Float64, + coupling_function), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic) +boundary_conditions2 = ( + # Connect left boundary with right boundary of left mesh + x_neg = BoundaryConditionCoupled(1, (:end, :i_forward), Float64, + coupling_function), + x_pos = BoundaryConditionCoupled(1, (:begin, :i_forward), Float64, + coupling_function), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic) + +# A semidiscretization collects data structures and functions for the spatial discretization +semi1 = SemidiscretizationHyperbolic(mesh1, equations, initial_condition_convergence_test, + solver, + boundary_conditions = boundary_conditions1) +semi2 = SemidiscretizationHyperbolic(mesh2, equations, initial_condition_convergence_test, + solver, + boundary_conditions = boundary_conditions2) +semi = SemidiscretizationCoupled(semi1, semi2) + +############################################################################### +# 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_callback1 = AnalysisCallback(semi1, interval = 100) +analysis_callback2 = AnalysisCallback(semi2, interval = 100) +analysis_callback = AnalysisCallbackCoupled(semi, analysis_callback1, analysis_callback2) + +analysis_interval = 100 +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +# The SaveSolutionCallback allows to save the solution to a file in regular intervals +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +# The StepsizeCallback handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl = 1.6) + +# Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +# OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 5.0e-2, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); + +# Print the timer summary +summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_euler_convergence_wavingflag_IDP.jl b/examples/structured_2d_dgsem/elixir_euler_convergence_wavingflag_IDP.jl index e7435e47123..eb675671aca 100644 --- a/examples/structured_2d_dgsem/elixir_euler_convergence_wavingflag_IDP.jl +++ b/examples/structured_2d_dgsem/elixir_euler_convergence_wavingflag_IDP.jl @@ -17,7 +17,6 @@ limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], positivity_variables_nonlinear = [pressure], positivity_correction_factor = 0.1, - spec_entropy = false, max_iterations_newton = 10, newton_tolerances = (1.0e-12, 1.0e-14), bar_states = true, diff --git a/examples/structured_2d_dgsem/elixir_euler_double_mach.jl b/examples/structured_2d_dgsem/elixir_euler_double_mach.jl index c40d79e3cc5..e1fff80510d 100644 --- a/examples/structured_2d_dgsem/elixir_euler_double_mach.jl +++ b/examples/structured_2d_dgsem/elixir_euler_double_mach.jl @@ -114,8 +114,9 @@ polydeg = 4 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], - spec_entropy = true, + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, + min)], positivity_correction_factor = 0.1, max_iterations_newton = 100, bar_states = true) diff --git a/examples/structured_2d_dgsem/elixir_euler_free_stream_sc_subcell.jl b/examples/structured_2d_dgsem/elixir_euler_free_stream_sc_subcell.jl index e67130f8815..64f28d63560 100644 --- a/examples/structured_2d_dgsem/elixir_euler_free_stream_sc_subcell.jl +++ b/examples/structured_2d_dgsem/elixir_euler_free_stream_sc_subcell.jl @@ -17,7 +17,6 @@ limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], positivity_variables_nonlinear = [pressure], positivity_correction_factor = 0.1, - spec_entropy = false, bar_states = true) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/structured_2d_dgsem/elixir_euler_shock_upstream_sc_subcell.jl b/examples/structured_2d_dgsem/elixir_euler_shock_upstream_sc_subcell.jl index 3bfa0271978..4d206c66ff5 100644 --- a/examples/structured_2d_dgsem/elixir_euler_shock_upstream_sc_subcell.jl +++ b/examples/structured_2d_dgsem/elixir_euler_shock_upstream_sc_subcell.jl @@ -37,8 +37,9 @@ polydeg = 5 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], - spec_entropy = true, + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, + min)], max_iterations_newton = 100, bar_states = true) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/structured_2d_dgsem/elixir_euler_source_terms_sc_subcell.jl b/examples/structured_2d_dgsem/elixir_euler_source_terms_sc_subcell.jl index 1f88427d3eb..34ce074f727 100644 --- a/examples/structured_2d_dgsem/elixir_euler_source_terms_sc_subcell.jl +++ b/examples/structured_2d_dgsem/elixir_euler_source_terms_sc_subcell.jl @@ -16,8 +16,9 @@ volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], - spec_entropy = true, + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, + min)], bar_states = false) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, diff --git a/examples/structured_2d_dgsem/elixir_mhd_coupled.jl b/examples/structured_2d_dgsem/elixir_mhd_coupled.jl new file mode 100644 index 00000000000..d3aa4ecf582 --- /dev/null +++ b/examples/structured_2d_dgsem/elixir_mhd_coupled.jl @@ -0,0 +1,136 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# Two semidiscretizations of the ideal GLM-MHD systems using converter functions such that +# they are coupled across the domain boundaries to generate a periodic system. +# +# In this elixir, we have a square domain that is divided into a left and right half. +# On each half of the domain, an independent SemidiscretizationHyperbolic is created for +# each set of ideal GLM-MHD equations. The two systems are coupled in the x-direction +# and are periodic in the y-direction. +# For a high-level overview, see also the figure below: +# +# (-2, 2) ( 2, 2) +# ┌────────────────────┬────────────────────┐ +# │ ↑ periodic ↑ │ ↑ periodic ↑ │ +# │ │ │ +# │ ========= │ ========= │ +# │ system #1 │ system #2 │ +# │ ========= │ ========= │ +# │ │ │ +# │<-- coupled │<-- coupled │ +# │ coupled -->│ coupled -->│ +# │ │ │ +# │ ↓ periodic ↓ │ ↓ periodic ↓ │ +# └────────────────────┴────────────────────┘ +# (-2, -2) ( 2, -2) + +gamma = 5 / 3 +equations = IdealGlmMhdEquations2D(gamma) + +cells_per_dimension = (32, 64) + +# Extend the definition of the non-conservative Powell flux functions. +import Trixi.flux_nonconservative_powell +function flux_nonconservative_powell(u_ll, u_rr, + normal_direction_ll::AbstractVector, + equations::IdealGlmMhdEquations2D) + flux_nonconservative_powell(u_ll, u_rr, normal_direction_ll, normal_direction_ll, + equations) +end +volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) +solver = DGSEM(polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) + +########### +# system #1 +########### + +initial_condition1 = initial_condition_convergence_test +coordinates_min1 = (-1 / sin(pi / 4), -1 / sin(pi / 4)) +coordinates_max1 = (0.0, 1 / sin(pi / 4)) +mesh1 = StructuredMesh(cells_per_dimension, + coordinates_min1, + coordinates_max1, + periodicity = (false, true)) + +coupling_function1 = (x, u, equations_other, equations_own) -> u +boundary_conditions1 = (x_neg = BoundaryConditionCoupled(2, (:end, :i_forward), Float64, + coupling_function1), + x_pos = BoundaryConditionCoupled(2, (:begin, :i_forward), Float64, + coupling_function1), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic) + +semi1 = SemidiscretizationHyperbolic(mesh1, equations, initial_condition1, solver, + boundary_conditions = boundary_conditions1) + +########### +# system #2 +########### + +initial_condition2 = initial_condition_convergence_test +coordinates_min2 = (0.0, -1 / sin(pi / 4)) +coordinates_max2 = (1 / sin(pi / 4), 1 / sin(pi / 4)) +mesh2 = StructuredMesh(cells_per_dimension, + coordinates_min2, + coordinates_max2, + periodicity = (false, true)) + +coupling_function2 = (x, u, equations_other, equations_own) -> u +boundary_conditions2 = (x_neg = BoundaryConditionCoupled(1, (:end, :i_forward), Float64, + coupling_function2), + x_pos = BoundaryConditionCoupled(1, (:begin, :i_forward), Float64, + coupling_function2), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic) + +semi2 = SemidiscretizationHyperbolic(mesh2, equations, initial_condition2, solver, + boundary_conditions = boundary_conditions2) + +# Create a semidiscretization that bundles all the semidiscretizations. +semi = SemidiscretizationCoupled(semi1, semi2) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 0.1) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 + +analysis_callback1 = AnalysisCallback(semi1, interval = 100) +analysis_callback2 = AnalysisCallback(semi2, interval = 100) +analysis_callback = AnalysisCallbackCoupled(semi, analysis_callback1, analysis_callback2) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 50, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +cfl = 1.0 + +stepsize_callback = StepsizeCallback(cfl = cfl) + +glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl, + semi_indices = [1, 2]) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.01, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_shallowwater_source_terms.jl b/examples/structured_2d_dgsem/elixir_shallowwater_source_terms.jl index 48fe37b9996..532fe8dbe7d 100644 --- a/examples/structured_2d_dgsem/elixir_shallowwater_source_terms.jl +++ b/examples/structured_2d_dgsem/elixir_shallowwater_source_terms.jl @@ -42,7 +42,7 @@ save_solution = SaveSolutionCallback(interval = 100, save_final_solution = true, solution_variables = cons2prim) -stepsize_callback = StepsizeCallback(cfl = 2.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, diff --git a/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced.jl b/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced.jl index a6a56aa807c..09abdf33843 100644 --- a/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced.jl +++ b/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced.jl @@ -108,7 +108,7 @@ save_solution = SaveSolutionCallback(interval = 1000, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl b/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl index 0589e76a6a9..1ed08e1961b 100644 --- a/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl +++ b/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl @@ -32,7 +32,7 @@ function (indicator::IndicatorSolutionIndependent)(u::AbstractArray{<:Any, 4}, outer_distance = 1.85 # Iterate over all elements. - for element in 1:length(alpha) + for element in eachindex(alpha) # Calculate periodic distance between cell and center. # This requires an uncurved mesh! coordinates = SVector(0.5 * (cache.elements.node_coordinates[1, 1, 1, element] + diff --git a/examples/tree_1d_dgsem/elixir_linearizedeuler_convergence.jl b/examples/tree_1d_dgsem/elixir_linearizedeuler_convergence.jl new file mode 100644 index 00000000000..5b17ab4f3dc --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_linearizedeuler_convergence.jl @@ -0,0 +1,59 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linearized Euler equations + +equations = LinearizedEulerEquations1D(v_mean_global = 0.0, c_mean_global = 1.0, + rho_mean_global = 1.0) + +initial_condition = initial_condition_convergence_test + +# Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (-1.0) +coordinates_max = (1.0) + +# Create a uniformly refined mesh with periodic boundaries +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000) + +# A semidiscretization collects data structures and functions for the spatial discretization +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.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() + +analysis_interval = 100 + +# The AnalysisCallback allows to analyse the solution in regular intervals and prints the results +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +# The AliveCallback prints short status information in regular intervals +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +# The StepsizeCallback handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl = 0.8) + +# Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, + stepsize_callback) + +############################################################################### +# 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); + +summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_linearizedeuler_gauss_wall.jl b/examples/tree_1d_dgsem/elixir_linearizedeuler_gauss_wall.jl new file mode 100644 index 00000000000..0884249559a --- /dev/null +++ b/examples/tree_1d_dgsem/elixir_linearizedeuler_gauss_wall.jl @@ -0,0 +1,65 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linearized Euler equations + +equations = LinearizedEulerEquations1D(v_mean_global = 0.5, c_mean_global = 1.0, + rho_mean_global = 1.0) + +solver = DGSEM(polydeg = 5, surface_flux = flux_hll) + +coordinates_min = (0.0,) +coordinates_max = (90.0,) + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 100_000, + periodicity = false) + +# Initialize density and pressure perturbation with a Gaussian bump +# that is advected to left with v - c and to the right with v + c. +# Correspondigly, the bump splits in half. +function initial_condition_gauss_wall(x, t, equations::LinearizedEulerEquations1D) + v1_prime = 0.0 + rho_prime = p_prime = 2 * exp(-(x[1] - 45)^2 / 25) + return SVector(rho_prime, v1_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 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, + 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_1d_dgsem/elixir_shallowwater_ec.jl b/examples/tree_1d_dgsem/elixir_shallowwater_ec.jl index a3df37fb966..af0da5d1768 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_ec.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_ec.jl @@ -77,7 +77,7 @@ save_solution = SaveSolutionCallback(interval = 100, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_well_balanced.jl b/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_well_balanced.jl index d9f1a52b500..a4f4b0189ba 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_well_balanced.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_well_balanced.jl @@ -70,7 +70,7 @@ save_solution = SaveSolutionCallback(interval = 1000, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced.jl b/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced.jl index 649e5023f6d..5851530e230 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced.jl @@ -73,7 +73,7 @@ save_solution = SaveSolutionCallback(interval = 1000, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/examples/tree_2d_dgsem/elixir_advection_amr_solution_independent.jl b/examples/tree_2d_dgsem/elixir_advection_amr_solution_independent.jl index 03a213689ec..c7412660b0c 100644 --- a/examples/tree_2d_dgsem/elixir_advection_amr_solution_independent.jl +++ b/examples/tree_2d_dgsem/elixir_advection_amr_solution_independent.jl @@ -31,7 +31,7 @@ function (indicator::IndicatorSolutionIndependent)(u::AbstractArray{<:Any, 4}, outer_distance = 1.85 #Iterate over all elements - for element in 1:length(alpha) + for element in eachindex(alpha) #Calculate periodic distance between cell and center. cell_id = cache.elements.cell_ids[element] coordinates = mesh.tree.coordinates[1:2, cell_id] diff --git a/examples/tree_2d_dgsem/elixir_euler_astro_jet_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_astro_jet_subcell.jl index 47988bfef01..09e4bdeccd5 100644 --- a/examples/tree_2d_dgsem/elixir_euler_astro_jet_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_astro_jet_subcell.jl @@ -42,8 +42,9 @@ basis = LobattoLegendreBasis(polydeg) # shock capturing necessary for this tough example limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], - spec_entropy = true, + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, + min)], bar_states = true, max_iterations_newton = 25) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell.jl index 2307a6d139b..e70e1970f6a 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell.jl @@ -39,8 +39,9 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], - math_entropy = true, + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_math, + max)], bar_states = false, smoothness_indicator = true) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell_nonperiodic.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell_nonperiodic.jl index 9d44eae4850..d40a6d2b3d3 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell_nonperiodic.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell_nonperiodic.jl @@ -41,8 +41,9 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], - math_entropy = true, + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_math, + max)], bar_states = false) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_IDP.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_IDP.jl index 66d6e776292..7fe7c3d53e7 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_IDP.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_IDP.jl @@ -17,7 +17,6 @@ limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], positivity_variables_nonlinear = [pressure], positivity_correction_factor = 0.1, - spec_entropy = false, max_iterations_newton = 10, newton_tolerances = (1.0e-12, 1.0e-14), bar_states = true, diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl index 5630b51c294..acc2f10019f 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl @@ -40,7 +40,6 @@ basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], positivity_variables_nonlinear = [pressure], - spec_entropy = false, bar_states = true) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl index 35d4317dde6..7aa94e7739a 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl @@ -42,8 +42,9 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_chandrashekar basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], - spec_entropy = true, + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, + min)], smoothness_indicator = false, bar_states = true) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/tree_2d_dgsem/elixir_euler_source_terms_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_source_terms_sc_subcell.jl index 5d9a90003d4..8e2f06bca4e 100644 --- a/examples/tree_2d_dgsem/elixir_euler_source_terms_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_source_terms_sc_subcell.jl @@ -14,7 +14,7 @@ volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho"], + local_twosided_variables_cons = ["rho"], positivity_variables_cons = ["rho"], positivity_variables_nonlinear = [pressure], bar_states = true, diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl index 6a1aae92b36..20a0c4f6f85 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl @@ -86,9 +86,8 @@ volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; - local_minmax_variables_cons = ["rho" * string(i) - for i in eachcomponent(equations)], - spec_entropy = false, + local_twosided_variables_cons = ["rho" * string(i) + for i in eachcomponent(equations)], bar_states = true) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl index 584f55644e7..04e83d2dff2 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl @@ -90,7 +90,6 @@ limiter_idp = SubcellLimiterIDP(equations, basis; for i in eachcomponent(equations)], positivity_variables_nonlinear = [], positivity_correction_factor = 0.1, - spec_entropy = false, bar_states = false) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_ec.jl b/examples/tree_2d_dgsem/elixir_shallowwater_ec.jl index bc528ae7756..8221dfebe39 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_ec.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_ec.jl @@ -105,7 +105,7 @@ save_solution = SaveSolutionCallback(dt = 0.2, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced.jl b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced.jl index 13023dfaba2..22043392b2a 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced.jl @@ -105,7 +105,7 @@ save_solution = SaveSolutionCallback(interval = 1000, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wall.jl b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wall.jl index f50bd4e4f65..19073b0504a 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wall.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wall.jl @@ -108,7 +108,7 @@ save_solution = SaveSolutionCallback(interval = 1000, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/examples/tree_3d_dgsem/elixir_linearizedeuler_gauss_wall.jl b/examples/tree_3d_dgsem/elixir_linearizedeuler_gauss_wall.jl new file mode 100644 index 00000000000..2eb8ef822a3 --- /dev/null +++ b/examples/tree_3d_dgsem/elixir_linearizedeuler_gauss_wall.jl @@ -0,0 +1,65 @@ +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linearized Euler equations + +equations = LinearizedEulerEquations3D(v_mean_global = (0.5, 0.5, 0.5), c_mean_global = 1.0, + rho_mean_global = 1.0) + +solver = DGSEM(polydeg = 5, surface_flux = flux_lax_friedrichs) + +coordinates_min = (0.0, 0.0, 0.0) +coordinates_max = (90.0, 90.0, 90.0) + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 100_000, + periodicity = false) + +# Initialize density and pressure perturbation with a Gaussian bump +# that splits into radial waves which are advected with v - c and v + c. +function initial_condition_gauss_wall(x, t, equations::LinearizedEulerEquations3D) + v1_prime = 0.0 + v2_prime = 0.0 + v3_prime = 0.0 + rho_prime = p_prime = 2 * exp(-((x[1] - 45)^2 + (x[2] - 45)^2) / 25) + return SVector(rho_prime, v1_prime, v2_prime, v3_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. + +# At t = 30, the wave moving with v + c crashes into the wall +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 StepsizeCallback handles the re-calculation of the maximum Δt after each time step +stepsize_callback = StepsizeCallback(cfl = 0.9) + +# Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver +callbacks = CallbackSet(summary_callback, analysis_callback, + stepsize_callback) + +############################################################################### +# 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/unstructured_2d_dgsem/elixir_shallowwater_ec.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec.jl index 9122fb8287d..1f4aa414905 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec.jl @@ -105,7 +105,7 @@ save_solution = SaveSolutionCallback(interval = 1000, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_shockcapturing.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_shockcapturing.jl index 98408db5a78..c4736e8b9a5 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_shockcapturing.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_shockcapturing.jl @@ -110,7 +110,7 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, stepsize_callback) diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_well_balanced.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_well_balanced.jl index 6bad3a77f03..6cefca853c1 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_well_balanced.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_well_balanced.jl @@ -104,7 +104,7 @@ save_solution = SaveSolutionCallback(interval = 1000, save_initial_solution = true, save_final_solution = true) -stepsize_callback = StepsizeCallback(cfl = 3.0) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, stepsize_callback) diff --git a/src/Trixi.jl b/src/Trixi.jl index 1f74814145a..4569bec13df 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -160,7 +160,7 @@ export AcousticPerturbationEquations2D, LatticeBoltzmannEquations2D, LatticeBoltzmannEquations3D, ShallowWaterEquations1D, ShallowWaterEquations2D, ShallowWaterEquationsQuasi1D, - LinearizedEulerEquations2D, + LinearizedEulerEquations1D, LinearizedEulerEquations2D, LinearizedEulerEquations3D, PolytropicEulerEquations2D, TrafficFlowLWREquations1D @@ -227,7 +227,8 @@ export entropy, energy_total, energy_kinetic, energy_internal, energy_magnetic, export lake_at_rest_error export ncomponents, eachcomponent -export TreeMesh, StructuredMesh, UnstructuredMesh2D, P4estMesh, T8codeMesh +export TreeMesh, StructuredMesh, StructuredMeshView, UnstructuredMesh2D, P4estMesh, + T8codeMesh export DG, DGSEM, LobattoLegendreBasis, @@ -264,7 +265,9 @@ export SummaryCallback, SteadyStateCallback, AnalysisCallback, AliveCallback, AveragingCallback, AMRCallback, StepsizeCallback, LimitingAnalysisCallback, GlmSpeedCallback, LBMCollisionCallback, EulerAcousticsCouplingCallback, - TrivialCallback, AnalysisCallbackCoupled + TrivialCallback, AnalysisCallbackCoupled, + AnalysisSurfaceIntegral, DragCoefficientPressure, LiftCoefficientPressure, + DragCoefficientShearStress, LiftCoefficientShearStress export load_mesh, load_time, load_timestep, load_timestep!, load_dt, load_adaptive_time_integrator! diff --git a/src/callbacks_stage/subcell_bounds_check.jl b/src/callbacks_stage/subcell_bounds_check.jl index 5ac9bc8b340..d41994ba931 100644 --- a/src/callbacks_stage/subcell_bounds_check.jl +++ b/src/callbacks_stage/subcell_bounds_check.jl @@ -89,28 +89,27 @@ function init_callback(callback::BoundsCheckCallback, semi, limiter::SubcellLimi return nothing end - (; local_minmax, positivity, spec_entropy, math_entropy) = limiter + (; local_twosided, positivity, local_onesided) = limiter (; output_directory) = callback variables = varnames(cons2cons, semi.equations) mkpath(output_directory) open("$output_directory/deviations.txt", "a") do f print(f, "# iter, simu_time") - if local_minmax - for v in limiter.local_minmax_variables_cons + if local_twosided + for v in limiter.local_twosided_variables_cons variable_string = string(variables[v]) print(f, ", " * variable_string * "_min, " * variable_string * "_max") end end - if spec_entropy - print(f, ", specEntr_min") - end - if math_entropy - print(f, ", mathEntr_max") + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + print(f, ", " * string(variable) * "_" * string(min_or_max)) + end end if positivity for v in limiter.positivity_variables_cons - if v in limiter.local_minmax_variables_cons + if v in limiter.local_twosided_variables_cons continue end print(f, ", " * string(variables[v]) * "_min") @@ -158,15 +157,15 @@ end @inline function finalize_callback(callback::BoundsCheckCallback, semi, limiter::SubcellLimiterIDP) - (; local_minmax, positivity, spec_entropy, math_entropy) = limiter + (; local_twosided, positivity, local_onesided) = limiter (; idp_bounds_delta_global) = limiter.cache variables = varnames(cons2cons, semi.equations) println("─"^100) println("Maximum deviation from bounds:") println("─"^100) - if local_minmax - for v in limiter.local_minmax_variables_cons + if local_twosided + for v in limiter.local_twosided_variables_cons v_string = string(v) println("$(variables[v]):") println("- lower bound: ", @@ -175,17 +174,19 @@ end idp_bounds_delta_global[Symbol(v_string, "_max")]) end end - if spec_entropy - println("spec. entropy:\n- lower bound: ", - idp_bounds_delta_global[:spec_entropy_min]) - end - if math_entropy - println("math. entropy:\n- upper bound: ", - idp_bounds_delta_global[:math_entropy_max]) + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + variable_string = string(variable) + minmax_string = string(min_or_max) + println("$variable_string:") + println("- $minmax_string bound: ", + idp_bounds_delta_global[Symbol(variable_string, "_", + minmax_string)]) + end end if positivity for v in limiter.positivity_variables_cons - if v in limiter.local_minmax_variables_cons + if v in limiter.local_twosided_variables_cons continue end println(string(variables[v]) * ":\n- positivity: ", diff --git a/src/callbacks_stage/subcell_bounds_check_2d.jl b/src/callbacks_stage/subcell_bounds_check_2d.jl index 4a15a326433..99060d9accf 100644 --- a/src/callbacks_stage/subcell_bounds_check_2d.jl +++ b/src/callbacks_stage/subcell_bounds_check_2d.jl @@ -7,7 +7,7 @@ @inline function check_bounds(u, mesh::AbstractMesh{2}, equations, solver, cache, limiter::SubcellLimiterIDP) - (; local_minmax, positivity, spec_entropy, math_entropy) = solver.volume_integral.limiter + (; local_twosided, positivity, local_onesided) = solver.volume_integral.limiter (; variable_bounds) = limiter.cache.subcell_limiter_coefficients (; idp_bounds_delta_local, idp_bounds_delta_global) = limiter.cache @@ -19,8 +19,8 @@ # `@batch` here to allow a possible redefinition of `@threaded` without creating errors here. # See also https://github.com/trixi-framework/Trixi.jl/pull/1888#discussion_r1537785293. - if local_minmax - for v in limiter.local_minmax_variables_cons + if local_twosided + for v in limiter.local_twosided_variables_cons v_string = string(v) key_min = Symbol(v_string, "_min") key_max = Symbol(v_string, "_max") @@ -44,35 +44,28 @@ idp_bounds_delta_local[key_max] = deviation_max end end - if spec_entropy - key = :spec_entropy_min - deviation = idp_bounds_delta_local[key] - @batch reduction=(max, deviation) for element in eachelement(solver, cache) - for j in eachnode(solver), i in eachnode(solver) - s = entropy_spec(get_node_vars(u, equations, solver, i, j, element), - equations) - deviation = max(deviation, variable_bounds[key][i, j, element] - s) - end - idp_bounds_delta_local[key_min] = deviation_min - idp_bounds_delta_local[key_max] = deviation_max - end - idp_bounds_delta_local[key] = deviation - end - if math_entropy - key = :math_entropy_max - deviation = idp_bounds_delta_local[key] - @batch reduction=(max, deviation) for element in eachelement(solver, cache) - for j in eachnode(solver), i in eachnode(solver) - s = entropy_math(get_node_vars(u, equations, solver, i, j, element), + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + key = Symbol(string(variable), "_", string(min_or_max)) + deviation = idp_bounds_delta_local[key] + sign_ = min_or_max(1.0, -1.0) + @batch reduction=(max, deviation) for element in eachelement(solver, cache) + for j in eachnode(solver), i in eachnode(solver) + v = variable(get_node_vars(u, equations, solver, i, j, element), equations) - deviation = max(deviation, s - variable_bounds[key][i, j, element]) + # Note: We always save the absolute deviations >= 0 and therefore use the + # `max` operator for lower and upper bounds. The different directions of + # upper and lower bounds are considered with `sign_`. + deviation = max(deviation, + sign_ * (v - variable_bounds[key][i, j, element])) + end end + idp_bounds_delta_local[key] = deviation end - idp_bounds_delta_local[key] = deviation end if positivity for v in limiter.positivity_variables_cons - if v in limiter.local_minmax_variables_cons + if v in limiter.local_twosided_variables_cons continue end key = Symbol(string(v), "_min") @@ -112,28 +105,29 @@ end @inline function save_bounds_check_errors(output_directory, u, time, iter, equations, limiter::SubcellLimiterIDP) - (; local_minmax, positivity, spec_entropy, math_entropy) = limiter + (; local_twosided, positivity, local_onesided) = limiter (; idp_bounds_delta_local) = limiter.cache - # Print errors to output file + # Print to output file open("$output_directory/deviations.txt", "a") do f print(f, iter, ", ", time) - if local_minmax - for v in limiter.local_minmax_variables_cons + if local_twosided + for v in limiter.local_twosided_variables_cons v_string = string(v) print(f, ", ", idp_bounds_delta_local[Symbol(v_string, "_min")], ", ", idp_bounds_delta_local[Symbol(v_string, "_max")]) end end - if spec_entropy - print(f, ", ", idp_bounds_delta_local[:spec_entropy_min]) - end - if math_entropy - print(f, ", ", idp_bounds_delta_local[:math_entropy_max]) + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + key = Symbol(string(variable), "_", string(min_or_max)) + print(f, ", ", + idp_bounds_delta_local[key]) + end end if positivity for v in limiter.positivity_variables_cons - if v in limiter.local_minmax_variables_cons + if v in limiter.local_twosided_variables_cons continue end print(f, ", ", idp_bounds_delta_local[Symbol(string(v), "_min")]) diff --git a/src/callbacks_step/amr.jl b/src/callbacks_step/amr.jl index 1ab65a3553e..45f03fba8fe 100644 --- a/src/callbacks_step/amr.jl +++ b/src/callbacks_step/amr.jl @@ -243,7 +243,7 @@ function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::TreeMesh, @unpack to_refine, to_coarsen = amr_callback.amr_cache empty!(to_refine) empty!(to_coarsen) - for element in 1:length(lambda) + for element in eachindex(lambda) controller_value = lambda[element] if controller_value > 0 push!(to_refine, leaf_cell_ids[element]) @@ -307,7 +307,7 @@ function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::TreeMesh, end # Extract only those parent cells for which all children should be coarsened - to_coarsen = collect(1:length(parents_to_coarsen))[parents_to_coarsen .== 2^ndims(mesh)] + to_coarsen = collect(eachindex(parents_to_coarsen))[parents_to_coarsen .== 2^ndims(mesh)] # Finally, coarsen mesh coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!(mesh.tree, @@ -395,7 +395,7 @@ function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::TreeMesh, @unpack to_refine, to_coarsen = amr_callback.amr_cache empty!(to_refine) empty!(to_coarsen) - for element in 1:length(lambda) + for element in eachindex(lambda) controller_value = lambda[element] if controller_value > 0 push!(to_refine, leaf_cell_ids[element]) @@ -456,7 +456,7 @@ function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::TreeMesh, end # Extract only those parent cells for which all children should be coarsened - to_coarsen = collect(1:length(parents_to_coarsen))[parents_to_coarsen .== 2^ndims(mesh)] + to_coarsen = collect(eachindex(parents_to_coarsen))[parents_to_coarsen .== 2^ndims(mesh)] # Finally, coarsen mesh coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!(mesh.tree, diff --git a/src/callbacks_step/analysis.jl b/src/callbacks_step/analysis.jl index ba232032951..8f89af755a2 100644 --- a/src/callbacks_step/analysis.jl +++ b/src/callbacks_step/analysis.jl @@ -9,11 +9,11 @@ # - analysis_interval part as PeriodicCallback called after a certain amount of simulation time """ AnalysisCallback(semi; interval=0, - save_analysis=false, - output_directory="out", - analysis_filename="analysis.dat", - extra_analysis_errors=Symbol[], - extra_analysis_integrals=()) + save_analysis=false, + output_directory="out", + analysis_filename="analysis.dat", + extra_analysis_errors=Symbol[], + extra_analysis_integrals=()) Analyze a numerical solution every `interval` time steps and print the results to the screen. If `save_analysis`, the results are also saved in @@ -634,9 +634,7 @@ pretty_form_utf(quantity) = get_name(quantity) pretty_form_ascii(quantity) = get_name(quantity) # Special analyze for `SemidiscretizationHyperbolicParabolic` such that -# precomputed gradients are available. For now only implemented for the `enstrophy` -#!!! warning "Experimental code" -# This code is experimental and may be changed or removed in any future release. +# precomputed gradients are available. function analyze(quantity::typeof(enstrophy), du, u, t, semi::SemidiscretizationHyperbolicParabolic) mesh, equations, solver, cache = mesh_equations_solver_cache(semi) @@ -691,6 +689,23 @@ end # @muladd # specialized implementations specific to some solvers include("analysis_dg1d.jl") include("analysis_dg2d.jl") +include("analysis_surface_integral_2d.jl") include("analysis_dg2d_parallel.jl") include("analysis_dg3d.jl") include("analysis_dg3d_parallel.jl") + +# Special analyze for `SemidiscretizationHyperbolicParabolic` such that +# precomputed gradients are available. Required for `enstrophy` (see above) and viscous forces. +# Note that this needs to be included after `analysis_surface_integral_2d.jl` to +# have `VariableViscous` available. +function analyze(quantity::AnalysisSurfaceIntegral{Variable}, + du, u, t, + semi::SemidiscretizationHyperbolicParabolic) where { + Variable <: + VariableViscous} + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + equations_parabolic = semi.equations_parabolic + cache_parabolic = semi.cache_parabolic + analyze(quantity, du, u, t, mesh, equations, equations_parabolic, solver, cache, + cache_parabolic) +end diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index a9e0cf87b0a..de6b9a2a4a6 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -30,7 +30,8 @@ function create_cache_analysis(analyzer, mesh::TreeMesh{2}, end function create_cache_analysis(analyzer, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, equations, dg::DG, cache, RealT, uEltype) @@ -107,8 +108,9 @@ function calc_error_norms(func, u, t, analyzer, end function calc_error_norms(func, u, t, analyzer, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, equations, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, + equations, initial_condition, dg::DGSEM, cache, cache_analysis) @unpack vandermonde, weights = analyzer @unpack node_coordinates, inverse_jacobian = cache.elements @@ -175,8 +177,10 @@ function integrate_via_indices(func::Func, u, end function integrate_via_indices(func::Func, u, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, equations, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, + equations, dg::DGSEM, cache, args...; normalize = true) where {Func} @unpack weights = dg.basis @@ -203,8 +207,8 @@ function integrate_via_indices(func::Func, u, end function integrate(func::Func, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, + mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, equations, dg::DG, cache; normalize = true) where {Func} integrate_via_indices(u, mesh, equations, dg, cache; normalize = normalize) do u, i, j, element, equations, dg @@ -232,8 +236,8 @@ function integrate(func::Func, u, end function analyze(::typeof(entropy_timederivative), du, u, t, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, + mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, equations, dg::DG, cache) # Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ integrate_via_indices(u, mesh, equations, dg, cache, diff --git a/src/callbacks_step/analysis_surface_integral_2d.jl b/src/callbacks_step/analysis_surface_integral_2d.jl new file mode 100644 index 00000000000..7ae259e5285 --- /dev/null +++ b/src/callbacks_step/analysis_surface_integral_2d.jl @@ -0,0 +1,400 @@ +# 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 + +# This file contains callbacks that are performed on the surface like computation of +# surface forces + +""" + AnalysisSurfaceIntegral{Semidiscretization, Variable}(semi, + boundary_symbol_or_boundary_symbols, + variable) + +This struct is used to compute the surface integral of a quantity of interest `variable` alongside +the boundary/boundaries associated with particular name(s) given in `boundary_symbol` +or `boundary_symbols`. +For instance, this can be used to compute the lift [`LiftCoefficientPressure`](@ref) or +drag coefficient [`DragCoefficientPressure`](@ref) of e.g. an airfoil with the boundary +name `:Airfoil` in 2D. + +- `semi::Semidiscretization`: Passed in to retrieve boundary condition information +- `boundary_symbol_or_boundary_symbols::Symbol|Vector{Symbol}`: Name(s) of the boundary/boundaries + where the quantity of interest is computed +- `variable::Variable`: Quantity of interest, like lift or drag +""" +struct AnalysisSurfaceIntegral{Variable} + indices::Vector{Int} # Indices in `boundary_condition_indices` where quantity of interest is computed + variable::Variable # Quantity of interest, like lift or drag + + function AnalysisSurfaceIntegral(semi, boundary_symbol, variable) + @unpack boundary_symbol_indices = semi.boundary_conditions + indices = boundary_symbol_indices[boundary_symbol] + + return new{typeof(variable)}(indices, variable) + end + + function AnalysisSurfaceIntegral(semi, boundary_symbols::Vector{Symbol}, variable) + @unpack boundary_symbol_indices = semi.boundary_conditions + indices = Vector{Int}() + for name in boundary_symbols + append!(indices, boundary_symbol_indices[name]) + end + sort!(indices) + + return new{typeof(variable)}(indices, variable) + end +end + +struct ForceState{RealT <: Real} + psi::Tuple{RealT, RealT} # Unit vector normal or parallel to freestream + rhoinf::RealT + uinf::RealT + linf::RealT +end + +struct LiftCoefficientPressure{RealT <: Real} + force_state::ForceState{RealT} +end + +struct DragCoefficientPressure{RealT <: Real} + force_state::ForceState{RealT} +end + +# Abstract base type used for dispatch of `analyze` for quantities +# requiring gradients of the velocity field. +abstract type VariableViscous end + +struct LiftCoefficientShearStress{RealT <: Real} <: VariableViscous + force_state::ForceState{RealT} +end + +struct DragCoefficientShearStress{RealT <: Real} <: VariableViscous + force_state::ForceState{RealT} +end + +""" + LiftCoefficientPressure(aoa, rhoinf, uinf, linf) + +Compute the lift coefficient +```math +C_{L,p} \\coloneqq \\frac{\\oint_{\\partial \\Omega} p \\boldsymbol n \\cdot \\psi_L \\, \\mathrm{d} S} + {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} +``` +based on the pressure distribution along a boundary. +Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) +which stores the boundary information and semidiscretization. + +- `aoa::Real`: Angle of attack in radians (for airfoils etc.) +- `rhoinf::Real`: Free-stream density +- `uinf::Real`: Free-stream velocity +- `linf::Real`: Reference length of geometry (e.g. airfoil chord length) +""" +function LiftCoefficientPressure(aoa, rhoinf, uinf, linf) + # psi_lift is the normal unit vector to the freestream direction. + # Note: The choice of the normal vector psi_lift = (-sin(aoa), cos(aoa)) + # leads to positive lift coefficients for positive angles of attack for airfoils. + # One could also use psi_lift = (sin(aoa), -cos(aoa)) which results in the same + # value, but with the opposite sign. + psi_lift = (-sin(aoa), cos(aoa)) + return LiftCoefficientPressure(ForceState(psi_lift, rhoinf, uinf, linf)) +end + +""" + DragCoefficientPressure(aoa, rhoinf, uinf, linf) + +Compute the drag coefficient +```math +C_{D,p} \\coloneqq \\frac{\\oint_{\\partial \\Omega} p \\boldsymbol n \\cdot \\psi_D \\, \\mathrm{d} S} + {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} +``` +based on the pressure distribution along a boundary. +Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) +which stores the boundary information and semidiscretization. + +- `aoa::Real`: Angle of attack in radians (for airfoils etc.) +- `rhoinf::Real`: Free-stream density +- `uinf::Real`: Free-stream velocity +- `linf::Real`: Reference length of geometry (e.g. airfoil chord length) +""" +function DragCoefficientPressure(aoa, rhoinf, uinf, linf) + # `psi_drag` is the unit vector tangent to the freestream direction + psi_drag = (cos(aoa), sin(aoa)) + return DragCoefficientPressure(ForceState(psi_drag, rhoinf, uinf, linf)) +end + +""" + LiftCoefficientShearStress(aoa, rhoinf, uinf, linf) + +Compute the lift coefficient +```math +C_{L,f} \\coloneqq \\frac{\\oint_{\\partial \\Omega} \\boldsymbol \\tau_w \\cdot \\psi_L \\, \\mathrm{d} S} + {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} +``` +based on the wall shear stress vector ``\\tau_w`` along a boundary. +Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) +which stores the boundary information and semidiscretization. + +- `aoa::Real`: Angle of attack in radians (for airfoils etc.) +- `rhoinf::Real`: Free-stream density +- `uinf::Real`: Free-stream velocity +- `linf::Real`: Reference length of geometry (e.g. airfoil chord length) +""" +function LiftCoefficientShearStress(aoa, rhoinf, uinf, linf) + # psi_lift is the normal unit vector to the freestream direction. + # Note: The choice of the normal vector psi_lift = (-sin(aoa), cos(aoa)) + # leads to negative lift coefficients for airfoils. + # One could also use psi_lift = (sin(aoa), -cos(aoa)) which results in the same + # value, but with the opposite sign. + psi_lift = (-sin(aoa), cos(aoa)) + return LiftCoefficientShearStress(ForceState(psi_lift, rhoinf, uinf, linf)) +end + +""" + DragCoefficientShearStress(aoa, rhoinf, uinf, linf) + +Compute the drag coefficient +```math +C_{D,f} \\coloneqq \\frac{\\oint_{\\partial \\Omega} \\boldsymbol \\tau_w \\cdot \\psi_D \\, \\mathrm{d} S} + {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} +``` +based on the wall shear stress vector ``\\tau_w`` along a boundary. +Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) +which stores the boundary information and semidiscretization. + +- `aoa::Real`: Angle of attack in radians (for airfoils etc.) +- `rhoinf::Real`: Free-stream density +- `uinf::Real`: Free-stream velocity +- `linf::Real`: Reference length of geometry (e.g. airfoil chord length) +""" +function DragCoefficientShearStress(aoa, rhoinf, uinf, linf) + # `psi_drag` is the unit vector tangent to the freestream direction + psi_drag = (cos(aoa), sin(aoa)) + return DragCoefficientShearStress(ForceState(psi_drag, rhoinf, uinf, linf)) +end + +function (lift_coefficient::LiftCoefficientPressure)(u, normal_direction, x, t, + equations) + p = pressure(u, equations) + @unpack psi, rhoinf, uinf, linf = lift_coefficient.force_state + # Normalize as `normal_direction` is not necessarily a unit vector + n = dot(normal_direction, psi) / norm(normal_direction) + return p * n / (0.5 * rhoinf * uinf^2 * linf) +end + +function (drag_coefficient::DragCoefficientPressure)(u, normal_direction, x, t, + equations) + p = pressure(u, equations) + @unpack psi, rhoinf, uinf, linf = drag_coefficient.force_state + # Normalize as `normal_direction` is not necessarily a unit vector + n = dot(normal_direction, psi) / norm(normal_direction) + return p * n / (0.5 * rhoinf * uinf^2 * linf) +end + +# Compute the three components of the symmetric viscous stress tensor +# (tau_11, tau_12, tau_22) based on the gradients of the velocity field. +# This is required for drag and lift coefficients based on shear stress, +# as well as for the non-integrated quantities such as +# skin friction coefficient (to be added). +function viscous_stress_tensor(u, normal_direction, equations_parabolic, + gradients_1, gradients_2) + _, dv1dx, dv2dx, _ = convert_derivative_to_primitive(u, gradients_1, + equations_parabolic) + _, dv1dy, dv2dy, _ = convert_derivative_to_primitive(u, gradients_2, + equations_parabolic) + + # Components of viscous stress tensor + # (4/3 * (v1)_x - 2/3 * (v2)_y) + tau_11 = 4.0 / 3.0 * dv1dx - 2.0 / 3.0 * dv2dy + # ((v1)_y + (v2)_x) + # stress tensor is symmetric + tau_12 = dv1dy + dv2dx # = tau_21 + # (4/3 * (v2)_y - 2/3 * (v1)_x) + tau_22 = 4.0 / 3.0 * dv2dy - 2.0 / 3.0 * dv1dx + + mu = dynamic_viscosity(u, equations_parabolic) + + return mu .* (tau_11, tau_12, tau_22) +end + +function viscous_stress_vector(u, normal_direction, equations_parabolic, + gradients_1, gradients_2) + # Normalize normal direction, should point *into* the fluid => *(-1) + n_normal = -normal_direction / norm(normal_direction) + + tau_11, tau_12, tau_22 = viscous_stress_tensor(u, normal_direction, + equations_parabolic, + gradients_1, gradients_2) + + # Viscous stress vector: Stress tensor * normal vector + visc_stress_vector_1 = tau_11 * n_normal[1] + tau_12 * n_normal[2] + visc_stress_vector_2 = tau_12 * n_normal[1] + tau_22 * n_normal[2] + + return (visc_stress_vector_1, visc_stress_vector_2) +end + +function (lift_coefficient::LiftCoefficientShearStress)(u, normal_direction, x, t, + equations_parabolic, + gradients_1, gradients_2) + visc_stress_vector = viscous_stress_vector(u, normal_direction, equations_parabolic, + gradients_1, gradients_2) + @unpack psi, rhoinf, uinf, linf = lift_coefficient.force_state + return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) / + (0.5 * rhoinf * uinf^2 * linf) +end + +function (drag_coefficient::DragCoefficientShearStress)(u, normal_direction, x, t, + equations_parabolic, + gradients_1, gradients_2) + visc_stress_vector = viscous_stress_vector(u, normal_direction, equations_parabolic, + gradients_1, gradients_2) + @unpack psi, rhoinf, uinf, linf = drag_coefficient.force_state + return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) / + (0.5 * rhoinf * uinf^2 * linf) +end + +function analyze(surface_variable::AnalysisSurfaceIntegral, du, u, t, + mesh::P4estMesh{2}, + equations, dg::DGSEM, cache) + @unpack boundaries = cache + @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements + @unpack weights = dg.basis + + @unpack indices, variable = surface_variable + + surface_integral = zero(eltype(u)) + index_range = eachnode(dg) + for boundary in indices + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) + j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) + + i_node = i_node_start + j_node = j_node_start + for node_index in index_range + u_node = Trixi.get_node_vars(cache.boundaries.u, equations, dg, node_index, + boundary) + # Extract normal direction at nodes which points from the elements outwards, + # i.e., *into* the structure. + normal_direction = get_normal_direction(direction, contravariant_vectors, + i_node, j_node, + element) + + # Coordinates at a boundary node + x = get_node_coords(node_coordinates, equations, dg, i_node, j_node, + element) + + # L2 norm of normal direction (contravariant_vector) is the surface element + dS = weights[node_index] * norm(normal_direction) + + # Integral over entire boundary surface. Note, it is assumed that the + # `normal_direction` is normalized to be a normal vector within the + # function `variable` and the division of the normal scaling factor + # `norm(normal_direction)` is then accounted for with the `dS` quantity. + surface_integral += variable(u_node, normal_direction, x, t, equations) * dS + + i_node += i_node_step + j_node += j_node_step + end + end + return surface_integral +end + +function analyze(surface_variable::AnalysisSurfaceIntegral{Variable}, + du, u, t, mesh::P4estMesh{2}, + equations, equations_parabolic, + dg::DGSEM, cache, + cache_parabolic) where {Variable <: VariableViscous} + @unpack boundaries = cache + @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements + @unpack weights = dg.basis + + @unpack indices, variable = surface_variable + + # Additions for parabolic + @unpack viscous_container = cache_parabolic + @unpack gradients = viscous_container + + gradients_x, gradients_y = gradients + + surface_integral = zero(eltype(u)) + index_range = eachnode(dg) + for boundary in indices + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) + j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) + + i_node = i_node_start + j_node = j_node_start + for node_index in index_range + u_node = Trixi.get_node_vars(cache.boundaries.u, equations, dg, node_index, + boundary) + # Extract normal direction at nodes which points from the elements outwards, + # i.e., *into* the structure. + normal_direction = get_normal_direction(direction, contravariant_vectors, + i_node, j_node, + element) + + # Coordinates at a boundary node + x = get_node_coords(node_coordinates, equations, dg, i_node, j_node, + element) + + # L2 norm of normal direction (contravariant_vector) is the surface element + dS = weights[node_index] * norm(normal_direction) + + gradients_1 = get_node_vars(gradients_x, equations_parabolic, dg, i_node, + j_node, element) + gradients_2 = get_node_vars(gradients_y, equations_parabolic, dg, i_node, + j_node, element) + + # Integral over whole boundary surface. Note, it is assumed that the + # `normal_direction` is normalized to be a normal vector within the + # function `variable` and the division of the normal scaling factor + # `norm(normal_direction)` is then accounted for with the `dS` quantity. + surface_integral += variable(u_node, normal_direction, x, t, + equations_parabolic, + gradients_1, gradients_2) * dS + + i_node += i_node_step + j_node += j_node_step + end + end + return surface_integral +end + +function pretty_form_ascii(::AnalysisSurfaceIntegral{<:LiftCoefficientPressure{<:Any}}) + "CL_p" +end +function pretty_form_utf(::AnalysisSurfaceIntegral{<:LiftCoefficientPressure{<:Any}}) + "CL_p" +end + +function pretty_form_ascii(::AnalysisSurfaceIntegral{<:DragCoefficientPressure{<:Any}}) + "CD_p" +end +function pretty_form_utf(::AnalysisSurfaceIntegral{<:DragCoefficientPressure{<:Any}}) + "CD_p" +end + +function pretty_form_ascii(::AnalysisSurfaceIntegral{<:LiftCoefficientShearStress{<:Any}}) + "CL_f" +end +function pretty_form_utf(::AnalysisSurfaceIntegral{<:LiftCoefficientShearStress{<:Any}}) + "CL_f" +end + +function pretty_form_ascii(::AnalysisSurfaceIntegral{<:DragCoefficientShearStress{<:Any}}) + "CD_f" +end +function pretty_form_utf(::AnalysisSurfaceIntegral{<:DragCoefficientShearStress{<:Any}}) + "CD_f" +end +end # muladd diff --git a/src/callbacks_step/euler_acoustics_coupling_dg2d.jl b/src/callbacks_step/euler_acoustics_coupling_dg2d.jl index 16fac4f2d8d..8a8bb893dcd 100644 --- a/src/callbacks_step/euler_acoustics_coupling_dg2d.jl +++ b/src/callbacks_step/euler_acoustics_coupling_dg2d.jl @@ -12,7 +12,7 @@ function calc_acoustic_sources!(acoustic_source_terms, u_euler, u_acoustics, dg::DGSEM, cache) acoustic_source_terms .= zero(eltype(acoustic_source_terms)) - @threaded for k in 1:length(coupled_element_ids) + @threaded for k in eachindex(coupled_element_ids) element = coupled_element_ids[k] for j in eachnode(dg), i in eachnode(dg) diff --git a/src/callbacks_step/glm_speed.jl b/src/callbacks_step/glm_speed.jl index 036f61a522b..8ee406af5f9 100644 --- a/src/callbacks_step/glm_speed.jl +++ b/src/callbacks_step/glm_speed.jl @@ -6,7 +6,7 @@ #! format: noindent """ - GlmSpeedCallback(; glm_scale=0.5, cfl) + GlmSpeedCallback(; glm_scale=0.5, cfl, semi_indices=()) Update the divergence cleaning wave speed `c_h` according to the time step computed in [`StepsizeCallback`](@ref) for the ideal GLM-MHD equations. @@ -14,18 +14,26 @@ The `cfl` number should be set to the same value as for the time step size calcu `glm_scale` ensures that the GLM wave speed is lower than the fastest physical waves in the MHD solution and should thus be set to a value within the interval [0,1]. Note that `glm_scale = 0` deactivates the divergence cleaning. + +In case of coupled semidiscretizations, specify for which `semi_index`, i.e. index of the +semidiscretization, the divergence cleaning should be applied. See also +[`SemidiscretizationCoupled`](@ref). +Note: `SemidiscretizationCoupled` and all related features are considered experimental and +may change at any time. """ struct GlmSpeedCallback{RealT <: Real} glm_scale::RealT cfl::RealT + semi_indices::Vector{Int} end function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:GlmSpeedCallback}) @nospecialize cb # reduce precompilation time glm_speed_callback = cb.affect! - @unpack glm_scale, cfl = glm_speed_callback - print(io, "GlmSpeedCallback(glm_scale=", glm_scale, ", cfl=", cfl, ")") + @unpack glm_scale, cfl, semi_indices = glm_speed_callback + print(io, "GlmSpeedCallback(glm_scale=", glm_scale, ", cfl=", cfl, "semi_indices=", + semi_indices, ")") end function Base.show(io::IO, ::MIME"text/plain", @@ -40,15 +48,16 @@ function Base.show(io::IO, ::MIME"text/plain", setup = [ "GLM wave speed scaling" => glm_speed_callback.glm_scale, "Expected CFL number" => glm_speed_callback.cfl, + "Selected semidiscretizations" => glm_speed_callback.semi_indices, ] summary_box(io, "GlmSpeedCallback", setup) end end -function GlmSpeedCallback(; glm_scale = 0.5, cfl) +function GlmSpeedCallback(; glm_scale = 0.5, cfl, semi_indices = Int[]) @assert 0<=glm_scale<=1 "glm_scale must be between 0 and 1" - glm_speed_callback = GlmSpeedCallback(glm_scale, cfl) + glm_speed_callback = GlmSpeedCallback(glm_scale, cfl, semi_indices) DiscreteCallback(glm_speed_callback, glm_speed_callback, # the first one is the condition, the second the affect! save_positions = (false, false), @@ -65,19 +74,29 @@ function (glm_speed_callback::GlmSpeedCallback)(u, t, integrator) return true end -# This method is called as callback after the StepsizeCallback during the time integration. -@inline function (glm_speed_callback::GlmSpeedCallback)(integrator) - dt = get_proposed_dt(integrator) - semi = integrator.p - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) +function update_cleaning_speed!(semi, glm_speed_callback, dt) @unpack glm_scale, cfl = glm_speed_callback + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + # compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR) c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache) # c_h is proportional to its own time step divided by the complete MHD time step equations.c_h = glm_scale * c_h_deltat / dt + return semi +end + +# This method is called as callback after the StepsizeCallback during the time integration. +@inline function (glm_speed_callback::GlmSpeedCallback)(integrator) + dt = get_proposed_dt(integrator) + semi = integrator.p + + # Call the appropriate update function (this indirection allows to specialize on, + # e.g., the semidiscretization type) + update_cleaning_speed!(semi, glm_speed_callback, dt) + # avoid re-evaluating possible FSAL stages u_modified!(integrator, false) diff --git a/src/callbacks_step/save_solution_dg.jl b/src/callbacks_step/save_solution_dg.jl index 350aee7336a..7367886ca94 100644 --- a/src/callbacks_step/save_solution_dg.jl +++ b/src/callbacks_step/save_solution_dg.jl @@ -7,6 +7,7 @@ function save_solution_file(u, time, dt, timestep, mesh::Union{SerialTreeMesh, StructuredMesh, + StructuredMeshView, UnstructuredMesh2D, SerialP4estMesh, SerialT8codeMesh}, equations, dg::DG, cache, diff --git a/src/callbacks_step/stepsize_dg2d.jl b/src/callbacks_step/stepsize_dg2d.jl index 8779480cc17..709f3cee13f 100644 --- a/src/callbacks_step/stepsize_dg2d.jl +++ b/src/callbacks_step/stepsize_dg2d.jl @@ -179,7 +179,7 @@ end function max_dt(u, t, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, + T8codeMesh{2}, StructuredMeshView{2}}, constant_speed::False, equations, dg::DG, cache) # to avoid a division by zero if the speed vanishes everywhere, # e.g. for steady-state linear advection @@ -215,7 +215,7 @@ end function max_dt(u, t, mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, + T8codeMesh{2}, StructuredMeshView{2}}, constant_speed::True, equations, dg::DG, cache) @unpack contravariant_vectors, inverse_jacobian = cache.elements diff --git a/src/callbacks_step/time_series_dg.jl b/src/callbacks_step/time_series_dg.jl index 3781a10662d..5ba072bf560 100644 --- a/src/callbacks_step/time_series_dg.jl +++ b/src/callbacks_step/time_series_dg.jl @@ -9,9 +9,9 @@ function save_time_series_file(time_series_callback, mesh::Union{TreeMesh, UnstructuredMesh2D}, equations, dg::DG) - @unpack (interval, solution_variables, variable_names, + @unpack (interval, variable_names, output_directory, filename, point_coordinates, - point_data, time, step, time_series_cache) = time_series_callback + point_data, time, step) = time_series_callback n_points = length(point_data) h5open(joinpath(output_directory, filename), "w") do file diff --git a/src/callbacks_step/time_series_dg_tree.jl b/src/callbacks_step/time_series_dg_tree.jl index 37d4e6ea705..0af1688a8ed 100644 --- a/src/callbacks_step/time_series_dg_tree.jl +++ b/src/callbacks_step/time_series_dg_tree.jl @@ -25,7 +25,7 @@ function get_elements_by_coordinates!(element_ids, coordinates, mesh::TreeMesh, cell_id = cell_ids[element] # Iterate over coordinates - for index in 1:length(element_ids) + for index in eachindex(element_ids) # Skip coordinates for which an element has already been found if element_ids[index] > 0 continue @@ -63,7 +63,7 @@ function calc_interpolating_polynomials!(interpolating_polynomials, coordinates, wbary = barycentric_weights(nodes) - for index in 1:length(element_ids) + for index in eachindex(element_ids) # Construct point x = SVector(ntuple(i -> coordinates[i, index], ndims(mesh))) @@ -94,7 +94,7 @@ function record_state_at_points!(point_data, u, solution_variables, new_length = old_length + n_solution_variables # Loop over all points/elements that should be recorded - for index in 1:length(element_ids) + for index in eachindex(element_ids) # Extract data array and element id data = point_data[index] element_id = element_ids[index] @@ -108,7 +108,7 @@ function record_state_at_points!(point_data, u, solution_variables, u_node = solution_variables(get_node_vars(u, equations, dg, i, element_id), equations) - for v in 1:length(u_node) + for v in eachindex(u_node) data[old_length + v] += (u_node[v] * interpolating_polynomials[i, 1, index]) end @@ -126,7 +126,7 @@ function record_state_at_points!(point_data, u, solution_variables, new_length = old_length + n_solution_variables # Loop over all points/elements that should be recorded - for index in 1:length(element_ids) + for index in eachindex(element_ids) # Extract data array and element id data = point_data[index] element_id = element_ids[index] @@ -140,7 +140,7 @@ function record_state_at_points!(point_data, u, solution_variables, u_node = solution_variables(get_node_vars(u, equations, dg, i, j, element_id), equations) - for v in 1:length(u_node) + for v in eachindex(u_node) data[old_length + v] += (u_node[v] * interpolating_polynomials[i, 1, index] * interpolating_polynomials[j, 2, index]) @@ -159,7 +159,7 @@ function record_state_at_points!(point_data, u, solution_variables, new_length = old_length + n_solution_variables # Loop over all points/elements that should be recorded - for index in 1:length(element_ids) + for index in eachindex(element_ids) # Extract data array and element id data = point_data[index] element_id = element_ids[index] @@ -173,7 +173,7 @@ function record_state_at_points!(point_data, u, solution_variables, u_node = solution_variables(get_node_vars(u, equations, dg, i, j, k, element_id), equations) - for v in 1:length(u_node) + for v in eachindex(u_node) data[old_length + v] += (u_node[v] * interpolating_polynomials[i, 1, index] * interpolating_polynomials[j, 2, index] diff --git a/src/callbacks_step/time_series_dg_unstructured.jl b/src/callbacks_step/time_series_dg_unstructured.jl index f6d1bb48f24..85427f1273a 100644 --- a/src/callbacks_step/time_series_dg_unstructured.jl +++ b/src/callbacks_step/time_series_dg_unstructured.jl @@ -31,7 +31,7 @@ function get_elements_by_coordinates!(element_ids, coordinates, # Iterate over coordinates distances = zeros(eltype(mesh.corners), mesh.n_elements) indices = zeros(Int, mesh.n_elements, 2) - for index in 1:length(element_ids) + for index in eachindex(element_ids) # Grab the current point for which the element needs found point = SVector(coordinates[1, index], coordinates[2, index]) @@ -77,7 +77,7 @@ function get_elements_by_coordinates!(element_ids, coordinates, # Loop through all the element candidates until we find a vector from the barycenter # to the surface that points in the same direction as the current `point` vector. # This then gives us the correct element. - for element in 1:length(candidates) + for element in eachindex(candidates) bary_center = SVector(bary_centers[1, candidates[element]], bary_centers[2, candidates[element]]) # Vector pointing from the barycenter toward the minimal `surface_point` @@ -153,7 +153,7 @@ function calc_interpolating_polynomials!(interpolating_polynomials, coordinates, # Helper array for a straight-sided quadrilateral element corners = zeros(eltype(mesh.corners), 4, 2) - for index in 1:length(element_ids) + for index in eachindex(element_ids) # Construct point x = SVector(ntuple(i -> coordinates[i, index], ndims(mesh))) @@ -280,7 +280,7 @@ function record_state_at_points!(point_data, u, solution_variables, new_length = old_length + n_solution_variables # Loop over all points/elements that should be recorded - for index in 1:length(element_ids) + for index in eachindex(element_ids) # Extract data array and element id data = point_data[index] element_id = element_ids[index] @@ -294,7 +294,7 @@ function record_state_at_points!(point_data, u, solution_variables, u_node = solution_variables(get_node_vars(u, equations, dg, i, j, element_id), equations) - for v in 1:length(u_node) + for v in eachindex(u_node) data[old_length + v] += (u_node[v] * interpolating_polynomials[i, 1, index] * interpolating_polynomials[j, 2, index]) diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index e3a93de4376..721e5c1c2d6 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -678,7 +678,7 @@ end end """ - flux_chandrashekar(u_ll, u_rr, orientation, equations::CompressibleEulerEquations2D) + flux_chandrashekar(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations2D) Entropy conserving two-point flux by - Chandrashekar (2013) @@ -724,6 +724,38 @@ Entropy conserving two-point flux by return SVector(f1, f2, f3, f4) end +@inline function flux_chandrashekar(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + beta_ll = 0.5 * rho_ll / p_ll + beta_rr = 0.5 * rho_rr / p_rr + specific_kin_ll = 0.5 * (v1_ll^2 + v2_ll^2) + specific_kin_rr = 0.5 * (v1_rr^2 + v2_rr^2) + + # Compute the necessary mean values + rho_avg = 0.5 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5 * (beta_ll + beta_rr) + v1_avg = 0.5 * (v1_ll + v1_rr) + v2_avg = 0.5 * (v2_ll + v2_rr) + p_mean = 0.5 * rho_avg / beta_avg + velocity_square_avg = specific_kin_ll + specific_kin_rr + + # Multiply with average of normal velocities + f1 = rho_mean * 0.5 * (v_dot_n_ll + v_dot_n_rr) + f2 = f1 * v1_avg + p_mean * normal_direction[1] + f3 = f1 * v2_avg + p_mean * normal_direction[2] + f4 = f1 * 0.5 * (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + + return SVector(f1, f2, f3, f4) +end + """ flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations2D) @@ -1980,9 +2012,10 @@ end return SVector(w1, w2, w3, w4) end -# Transformation from conservative variables u to entropy vector dSdu, -# S = -rho*s/(gamma-1), s=ln(p)-gamma*ln(rho) -@inline function cons2entropy_spec(u, equations::CompressibleEulerEquations2D) +# Transformation from conservative variables u to entropy vector ds_0/du, +# using the modified specific entropy of Guermond et al. (2019): s_0 = p * rho^(-gamma) / (gamma-1). +# Note: This is *not* the "conventional" specific entropy s = ln(p / rho^(gamma)). +@inline function cons2entropy_guermond_etal(u, equations::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u v1 = rho_v1 / rho @@ -1997,13 +2030,6 @@ end w3 = -rho_v2 * inv_rho_gammap1 w4 = (1 / rho)^equations.gamma - # The derivative vector for other specific entropy - # sp = 1.0/(gammam1 * (rho_e - 0.5 * rho * v_square) - # w1 = gammam1 * 0.5 * v_square * sp - gamma / rho - # w2 = -gammam1 * v1 * sp - # w3 = -gammam1 * v2 * sp - # w4 = gammam1 * sp - return SVector(w1, w2, w3, w4) end @@ -2113,28 +2139,26 @@ end end # Transformation from conservative variables u to d(s)/d(u) -@inline function variable_derivative(::typeof(entropy_math), - u, equations::CompressibleEulerEquations2D) +@inline function gradient_conservative(::typeof(entropy_math), + u, equations::CompressibleEulerEquations2D) return cons2entropy(u, equations) end -# Calculate specific entropy for conservative variable u -@inline function entropy_spec(u, equations::CompressibleEulerEquations2D) +# Calculate the modified specific entropy of Guermond et al. (2019): s_0 = p * rho^(-gamma) / (gamma-1). +# Note: This is *not* the "conventional" specific entropy s = ln(p / rho^(gamma)). +@inline function entropy_guermond_etal(u, equations::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u # Modified specific entropy from Guermond et al. (2019) s = (rho_e - 0.5 * (rho_v1^2 + rho_v2^2) / rho) * (1 / rho)^equations.gamma - # Other specific entropy - # rho_sp = rho/((equations.gamma - 1.0) * (rho_e - 0.5 * rho * v_square)) - # s = log(p) - (equaions.gamma + 1) * log(rho) return s end # Transformation from conservative variables u to d(s)/d(u) -@inline function variable_derivative(::typeof(entropy_spec), - u, equations::CompressibleEulerEquations2D) - return cons2entropy_spec(u, equations) +@inline function gradient_conservative(::typeof(entropy_guermond_etal), + u, equations::CompressibleEulerEquations2D) + return cons2entropy_guermond_etal(u, equations) end # Default entropy is the mathematical entropy diff --git a/src/equations/compressible_euler_3d.jl b/src/equations/compressible_euler_3d.jl index 292b912f009..f156aa29689 100644 --- a/src/equations/compressible_euler_3d.jl +++ b/src/equations/compressible_euler_3d.jl @@ -602,7 +602,7 @@ end end """ - flux_chandrashekar(u_ll, u_rr, orientation, equations::CompressibleEulerEquations3D) + flux_chandrashekar(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations3D) Entropy conserving two-point flux by - Chandrashekar (2013) @@ -659,6 +659,44 @@ Entropy conserving two-point flux by return SVector(f1, f2, f3, f4, f5) end +@inline function flux_chandrashekar(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + beta_ll = 0.5 * rho_ll / p_ll + beta_rr = 0.5 * rho_rr / p_rr + specific_kin_ll = 0.5 * (v1_ll^2 + v2_ll^2 + v3_ll^2) + specific_kin_rr = 0.5 * (v1_rr^2 + v2_rr^2 + v3_rr^2) + + # Compute the necessary mean values + rho_avg = 0.5 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5 * (beta_ll + beta_rr) + v1_avg = 0.5 * (v1_ll + v1_rr) + v2_avg = 0.5 * (v2_ll + v2_rr) + v3_avg = 0.5 * (v3_ll + v3_rr) + p_mean = 0.5 * rho_avg / beta_avg + velocity_square_avg = specific_kin_ll + specific_kin_rr + + # Multiply with average of normal velocities + f1 = rho_mean * 0.5 * (v_dot_n_ll + v_dot_n_rr) + f2 = f1 * v1_avg + p_mean * normal_direction[1] + f3 = f1 * v2_avg + p_mean * normal_direction[2] + f4 = f1 * v3_avg + p_mean * normal_direction[3] + f5 = f1 * 0.5 * (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + f4 * v3_avg + + return SVector(f1, f2, f3, f4, f5) +end + """ flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations3D) diff --git a/src/equations/equations.jl b/src/equations/equations.jl index 98ec84a76b4..d1567bdbf5a 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -560,7 +560,9 @@ include("acoustic_perturbation_2d.jl") # Linearized Euler equations abstract type AbstractLinearizedEulerEquations{NDIMS, NVARS} <: AbstractEquations{NDIMS, NVARS} end +include("linearized_euler_1d.jl") include("linearized_euler_2d.jl") +include("linearized_euler_3d.jl") abstract type AbstractEquationsParabolic{NDIMS, NVARS, GradientVariables} <: AbstractEquations{NDIMS, NVARS} end diff --git a/src/equations/inviscid_burgers_1d.jl b/src/equations/inviscid_burgers_1d.jl index f2387f26ba7..130196a4929 100644 --- a/src/equations/inviscid_burgers_1d.jl +++ b/src/equations/inviscid_burgers_1d.jl @@ -168,6 +168,7 @@ end # Convert conservative variables to entropy variables @inline cons2entropy(u, equation::InviscidBurgersEquation1D) = u +@inline entropy2cons(u, equation::InviscidBurgersEquation1D) = u # Calculate entropy for a conservative state `cons` @inline entropy(u::Real, ::InviscidBurgersEquation1D) = 0.5 * u^2 diff --git a/src/equations/laplace_diffusion_1d.jl b/src/equations/laplace_diffusion_1d.jl index 815b9908c1e..64a72ef3a13 100644 --- a/src/equations/laplace_diffusion_1d.jl +++ b/src/equations/laplace_diffusion_1d.jl @@ -24,12 +24,13 @@ function flux(u, gradients, orientation::Integer, equations_parabolic::LaplaceDi return equations_parabolic.diffusivity * dudx end -# Dirichlet-type boundary condition for use with a parabolic solver in weak form +# Dirichlet and Neumann boundary conditions for use with parabolic solvers in weak form. +# Note that these are general, so they apply to LaplaceDiffusion in any spatial dimension. @inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, u_inner, normal::AbstractVector, x, t, operator_type::Gradient, - equations_parabolic::LaplaceDiffusion1D) + equations_parabolic::AbstractLaplaceDiffusion) return boundary_condition.boundary_value_function(x, t, equations_parabolic) end @@ -37,7 +38,7 @@ end normal::AbstractVector, x, t, operator_type::Divergence, - equations_parabolic::LaplaceDiffusion1D) + equations_parabolic::AbstractLaplaceDiffusion) return flux_inner end @@ -45,7 +46,7 @@ end normal::AbstractVector, x, t, operator_type::Divergence, - equations_parabolic::LaplaceDiffusion1D) + equations_parabolic::AbstractLaplaceDiffusion) return boundary_condition.boundary_normal_flux_function(x, t, equations_parabolic) end @@ -53,6 +54,6 @@ end normal::AbstractVector, x, t, operator_type::Gradient, - equations_parabolic::LaplaceDiffusion1D) + equations_parabolic::AbstractLaplaceDiffusion) return flux_inner end diff --git a/src/equations/laplace_diffusion_2d.jl b/src/equations/laplace_diffusion_2d.jl index b848633fbcb..5de989849b6 100644 --- a/src/equations/laplace_diffusion_2d.jl +++ b/src/equations/laplace_diffusion_2d.jl @@ -35,35 +35,4 @@ function penalty(u_outer, u_inner, inv_h, equations_parabolic::LaplaceDiffusion2 return dg.penalty_parameter * (u_outer - u_inner) * equations_parabolic.diffusivity end -# Dirichlet-type boundary condition for use with a parabolic solver in weak form -@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Gradient, - equations_parabolic::LaplaceDiffusion2D) - return boundary_condition.boundary_value_function(x, t, equations_parabolic) -end - -@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Divergence, - equations_parabolic::LaplaceDiffusion2D) - return flux_inner -end - -@inline function (boundary_condition::BoundaryConditionNeumann)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Divergence, - equations_parabolic::LaplaceDiffusion2D) - return boundary_condition.boundary_normal_flux_function(x, t, equations_parabolic) -end - -@inline function (boundary_condition::BoundaryConditionNeumann)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Gradient, - equations_parabolic::LaplaceDiffusion2D) - return flux_inner -end +# General Dirichlet and Neumann boundary condition functions are defined in `src/equations/laplace_diffusion_1d.jl`. diff --git a/src/equations/laplace_diffusion_3d.jl b/src/equations/laplace_diffusion_3d.jl index 3988ce7144b..fbd3d277257 100644 --- a/src/equations/laplace_diffusion_3d.jl +++ b/src/equations/laplace_diffusion_3d.jl @@ -38,35 +38,4 @@ function penalty(u_outer, u_inner, inv_h, equations_parabolic::LaplaceDiffusion3 return dg.penalty_parameter * (u_outer - u_inner) * equations_parabolic.diffusivity end -# Dirichlet-type boundary condition for use with a parabolic solver in weak form -@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Gradient, - equations_parabolic::LaplaceDiffusion3D) - return boundary_condition.boundary_value_function(x, t, equations_parabolic) -end - -@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Divergence, - equations_parabolic::LaplaceDiffusion3D) - return flux_inner -end - -@inline function (boundary_condition::BoundaryConditionNeumann)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Divergence, - equations_parabolic::LaplaceDiffusion3D) - return boundary_condition.boundary_normal_flux_function(x, t, equations_parabolic) -end - -@inline function (boundary_condition::BoundaryConditionNeumann)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Gradient, - equations_parabolic::LaplaceDiffusion3D) - return flux_inner -end +# General Dirichlet and Neumann boundary condition functions are defined in `src/equations/laplace_diffusion_1d.jl`. diff --git a/src/equations/linearized_euler_1d.jl b/src/equations/linearized_euler_1d.jl new file mode 100644 index 00000000000..19a3bdcb3bd --- /dev/null +++ b/src/equations/linearized_euler_1d.jl @@ -0,0 +1,144 @@ +# 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 + +@doc raw""" + LinearizedEulerEquations1D(v_mean_global, c_mean_global, rho_mean_global) + +Linearized Euler equations in one space dimension. The equations are given by +```math +\partial_t +\begin{pmatrix} + \rho' \\ v_1' \\ p' +\end{pmatrix} ++ +\partial_x +\begin{pmatrix} + \bar{\rho} v_1' + \bar{v_1} \rho ' \\ \bar{v_1} v_1' + \frac{p'}{\bar{\rho}} \\ \bar{v_1} p' + c^2 \bar{\rho} v_1' +\end{pmatrix} += +\begin{pmatrix} + 0 \\ 0 \\ 0 +\end{pmatrix} +``` +The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. +The unknowns are the perturbation quantities of the acoustic velocity ``v_1'``, the pressure ``p'`` +and the density ``\rho'``. +""" +struct LinearizedEulerEquations1D{RealT <: Real} <: + AbstractLinearizedEulerEquations{1, 3} + v_mean_global::RealT + c_mean_global::RealT + rho_mean_global::RealT +end + +function LinearizedEulerEquations1D(v_mean_global::Real, + c_mean_global::Real, rho_mean_global::Real) + if rho_mean_global < 0 + throw(ArgumentError("rho_mean_global must be non-negative")) + elseif c_mean_global < 0 + throw(ArgumentError("c_mean_global must be non-negative")) + end + + return LinearizedEulerEquations1D(v_mean_global, c_mean_global, + rho_mean_global) +end + +# Constructor with keywords +function LinearizedEulerEquations1D(; v_mean_global::Real, + c_mean_global::Real, rho_mean_global::Real) + return LinearizedEulerEquations1D(v_mean_global, c_mean_global, + rho_mean_global) +end + +function varnames(::typeof(cons2cons), ::LinearizedEulerEquations1D) + ("rho_prime", "v1_prime", "p_prime") +end +function varnames(::typeof(cons2prim), ::LinearizedEulerEquations1D) + ("rho_prime", "v1_prime", "p_prime") +end + +""" + initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations1D) + +A smooth initial condition used for convergence tests. +""" +function initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations1D) + rho_prime = -cospi(2 * t) * sinpi(2 * x[1]) + v1_prime = sinpi(2 * t) * cospi(2 * x[1]) + p_prime = rho_prime + + return SVector(rho_prime, v1_prime, p_prime) +end + +""" + boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, + equations::LinearizedEulerEquations1D) + +Boundary conditions for a solid wall. +""" +function boundary_condition_wall(u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LinearizedEulerEquations1D) + # Boundary state is equal to the inner state except for the velocity. For boundaries + # in the -x/+x direction, we multiply the velocity (in the x direction by) -1. + u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3]) + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux +end + +# Calculate 1D flux for a single point +@inline function flux(u, orientation::Integer, equations::LinearizedEulerEquations1D) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, p_prime = u + f1 = v_mean_global * rho_prime + rho_mean_global * v1_prime + f2 = v_mean_global * v1_prime + p_prime / rho_mean_global + f3 = v_mean_global * p_prime + c_mean_global^2 * rho_mean_global * v1_prime + + return SVector(f1, f2, f3) +end + +@inline have_constant_speed(::LinearizedEulerEquations1D) = True() + +@inline function max_abs_speeds(equations::LinearizedEulerEquations1D) + @unpack v_mean_global, c_mean_global = equations + return abs(v_mean_global) + c_mean_global +end + +@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations1D) + @unpack v_mean_global, c_mean_global = equations + return abs(v_mean_global) + c_mean_global +end + +# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations1D) + min_max_speed_davis(u_ll, u_rr, orientation, equations) +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations1D) + @unpack v_mean_global, c_mean_global = equations + + λ_min = v_mean_global - c_mean_global + λ_max = v_mean_global + c_mean_global + + return λ_min, λ_max +end + +# Convert conservative variables to primitive +@inline cons2prim(u, equations::LinearizedEulerEquations1D) = u +@inline cons2entropy(u, ::LinearizedEulerEquations1D) = u +end # muladd diff --git a/src/equations/linearized_euler_2d.jl b/src/equations/linearized_euler_2d.jl index d497762bf62..3df3093069d 100644 --- a/src/equations/linearized_euler_2d.jl +++ b/src/equations/linearized_euler_2d.jl @@ -8,7 +8,7 @@ @doc raw""" LinearizedEulerEquations2D(v_mean_global, c_mean_global, rho_mean_global) -Linearized euler equations in two space dimensions. The equations are given by +Linearized Euler equations in two space dimensions. The equations are given by ```math \partial_t \begin{pmatrix} @@ -29,7 +29,7 @@ Linearized euler equations in two space dimensions. The equations are given by 0 \\ 0 \\ 0 \\ 0 \end{pmatrix} ``` -The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and c is the speed of sound. +The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. The unknowns are the acoustic velocities ``v' = (v_1', v_2')``, the pressure ``p'`` and the density ``\rho'``. """ struct LinearizedEulerEquations2D{RealT <: Real} <: diff --git a/src/equations/linearized_euler_3d.jl b/src/equations/linearized_euler_3d.jl new file mode 100644 index 00000000000..ab5f9863db8 --- /dev/null +++ b/src/equations/linearized_euler_3d.jl @@ -0,0 +1,254 @@ +# 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 + +@doc raw""" + LinearizedEulerEquations3D(v_mean_global, c_mean_global, rho_mean_global) + +Linearized Euler equations in three space dimensions. The equations are given by +```math +\partial_t +\begin{pmatrix} + \rho' \\ v_1' \\ v_2' \\ v_3' \\ p' +\end{pmatrix} ++ +\partial_x +\begin{pmatrix} + \bar{\rho} v_1' + \bar{v_1} \rho ' \\ + \bar{v_1} v_1' + \frac{p'}{\bar{\rho}} \\ + \bar{v_1} v_2' \\ + \bar{v_1} v_3' \\ + \bar{v_1} p' + c^2 \bar{\rho} v_1' +\end{pmatrix} ++ +\partial_y +\begin{pmatrix} + \bar{\rho} v_2' + \bar{v_2} \rho ' \\ + \bar{v_2} v_1' \\ + \bar{v_2} v_2' + \frac{p'}{\bar{\rho}} \\ + \bar{v_2} v_3' \\ + \bar{v_2} p' + c^2 \bar{\rho} v_2' +\end{pmatrix} ++ +\partial_z +\begin{pmatrix} + \bar{\rho} v_3' + \bar{v_3} \rho ' \\ + \bar{v_3} v_1' \\ + \bar{v_3} v_2' \\ + \bar{v_3} v_3' + \frac{p'}{\bar{\rho}} \\ + \bar{v_3} p' + c^2 \bar{\rho} v_3' +\end{pmatrix} += +\begin{pmatrix} + 0 \\ 0 \\ 0 \\ 0 \\ 0 +\end{pmatrix} +``` +The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. +The unknowns are the acoustic velocities ``v' = (v_1', v_2, v_3')``, the pressure ``p'`` and the density ``\rho'``. +""" +struct LinearizedEulerEquations3D{RealT <: Real} <: + AbstractLinearizedEulerEquations{3, 5} + v_mean_global::SVector{3, RealT} + c_mean_global::RealT + rho_mean_global::RealT +end + +function LinearizedEulerEquations3D(v_mean_global::NTuple{3, <:Real}, + c_mean_global::Real, rho_mean_global::Real) + if rho_mean_global < 0 + throw(ArgumentError("rho_mean_global must be non-negative")) + elseif c_mean_global < 0 + throw(ArgumentError("c_mean_global must be non-negative")) + end + + return LinearizedEulerEquations3D(SVector(v_mean_global), c_mean_global, + rho_mean_global) +end + +function LinearizedEulerEquations3D(; v_mean_global::NTuple{3, <:Real}, + c_mean_global::Real, rho_mean_global::Real) + return LinearizedEulerEquations3D(v_mean_global, c_mean_global, + rho_mean_global) +end + +function varnames(::typeof(cons2cons), ::LinearizedEulerEquations3D) + ("rho_prime", "v1_prime", "v2_prime", "v3_prime", "p_prime") +end +function varnames(::typeof(cons2prim), ::LinearizedEulerEquations3D) + ("rho_prime", "v1_prime", "v2_prime", "v3_prime", "p_prime") +end + +""" + initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations3D) + +A smooth initial condition used for convergence tests. +""" +function initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations3D) + rho_prime = -cospi(2 * t) * (sinpi(2 * x[1]) + sinpi(2 * x[2]) + sinpi(2 * x[3])) + v1_prime = sinpi(2 * t) * cospi(2 * x[1]) + v2_prime = sinpi(2 * t) * cospi(2 * x[2]) + v3_prime = sinpi(2 * t) * cospi(2 * x[3]) + p_prime = rho_prime + + return SVector(rho_prime, v1_prime, v2_prime, v3_prime, p_prime) +end + +""" + boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, + equations::LinearizedEulerEquations3D) + +Boundary conditions for a solid wall. +""" +function boundary_condition_wall(u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LinearizedEulerEquations3D) + # Boundary state is equal to the inner state except for the velocity. For boundaries + # in the -x/+x direction, we multiply the velocity in the x direction by -1. + # Similarly, for boundaries in the -y/+y or -z/+z direction, we multiply the + # velocity in the y or z direction by -1 + if direction in (1, 2) # x direction + u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3], u_inner[4], + u_inner[5]) + elseif direction in (3, 4) # y direction + u_boundary = SVector(u_inner[1], u_inner[2], -u_inner[3], u_inner[4], + u_inner[5]) + else # z direction = (5, 6) + u_boundary = SVector(u_inner[1], u_inner[2], u_inner[3], -u_inner[4], + u_inner[5]) + end + + # Calculate boundary flux depending on the orientation of the boundary + # Odd directions are in negative coordinate direction, + # even directions are in positive coordinate direction. + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux +end + +# Calculate 3D flux for a single point +@inline function flux(u, orientation::Integer, equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, v2_prime, v3_prime, p_prime = u + if orientation == 1 + f1 = v_mean_global[1] * rho_prime + rho_mean_global * v1_prime + f2 = v_mean_global[1] * v1_prime + p_prime / rho_mean_global + f3 = v_mean_global[1] * v2_prime + f4 = v_mean_global[1] * v3_prime + f5 = v_mean_global[1] * p_prime + c_mean_global^2 * rho_mean_global * v1_prime + elseif orientation == 2 + f1 = v_mean_global[2] * rho_prime + rho_mean_global * v2_prime + f2 = v_mean_global[2] * v1_prime + f3 = v_mean_global[2] * v2_prime + p_prime / rho_mean_global + f4 = v_mean_global[2] * v3_prime + f5 = v_mean_global[2] * p_prime + c_mean_global^2 * rho_mean_global * v2_prime + else # orientation == 3 + f1 = v_mean_global[3] * rho_prime + rho_mean_global * v3_prime + f2 = v_mean_global[3] * v1_prime + f3 = v_mean_global[3] * v2_prime + f4 = v_mean_global[3] * v3_prime + p_prime / rho_mean_global + f5 = v_mean_global[3] * p_prime + c_mean_global^2 * rho_mean_global * v3_prime + end + + return SVector(f1, f2, f3, f4, f5) +end + +# Calculate 3D flux for a single point +@inline function flux(u, normal_direction::AbstractVector, + equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, v2_prime, v3_prime, p_prime = u + + v_mean_normal = v_mean_global[1] * normal_direction[1] + + v_mean_global[2] * normal_direction[2] + + v_mean_global[3] * normal_direction[3] + v_prime_normal = v1_prime * normal_direction[1] + v2_prime * normal_direction[2] + + v3_prime * normal_direction[3] + + f1 = v_mean_normal * rho_prime + rho_mean_global * v_prime_normal + f2 = v_mean_normal * v1_prime + normal_direction[1] * p_prime / rho_mean_global + f3 = v_mean_normal * v2_prime + normal_direction[2] * p_prime / rho_mean_global + f4 = v_mean_normal * v3_prime + normal_direction[3] * p_prime / rho_mean_global + f5 = v_mean_normal * p_prime + c_mean_global^2 * rho_mean_global * v_prime_normal + + return SVector(f1, f2, f3, f4, f5) +end + +@inline have_constant_speed(::LinearizedEulerEquations3D) = True() + +@inline function max_abs_speeds(equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global = equations + return abs(v_mean_global[1]) + c_mean_global, abs(v_mean_global[2]) + c_mean_global, + abs(v_mean_global[3]) + c_mean_global +end + +@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global = equations + if orientation == 1 + return abs(v_mean_global[1]) + c_mean_global + elseif orientation == 2 + return abs(v_mean_global[2]) + c_mean_global + else # orientation == 3 + return abs(v_mean_global[3]) + c_mean_global + end +end + +@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global = equations + v_mean_normal = normal_direction[1] * v_mean_global[1] + + normal_direction[2] * v_mean_global[2] + + normal_direction[3] * v_mean_global[3] + return abs(v_mean_normal) + c_mean_global * norm(normal_direction) +end + +# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations3D) + min_max_speed_davis(u_ll, u_rr, orientation, equations) +end + +@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations3D) + min_max_speed_davis(u_ll, u_rr, normal_direction, equations) +end + +# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes +@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global = equations + + λ_min = v_mean_global[orientation] - c_mean_global + λ_max = v_mean_global[orientation] + c_mean_global + + return λ_min, λ_max +end + +@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global = equations + + norm_ = norm(normal_direction) + + v_normal = v_mean_global[1] * normal_direction[1] + + v_mean_global[2] * normal_direction[2] + + v_mean_global[3] * normal_direction[3] + + # The v_normals are already scaled by the norm + λ_min = v_normal - c_mean_global * norm_ + λ_max = v_normal + c_mean_global * norm_ + + return λ_min, λ_max +end + +# Convert conservative variables to primitive +@inline cons2prim(u, equations::LinearizedEulerEquations3D) = u +@inline cons2entropy(u, ::LinearizedEulerEquations3D) = u +end # muladd diff --git a/src/equations/shallow_water_1d.jl b/src/equations/shallow_water_1d.jl index e348ef946b7..7007bea887a 100644 --- a/src/equations/shallow_water_1d.jl +++ b/src/equations/shallow_water_1d.jl @@ -551,7 +551,7 @@ end h = waterheight(u, equations) v = velocity(u, equations) - c = equations.gravity * sqrt(h) + c = sqrt(equations.gravity * h) return (abs(v) + c,) end diff --git a/src/equations/shallow_water_2d.jl b/src/equations/shallow_water_2d.jl index 74a299a51e6..73fae89a0fa 100644 --- a/src/equations/shallow_water_2d.jl +++ b/src/equations/shallow_water_2d.jl @@ -927,7 +927,7 @@ end h = waterheight(u, equations) v1, v2 = velocity(u, equations) - c = equations.gravity * sqrt(h) + c = sqrt(equations.gravity * h) return abs(v1) + c, abs(v2) + c end diff --git a/src/equations/shallow_water_quasi_1d.jl b/src/equations/shallow_water_quasi_1d.jl index 51c360104a7..08620021254 100644 --- a/src/equations/shallow_water_quasi_1d.jl +++ b/src/equations/shallow_water_quasi_1d.jl @@ -248,7 +248,7 @@ end h = waterheight(u, equations) v = velocity(u, equations) - c = equations.gravity * sqrt(h) + c = sqrt(equations.gravity * h) return (abs(v) + c,) end diff --git a/src/meshes/mesh_io.jl b/src/meshes/mesh_io.jl index 28e6efa8c57..d74a0c0cea1 100644 --- a/src/meshes/mesh_io.jl +++ b/src/meshes/mesh_io.jl @@ -97,7 +97,10 @@ end # of the mesh, like its size and the type of boundary mapping function. # Then, within Trixi2Vtk, the StructuredMesh and its node coordinates are reconstructured from # these attributes for plotting purposes -function save_mesh_file(mesh::StructuredMesh, output_directory; system = "") +# Note: the `timestep` argument is needed for compatibility with the method for +# `StructuredMeshView` +function save_mesh_file(mesh::StructuredMesh, output_directory; system = "", + timestep = 0) # Create output directory (if it does not exist) mkpath(output_directory) @@ -256,7 +259,7 @@ function load_mesh_serial(mesh_file::AbstractString; n_cells_max, RealT) end mesh = TreeMesh(SerialTree{ndims}, max(n_cells_max, capacity)) load_mesh!(mesh, mesh_file) - elseif mesh_type == "StructuredMesh" + elseif mesh_type in ("StructuredMesh", "StructuredMeshView") size_, mapping_as_string = h5open(mesh_file, "r") do file return read(attributes(file)["size"]), read(attributes(file)["mapping"]) @@ -299,6 +302,7 @@ function load_mesh_serial(mesh_file::AbstractString; n_cells_max, RealT) mesh = UnstructuredMesh2D(mesh_filename; RealT = RealT, periodicity = periodicity_, unsaved_changes = false) + mesh.current_filename = mesh_file elseif mesh_type == "P4estMesh" p4est_filename, tree_node_coordinates, nodes, boundary_names_ = h5open(mesh_file, "r") do file @@ -317,7 +321,7 @@ function load_mesh_serial(mesh_file::AbstractString; n_cells_max, RealT) p4est = load_p4est(p4est_file, Val(ndims)) mesh = P4estMesh{ndims}(p4est, tree_node_coordinates, - nodes, boundary_names, "", false, true) + nodes, boundary_names, mesh_file, false, true) else error("Unknown mesh type!") end @@ -405,7 +409,7 @@ function load_mesh_parallel(mesh_file::AbstractString; n_cells_max, RealT) p4est = load_p4est(p4est_file, Val(ndims_)) mesh = P4estMesh{ndims_}(p4est, tree_node_coordinates, - nodes, boundary_names, "", false, true) + nodes, boundary_names, mesh_file, false, true) else error("Unknown mesh type!") end diff --git a/src/meshes/meshes.jl b/src/meshes/meshes.jl index ed2158b169a..4d6016e5564 100644 --- a/src/meshes/meshes.jl +++ b/src/meshes/meshes.jl @@ -7,6 +7,7 @@ include("tree_mesh.jl") include("structured_mesh.jl") +include("structured_mesh_view.jl") include("surface_interpolant.jl") include("unstructured_mesh.jl") include("face_interpolant.jl") diff --git a/src/meshes/structured_mesh_view.jl b/src/meshes/structured_mesh_view.jl new file mode 100644 index 00000000000..bd55115cc90 --- /dev/null +++ b/src/meshes/structured_mesh_view.jl @@ -0,0 +1,132 @@ +# 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 + +""" + StructuredMeshView{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS} + +A view on a structured curved mesh. +""" +mutable struct StructuredMeshView{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS} + parent::StructuredMesh{NDIMS, RealT} + cells_per_dimension::NTuple{NDIMS, Int} + mapping::Any # Not relevant for performance + mapping_as_string::String + current_filename::String + indices_min::NTuple{NDIMS, Int} + indices_max::NTuple{NDIMS, Int} + unsaved_changes::Bool +end + +""" + StructuredMeshView(parent; indices_min, indices_max) + +Create a StructuredMeshView on a StructuredMesh parent. + +# Arguments +- `parent`: the parent StructuredMesh. +- `indices_min`: starting indices of the parent mesh. +- `indices_max`: ending indices of the parent mesh. +""" +function StructuredMeshView(parent::StructuredMesh{NDIMS, RealT}; + indices_min = ntuple(_ -> 1, Val(NDIMS)), + indices_max = size(parent)) where {NDIMS, RealT} + @assert indices_min <= indices_max + @assert all(indices_min .> 0) + @assert indices_max <= size(parent) + + cells_per_dimension = indices_max .- indices_min .+ 1 + + # Compute cell sizes `deltas` + deltas = (parent.mapping.coordinates_max .- parent.mapping.coordinates_min) ./ + parent.cells_per_dimension + # Calculate the domain boundaries. + coordinates_min = parent.mapping.coordinates_min .+ deltas .* (indices_min .- 1) + coordinates_max = parent.mapping.coordinates_min .+ deltas .* indices_max + mapping = coordinates2mapping(coordinates_min, coordinates_max) + mapping_as_string = """ + coordinates_min = $coordinates_min + coordinates_max = $coordinates_max + mapping = coordinates2mapping(coordinates_min, coordinates_max) + """ + + return StructuredMeshView{NDIMS, RealT}(parent, cells_per_dimension, mapping, + mapping_as_string, + parent.current_filename, + indices_min, indices_max, + parent.unsaved_changes) +end + +# Check if mesh is periodic +function isperiodic(mesh::StructuredMeshView) + @unpack parent = mesh + return isperiodic(parent) && size(parent) == size(mesh) +end + +function isperiodic(mesh::StructuredMeshView, dimension) + @unpack parent, indices_min, indices_max = mesh + return (isperiodic(parent, dimension) && + indices_min[dimension] == 1 && + indices_max[dimension] == size(parent, dimension)) +end + +@inline Base.ndims(::StructuredMeshView{NDIMS}) where {NDIMS} = NDIMS +@inline Base.real(::StructuredMeshView{NDIMS, RealT}) where {NDIMS, RealT} = RealT +function Base.size(mesh::StructuredMeshView) + @unpack indices_min, indices_max = mesh + return indices_max .- indices_min .+ 1 +end +function Base.size(mesh::StructuredMeshView, i) + @unpack indices_min, indices_max = mesh + return indices_max[i] - indices_min[i] + 1 +end +Base.axes(mesh::StructuredMeshView) = map(Base.OneTo, size(mesh)) +Base.axes(mesh::StructuredMeshView, i) = Base.OneTo(size(mesh, i)) + +function calc_node_coordinates!(node_coordinates, element, + cell_x, cell_y, mapping, + mesh::StructuredMeshView{2}, + basis) + @unpack nodes = basis + + # Get cell length in reference mesh + dx = 2 / size(mesh, 1) + dy = 2 / size(mesh, 2) + + # Calculate node coordinates of reference mesh + cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 + cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 + + for j in eachnode(basis), i in eachnode(basis) + # node_coordinates are the mapped reference node_coordinates + node_coordinates[:, i, j, element] .= mapping(cell_x_offset + dx / 2 * nodes[i], + cell_y_offset + dy / 2 * nodes[j]) + end +end + +# Does not save the mesh itself to an HDF5 file. Instead saves important attributes +# of the mesh, like its size and the type of boundary mapping function. +# Then, within Trixi2Vtk, the StructuredMesh and its node coordinates are reconstructured from +# these attributes for plotting purposes. +function save_mesh_file(mesh::StructuredMeshView, output_directory; system = "", + timestep = 0) + # Create output directory (if it does not exist) + mkpath(output_directory) + + filename = joinpath(output_directory, @sprintf("mesh_%s_%06d.h5", system, timestep)) + + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["size"] = collect(size(mesh)) + attributes(file)["mapping"] = mesh.mapping_as_string + end + + return filename +end +end # @muladd diff --git a/src/semidiscretization/semidiscretization_coupled.jl b/src/semidiscretization/semidiscretization_coupled.jl index ded9373cc86..e7441264299 100644 --- a/src/semidiscretization/semidiscretization_coupled.jl +++ b/src/semidiscretization/semidiscretization_coupled.jl @@ -316,7 +316,8 @@ function save_mesh(semi::SemidiscretizationCoupled, output_directory, timestep = mesh, _, _, _ = mesh_equations_solver_cache(semi.semis[i]) if mesh.unsaved_changes - mesh.current_filename = save_mesh_file(mesh, output_directory, system = i) + mesh.current_filename = save_mesh_file(mesh, output_directory; system = i, + timestep = timestep) mesh.unsaved_changes = false end end @@ -348,6 +349,36 @@ function calculate_dt(u_ode, t, cfl_number, semi::SemidiscretizationCoupled) return dt end +function update_cleaning_speed!(semi_coupled::SemidiscretizationCoupled, + glm_speed_callback, dt) + @unpack glm_scale, cfl, semi_indices = glm_speed_callback + + if length(semi_indices) == 0 + throw("Since you have more than one semidiscretization you need to specify the 'semi_indices' for which the GLM speed needs to be calculated.") + end + + # Check that all MHD semidiscretizations received a GLM cleaning speed update. + for (semi_index, semi) in enumerate(semi_coupled.semis) + if (typeof(semi.equations) <: AbstractIdealGlmMhdEquations && + !(semi_index in semi_indices)) + error("Equation of semidiscretization $semi_index needs to be included in 'semi_indices' of 'GlmSpeedCallback'.") + end + end + + for semi_index in semi_indices + semi = semi_coupled.semis[semi_index] + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + + # compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR) + c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache) + + # c_h is proportional to its own time step divided by the complete MHD time step + equations.c_h = glm_scale * c_h_deltat / dt + end + + return semi_coupled +end + ################################################################################ ### Equations ################################################################################ @@ -435,10 +466,28 @@ function (boundary_condition::BoundaryConditionCoupled)(u_inner, orientation, di Val(nvariables(equations)))) # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + if surface_flux_function isa Tuple + # In case of conservative (index 1) and non-conservative (index 2) fluxes, + # add the non-conservative one with a factor of 1/2. + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = (surface_flux_function[1](u_inner, u_boundary, orientation, + equations) + + 0.5 * + surface_flux_function[2](u_inner, u_boundary, orientation, + equations)) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = (surface_flux_function[1](u_boundary, u_inner, orientation, + equations) + + 0.5 * + surface_flux_function[2](u_boundary, u_inner, orientation, + equations)) + end + else + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end end return flux @@ -582,7 +631,9 @@ end @inline function calc_boundary_flux_by_direction!(surface_flux_values, u, t, orientation, boundary_condition::BoundaryConditionCoupled, - mesh::StructuredMesh, equations, + mesh::Union{StructuredMesh, + StructuredMeshView}, + equations, surface_integral, dg::DG, cache, direction, node_indices, surface_node_indices, element) @@ -614,7 +665,8 @@ end end end -function get_boundary_indices(element, orientation, mesh::StructuredMesh{2}) +function get_boundary_indices(element, orientation, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}) cartesian_indices = CartesianIndices(size(mesh)) if orientation == 1 # Get index of element in y-direction diff --git a/src/semidiscretization/semidiscretization_euler_acoustics.jl b/src/semidiscretization/semidiscretization_euler_acoustics.jl index 173523ff892..286315fb960 100644 --- a/src/semidiscretization/semidiscretization_euler_acoustics.jl +++ b/src/semidiscretization/semidiscretization_euler_acoustics.jl @@ -103,7 +103,7 @@ function precompute_weights(source_region, weights, coupled_element_ids, equatio (nnodes(dg), nnodes(dg), length(coupled_element_ids))) - @threaded for k in 1:length(coupled_element_ids) + @threaded for k in eachindex(coupled_element_ids) element = coupled_element_ids[k] for j in eachnode(dg), i in eachnode(dg) x = get_node_coords(cache.elements.node_coordinates, equations, dg, i, j, @@ -197,7 +197,7 @@ function add_acoustic_source_terms!(du_acoustics, acoustic_source_terms, source_ coupled_element_ids, mesh::TreeMesh{2}, equations, dg::DGSEM, cache) - @threaded for k in 1:length(coupled_element_ids) + @threaded for k in eachindex(coupled_element_ids) element = coupled_element_ids[k] for j in eachnode(dg), i in eachnode(dg) diff --git a/src/semidiscretization/semidiscretization_hyperbolic.jl b/src/semidiscretization/semidiscretization_hyperbolic.jl index f61378a7dca..dcd211671c8 100644 --- a/src/semidiscretization/semidiscretization_hyperbolic.jl +++ b/src/semidiscretization/semidiscretization_hyperbolic.jl @@ -259,7 +259,8 @@ function check_periodicity_mesh_boundary_conditions(mesh::Union{TreeMesh{1}, end function check_periodicity_mesh_boundary_conditions(mesh::Union{TreeMesh{2}, - StructuredMesh{2}}, + StructuredMesh{2}, + StructuredMeshView{2}}, boundary_conditions::Union{NamedTuple, Tuple}) check_periodicity_mesh_boundary_conditions_x(mesh, boundary_conditions[1], diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 11f7d69f8d4..2e79c5e6d14 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -448,8 +448,9 @@ function get_node_variables!(node_variables, mesh, equations, dg::DG, cache) get_node_variables!(node_variables, mesh, equations, dg.volume_integral, dg, cache) end -const MeshesDGSEM = Union{TreeMesh, StructuredMesh, UnstructuredMesh2D, P4estMesh, - T8codeMesh} +const MeshesDGSEM = Union{TreeMesh, StructuredMesh, StructuredMeshView, + UnstructuredMesh2D, + P4estMesh, T8codeMesh} @inline function ndofs(mesh::MeshesDGSEM, dg::DG, cache) nelements(cache.elements) * nnodes(dg)^ndims(mesh) diff --git a/src/solvers/dgmulti/flux_differencing_gauss_sbp.jl b/src/solvers/dgmulti/flux_differencing_gauss_sbp.jl index 9059caf87f6..63a37f6780b 100644 --- a/src/solvers/dgmulti/flux_differencing_gauss_sbp.jl +++ b/src/solvers/dgmulti/flux_differencing_gauss_sbp.jl @@ -51,6 +51,29 @@ struct TensorProductGaussFaceOperator{NDIMS, OperatorType <: AbstractGaussOperat nfaces::Int end +function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, + dg::DGMulti{1, Line, GaussSBP}) + rd = dg.basis + + rq1D, wq1D = StartUpDG.gauss_quad(0, 0, polydeg(dg)) + interp_matrix_gauss_to_face_1d = polynomial_interpolation_matrix(rq1D, [-1; 1]) + + nnodes_1d = length(rq1D) + face_indices_tensor_product = nothing # not needed in 1D; we fall back to mul! + + num_faces = 2 + + T_op = typeof(operator) + Tm = typeof(interp_matrix_gauss_to_face_1d) + Tw = typeof(inv.(wq1D)) + Tf = typeof(rd.wf) + Ti = typeof(face_indices_tensor_product) + return TensorProductGaussFaceOperator{1, T_op, Tm, Tw, Tf, Ti}(interp_matrix_gauss_to_face_1d, + inv.(wq1D), rd.wf, + face_indices_tensor_product, + nnodes_1d, num_faces) +end + # constructor for a 2D operator function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, dg::DGMulti{2, Quad, GaussSBP}) @@ -126,6 +149,21 @@ end end end +@inline function tensor_product_gauss_face_operator!(out::AbstractVector, + A::TensorProductGaussFaceOperator{1, + Interpolation}, + x::AbstractVector) + mul!(out, A.interp_matrix_gauss_to_face_1d, x) +end + +@inline function tensor_product_gauss_face_operator!(out::AbstractVector, + A::TensorProductGaussFaceOperator{1, + <:Projection}, + x::AbstractVector) + mul!(out, A.interp_matrix_gauss_to_face_1d', x) + @. out *= A.inv_volume_weights_1d +end + # By default, Julia/LLVM does not use fused multiply-add operations (FMAs). # Since these FMAs can increase the performance of many numerical algorithms, # we need to opt-in explicitly. @@ -352,7 +390,7 @@ end # For now, this is mostly the same as `create_cache` for DGMultiFluxDiff{<:Polynomial}. # In the future, we may modify it so that we can specialize additional parts of GaussSBP() solvers. function create_cache(mesh::DGMultiMesh, equations, - dg::DGMultiFluxDiff{<:GaussSBP, <:Union{Quad, Hex}}, RealT, + dg::DGMultiFluxDiff{<:GaussSBP, <:Union{Line, Quad, Hex}}, RealT, uEltype) # call general Polynomial flux differencing constructor diff --git a/src/solvers/dgmulti/types.jl b/src/solvers/dgmulti/types.jl index 813bc67061e..ef9d7d2bf09 100644 --- a/src/solvers/dgmulti/types.jl +++ b/src/solvers/dgmulti/types.jl @@ -347,6 +347,9 @@ function SimpleKronecker(NDIMS, A, eltype_A = eltype(A)) return SimpleKronecker{NDIMS, typeof(A), typeof(tmp_storage)}(A, tmp_storage) end +# fall back to mul! for a 1D Kronecker product +LinearAlgebra.mul!(b, A_kronecker::SimpleKronecker{1}, x) = mul!(b, A_kronecker.A, x) + # Computes `b = kron(A, A) * x` in an optimized fashion function LinearAlgebra.mul!(b_in, A_kronecker::SimpleKronecker{2}, x_in) @unpack A = A_kronecker diff --git a/src/solvers/dgsem_p4est/dg.jl b/src/solvers/dgsem_p4est/dg.jl index ad3b0717498..4173c974c6b 100644 --- a/src/solvers/dgsem_p4est/dg.jl +++ b/src/solvers/dgsem_p4est/dg.jl @@ -36,7 +36,8 @@ end orientation = (direction + 1) >> 1 normal = get_contravariant_vector(orientation, contravariant_vectors, indices...) - # Contravariant vectors at interfaces in negative coordinate direction are pointing inwards + # Contravariant vectors at interfaces in negative coordinate direction are pointing inwards, + # flip sign to make them point outwards if isodd(direction) return -normal else diff --git a/src/solvers/dgsem_structured/containers.jl b/src/solvers/dgsem_structured/containers.jl index 8adf005b782..7b0d275c5b5 100644 --- a/src/solvers/dgsem_structured/containers.jl +++ b/src/solvers/dgsem_structured/containers.jl @@ -23,7 +23,8 @@ struct ElementContainer{NDIMS, RealT <: Real, uEltype <: Real, NDIMSP1, NDIMSP2, end # Create element container and initialize element data -function init_elements(mesh::StructuredMesh{NDIMS, RealT}, +function init_elements(mesh::Union{StructuredMesh{NDIMS, RealT}, + StructuredMeshView{NDIMS, RealT}}, equations::AbstractEquations, basis, ::Type{uEltype}) where {NDIMS, RealT <: Real, uEltype <: Real} diff --git a/src/solvers/dgsem_structured/containers_2d.jl b/src/solvers/dgsem_structured/containers_2d.jl index fb6db48e0a5..8a0722fc5d5 100644 --- a/src/solvers/dgsem_structured/containers_2d.jl +++ b/src/solvers/dgsem_structured/containers_2d.jl @@ -6,7 +6,8 @@ #! format: noindent # Initialize data structures in element container -function init_elements!(elements, mesh::StructuredMesh{2}, basis::LobattoLegendreBasis) +function init_elements!(elements, mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, + basis::LobattoLegendreBasis) @unpack node_coordinates, left_neighbors, jacobian_matrix, contravariant_vectors, inverse_jacobian = elements @@ -148,7 +149,9 @@ function calc_inverse_jacobian!(inverse_jacobian::AbstractArray{<:Any, 3}, eleme end # Save id of left neighbor of every element -function initialize_left_neighbor_connectivity!(left_neighbors, mesh::StructuredMesh{2}, +function initialize_left_neighbor_connectivity!(left_neighbors, + mesh::Union{StructuredMesh{2}, + StructuredMeshView{2}}, linear_indices) # Neighbors in x-direction for cell_y in 1:size(mesh, 2) diff --git a/src/solvers/dgsem_structured/dg.jl b/src/solvers/dgsem_structured/dg.jl index de4601a2203..ecb2485c68b 100644 --- a/src/solvers/dgsem_structured/dg.jl +++ b/src/solvers/dgsem_structured/dg.jl @@ -8,7 +8,8 @@ # 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::StructuredMesh, equations::AbstractEquations, dg::DG, ::Any, +function create_cache(mesh::Union{StructuredMesh, StructuredMeshView}, + equations::AbstractEquations, dg::DG, ::Any, ::Type{uEltype}) where {uEltype <: Real} elements = init_elements(mesh, equations, dg.basis, uEltype) @@ -30,7 +31,9 @@ end @inline function calc_boundary_flux_by_direction!(surface_flux_values, u, t, orientation, boundary_condition::BoundaryConditionPeriodic, - mesh::StructuredMesh, equations, + mesh::Union{StructuredMesh, + StructuredMeshView}, + equations, surface_integral, dg::DG, cache, direction, node_indices, surface_node_indices, element) @@ -40,7 +43,9 @@ end @inline function calc_boundary_flux_by_direction!(surface_flux_values, u, t, orientation, boundary_condition, - mesh::StructuredMesh, equations, + mesh::Union{StructuredMesh, + StructuredMeshView}, + equations, surface_integral, dg::DG, cache, direction, node_indices, surface_node_indices, element) diff --git a/src/solvers/dgsem_structured/dg_2d.jl b/src/solvers/dgsem_structured/dg_2d.jl index 467b92a7fce..0723751ee76 100644 --- a/src/solvers/dgsem_structured/dg_2d.jl +++ b/src/solvers/dgsem_structured/dg_2d.jl @@ -6,7 +6,7 @@ #! format: noindent function rhs!(du, u, t, - mesh::StructuredMesh{2}, equations, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, equations, initial_condition, boundary_conditions, source_terms::Source, dg::DG, cache) where {Source} # Reset du @@ -59,8 +59,9 @@ See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-17 =# @inline function weak_form_kernel!(du, u, element, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}}, nonconservative_terms::False, equations, dg::DGSEM, cache, alpha = true) # true * [some floating point value] == [exactly the same floating point value] @@ -388,7 +389,7 @@ end end function calc_interface_flux!(cache, u, - mesh::StructuredMesh{2}, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, nonconservative_terms, # can be True/False equations, surface_integral, dg::DG) @unpack elements = cache @@ -417,7 +418,8 @@ end @inline function calc_interface_flux!(surface_flux_values, left_element, right_element, orientation, u, - mesh::StructuredMesh{2}, + mesh::Union{StructuredMesh{2}, + StructuredMeshView{2}}, nonconservative_terms::False, equations, surface_integral, dg::DG, cache) # This is slow for LSA, but for some reason faster for Euler (see #519) @@ -552,13 +554,14 @@ end # TODO: Taal dimension agnostic function calc_boundary_flux!(cache, u, t, boundary_condition::BoundaryConditionPeriodic, - mesh::StructuredMesh{2}, equations, surface_integral, - dg::DG) + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, + equations, surface_integral, dg::DG) @assert isperiodic(mesh) end function calc_boundary_flux!(cache, u, t, boundary_conditions::NamedTuple, - mesh::StructuredMesh{2}, equations, surface_integral, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, + equations, surface_integral, dg::DG) @unpack surface_flux_values = cache.elements linear_indices = LinearIndices(size(mesh)) @@ -617,8 +620,8 @@ function calc_boundary_flux!(cache, u, t, boundary_conditions::NamedTuple, end function apply_jacobian!(du, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, equations, dg::DG, cache) @unpack inverse_jacobian = cache.elements diff --git a/src/solvers/dgsem_tree/dg_2d.jl b/src/solvers/dgsem_tree/dg_2d.jl index e7c9978b524..ba0f1a2c7eb 100644 --- a/src/solvers/dgsem_tree/dg_2d.jl +++ b/src/solvers/dgsem_tree/dg_2d.jl @@ -192,8 +192,8 @@ end function calc_volume_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, + StructuredMeshView{2}, UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}}, nonconservative_terms, equations, volume_integral::VolumeIntegralWeakForm, dg::DGSEM, cache) @@ -1097,7 +1097,9 @@ end return nothing end -function calc_surface_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}}, +function calc_surface_integral!(du, u, + mesh::Union{TreeMesh{2}, StructuredMesh{2}, + StructuredMeshView{2}}, equations, surface_integral::SurfaceIntegralWeakForm, dg::DG, cache) @unpack boundary_interpolation = dg.basis diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 52dc6813c30..297967838ea 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -866,9 +866,9 @@ end (; variable_bounds) = limiter.cache.subcell_limiter_coefficients (; bar_states1, bar_states2) = limiter.cache.container_bar_states - # state variables - if limiter.local_minmax - for v in limiter.local_minmax_variables_cons + # Local two-sided limiting for conservatie variables + if limiter.local_twosided + for v in limiter.local_twosided_variables_cons v_string = string(v) var_min = variable_bounds[Symbol(v_string, "_min")] var_max = variable_bounds[Symbol(v_string, "_max")] @@ -907,78 +907,54 @@ end end end end - # Specific Entropy - if limiter.spec_entropy - s_min = variable_bounds[:spec_entropy_min] - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - s_min[i, j, element] = typemax(eltype(s_min)) - end - # FV solution at node (i, j) - for j in eachnode(dg), i in eachnode(dg) - s = entropy_spec(get_node_vars(u, equations, dg, i, j, element), - equations) - s_min[i, j, element] = min(s_min[i, j, element], s) - # TODO: Add source term! - end - # xi direction: subcell face between (i-1, j) and (i, j) - for j in eachnode(dg), i in 1:(nnodes(dg) + 1) - s = entropy_spec(get_node_vars(bar_states1, equations, dg, i, j, - element), equations) - if i <= nnodes(dg) - s_min[i, j, element] = min(s_min[i, j, element], s) - end - if i > 1 - s_min[i - 1, j, element] = min(s_min[i - 1, j, element], s) - end - end - # eta direction: subcell face between (i, j-1) and (i, j) - for j in 1:(nnodes(dg) + 1), i in eachnode(dg) - s = entropy_spec(get_node_vars(bar_states2, equations, dg, i, j, - element), equations) - if j <= nnodes(dg) - s_min[i, j, element] = min(s_min[i, j, element], s) - end - if j > 1 - s_min[i, j - 1, element] = min(s_min[i, j - 1, element], s) - end - end - end - end - # Mathematical entropy - if limiter.math_entropy - s_max = variable_bounds[:math_entropy_max] - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - s_max[i, j, element] = typemin(eltype(s_max)) - end - # FV solution at node (i, j) - for j in eachnode(dg), i in eachnode(dg) - s = entropy_math(get_node_vars(u, equations, dg, i, j, element), - equations) - s_max[i, j, element] = max(s_max[i, j, element], s) - # TODO: Add source term! - end - # xi direction: subcell face between (i-1, j) and (i, j) - for j in eachnode(dg), i in 1:(nnodes(dg) + 1) - s = entropy_math(get_node_vars(bar_states1, equations, dg, i, j, - element), equations) - if i <= nnodes(dg) - s_max[i, j, element] = max(s_max[i, j, element], s) + # Local two-sided limiting for non-linear variables + if limiter.local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + var_minmax = variable_bounds[Symbol(string(variable), "_", + string(min_or_max))] + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + if min_or_max === max + var_minmax[i, j, element] = typemin(eltype(var_minmax)) + else + var_minmax[i, j, element] = typemax(eltype(var_minmax)) + end end - if i > 1 - s_max[i - 1, j, element] = max(s_max[i - 1, j, element], s) + # FV solution at node (i, j) + for j in eachnode(dg), i in eachnode(dg) + var = variable(get_node_vars(u, equations, dg, i, j, element), + equations) + var_minmax[i, j, element] = min_or_max(var_minmax[i, j, element], + var) + # TODO: Add source term! end - end - # eta direction: subcell face between (i, j-1) and (i, j) - for j in 1:(nnodes(dg) + 1), i in eachnode(dg) - s = entropy_math(get_node_vars(bar_states2, equations, dg, i, j, - element), equations) - if j <= nnodes(dg) - s_max[i, j, element] = max(s_max[i, j, element], s) + # xi direction: subcell face between (i-1, j) and (i, j) + for j in eachnode(dg), i in 1:(nnodes(dg) + 1) + var = variable(get_node_vars(bar_states1, equations, dg, i, j, + element), equations) + if i <= nnodes(dg) + var_minmax[i, j, element] = min_or_max(var_minmax[i, j, + element], var) + end + if i > 1 + var_minmax[i - 1, j, element] = min_or_max(var_minmax[i - 1, j, + element], + var) + end end - if j > 1 - s_max[i, j - 1, element] = max(s_max[i, j - 1, element], s) + # eta direction: subcell face between (i, j-1) and (i, j) + for j in 1:(nnodes(dg) + 1), i in eachnode(dg) + var = variable(get_node_vars(bar_states2, equations, dg, i, j, + element), equations) + if j <= nnodes(dg) + var_minmax[i, j, element] = min_or_max(var_minmax[i, j, + element], var) + end + if j > 1 + var_minmax[i, j - 1, element] = min_or_max(var_minmax[i, j - 1, + element], + var) + end end end end diff --git a/src/solvers/dgsem_tree/subcell_limiters.jl b/src/solvers/dgsem_tree/subcell_limiters.jl index d93307d476a..f3908fbb494 100644 --- a/src/solvers/dgsem_tree/subcell_limiters.jl +++ b/src/solvers/dgsem_tree/subcell_limiters.jl @@ -20,12 +20,11 @@ end """ SubcellLimiterIDP(equations::AbstractEquations, basis; - local_minmax_variables_cons = String[], + local_twosided_variables_cons = String[], positivity_variables_cons = String[], positivity_variables_nonlinear = [], positivity_correction_factor = 0.1, - spec_entropy = false, - math_entropy = false, + local_onesided_variables_nonlinear = [], bar_states = true, max_iterations_newton = 10, newton_tolerances = (1.0e-12, 1.0e-14), @@ -36,18 +35,25 @@ end Subcell invariant domain preserving (IDP) limiting used with [`VolumeIntegralSubcellLimiting`](@ref) including: -- Local maximum/minimum Zalesak-type limiting for conservative variables (`local_minmax_variables_cons`) +- Local two-sided Zalesak-type limiting for conservative variables (`local_twosided_variables_cons`) - Positivity limiting for conservative variables (`positivity_variables_cons`) and nonlinear variables (`positivity_variables_nonlinear`) -- One-sided limiting for specific and mathematical entropy (`spec_entropy`, `math_entropy`) +- Local one-sided limiting for nonlinear variables, e.g. `entropy_guermond_etal` and `entropy_math` +with `local_onesided_variables_nonlinear` -Conservative variables to be limited are passed as a vector of strings, e.g. `local_minmax_variables_cons = ["rho"]` -and `positivity_variables_cons = ["rho"]`. For nonlinear variables the specific functions are -passed in a vector, e.g. `positivity_variables_nonlinear = [pressure]`. +To use these three limiting options use the following structure: + +***Conservative variables*** to be limited are passed as a vector of strings, e.g. +`local_twosided_variables_cons = ["rho"]` and `positivity_variables_cons = ["rho"]`. +For ***nonlinear variables***, the wanted variable functions are passed within a vector: To ensure +positivity use a plain vector including the desired variables, e.g. `positivity_variables_nonlinear = [pressure]`. +For local one-sided limiting pass the variable function combined with the requested bound +(`min` or `max`) as a tuple. For instance, to impose a lower local bound on the modified specific +entropy by Guermond et al. use `local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, min)]`. The bounds can be calculated using the `bar_states` or the low-order FV solution. The positivity limiter uses `positivity_correction_factor` such that `u^new >= positivity_correction_factor * u^FV`. -The limiting of nonlinear variables uses a Newton-bisection method with a maximum of +Local and global limiting of nonlinear variables uses a Newton-bisection method with a maximum of `max_iterations_newton` iterations, relative and absolute tolerances of `newton_tolerances` and a provisional update constant `gamma_constant_newton` (`gamma_constant_newton>=2*d`, where `d = #dimensions`). See equation (20) of Pazner (2020) and equation (30) of Rueda-Ramírez et al. (2022). @@ -73,15 +79,16 @@ indicator values <= `threshold_smoothness_indicator`. This is an experimental feature and may change in future releases. """ struct SubcellLimiterIDP{RealT <: Real, LimitingVariablesNonlinear, - Cache, Indicator} <: AbstractSubcellLimiter - local_minmax::Bool - local_minmax_variables_cons::Vector{Int} # Local mininum/maximum principles for conservative variables + LimitingOnesidedVariablesNonlinear, Cache, + Indicator} <: AbstractSubcellLimiter + local_twosided::Bool + local_twosided_variables_cons::Vector{Int} # Local two-sided limiting for conservative variables positivity::Bool positivity_variables_cons::Vector{Int} # Positivity for conservative variables positivity_variables_nonlinear::LimitingVariablesNonlinear # Positivity for nonlinear variables positivity_correction_factor::RealT - spec_entropy::Bool - math_entropy::Bool + local_onesided::Bool + local_onesided_variables_nonlinear::LimitingOnesidedVariablesNonlinear # Local one-sided limiting for nonlinear variables bar_states::Bool cache::Cache max_iterations_newton::Int @@ -94,12 +101,11 @@ end # this method is used when the limiter is constructed as for shock-capturing volume integrals function SubcellLimiterIDP(equations::AbstractEquations, basis; - local_minmax_variables_cons = String[], + local_twosided_variables_cons = String[], positivity_variables_cons = String[], positivity_variables_nonlinear = [], positivity_correction_factor = 0.1, - spec_entropy = false, - math_entropy = false, + local_onesided_variables_nonlinear = [], bar_states = true, max_iterations_newton = 10, newton_tolerances = (1.0e-12, 1.0e-14), @@ -107,34 +113,48 @@ function SubcellLimiterIDP(equations::AbstractEquations, basis; smoothness_indicator = false, threshold_smoothness_indicator = 0.1, variable_smoothness_indicator = density_pressure) - local_minmax = (length(local_minmax_variables_cons) > 0) + local_twosided = (length(local_twosided_variables_cons) > 0) + local_onesided = (length(local_onesided_variables_nonlinear) > 0) positivity = (length(positivity_variables_cons) + length(positivity_variables_nonlinear) > 0) - if math_entropy && spec_entropy - error("Only one of the two can be selected: math_entropy/spec_entropy") + + # When passing `min` or `max` in the elixir, the specific function of Base is used. + # To speed up the simulation, we replace it with `Trixi.min` and `Trixi.max` respectively. + local_onesided_variables_nonlinear_ = Tuple{Function, Function}[] + for (variable, min_or_max) in local_onesided_variables_nonlinear + if min_or_max === Base.max + push!(local_onesided_variables_nonlinear_, (variable, max)) + elseif min_or_max === Base.min + push!(local_onesided_variables_nonlinear_, (variable, min)) + elseif min_or_max === Trixi.max || min_or_max === Trixi.min + push!(local_onesided_variables_nonlinear_, (variable, min_or_max)) + else + error("Parameter $min_or_max is not a valid input. Use `max` or `min` instead.") + end end + local_onesided_variables_nonlinear_ = Tuple(local_onesided_variables_nonlinear_) - local_minmax_variables_cons_ = get_variable_index.(local_minmax_variables_cons, - equations) + local_twosided_variables_cons_ = get_variable_index.(local_twosided_variables_cons, + equations) positivity_variables_cons_ = get_variable_index.(positivity_variables_cons, equations) bound_keys = () - if local_minmax - for v in local_minmax_variables_cons_ + if local_twosided + for v in local_twosided_variables_cons_ v_string = string(v) bound_keys = (bound_keys..., Symbol(v_string, "_min"), Symbol(v_string, "_max")) end end - if spec_entropy - bound_keys = (bound_keys..., :spec_entropy_min) - end - if math_entropy - bound_keys = (bound_keys..., :math_entropy_max) + if local_onesided + for (variable, min_or_max) in local_onesided_variables_nonlinear_ + bound_keys = (bound_keys..., + Symbol(string(variable), "_", string(min_or_max))) + end end for v in positivity_variables_cons_ - if !(v in local_minmax_variables_cons_) + if !(v in local_twosided_variables_cons_) bound_keys = (bound_keys..., Symbol(string(v), "_min")) end end @@ -153,43 +173,40 @@ function SubcellLimiterIDP(equations::AbstractEquations, basis; end SubcellLimiterIDP{typeof(positivity_correction_factor), typeof(positivity_variables_nonlinear), - typeof(cache), typeof(IndicatorHG)}(local_minmax, - local_minmax_variables_cons_, - positivity, - positivity_variables_cons_, - positivity_variables_nonlinear, - positivity_correction_factor, - spec_entropy, math_entropy, - bar_states, - cache, - max_iterations_newton, - newton_tolerances, - gamma_constant_newton, - smoothness_indicator, - threshold_smoothness_indicator, - IndicatorHG) + typeof(local_onesided_variables_nonlinear_), + typeof(cache), + typeof(IndicatorHG)}(local_twosided, + local_twosided_variables_cons_, + positivity, positivity_variables_cons_, + positivity_variables_nonlinear, + positivity_correction_factor, + local_onesided, + local_onesided_variables_nonlinear_, + bar_states, cache, + max_iterations_newton, newton_tolerances, + gamma_constant_newton, + smoothness_indicator, + threshold_smoothness_indicator, + IndicatorHG) end function Base.show(io::IO, limiter::SubcellLimiterIDP) @nospecialize limiter # reduce precompilation time - (; local_minmax, positivity, spec_entropy, math_entropy) = limiter + (; local_twosided, positivity, local_onesided) = limiter print(io, "SubcellLimiterIDP(") - if !(local_minmax || positivity || spec_entropy || math_entropy) + if !(local_twosided || positivity || local_onesided) print(io, "No limiter selected => pure DG method") else features = String[] - if local_minmax + if local_twosided push!(features, "local min/max") end if positivity push!(features, "positivity") end - if spec_entropy - push!(features, "specific entropy") - end - if math_entropy - push!(features, "mathematical entropy") + if local_onesided + push!(features, "local onesided") end join(io, features, ", ") print(io, "Limiter=($features), ") @@ -204,19 +221,19 @@ end function Base.show(io::IO, ::MIME"text/plain", limiter::SubcellLimiterIDP) @nospecialize limiter # reduce precompilation time - (; local_minmax, positivity, spec_entropy, math_entropy) = limiter + (; local_twosided, positivity, local_onesided) = limiter if get(io, :compact, false) show(io, limiter) else - if !(local_minmax || positivity || spec_entropy || math_entropy) + if !(local_twosided || positivity || local_onesided) setup = ["Limiter" => "No limiter selected => pure DG method"] else setup = ["Limiter" => ""] - if local_minmax + if local_twosided setup = [ setup..., - "" => "Local maximum/minimum limiting for conservative variables $(limiter.local_minmax_variables_cons)", + "" => "Local two-sided limiting for conservative variables $(limiter.local_twosided_variables_cons)", ] end if positivity @@ -227,14 +244,10 @@ function Base.show(io::IO, ::MIME"text/plain", limiter::SubcellLimiterIDP) "" => "- with positivity correction factor = $(limiter.positivity_correction_factor)", ] end - if spec_entropy - setup = [setup..., "" => "Local minimum limiting for specific entropy"] - end - if math_entropy - setup = [ - setup..., - "" => "Local maximum limiting for mathematical entropy", - ] + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + setup = [setup..., "" => "Local $min_or_max limiting for $variable"] + end end setup = [ setup..., diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 9882ab203b1..8a97946bcef 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -53,22 +53,19 @@ function (limiter::SubcellLimiterIDP)(u::AbstractArray{<:Any, 4}, semi, dg::DGSE elements = eachelement(dg, semi.cache) end - if limiter.local_minmax - @trixi_timeit timer() "local min/max limiting" idp_local_minmax!(alpha, limiter, - u, t, dt, semi, - elements) + if limiter.local_twosided + @trixi_timeit timer() "local twosided" idp_local_twosided!(alpha, limiter, + u, t, dt, semi, + elements) end if limiter.positivity @trixi_timeit timer() "positivity" idp_positivity!(alpha, limiter, u, dt, semi, elements) end - if limiter.spec_entropy - @trixi_timeit timer() "spec_entropy" idp_spec_entropy!(alpha, limiter, u, t, - dt, semi, mesh, elements) - end - if limiter.math_entropy - @trixi_timeit timer() "math_entropy" idp_math_entropy!(alpha, limiter, u, t, - dt, semi, mesh, elements) + if limiter.local_onesided + @trixi_timeit timer() "local onesided" idp_local_onesided!(alpha, limiter, + u, t, dt, semi, + elements) end # Calculate alpha1 and alpha2 @@ -197,44 +194,48 @@ end return nothing end -@inline function calc_bounds_onesided!(var_minmax, minmax, typeminmax, variable, u, t, - semi) +@inline function calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi) mesh, equations, dg, cache = mesh_equations_solver_cache(semi) # Calc bounds inside elements @threaded for element in eachelement(dg, cache) + # Reset bounds for j in eachnode(dg), i in eachnode(dg) - var_minmax[i, j, element] = typeminmax(eltype(var_minmax)) + if min_or_max === max + var_minmax[i, j, element] = typemin(eltype(var_minmax)) + else + var_minmax[i, j, element] = typemax(eltype(var_minmax)) + end end # Calculate bounds at Gauss-Lobatto nodes using u for j in eachnode(dg), i in eachnode(dg) var = variable(get_node_vars(u, equations, dg, i, j, element), equations) - var_minmax[i, j, element] = minmax(var_minmax[i, j, element], var) + var_minmax[i, j, element] = min_or_max(var_minmax[i, j, element], var) if i > 1 - var_minmax[i - 1, j, element] = minmax(var_minmax[i - 1, j, element], - var) + var_minmax[i - 1, j, element] = min_or_max(var_minmax[i - 1, j, + element], var) end if i < nnodes(dg) - var_minmax[i + 1, j, element] = minmax(var_minmax[i + 1, j, element], - var) + var_minmax[i + 1, j, element] = min_or_max(var_minmax[i + 1, j, + element], var) end if j > 1 - var_minmax[i, j - 1, element] = minmax(var_minmax[i, j - 1, element], - var) + var_minmax[i, j - 1, element] = min_or_max(var_minmax[i, j - 1, + element], var) end if j < nnodes(dg) - var_minmax[i, j + 1, element] = minmax(var_minmax[i, j + 1, element], - var) + var_minmax[i, j + 1, element] = min_or_max(var_minmax[i, j + 1, + element], var) end end end # Values at element boundary - calc_bounds_onesided_interface!(var_minmax, minmax, variable, u, t, semi, mesh) + calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u, t, semi, mesh) end -@inline function calc_bounds_onesided_interface!(var_minmax, minmax, variable, u, t, +@inline function calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u, t, semi, mesh::TreeMesh2D) _, equations, dg, cache = mesh_equations_solver_cache(semi) (; boundary_conditions) = semi @@ -257,10 +258,10 @@ end var_right = variable(get_node_vars(u, equations, dg, index_right..., right), equations) - var_minmax[index_right..., right] = minmax(var_minmax[index_right..., - right], var_left) - var_minmax[index_left..., left] = minmax(var_minmax[index_left..., left], - var_right) + var_minmax[index_right..., right] = min_or_max(var_minmax[index_right..., + right], var_left) + var_minmax[index_left..., left] = min_or_max(var_minmax[index_left..., + left], var_right) end end @@ -290,8 +291,8 @@ end index..., element) var_outer = variable(u_outer, equations) - var_minmax[index..., element] = minmax(var_minmax[index..., element], - var_outer) + var_minmax[index..., element] = min_or_max(var_minmax[index..., element], + var_outer) end end @@ -299,20 +300,20 @@ end end ############################################################################### -# Local minimum/maximum limiting +# Local two-sided limiting of conservative variables -@inline function idp_local_minmax!(alpha, limiter, u, t, dt, semi, elements) +@inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi, elements) mesh, _, _, _ = mesh_equations_solver_cache(semi) - for variable in limiter.local_minmax_variables_cons - idp_local_minmax!(alpha, limiter, u, t, dt, semi, mesh, elements, variable) + for variable in limiter.local_twosided_variables_cons + idp_local_twosided!(alpha, limiter, u, t, dt, semi, mesh, elements, variable) end return nothing end -@inline function idp_local_minmax!(alpha, limiter, u, t, dt, semi, mesh::TreeMesh{2}, - elements, variable) +@inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi, mesh::TreeMesh{2}, + elements, variable) _, _, dg, cache = mesh_equations_solver_cache(semi) (; variable_bounds) = limiter.cache.subcell_limiter_coefficients @@ -326,17 +327,17 @@ end @threaded for element in elements inverse_jacobian = cache.elements.inverse_jacobian[element] for j in eachnode(dg), i in eachnode(dg) - idp_local_minmax_inner!(alpha, inverse_jacobian, u, dt, dg, cache, variable, - var_min, var_max, i, j, element) + idp_local_twosided_inner!(alpha, inverse_jacobian, u, dt, dg, cache, + variable, var_min, var_max, i, j, element) end end return nothing end -@inline function idp_local_minmax!(alpha, limiter, u, t, dt, semi, - mesh::Union{StructuredMesh{2}, P4estMesh{2}}, - elements, variable) +@inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi, + mesh::Union{StructuredMesh{2}, P4estMesh{2}}, + elements, variable) _, _, dg, cache = mesh_equations_solver_cache(semi) (; variable_bounds) = limiter.cache.subcell_limiter_coefficients @@ -350,8 +351,8 @@ end @threaded for element in elements for j in eachnode(dg), i in eachnode(dg) inverse_jacobian = cache.elements.inverse_jacobian[i, j, element] - idp_local_minmax_inner!(alpha, inverse_jacobian, u, dt, dg, cache, variable, - var_min, var_max, i, j, element) + idp_local_twosided_inner!(alpha, inverse_jacobian, u, dt, dg, cache, + variable, var_min, var_max, i, j, element) end end @@ -359,8 +360,8 @@ end end # Function barrier to dispatch outer function by mesh type -@inline function idp_local_minmax_inner!(alpha, inverse_jacobian, u, dt, dg, cache, - variable, var_min, var_max, i, j, element) +@inline function idp_local_twosided_inner!(alpha, inverse_jacobian, u, dt, dg, cache, + variable, var_min, var_max, i, j, element) (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes (; inverse_weights) = dg.basis @@ -409,68 +410,27 @@ end return nothing end -############################################################################### -# Local minimum limiting of specific entropy - -@inline function idp_spec_entropy!(alpha, limiter, u, t, dt, semi, mesh::TreeMesh{2}, - elements) - _, equations, dg, cache = mesh_equations_solver_cache(semi) - (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - s_min = variable_bounds[:spec_entropy_min] - if !limiter.bar_states - calc_bounds_onesided!(s_min, min, typemax, entropy_spec, u, t, semi) - end - - # Perform Newton's bisection method to find new alpha - @threaded for element in elements - inverse_jacobian = cache.elements.inverse_jacobian[element] - for j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, element) - newton_loops_alpha!(alpha, s_min[i, j, element], u_local, inverse_jacobian, - i, j, element, dt, equations, dg, cache, limiter, - entropy_spec, initial_check_entropy_spec_newton_idp, - final_check_standard_newton_idp) - end - end +############################################################################## +# Local one-sided limiting of nonlinear variables - return nothing -end +@inline function idp_local_onesided!(alpha, limiter, u, t, dt, semi, elements) + mesh, _, _, _ = mesh_equations_solver_cache(semi) -@inline function idp_spec_entropy!(alpha, limiter, u, t, dt, semi, - mesh::Union{StructuredMesh{2}, P4estMesh{2}}, - elements) - _, equations, dg, cache = mesh_equations_solver_cache(semi) - (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - s_min = variable_bounds[:spec_entropy_min] - if !limiter.bar_states - calc_bounds_onesided!(s_min, min, typemax, entropy_spec, u, t, semi) - end - - # Perform Newton's bisection method to find new alpha - @threaded for element in elements - for j in eachnode(dg), i in eachnode(dg) - inverse_jacobian = cache.elements.inverse_jacobian[i, j, element] - u_local = get_node_vars(u, equations, dg, i, j, element) - newton_loops_alpha!(alpha, s_min[i, j, element], u_local, inverse_jacobian, - i, j, element, dt, equations, dg, cache, limiter, - entropy_spec, initial_check_entropy_spec_newton_idp, - final_check_standard_newton_idp) - end + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + idp_local_onesided!(alpha, limiter, u, t, dt, semi, mesh, elements, + variable, min_or_max) end return nothing end -############################################################################### -# Local maximum limiting of mathematical entropy - -@inline function idp_math_entropy!(alpha, limiter, u, t, dt, semi, mesh::TreeMesh{2}, - elements) +@inline function idp_local_onesided!(alpha, limiter, u, t, dt, semi, mesh::TreeMesh{2}, + elements, variable, min_or_max) _, equations, dg, cache = mesh_equations_solver_cache(semi) (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - s_max = variable_bounds[:math_entropy_max] + var_minmax = variable_bounds[Symbol(string(variable), "_", string(min_or_max))] if !limiter.bar_states - calc_bounds_onesided!(s_max, max, typemin, entropy_math, u, t, semi) + calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi) end # Perform Newton's bisection method to find new alpha @@ -478,24 +438,26 @@ end inverse_jacobian = cache.elements.inverse_jacobian[element] for j in eachnode(dg), i in eachnode(dg) u_local = get_node_vars(u, equations, dg, i, j, element) - newton_loops_alpha!(alpha, s_max[i, j, element], u_local, inverse_jacobian, - i, j, element, dt, equations, dg, cache, limiter, - entropy_math, initial_check_entropy_math_newton_idp, - final_check_standard_newton_idp) + newton_loops_alpha!(alpha, var_minmax[i, j, element], u_local, + i, j, element, variable, min_or_max, + initial_check_local_onesided_newton_idp, + final_check_local_onesided_newton_idp, inverse_jacobian, + dt, equations, dg, cache, limiter) end end return nothing end -@inline function idp_math_entropy!(alpha, limiter, u, t, dt, semi, - mesh::Union{StructuredMesh{2}, P4estMesh{2}}, - elements) +@inline function idp_local_onesided!(alpha, limiter, u, t, dt, semi, + mesh::Union{StructuredMesh{2}, P4estMesh{2}}, + elements, + variable, min_or_max) _, equations, dg, cache = mesh_equations_solver_cache(semi) (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - s_max = variable_bounds[:math_entropy_max] + var_minmax = variable_bounds[Symbol(string(variable), "_", string(min_or_max))] if !limiter.bar_states - calc_bounds_onesided!(s_max, max, typemin, entropy_math, u, t, semi) + calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi) end # Perform Newton's bisection method to find new alpha @@ -503,10 +465,11 @@ end for j in eachnode(dg), i in eachnode(dg) inverse_jacobian = cache.elements.inverse_jacobian[i, j, element] u_local = get_node_vars(u, equations, dg, i, j, element) - newton_loops_alpha!(alpha, s_max[i, j, element], u_local, inverse_jacobian, - i, j, element, dt, equations, dg, cache, limiter, - entropy_math, initial_check_entropy_math_newton_idp, - final_check_standard_newton_idp) + newton_loops_alpha!(alpha, var_minmax[i, j, element], u_local, + i, j, element, variable, min_or_max, + initial_check_local_onesided_newton_idp, + final_check_local_onesided_newton_idp, inverse_jacobian, + dt, equations, dg, cache, limiter) end end @@ -603,7 +566,7 @@ end # Compute bound if limiter.local_minmax && - variable in limiter.local_minmax_variables_cons && + variable in limiter.local_twosided_variables_cons && var_min[i, j, element] >= positivity_correction_factor * var # Local limiting is more restrictive that positivity limiting # => Skip positivity limiting for this node @@ -699,10 +662,10 @@ end var_min[i, j, element] = limiter.positivity_correction_factor * var # Perform Newton's bisection method to find new alpha - newton_loops_alpha!(alpha, var_min[i, j, element], u_local, inverse_jacobian, i, j, - element, dt, equations, dg, cache, limiter, variable, - initial_check_nonnegative_newton_idp, - final_check_nonnegative_newton_idp) + newton_loops_alpha!(alpha, var_min[i, j, element], u_local, i, j, element, variable, + min, initial_check_nonnegative_newton_idp, + final_check_nonnegative_newton_idp, inverse_jacobian, + dt, equations, dg, cache, limiter) return nothing end @@ -710,9 +673,10 @@ end ############################################################################### # Newton-bisection method -@inline function newton_loops_alpha!(alpha, bound, u, inverse_jacobian, i, j, element, - dt, equations, dg, cache, limiter, - variable, initial_check, final_check) +@inline function newton_loops_alpha!(alpha, bound, u, i, j, element, variable, + min_or_max, initial_check, final_check, + inverse_jacobian, dt, equations, dg, cache, + limiter) (; inverse_weights) = dg.basis (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes @@ -722,37 +686,38 @@ end antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[i] * get_node_vars(antidiffusive_flux1_R, equations, dg, i, j, element) - newton_loop!(alpha, bound, u, i, j, element, variable, initial_check, final_check, - equations, dt, limiter, antidiffusive_flux) + newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + final_check, equations, dt, limiter, antidiffusive_flux) # positive xi direction antidiffusive_flux = -gamma_constant_newton * inverse_jacobian * inverse_weights[i] * get_node_vars(antidiffusive_flux1_L, equations, dg, i + 1, j, element) - newton_loop!(alpha, bound, u, i, j, element, variable, initial_check, final_check, - equations, dt, limiter, antidiffusive_flux) + newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + final_check, equations, dt, limiter, antidiffusive_flux) # negative eta direction antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[j] * get_node_vars(antidiffusive_flux2_R, equations, dg, i, j, element) - newton_loop!(alpha, bound, u, i, j, element, variable, initial_check, final_check, - equations, dt, limiter, antidiffusive_flux) + newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + final_check, equations, dt, limiter, antidiffusive_flux) # positive eta direction antidiffusive_flux = -gamma_constant_newton * inverse_jacobian * inverse_weights[j] * get_node_vars(antidiffusive_flux2_L, equations, dg, i, j + 1, element) - newton_loop!(alpha, bound, u, i, j, element, variable, initial_check, final_check, - equations, dt, limiter, antidiffusive_flux) + newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + final_check, equations, dt, limiter, antidiffusive_flux) return nothing end -@inline function newton_loop!(alpha, bound, u, i, j, element, variable, initial_check, - final_check, equations, dt, limiter, antidiffusive_flux) +@inline function newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, + initial_check, final_check, equations, dt, limiter, + antidiffusive_flux) newton_reltol, newton_abstol = limiter.newton_tolerances beta = 1 - alpha[i, j, element] @@ -766,7 +731,7 @@ end if isvalid(u_curr, equations) goal = goal_function_newton_idp(variable, bound, u_curr, equations) - initial_check(bound, goal, newton_abstol) && return nothing + initial_check(min_or_max, bound, goal, newton_abstol) && return nothing end # Newton iterations @@ -801,7 +766,7 @@ end # Check new beta for condition and update bounds goal = goal_function_newton_idp(variable, bound, u_curr, equations) - if initial_check(bound, goal, newton_abstol) + if initial_check(min_or_max, bound, goal, newton_abstol) # New beta fulfills condition beta_L = beta else @@ -834,26 +799,25 @@ end end new_alpha = 1 - beta - if alpha[i, j, element] > new_alpha + newton_abstol - error("Alpha is getting smaller. old: $(alpha[i, j, element]), new: $new_alpha") - else - alpha[i, j, element] = new_alpha - end + alpha[i, j, element] = new_alpha return nothing end ### Auxiliary routines for Newton's bisection method ### # Initial checks -@inline function initial_check_entropy_spec_newton_idp(bound, goal, newton_abstol) +@inline function initial_check_local_onesided_newton_idp(::typeof(min), bound, + goal, newton_abstol) goal <= max(newton_abstol, abs(bound) * newton_abstol) end -@inline function initial_check_entropy_math_newton_idp(bound, goal, newton_abstol) +@inline function initial_check_local_onesided_newton_idp(::typeof(max), bound, + goal, newton_abstol) goal >= -max(newton_abstol, abs(bound) * newton_abstol) end -@inline initial_check_nonnegative_newton_idp(bound, goal, newton_abstol) = goal <= 0 +@inline initial_check_nonnegative_newton_idp(min_or_max, bound, goal, newton_abstol) = goal <= + 0 # Goal and d(Goal)d(u) function @inline goal_function_newton_idp(variable, bound, u, equations) = bound - @@ -864,10 +828,12 @@ end end # Final checks -@inline function final_check_standard_newton_idp(bound, goal, newton_abstol) +# final check for one-sided local limiting +@inline function final_check_local_onesided_newton_idp(bound, goal, newton_abstol) abs(goal) < max(newton_abstol, abs(bound) * newton_abstol) end +# final check for nonnegativity limiting @inline function final_check_nonnegative_newton_idp(bound, goal, newton_abstol) (goal <= eps()) && (goal > -max(newton_abstol, abs(bound) * newton_abstol)) end diff --git a/src/solvers/dgsem_unstructured/sort_boundary_conditions.jl b/src/solvers/dgsem_unstructured/sort_boundary_conditions.jl index b5388cadc8b..2c2c6876d70 100644 --- a/src/solvers/dgsem_unstructured/sort_boundary_conditions.jl +++ b/src/solvers/dgsem_unstructured/sort_boundary_conditions.jl @@ -8,8 +8,8 @@ """ UnstructuredSortedBoundaryTypes -General container to sort the boundary conditions by type for some unstructured meshes/solvers. -It stores a set of global indices for each boundary condition type to expedite computation +General container to sort the boundary conditions by type and name for some unstructured meshes/solvers. +It stores a set of global indices for each boundary condition type and name to expedite computation during the call to `calc_boundary_flux!`. The original dictionary form of the boundary conditions set by the user in the elixir file is also stored for printing. """ @@ -17,6 +17,7 @@ mutable struct UnstructuredSortedBoundaryTypes{N, BCs <: NTuple{N, Any}} boundary_condition_types::BCs # specific boundary condition type(s), e.g. BoundaryConditionDirichlet boundary_indices::NTuple{N, Vector{Int}} # integer vectors containing global boundary indices boundary_dictionary::Dict{Symbol, Any} # boundary conditions as set by the user in the elixir file + boundary_symbol_indices::Dict{Symbol, Vector{Int}} # integer vectors containing global boundary indices per boundary identifier end # constructor that "eats" the original boundary condition dictionary and sorts the information @@ -28,10 +29,14 @@ function UnstructuredSortedBoundaryTypes(boundary_conditions::Dict, cache) n_boundary_types = length(boundary_condition_types) boundary_indices = ntuple(_ -> [], n_boundary_types) + # Initialize `boundary_symbol_indices` as an empty dictionary, filled later in `initialize!` + boundary_symbol_indices = Dict{Symbol, Vector{Int}}() + container = UnstructuredSortedBoundaryTypes{n_boundary_types, typeof(boundary_condition_types)}(boundary_condition_types, boundary_indices, - boundary_conditions) + boundary_conditions, + boundary_symbol_indices) initialize!(container, cache) end @@ -97,6 +102,13 @@ function initialize!(boundary_types_container::UnstructuredSortedBoundaryTypes{N # convert the work array with the boundary indices into a tuple boundary_types_container.boundary_indices = Tuple(_boundary_indices) + # Store boundary indices per symbol (required for force computations, for instance) + for (symbol, _) in boundary_dictionary + indices = findall(x -> x === symbol, cache.boundaries.name) + # Store the indices in `boundary_symbol_indices` dictionary + boundary_types_container.boundary_symbol_indices[symbol] = sort!(indices) + end + return boundary_types_container end end # @muladd diff --git a/src/time_integration/methods_SSP.jl b/src/time_integration/methods_SSP.jl index 4f1df9fcf1d..8c99da803dc 100644 --- a/src/time_integration/methods_SSP.jl +++ b/src/time_integration/methods_SSP.jl @@ -88,7 +88,7 @@ mutable struct SimpleIntegratorSSP{RealT <: Real, uType, Params, Sol, F, Alg, t::RealT tdir::RealT dt::RealT # current time step - dtcache::RealT # ignored + dtcache::RealT # manually set time step iter::Int # current number of time steps (iteration) p::Params # will be the semidiscretization from Trixi sol::Sol # faked @@ -102,7 +102,7 @@ end """ add_tstop!(integrator::SimpleIntegratorSSP, t) -Add a time stop during the time integration process. +Add a time stop during the time integration process. This function is called after the periodic SaveSolutionCallback to specify the next stop to save the solution. """ function add_tstop!(integrator::SimpleIntegratorSSP, t) @@ -145,7 +145,7 @@ function solve(ode::ODEProblem, alg = SimpleSSPRK33()::SimpleAlgorithmSSP; t = first(ode.tspan) tdir = sign(ode.tspan[end] - ode.tspan[1]) iter = 0 - integrator = SimpleIntegratorSSP(u, du, r0, t, tdir, dt, zero(dt), iter, ode.p, + integrator = SimpleIntegratorSSP(u, du, r0, t, tdir, dt, dt, iter, ode.p, (prob = ode,), ode.f, alg, SimpleIntegratorSSPOptions(callback, ode.tspan; kwargs...), @@ -185,6 +185,8 @@ function solve!(integrator::SimpleIntegratorSSP) error("time step size `dt` is NaN") end + modify_dt_for_tstops!(integrator) + # if the next iteration would push the simulation beyond the end time, set dt accordingly if integrator.t + integrator.dt > t_end || isapprox(integrator.t + integrator.dt, t_end) @@ -192,8 +194,6 @@ function solve!(integrator::SimpleIntegratorSSP) terminate!(integrator) end - modify_dt_for_tstops!(integrator) - @. integrator.r0 = integrator.u for stage in eachindex(alg.c) t_stage = integrator.t + integrator.dt * alg.c[stage] @@ -232,7 +232,7 @@ function solve!(integrator::SimpleIntegratorSSP) end end - # Empty the tstops array. + # Empty the tstops array. # This cannot be done in terminate!(integrator::SimpleIntegratorSSP) because DiffEqCallbacks.PeriodicCallbackAffect would return at error. extract_all!(integrator.opts.tstops) @@ -253,12 +253,12 @@ u_modified!(integrator::SimpleIntegratorSSP, ::Bool) = false # used by adaptive timestepping algorithms in DiffEq function set_proposed_dt!(integrator::SimpleIntegratorSSP, dt) - integrator.dt = dt + (integrator.dt = dt; integrator.dtcache = dt) end # used by adaptive timestepping algorithms in DiffEq function get_proposed_dt(integrator::SimpleIntegratorSSP) - return integrator.dt + return ifelse(integrator.opts.adaptive, integrator.dt, integrator.dtcache) end # stop the time integration diff --git a/src/visualization/utilities.jl b/src/visualization/utilities.jl index 05457395ac0..c1108128c92 100644 --- a/src/visualization/utilities.jl +++ b/src/visualization/utilities.jl @@ -495,7 +495,7 @@ function cell2node(cell_centered_data) resolution_in, _ = size(first(cell_centered_data)) resolution_out = resolution_in + 1 node_centered_data = [Matrix{Float64}(undef, resolution_out, resolution_out) - for _ in 1:length(cell_centered_data)] + for _ in eachindex(cell_centered_data)] for (cell_data, node_data) in zip(cell_centered_data, node_centered_data) # Fill center with original data @@ -1545,7 +1545,7 @@ end # Create an axis. function axis_curve(nodes_x, nodes_y, nodes_z, slice, point, n_points) - if n_points == nothing + if n_points === nothing n_points = 64 end dimensions = length(point) diff --git a/test/test_dgmulti_1d.jl b/test/test_dgmulti_1d.jl index e470de71efb..0d083cf9a72 100644 --- a/test/test_dgmulti_1d.jl +++ b/test/test_dgmulti_1d.jl @@ -29,6 +29,22 @@ isdir(outdir) && rm(outdir, recursive = true) end end +@trixi_testset "elixir_burgers_gauss_shock_capturing.jl " begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_burgers_gauss_shock_capturing.jl"), + cells_per_dimension=(8,), tspan=(0.0, 0.1), + l2=[0.445804588167854], + linf=[0.74780611426038]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end + @trixi_testset "elixir_euler_flux_diff.jl " begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_flux_diff.jl"), cells_per_dimension=(16,), diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index 5d4db4c48de..ee6b944f617 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -230,6 +230,32 @@ end end end +@trixi_testset "elixir_euler_shockcapturing_ec.jl (flux_chandrashekar)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_ec.jl"), + l2=[ + 0.09527896382082567, + 0.10557894830184737, + 0.10559379376154387, + 0.3503791205165925, + ], + linf=[ + 0.2733486454092644, + 0.3877283966722886, + 0.38650482703821426, + 1.0053712251056308, + ], + tspan=(0.0, 1.0), + volume_flux=flux_chandrashekar) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end + @trixi_testset "elixir_euler_sedov.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), l2=[ @@ -693,6 +719,86 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end + +@trixi_testset "elixir_euler_subsonic_cylinder.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_subsonic_cylinder.jl"), + l2=[ + 0.00011914390523852561, + 0.00010776028621724485, + 6.139954358305467e-5, + 0.0003067693731825959, + ], + linf=[ + 0.1653075586200805, + 0.1868437275544909, + 0.09772818519679008, + 0.4311796171737692, + ], tspan=(0.0, 0.001)) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) # Just a placeholder in this case + + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + drag = Trixi.analyze(drag_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache) + lift = Trixi.analyze(lift_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache) + + @test isapprox(lift, -6.501138753497174e-15, atol = 1e-13) + @test isapprox(drag, 2.588589856781827, atol = 1e-13) +end + +# Forces computation test in an AMR code +@trixi_testset "elixir_euler_NACA0012airfoil_mach085.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_NACA0012airfoil_mach085.jl"), + l2=[ + 5.371568111383228e-7, 6.4158131303956445e-6, + 1.0324346542348325e-5, 0.0006348064933187732, + ], + linf=[ + 0.0016263400091978443, 0.028471072159724428, + 0.02986133204785877, 1.9481060511014872, + ], + base_level=0, med_level=1, max_level=1, + tspan=(0.0, 0.0001), + adapt_initial_condition=false, + adapt_initial_condition_only_refine=false, + # With the default `maxiters = 1` in coverage tests, + # the values for `drag` and `lift` below would differ. + coverage_override=(maxiters = 100_000,)) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) # Just a placeholder in this case + + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + drag = Trixi.analyze(drag_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache) + lift = Trixi.analyze(lift_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache) + + @test isapprox(lift, 0.0262382560809345, atol = 1e-13) + @test isapprox(drag, 0.10898248971932244, atol = 1e-13) +end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_p4est_3d.jl b/test/test_p4est_3d.jl index ea7d9193add..7483cde2752 100644 --- a/test/test_p4est_3d.jl +++ b/test/test_p4est_3d.jl @@ -313,6 +313,35 @@ end end end +@trixi_testset "elixir_euler_ec.jl (flux_chandrashekar)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2=[ + 0.010368548525287055, + 0.006216054794583285, + 0.006020401857347216, + 0.006019175682769779, + 0.026228080232814154, + ], + linf=[ + 0.3169376449662026, + 0.28950510175646726, + 0.4402523227566396, + 0.4869168122387365, + 0.7999141641954051, + ], + tspan=(0.0, 0.2), + volume_flux=flux_chandrashekar, + coverage_override=(polydeg = 3,)) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end + @trixi_testset "elixir_euler_sedov.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), l2=[ @@ -535,6 +564,28 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end + +@trixi_testset "elixir_linearizedeuler_convergence.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_linearizedeuler_convergence.jl"), + l2=[ + 0.04452389418193219, 0.03688186699434862, + 0.03688186699434861, 0.03688186699434858, + 0.044523894181932186, + ], + linf=[ + 0.2295447498696467, 0.058369658071546704, + 0.05836965807154648, 0.05836965807154648, 0.2295447498696468, + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_parabolic_1d.jl b/test/test_parabolic_1d.jl index 41d375e2e31..38bebdcce1d 100644 --- a/test/test_parabolic_1d.jl +++ b/test/test_parabolic_1d.jl @@ -121,8 +121,8 @@ end ], linf=[ 0.002996375101363302, - 0.002863904256059634, - 0.012691132946258676, + 0.0028639041695096433, + 0.012691132694550689, ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -148,8 +148,8 @@ end ], linf=[ 0.002754803146635787, - 0.0028567714697580906, - 0.012941794048176192, + 0.0028567713744625124, + 0.012941793784197131, ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -168,14 +168,14 @@ end mu = mu(), Prandtl = prandtl_number()), l2=[ - 2.5278824700860636e-5, - 2.5540078777006958e-5, - 0.00012118655083858043, + 2.5278845598681636e-5, + 2.5540145802666872e-5, + 0.0001211867535580826, ], linf=[ - 0.0001466387075579334, - 0.00019422427462629705, - 0.0009556446847707178, + 0.0001466387202588848, + 0.00019422419092429135, + 0.0009556449835592673, ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -195,14 +195,14 @@ end Prandtl = prandtl_number(), gradient_variables = GradientVariablesEntropy()), l2=[ - 2.4593501090944024e-5, - 2.3928163240907908e-5, - 0.00011252309905552921, + 2.4593521887223632e-5, + 2.3928212900127102e-5, + 0.00011252332663824173, ], linf=[ - 0.0001185048754512863, - 0.0001898766501935486, - 0.0009597450028770993, + 0.00011850494672183132, + 0.00018987676556476442, + 0.0009597461727750556, ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) diff --git a/test/test_parabolic_2d.jl b/test/test_parabolic_2d.jl index d47c34f9e75..7749a0c4780 100644 --- a/test/test_parabolic_2d.jl +++ b/test/test_parabolic_2d.jl @@ -579,8 +579,8 @@ end @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", "elixir_advection_diffusion_nonperiodic_amr.jl"), tspan=(0.0, 0.01), - l2=[0.007933791324450538], - linf=[0.11029480573492567]) + l2=[0.007934195641974433], + linf=[0.11030265194954081]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -707,6 +707,55 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end + +@trixi_testset "elixir_navierstokes_NACA0012airfoil_mach08.jl" begin + @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", + "elixir_navierstokes_NACA0012airfoil_mach08.jl"), + l2=[0.000186486564226516, + 0.0005076712323400374, + 0.00038074588984354107, + 0.002128177239782089], + linf=[0.5153387072802718, + 1.199362305026636, + 0.9077214424040279, + 5.666071182328691], tspan=(0.0, 0.001), + initial_refinement_level=0, + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 10_000,)) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) # Just a placeholder in this case + + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + + drag_p = Trixi.analyze(drag_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache) + lift_p = Trixi.analyze(lift_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache) + + drag_f = Trixi.analyze(drag_coefficient_shear_force, du, u, tspan[2], mesh, + equations, equations_parabolic, solver, + semi.cache, semi.cache_parabolic) + lift_f = Trixi.analyze(lift_coefficient_shear_force, du, u, tspan[2], mesh, + equations, equations_parabolic, solver, + semi.cache, semi.cache_parabolic) + + @test isapprox(drag_p, 0.17963843913309516, atol = 1e-13) + @test isapprox(lift_p, 0.26462588007949367, atol = 1e-13) + + @test isapprox(drag_f, 1.5427441885921553, atol = 1e-13) + @test isapprox(lift_f, 0.005621910087395724, atol = 1e-13) +end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_structured_1d.jl b/test/test_structured_1d.jl index fea06554c57..f97696d089a 100644 --- a/test/test_structured_1d.jl +++ b/test/test_structured_1d.jl @@ -139,6 +139,21 @@ end end end +@trixi_testset "elixir_linearizedeuler_characteristic_system.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_linearizedeuler_characteristic_system.jl"), + l2=[2.9318078842789714e-6, 0.0, 0.0], + linf=[4.291208715723194e-5, 0.0, 0.0]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end + @trixi_testset "elixir_traffic_flow_lwr_greenlight.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_traffic_flow_lwr_greenlight.jl"), diff --git a/test/test_structured_2d.jl b/test/test_structured_2d.jl index 42c56017fe8..47d180bf813 100644 --- a/test/test_structured_2d.jl +++ b/test/test_structured_2d.jl @@ -70,6 +70,30 @@ end end end +@trixi_testset "elixir_advection_meshview.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_meshview.jl"), + l2=[ + 8.311947673083206e-6, + 8.311947673068427e-6, + ], + linf=[ + 6.627000273318195e-5, + 6.62700027264096e-5, + ], + coverage_override=(maxiters = 10^5,)) + + @testset "analysis_callback(sol) for AnalysisCallbackCoupled" begin + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end +end + @trixi_testset "elixir_advection_extended.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), l2=[4.220397559713772e-6], @@ -1084,15 +1108,15 @@ end @trixi_testset "elixir_shallowwater_source_terms.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), l2=[ - 0.0017285599436729316, - 0.025584610912606776, - 0.028373834961180594, + 0.0017286908591070864, + 0.025585037307655684, + 0.028374244567802766, 6.274146767730866e-5, ], linf=[ - 0.012972309788264802, - 0.108283714215621, - 0.15831585777928936, + 0.012973752001194772, + 0.10829375385832263, + 0.15832858475438094, 0.00018196759554722775, ], tspan=(0.0, 0.05)) @@ -1152,6 +1176,61 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end + +@trixi_testset "elixir_mhd_coupled.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_coupled.jl"), + l2=[ + 1.0743426980507015e-7, 0.030901698521864966, + 0.030901698662039206, 0.04370160129981656, + 8.259193827852516e-8, 0.03090169908364623, + 0.030901699039770684, 0.04370160128147447, + 8.735923402748945e-9, 1.0743426996067106e-7, + 0.03090169852186498, 0.030901698662039206, + 0.04370160129981657, 8.259193829690747e-8, + 0.03090169908364624, 0.030901699039770726, + 0.04370160128147445, 8.73592340076897e-9, + ], + linf=[ + 9.021023431587949e-7, 0.043701454182710486, + 0.043701458294527366, 0.061803146322536154, + 9.487023335807976e-7, 0.043701561010342616, + 0.04370147392153734, 0.06180318786081025, + 3.430673132525334e-8, 9.02102342825728e-7, + 0.043701454182710764, 0.043701458294525895, + 0.06180314632253597, 9.487023254761695e-7, + 0.04370156101034084, 0.04370147392153745, + 0.06180318786081015, 3.430672973680963e-8, + ], + coverage_override=(maxiters = 10^5,)) + + @testset "analysis_callback(sol) for AnalysisCallbackCoupled" begin + errors = analysis_callback(sol) + @test errors.l2≈[ + 1.0743426980507015e-7, 0.030901698521864966, 0.030901698662039206, + 0.04370160129981656, 8.259193827852516e-8, 0.03090169908364623, + 0.030901699039770684, 0.04370160128147447, 8.735923402748945e-9, + 1.0743426996067106e-7, 0.03090169852186498, 0.030901698662039206, + 0.04370160129981657, 8.259193829690747e-8, 0.03090169908364624, + 0.030901699039770726, 0.04370160128147445, 8.73592340076897e-9, + ] rtol=1.0e-4 + @test errors.linf≈[ + 9.021023431587949e-7, 0.043701454182710486, 0.043701458294527366, + 0.061803146322536154, 9.487023335807976e-7, 0.043701561010342616, + 0.04370147392153734, 0.06180318786081025, 3.430673132525334e-8, + 9.02102342825728e-7, 0.043701454182710764, 0.043701458294525895, + 0.06180314632253597, 9.487023254761695e-7, 0.04370156101034084, + 0.04370147392153745, 0.06180318786081015, 3.430672973680963e-8, + ] rtol=1.0e-4 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end +end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_tree_1d.jl b/test/test_tree_1d.jl index 4a25a51a45e..76129f15e07 100644 --- a/test/test_tree_1d.jl +++ b/test/test_tree_1d.jl @@ -48,6 +48,9 @@ isdir(outdir) && rm(outdir, recursive = true) # Traffic flow LWR include("test_tree_1d_traffic_flow_lwr.jl") + + # Linearized Euler + include("test_tree_1d_linearizedeuler.jl") end # Coverage test for all initial conditions diff --git a/test/test_tree_1d_linearizedeuler.jl b/test/test_tree_1d_linearizedeuler.jl new file mode 100644 index 00000000000..c7cffee3f66 --- /dev/null +++ b/test/test_tree_1d_linearizedeuler.jl @@ -0,0 +1,51 @@ + +using Test +using Trixi + +include("test_trixi.jl") + +EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") + +@testset "Linearized Euler Equations 1D" begin +#! format: noindent + +@trixi_testset "elixir_linearizedeuler_convergence.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_convergence.jl"), + l2=[ + 0.00010894927270421941, + 0.00014295255695912358, + 0.00010894927270421941, + ], + linf=[ + 0.0005154647164193893, + 0.00048457837684242266, + 0.0005154647164193893, + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end + +@trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), + l2=[0.650082087850354, 0.2913911415488769, 0.650082087850354], + linf=[ + 1.9999505145390108, + 0.9999720404625275, + 1.9999505145390108, + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end +end diff --git a/test/test_tree_1d_shallowwater.jl b/test/test_tree_1d_shallowwater.jl index 41ad5c32bbd..8fe3291a938 100644 --- a/test/test_tree_1d_shallowwater.jl +++ b/test/test_tree_1d_shallowwater.jl @@ -12,10 +12,14 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @trixi_testset "elixir_shallowwater_ec.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), - l2=[0.244729018751225, 0.8583565222389505, 0.07330427577586297], + l2=[ + 0.24476140682560343, + 0.8587309324660326, + 0.07330427577586297, + ], linf=[ - 2.1635021283528504, - 3.8717508164234453, + 2.1636963952308372, + 3.8737770522883115, 1.7711213427919539, ], tspan=(0.0, 0.25)) @@ -32,13 +36,13 @@ end @trixi_testset "elixir_shallowwater_ec.jl with initial_condition_weak_blast_wave" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), l2=[ - 0.39464782107209717, - 2.03880864210846, + 0.39472828074570576, + 2.0390687947320076, 4.1623084150546725e-10, ], linf=[ - 0.778905801278281, - 3.2409883402608273, + 0.7793741954662221, + 3.2411927977882096, 7.419800190922032e-10, ], initial_condition=initial_condition_weak_blast_wave, diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index a32ba98ada2..0f42806d91a 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -284,6 +284,38 @@ end end end +@trixi_testset "elixir_euler_shockcapturing_subcell.jl (fixed time step)" begin + # Testing local SSP method without stepsize callback + # Additionally, tests combination with SaveSolutionCallback using time interval + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_shockcapturing_subcell.jl"), + dt=2.0e-3, + tspan=(0.0, 0.25), + save_solution=SaveSolutionCallback(dt = 0.1 + 1.0e-8), + callbacks=CallbackSet(summary_callback, save_solution, + analysis_callback, alive_callback), + l2=[ + 0.05624855363458103, + 0.06931288786158463, + 0.06931283188960778, + 0.6200535829842072, + ], + linf=[ + 0.29029967648805566, + 0.6494728865862608, + 0.6494729363533714, + 3.0949621505674787, + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + @trixi_testset "elixir_euler_blast_wave.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave.jl"), l2=[ @@ -393,16 +425,16 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_sc_subcell_nonperiodic.jl"), l2=[ - 0.32211935909459755, - 0.17984734129031393, - 0.17983517637561672, - 0.6136858672602447, + 0.3221177942225801, + 0.1798478357478982, + 0.1798364616438908, + 0.6136884131056267, ], linf=[ - 1.3435940880493509, - 1.1748248970276045, - 1.1745481442875036, - 2.421529617190895, + 1.343766644801395, + 1.1749593109683463, + 1.1747613085307178, + 2.4216006041018785, ], tspan=(0.0, 0.5), initial_refinement_level=4, diff --git a/test/test_tree_2d_eulermulti.jl b/test/test_tree_2d_eulermulti.jl index 323d88c01e3..591854a5d00 100644 --- a/test/test_tree_2d_eulermulti.jl +++ b/test/test_tree_2d_eulermulti.jl @@ -98,11 +98,11 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl"), l2=[ - 76.59096367977872, - 1.9879932386864356, - 59851.34515039375, - 0.18710988181124935, - 0.010631432251136084, + 73.10860950390489, + 1.4599090197303102, + 57176.23978426408, + 0.17812910616624406, + 0.010123079422717837, ], linf=[ 212.71245739310544, diff --git a/test/test_tree_2d_shallowwater.jl b/test/test_tree_2d_shallowwater.jl index 01742644736..9a3ba36c7d4 100644 --- a/test/test_tree_2d_shallowwater.jl +++ b/test/test_tree_2d_shallowwater.jl @@ -13,15 +13,15 @@ EXAMPLES_DIR = joinpath(examples_dir(), "tree_2d_dgsem") @trixi_testset "elixir_shallowwater_ec.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), l2=[ - 0.991181203601035, - 0.734130029040644, - 0.7447696147162621, + 0.9911802019934329, + 0.7340106828033273, + 0.7446338002084801, 0.5875351036989047, ], linf=[ - 2.0117744577945413, - 2.9962317608172127, - 2.6554999727293653, + 2.0120253138457564, + 2.991158989293406, + 2.6557412817714035, 3.0, ], tspan=(0.0, 0.25)) @@ -280,15 +280,15 @@ end @trixi_testset "elixir_shallowwater_wall.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_wall.jl"), l2=[ - 0.13517233723296504, - 0.20010876311162215, - 0.20010876311162223, + 0.1351723240085936, + 0.20010881416550014, + 0.2001088141654999, 2.719538414346464e-7, ], linf=[ - 0.5303607982988336, - 0.5080989745682338, - 0.5080989745682352, + 0.5303608302490757, + 0.5080987791967457, + 0.5080987791967506, 1.1301675764130437e-6, ], tspan=(0.0, 0.25)) diff --git a/test/test_tree_3d_linearizedeuler.jl b/test/test_tree_3d_linearizedeuler.jl new file mode 100644 index 00000000000..00f8d62dad9 --- /dev/null +++ b/test/test_tree_3d_linearizedeuler.jl @@ -0,0 +1,35 @@ + +using Test +using Trixi + +include("test_trixi.jl") + +EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_dgsem") + +@testset "Linearized Euler Equations 3D" begin +#! format: noindent + +@trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), + l2=[ + 0.020380328336745232, 0.027122442311921492, + 0.02712244231192152, 8.273108096127844e-17, + 0.020380328336745232, + ], + linf=[ + 0.2916021983572774, 0.32763703462270843, + 0.32763703462270855, 1.641012595221666e-15, + 0.2916021983572774, + ], + tspan=(0.0, 1.0)) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end +end diff --git a/test/test_tree_3d_part2.jl b/test/test_tree_3d_part2.jl index 5c7301654ad..4b9da039f98 100644 --- a/test/test_tree_3d_part2.jl +++ b/test/test_tree_3d_part2.jl @@ -22,6 +22,9 @@ isdir(outdir) && rm(outdir, recursive = true) # Compressible Euler with self-gravity include("test_tree_3d_eulergravity.jl") + + # Linearized Euler + include("test_tree_3d_linearizedeuler.jl") end @trixi_testset "Additional tests in 3D" begin diff --git a/test/test_trixi.jl b/test/test_trixi.jl index cebe2164ae6..78195825886 100644 --- a/test/test_trixi.jl +++ b/test/test_trixi.jl @@ -153,7 +153,9 @@ macro test_nowarn_mod(expr, additional_ignore_content = String[]) r"┌ Warning: Keyword argument letter not supported with Plots.+\n└ @ Plots.+\n", r"┌ Warning: `parse\(::Type, ::Coloarant\)` is deprecated.+\n│.+\n│.+\n└ @ Plots.+\n", # TODO: Silence warning introduced by Flux v0.13.13. Should be properly fixed. - r"┌ Warning: Layer with Float32 parameters got Float64 input.+\n│.+\n│.+\n│.+\n└ @ Flux.+\n"] + r"┌ Warning: Layer with Float32 parameters got Float64 input.+\n│.+\n│.+\n│.+\n└ @ Flux.+\n", + # NOTE: These warnings arose from Julia 1.10 onwards + r"WARNING: Method definition .* in module .* at .* overwritten .*.\n"] append!(ignore_content, $additional_ignore_content) for pattern in ignore_content stderr_content = replace(stderr_content, pattern => "") diff --git a/test/test_unit.jl b/test/test_unit.jl index 575df8d493f..2e20c1c5805 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -284,7 +284,7 @@ end function MyContainer(data, capacity) c = MyContainer(Vector{Int}(undef, capacity + 1), capacity, length(data), capacity + 1) - c.data[1:length(data)] .= data + c.data[eachindex(data)] .= data return c end MyContainer(data::AbstractArray) = MyContainer(data, length(data)) @@ -416,9 +416,9 @@ end indicator_hg = IndicatorHennemannGassner(1.0, 0.0, true, "variable", "cache") @test_nowarn show(stdout, indicator_hg) - limiter_idp = SubcellLimiterIDP(true, [1], true, [1], ["variable"], 0.1, true, true, - true, "cache", 1, (1.0, 1.0), 1.0, true, 1.0, - nothing) + limiter_idp = SubcellLimiterIDP(true, [1], true, [1], ["variable"], 0.1, + true, [(Trixi.entropy_guermond_etal, min)], "cache", + 1, (1.0, 1.0), 1.0, true, 1.0, nothing) @test_nowarn show(stdout, limiter_idp) limiter_mcl = SubcellLimiterMCL("cache", true, true, true, true, true, true, true, @@ -1436,7 +1436,8 @@ end u_values = [SVector(1.0, 0.5, -0.7, 1.0), SVector(1.5, -0.2, 0.1, 5.0)] fluxes = [flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, - FluxLMARS(340), flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, flux_hllc, + FluxLMARS(340), flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, + flux_hllc, flux_chandrashekar, ] for f_std in fluxes @@ -1460,7 +1461,8 @@ end u_values = [SVector(1.0, 0.5, -0.7, 0.1, 1.0), SVector(1.5, -0.2, 0.1, 0.2, 5.0)] fluxes = [flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, - FluxLMARS(340), flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, flux_hllc, + FluxLMARS(340), flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, + flux_hllc, flux_chandrashekar, ] for f_std in fluxes @@ -1541,6 +1543,60 @@ end end end +@testset "Equivalent Wave Speed Estimates" begin + @timed_testset "Linearized Euler 3D" begin + equations = LinearizedEulerEquations3D(v_mean_global = (0.42, 0.37, 0.7), + c_mean_global = 1.0, + rho_mean_global = 1.0) + + normal_x = SVector(1.0, 0.0, 0.0) + normal_y = SVector(0.0, 1.0, 0.0) + normal_z = SVector(0.0, 0.0, 1.0) + + u_ll = SVector(0.3, 0.5, -0.7, 0.1, 1.0) + u_rr = SVector(0.5, -0.2, 0.1, 0.2, 5.0) + + @test all(isapprox(x, y) + for (x, y) in zip(max_abs_speed_naive(u_ll, u_rr, 1, equations), + max_abs_speed_naive(u_ll, u_rr, normal_x, + equations))) + @test all(isapprox(x, y) + for (x, y) in zip(max_abs_speed_naive(u_ll, u_rr, 2, equations), + max_abs_speed_naive(u_ll, u_rr, normal_y, + equations))) + @test all(isapprox(x, y) + for (x, y) in zip(max_abs_speed_naive(u_ll, u_rr, 3, equations), + max_abs_speed_naive(u_ll, u_rr, normal_z, + equations))) + + @test all(isapprox(x, y) + for (x, y) in zip(min_max_speed_naive(u_ll, u_rr, 1, equations), + min_max_speed_naive(u_ll, u_rr, normal_x, + equations))) + @test all(isapprox(x, y) + for (x, y) in zip(min_max_speed_naive(u_ll, u_rr, 2, equations), + min_max_speed_naive(u_ll, u_rr, normal_y, + equations))) + @test all(isapprox(x, y) + for (x, y) in zip(min_max_speed_naive(u_ll, u_rr, 3, equations), + min_max_speed_naive(u_ll, u_rr, normal_z, + equations))) + + @test all(isapprox(x, y) + for (x, y) in zip(min_max_speed_davis(u_ll, u_rr, 1, equations), + min_max_speed_davis(u_ll, u_rr, normal_x, + equations))) + @test all(isapprox(x, y) + for (x, y) in zip(min_max_speed_davis(u_ll, u_rr, 2, equations), + min_max_speed_davis(u_ll, u_rr, normal_y, + equations))) + @test all(isapprox(x, y) + for (x, y) in zip(min_max_speed_davis(u_ll, u_rr, 3, equations), + min_max_speed_davis(u_ll, u_rr, normal_z, + equations))) + end +end + @testset "SimpleKronecker" begin N = 3 diff --git a/test/test_unstructured_2d.jl b/test/test_unstructured_2d.jl index 6814250dd47..5c228d1e04c 100644 --- a/test/test_unstructured_2d.jl +++ b/test/test_unstructured_2d.jl @@ -41,8 +41,8 @@ end 1.4585198700082895e-13, 4.716940764877479e-13, ], linf=[ - 8.804956763697191e-12, 6.261199891888225e-11, - 2.936639820205755e-11, 1.20543575121701e-10, + 7.774003663030271e-12, 9.183176441496244e-11, + 4.5685344396417804e-11, 1.0534506600379245e-10, ], tspan=(0.0, 0.1), atol=3.0e-13) @@ -301,15 +301,15 @@ end @trixi_testset "elixir_shallowwater_ec.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), l2=[ - 0.6106939484178353, - 0.48586236867426724, - 0.48234490854514356, - 0.29467422718511727, + 0.6107326269462766, + 0.48666631722018877, + 0.48309775159067053, + 0.29467422718511704, ], linf=[ - 2.775979948281604, - 3.1721242154451548, - 3.5713448319601393, + 2.776782342826098, + 3.2158378644333707, + 3.652920889487258, 2.052861364219655, ], tspan=(0.0, 0.25)) @@ -328,13 +328,13 @@ end l2=[ 1.2164292510839076, 2.6118925543469468e-12, - 1.1636046671473883e-12, + 2.459878823146057e-12, 1.2164292510839079, ], linf=[ 1.5138512282315846, - 4.998482888288039e-11, - 2.0246214978154587e-11, + 4.706289937431355e-11, + 4.913910192312011e-11, 1.513851228231574, ], tspan=(0.0, 0.25)) @@ -353,13 +353,13 @@ end l2=[ 1.2164292510839085, 1.2643106818778908e-12, - 7.46884905098358e-13, + 1.269230436589819e-12, 1.2164292510839079, ], linf=[ 1.513851228231562, - 1.6287765844373185e-11, - 6.8766999132716964e-12, + 1.6670644673575802e-11, + 1.8426585188623954e-11, 1.513851228231574, ], surface_flux=(FluxHydrostaticReconstruction(flux_lax_friedrichs, @@ -381,13 +381,13 @@ end l2=[ 1.2164292510839083, 2.590643638636187e-12, - 1.0945471514840143e-12, + 2.388742604639019e-12, 1.2164292510839079, ], linf=[ 1.5138512282315792, - 5.0276441977281156e-11, - 1.9816934589292803e-11, + 4.761278694199934e-11, + 4.910549479958249e-11, 1.513851228231574, ], surface_flux=(flux_wintermeyer_etal, @@ -408,16 +408,16 @@ end @trixi_testset "elixir_shallowwater_source_terms.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), l2=[ - 0.0011197623982310795, - 0.04456344888447023, - 0.014317376629669337, - 5.089218476758975e-6, + 0.001118134082248467, + 0.044560486817464634, + 0.01430926600634214, + 5.089218476759981e-6, ], linf=[ - 0.007835284004819698, - 0.3486891284278597, - 0.11242778979399048, - 2.6407324614119432e-5, + 0.007798727223654822, + 0.34782952734839157, + 0.11161614702628064, + 2.6407324614341476e-5, ], tspan=(0.0, 0.025)) # Ensure that we do not have excessive memory allocations @@ -433,15 +433,15 @@ end @trixi_testset "elixir_shallowwater_source_terms.jl with FluxHydrostaticReconstruction" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), l2=[ - 0.001119678684752799, - 0.015429108794630785, - 0.01708275441241111, - 5.089218476758271e-6, + 0.0011196838135485918, + 0.01542895635133927, + 0.017082803023121197, + 5.089218476759981e-6, ], linf=[ - 0.014299564388827513, - 0.12785126473870534, - 0.17626788561725526, + 0.014299541415654371, + 0.12783948113206955, + 0.17626489583921323, 2.6407324614341476e-5, ], surface_flux=(FluxHydrostaticReconstruction(flux_hll, @@ -461,15 +461,15 @@ end @trixi_testset "elixir_shallowwater_source_terms.jl with flux_nonconservative_ersing_etal" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), l2=[ - 0.0011196687776346434, - 0.044562672453443995, - 0.014306265289763618, + 0.001118046975499805, + 0.04455969246244461, + 0.014298120235633432, 5.089218476759981e-6, ], linf=[ - 0.007825021762002393, - 0.348550815397918, - 0.1115517935018282, + 0.007776521213640031, + 0.34768318303226353, + 0.11075311228066198, 2.6407324614341476e-5, ], surface_flux=(flux_wintermeyer_etal, @@ -490,15 +490,15 @@ 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.0011196786847528799, - 0.015429108794631075, - 0.017082754412411742, + 0.0011196838135486059, + 0.015428956351339451, + 0.017082803023120943, 5.089218476759981e-6, ], linf=[ - 0.014299564388830177, - 0.12785126473870667, - 0.17626788561728546, + 0.01429954141565526, + 0.12783948113205668, + 0.176264895839215, 2.6407324614341476e-5, ], surface_flux=(flux_hll, @@ -561,15 +561,15 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec_shockcapturing.jl"), l2=[ - 0.6124656312639043, - 0.504371951785709, - 0.49180896200746366, - 0.29467422718511727, + 0.612551520607341, + 0.5039173660221961, + 0.49136517934903523, + 0.29467422718511704, ], linf=[ - 2.7639232436274392, - 3.3985508653311767, - 3.3330308209196224, + 2.7636771472622197, + 3.236168963021072, + 3.3363936775653826, 2.052861364219655, ], tspan=(0.0, 0.25)) @@ -650,7 +650,7 @@ end l2=[4.085391175504837e-5, 7.19179253772227e-5, 7.191792537723135e-5, - 0.00021775241532855398], + 0.0002177522206115571], linf=[0.0004054489124620808, 0.0006164432358217731, 0.0006164432358186644, @@ -701,7 +701,7 @@ end linf=[3.354871935812298e-11, 7.006478730531285e-12, 1.148153794261475e-11, - 9.041265514042607e-10], + 7.461231632532872e-10], tspan=(0.0, 0.05), atol=1.0e-10) # Ensure that we do not have excessive memory allocations diff --git a/utils/trixi2txt.jl b/utils/trixi2txt.jl index 12a3d46760e..52ee904d2f6 100644 --- a/utils/trixi2txt.jl +++ b/utils/trixi2txt.jl @@ -86,7 +86,7 @@ function trixi2txt(filename::AbstractString...; "maximum supported level $max_supported_level") end max_available_nodes_per_finest_element = 2^(max_supported_level - max_level) - if nvisnodes == nothing + if nvisnodes === nothing max_nvisnodes = 2 * n_nodes elseif nvisnodes == 0 max_nvisnodes = n_nodes @@ -137,9 +137,9 @@ function trixi2txt(filename::AbstractString...; println(io) # Data - for idx in 1:length(xs) + for idx in eachindex(xs) @printf(io, "%+10.8e", xs[idx]) - for variable_id in 1:length(variables) + for variable_id in eachindex(variables) @printf(io, " %+10.8e ", node_centered_data[idx, variable_id]) end println(io) @@ -199,7 +199,7 @@ function read_meshfile(filename::String) # Extract leaf cells (= cells to be plotted) and contract all other arrays accordingly leaf_cells = similar(levels) n_cells = 0 - for cell_id in 1:length(levels) + for cell_id in eachindex(levels) if sum(child_ids[:, cell_id]) > 0 continue end