Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/docs/literate/src/files/scalar_linear_advection_1d.jl
5591 views
1
#src Introduction to DG methods
2
using Test: @test #src
3
4
# This tutorial is about how to set up a simple way to approximate the solution of a hyperbolic partial
5
# differential equation. First, we will implement a basic and naive algorithm. Then, we will use predefined
6
# features from [Trixi.jl](https://github.com/trixi-framework/Trixi.jl) to show how you can use Trixi.jl on your own.
7
8
# We will implement the scalar linear advection equation in 1D with the advection velocity $1$.
9
# ```math
10
# u_t + u_x = 0,\; \text{for} \;t\in \mathbb{R}^+, x\in\Omega=[-1,1]
11
# ```
12
# We define the domain $\Omega$ by setting the boundaries.
13
coordinates_min = -1.0 # minimum coordinate
14
coordinates_max = 1.0 # maximum coordinate
15
16
# We assume periodic boundaries and the following initial condition.
17
initial_condition_sine_wave(x) = 1.0 + 0.5 * sinpi(x)
18
19
# ## The discontinuous Galerkin collocation spectral element method (DGSEM)
20
# ### i. Discretization of the physical domain
21
# To improve precision we want to approximate the solution on small parts of the physical domain.
22
# So, we split the domain $\Omega=[-1, 1]$ into elements $Q_l$ of length $dx$.
23
24
n_elements = 16 # number of elements
25
26
dx = (coordinates_max - coordinates_min) / n_elements # length of one element
27
28
# To make the calculation more efficient and storing less information, we transform each element
29
# $Q_l$ with center point $x_l$ to a reference element $E=[-1, 1]$
30
# ```math
31
# Q_l=\Big[x_l-\frac{dx}{2}, x_l+\frac{dx}{2}\Big] \underset{x(\xi)}{\overset{\xi(x)}{\rightleftarrows}} [-1, 1].
32
# ```
33
# So, for every element the transformation from the reference domain to the physical domain is defined by
34
# ```math
35
# x(\xi) = x_l + \frac{dx}{2} \xi,\; \xi\in[-1, 1]
36
# ```
37
# Therefore,
38
# ```math
39
# \begin{align*}
40
# u &= u(x(\xi), t) \\
41
# u_x &= u_\xi \frac{d\xi}{dx} \\[3pt]
42
# \frac{d\xi}{dx} &= (x_\xi)^{-1} = \frac{2}{dx} =: J^{-1}. \\
43
# \end{align*}
44
# ```
45
# Here, $J$ is the Jacobian determinant of the transformation.
46
47
# Using this transformation, we can transform our equation for each element $Q_l$.
48
# ```math
49
# \frac{dx}{2} u_t^{Q_l} + u_\xi^{Q_l} = 0 \text{, for }t\in\mathbb{R}^+,\; \xi\in[-1, 1]
50
# ```
51
# Here, $u_t^{Q_l}$ and $u_\xi^{Q_l}$ denote the time and spatial derivatives of the solution on the element $Q_l$.
52
53
# ### ii. Polynomial approach
54
# Now, we want to approximate the solution in each element $Q_l$ by a polynomial of degree $N$. Since we transformed
55
# the equation, we can use the same polynomial approach for the reference coordinate $\xi\in[-1, 1]$ in every
56
# physical element $Q_l$. This saves a lot of resources by reducing the amount of calculations needed
57
# and storing less information.
58
59
# For DGSEM we choose [Lagrange basis functions](https://en.wikipedia.org/wiki/Lagrange_polynomial)
60
# $\{l_j\}_{j=0}^N$ as our polynomial basis of degree $N$ in $[-1, 1]$.
61
# The solution in element $Q_l$ can be approximated by
62
# ```math
63
# u(x(\xi), t)\big|_{Q_l} \approx u^{Q_l}(\xi, t) = \sum_{j=0}^N u_j^{Q_l}(t) l_j(\xi)
64
# ```
65
# with $N+1$ coefficients $\{u_j^{Q_l}\}_{j=0}^N$.
66
# By construction the Lagrange basis has some useful advantages. This basis is defined by $N+1$ nodes, which
67
# fulfill a Kronecker property at the exact same nodes. Let $\{\xi_i\}_{i=0}^N$ be these nodes.
68
# ```math
69
# l_j(\xi_i) = \delta_{i,j} =
70
# \begin{cases}
71
# 1, & \text{if } i=j \\
72
# 0, & \text{else.}
73
# \end{cases}
74
# ```
75
# Because of this property, the polynomial coefficients are exact the values of $u^{Q_l}$ at the nodes
76
# ```math
77
# u^{Q_l}(\xi_i, t) = \sum_{j=0}^N u_j^{Q_l}(t) \underbrace{l_j(\xi_i)}_{=\delta_{ij}} = u_i^{Q_l}(t).
78
# ```
79
80
# Next, we want to select the nodes $\{\xi_i\}_{i=0}^N$, which we use for the construction of the Lagrange
81
# polynomials. We choose the $N+1$ Gauss-Lobatto nodes, which are used for the
82
# [Gaussian-Lobatto quadrature](https://mathworld.wolfram.com/LobattoQuadrature.html).
83
# These always contain the boundary points at $-1$ and $+1$ and are well suited as interpolation nodes.
84
# The corresponding weights will be referred to as $\{w_j\}_{j=0}^N$.
85
# In Trixi.jl the basis with Lagrange polynomials on Gauss-Lobatto nodes is already defined.
86
using Trixi
87
polydeg = 3 #= polynomial degree = N =#
88
basis = LobattoLegendreBasis(polydeg)
89
# The Gauss-Lobatto nodes are
90
nodes = basis.nodes
91
# with the corresponding weights
92
weights = basis.weights
93
94
# To illustrate how you can integrate using numerical quadrature with this Legendre-Gauss-Lobatto nodes,
95
# we give an example for $f(x)=x^3$. Since $f$ is of degree $3$, a polynomial interpolation with $N=3$ is exact.
96
# Therefore, the integral on $[-1, 1]$ can be calculated by
97
# ```math
98
# \begin{align*}
99
# \int_{-1}^1 f(x) dx &= \int_{-1}^1 \Big( \sum_{j=0}^3 f(\xi_j)l_j(x) \Big) dx
100
# = \sum_{j=0}^3 f(\xi_j) \int_{-1}^1 l_j(x)dx \\
101
# &=: \sum_{j=0}^3 f(\xi_j) w_j
102
# = \sum_{j=0}^3 \xi_j^3 w_j
103
# \end{align*}
104
# ```
105
# Let's use our nodes and weights for $N=3$ and plug in
106
integral = sum(nodes .^ 3 .* weights)
107
108
# Using this polynomial approach leads to the equation
109
# ```math
110
# \frac{dx}{2} \dot{u}^{Q_l}(\xi, t) + u^{Q_l}(\xi, t)' = 0
111
# ```
112
# with $\dot{u}=\frac{\partial}{\partial t}u$ and $u'=\frac{\partial}{\partial x}u$.
113
# To approximate the solution, we need to get the polynomial coefficients $\{u_j^{Q_l}\}_{j=0}^N$
114
# for every element $Q_l$.
115
116
# After defining all nodes, we can implement the spatial coordinate $x$ and its initial value $u0 = u(t_0)$
117
# for every node.
118
x = Matrix{Float64}(undef, length(nodes), n_elements)
119
for element in 1:n_elements
120
x_l = coordinates_min + (element - 1) * dx + dx / 2
121
for i in eachindex(nodes)
122
ξ = nodes[i] # nodes in [-1, 1]
123
x[i, element] = x_l + dx / 2 * ξ
124
end
125
end
126
127
u0 = initial_condition_sine_wave.(x)
128
129
# To have a look at the initial sinus curve, we plot it.
130
using Plots
131
plot(vec(x), vec(u0), label = "initial condition", legend = :topleft, markershape = :circle)
132
133
# ### iii. Variational formulation
134
# After defining the equation and initial condition, we want to implement an algorithm to
135
# approximate the solution.
136
137
# From now on, we only write $u$ instead of $u^{Q_l}$ for simplicity, but consider that all the following
138
# calculation only concern one element.
139
# Multiplying the new equation with the smooth Lagrange polynomials $\{l_i\}_{i=0}^N$ (test functions)
140
# and integrating over the reference element $E=[-1,1]$, we get the variational formulation of our
141
# transformed partial differential equation for $i=0,...,N$:
142
# ```math
143
# \begin{align*}
144
# \int_{-1}^1 \Big( \frac{dx}{2} \dot{u}(\xi, t) + u'(\xi, t) \Big) l_i(\xi)d\xi
145
# &= \underbrace{\frac{dx}{2} \int_{-1}^1 \dot{u}(\xi, t) l_i(\xi)d\xi}_{\text{Term I}} + \underbrace{\int_{-1}^1 u'(\xi, t) l_i(\xi)d\xi}_{\text{Term II}} = 0
146
# \end{align*}
147
# ```
148
149
# We deal with the two terms separately. We write $\int_{-1, N}^1 \;\cdot\; d\xi$ for the approximation
150
# of the integral using numerical quadrature with $N+1$ basis points. We use the Gauss-Lobatto nodes
151
# again. The numerical scalar product $\langle\cdot, \cdot\rangle_N$ is defined by
152
# $\langle f, g\rangle_N := \int_{-1, N}^1 f(\xi) g(\xi) d\xi$.
153
154
# #### Term I:
155
# In the following calculation we approximate the integral numerically with quadrature on the Gauss-Lobatto
156
# nodes $\{\xi_i\}_{i=0}^N$ and then use the Kronecker property of the Lagrange polynomials. This approach
157
# of using the same nodes for the interpolation and quadrature is called collocation.
158
# ```math
159
# \begin{align*}
160
# \frac{dx}{2} \int_{-1}^1 \dot{u}(\xi, t) l_i(\xi)d\xi
161
# &\approx \frac{dx}{2} \int_{-1, N}^1 \dot{u}(\xi, t) l_i(\xi)d\xi \\
162
# &= \frac{dx}{2} \sum_{k=0}^N \underbrace{\dot{u}(\xi_k, t)}_{=\dot{u}_k(t)} \underbrace{l_i(\xi_k)}_{=\delta_{k,i}}w_k \\
163
# &= \frac{dx}{2} \dot{u}_i(t) w_i
164
# \end{align*}
165
# ```
166
# We define the Legendre-Gauss-Lobatto (LGL) mass matrix $M$ and by the Kronecker property follows:
167
# ```math
168
# M_{ij} = \langle l_j, l_i\rangle_N = \delta_{ij} w_j,\; i,j=0,...,N.
169
# ```
170
using LinearAlgebra
171
M = diagm(weights)
172
# Now, we can write the integral with this new matrix.
173
# ```math
174
# \frac{dx}{2} \int_{-1, N}^1 \dot{u}(\xi, t) \underline{l}(\xi)d\xi = \frac{dx}{2} M \underline{\dot{u}}(t),
175
# ```
176
# where $\underline{\dot{u}} = (\dot{u}_0, \dots, \dot{u}_N)^T$ and $\underline{l} = (l_0, \dots, l_N)^T$ respectively.
177
178
# **Note:** Since the LGL quadrature with $N+1$ nodes is exact up to functions of degree $2N-1$ and
179
# $\dot{u}(\xi, t) l_i(\xi)$ is of degree $2N$, in general the following holds
180
# ```math
181
# \int_{-1}^1 \dot{u}(\xi, t) l_i(\xi) d\xi \neq \int_{-1, N}^1 \dot{u}(\xi, t) l_i(\xi) d\xi.
182
# ```
183
# With an exact integration the mass matrix would be dense. Choosing numerical integrating and quadrature
184
# with the exact same nodes (collocation) leads to the sparse and diagonal mass matrix $M$. This
185
# is called mass lumping and has the big advantage of an easy inversion of the matrix.
186
187
# #### Term II:
188
# We use spatial partial integration for the second term:
189
# ```math
190
# \int_{-1}^1 u'(\xi, t) l_i(\xi) d\xi = [u l_i]_{-1}^1 - \int_{-1}^1 u l_i'd\xi
191
# ```
192
# The resulting integral can be solved exactly with LGL quadrature since the polynomial is now
193
# of degree $2N-1$.
194
195
# Again, we split the calculation in two steps.
196
197
# #### Surface term
198
# As mentioned before, we approximate the solution with a polynomial in every element. Therefore, in
199
# general the value of this approximation at the interfaces between two elements is not unique. To solve
200
# this problem we introduce the idea of the numerical flux $u^*$, which will give an exact value at
201
# the interfaces. One of many different approaches and definitions for the calculation of the
202
# numerical flux we will deal with in [4. Numerical flux](@ref numerical_flux).
203
# ```math
204
# [u l_i]_{-1}^1 = u^*\big|^1 l_i(+1) - u^*\big|_{-1} l_i(-1)
205
# ```
206
# Since the Gauss-Lobatto nodes contain the element boundaries $-1$ and $+1$, we can use the
207
# Kronecker property of $l_i$ for the calculation of $l_i(-1)$ and $l_i(+1)$.
208
# ```math
209
# [u \underline{l}]_{-1}^1 = u^*\big|^1 \left(\begin{array}{c} 0 \\ \vdots \\ 0 \\ 1 \end{array}\right)
210
# - u^*\big|_{-1} \left(\begin{array}{c} 1 \\ 0 \\ \vdots \\ 0\end{array}\right)
211
# = B \underline{u}^*(t)
212
# ```
213
# with the boundary matrix
214
# ```math
215
# B = \begin{pmatrix}
216
# -1 & 0 & \cdots & 0\\
217
# 0 & 0 & \cdots & 0\\
218
# \vdots & \vdots & 0 & 0\\
219
# 0 & \cdots & 0 & 1
220
# \end{pmatrix}
221
# \qquad\text{and}\qquad
222
# \underline{u}^*(t) = \left(\begin{array}{c} u^*\big|_{-1} \\ 0 \\ \vdots \\ 0 \\ u^*\big|^1\end{array}\right).
223
# ```
224
B = diagm([-1; zeros(polydeg - 1); 1])
225
226
# #### Volume term
227
# As mentioned before, the new integral can be solved exact since the function inside is of degree $2N-1$.
228
# ```math
229
# - \int_{-1}^1 u l_i'd\xi = - \int_{-1, N}^1 u l_i' d\xi
230
# = - \sum_{k=0}^N u(\xi_k, t) l_i'(\xi_k) w_k
231
# = - \sum_{k=0}^N u_k(t) D_{ki} w_k
232
# ```
233
# where $D$ is the derivative matrix defined by $D_{ki} = l_i'(\xi_k)$ for $i,k=0,...,N$.
234
D = basis.derivative_matrix
235
236
# To show why this matrix is called the derivative matrix, we go back to our example $f(x)=x^3$.
237
# We calculate the derivation of $f$ at the Gauss-Lobatto nodes $\{\xi_k\}_{k=0}^N$ with $N=8$.
238
# ```math
239
# f'|_{x=\xi_k} = \Big( \sum_{j=0}^8 f(\xi_j) l_j(x) \Big)'|_{x=\xi_k} = \sum_{j=0}^8 f(\xi_j) l_j'(\xi_k)
240
# = \sum_{j=0}^8 f(\xi_j) D_{kj}
241
# ```
242
# for $k=0,...,N$ and therefore, $\underline{f}' = D \underline{f}$.
243
basis_N8 = LobattoLegendreBasis(8)
244
plot(vec(x), x -> 3 * x^2, label = "f'", lw = 2)
245
scatter!(basis_N8.nodes, basis_N8.derivative_matrix * basis_N8.nodes .^ 3, label = "Df",
246
lw = 3)
247
248
# Combining the volume term for every $i=0,...,N$ results in
249
# ```math
250
# \int_{-1}^1 u \underline{l'} d\xi = - D^T M \underline{u}(t)
251
# ```
252
253
# Putting all parts together we get the following equation for the element $Q_l$
254
# ```math
255
# \frac{dx}{2} M \underline{\dot{u}}(t) = - B \underline{u}^*(t) + D^T M \underline{u}(t)
256
# ```
257
# or equivalent
258
# ```math
259
# \underline{\dot{u}}^{Q_l}(t) = \frac{2}{dx} \Big[ - M^{-1} B \underline{u}^{{Q_l}^*}(t) + M^{-1} D^T M \underline{u}^{Q_l}(t)\Big].
260
# ```
261
# This is called the weak form of the DGSEM.
262
263
# **Note:** For every element $Q_l$ we get a system of $N+1$ ordinary differential equations to
264
# calculate $N+1$ coefficients. Since the numerical flux $u^*$ is depending on extern values at
265
# the interfaces, the equation systems of adjacent elements are weakly linked.
266
267
# ### [iv. Numerical flux](@id numerical_flux)
268
# As mentioned above, we still have to handle the problem of different values at the same point at
269
# the interfaces. This happens with the ideas of the numerical flux $f^*(u)=u^*$. The role of $f^*$
270
# might seem minor in this simple example, but is important for more complicated problems.
271
# There are two values at the same spatial coordinate. Let's say we are looking at the interface between
272
# the elements $Q_l$ and $Q_{l+1}$, while both elements got $N+1$ nodes as defined before. We call
273
# the first value of the right element $u_R=u_0^{Q_{l+1}}$ and the last one of the left element
274
# $u_L=u_N^{Q_l}$. So, for the value of the numerical flux on that interface the following holds
275
# ```math
276
# u^* = u^*(u_L, u_R).
277
# ```
278
# These values are interpreted as start values of a so-called Riemann problem. There are many
279
# different (approximate) Riemann solvers available and useful for different problems. We will
280
# use the local Lax-Friedrichs flux.
281
surface_flux = flux_lax_friedrichs
282
283
# The only missing ingredient is the flux calculation at the boundaries $-1$ and $+1$.
284
# ```math
285
# u^{{Q_{first}}^*}\big|_{-1} = u^{{Q_{first}}^*}\big|_{-1}(u^{bound}(-1), u_R)
286
# \quad\text{and}\quad
287
# u^{{Q_{last}}^*}\big|^1 = u^{{Q_{last}}^*}\big|^1(u_L, u^{bound}(1))
288
# ```
289
# The boundaries are periodic, which means that the last value of the last element $u^{Q_{last}}_N$
290
# is used as $u_L$ at the first interface and accordingly for the other boundary.
291
292
# Now, we implement a function, that calculates $\underline{\dot{u}}^{Q_l}$ for the given matrices,
293
# $\underline{u}$ and $\underline{u}^*$.
294
function rhs!(du, u, x, t)
295
## Reset du and flux matrix
296
du .= zero(eltype(du))
297
flux_numerical = copy(du)
298
299
## Calculate interface and boundary fluxes, $u^* = (u^*|_{-1}, 0, ..., 0, u^*|^1)^T$
300
## Since we use the flux Lax-Friedrichs from Trixi.jl, we have to pass some extra arguments.
301
## Trixi.jl needs the equation we are dealing with and an additional `1`, that indicates the
302
## first coordinate direction.
303
equations = LinearScalarAdvectionEquation1D(1.0)
304
for element in 2:(n_elements - 1)
305
## left interface
306
flux_numerical[1, element] = surface_flux(u[end, element - 1], u[1, element], 1,
307
equations)
308
flux_numerical[end, element - 1] = flux_numerical[1, element]
309
## right interface
310
flux_numerical[end, element] = surface_flux(u[end, element], u[1, element + 1], 1,
311
equations)
312
flux_numerical[1, element + 1] = flux_numerical[end, element]
313
end
314
## boundary flux
315
flux_numerical[1, 1] = surface_flux(u[end, end], u[1, 1], 1, equations)
316
flux_numerical[end, end] = flux_numerical[1, 1]
317
318
## Calculate surface integrals, $- M^{-1} * B * u^*$
319
for element in 1:n_elements
320
du[:, element] -= (M \ B) * flux_numerical[:, element]
321
end
322
323
## Calculate volume integral, $+ M^{-1} * D^T * M * u$
324
for element in 1:n_elements
325
flux = u[:, element]
326
du[:, element] += (M \ transpose(D)) * M * flux
327
end
328
329
## Apply Jacobian from mapping to reference element
330
for element in 1:n_elements
331
du[:, element] *= 2 / dx
332
end
333
334
return nothing
335
end
336
337
# Combining all definitions and the function that calculates the right-hand side, we define the ODE and
338
# solve it until `t=2` with OrdinaryDiffEq's `solve` function and the Runge-Kutta method `RDPK3SpFSAL49()`,
339
# which is optimized for discontinuous Galerkin methods and hyperbolic PDEs. We set some common
340
# error tolerances `abstol=1.0e-6, reltol=1.0e-6` and pass `save_everystep=false` to avoid saving intermediate
341
# solution vectors in memory.
342
using OrdinaryDiffEqLowStorageRK
343
tspan = (0.0, 2.0)
344
ode = ODEProblem(rhs!, u0, tspan, x)
345
346
sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,
347
ode_default_options()...)
348
@test maximum(abs.(u0 - sol.u[end])) < 5e-5 #src
349
350
plot(vec(x), vec(sol.u[end]), label = "solution at t=$(tspan[2])", legend = :topleft,
351
lw = 3)
352
353
# ## Alternative Implementation based on Trixi.jl
354
# Now, we implement the same example. But this time, we directly use the functionality that Trixi.jl
355
# provides.
356
357
using Trixi, OrdinaryDiffEqLowStorageRK, Plots
358
359
# First, define the equation with a advection_velocity of `1`.
360
advection_velocity = 1.0
361
equations = LinearScalarAdvectionEquation1D(advection_velocity)
362
363
# Then, create a DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux.
364
# The implementation of the basis and the numerical flux is now already done.
365
solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)
366
367
# We will now create a mesh with 16 elements for the physical domain `[-1, 1]` with periodic boundaries.
368
# We use Trixi.jl's standard mesh [`TreeMesh`](@ref). Since it's limited to hypercube domains, we
369
# choose `2^4=16` elements. The mesh type supports Adaptive Mesh Refinement (AMR), that is why `n_cells_max` has to be set, even
370
# if we don't need AMR here.
371
coordinates_min = -1.0 # minimum coordinate
372
coordinates_max = 1.0 # maximum coordinate
373
mesh = TreeMesh(coordinates_min, coordinates_max,
374
initial_refinement_level = 4, # number of elements = 2^4
375
n_cells_max = 30_000, # set maximum capacity of tree data structure (only needed for AMR)
376
periodicity = true)
377
378
# A semidiscretization collects data structures and functions for the spatial discretization.
379
# In Trixi.jl, an initial condition has the following parameter structure and is of the type `SVector`.
380
function initial_condition_sine_wave(x, t, equations)
381
return SVector(1.0 + 0.5 * sin(pi * sum(x - equations.advection_velocity * t)))
382
end
383
384
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sine_wave, solver;
385
boundary_conditions = boundary_condition_periodic)
386
387
# Again, combining all definitions and the function that calculates the right-hand side, we define the ODE and
388
# solve it until `t=2` with OrdinaryDiffEq's `solve` function and the Runge-Kutta method `RDPK3SpFSAL49()`.
389
tspan = (0.0, 2.0)
390
ode_trixi = semidiscretize(semi, tspan)
391
392
sol_trixi = solve(ode_trixi, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,
393
ode_default_options()...);
394
395
# We add a plot of the new approximated solution to the one calculated before.
396
plot!(sol_trixi, label = "solution at t=$(tspan[2]) with Trixi.jl", legend = :topleft,
397
linestyle = :dash, lw = 2)
398
@test maximum(abs.(vec(u0) - sol_trixi.u[end])) maximum(abs.(u0 - sol.u[end])) #src
399
400
# ## Summary of the code
401
# To sum up, here is the complete code that we used.
402
403
# ### Raw implementation
404
## basis: Legendre-Gauss-Lobatto
405
using OrdinaryDiffEqLowStorageRK
406
using Trixi, LinearAlgebra, Plots
407
polydeg = 3 #= polynomial degree =#
408
basis = LobattoLegendreBasis(polydeg)
409
nodes = basis.nodes # Gauss-Lobatto nodes in [-1, 1]
410
D = basis.derivative_matrix
411
M = diagm(basis.weights) # mass matrix
412
B = diagm([-1; zeros(polydeg - 1); 1])
413
414
## mesh
415
coordinates_min = -1.0 # minimum coordinate
416
coordinates_max = 1.0 # maximum coordinate
417
n_elements = 16 # number of elements
418
419
dx = (coordinates_max - coordinates_min) / n_elements # length of one element
420
421
x = Matrix{Float64}(undef, length(nodes), n_elements)
422
for element in 1:n_elements
423
x_l = -1 + (element - 1) * dx + dx / 2
424
for i in eachindex(nodes) # basis points in [-1, 1]
425
ξ = nodes[i]
426
x[i, element] = x_l + dx / 2 * ξ
427
end
428
end
429
430
## initial condition
431
initial_condition_sine_wave(x) = 1.0 + 0.5 * sinpi(x)
432
u0 = initial_condition_sine_wave.(x)
433
434
plot(vec(x), vec(u0), label = "initial condition", legend = :topleft, markershape = :circle)
435
436
## flux Lax-Friedrichs
437
surface_flux = flux_lax_friedrichs
438
439
## rhs! method
440
function rhs!(du, u, x, t)
441
## reset du
442
du .= zero(eltype(du))
443
flux_numerical = copy(du)
444
445
## calculate interface and boundary fluxes
446
equations = LinearScalarAdvectionEquation1D(1.0)
447
for element in 2:(n_elements - 1)
448
## left interface
449
flux_numerical[1, element] = surface_flux(u[end, element - 1], u[1, element], 1,
450
equations)
451
flux_numerical[end, element - 1] = flux_numerical[1, element]
452
## right interface
453
flux_numerical[end, element] = surface_flux(u[end, element], u[1, element + 1], 1,
454
equations)
455
flux_numerical[1, element + 1] = flux_numerical[end, element]
456
end
457
## boundary flux
458
flux_numerical[1, 1] = surface_flux(u[end, end], u[1, 1], 1, equations)
459
flux_numerical[end, end] = flux_numerical[1, 1]
460
461
## calculate surface integrals
462
for element in 1:n_elements
463
du[:, element] -= (M \ B) * flux_numerical[:, element]
464
end
465
466
## calculate volume integral
467
for element in 1:n_elements
468
flux = u[:, element]
469
du[:, element] += (M \ transpose(D)) * M * flux
470
end
471
472
## apply Jacobian from mapping to reference element
473
for element in 1:n_elements
474
du[:, element] *= 2 / dx
475
end
476
477
return nothing
478
end
479
480
## create ODE problem
481
tspan = (0.0, 2.0)
482
ode = ODEProblem(rhs!, u0, tspan, x)
483
484
## solve
485
sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,
486
ode_default_options()...)
487
@test maximum(abs.(vec(u0) - sol_trixi.u[end])) maximum(abs.(u0 - sol.u[end])) #src
488
489
plot(vec(x), vec(sol.u[end]), label = "solution at t=$(tspan[2])", legend = :topleft,
490
lw = 3)
491
492
# ### Alternative Implementation based on Trixi.jl
493
using Trixi, OrdinaryDiffEqLowStorageRK, Plots
494
495
## equation with a advection_velocity of `1`.
496
advection_velocity = 1.0
497
equations = LinearScalarAdvectionEquation1D(advection_velocity)
498
499
## create DG solver with flux lax friedrichs and LGL basis
500
solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)
501
502
## distretize domain with `TreeMesh`
503
coordinates_min = -1.0 # minimum coordinate
504
coordinates_max = 1.0 # maximum coordinate
505
mesh = TreeMesh(coordinates_min, coordinates_max,
506
initial_refinement_level = 4, # number of elements = 2^4
507
n_cells_max = 30_000,
508
periodicity = true)
509
510
## create initial condition and semidiscretization
511
function initial_condition_sine_wave(x, t, equations)
512
return SVector(1.0 + 0.5 * sin(pi * sum(x - equations.advection_velocity * t)))
513
end
514
515
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sine_wave, solver;
516
boundary_conditions = boundary_condition_periodic)
517
518
## solve
519
tspan = (0.0, 2.0)
520
ode_trixi = semidiscretize(semi, tspan)
521
sol_trixi = solve(ode_trixi, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,
522
ode_default_options()...);
523
524
plot!(sol_trixi, label = "solution at t=$(tspan[2]) with Trixi.jl", legend = :topleft,
525
linestyle = :dash, lw = 2)
526
@test maximum(abs.(vec(u0) - sol_trixi.u[end])) maximum(abs.(u0 - sol.u[end])) #src
527
528
# ## Package versions
529
530
# These results were obtained using the following versions.
531
532
using InteractiveUtils
533
versioninfo()
534
535
using Pkg
536
Pkg.status(["Trixi", "OrdinaryDiffEqLowStorageRK", "Plots"],
537
mode = PKGMODE_MANIFEST)
538
539