Path: blob/main/docs/literate/src/files/adding_new_scalar_equations.jl
5591 views
#src # Adding a new equation: scalar conservation laws12# If you want to use Trixi.jl for your own research, you might be interested in3# a new physics model that's not already included in Trixi.jl. In this tutorial,4# we will implement the cubic conservation law5# ```math6# \partial_t u(t,x) + \partial_x u(t,x)^3 = 07# ```8# in a periodic domain in one space dimension. In Trixi.jl, such a mathematical model9# is encoded as a subtype of [`Trixi.AbstractEquations`](@ref).1011# ## Basic setup12using Trixi1314struct CubicEquation <: Trixi.AbstractEquations{1, # number of spatial dimensions151} # number of primary variables, i.e. scalar16end1718# We create `CubicEquation` as an empty `struct` since we do not use any parameters19# for this equation. Other models could bundle arbitrary parameters, e.g., the20# ideal gas constant for the compressible Euler equations.2122# Next, we define the physical flux `f(u) = u^3` using the calling structure23# used in Trixi.jl.2425Trixi.flux(u, orientation, equation::CubicEquation) = u .^ 326Trixi.varnames(_, ::CubicEquation) = ("scalar",)2728# In Trixi.jl, the conserved variables `u` are usually passed as `SVector`s of variables29# at a single physical location. Hence, we must use `u.^3` instead of the scalar30# operation `u^3`.3132# That's already enough to run a simple simulation with a standard DGSEM discretization33# using the non-dissipative central flux at interfaces.3435using OrdinaryDiffEqSSPRK3637## Create a simulation setup38equation = CubicEquation()3940initial_condition_sine(x, t, equation::CubicEquation) = SVector(sinpi(x[1]))4142mesh = TreeMesh(-1.0, 1.0, # min/max coordinates43initial_refinement_level = 4,44n_cells_max = 10^4,45periodicity = true)4647solver = DGSEM(3, flux_central) # set polynomial degree to 34849semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;50boundary_conditions = boundary_condition_periodic)5152# We wrap the return value of the `initial_condition_sine` inside an `SVector` since that's the approach53# used in Trixi.jl also for systems of equations. We need to index the spatial coordinate `x[1]`,54# since it is an `SVector` with one component. In multiple space dimensions, all spatial coordinates55# are passed together.5657# Next, we create an `ODEProblem` from the SciML/DifferentialEquations ecosystem.58# We can solve this ODE numerically using any time integration method,59# e.g., `SSPRK43` from [OrdinaryDiffEqSSPRK.jl](https://github.com/SciML/OrdinaryDiffEq.jl).60# Before, we set up a [callback](@ref callbacks-id) to summarize the simulation setup.6162## Create ODE problem with given time span63tspan = (0.0, 0.09)64ode = semidiscretize(semi, tspan)6566summary_callback = SummaryCallback()67callbacks = CallbackSet(summary_callback)6869## OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks70sol = solve(ode, SSPRK43();71ode_default_options()..., callback = callbacks);7273# That's it, you ran your first simulation using your new equation with Trixi.jl! Now, we can plot74# the solution at the final time using Plots.jl.75using Plots76plot(sol)7778# You can already see that discontinuities will develop and oscillations start to79# occur around steep parts of the wave. That's expected from our central discretization.80# To avoid these issues, we need to use dissipative numerical fluxes (approximate81# Riemann solvers) at interfaces.8283# ## Advanced setup8485# Thus, we add a Godunov's flux for our cubic equation. That is easy for this equation86# since the wave speed `f'(u) = 3u^2` is always non-negative.8788@inline Trixi.flux_godunov(u_ll, u_rr, orientation, equation::CubicEquation) = flux(u_ll,89orientation,90equation)9192# Let's run the example again but with a dissipative numerical flux at interfaces.93# `remake` will recreate the semidiscretization we used before and only change94# selected parameters, in this case the `solver`.9596## A new setup with dissipation97semi = remake(semi, solver = DGSEM(3, flux_godunov))98ode = semidiscretize(semi, tspan)99sol = solve(ode, SSPRK43(); ode_default_options()...)100plot!(sol)101102# You can see that there are fewer oscillations, in particular around steep edges.103# Now let's increase the final time (and also the spatial resolution).104105## A larger final time: Nonclassical shocks develop (you can even increase the refinement to 12)106semi = remake(semi,107mesh = TreeMesh(-1.0, 1.0, initial_refinement_level = 8, n_cells_max = 10^5,108periodicity = true))109ode = semidiscretize(semi, (0.0, 0.5)) # set tspan to (0.0, 0.5)110sol = solve(ode, SSPRK43(); ode_default_options()...)111plot(sol)112113# You can observe that nonclassical shocks develop and are stable under grid refinement,114# e.g. for `initial_refinement_level=12`. In this case, these nonclassical shocks115# can be avoided by using an entropy-dissipative semidiscretization. Thus, we need116# to define an entropy-conservative numerical flux117118@inline function Trixi.flux_ec(u_ll, u_rr, orientation, equation::CubicEquation)119return SVector(0.25 *120(u_ll[1]^3 + u_ll[1]^2 * u_rr[1] + u_ll[1] * u_rr[1]^2 + u_rr[1]^3))121end122123# and use a [`VolumeIntegralFluxDifferencing`](@ref) instead of the standard124# [`VolumeIntegralWeakForm`](@ref) in the DGSEM.125126## Let's use a provably entropy-dissipative semidiscretization127semi = remake(semi,128solver = DGSEM(3, flux_godunov, VolumeIntegralFluxDifferencing(flux_ec)))129ode = semidiscretize(semi, (0.0, 0.5))130sol = solve(ode, SSPRK43(); ode_default_options()...);131plot(sol)132133# Possible next steps could be134# - to define `Trixi.max_abs_speeds(u, equations::CubicEquation) = 3 * u[1]^2`135# to use CFL-based time step control via a [`StepsizeCallback`](@ref)136# - to define quantities of interest like `Trixi.entropy(u, equations::CubicEquation) = u[1]^2`137# and integrate them in a simulation using the [`AnalysisCallback`](@ref)138# - to experiment with shock-capturing volume integrals [`VolumeIntegralShockCapturingHG`](@ref)139# and adaptive mesh refinement [`AMRCallback`](@ref)140141# For further reading, Trixi.jl provides another example on adding a scalar equation. In the142# [elixir about the KPP problem](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/tree_2d_dgsem/elixir_kpp.jl),143# the 2D scalar "KPP equation" from [Kurganov, Petrova, Popov (2007)](https://doi.org/10.1137/040614189) is144# implemented.145146# ## Summary of the code147148# To sum up, here is the complete code that we used (without the callbacks since these create a149# lot of unnecessary output in the doctests of this tutorial).150# In addition, we create the `struct` inside the new module `CubicConservationLaw`. That151# ensures that we can re-create `struct`s defined therein without having to restart Julia.152153## Define new physics154module CubicConservationLaw155156using Trixi157158struct CubicEquation <: Trixi.AbstractEquations{1, # number of spatial dimensions1591} # number of primary variables, i.e. scalar160end161162@inline Trixi.flux(u, orientation, equation::CubicEquation) = u .^ 3163Trixi.varnames(_, ::CubicEquation) = ("scalar",)164165@inline Trixi.flux_godunov(u_ll, u_rr, orientation, equation::CubicEquation) = flux(u_ll,166orientation,167equation)168@inline function Trixi.flux_ec(u_ll, u_rr, orientation, equation::CubicEquation)169return SVector(0.25 *170(u_ll[1]^3 + u_ll[1]^2 * u_rr[1] + u_ll[1] * u_rr[1]^2 + u_rr[1]^3))171end172173end # module174175## Create a simulation setup176import .CubicConservationLaw177using Trixi178using OrdinaryDiffEqSSPRK179using Plots180181equation = CubicConservationLaw.CubicEquation()182183function initial_condition_sine(x, t, equation::CubicConservationLaw.CubicEquation)184return SVector(sinpi(x[1]))185end186187mesh = TreeMesh(-1.0, 1.0, # min/max coordinates188initial_refinement_level = 4,189n_cells_max = 10^4,190periodicity = true)191192solver = DGSEM(3, flux_central) # set polynomial degree to 3193194semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;195boundary_conditions = boundary_condition_periodic)196197## Create ODE problem with given time span198tspan = (0.0, 0.1)199ode = semidiscretize(semi, tspan)200201## OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks202sol = solve(ode, SSPRK43(); ode_default_options()...)203plot(sol)204205## A new setup with dissipation206semi = remake(semi, solver = DGSEM(3, flux_godunov))207ode = semidiscretize(semi, tspan)208sol = solve(ode, SSPRK43(); ode_default_options()...)209plot!(sol)210211## A larger final time: Nonclassical shocks develop (you can even increase the refinement to 12)212semi = remake(semi,213mesh = TreeMesh(-1.0, 1.0, initial_refinement_level = 8, n_cells_max = 10^5,214periodicity = true))215ode = semidiscretize(semi, (0.0, 0.5))216sol = solve(ode, SSPRK43(); ode_default_options()...)217plot(sol)218219## Let's use a provably entropy-dissipative semidiscretization220semi = remake(semi,221solver = DGSEM(3, flux_godunov, VolumeIntegralFluxDifferencing(flux_ec)))222ode = semidiscretize(semi, (0.0, 0.5))223sol = solve(ode, SSPRK43(); ode_default_options()...)224plot(sol)225226# ## Package versions227228# These results were obtained using the following versions.229230using InteractiveUtils231versioninfo()232233using Pkg234Pkg.status(["Trixi", "OrdinaryDiffEqSSPRK", "Plots"],235mode = PKGMODE_MANIFEST)236237238