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_new_scalar_equations.jl
5591 views
1
#src # Adding a new equation: scalar conservation laws
2
3
# If you want to use Trixi.jl for your own research, you might be interested in
4
# a new physics model that's not already included in Trixi.jl. In this tutorial,
5
# we will implement the cubic conservation law
6
# ```math
7
# \partial_t u(t,x) + \partial_x u(t,x)^3 = 0
8
# ```
9
# in a periodic domain in one space dimension. In Trixi.jl, such a mathematical model
10
# is encoded as a subtype of [`Trixi.AbstractEquations`](@ref).
11
12
# ## Basic setup
13
using Trixi
14
15
struct CubicEquation <: Trixi.AbstractEquations{1, # number of spatial dimensions
16
1} # number of primary variables, i.e. scalar
17
end
18
19
# We create `CubicEquation` as an empty `struct` since we do not use any parameters
20
# for this equation. Other models could bundle arbitrary parameters, e.g., the
21
# ideal gas constant for the compressible Euler equations.
22
23
# Next, we define the physical flux `f(u) = u^3` using the calling structure
24
# used in Trixi.jl.
25
26
Trixi.flux(u, orientation, equation::CubicEquation) = u .^ 3
27
Trixi.varnames(_, ::CubicEquation) = ("scalar",)
28
29
# In Trixi.jl, the conserved variables `u` are usually passed as `SVector`s of variables
30
# at a single physical location. Hence, we must use `u.^3` instead of the scalar
31
# operation `u^3`.
32
33
# That's already enough to run a simple simulation with a standard DGSEM discretization
34
# using the non-dissipative central flux at interfaces.
35
36
using OrdinaryDiffEqSSPRK
37
38
## Create a simulation setup
39
equation = CubicEquation()
40
41
initial_condition_sine(x, t, equation::CubicEquation) = SVector(sinpi(x[1]))
42
43
mesh = TreeMesh(-1.0, 1.0, # min/max coordinates
44
initial_refinement_level = 4,
45
n_cells_max = 10^4,
46
periodicity = true)
47
48
solver = DGSEM(3, flux_central) # set polynomial degree to 3
49
50
semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;
51
boundary_conditions = boundary_condition_periodic)
52
53
# We wrap the return value of the `initial_condition_sine` inside an `SVector` since that's the approach
54
# used in Trixi.jl also for systems of equations. We need to index the spatial coordinate `x[1]`,
55
# since it is an `SVector` with one component. In multiple space dimensions, all spatial coordinates
56
# are passed together.
57
58
# Next, we create an `ODEProblem` from the SciML/DifferentialEquations ecosystem.
59
# We can solve this ODE numerically using any time integration method,
60
# e.g., `SSPRK43` from [OrdinaryDiffEqSSPRK.jl](https://github.com/SciML/OrdinaryDiffEq.jl).
61
# Before, we set up a [callback](@ref callbacks-id) to summarize the simulation setup.
62
63
## Create ODE problem with given time span
64
tspan = (0.0, 0.09)
65
ode = semidiscretize(semi, tspan)
66
67
summary_callback = SummaryCallback()
68
callbacks = CallbackSet(summary_callback)
69
70
## OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks
71
sol = solve(ode, SSPRK43();
72
ode_default_options()..., callback = callbacks);
73
74
# That's it, you ran your first simulation using your new equation with Trixi.jl! Now, we can plot
75
# the solution at the final time using Plots.jl.
76
using Plots
77
plot(sol)
78
79
# You can already see that discontinuities will develop and oscillations start to
80
# occur around steep parts of the wave. That's expected from our central discretization.
81
# To avoid these issues, we need to use dissipative numerical fluxes (approximate
82
# Riemann solvers) at interfaces.
83
84
# ## Advanced setup
85
86
# Thus, we add a Godunov's flux for our cubic equation. That is easy for this equation
87
# since the wave speed `f'(u) = 3u^2` is always non-negative.
88
89
@inline Trixi.flux_godunov(u_ll, u_rr, orientation, equation::CubicEquation) = flux(u_ll,
90
orientation,
91
equation)
92
93
# Let's run the example again but with a dissipative numerical flux at interfaces.
94
# `remake` will recreate the semidiscretization we used before and only change
95
# selected parameters, in this case the `solver`.
96
97
## A new setup with dissipation
98
semi = remake(semi, solver = DGSEM(3, flux_godunov))
99
ode = semidiscretize(semi, tspan)
100
sol = solve(ode, SSPRK43(); ode_default_options()...)
101
plot!(sol)
102
103
# You can see that there are fewer oscillations, in particular around steep edges.
104
# Now let's increase the final time (and also the spatial resolution).
105
106
## A larger final time: Nonclassical shocks develop (you can even increase the refinement to 12)
107
semi = remake(semi,
108
mesh = TreeMesh(-1.0, 1.0, initial_refinement_level = 8, n_cells_max = 10^5,
109
periodicity = true))
110
ode = semidiscretize(semi, (0.0, 0.5)) # set tspan to (0.0, 0.5)
111
sol = solve(ode, SSPRK43(); ode_default_options()...)
112
plot(sol)
113
114
# You can observe that nonclassical shocks develop and are stable under grid refinement,
115
# e.g. for `initial_refinement_level=12`. In this case, these nonclassical shocks
116
# can be avoided by using an entropy-dissipative semidiscretization. Thus, we need
117
# to define an entropy-conservative numerical flux
118
119
@inline function Trixi.flux_ec(u_ll, u_rr, orientation, equation::CubicEquation)
120
return SVector(0.25 *
121
(u_ll[1]^3 + u_ll[1]^2 * u_rr[1] + u_ll[1] * u_rr[1]^2 + u_rr[1]^3))
122
end
123
124
# and use a [`VolumeIntegralFluxDifferencing`](@ref) instead of the standard
125
# [`VolumeIntegralWeakForm`](@ref) in the DGSEM.
126
127
## Let's use a provably entropy-dissipative semidiscretization
128
semi = remake(semi,
129
solver = DGSEM(3, flux_godunov, VolumeIntegralFluxDifferencing(flux_ec)))
130
ode = semidiscretize(semi, (0.0, 0.5))
131
sol = solve(ode, SSPRK43(); ode_default_options()...);
132
plot(sol)
133
134
# Possible next steps could be
135
# - to define `Trixi.max_abs_speeds(u, equations::CubicEquation) = 3 * u[1]^2`
136
# to use CFL-based time step control via a [`StepsizeCallback`](@ref)
137
# - to define quantities of interest like `Trixi.entropy(u, equations::CubicEquation) = u[1]^2`
138
# and integrate them in a simulation using the [`AnalysisCallback`](@ref)
139
# - to experiment with shock-capturing volume integrals [`VolumeIntegralShockCapturingHG`](@ref)
140
# and adaptive mesh refinement [`AMRCallback`](@ref)
141
142
# For further reading, Trixi.jl provides another example on adding a scalar equation. In the
143
# [elixir about the KPP problem](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/tree_2d_dgsem/elixir_kpp.jl),
144
# the 2D scalar "KPP equation" from [Kurganov, Petrova, Popov (2007)](https://doi.org/10.1137/040614189) is
145
# implemented.
146
147
# ## Summary of the code
148
149
# To sum up, here is the complete code that we used (without the callbacks since these create a
150
# lot of unnecessary output in the doctests of this tutorial).
151
# In addition, we create the `struct` inside the new module `CubicConservationLaw`. That
152
# ensures that we can re-create `struct`s defined therein without having to restart Julia.
153
154
## Define new physics
155
module CubicConservationLaw
156
157
using Trixi
158
159
struct CubicEquation <: Trixi.AbstractEquations{1, # number of spatial dimensions
160
1} # number of primary variables, i.e. scalar
161
end
162
163
@inline Trixi.flux(u, orientation, equation::CubicEquation) = u .^ 3
164
Trixi.varnames(_, ::CubicEquation) = ("scalar",)
165
166
@inline Trixi.flux_godunov(u_ll, u_rr, orientation, equation::CubicEquation) = flux(u_ll,
167
orientation,
168
equation)
169
@inline function Trixi.flux_ec(u_ll, u_rr, orientation, equation::CubicEquation)
170
return SVector(0.25 *
171
(u_ll[1]^3 + u_ll[1]^2 * u_rr[1] + u_ll[1] * u_rr[1]^2 + u_rr[1]^3))
172
end
173
174
end # module
175
176
## Create a simulation setup
177
import .CubicConservationLaw
178
using Trixi
179
using OrdinaryDiffEqSSPRK
180
using Plots
181
182
equation = CubicConservationLaw.CubicEquation()
183
184
function initial_condition_sine(x, t, equation::CubicConservationLaw.CubicEquation)
185
return SVector(sinpi(x[1]))
186
end
187
188
mesh = TreeMesh(-1.0, 1.0, # min/max coordinates
189
initial_refinement_level = 4,
190
n_cells_max = 10^4,
191
periodicity = true)
192
193
solver = DGSEM(3, flux_central) # set polynomial degree to 3
194
195
semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver;
196
boundary_conditions = boundary_condition_periodic)
197
198
## Create ODE problem with given time span
199
tspan = (0.0, 0.1)
200
ode = semidiscretize(semi, tspan)
201
202
## OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks
203
sol = solve(ode, SSPRK43(); ode_default_options()...)
204
plot(sol)
205
206
## A new setup with dissipation
207
semi = remake(semi, solver = DGSEM(3, flux_godunov))
208
ode = semidiscretize(semi, tspan)
209
sol = solve(ode, SSPRK43(); ode_default_options()...)
210
plot!(sol)
211
212
## A larger final time: Nonclassical shocks develop (you can even increase the refinement to 12)
213
semi = remake(semi,
214
mesh = TreeMesh(-1.0, 1.0, initial_refinement_level = 8, n_cells_max = 10^5,
215
periodicity = true))
216
ode = semidiscretize(semi, (0.0, 0.5))
217
sol = solve(ode, SSPRK43(); ode_default_options()...)
218
plot(sol)
219
220
## Let's use a provably entropy-dissipative semidiscretization
221
semi = remake(semi,
222
solver = DGSEM(3, flux_godunov, VolumeIntegralFluxDifferencing(flux_ec)))
223
ode = semidiscretize(semi, (0.0, 0.5))
224
sol = solve(ode, SSPRK43(); ode_default_options()...)
225
plot(sol)
226
227
# ## Package versions
228
229
# These results were obtained using the following versions.
230
231
using InteractiveUtils
232
versioninfo()
233
234
using Pkg
235
Pkg.status(["Trixi", "OrdinaryDiffEqSSPRK", "Plots"],
236
mode = PKGMODE_MANIFEST)
237
238