Path: blob/main/docs/literate/src/files/scalar_linear_advection_1d.jl
5591 views
#src Introduction to DG methods1using Test: @test #src23# This tutorial is about how to set up a simple way to approximate the solution of a hyperbolic partial4# differential equation. First, we will implement a basic and naive algorithm. Then, we will use predefined5# features from [Trixi.jl](https://github.com/trixi-framework/Trixi.jl) to show how you can use Trixi.jl on your own.67# We will implement the scalar linear advection equation in 1D with the advection velocity $1$.8# ```math9# u_t + u_x = 0,\; \text{for} \;t\in \mathbb{R}^+, x\in\Omega=[-1,1]10# ```11# We define the domain $\Omega$ by setting the boundaries.12coordinates_min = -1.0 # minimum coordinate13coordinates_max = 1.0 # maximum coordinate1415# We assume periodic boundaries and the following initial condition.16initial_condition_sine_wave(x) = 1.0 + 0.5 * sinpi(x)1718# ## The discontinuous Galerkin collocation spectral element method (DGSEM)19# ### i. Discretization of the physical domain20# To improve precision we want to approximate the solution on small parts of the physical domain.21# So, we split the domain $\Omega=[-1, 1]$ into elements $Q_l$ of length $dx$.2223n_elements = 16 # number of elements2425dx = (coordinates_max - coordinates_min) / n_elements # length of one element2627# To make the calculation more efficient and storing less information, we transform each element28# $Q_l$ with center point $x_l$ to a reference element $E=[-1, 1]$29# ```math30# Q_l=\Big[x_l-\frac{dx}{2}, x_l+\frac{dx}{2}\Big] \underset{x(\xi)}{\overset{\xi(x)}{\rightleftarrows}} [-1, 1].31# ```32# So, for every element the transformation from the reference domain to the physical domain is defined by33# ```math34# x(\xi) = x_l + \frac{dx}{2} \xi,\; \xi\in[-1, 1]35# ```36# Therefore,37# ```math38# \begin{align*}39# u &= u(x(\xi), t) \\40# u_x &= u_\xi \frac{d\xi}{dx} \\[3pt]41# \frac{d\xi}{dx} &= (x_\xi)^{-1} = \frac{2}{dx} =: J^{-1}. \\42# \end{align*}43# ```44# Here, $J$ is the Jacobian determinant of the transformation.4546# Using this transformation, we can transform our equation for each element $Q_l$.47# ```math48# \frac{dx}{2} u_t^{Q_l} + u_\xi^{Q_l} = 0 \text{, for }t\in\mathbb{R}^+,\; \xi\in[-1, 1]49# ```50# Here, $u_t^{Q_l}$ and $u_\xi^{Q_l}$ denote the time and spatial derivatives of the solution on the element $Q_l$.5152# ### ii. Polynomial approach53# Now, we want to approximate the solution in each element $Q_l$ by a polynomial of degree $N$. Since we transformed54# the equation, we can use the same polynomial approach for the reference coordinate $\xi\in[-1, 1]$ in every55# physical element $Q_l$. This saves a lot of resources by reducing the amount of calculations needed56# and storing less information.5758# For DGSEM we choose [Lagrange basis functions](https://en.wikipedia.org/wiki/Lagrange_polynomial)59# $\{l_j\}_{j=0}^N$ as our polynomial basis of degree $N$ in $[-1, 1]$.60# The solution in element $Q_l$ can be approximated by61# ```math62# 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)63# ```64# with $N+1$ coefficients $\{u_j^{Q_l}\}_{j=0}^N$.65# By construction the Lagrange basis has some useful advantages. This basis is defined by $N+1$ nodes, which66# fulfill a Kronecker property at the exact same nodes. Let $\{\xi_i\}_{i=0}^N$ be these nodes.67# ```math68# l_j(\xi_i) = \delta_{i,j} =69# \begin{cases}70# 1, & \text{if } i=j \\71# 0, & \text{else.}72# \end{cases}73# ```74# Because of this property, the polynomial coefficients are exact the values of $u^{Q_l}$ at the nodes75# ```math76# 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).77# ```7879# Next, we want to select the nodes $\{\xi_i\}_{i=0}^N$, which we use for the construction of the Lagrange80# polynomials. We choose the $N+1$ Gauss-Lobatto nodes, which are used for the81# [Gaussian-Lobatto quadrature](https://mathworld.wolfram.com/LobattoQuadrature.html).82# These always contain the boundary points at $-1$ and $+1$ and are well suited as interpolation nodes.83# The corresponding weights will be referred to as $\{w_j\}_{j=0}^N$.84# In Trixi.jl the basis with Lagrange polynomials on Gauss-Lobatto nodes is already defined.85using Trixi86polydeg = 3 #= polynomial degree = N =#87basis = LobattoLegendreBasis(polydeg)88# The Gauss-Lobatto nodes are89nodes = basis.nodes90# with the corresponding weights91weights = basis.weights9293# To illustrate how you can integrate using numerical quadrature with this Legendre-Gauss-Lobatto nodes,94# we give an example for $f(x)=x^3$. Since $f$ is of degree $3$, a polynomial interpolation with $N=3$ is exact.95# Therefore, the integral on $[-1, 1]$ can be calculated by96# ```math97# \begin{align*}98# \int_{-1}^1 f(x) dx &= \int_{-1}^1 \Big( \sum_{j=0}^3 f(\xi_j)l_j(x) \Big) dx99# = \sum_{j=0}^3 f(\xi_j) \int_{-1}^1 l_j(x)dx \\100# &=: \sum_{j=0}^3 f(\xi_j) w_j101# = \sum_{j=0}^3 \xi_j^3 w_j102# \end{align*}103# ```104# Let's use our nodes and weights for $N=3$ and plug in105integral = sum(nodes .^ 3 .* weights)106107# Using this polynomial approach leads to the equation108# ```math109# \frac{dx}{2} \dot{u}^{Q_l}(\xi, t) + u^{Q_l}(\xi, t)' = 0110# ```111# with $\dot{u}=\frac{\partial}{\partial t}u$ and $u'=\frac{\partial}{\partial x}u$.112# To approximate the solution, we need to get the polynomial coefficients $\{u_j^{Q_l}\}_{j=0}^N$113# for every element $Q_l$.114115# After defining all nodes, we can implement the spatial coordinate $x$ and its initial value $u0 = u(t_0)$116# for every node.117x = Matrix{Float64}(undef, length(nodes), n_elements)118for element in 1:n_elements119x_l = coordinates_min + (element - 1) * dx + dx / 2120for i in eachindex(nodes)121ξ = nodes[i] # nodes in [-1, 1]122x[i, element] = x_l + dx / 2 * ξ123end124end125126u0 = initial_condition_sine_wave.(x)127128# To have a look at the initial sinus curve, we plot it.129using Plots130plot(vec(x), vec(u0), label = "initial condition", legend = :topleft, markershape = :circle)131132# ### iii. Variational formulation133# After defining the equation and initial condition, we want to implement an algorithm to134# approximate the solution.135136# From now on, we only write $u$ instead of $u^{Q_l}$ for simplicity, but consider that all the following137# calculation only concern one element.138# Multiplying the new equation with the smooth Lagrange polynomials $\{l_i\}_{i=0}^N$ (test functions)139# and integrating over the reference element $E=[-1,1]$, we get the variational formulation of our140# transformed partial differential equation for $i=0,...,N$:141# ```math142# \begin{align*}143# \int_{-1}^1 \Big( \frac{dx}{2} \dot{u}(\xi, t) + u'(\xi, t) \Big) l_i(\xi)d\xi144# &= \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}} = 0145# \end{align*}146# ```147148# We deal with the two terms separately. We write $\int_{-1, N}^1 \;\cdot\; d\xi$ for the approximation149# of the integral using numerical quadrature with $N+1$ basis points. We use the Gauss-Lobatto nodes150# again. The numerical scalar product $\langle\cdot, \cdot\rangle_N$ is defined by151# $\langle f, g\rangle_N := \int_{-1, N}^1 f(\xi) g(\xi) d\xi$.152153# #### Term I:154# In the following calculation we approximate the integral numerically with quadrature on the Gauss-Lobatto155# nodes $\{\xi_i\}_{i=0}^N$ and then use the Kronecker property of the Lagrange polynomials. This approach156# of using the same nodes for the interpolation and quadrature is called collocation.157# ```math158# \begin{align*}159# \frac{dx}{2} \int_{-1}^1 \dot{u}(\xi, t) l_i(\xi)d\xi160# &\approx \frac{dx}{2} \int_{-1, N}^1 \dot{u}(\xi, t) l_i(\xi)d\xi \\161# &= \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 \\162# &= \frac{dx}{2} \dot{u}_i(t) w_i163# \end{align*}164# ```165# We define the Legendre-Gauss-Lobatto (LGL) mass matrix $M$ and by the Kronecker property follows:166# ```math167# M_{ij} = \langle l_j, l_i\rangle_N = \delta_{ij} w_j,\; i,j=0,...,N.168# ```169using LinearAlgebra170M = diagm(weights)171# Now, we can write the integral with this new matrix.172# ```math173# \frac{dx}{2} \int_{-1, N}^1 \dot{u}(\xi, t) \underline{l}(\xi)d\xi = \frac{dx}{2} M \underline{\dot{u}}(t),174# ```175# where $\underline{\dot{u}} = (\dot{u}_0, \dots, \dot{u}_N)^T$ and $\underline{l} = (l_0, \dots, l_N)^T$ respectively.176177# **Note:** Since the LGL quadrature with $N+1$ nodes is exact up to functions of degree $2N-1$ and178# $\dot{u}(\xi, t) l_i(\xi)$ is of degree $2N$, in general the following holds179# ```math180# \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.181# ```182# With an exact integration the mass matrix would be dense. Choosing numerical integrating and quadrature183# with the exact same nodes (collocation) leads to the sparse and diagonal mass matrix $M$. This184# is called mass lumping and has the big advantage of an easy inversion of the matrix.185186# #### Term II:187# We use spatial partial integration for the second term:188# ```math189# \int_{-1}^1 u'(\xi, t) l_i(\xi) d\xi = [u l_i]_{-1}^1 - \int_{-1}^1 u l_i'd\xi190# ```191# The resulting integral can be solved exactly with LGL quadrature since the polynomial is now192# of degree $2N-1$.193194# Again, we split the calculation in two steps.195196# #### Surface term197# As mentioned before, we approximate the solution with a polynomial in every element. Therefore, in198# general the value of this approximation at the interfaces between two elements is not unique. To solve199# this problem we introduce the idea of the numerical flux $u^*$, which will give an exact value at200# the interfaces. One of many different approaches and definitions for the calculation of the201# numerical flux we will deal with in [4. Numerical flux](@ref numerical_flux).202# ```math203# [u l_i]_{-1}^1 = u^*\big|^1 l_i(+1) - u^*\big|_{-1} l_i(-1)204# ```205# Since the Gauss-Lobatto nodes contain the element boundaries $-1$ and $+1$, we can use the206# Kronecker property of $l_i$ for the calculation of $l_i(-1)$ and $l_i(+1)$.207# ```math208# [u \underline{l}]_{-1}^1 = u^*\big|^1 \left(\begin{array}{c} 0 \\ \vdots \\ 0 \\ 1 \end{array}\right)209# - u^*\big|_{-1} \left(\begin{array}{c} 1 \\ 0 \\ \vdots \\ 0\end{array}\right)210# = B \underline{u}^*(t)211# ```212# with the boundary matrix213# ```math214# B = \begin{pmatrix}215# -1 & 0 & \cdots & 0\\216# 0 & 0 & \cdots & 0\\217# \vdots & \vdots & 0 & 0\\218# 0 & \cdots & 0 & 1219# \end{pmatrix}220# \qquad\text{and}\qquad221# \underline{u}^*(t) = \left(\begin{array}{c} u^*\big|_{-1} \\ 0 \\ \vdots \\ 0 \\ u^*\big|^1\end{array}\right).222# ```223B = diagm([-1; zeros(polydeg - 1); 1])224225# #### Volume term226# As mentioned before, the new integral can be solved exact since the function inside is of degree $2N-1$.227# ```math228# - \int_{-1}^1 u l_i'd\xi = - \int_{-1, N}^1 u l_i' d\xi229# = - \sum_{k=0}^N u(\xi_k, t) l_i'(\xi_k) w_k230# = - \sum_{k=0}^N u_k(t) D_{ki} w_k231# ```232# where $D$ is the derivative matrix defined by $D_{ki} = l_i'(\xi_k)$ for $i,k=0,...,N$.233D = basis.derivative_matrix234235# To show why this matrix is called the derivative matrix, we go back to our example $f(x)=x^3$.236# We calculate the derivation of $f$ at the Gauss-Lobatto nodes $\{\xi_k\}_{k=0}^N$ with $N=8$.237# ```math238# 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)239# = \sum_{j=0}^8 f(\xi_j) D_{kj}240# ```241# for $k=0,...,N$ and therefore, $\underline{f}' = D \underline{f}$.242basis_N8 = LobattoLegendreBasis(8)243plot(vec(x), x -> 3 * x^2, label = "f'", lw = 2)244scatter!(basis_N8.nodes, basis_N8.derivative_matrix * basis_N8.nodes .^ 3, label = "Df",245lw = 3)246247# Combining the volume term for every $i=0,...,N$ results in248# ```math249# \int_{-1}^1 u \underline{l'} d\xi = - D^T M \underline{u}(t)250# ```251252# Putting all parts together we get the following equation for the element $Q_l$253# ```math254# \frac{dx}{2} M \underline{\dot{u}}(t) = - B \underline{u}^*(t) + D^T M \underline{u}(t)255# ```256# or equivalent257# ```math258# \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].259# ```260# This is called the weak form of the DGSEM.261262# **Note:** For every element $Q_l$ we get a system of $N+1$ ordinary differential equations to263# calculate $N+1$ coefficients. Since the numerical flux $u^*$ is depending on extern values at264# the interfaces, the equation systems of adjacent elements are weakly linked.265266# ### [iv. Numerical flux](@id numerical_flux)267# As mentioned above, we still have to handle the problem of different values at the same point at268# the interfaces. This happens with the ideas of the numerical flux $f^*(u)=u^*$. The role of $f^*$269# might seem minor in this simple example, but is important for more complicated problems.270# There are two values at the same spatial coordinate. Let's say we are looking at the interface between271# the elements $Q_l$ and $Q_{l+1}$, while both elements got $N+1$ nodes as defined before. We call272# the first value of the right element $u_R=u_0^{Q_{l+1}}$ and the last one of the left element273# $u_L=u_N^{Q_l}$. So, for the value of the numerical flux on that interface the following holds274# ```math275# u^* = u^*(u_L, u_R).276# ```277# These values are interpreted as start values of a so-called Riemann problem. There are many278# different (approximate) Riemann solvers available and useful for different problems. We will279# use the local Lax-Friedrichs flux.280surface_flux = flux_lax_friedrichs281282# The only missing ingredient is the flux calculation at the boundaries $-1$ and $+1$.283# ```math284# u^{{Q_{first}}^*}\big|_{-1} = u^{{Q_{first}}^*}\big|_{-1}(u^{bound}(-1), u_R)285# \quad\text{and}\quad286# u^{{Q_{last}}^*}\big|^1 = u^{{Q_{last}}^*}\big|^1(u_L, u^{bound}(1))287# ```288# The boundaries are periodic, which means that the last value of the last element $u^{Q_{last}}_N$289# is used as $u_L$ at the first interface and accordingly for the other boundary.290291# Now, we implement a function, that calculates $\underline{\dot{u}}^{Q_l}$ for the given matrices,292# $\underline{u}$ and $\underline{u}^*$.293function rhs!(du, u, x, t)294## Reset du and flux matrix295du .= zero(eltype(du))296flux_numerical = copy(du)297298## Calculate interface and boundary fluxes, $u^* = (u^*|_{-1}, 0, ..., 0, u^*|^1)^T$299## Since we use the flux Lax-Friedrichs from Trixi.jl, we have to pass some extra arguments.300## Trixi.jl needs the equation we are dealing with and an additional `1`, that indicates the301## first coordinate direction.302equations = LinearScalarAdvectionEquation1D(1.0)303for element in 2:(n_elements - 1)304## left interface305flux_numerical[1, element] = surface_flux(u[end, element - 1], u[1, element], 1,306equations)307flux_numerical[end, element - 1] = flux_numerical[1, element]308## right interface309flux_numerical[end, element] = surface_flux(u[end, element], u[1, element + 1], 1,310equations)311flux_numerical[1, element + 1] = flux_numerical[end, element]312end313## boundary flux314flux_numerical[1, 1] = surface_flux(u[end, end], u[1, 1], 1, equations)315flux_numerical[end, end] = flux_numerical[1, 1]316317## Calculate surface integrals, $- M^{-1} * B * u^*$318for element in 1:n_elements319du[:, element] -= (M \ B) * flux_numerical[:, element]320end321322## Calculate volume integral, $+ M^{-1} * D^T * M * u$323for element in 1:n_elements324flux = u[:, element]325du[:, element] += (M \ transpose(D)) * M * flux326end327328## Apply Jacobian from mapping to reference element329for element in 1:n_elements330du[:, element] *= 2 / dx331end332333return nothing334end335336# Combining all definitions and the function that calculates the right-hand side, we define the ODE and337# solve it until `t=2` with OrdinaryDiffEq's `solve` function and the Runge-Kutta method `RDPK3SpFSAL49()`,338# which is optimized for discontinuous Galerkin methods and hyperbolic PDEs. We set some common339# error tolerances `abstol=1.0e-6, reltol=1.0e-6` and pass `save_everystep=false` to avoid saving intermediate340# solution vectors in memory.341using OrdinaryDiffEqLowStorageRK342tspan = (0.0, 2.0)343ode = ODEProblem(rhs!, u0, tspan, x)344345sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,346ode_default_options()...)347@test maximum(abs.(u0 - sol.u[end])) < 5e-5 #src348349plot(vec(x), vec(sol.u[end]), label = "solution at t=$(tspan[2])", legend = :topleft,350lw = 3)351352# ## Alternative Implementation based on Trixi.jl353# Now, we implement the same example. But this time, we directly use the functionality that Trixi.jl354# provides.355356using Trixi, OrdinaryDiffEqLowStorageRK, Plots357358# First, define the equation with a advection_velocity of `1`.359advection_velocity = 1.0360equations = LinearScalarAdvectionEquation1D(advection_velocity)361362# Then, create a DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux.363# The implementation of the basis and the numerical flux is now already done.364solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)365366# We will now create a mesh with 16 elements for the physical domain `[-1, 1]` with periodic boundaries.367# We use Trixi.jl's standard mesh [`TreeMesh`](@ref). Since it's limited to hypercube domains, we368# choose `2^4=16` elements. The mesh type supports Adaptive Mesh Refinement (AMR), that is why `n_cells_max` has to be set, even369# if we don't need AMR here.370coordinates_min = -1.0 # minimum coordinate371coordinates_max = 1.0 # maximum coordinate372mesh = TreeMesh(coordinates_min, coordinates_max,373initial_refinement_level = 4, # number of elements = 2^4374n_cells_max = 30_000, # set maximum capacity of tree data structure (only needed for AMR)375periodicity = true)376377# A semidiscretization collects data structures and functions for the spatial discretization.378# In Trixi.jl, an initial condition has the following parameter structure and is of the type `SVector`.379function initial_condition_sine_wave(x, t, equations)380return SVector(1.0 + 0.5 * sin(pi * sum(x - equations.advection_velocity * t)))381end382383semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sine_wave, solver;384boundary_conditions = boundary_condition_periodic)385386# Again, combining all definitions and the function that calculates the right-hand side, we define the ODE and387# solve it until `t=2` with OrdinaryDiffEq's `solve` function and the Runge-Kutta method `RDPK3SpFSAL49()`.388tspan = (0.0, 2.0)389ode_trixi = semidiscretize(semi, tspan)390391sol_trixi = solve(ode_trixi, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,392ode_default_options()...);393394# We add a plot of the new approximated solution to the one calculated before.395plot!(sol_trixi, label = "solution at t=$(tspan[2]) with Trixi.jl", legend = :topleft,396linestyle = :dash, lw = 2)397@test maximum(abs.(vec(u0) - sol_trixi.u[end])) ≈ maximum(abs.(u0 - sol.u[end])) #src398399# ## Summary of the code400# To sum up, here is the complete code that we used.401402# ### Raw implementation403## basis: Legendre-Gauss-Lobatto404using OrdinaryDiffEqLowStorageRK405using Trixi, LinearAlgebra, Plots406polydeg = 3 #= polynomial degree =#407basis = LobattoLegendreBasis(polydeg)408nodes = basis.nodes # Gauss-Lobatto nodes in [-1, 1]409D = basis.derivative_matrix410M = diagm(basis.weights) # mass matrix411B = diagm([-1; zeros(polydeg - 1); 1])412413## mesh414coordinates_min = -1.0 # minimum coordinate415coordinates_max = 1.0 # maximum coordinate416n_elements = 16 # number of elements417418dx = (coordinates_max - coordinates_min) / n_elements # length of one element419420x = Matrix{Float64}(undef, length(nodes), n_elements)421for element in 1:n_elements422x_l = -1 + (element - 1) * dx + dx / 2423for i in eachindex(nodes) # basis points in [-1, 1]424ξ = nodes[i]425x[i, element] = x_l + dx / 2 * ξ426end427end428429## initial condition430initial_condition_sine_wave(x) = 1.0 + 0.5 * sinpi(x)431u0 = initial_condition_sine_wave.(x)432433plot(vec(x), vec(u0), label = "initial condition", legend = :topleft, markershape = :circle)434435## flux Lax-Friedrichs436surface_flux = flux_lax_friedrichs437438## rhs! method439function rhs!(du, u, x, t)440## reset du441du .= zero(eltype(du))442flux_numerical = copy(du)443444## calculate interface and boundary fluxes445equations = LinearScalarAdvectionEquation1D(1.0)446for element in 2:(n_elements - 1)447## left interface448flux_numerical[1, element] = surface_flux(u[end, element - 1], u[1, element], 1,449equations)450flux_numerical[end, element - 1] = flux_numerical[1, element]451## right interface452flux_numerical[end, element] = surface_flux(u[end, element], u[1, element + 1], 1,453equations)454flux_numerical[1, element + 1] = flux_numerical[end, element]455end456## boundary flux457flux_numerical[1, 1] = surface_flux(u[end, end], u[1, 1], 1, equations)458flux_numerical[end, end] = flux_numerical[1, 1]459460## calculate surface integrals461for element in 1:n_elements462du[:, element] -= (M \ B) * flux_numerical[:, element]463end464465## calculate volume integral466for element in 1:n_elements467flux = u[:, element]468du[:, element] += (M \ transpose(D)) * M * flux469end470471## apply Jacobian from mapping to reference element472for element in 1:n_elements473du[:, element] *= 2 / dx474end475476return nothing477end478479## create ODE problem480tspan = (0.0, 2.0)481ode = ODEProblem(rhs!, u0, tspan, x)482483## solve484sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,485ode_default_options()...)486@test maximum(abs.(vec(u0) - sol_trixi.u[end])) ≈ maximum(abs.(u0 - sol.u[end])) #src487488plot(vec(x), vec(sol.u[end]), label = "solution at t=$(tspan[2])", legend = :topleft,489lw = 3)490491# ### Alternative Implementation based on Trixi.jl492using Trixi, OrdinaryDiffEqLowStorageRK, Plots493494## equation with a advection_velocity of `1`.495advection_velocity = 1.0496equations = LinearScalarAdvectionEquation1D(advection_velocity)497498## create DG solver with flux lax friedrichs and LGL basis499solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)500501## distretize domain with `TreeMesh`502coordinates_min = -1.0 # minimum coordinate503coordinates_max = 1.0 # maximum coordinate504mesh = TreeMesh(coordinates_min, coordinates_max,505initial_refinement_level = 4, # number of elements = 2^4506n_cells_max = 30_000,507periodicity = true)508509## create initial condition and semidiscretization510function initial_condition_sine_wave(x, t, equations)511return SVector(1.0 + 0.5 * sin(pi * sum(x - equations.advection_velocity * t)))512end513514semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sine_wave, solver;515boundary_conditions = boundary_condition_periodic)516517## solve518tspan = (0.0, 2.0)519ode_trixi = semidiscretize(semi, tspan)520sol_trixi = solve(ode_trixi, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6,521ode_default_options()...);522523plot!(sol_trixi, label = "solution at t=$(tspan[2]) with Trixi.jl", legend = :topleft,524linestyle = :dash, lw = 2)525@test maximum(abs.(vec(u0) - sol_trixi.u[end])) ≈ maximum(abs.(u0 - sol.u[end])) #src526527# ## Package versions528529# These results were obtained using the following versions.530531using InteractiveUtils532versioninfo()533534using Pkg535Pkg.status(["Trixi", "OrdinaryDiffEqLowStorageRK", "Plots"],536mode = PKGMODE_MANIFEST)537538539