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
5586 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, SourceTermsParabolic,
19
Solver, SolverParabolic,
20
Cache, CacheParabolic} <:
21
AbstractSemidiscretization
22
mesh::Mesh
23
24
equations::Equations
25
equations_parabolic::EquationsParabolic
26
27
# This guy is a bit messy since we abuse it as some kind of "exact solution"
28
# although this doesn't really exist...
29
initial_condition::InitialCondition
30
31
boundary_conditions::BoundaryConditions
32
boundary_conditions_parabolic::BoundaryConditionsParabolic
33
34
source_terms::SourceTerms
35
source_terms_parabolic::SourceTermsParabolic
36
37
solver::Solver
38
solver_parabolic::SolverParabolic
39
40
cache::Cache
41
cache_parabolic::CacheParabolic
42
43
performance_counter::PerformanceCounterList{2}
44
end
45
# We assume some properties of the fields of the semidiscretization, e.g.,
46
# the `equations` and the `mesh` should have the same dimension. We check these
47
# properties in the outer constructor defined below. While we could ensure
48
# them even better in an inner constructor, we do not use this approach to
49
# simplify the integration with Adapt.jl for GPU usage, see
50
# https://github.com/trixi-framework/Trixi.jl/pull/2677#issuecomment-3591789921
51
52
"""
53
SemidiscretizationHyperbolicParabolic(mesh, both_equations, initial_condition, solver;
54
solver_parabolic=default_parabolic_solver(),
55
source_terms=nothing,
56
source_terms_parabolic=nothing,
57
bboundary_conditions,
58
RealT=real(solver),
59
uEltype=RealT)
60
61
Construct a semidiscretization of a hyperbolic-parabolic PDE.
62
63
Boundary conditions must be provided explicitly as a tuple of two boundary conditions, where the first entry corresponds to the
64
hyperbolic part and the second to the parabolic part. The boundary conditions for the hyperbolic and parabolic part can be
65
either passed as `NamedTuple` or as a single boundary condition that is applied to all boundaries.
66
"""
67
function SemidiscretizationHyperbolicParabolic(mesh, equations::Tuple,
68
initial_condition, solver;
69
solver_parabolic = default_parabolic_solver(),
70
source_terms = nothing,
71
source_terms_parabolic = nothing,
72
boundary_conditions,
73
# `RealT` is used as real type for node locations etc.
74
# while `uEltype` is used as element type of solutions etc.
75
RealT = real(solver), uEltype = RealT)
76
equations, equations_parabolic = equations
77
78
@assert ndims(mesh) == ndims(equations)
79
@assert ndims(mesh) == ndims(equations_parabolic)
80
81
if !(nvariables(equations) == nvariables(equations_parabolic))
82
throw(ArgumentError("Current implementation of parabolic terms requires the same number of conservative and gradient variables."))
83
end
84
85
boundary_conditions, boundary_conditions_parabolic = boundary_conditions
86
87
cache = create_cache(mesh, equations, solver, RealT, uEltype)
88
_boundary_conditions = digest_boundary_conditions(boundary_conditions,
89
mesh, solver, cache)
90
check_periodicity_mesh_boundary_conditions(mesh, _boundary_conditions)
91
92
cache_parabolic = create_cache_parabolic(mesh, equations, solver,
93
nelements(solver, cache), uEltype)
94
95
_boundary_conditions_parabolic = digest_boundary_conditions(boundary_conditions_parabolic,
96
mesh, solver, cache)
97
check_periodicity_mesh_boundary_conditions(mesh, _boundary_conditions_parabolic)
98
99
performance_counter = PerformanceCounterList{2}(false)
100
101
return SemidiscretizationHyperbolicParabolic{typeof(mesh),
102
typeof(equations),
103
typeof(equations_parabolic),
104
typeof(initial_condition),
105
typeof(_boundary_conditions),
106
typeof(_boundary_conditions_parabolic),
107
typeof(source_terms),
108
typeof(source_terms_parabolic),
109
typeof(solver),
110
typeof(solver_parabolic),
111
typeof(cache),
112
typeof(cache_parabolic)}(mesh,
113
equations,
114
equations_parabolic,
115
initial_condition,
116
_boundary_conditions,
117
_boundary_conditions_parabolic,
118
source_terms,
119
source_terms_parabolic,
120
solver,
121
solver_parabolic,
122
cache,
123
cache_parabolic,
124
performance_counter)
125
end
126
127
# Create a new semidiscretization but change some parameters compared to the input.
128
# `Base.similar` follows a related concept but would require us to `copy` the `mesh`,
129
# which would impact the performance. Instead, `SciMLBase.remake` has exactly the
130
# semantics we want to use here. In particular, it allows us to re-use mutable parts,
131
# e.g. `remake(semi).mesh === semi.mesh`.
132
function remake(semi::SemidiscretizationHyperbolicParabolic;
133
uEltype = real(semi.solver),
134
mesh = semi.mesh,
135
equations = semi.equations,
136
equations_parabolic = semi.equations_parabolic,
137
initial_condition = semi.initial_condition,
138
solver = semi.solver,
139
solver_parabolic = semi.solver_parabolic,
140
source_terms = semi.source_terms,
141
source_terms_parabolic = semi.source_terms_parabolic,
142
boundary_conditions = semi.boundary_conditions,
143
boundary_conditions_parabolic = semi.boundary_conditions_parabolic)
144
# TODO: Which parts do we want to `remake`? At least the solver needs some
145
# special care if shock-capturing volume integrals are used (because of
146
# the indicators and their own caches...).
147
return SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic),
148
initial_condition, solver;
149
solver_parabolic, source_terms,
150
source_terms_parabolic,
151
boundary_conditions = (boundary_conditions,
152
boundary_conditions_parabolic),
153
uEltype)
154
end
155
156
function Base.show(io::IO, semi::SemidiscretizationHyperbolicParabolic)
157
@nospecialize semi # reduce precompilation time
158
159
print(io, "SemidiscretizationHyperbolicParabolic(")
160
print(io, semi.mesh)
161
print(io, ", ", semi.equations)
162
print(io, ", ", semi.equations_parabolic)
163
print(io, ", ", semi.initial_condition)
164
print(io, ", ", semi.boundary_conditions)
165
print(io, ", ", semi.boundary_conditions_parabolic)
166
print(io, ", ", semi.source_terms)
167
print(io, ", ", semi.source_terms_parabolic)
168
print(io, ", ", semi.solver)
169
print(io, ", ", semi.solver_parabolic)
170
print(io, ", cache(")
171
for (idx, key) in enumerate(keys(semi.cache))
172
idx > 1 && print(io, " ")
173
print(io, key)
174
end
175
print(io, "))")
176
return nothing
177
end
178
179
function Base.show(io::IO, ::MIME"text/plain",
180
semi::SemidiscretizationHyperbolicParabolic)
181
@nospecialize semi # reduce precompilation time
182
183
if get(io, :compact, false)
184
show(io, semi)
185
else
186
summary_header(io, "SemidiscretizationHyperbolicParabolic")
187
summary_line(io, "#spatial dimensions", ndims(semi.equations))
188
summary_line(io, "mesh", semi.mesh)
189
summary_line(io, "hyperbolic equations", semi.equations |> typeof |> nameof)
190
summary_line(io, "parabolic equations",
191
semi.equations_parabolic |> typeof |> nameof)
192
summary_line(io, "initial condition", semi.initial_condition)
193
194
# print_boundary_conditions(io, semi)
195
196
summary_line(io, "source terms", semi.source_terms)
197
summary_line(io, "source terms parabolic", semi.source_terms_parabolic)
198
summary_line(io, "solver", semi.solver |> typeof |> nameof)
199
summary_line(io, "parabolic solver", semi.solver_parabolic |> typeof |> nameof)
200
summary_line(io, "total #DOFs per field", ndofsglobal(semi))
201
summary_footer(io)
202
end
203
end
204
205
@inline Base.ndims(semi::SemidiscretizationHyperbolicParabolic) = ndims(semi.mesh)
206
207
@inline function nvariables(semi::SemidiscretizationHyperbolicParabolic)
208
return nvariables(semi.equations)
209
end
210
211
@inline Base.real(semi::SemidiscretizationHyperbolicParabolic) = real(semi.solver)
212
213
# retain dispatch on hyperbolic equations only
214
@inline function mesh_equations_solver_cache(semi::SemidiscretizationHyperbolicParabolic)
215
@unpack mesh, equations, solver, cache = semi
216
return mesh, equations, solver, cache
217
end
218
219
function calc_error_norms(func, u_ode, t, analyzer,
220
semi::SemidiscretizationHyperbolicParabolic, cache_analysis)
221
@unpack mesh, equations, initial_condition, solver, cache = semi
222
u = wrap_array(u_ode, mesh, equations, solver, cache)
223
224
return calc_error_norms(func, u, t, analyzer, mesh, equations, initial_condition,
225
solver,
226
cache, cache_analysis)
227
end
228
229
function compute_coefficients(t, semi::SemidiscretizationHyperbolicParabolic)
230
# Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl`
231
return compute_coefficients(semi.initial_condition, t, semi)
232
end
233
234
function compute_coefficients!(u_ode, t, semi::SemidiscretizationHyperbolicParabolic)
235
return compute_coefficients!(u_ode, semi.initial_condition, t, semi)
236
end
237
238
# Required for storing `extra_node_variables` in the `SaveSolutionCallback`.
239
# Not to be confused with `get_node_vars` which returns the variables of the simulated equation.
240
function get_node_variables!(node_variables, u_ode,
241
semi::SemidiscretizationHyperbolicParabolic)
242
return get_node_variables!(node_variables, u_ode,
243
mesh_equations_solver_cache(semi)...,
244
semi.equations_parabolic, semi.cache_parabolic)
245
end
246
247
"""
248
semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan;
249
jac_prototype_parabolic::Union{AbstractMatrix, Nothing} = nothing,
250
colorvec_parabolic::Union{AbstractVector, Nothing} = nothing)
251
252
Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan`
253
that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/).
254
The parabolic right-hand side is the first function of the split ODE problem and
255
will be used by default by the implicit part of IMEX methods from the
256
SciML ecosystem.
257
258
Optional keyword arguments:
259
- `jac_prototype_parabolic`: Expected to come from [SparseConnectivityTracer.jl](https://github.com/adrhill/SparseConnectivityTracer.jl).
260
Specifies the sparsity structure of the parabolic function's Jacobian to enable e.g. efficient implicit time stepping.
261
The [`SplitODEProblem`](https://docs.sciml.ai/DiffEqDocs/stable/types/split_ode_types/#SciMLBase.SplitODEProblem) only expects the Jacobian
262
to be defined on the first function it takes in, which is treated implicitly. This corresponds to the parabolic right-hand side in Trixi.jl.
263
The hyperbolic right-hand side is expected to be treated explicitly, and therefore its Jacobian is irrelevant.
264
- `colorvec_parabolic`: Expected to come from [SparseMatrixColorings.jl](https://github.com/gdalle/SparseMatrixColorings.jl).
265
Allows for even faster Jacobian computation. Not necessarily required when `jac_prototype_parabolic` is given.
266
"""
267
function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan;
268
jac_prototype_parabolic::Union{AbstractMatrix, Nothing} = nothing,
269
colorvec_parabolic::Union{AbstractVector, Nothing} = nothing,
270
reset_threads = true)
271
# Optionally reset Polyester.jl threads. See
272
# https://github.com/trixi-framework/Trixi.jl/issues/1583
273
# https://github.com/JuliaSIMD/Polyester.jl/issues/30
274
if reset_threads
275
Polyester.reset_threads!()
276
end
277
278
u0_ode = compute_coefficients(first(tspan), semi)
279
# TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using
280
# mpi_isparallel() && MPI.Barrier(mpi_comm())
281
# See https://github.com/trixi-framework/Trixi.jl/issues/328
282
iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs!
283
284
# Check if Jacobian prototype is provided for sparse Jacobian
285
if jac_prototype_parabolic !== nothing
286
# Convert `jac_prototype_parabolic` to real type, as seen here:
287
# https://docs.sciml.ai/DiffEqDocs/stable/tutorials/advanced_ode_example/#Declaring-a-Sparse-Jacobian-with-Automatic-Sparsity-Detection
288
parabolic_ode = SciMLBase.ODEFunction(rhs_parabolic!,
289
jac_prototype = convert.(eltype(u0_ode),
290
jac_prototype_parabolic),
291
colorvec = colorvec_parabolic) # coloring vector is optional
292
293
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
294
# first function implicitly and the second one explicitly. Thus, we pass the
295
# (potentially) stiffer parabolic function first.
296
return SplitODEProblem{iip}(parabolic_ode, rhs!, u0_ode, tspan, semi)
297
else
298
# We could also construct an `ODEFunction` explicitly without the Jacobian here,
299
# but we stick to the lean direct in-place functions `rhs_parabolic!` and
300
# let OrdinaryDiffEq.jl handle the rest
301
return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)
302
end
303
end
304
305
"""
306
semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,
307
restart_file::AbstractString;
308
jac_prototype_parabolic::Union{AbstractMatrix, Nothing} = nothing,
309
colorvec_parabolic::Union{AbstractVector, Nothing} = nothing)
310
311
Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan`
312
that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/).
313
The parabolic right-hand side is the first function of the split ODE problem and
314
will be used by default by the implicit part of IMEX methods from the
315
SciML ecosystem.
316
317
The initial condition etc. is taken from the `restart_file`.
318
319
Optional keyword arguments:
320
- `jac_prototype_parabolic`: Expected to come from [SparseConnectivityTracer.jl](https://github.com/adrhill/SparseConnectivityTracer.jl).
321
Specifies the sparsity structure of the parabolic function's Jacobian to enable e.g. efficient implicit time stepping.
322
The [`SplitODEProblem`](https://docs.sciml.ai/DiffEqDocs/stable/types/split_ode_types/#SciMLBase.SplitODEProblem) only expects the Jacobian
323
to be defined on the first function it takes in, which is treated implicitly. This corresponds to the parabolic right-hand side in Trixi.jl.
324
The hyperbolic right-hand side is expected to be treated explicitly, and therefore its Jacobian is irrelevant.
325
- `colorvec_parabolic`: Expected to come from [SparseMatrixColorings.jl](https://github.com/gdalle/SparseMatrixColorings.jl).
326
Allows for even faster Jacobian computation. Not necessarily required when `jac_prototype_parabolic` is given.
327
"""
328
function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,
329
restart_file::AbstractString;
330
jac_prototype_parabolic::Union{AbstractMatrix, Nothing} = nothing,
331
colorvec_parabolic::Union{AbstractVector, Nothing} = nothing,
332
reset_threads = true)
333
# Optionally reset Polyester.jl threads. See
334
# https://github.com/trixi-framework/Trixi.jl/issues/1583
335
# https://github.com/JuliaSIMD/Polyester.jl/issues/30
336
if reset_threads
337
Polyester.reset_threads!()
338
end
339
340
u0_ode = load_restart_file(semi, restart_file)
341
# TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using
342
# mpi_isparallel() && MPI.Barrier(mpi_comm())
343
# See https://github.com/trixi-framework/Trixi.jl/issues/328
344
iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs!
345
346
# Check if Jacobian prototype is provided for sparse Jacobian
347
if jac_prototype_parabolic !== nothing
348
# Convert `jac_prototype_parabolic` to real type, as seen here:
349
# https://docs.sciml.ai/DiffEqDocs/stable/tutorials/advanced_ode_example/#Declaring-a-Sparse-Jacobian-with-Automatic-Sparsity-Detection
350
parabolic_ode = SciMLBase.ODEFunction(rhs_parabolic!,
351
jac_prototype = convert.(eltype(u0_ode),
352
jac_prototype_parabolic),
353
colorvec = colorvec_parabolic) # coloring vector is optional
354
355
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
356
# first function implicitly and the second one explicitly. Thus, we pass the
357
# (potentially) stiffer parabolic function first.
358
return SplitODEProblem{iip}(parabolic_ode, rhs!, u0_ode, tspan, semi)
359
else
360
# We could also construct an `ODEFunction` explicitly without the Jacobian here,
361
# but we stick to the lean direct in-place function `rhs_parabolic!` and
362
# let OrdinaryDiffEq.jl handle the rest
363
return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)
364
end
365
end
366
367
function rhs!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t)
368
@unpack mesh, equations, boundary_conditions, source_terms, solver, cache = semi
369
370
u = wrap_array(u_ode, mesh, equations, solver, cache)
371
du = wrap_array(du_ode, mesh, equations, solver, cache)
372
backend = trixi_backend(u)
373
374
# TODO: Taal decide, do we need to pass the mesh?
375
time_start = time_ns()
376
@trixi_timeit_ext backend timer() "rhs!" rhs!(du, u, t, mesh, equations,
377
boundary_conditions, source_terms,
378
solver, cache)
379
runtime = time_ns() - time_start
380
put!(semi.performance_counter.counters[1], runtime)
381
382
return nothing
383
end
384
385
function rhs_parabolic!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t)
386
@unpack mesh, equations_parabolic, boundary_conditions_parabolic, source_terms_parabolic, solver, solver_parabolic, cache, cache_parabolic = semi
387
388
u = wrap_array(u_ode, mesh, equations_parabolic, solver, cache)
389
du = wrap_array(du_ode, mesh, equations_parabolic, solver, cache)
390
backend = trixi_backend(u)
391
392
# TODO: Taal decide, do we need to pass the mesh?
393
time_start = time_ns()
394
@trixi_timeit_ext backend timer() "parabolic rhs!" rhs_parabolic!(du, u, t, mesh,
395
equations_parabolic,
396
boundary_conditions_parabolic,
397
source_terms_parabolic,
398
solver,
399
solver_parabolic,
400
cache,
401
cache_parabolic)
402
runtime = time_ns() - time_start
403
put!(semi.performance_counter.counters[2], runtime)
404
405
return nothing
406
end
407
408
"""
409
linear_structure(semi::SemidiscretizationHyperbolicParabolic;
410
t0 = zero(real(semi)))
411
412
Wraps the right-hand side operator of the hyperbolic-parabolic semidiscretization `semi`
413
at time `t0` as an affine-linear operator given by a linear operator `A`
414
and a vector `b`:
415
```math
416
\\partial_t u(t) = A u(t) - b.
417
```
418
Works only for linear equations, i.e.,
419
equations which `have_constant_speed(equations) == True()` and `have_constant_diffusivity(equations_parabolic) == True()`
420
421
This has the benefit of greatly reduced memory consumption compared to constructing
422
the full system matrix explicitly, as done for instance in
423
[`jacobian_fd`](@ref) and [`jacobian_ad_forward`](@ref).
424
425
The returned linear operator `A` is a matrix-free representation which can be
426
supplied to iterative solvers from, e.g., [Krylov.jl](https://github.com/JuliaSmoothOptimizers/Krylov.jl).
427
"""
428
function linear_structure(semi::SemidiscretizationHyperbolicParabolic;
429
t0 = zero(real(semi)))
430
if (have_constant_speed(semi.equations) == False() ||
431
have_constant_diffusivity(semi.equations_parabolic) == False())
432
throw(ArgumentError("`linear_structure` expects linear equations."))
433
end
434
435
# additional storage for parabolic part
436
dest_para = allocate_coefficients(mesh_equations_solver_cache(semi)...)
437
438
apply_rhs! = function (dest, src)
439
rhs!(dest, src, semi, t0)
440
rhs_parabolic!(dest_para, src, semi, t0)
441
@. dest += dest_para
442
return dest
443
end
444
445
return _linear_structure_from_rhs(semi, apply_rhs!)
446
end
447
448
function _jacobian_ad_forward(semi::SemidiscretizationHyperbolicParabolic, t0, u0_ode,
449
du_ode, config)
450
new_semi = remake(semi, uEltype = eltype(config))
451
452
du_ode_hyp = Vector{eltype(config)}(undef, length(du_ode))
453
J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode
454
# Implementation of split ODE problem in OrdinaryDiffEq
455
rhs!(du_ode_hyp, u_ode, new_semi, t0)
456
rhs_parabolic!(du_ode, u_ode, new_semi, t0)
457
return du_ode .+= du_ode_hyp
458
end
459
460
return J
461
end
462
463
"""
464
linear_structure_parabolic(semi::SemidiscretizationHyperbolicParabolic;
465
t0 = zero(real(semi)))
466
467
Wraps the **parabolic part** right-hand side operator of the hyperbolic-parabolic semidiscretization `semi`
468
at time `t0` as an affine-linear operator given by a linear operator `A`
469
and a vector `b`:
470
```math
471
\\partial_t u(t) = A u(t) - b.
472
```
473
Works only for linear parabolic equations, i.e.,
474
equations which `have_constant_diffusivity(equations_parabolic) == True()`.
475
476
This has the benefit of greatly reduced memory consumption compared to constructing
477
the full system matrix explicitly, as done for instance in
478
[`jacobian_ad_forward_parabolic`](@ref).
479
480
The returned linear operator `A` is a matrix-free representation which can be
481
supplied to iterative solvers from, e.g., [Krylov.jl](https://github.com/JuliaSmoothOptimizers/Krylov.jl).
482
483
It is also possible to use this to construct a sparse matrix without the detour of constructing
484
first the full Jacobian by calling
485
```julia
486
using SparseArrays
487
A_map, b = linear_structure_parabolic(semi, t0 = t0)
488
A_sparse = sparse(A_map)
489
```
490
which can then be further used to construct for instance a
491
[`MatrixOperator`](https://docs.sciml.ai/SciMLOperators/stable/tutorials/getting_started/#Simplest-Operator:-MatrixOperator)
492
from [SciMLOperators.jl](https://docs.sciml.ai/SciMLOperators/stable/).
493
This is especially useful for IMEX schemes where the parabolic part is implicitly,
494
and for a `MatrixOperator` only factorized once.
495
"""
496
function linear_structure_parabolic(semi::SemidiscretizationHyperbolicParabolic;
497
t0 = zero(real(semi)))
498
if have_constant_diffusivity(semi.equations_parabolic) == False()
499
throw(ArgumentError("`linear_structure_parabolic` expects equations with constant diffusive terms."))
500
end
501
502
apply_rhs! = function (dest, src)
503
return rhs_parabolic!(dest, src, semi, t0)
504
end
505
506
return _linear_structure_from_rhs(semi, apply_rhs!)
507
end
508
509
"""
510
jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic;
511
t0 = zero(real(semi)),
512
u0_ode = compute_coefficients(t0, semi))
513
514
Uses the *parabolic part* of the right-hand side operator of the [`SemidiscretizationHyperbolicParabolic`](@ref) `semi`
515
and forward mode automatic differentiation to compute the Jacobian `J` of the
516
parabolic/diffusive contribution only at time `t0` and state `u0_ode`.
517
518
This might be useful for operator-splitting methods, e.g., the construction of optimized
519
time integrators which optimize different methods for the hyperbolic and parabolic part separately.
520
"""
521
function jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic;
522
t0 = zero(real(semi)),
523
u0_ode = compute_coefficients(t0, semi))
524
return jacobian_ad_forward_parabolic(semi, t0, u0_ode)
525
end
526
527
# The following version is for plain arrays
528
function jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic,
529
t0, u0_ode)
530
du_ode = similar(u0_ode)
531
config = ForwardDiff.JacobianConfig(nothing, du_ode, u0_ode)
532
533
# Use a function barrier since the generation of the `config` we use above
534
# is not type-stable
535
return _jacobian_ad_forward_parabolic(semi, t0, u0_ode, du_ode, config)
536
end
537
538
function _jacobian_ad_forward_parabolic(semi, t0, u0_ode, du_ode, config)
539
new_semi = remake(semi, uEltype = eltype(config))
540
# Create anonymous function passed as first argument to `ForwardDiff.jacobian` to match
541
# `ForwardDiff.jacobian(f!, y::AbstractArray, x::AbstractArray,
542
# cfg::JacobianConfig = JacobianConfig(f!, y, x), check=Val{true}())`
543
J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode
544
return Trixi.rhs_parabolic!(du_ode, u_ode, new_semi, t0)
545
end
546
547
return J
548
end
549
end # @muladd
550
551