Path: blob/main/docs/literate/src/files/adding_nonconservative_equation.jl
5591 views
#src # Adding a new equation: nonconservative linear advection1using Test: @test #src23# If you want to use Trixi.jl for your own research, you might be interested in4# a new physics model that is not present in Trixi.jl. In this tutorial,5# we will implement the nonconservative linear advection equation in a periodic domain6# ```math7# \left\{8# \begin{aligned}&\partial_t u(t,x) + a(x) \partial_x u(t,x) = 0 \\9# &u(0,x)=\sin(x) \\10# &u(t,-\pi)=u(t,\pi)11# \end{aligned}12# \right.13# ```14# where $a(x) = 2 + \cos(x)$. The analytic solution is15# ```math16# u(t,x)=-\sin \left(2 \tan ^{-1}\left(\sqrt{3} \tan \left(\frac{\sqrt{3} t}{2}-\tan ^{-1}\left(\frac{1}{\sqrt{3}}\tan \left(\frac{x}{2}\right)\right)\right)\right)\right)17# ```18# In Trixi.jl, such a mathematical model19# is encoded as a subtype of [`Trixi.AbstractEquations`](@ref).2021# ## Basic setup2223# Since there is no native support for variable coefficients, we need to transform the PDE to the following system:24# ```math25# \left\{26# \begin{aligned}&\partial_t \begin{pmatrix}u(t,x)\\a(t,x) \end{pmatrix} +\begin{pmatrix} a(t,x) \partial_x u(t,x) \\ 0 \end{pmatrix} = 0 \\27# &u(0,x)=\sin(x) \\28# &a(0,x)=2+\cos(x) \\29# &u(t,-\pi)=u(t,\pi)30# \end{aligned}31# \right.32# ```3334## Define new physics35using Trixi36using Trixi: AbstractEquations, get_node_vars37import Trixi: varnames, default_analysis_integrals, flux, max_abs_speed_naive,38have_nonconservative_terms3940## Since there is no native support for variable coefficients, we use two41## variables: one for the basic unknown `u` and another one for the coefficient `a`42struct NonconservativeLinearAdvectionEquation <: AbstractEquations{1, # spatial dimension432} # two variables (u,a)44end4546function varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation)47return ("scalar", "advection_velocity")48end4950default_analysis_integrals(::NonconservativeLinearAdvectionEquation) = ()5152## The conservative part of the flux is zero53flux(u, orientation, equation::NonconservativeLinearAdvectionEquation) = zero(u)5455## Calculate maximum wave speed for local Lax-Friedrichs-type dissipation56function max_abs_speed_naive(u_ll, u_rr, orientation::Integer,57::NonconservativeLinearAdvectionEquation)58_, advection_velocity_ll = u_ll59_, advection_velocity_rr = u_rr6061return max(abs(advection_velocity_ll), abs(advection_velocity_rr))62end6364## We use nonconservative terms65have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.True()6667## This "nonconservative numerical flux" implements the nonconservative terms.68## In general, nonconservative terms can be written in the form69## g(u) ∂ₓ h(u)70## Thus, a discrete difference approximation of this nonconservative term needs71## - `u mine`: the value of `u` at the current position (for g(u))72## - `u_other`: the values of `u` in a neighborhood of the current position (for ∂ₓ h(u))73function flux_nonconservative(u_mine, u_other, orientation,74equations::NonconservativeLinearAdvectionEquation)75_, advection_velocity = u_mine76scalar, _ = u_other7778return SVector(advection_velocity * scalar, zero(scalar))79end8081# The implementation of nonconservative terms uses a single "nonconservative flux"82# function `flux_nonconservative`. It will basically be applied in a loop of the83# form84# ````julia85# du_m(D, u) = sum(D[m, l] * flux_nonconservative(u[m], u[l], 1, equations)) # orientation 1: x86# ````87# where `D` is the derivative matrix and `u` contains the nodal solution values.8889# Now, we can run a simple simulation using a DGSEM discretization.9091## Create a simulation setup92using Trixi93using OrdinaryDiffEqTsit59495equation = NonconservativeLinearAdvectionEquation()9697## You can derive the exact solution for this setup using the method of98## characteristics99function initial_condition_sine(x, t, equation::NonconservativeLinearAdvectionEquation)100x0 = -2 * atan(sqrt(3) * tan(sqrt(3) / 2 * t - atan(tan(x[1] / 2) / sqrt(3))))101scalar = sin(x0)102advection_velocity = 2 + cos(x[1])103return SVector(scalar, advection_velocity)104end105106## Create a uniform mesh in 1D in the interval [-π, π] with periodic boundaries107mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates108initial_refinement_level = 4, n_cells_max = 10^4, periodicity = true)109110## Create a DGSEM solver with polynomials of degree `polydeg`111## Remember to pass a tuple of the form `(conservative_flux, nonconservative_flux)`112## as `surface_flux` and `volume_flux` when working with nonconservative terms113volume_flux = (flux_central, flux_nonconservative)114surface_flux = (flux_lax_friedrichs, flux_nonconservative)115solver = DGSEM(polydeg = 3, surface_flux = surface_flux,116volume_integral = VolumeIntegralFluxDifferencing(volume_flux))117118## Setup the spatial semidiscretization containing all ingredients119semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;120boundary_conditions = boundary_condition_periodic)121122## Create an ODE problem with given time span123tspan = (0.0, 1.0)124ode = semidiscretize(semi, tspan)125126## Set up some standard callbacks summarizing the simulation setup and computing127## errors of the numerical solution128summary_callback = SummaryCallback()129analysis_callback = AnalysisCallback(semi, interval = 50)130callbacks = CallbackSet(summary_callback, analysis_callback)131132## OrdinaryDiffEq's `solve` method evolves the solution in time and executes133## the passed callbacks134sol = solve(ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6;135ode_default_options()..., callback = callbacks)136137## Plot the numerical solution at the final time138using Plots: plot139plot(sol)140141# You see a plot of the final solution.142143# We can check whether everything fits together by refining the grid and comparing144# the numerical errors. First, we look at the error using the grid resolution145# above.146147error_1 = analysis_callback(sol).l2 |> first148@test isapprox(error_1, 0.00029609575838969394) #src149# Next, we increase the grid resolution by one refinement level and run the150# simulation again.151152mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates153initial_refinement_level = 5, n_cells_max = 10^4, periodicity = true)154155semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;156boundary_conditions = boundary_condition_periodic)157158tspan = (0.0, 1.0)159ode = semidiscretize(semi, tspan);160161summary_callback = SummaryCallback()162analysis_callback = AnalysisCallback(semi, interval = 50)163callbacks = CallbackSet(summary_callback, analysis_callback);164165sol = solve(ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6;166ode_default_options()..., callback = callbacks);167168#nb #-169error_2 = analysis_callback(sol).l2 |> first170@test isapprox(error_2, 1.860295931682964e-5, rtol = 0.05) #src171#-172error_1 / error_2173@test isapprox(error_1 / error_2, 15.916970234784808, rtol = 0.05) #src174# As expected, the new error is roughly reduced by a factor of 16, corresponding175# to an experimental order of convergence of 4 (for polynomials of degree 3).176177# For non-trivial boundary conditions involving non-conservative terms,178# please refer to the section on [Other available example elixirs with non-trivial BC](https://trixi-framework.github.io/TrixiDocumentation/stable/tutorials/non_periodic_boundaries/#Other-available-example-elixirs-with-non-trivial-BC).179180# ## Summary of the code181182# Here is the complete code that we used (without the callbacks since these183# create a lot of unnecessary output in the doctests of this tutorial).184# In addition, we create the `struct` inside the new module `NonconservativeLinearAdvection`.185# That ensures that we can re-create `struct`s defined therein without having to186# restart Julia.187188# Define new physics189module NonconservativeLinearAdvection190191using Trixi192using Trixi: AbstractEquations, get_node_vars193import Trixi: varnames, default_analysis_integrals, flux, max_abs_speed_naive,194have_nonconservative_terms195196## Since there is not yet native support for variable coefficients, we use two197## variables: one for the basic unknown `u` and another one for the coefficient `a`198struct NonconservativeLinearAdvectionEquation <: AbstractEquations{1, # spatial dimension1992} # two variables (u,a)200end201202function varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation)203return ("scalar", "advection_velocity")204end205206default_analysis_integrals(::NonconservativeLinearAdvectionEquation) = ()207208## The conservative part of the flux is zero209flux(u, orientation, equation::NonconservativeLinearAdvectionEquation) = zero(u)210211## Calculate maximum wave speed for local Lax-Friedrichs-type dissipation212function max_abs_speed_naive(u_ll, u_rr, orientation::Integer,213::NonconservativeLinearAdvectionEquation)214_, advection_velocity_ll = u_ll215_, advection_velocity_rr = u_rr216217return max(abs(advection_velocity_ll), abs(advection_velocity_rr))218end219220## We use nonconservative terms221have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.True()222223## This "nonconservative numerical flux" implements the nonconservative terms.224## In general, nonconservative terms can be written in the form225## g(u) ∂ₓ h(u)226## Thus, a discrete difference approximation of this nonconservative term needs227## - `u mine`: the value of `u` at the current position (for g(u))228## - `u_other`: the values of `u` in a neighborhood of the current position (for ∂ₓ h(u))229function flux_nonconservative(u_mine, u_other, orientation,230equations::NonconservativeLinearAdvectionEquation)231_, advection_velocity = u_mine232scalar, _ = u_other233234return SVector(advection_velocity * scalar, zero(scalar))235end236237end # module238239## Create a simulation setup240import .NonconservativeLinearAdvection241using Trixi242using OrdinaryDiffEqTsit5243244equation = NonconservativeLinearAdvection.NonconservativeLinearAdvectionEquation()245246## You can derive the exact solution for this setup using the method of247## characteristics248function initial_condition_sine(x, t,249equation::NonconservativeLinearAdvection.NonconservativeLinearAdvectionEquation)250x0 = -2 * atan(sqrt(3) * tan(sqrt(3) / 2 * t - atan(tan(x[1] / 2) / sqrt(3))))251scalar = sin(x0)252advection_velocity = 2 + cos(x[1])253return SVector(scalar, advection_velocity)254end255256## Create a uniform mesh in 1D in the interval [-π, π] with periodic boundaries257mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates258initial_refinement_level = 4, n_cells_max = 10^4, periodicity = true)259260## Create a DGSEM solver with polynomials of degree `polydeg`261## Remember to pass a tuple of the form `(conservative_flux, nonconservative_flux)`262## as `surface_flux` and `volume_flux` when working with nonconservative terms263volume_flux = (flux_central, NonconservativeLinearAdvection.flux_nonconservative)264surface_flux = (flux_lax_friedrichs, NonconservativeLinearAdvection.flux_nonconservative)265solver = DGSEM(polydeg = 3, surface_flux = surface_flux,266volume_integral = VolumeIntegralFluxDifferencing(volume_flux))267268## Setup the spatial semidiscretization containing all ingredients269semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;270boundary_conditions = boundary_condition_periodic)271272## Create an ODE problem with given time span273tspan = (0.0, 1.0)274ode = semidiscretize(semi, tspan);275276## Set up some standard callbacks summarizing the simulation setup and computing277## errors of the numerical solution278summary_callback = SummaryCallback()279analysis_callback = AnalysisCallback(semi, interval = 50)280callbacks = CallbackSet(summary_callback, analysis_callback);281282## OrdinaryDiffEq's `solve` method evolves the solution in time and executes283## the passed callbacks284sol = solve(ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6;285ode_default_options()...);286287## Plot the numerical solution at the final time288using Plots: plot289plot(sol);290291# ## Package versions292293# These results were obtained using the following versions.294295using InteractiveUtils296versioninfo()297298using Pkg299Pkg.status(["Trixi", "OrdinaryDiffEqTsit5", "Plots"],300mode = PKGMODE_MANIFEST)301302# ## Additional modifications303304# When one carries auxiliary variable(s) in the solution vector, e.g., for non-constant305# coefficient advection problems some routines may require modification to avoid adding306# dissipation to the variable coefficient quantity `a` that is carried as an auxiliary variable in307# the solution vector. In particular, a specialized [`DissipationLocalLaxFriedrichs`](@ref) term308# used together with the numerical surface flux [`flux_lax_friedrichs`](@ref) prevents "smearing"309# the variable coefficient `a` artificially.310311## Specialized dissipation term for the Lax-Friedrichs surface flux312@inline function (dissipation::DissipationLocalLaxFriedrichs)(u_ll, u_rr,313orientation::Integer,314equation::NonconservativeLinearAdvectionEquation)315λ = dissipation.max_abs_speed(u_ll, u_rr, orientation, equation)316317diss = -0.5 * λ * (u_rr - u_ll)318## do not add dissipation to the variable coefficient a used as last entry of u319return SVector(diss[1], zero(u_ll))320end321322# Another modification is necessary if one wishes to use the stage limiter [`PositivityPreservingLimiterZhangShu`](@ref)323# during the time integration. This limiter takes in a `variable` (or set of variables) to limit and ensure positivity.324# However, these variables are used to compute the limiter quantities that are then applied to every325# variable in the solution vector `u`. To avoid artificially limiting (and in turn changing) the variable coefficient326# quantity that should remain unchanged, a specialized implementation of the `limiter_zhang_shu!` function is required.327# For the example equation given in this tutorial, this new function for the limiting would take the form328329## Specialized positivity limiter that avoids modification of the auxiliary variable `a`330function Trixi.limiter_zhang_shu!(u, threshold, variable, mesh,331equations::NonconservativeLinearAdvectionEquation,332dg, cache)333weights = dg.basis334335for element in eachelement(dg, cache)336## determine minimum value337value_min = typemax(eltype(u))338for i in eachnode(dg)339u_node = get_node_vars(u, equations, dg, i, element)340value_min = min(value_min, variable(u_node, equations))341end342343## detect if limiting is necessary344value_min < threshold || continue345346## compute mean value347u_mean = zero(get_node_vars(u, equations, dg, 1, element))348for i in eachnode(dg)349u_node = get_node_vars(u, equations, dg, i, element)350u_mean += u_node * weights[i]351end352## note that the reference element is [-1,1]^ndims(dg), thus the weights sum to 2353u_mean = u_mean / 2^ndims(mesh)354355## Compute the value directly with the mean values, as we assume that356## Jensen's inequality holds.357value_mean = variable(u_mean, equations)358theta = (value_mean - threshold) / (value_mean - value_min)359for i in eachnode(dg)360u_node = get_node_vars(u, equations, dg, i, element)361362_, a_node = u_node363scalar_mean, _ = u_mean364365## mean values of variable coefficient not used as it must not be overwritten366u_mean = SVector(scalar_mean, a_node)367368set_node_vars!(u, theta * u_node + (1 - theta) * u_mean,369equations, dg, i, element)370end371end372373return nothing374end375376377