Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/docs/literate/src/files/adding_nonconservative_equation.jl
5591 views
1
#src # Adding a new equation: nonconservative linear advection
2
using Test: @test #src
3
4
# If you want to use Trixi.jl for your own research, you might be interested in
5
# a new physics model that is not present in Trixi.jl. In this tutorial,
6
# we will implement the nonconservative linear advection equation in a periodic domain
7
# ```math
8
# \left\{
9
# \begin{aligned}&\partial_t u(t,x) + a(x) \partial_x u(t,x) = 0 \\
10
# &u(0,x)=\sin(x) \\
11
# &u(t,-\pi)=u(t,\pi)
12
# \end{aligned}
13
# \right.
14
# ```
15
# where $a(x) = 2 + \cos(x)$. The analytic solution is
16
# ```math
17
# 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)
18
# ```
19
# In Trixi.jl, such a mathematical model
20
# is encoded as a subtype of [`Trixi.AbstractEquations`](@ref).
21
22
# ## Basic setup
23
24
# Since there is no native support for variable coefficients, we need to transform the PDE to the following system:
25
# ```math
26
# \left\{
27
# \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 \\
28
# &u(0,x)=\sin(x) \\
29
# &a(0,x)=2+\cos(x) \\
30
# &u(t,-\pi)=u(t,\pi)
31
# \end{aligned}
32
# \right.
33
# ```
34
35
## Define new physics
36
using Trixi
37
using Trixi: AbstractEquations, get_node_vars
38
import Trixi: varnames, default_analysis_integrals, flux, max_abs_speed_naive,
39
have_nonconservative_terms
40
41
## Since there is no native support for variable coefficients, we use two
42
## variables: one for the basic unknown `u` and another one for the coefficient `a`
43
struct NonconservativeLinearAdvectionEquation <: AbstractEquations{1, # spatial dimension
44
2} # two variables (u,a)
45
end
46
47
function varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation)
48
return ("scalar", "advection_velocity")
49
end
50
51
default_analysis_integrals(::NonconservativeLinearAdvectionEquation) = ()
52
53
## The conservative part of the flux is zero
54
flux(u, orientation, equation::NonconservativeLinearAdvectionEquation) = zero(u)
55
56
## Calculate maximum wave speed for local Lax-Friedrichs-type dissipation
57
function max_abs_speed_naive(u_ll, u_rr, orientation::Integer,
58
::NonconservativeLinearAdvectionEquation)
59
_, advection_velocity_ll = u_ll
60
_, advection_velocity_rr = u_rr
61
62
return max(abs(advection_velocity_ll), abs(advection_velocity_rr))
63
end
64
65
## We use nonconservative terms
66
have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.True()
67
68
## This "nonconservative numerical flux" implements the nonconservative terms.
69
## In general, nonconservative terms can be written in the form
70
## g(u) ∂ₓ h(u)
71
## Thus, a discrete difference approximation of this nonconservative term needs
72
## - `u mine`: the value of `u` at the current position (for g(u))
73
## - `u_other`: the values of `u` in a neighborhood of the current position (for ∂ₓ h(u))
74
function flux_nonconservative(u_mine, u_other, orientation,
75
equations::NonconservativeLinearAdvectionEquation)
76
_, advection_velocity = u_mine
77
scalar, _ = u_other
78
79
return SVector(advection_velocity * scalar, zero(scalar))
80
end
81
82
# The implementation of nonconservative terms uses a single "nonconservative flux"
83
# function `flux_nonconservative`. It will basically be applied in a loop of the
84
# form
85
# ````julia
86
# du_m(D, u) = sum(D[m, l] * flux_nonconservative(u[m], u[l], 1, equations)) # orientation 1: x
87
# ````
88
# where `D` is the derivative matrix and `u` contains the nodal solution values.
89
90
# Now, we can run a simple simulation using a DGSEM discretization.
91
92
## Create a simulation setup
93
using Trixi
94
using OrdinaryDiffEqTsit5
95
96
equation = NonconservativeLinearAdvectionEquation()
97
98
## You can derive the exact solution for this setup using the method of
99
## characteristics
100
function initial_condition_sine(x, t, equation::NonconservativeLinearAdvectionEquation)
101
x0 = -2 * atan(sqrt(3) * tan(sqrt(3) / 2 * t - atan(tan(x[1] / 2) / sqrt(3))))
102
scalar = sin(x0)
103
advection_velocity = 2 + cos(x[1])
104
return SVector(scalar, advection_velocity)
105
end
106
107
## Create a uniform mesh in 1D in the interval [-π, π] with periodic boundaries
108
mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates
109
initial_refinement_level = 4, n_cells_max = 10^4, periodicity = true)
110
111
## Create a DGSEM solver with polynomials of degree `polydeg`
112
## Remember to pass a tuple of the form `(conservative_flux, nonconservative_flux)`
113
## as `surface_flux` and `volume_flux` when working with nonconservative terms
114
volume_flux = (flux_central, flux_nonconservative)
115
surface_flux = (flux_lax_friedrichs, flux_nonconservative)
116
solver = DGSEM(polydeg = 3, surface_flux = surface_flux,
117
volume_integral = VolumeIntegralFluxDifferencing(volume_flux))
118
119
## Setup the spatial semidiscretization containing all ingredients
120
semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;
121
boundary_conditions = boundary_condition_periodic)
122
123
## Create an ODE problem with given time span
124
tspan = (0.0, 1.0)
125
ode = semidiscretize(semi, tspan)
126
127
## Set up some standard callbacks summarizing the simulation setup and computing
128
## errors of the numerical solution
129
summary_callback = SummaryCallback()
130
analysis_callback = AnalysisCallback(semi, interval = 50)
131
callbacks = CallbackSet(summary_callback, analysis_callback)
132
133
## OrdinaryDiffEq's `solve` method evolves the solution in time and executes
134
## the passed callbacks
135
sol = solve(ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6;
136
ode_default_options()..., callback = callbacks)
137
138
## Plot the numerical solution at the final time
139
using Plots: plot
140
plot(sol)
141
142
# You see a plot of the final solution.
143
144
# We can check whether everything fits together by refining the grid and comparing
145
# the numerical errors. First, we look at the error using the grid resolution
146
# above.
147
148
error_1 = analysis_callback(sol).l2 |> first
149
@test isapprox(error_1, 0.00029609575838969394) #src
150
# Next, we increase the grid resolution by one refinement level and run the
151
# simulation again.
152
153
mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates
154
initial_refinement_level = 5, n_cells_max = 10^4, periodicity = true)
155
156
semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;
157
boundary_conditions = boundary_condition_periodic)
158
159
tspan = (0.0, 1.0)
160
ode = semidiscretize(semi, tspan);
161
162
summary_callback = SummaryCallback()
163
analysis_callback = AnalysisCallback(semi, interval = 50)
164
callbacks = CallbackSet(summary_callback, analysis_callback);
165
166
sol = solve(ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6;
167
ode_default_options()..., callback = callbacks);
168
169
#nb #-
170
error_2 = analysis_callback(sol).l2 |> first
171
@test isapprox(error_2, 1.860295931682964e-5, rtol = 0.05) #src
172
#-
173
error_1 / error_2
174
@test isapprox(error_1 / error_2, 15.916970234784808, rtol = 0.05) #src
175
# As expected, the new error is roughly reduced by a factor of 16, corresponding
176
# to an experimental order of convergence of 4 (for polynomials of degree 3).
177
178
# For non-trivial boundary conditions involving non-conservative terms,
179
# 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).
180
181
# ## Summary of the code
182
183
# Here is the complete code that we used (without the callbacks since these
184
# create a lot of unnecessary output in the doctests of this tutorial).
185
# In addition, we create the `struct` inside the new module `NonconservativeLinearAdvection`.
186
# That ensures that we can re-create `struct`s defined therein without having to
187
# restart Julia.
188
189
# Define new physics
190
module NonconservativeLinearAdvection
191
192
using Trixi
193
using Trixi: AbstractEquations, get_node_vars
194
import Trixi: varnames, default_analysis_integrals, flux, max_abs_speed_naive,
195
have_nonconservative_terms
196
197
## Since there is not yet native support for variable coefficients, we use two
198
## variables: one for the basic unknown `u` and another one for the coefficient `a`
199
struct NonconservativeLinearAdvectionEquation <: AbstractEquations{1, # spatial dimension
200
2} # two variables (u,a)
201
end
202
203
function varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation)
204
return ("scalar", "advection_velocity")
205
end
206
207
default_analysis_integrals(::NonconservativeLinearAdvectionEquation) = ()
208
209
## The conservative part of the flux is zero
210
flux(u, orientation, equation::NonconservativeLinearAdvectionEquation) = zero(u)
211
212
## Calculate maximum wave speed for local Lax-Friedrichs-type dissipation
213
function max_abs_speed_naive(u_ll, u_rr, orientation::Integer,
214
::NonconservativeLinearAdvectionEquation)
215
_, advection_velocity_ll = u_ll
216
_, advection_velocity_rr = u_rr
217
218
return max(abs(advection_velocity_ll), abs(advection_velocity_rr))
219
end
220
221
## We use nonconservative terms
222
have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.True()
223
224
## This "nonconservative numerical flux" implements the nonconservative terms.
225
## In general, nonconservative terms can be written in the form
226
## g(u) ∂ₓ h(u)
227
## Thus, a discrete difference approximation of this nonconservative term needs
228
## - `u mine`: the value of `u` at the current position (for g(u))
229
## - `u_other`: the values of `u` in a neighborhood of the current position (for ∂ₓ h(u))
230
function flux_nonconservative(u_mine, u_other, orientation,
231
equations::NonconservativeLinearAdvectionEquation)
232
_, advection_velocity = u_mine
233
scalar, _ = u_other
234
235
return SVector(advection_velocity * scalar, zero(scalar))
236
end
237
238
end # module
239
240
## Create a simulation setup
241
import .NonconservativeLinearAdvection
242
using Trixi
243
using OrdinaryDiffEqTsit5
244
245
equation = NonconservativeLinearAdvection.NonconservativeLinearAdvectionEquation()
246
247
## You can derive the exact solution for this setup using the method of
248
## characteristics
249
function initial_condition_sine(x, t,
250
equation::NonconservativeLinearAdvection.NonconservativeLinearAdvectionEquation)
251
x0 = -2 * atan(sqrt(3) * tan(sqrt(3) / 2 * t - atan(tan(x[1] / 2) / sqrt(3))))
252
scalar = sin(x0)
253
advection_velocity = 2 + cos(x[1])
254
return SVector(scalar, advection_velocity)
255
end
256
257
## Create a uniform mesh in 1D in the interval [-π, π] with periodic boundaries
258
mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates
259
initial_refinement_level = 4, n_cells_max = 10^4, periodicity = true)
260
261
## Create a DGSEM solver with polynomials of degree `polydeg`
262
## Remember to pass a tuple of the form `(conservative_flux, nonconservative_flux)`
263
## as `surface_flux` and `volume_flux` when working with nonconservative terms
264
volume_flux = (flux_central, NonconservativeLinearAdvection.flux_nonconservative)
265
surface_flux = (flux_lax_friedrichs, NonconservativeLinearAdvection.flux_nonconservative)
266
solver = DGSEM(polydeg = 3, surface_flux = surface_flux,
267
volume_integral = VolumeIntegralFluxDifferencing(volume_flux))
268
269
## Setup the spatial semidiscretization containing all ingredients
270
semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;
271
boundary_conditions = boundary_condition_periodic)
272
273
## Create an ODE problem with given time span
274
tspan = (0.0, 1.0)
275
ode = semidiscretize(semi, tspan);
276
277
## Set up some standard callbacks summarizing the simulation setup and computing
278
## errors of the numerical solution
279
summary_callback = SummaryCallback()
280
analysis_callback = AnalysisCallback(semi, interval = 50)
281
callbacks = CallbackSet(summary_callback, analysis_callback);
282
283
## OrdinaryDiffEq's `solve` method evolves the solution in time and executes
284
## the passed callbacks
285
sol = solve(ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6;
286
ode_default_options()...);
287
288
## Plot the numerical solution at the final time
289
using Plots: plot
290
plot(sol);
291
292
# ## Package versions
293
294
# These results were obtained using the following versions.
295
296
using InteractiveUtils
297
versioninfo()
298
299
using Pkg
300
Pkg.status(["Trixi", "OrdinaryDiffEqTsit5", "Plots"],
301
mode = PKGMODE_MANIFEST)
302
303
# ## Additional modifications
304
305
# When one carries auxiliary variable(s) in the solution vector, e.g., for non-constant
306
# coefficient advection problems some routines may require modification to avoid adding
307
# dissipation to the variable coefficient quantity `a` that is carried as an auxiliary variable in
308
# the solution vector. In particular, a specialized [`DissipationLocalLaxFriedrichs`](@ref) term
309
# used together with the numerical surface flux [`flux_lax_friedrichs`](@ref) prevents "smearing"
310
# the variable coefficient `a` artificially.
311
312
## Specialized dissipation term for the Lax-Friedrichs surface flux
313
@inline function (dissipation::DissipationLocalLaxFriedrichs)(u_ll, u_rr,
314
orientation::Integer,
315
equation::NonconservativeLinearAdvectionEquation)
316
λ = dissipation.max_abs_speed(u_ll, u_rr, orientation, equation)
317
318
diss = -0.5 * λ * (u_rr - u_ll)
319
## do not add dissipation to the variable coefficient a used as last entry of u
320
return SVector(diss[1], zero(u_ll))
321
end
322
323
# Another modification is necessary if one wishes to use the stage limiter [`PositivityPreservingLimiterZhangShu`](@ref)
324
# during the time integration. This limiter takes in a `variable` (or set of variables) to limit and ensure positivity.
325
# However, these variables are used to compute the limiter quantities that are then applied to every
326
# variable in the solution vector `u`. To avoid artificially limiting (and in turn changing) the variable coefficient
327
# quantity that should remain unchanged, a specialized implementation of the `limiter_zhang_shu!` function is required.
328
# For the example equation given in this tutorial, this new function for the limiting would take the form
329
330
## Specialized positivity limiter that avoids modification of the auxiliary variable `a`
331
function Trixi.limiter_zhang_shu!(u, threshold, variable, mesh,
332
equations::NonconservativeLinearAdvectionEquation,
333
dg, cache)
334
weights = dg.basis
335
336
for element in eachelement(dg, cache)
337
## determine minimum value
338
value_min = typemax(eltype(u))
339
for i in eachnode(dg)
340
u_node = get_node_vars(u, equations, dg, i, element)
341
value_min = min(value_min, variable(u_node, equations))
342
end
343
344
## detect if limiting is necessary
345
value_min < threshold || continue
346
347
## compute mean value
348
u_mean = zero(get_node_vars(u, equations, dg, 1, element))
349
for i in eachnode(dg)
350
u_node = get_node_vars(u, equations, dg, i, element)
351
u_mean += u_node * weights[i]
352
end
353
## note that the reference element is [-1,1]^ndims(dg), thus the weights sum to 2
354
u_mean = u_mean / 2^ndims(mesh)
355
356
## Compute the value directly with the mean values, as we assume that
357
## Jensen's inequality holds.
358
value_mean = variable(u_mean, equations)
359
theta = (value_mean - threshold) / (value_mean - value_min)
360
for i in eachnode(dg)
361
u_node = get_node_vars(u, equations, dg, i, element)
362
363
_, a_node = u_node
364
scalar_mean, _ = u_mean
365
366
## mean values of variable coefficient not used as it must not be overwritten
367
u_mean = SVector(scalar_mean, a_node)
368
369
set_node_vars!(u, theta * u_node + (1 - theta) * u_mean,
370
equations, dg, i, element)
371
end
372
end
373
374
return nothing
375
end
376
377