Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl
2055 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
"""
9
SemidiscretizationHyperbolicParabolic
10
11
A struct containing everything needed to describe a spatial semidiscretization
12
of a mixed hyperbolic-parabolic conservation law.
13
"""
14
struct SemidiscretizationHyperbolicParabolic{Mesh, Equations, EquationsParabolic,
15
InitialCondition,
16
BoundaryConditions,
17
BoundaryConditionsParabolic,
18
SourceTerms, Solver, SolverParabolic,
19
Cache, CacheParabolic} <:
20
AbstractSemidiscretization
21
mesh::Mesh
22
23
equations::Equations
24
equations_parabolic::EquationsParabolic
25
26
# This guy is a bit messy since we abuse it as some kind of "exact solution"
27
# although this doesn't really exist...
28
initial_condition::InitialCondition
29
30
boundary_conditions::BoundaryConditions
31
boundary_conditions_parabolic::BoundaryConditionsParabolic
32
33
source_terms::SourceTerms
34
35
solver::Solver
36
solver_parabolic::SolverParabolic
37
38
cache::Cache
39
cache_parabolic::CacheParabolic
40
41
performance_counter::PerformanceCounterList{2}
42
43
function SemidiscretizationHyperbolicParabolic{Mesh, Equations, EquationsParabolic,
44
InitialCondition, BoundaryConditions,
45
BoundaryConditionsParabolic,
46
SourceTerms, Solver,
47
SolverParabolic, Cache,
48
CacheParabolic}(mesh::Mesh,
49
equations::Equations,
50
equations_parabolic::EquationsParabolic,
51
initial_condition::InitialCondition,
52
boundary_conditions::BoundaryConditions,
53
boundary_conditions_parabolic::BoundaryConditionsParabolic,
54
source_terms::SourceTerms,
55
solver::Solver,
56
solver_parabolic::SolverParabolic,
57
cache::Cache,
58
cache_parabolic::CacheParabolic) where {
59
Mesh,
60
Equations,
61
EquationsParabolic,
62
InitialCondition,
63
BoundaryConditions,
64
BoundaryConditionsParabolic,
65
SourceTerms,
66
Solver,
67
SolverParabolic,
68
Cache,
69
CacheParabolic
70
}
71
@assert ndims(mesh) == ndims(equations)
72
73
# Todo: assert nvariables(equations)==nvariables(equations_parabolic)
74
75
performance_counter = PerformanceCounterList{2}(false)
76
77
new(mesh, equations, equations_parabolic, initial_condition,
78
boundary_conditions, boundary_conditions_parabolic,
79
source_terms, solver, solver_parabolic, cache, cache_parabolic,
80
performance_counter)
81
end
82
end
83
84
"""
85
SemidiscretizationHyperbolicParabolic(mesh, both_equations, initial_condition, solver;
86
solver_parabolic=default_parabolic_solver(),
87
source_terms=nothing,
88
both_boundary_conditions=(boundary_condition_periodic, boundary_condition_periodic),
89
RealT=real(solver),
90
uEltype=RealT)
91
92
Construct a semidiscretization of a hyperbolic-parabolic PDE.
93
"""
94
function SemidiscretizationHyperbolicParabolic(mesh, equations::Tuple,
95
initial_condition, solver;
96
solver_parabolic = default_parabolic_solver(),
97
source_terms = nothing,
98
boundary_conditions = (boundary_condition_periodic,
99
boundary_condition_periodic),
100
# `RealT` is used as real type for node locations etc.
101
# while `uEltype` is used as element type of solutions etc.
102
RealT = real(solver), uEltype = RealT)
103
equations_hyperbolic, equations_parabolic = equations
104
boundary_conditions_hyperbolic, boundary_conditions_parabolic = boundary_conditions
105
106
return SemidiscretizationHyperbolicParabolic(mesh, equations_hyperbolic,
107
equations_parabolic,
108
initial_condition, solver;
109
solver_parabolic, source_terms,
110
boundary_conditions = boundary_conditions_hyperbolic,
111
boundary_conditions_parabolic = boundary_conditions_parabolic,
112
RealT, uEltype)
113
end
114
115
function SemidiscretizationHyperbolicParabolic(mesh, equations, equations_parabolic,
116
initial_condition, solver;
117
solver_parabolic = default_parabolic_solver(),
118
source_terms = nothing,
119
boundary_conditions = boundary_condition_periodic,
120
boundary_conditions_parabolic = boundary_condition_periodic,
121
# `RealT` is used as real type for node locations etc.
122
# while `uEltype` is used as element type of solutions etc.
123
RealT = real(solver), uEltype = RealT)
124
cache = create_cache(mesh, equations, solver, RealT, uEltype)
125
_boundary_conditions = digest_boundary_conditions(boundary_conditions, mesh, solver,
126
cache)
127
_boundary_conditions_parabolic = digest_boundary_conditions(boundary_conditions_parabolic,
128
mesh, solver, cache)
129
130
check_periodicity_mesh_boundary_conditions(mesh, _boundary_conditions)
131
132
cache_parabolic = create_cache_parabolic(mesh, equations, equations_parabolic,
133
solver, solver_parabolic, RealT,
134
uEltype)
135
136
SemidiscretizationHyperbolicParabolic{typeof(mesh), typeof(equations),
137
typeof(equations_parabolic),
138
typeof(initial_condition),
139
typeof(_boundary_conditions),
140
typeof(_boundary_conditions_parabolic),
141
typeof(source_terms), typeof(solver),
142
typeof(solver_parabolic), typeof(cache),
143
typeof(cache_parabolic)}(mesh, equations,
144
equations_parabolic,
145
initial_condition,
146
_boundary_conditions,
147
_boundary_conditions_parabolic,
148
source_terms,
149
solver,
150
solver_parabolic,
151
cache,
152
cache_parabolic)
153
end
154
155
# Create a new semidiscretization but change some parameters compared to the input.
156
# `Base.similar` follows a related concept but would require us to `copy` the `mesh`,
157
# which would impact the performance. Instead, `SciMLBase.remake` has exactly the
158
# semantics we want to use here. In particular, it allows us to re-use mutable parts,
159
# e.g. `remake(semi).mesh === semi.mesh`.
160
function remake(semi::SemidiscretizationHyperbolicParabolic;
161
uEltype = real(semi.solver),
162
mesh = semi.mesh,
163
equations = semi.equations,
164
equations_parabolic = semi.equations_parabolic,
165
initial_condition = semi.initial_condition,
166
solver = semi.solver,
167
solver_parabolic = semi.solver_parabolic,
168
source_terms = semi.source_terms,
169
boundary_conditions = semi.boundary_conditions,
170
boundary_conditions_parabolic = semi.boundary_conditions_parabolic)
171
# TODO: Which parts do we want to `remake`? At least the solver needs some
172
# special care if shock-capturing volume integrals are used (because of
173
# the indicators and their own caches...).
174
SemidiscretizationHyperbolicParabolic(mesh, equations, equations_parabolic,
175
initial_condition, solver; solver_parabolic,
176
source_terms, boundary_conditions,
177
boundary_conditions_parabolic, uEltype)
178
end
179
180
function Base.show(io::IO, semi::SemidiscretizationHyperbolicParabolic)
181
@nospecialize semi # reduce precompilation time
182
183
print(io, "SemidiscretizationHyperbolicParabolic(")
184
print(io, semi.mesh)
185
print(io, ", ", semi.equations)
186
print(io, ", ", semi.equations_parabolic)
187
print(io, ", ", semi.initial_condition)
188
print(io, ", ", semi.boundary_conditions)
189
print(io, ", ", semi.boundary_conditions_parabolic)
190
print(io, ", ", semi.source_terms)
191
print(io, ", ", semi.solver)
192
print(io, ", ", semi.solver_parabolic)
193
print(io, ", cache(")
194
for (idx, key) in enumerate(keys(semi.cache))
195
idx > 1 && print(io, " ")
196
print(io, key)
197
end
198
print(io, "))")
199
end
200
201
function Base.show(io::IO, ::MIME"text/plain",
202
semi::SemidiscretizationHyperbolicParabolic)
203
@nospecialize semi # reduce precompilation time
204
205
if get(io, :compact, false)
206
show(io, semi)
207
else
208
summary_header(io, "SemidiscretizationHyperbolicParabolic")
209
summary_line(io, "#spatial dimensions", ndims(semi.equations))
210
summary_line(io, "mesh", semi.mesh)
211
summary_line(io, "hyperbolic equations", semi.equations |> typeof |> nameof)
212
summary_line(io, "parabolic equations",
213
semi.equations_parabolic |> typeof |> nameof)
214
summary_line(io, "initial condition", semi.initial_condition)
215
216
# print_boundary_conditions(io, semi)
217
218
summary_line(io, "source terms", semi.source_terms)
219
summary_line(io, "solver", semi.solver |> typeof |> nameof)
220
summary_line(io, "parabolic solver", semi.solver_parabolic |> typeof |> nameof)
221
summary_line(io, "total #DOFs per field", ndofsglobal(semi))
222
summary_footer(io)
223
end
224
end
225
226
@inline Base.ndims(semi::SemidiscretizationHyperbolicParabolic) = ndims(semi.mesh)
227
228
@inline function nvariables(semi::SemidiscretizationHyperbolicParabolic)
229
nvariables(semi.equations)
230
end
231
232
@inline Base.real(semi::SemidiscretizationHyperbolicParabolic) = real(semi.solver)
233
234
# retain dispatch on hyperbolic equations only
235
@inline function mesh_equations_solver_cache(semi::SemidiscretizationHyperbolicParabolic)
236
@unpack mesh, equations, solver, cache = semi
237
return mesh, equations, solver, cache
238
end
239
240
function calc_error_norms(func, u_ode, t, analyzer,
241
semi::SemidiscretizationHyperbolicParabolic, cache_analysis)
242
@unpack mesh, equations, initial_condition, solver, cache = semi
243
u = wrap_array(u_ode, mesh, equations, solver, cache)
244
245
calc_error_norms(func, u, t, analyzer, mesh, equations, initial_condition, solver,
246
cache, cache_analysis)
247
end
248
249
function compute_coefficients(t, semi::SemidiscretizationHyperbolicParabolic)
250
# Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl`
251
compute_coefficients(semi.initial_condition, t, semi)
252
end
253
254
function compute_coefficients!(u_ode, t, semi::SemidiscretizationHyperbolicParabolic)
255
compute_coefficients!(u_ode, semi.initial_condition, t, semi)
256
end
257
258
# Required for storing `extra_node_variables` in the `SaveSolutionCallback`.
259
# Not to be confused with `get_node_vars` which returns the variables of the simulated equation.
260
function get_node_variables!(node_variables, u_ode,
261
semi::SemidiscretizationHyperbolicParabolic)
262
get_node_variables!(node_variables, u_ode, mesh_equations_solver_cache(semi)...,
263
semi.equations_parabolic, semi.cache_parabolic)
264
end
265
266
"""
267
semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan)
268
269
Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan`
270
that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/).
271
The parabolic right-hand side is the first function of the split ODE problem and
272
will be used by default by the implicit part of IMEX methods from the
273
SciML ecosystem.
274
"""
275
function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan;
276
reset_threads = true)
277
# Optionally reset Polyester.jl threads. See
278
# https://github.com/trixi-framework/Trixi.jl/issues/1583
279
# https://github.com/JuliaSIMD/Polyester.jl/issues/30
280
if reset_threads
281
Polyester.reset_threads!()
282
end
283
284
u0_ode = compute_coefficients(first(tspan), semi)
285
# TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using
286
# mpi_isparallel() && MPI.Barrier(mpi_comm())
287
# See https://github.com/trixi-framework/Trixi.jl/issues/328
288
iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs!
289
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
290
# first function implicitly and the second one explicitly. Thus, we pass the
291
# stiffer parabolic function first.
292
return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)
293
end
294
295
"""
296
semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,
297
restart_file::AbstractString)
298
299
Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan`
300
that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/).
301
The parabolic right-hand side is the first function of the split ODE problem and
302
will be used by default by the implicit part of IMEX methods from the
303
SciML ecosystem.
304
305
The initial condition etc. is taken from the `restart_file`.
306
"""
307
function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,
308
restart_file::AbstractString;
309
reset_threads = true)
310
# Optionally reset Polyester.jl threads. See
311
# https://github.com/trixi-framework/Trixi.jl/issues/1583
312
# https://github.com/JuliaSIMD/Polyester.jl/issues/30
313
if reset_threads
314
Polyester.reset_threads!()
315
end
316
317
u0_ode = load_restart_file(semi, restart_file)
318
# TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using
319
# mpi_isparallel() && MPI.Barrier(mpi_comm())
320
# See https://github.com/trixi-framework/Trixi.jl/issues/328
321
iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs!
322
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
323
# first function implicitly and the second one explicitly. Thus, we pass the
324
# stiffer parabolic function first.
325
return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)
326
end
327
328
function rhs!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t)
329
@unpack mesh, equations, boundary_conditions, source_terms, solver, cache = semi
330
331
u = wrap_array(u_ode, mesh, equations, solver, cache)
332
du = wrap_array(du_ode, mesh, equations, solver, cache)
333
334
# TODO: Taal decide, do we need to pass the mesh?
335
time_start = time_ns()
336
@trixi_timeit timer() "rhs!" rhs!(du, u, t, mesh, equations,
337
boundary_conditions, source_terms, solver, cache)
338
runtime = time_ns() - time_start
339
put!(semi.performance_counter.counters[1], runtime)
340
341
return nothing
342
end
343
344
function rhs_parabolic!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t)
345
@unpack mesh, equations_parabolic, boundary_conditions_parabolic, source_terms, solver, solver_parabolic, cache, cache_parabolic = semi
346
347
u = wrap_array(u_ode, mesh, equations_parabolic, solver, cache_parabolic)
348
du = wrap_array(du_ode, mesh, equations_parabolic, solver, cache_parabolic)
349
350
# TODO: Taal decide, do we need to pass the mesh?
351
time_start = time_ns()
352
@trixi_timeit timer() "parabolic rhs!" rhs_parabolic!(du, u, t, mesh,
353
equations_parabolic,
354
boundary_conditions_parabolic,
355
source_terms,
356
solver, solver_parabolic,
357
cache, cache_parabolic)
358
runtime = time_ns() - time_start
359
put!(semi.performance_counter.counters[2], runtime)
360
361
return nothing
362
end
363
364
function _jacobian_ad_forward(semi::SemidiscretizationHyperbolicParabolic, t0, u0_ode,
365
du_ode, config)
366
new_semi = remake(semi, uEltype = eltype(config))
367
368
du_ode_hyp = Vector{eltype(config)}(undef, length(du_ode))
369
J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode
370
# Implementation of split ODE problem in OrdinaryDiffEq
371
rhs!(du_ode_hyp, u_ode, new_semi, t0)
372
rhs_parabolic!(du_ode, u_ode, new_semi, t0)
373
du_ode .+= du_ode_hyp
374
end
375
376
return J
377
end
378
379
"""
380
jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic;
381
t0=zero(real(semi)),
382
u0_ode=compute_coefficients(t0, semi))
383
384
Uses the *parabolic part* of the right-hand side operator of the [`SemidiscretizationHyperbolicParabolic`](@ref) `semi`
385
and forward mode automatic differentiation to compute the Jacobian `J` of the
386
parabolic/diffusive contribution only at time `t0` and state `u0_ode`.
387
388
This might be useful for operator-splitting methods, e.g., the construction of optimized
389
time integrators which optimize different methods for the hyperbolic and parabolic part separately.
390
"""
391
function jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic;
392
t0 = zero(real(semi)),
393
u0_ode = compute_coefficients(t0, semi))
394
jacobian_ad_forward_parabolic(semi, t0, u0_ode)
395
end
396
397
# The following version is for plain arrays
398
function jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic,
399
t0, u0_ode)
400
du_ode = similar(u0_ode)
401
config = ForwardDiff.JacobianConfig(nothing, du_ode, u0_ode)
402
403
# Use a function barrier since the generation of the `config` we use above
404
# is not type-stable
405
_jacobian_ad_forward_parabolic(semi, t0, u0_ode, du_ode, config)
406
end
407
408
function _jacobian_ad_forward_parabolic(semi, t0, u0_ode, du_ode, config)
409
new_semi = remake(semi, uEltype = eltype(config))
410
# Create anonymous function passed as first argument to `ForwardDiff.jacobian` to match
411
# `ForwardDiff.jacobian(f!, y::AbstractArray, x::AbstractArray,
412
# cfg::JacobianConfig = JacobianConfig(f!, y, x), check=Val{true}())`
413
J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode
414
Trixi.rhs_parabolic!(du_ode, u_ode, new_semi, t0)
415
end
416
417
return J
418
end
419
end # @muladd
420
421