Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/docs/literate/src/files/custom_semidiscretization.jl
5591 views
1
#src # Custom semidiscretizations
2
3
# As described in the [overview section](@ref overview-semidiscretizations),
4
# semidiscretizations are high-level descriptions of spatial discretizations
5
# in Trixi.jl. Trixi.jl's main focus is on hyperbolic conservation
6
# laws represented in a [`SemidiscretizationHyperbolic`](@ref).
7
# Hyperbolic-parabolic problems based on the advection-diffusion equation or
8
# the compressible Navier-Stokes equations can be represented in a
9
# [`SemidiscretizationHyperbolicParabolic`](@ref). This is described in the
10
# [basic tutorial on parabolic terms](@ref parabolic_terms) and its extension to
11
# [custom parabolic terms](@ref adding_new_parabolic_terms).
12
# In this tutorial, we will describe how these semidiscretizations work and how
13
# they can be used to create custom semidiscretizations involving also other tasks.
14
15
# ## Overview of the right-hand side evaluation
16
17
# The semidiscretizations provided by Trixi.jl are set up to create `ODEProblem`s from the
18
# [SciML ecosystem for ordinary differential equations](https://diffeq.sciml.ai/latest/).
19
# In particular, a spatial semidiscretization can be wrapped in an ODE problem
20
# using [`semidiscretize`](@ref), which returns an `ODEProblem`. This `ODEProblem`
21
# bundles an initial condition, a right-hand side (RHS) function, the time span,
22
# and possible parameters. The `ODEProblem`s created by Trixi.jl use the semidiscretization
23
# passed to [`semidiscretize`](@ref) as a parameter.
24
# For a [`SemidiscretizationHyperbolic`](@ref), the `ODEProblem` wraps
25
# `Trixi.rhs!` as ODE RHS.
26
# For a [`SemidiscretizationHyperbolicParabolic`](@ref), Trixi.jl
27
# uses a `SplitODEProblem` combining `Trixi.rhs_parabolic!` for the
28
# (potentially) stiff part and `Trixi.rhs!` for the other part.
29
30
# ## Standard Trixi.jl setup
31
32
# In this tutorial, we will consider the linear advection equation
33
# with source term
34
# ```math
35
# \partial_t u(t,x) + \partial_x u(t,x) = -\exp(-t) \sin\bigl(\pi (x - t) \bigr)
36
# ```
37
# with periodic boundary conditions in the domain `[-1, 1]` as a
38
# model problem.
39
# The initial condition is
40
# ```math
41
# u(0,x) = \sin(\pi x).
42
# ```
43
# The source term results in some damping and the analytical solution
44
# ```math
45
# u(t,x) = \exp(-t) \sin\bigl(\pi (x - t) \bigr).
46
# ```
47
# First, we discretize this equation using the standard functionality
48
# of Trixi.jl.
49
50
using OrdinaryDiffEqLowStorageRK
51
using Trixi, Plots
52
53
# The linear scalar advection equation is already implemented in
54
# Trixi.jl as [`LinearScalarAdvectionEquation1D`](@ref). We construct
55
# it with an advection velocity `1.0`.
56
57
equations = LinearScalarAdvectionEquation1D(1.0)
58
59
# Next, we use a standard [`DGSEM`](@ref) solver.
60
61
solver = DGSEM(polydeg = 3)
62
63
# We create a simple [`TreeMesh`](@ref) in 1D.
64
65
coordinates_min = (-1.0,)
66
coordinates_max = (+1.0,)
67
mesh = TreeMesh(coordinates_min, coordinates_max;
68
initial_refinement_level = 4,
69
n_cells_max = 10^4,
70
periodicity = true)
71
72
# We wrap everything in in a semidiscretization and pass the source
73
# terms as a standard Julia function. Please note that Trixi.jl uses
74
# `SVector`s from
75
# [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl)
76
# to store the conserved variables `u`. Thus, the return value of the
77
# source terms must be wrapped in an `SVector` - even if we consider
78
# just a scalar problem.
79
80
function initial_condition(x, t, equations)
81
return SVector(exp(-t) * sinpi(x[1] - t))
82
end
83
84
function source_terms_standard(u, x, t, equations)
85
return -initial_condition(x, t, equations)
86
end
87
88
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition,
89
solver;
90
boundary_conditions = boundary_condition_periodic,
91
source_terms = source_terms_standard)
92
93
# Now, we can create the `ODEProblem`, solve the resulting ODE
94
# using a time integration method from
95
# [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl),
96
# and visualize the numerical solution at the final time using
97
# [Plots.jl](https://github.com/JuliaPlots/Plots.jl).
98
99
tspan = (0.0, 3.0)
100
ode = semidiscretize(semi, tspan)
101
102
sol = solve(ode, RDPK3SpFSAL49(); ode_default_options()...)
103
104
plot(sol; label = "numerical sol.", legend = :topright)
105
106
# We can also plot the analytical solution for comparison.
107
# Since Trixi.jl uses `SVector`s for the variables, we take their `first`
108
# (and only) component to get the scalar value for manual plotting.
109
110
let
111
x = range(-1.0, 1.0; length = 200)
112
plot!(x, first.(initial_condition.(x, sol.t[end], equations)),
113
label = "analytical sol.", linestyle = :dash, legend = :topright)
114
end
115
116
# We can also add the initial condition to the plot.
117
118
plot!(sol.u[1], semi, label = "u0", linestyle = :dot, legend = :topleft)
119
120
# You can of course also use some
121
# [callbacks](https://trixi-framework.github.io/TrixiDocumentation/stable/callbacks/)
122
# provided by Trixi.jl as usual.
123
124
summary_callback = SummaryCallback()
125
analysis_interval = 100
126
analysis_callback = AnalysisCallback(semi; interval = analysis_interval)
127
alive_callback = AliveCallback(; analysis_interval)
128
callbacks = CallbackSet(summary_callback,
129
analysis_callback,
130
alive_callback)
131
132
sol = solve(ode, RDPK3SpFSAL49();
133
ode_default_options()..., callback = callbacks)
134
135
# ## Using a custom ODE right-hand side function
136
137
# Next, we will solve the same problem but use our own ODE RHS function.
138
# To demonstrate this, we will artificially create a global variable
139
# containing the current time of the simulation.
140
141
const GLOBAL_TIME = Ref(0.0)
142
143
function source_terms_custom(u, x, t, equations)
144
t = GLOBAL_TIME[]
145
return -initial_condition(x, t, equations)
146
end
147
148
# Next, we create our own RHS function to update the global time of
149
# the simulation before calling the RHS function from Trixi.jl.
150
151
function rhs_source_custom!(du_ode, u_ode, semi, t)
152
GLOBAL_TIME[] = t
153
return Trixi.rhs!(du_ode, u_ode, semi, t)
154
end
155
156
# Next, we create an `ODEProblem` manually copying over the data from
157
# the one we got from [`semidiscretize`](@ref) earlier.
158
159
ode_source_custom = ODEProblem(rhs_source_custom!,
160
ode.u0,
161
ode.tspan,
162
ode.p) # semi
163
sol_source_custom = solve(ode_source_custom, RDPK3SpFSAL49();
164
ode_default_options()...)
165
166
plot(sol_source_custom; label = "numerical sol.")
167
let
168
x = range(-1.0, 1.0; length = 200)
169
plot!(x, first.(initial_condition.(x, sol_source_custom.t[end], equations)),
170
label = "analytical sol.", linestyle = :dash, legend = :topleft)
171
end
172
plot!(sol_source_custom.u[1], semi, label = "u0", linestyle = :dot, legend = :topleft)
173
174
# This also works with callbacks as usual.
175
176
summary_callback = SummaryCallback()
177
analysis_interval = 100
178
analysis_callback = AnalysisCallback(semi; interval = analysis_interval)
179
alive_callback = AliveCallback(; analysis_interval)
180
callbacks = CallbackSet(summary_callback,
181
analysis_callback,
182
alive_callback)
183
184
sol = solve(ode_source_custom, RDPK3SpFSAL49();
185
ode_default_options()..., callback = callbacks)
186
187
# ## Setting up a custom semidiscretization
188
189
# Using a global constant is of course not really nice from a software
190
# engineering point of view. Thus, it can often be useful to collect
191
# additional data in the parameters of the `ODEProblem`. Thus, it is
192
# time to create our own semidiscretization. Here, we create a small
193
# wrapper of a standard semidiscretization of Trixi.jl and the current
194
# global time of the simulation.
195
196
struct CustomSemidiscretization{Semi, T} <: Trixi.AbstractSemidiscretization
197
semi::Semi
198
t::T
199
end
200
201
semi_custom = CustomSemidiscretization(semi, Ref(0.0))
202
203
# To get pretty printing in the REPL, you can consider specializing
204
#
205
# - `Base.show(io::IO, parameters::CustomSemidiscretization)`
206
# - `Base.show(io::IO, ::MIME"text/plain", parameters::CustomSemidiscretization)`
207
#
208
# for your custom semidiscretiation.
209
210
# Next, we create our own source terms that use the global time stored
211
# in the custom semidiscretiation.
212
213
source_terms_custom_semi = let semi_custom = semi_custom
214
function source_terms_custom_semi(u, x, t, equations)
215
t = semi_custom.t[]
216
return -initial_condition(x, t, equations)
217
end
218
end
219
220
# We also create a custom ODE RHS to update the current global time
221
# stored in the custom semidiscretization. We unpack the standard
222
# semidiscretization created by Trixi.jl and pass it to `Trixi.rhs!`.
223
224
function rhs_semi_custom!(du_ode, u_ode, semi_custom, t)
225
semi_custom.t[] = t
226
return Trixi.rhs!(du_ode, u_ode, semi_custom.semi, t)
227
end
228
229
# Finally, we set up an `ODEProblem` and solve it numerically.
230
231
ode_semi_custom = ODEProblem(rhs_semi_custom!,
232
ode.u0,
233
ode.tspan,
234
semi_custom)
235
sol_semi_custom = solve(ode_semi_custom, RDPK3SpFSAL49();
236
ode_default_options()...)
237
238
# If we want to make use of additional functionality provided by
239
# Trixi.jl, e.g., for plotting, we need to implement a few additional
240
# specializations. In this case, we forward everything to the standard
241
# semidiscretization provided by Trixi.jl wrapped in our custom
242
# semidiscretization.
243
244
Base.ndims(semi::CustomSemidiscretization) = ndims(semi.semi)
245
function Trixi.mesh_equations_solver_cache(semi::CustomSemidiscretization)
246
return Trixi.mesh_equations_solver_cache(semi.semi)
247
end
248
249
# Now, we can plot the numerical solution as usual.
250
251
plot(sol_semi_custom; label = "numerical sol.")
252
let
253
x = range(-1.0, 1.0; length = 200)
254
plot!(x, first.(initial_condition.(x, sol_semi_custom.t[end], equations)),
255
label = "analytical sol.", linestyle = :dash, legend = :topleft)
256
end
257
plot!(sol_semi_custom.u[1], semi, label = "u0", linestyle = :dot, legend = :topleft)
258
259
# This also works with many callbacks as usual. However, the
260
# [`AnalysisCallback`](@ref) requires some special handling since it
261
# makes use of a performance counter contained in the standard
262
# semidiscretizations of Trixi.jl to report some
263
# [performance metrics](@ref performance-metrics).
264
# Here, we forward all accesses to the performance counter to the
265
# wrapped semidiscretization.
266
267
function Base.getproperty(semi::CustomSemidiscretization, s::Symbol)
268
if s === :performance_counter
269
wrapped_semi = getfield(semi, :semi)
270
wrapped_semi.performance_counter
271
else
272
getfield(semi, s)
273
end
274
end
275
276
# Moreover, the [`AnalysisCallback`](@ref) also performs some error
277
# calculations. We also need to forward them to the wrapped
278
# semidiscretization.
279
280
function Trixi.calc_error_norms(func, u, t, analyzer,
281
semi::CustomSemidiscretization,
282
cache_analysis)
283
return Trixi.calc_error_norms(func, u, t, analyzer,
284
semi.semi,
285
cache_analysis)
286
end
287
288
# Now, we can work with the callbacks used before as usual.
289
290
summary_callback = SummaryCallback()
291
analysis_interval = 100
292
analysis_callback = AnalysisCallback(semi_custom;
293
interval = analysis_interval)
294
alive_callback = AliveCallback(; analysis_interval)
295
callbacks = CallbackSet(summary_callback,
296
analysis_callback,
297
alive_callback)
298
299
sol = solve(ode_semi_custom, RDPK3SpFSAL49();
300
ode_default_options()..., callback = callbacks)
301
302
# For even more advanced usage of custom semidiscretizations, you
303
# may look at the source code of the ones contained in Trixi.jl, e.g.,
304
# - [`SemidiscretizationHyperbolicParabolic`](@ref)
305
# - [`SemidiscretizationEulerGravity`](@ref)
306
# - [`SemidiscretizationEulerAcoustics`](@ref)
307
# - [`SemidiscretizationCoupled`](@ref)
308
309
# ## Package versions
310
311
# These results were obtained using the following versions.
312
313
using InteractiveUtils
314
versioninfo()
315
316
using Pkg
317
Pkg.status(["Trixi", "OrdinaryDiffEqLowStorageRK", "Plots"],
318
mode = PKGMODE_MANIFEST)
319
320