Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/semidiscretization/semidiscretization_coupled.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
SemidiscretizationCoupled
10
11
A struct used to bundle multiple semidiscretizations.
12
[`semidiscretize`](@ref) will return an `ODEProblem` that synchronizes time steps between the semidiscretizations.
13
Each call of `rhs!` will call `rhs!` for each semidiscretization individually.
14
The semidiscretizations can be coupled by gluing meshes together using [`BoundaryConditionCoupled`](@ref).
15
16
!!! warning "Experimental code"
17
This is an experimental feature and can change any time.
18
"""
19
mutable struct SemidiscretizationCoupled{S, Indices} <:
20
AbstractSemidiscretization
21
semis::S
22
u_indices::Indices # u_ode[u_indices[i]] is the part of u_ode corresponding to semis[i]
23
performance_counter::PerformanceCounter
24
end
25
# We assume some properties of the fields of the semidiscretization, e.g.,
26
# the `equations` and the `mesh` should have the same dimension. We check these
27
# properties in the outer constructor defined below. While we could ensure
28
# them even better in an inner constructor, we do not use this approach to
29
# simplify the integration with Adapt.jl for GPU usage, see
30
# https://github.com/trixi-framework/Trixi.jl/pull/2677#issuecomment-3591789921
31
32
"""
33
SemidiscretizationCoupled(semis...)
34
35
Create a coupled semidiscretization that consists of the semidiscretizations passed as arguments.
36
"""
37
function SemidiscretizationCoupled(semis...)
38
@assert all(semi -> ndims(semi) == ndims(semis[1]), semis) "All semidiscretizations must have the same dimension!"
39
40
# Number of coefficients for each semidiscretization
41
n_coefficients = zeros(Int, length(semis))
42
for i in 1:length(semis)
43
_, equations, _, _ = mesh_equations_solver_cache(semis[i])
44
n_coefficients[i] = ndofs(semis[i]) * nvariables(equations)
45
end
46
47
# Compute range of coefficients associated with each semidiscretization and allocate coupled BCs
48
u_indices = Vector{UnitRange{Int}}(undef, length(semis))
49
for i in 1:length(semis)
50
offset = sum(n_coefficients[1:(i - 1)]) + 1
51
u_indices[i] = range(offset, length = n_coefficients[i])
52
53
allocate_coupled_boundary_conditions(semis[i])
54
end
55
56
performance_counter = PerformanceCounter()
57
58
return SemidiscretizationCoupled{typeof(semis), typeof(u_indices)}(semis, u_indices,
59
performance_counter)
60
end
61
62
function Base.show(io::IO, semi::SemidiscretizationCoupled)
63
@nospecialize semi # reduce precompilation time
64
65
print(io, "SemidiscretizationCoupled($(semi.semis))")
66
return nothing
67
end
68
69
function Base.show(io::IO, ::MIME"text/plain", semi::SemidiscretizationCoupled)
70
@nospecialize semi # reduce precompilation time
71
72
if get(io, :compact, false)
73
show(io, semi)
74
else
75
summary_header(io, "SemidiscretizationCoupled")
76
summary_line(io, "#spatial dimensions", ndims(semi.semis[1]))
77
summary_line(io, "#systems", nsystems(semi))
78
for i in eachsystem(semi)
79
summary_line(io, "system", i)
80
mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i])
81
summary_line(increment_indent(io), "mesh", mesh |> typeof |> nameof)
82
summary_line(increment_indent(io), "equations",
83
equations |> typeof |> nameof)
84
summary_line(increment_indent(io), "initial condition",
85
semi.semis[i].initial_condition)
86
# no boundary conditions since that could be too much
87
summary_line(increment_indent(io), "source terms",
88
semi.semis[i].source_terms)
89
summary_line(increment_indent(io), "solver", solver |> typeof |> nameof)
90
end
91
summary_line(io, "total #DOFs per field", ndofsglobal(semi))
92
summary_footer(io)
93
end
94
end
95
96
function print_summary_semidiscretization(io::IO, semi::SemidiscretizationCoupled)
97
show(io, MIME"text/plain"(), semi)
98
println(io, "\n")
99
for i in eachsystem(semi)
100
mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i])
101
summary_header(io, "System #$i")
102
103
summary_line(io, "mesh", mesh |> typeof |> nameof)
104
show(increment_indent(io), MIME"text/plain"(), mesh)
105
106
summary_line(io, "equations", equations |> typeof |> nameof)
107
show(increment_indent(io), MIME"text/plain"(), equations)
108
109
summary_line(io, "solver", solver |> typeof |> nameof)
110
show(increment_indent(io), MIME"text/plain"(), solver)
111
112
summary_footer(io)
113
println(io, "\n")
114
end
115
end
116
117
@inline Base.ndims(semi::SemidiscretizationCoupled) = ndims(semi.semis[1])
118
119
@inline nsystems(semi::SemidiscretizationCoupled) = length(semi.semis)
120
121
@inline eachsystem(semi::SemidiscretizationCoupled) = Base.OneTo(nsystems(semi))
122
123
@inline Base.real(semi::SemidiscretizationCoupled) = promote_type(real.(semi.semis)...)
124
125
@inline function Base.eltype(semi::SemidiscretizationCoupled)
126
return promote_type(eltype.(semi.semis)...)
127
end
128
129
@inline function ndofs(semi::SemidiscretizationCoupled)
130
return sum(ndofs, semi.semis)
131
end
132
133
"""
134
ndofsglobal(semi::SemidiscretizationCoupled)
135
136
Return the global number of degrees of freedom associated with each scalar variable across all MPI ranks, and summed up over all coupled systems.
137
This is the same as [`ndofs`](@ref) for simulations running in serial or
138
parallelized via threads. It will in general be different for simulations
139
running in parallel with MPI.
140
"""
141
@inline function ndofsglobal(semi::SemidiscretizationCoupled)
142
return sum(ndofsglobal, semi.semis)
143
end
144
145
function compute_coefficients(t, semi::SemidiscretizationCoupled)
146
@unpack u_indices = semi
147
148
u_ode = Vector{real(semi)}(undef, u_indices[end][end])
149
150
for i in eachsystem(semi)
151
# Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl`
152
u_ode[u_indices[i]] .= compute_coefficients(t, semi.semis[i])
153
end
154
155
return u_ode
156
end
157
158
@inline function get_system_u_ode(u_ode, index, semi::SemidiscretizationCoupled)
159
@view u_ode[semi.u_indices[index]]
160
end
161
162
# Same as `foreach(enumerate(something))`, but without allocations.
163
#
164
# Note that compile times may increase if this is used with big tuples.
165
@inline foreach_enumerate(func, collection) = foreach_enumerate(func, collection, 1)
166
@inline foreach_enumerate(func, collection::Tuple{}, index) = nothing
167
168
@inline function foreach_enumerate(func, collection, index)
169
element = first(collection)
170
remaining_collection = Base.tail(collection)
171
172
func((index, element))
173
174
# Process remaining collection
175
return foreach_enumerate(func, remaining_collection, index + 1)
176
end
177
178
function rhs!(du_ode, u_ode, semi::SemidiscretizationCoupled, t)
179
@unpack u_indices = semi
180
181
time_start = time_ns()
182
183
@trixi_timeit timer() "copy to coupled boundaries" begin
184
foreach(semi.semis) do semi_
185
return copy_to_coupled_boundary!(semi_.boundary_conditions, u_ode, semi,
186
semi_)
187
end
188
end
189
190
# Call rhs! for each semidiscretization
191
foreach_enumerate(semi.semis) do (i, semi_)
192
u_loc = get_system_u_ode(u_ode, i, semi)
193
du_loc = get_system_u_ode(du_ode, i, semi)
194
return rhs!(du_loc, u_loc, semi_, t)
195
end
196
197
runtime = time_ns() - time_start
198
put!(semi.performance_counter, runtime)
199
200
return nothing
201
end
202
203
################################################################################
204
### AnalysisCallback
205
################################################################################
206
207
"""
208
AnalysisCallbackCoupled(semi, callbacks...)
209
210
Combine multiple analysis callbacks for coupled simulations with a
211
[`SemidiscretizationCoupled`](@ref). For each coupled system, an indididual
212
[`AnalysisCallback`](@ref) **must** be created and passed to the `AnalysisCallbackCoupled` **in
213
order**, i.e., in the same sequence as the indidvidual semidiscretizations are stored in the
214
`SemidiscretizationCoupled`.
215
216
!!! warning "Experimental code"
217
This is an experimental feature and can change any time.
218
"""
219
struct AnalysisCallbackCoupled{CB}
220
callbacks::CB
221
end
222
223
function Base.show(io::IO, ::MIME"text/plain",
224
cb_coupled::DiscreteCallback{<:Any, <:AnalysisCallbackCoupled})
225
@nospecialize cb_coupled # reduce precompilation time
226
227
if get(io, :compact, false)
228
show(io, cb_coupled)
229
else
230
analysis_callback_coupled = cb_coupled.affect!
231
232
summary_header(io, "AnalysisCallbackCoupled")
233
for (i, cb) in enumerate(analysis_callback_coupled.callbacks)
234
summary_line(io, "Callback #$i", "")
235
show(increment_indent(io), MIME"text/plain"(), cb)
236
end
237
summary_footer(io)
238
end
239
end
240
241
# Convenience constructor for the coupled callback that gets called directly from the elixirs
242
function AnalysisCallbackCoupled(semi_coupled, callbacks...)
243
if length(callbacks) != nsystems(semi_coupled)
244
error("an AnalysisCallbackCoupled requires one AnalysisCallback for each semidiscretization")
245
end
246
247
analysis_callback_coupled = AnalysisCallbackCoupled{typeof(callbacks)}(callbacks)
248
249
# This callback is triggered if any of its subsidiary callbacks' condition is triggered
250
condition = (u, t, integrator) -> any(callbacks) do callback
251
return callback.condition(u, t, integrator)
252
end
253
254
return DiscreteCallback(condition, analysis_callback_coupled,
255
save_positions = (false, false),
256
initialize = initialize!)
257
end
258
259
# This method gets called during initialization from OrdinaryDiffEq's `solve(...)`
260
function initialize!(cb_coupled::DiscreteCallback{Condition, Affect!}, u_ode_coupled, t,
261
integrator) where {Condition, Affect! <: AnalysisCallbackCoupled}
262
analysis_callback_coupled = cb_coupled.affect!
263
semi_coupled = integrator.p
264
du_ode_coupled = first(get_tmp_cache(integrator))
265
266
# Loop over coupled systems' callbacks and initialize them individually
267
for i in eachsystem(semi_coupled)
268
cb = analysis_callback_coupled.callbacks[i]
269
semi = semi_coupled.semis[i]
270
u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)
271
du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled)
272
initialize!(cb, u_ode, du_ode, t, integrator, semi)
273
end
274
end
275
276
# This method gets called from OrdinaryDiffEq's `solve(...)`
277
function (analysis_callback_coupled::AnalysisCallbackCoupled)(integrator)
278
semi_coupled = integrator.p
279
u_ode_coupled = integrator.u
280
du_ode_coupled = first(get_tmp_cache(integrator))
281
282
# Loop over coupled systems' callbacks and call them individually
283
for i in eachsystem(semi_coupled)
284
@unpack condition = analysis_callback_coupled.callbacks[i]
285
analysis_callback = analysis_callback_coupled.callbacks[i].affect!
286
u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)
287
288
# Check condition and skip callback if it is not yet its turn
289
if !condition(u_ode, integrator.t, integrator)
290
continue
291
end
292
293
semi = semi_coupled.semis[i]
294
du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled)
295
analysis_callback(u_ode, du_ode, integrator, semi)
296
end
297
end
298
299
# used for error checks and EOC analysis
300
function (cb::DiscreteCallback{Condition, Affect!})(sol) where {Condition,
301
Affect! <:
302
AnalysisCallbackCoupled}
303
semi_coupled = sol.prob.p
304
u_ode_coupled = sol.u[end]
305
@unpack callbacks = cb.affect!
306
307
uEltype = real(semi_coupled)
308
l2_error_collection = uEltype[]
309
linf_error_collection = uEltype[]
310
for i in eachsystem(semi_coupled)
311
analysis_callback = callbacks[i].affect!
312
@unpack analyzer = analysis_callback
313
cache_analysis = analysis_callback.cache
314
315
semi = semi_coupled.semis[i]
316
u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)
317
318
l2_error, linf_error = calc_error_norms(u_ode, sol.t[end], analyzer, semi,
319
cache_analysis)
320
append!(l2_error_collection, l2_error)
321
append!(linf_error_collection, linf_error)
322
end
323
324
return (; l2 = l2_error_collection, linf = linf_error_collection)
325
end
326
327
################################################################################
328
### SaveSolutionCallback
329
################################################################################
330
331
# Save mesh for a coupled semidiscretization, which contains multiple meshes internally
332
function save_mesh(semi::SemidiscretizationCoupled, output_directory, timestep = 0)
333
for i in eachsystem(semi)
334
mesh, _, _, _ = mesh_equations_solver_cache(semi.semis[i])
335
336
if mesh.unsaved_changes
337
mesh.current_filename = save_mesh_file(mesh, output_directory; system = i,
338
timestep = timestep)
339
mesh.unsaved_changes = false
340
end
341
end
342
end
343
344
@inline function save_solution_file(semi::SemidiscretizationCoupled, u_ode,
345
solution_callback,
346
integrator)
347
@unpack semis = semi
348
349
for i in eachsystem(semi)
350
u_ode_slice = get_system_u_ode(u_ode, i, semi)
351
save_solution_file(semis[i], u_ode_slice, solution_callback, integrator,
352
system = i)
353
end
354
end
355
356
################################################################################
357
### StepsizeCallback
358
################################################################################
359
# In case of coupled system, use minimum timestep over all systems
360
function calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic,
361
semi::SemidiscretizationCoupled)
362
dt = minimum(eachsystem(semi)) do i
363
u_ode_slice = get_system_u_ode(u_ode, i, semi)
364
return calculate_dt(u_ode_slice, t, cfl_hyperbolic, cfl_parabolic,
365
semi.semis[i])
366
end
367
368
return dt
369
end
370
371
function update_cleaning_speed!(semi_coupled::SemidiscretizationCoupled,
372
glm_speed_callback, dt, t)
373
@unpack glm_scale, cfl, semi_indices = glm_speed_callback
374
375
if length(semi_indices) == 0
376
throw("Since you have more than one semidiscretization you need to specify the 'semi_indices' for which the GLM speed needs to be calculated.")
377
end
378
379
# Check that all MHD semidiscretizations received a GLM cleaning speed update.
380
for (semi_index, semi) in enumerate(semi_coupled.semis)
381
if (typeof(semi.equations) <: AbstractIdealGlmMhdEquations &&
382
!(semi_index in semi_indices))
383
error("Equation of semidiscretization $semi_index needs to be included in 'semi_indices' of 'GlmSpeedCallback'.")
384
end
385
end
386
387
for semi_index in semi_indices
388
semi = semi_coupled.semis[semi_index]
389
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
390
391
# compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR)
392
c_h_deltat = calc_dt_for_cleaning_speed(cfl(t),
393
mesh, equations, solver, cache)
394
395
# c_h is proportional to its own time step divided by the complete MHD time step
396
# We use @reset here since the equations are immutable (to work on GPUs etc.).
397
# Thus, we need to modify the equations field of the semidiscretization.
398
@reset equations.c_h = glm_scale * c_h_deltat / dt
399
semi.equations = equations
400
end
401
402
return semi_coupled
403
end
404
405
################################################################################
406
### Equations
407
################################################################################
408
409
"""
410
BoundaryConditionCoupled(other_semi_index, indices, uEltype, coupling_converter)
411
412
Boundary condition to glue two meshes together. Solution values at the boundary
413
of another mesh will be used as boundary values. This requires the use
414
of [`SemidiscretizationCoupled`](@ref). The other mesh is specified by `other_semi_index`,
415
which is the index of the mesh in the tuple of semidiscretizations.
416
417
Note that the elements and nodes of the two meshes at the coupled boundary must coincide.
418
This is currently only implemented for [`StructuredMesh`](@ref).
419
420
# Arguments
421
- `other_semi_index`: the index in `SemidiscretizationCoupled` of the semidiscretization
422
from which the values are copied
423
- `indices::Tuple`: node/cell indices at the boundary of the mesh in the other
424
semidiscretization. See examples below.
425
- `uEltype::Type`: element type of solution
426
- `coupling_converter::CouplingConverter`: function to call for converting the solution
427
state of one system to the other system
428
429
# Examples
430
```julia
431
# Connect the left boundary of mesh 2 to our boundary such that our positive
432
# boundary direction will match the positive y direction of the other boundary
433
BoundaryConditionCoupled(2, (:begin, :i), Float64, fun)
434
435
# Connect the same two boundaries oppositely oriented
436
BoundaryConditionCoupled(2, (:begin, :i_backwards), Float64, fun)
437
438
# Using this as y_neg boundary will connect `our_cells[i, 1, j]` to `other_cells[j, end-i, end]`
439
BoundaryConditionCoupled(2, (:j, :i_backwards, :end), Float64, fun)
440
```
441
442
!!! warning "Experimental code"
443
This is an experimental feature and can change any time.
444
"""
445
mutable struct BoundaryConditionCoupled{NDIMS,
446
# Store the other semi index as type parameter,
447
# so that retrieving the other semidiscretization
448
# is type-stable.
449
# x-ref: https://github.com/trixi-framework/Trixi.jl/pull/1979
450
other_semi_index, NDIMST2M1,
451
uEltype <: Real, Indices, CouplingConverter}
452
# NDIMST2M1 == NDIMS * 2 - 1
453
# Buffer for boundary values: [variable, nodes_i, nodes_j, cell_i, cell_j]
454
u_boundary :: Array{uEltype, NDIMST2M1} # NDIMS * 2 - 1
455
const other_orientation :: Int
456
const indices :: Indices
457
const coupling_converter :: CouplingConverter
458
459
function BoundaryConditionCoupled(other_semi_index, indices, uEltype,
460
coupling_converter)
461
NDIMS = length(indices)
462
u_boundary = Array{uEltype, NDIMS * 2 - 1}(undef, ntuple(_ -> 0, NDIMS * 2 - 1))
463
464
if indices[1] in (:begin, :end)
465
other_orientation = 1
466
elseif indices[2] in (:begin, :end)
467
other_orientation = 2
468
else # indices[3] in (:begin, :end)
469
other_orientation = 3
470
end
471
472
return new{NDIMS, other_semi_index, NDIMS * 2 - 1, uEltype, typeof(indices),
473
typeof(coupling_converter)}(u_boundary,
474
other_orientation,
475
indices, coupling_converter)
476
end
477
end
478
479
function Base.eltype(boundary_condition::BoundaryConditionCoupled)
480
return eltype(boundary_condition.u_boundary)
481
end
482
483
function (boundary_condition::BoundaryConditionCoupled)(u_inner, orientation, direction,
484
cell_indices,
485
surface_node_indices,
486
surface_flux_function,
487
equations)
488
# get_node_vars(boundary_condition.u_boundary, equations, solver, surface_node_indices..., cell_indices...),
489
# but we don't have a solver here
490
u_boundary = SVector(ntuple(v -> boundary_condition.u_boundary[v,
491
surface_node_indices...,
492
cell_indices...],
493
Val(nvariables(equations))))
494
495
# Calculate boundary flux
496
if surface_flux_function isa Tuple
497
# In case of conservative (index 1) and non-conservative (index 2) fluxes,
498
# add the non-conservative one with a factor of 1/2.
499
if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary
500
flux = (surface_flux_function[1](u_inner, u_boundary, orientation,
501
equations),
502
surface_flux_function[2](u_inner, u_boundary, orientation,
503
equations))
504
else # u_boundary is "left" of boundary, u_inner is "right" of boundary
505
flux = (surface_flux_function[1](u_boundary, u_inner, orientation,
506
equations),
507
surface_flux_function[2](u_boundary, u_inner, orientation,
508
equations))
509
end
510
else
511
if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary
512
flux = surface_flux_function(u_inner, u_boundary, orientation, equations)
513
else # u_boundary is "left" of boundary, u_inner is "right" of boundary
514
flux = surface_flux_function(u_boundary, u_inner, orientation, equations)
515
end
516
end
517
518
return flux
519
end
520
521
function allocate_coupled_boundary_conditions(semi::AbstractSemidiscretization)
522
n_boundaries = 2 * ndims(semi)
523
mesh, equations, solver, _ = mesh_equations_solver_cache(semi)
524
525
for direction in 1:n_boundaries
526
boundary_condition = semi.boundary_conditions[direction]
527
528
allocate_coupled_boundary_condition(boundary_condition, direction, mesh,
529
equations,
530
solver)
531
end
532
end
533
534
# Don't do anything for other BCs than BoundaryConditionCoupled
535
function allocate_coupled_boundary_condition(boundary_condition, direction, mesh,
536
equations,
537
solver)
538
return nothing
539
end
540
541
# In 2D
542
function allocate_coupled_boundary_condition(boundary_condition::BoundaryConditionCoupled{2},
543
direction, mesh, equations, dg::DGSEM)
544
if direction in (1, 2)
545
cell_size = size(mesh, 2)
546
else
547
cell_size = size(mesh, 1)
548
end
549
550
uEltype = eltype(boundary_condition)
551
return boundary_condition.u_boundary = Array{uEltype, 3}(undef,
552
nvariables(equations),
553
nnodes(dg),
554
cell_size)
555
end
556
557
# Don't do anything for other BCs than BoundaryConditionCoupled
558
function copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi)
559
return nothing
560
end
561
562
function copy_to_coupled_boundary!(u_ode, semi_coupled, semi, i, n_boundaries,
563
boundary_condition, boundary_conditions...)
564
copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi)
565
if i < n_boundaries
566
copy_to_coupled_boundary!(u_ode, semi_coupled, semi, i + 1, n_boundaries,
567
boundary_conditions...)
568
end
569
end
570
571
function copy_to_coupled_boundary!(boundary_conditions::Union{Tuple, NamedTuple}, u_ode,
572
semi_coupled, semi)
573
return copy_to_coupled_boundary!(u_ode, semi_coupled, semi, 1,
574
length(boundary_conditions),
575
boundary_conditions...)
576
end
577
578
# In 2D
579
function copy_to_coupled_boundary!(boundary_condition::BoundaryConditionCoupled{2,
580
other_semi_index},
581
u_ode, semi_coupled, semi) where {other_semi_index}
582
@unpack u_indices = semi_coupled
583
@unpack other_orientation, indices = boundary_condition
584
@unpack coupling_converter, u_boundary = boundary_condition
585
586
mesh_own, equations_own, solver_own, cache_own = mesh_equations_solver_cache(semi)
587
other_semi = semi_coupled.semis[other_semi_index]
588
mesh_other, equations_other, solver_other, cache_other = mesh_equations_solver_cache(other_semi)
589
590
node_coordinates_other = cache_other.elements.node_coordinates
591
u_ode_other = get_system_u_ode(u_ode, other_semi_index, semi_coupled)
592
u_other = wrap_array(u_ode_other, mesh_other, equations_other, solver_other,
593
cache_other)
594
595
linear_indices = LinearIndices(size(mesh_other))
596
597
if other_orientation == 1
598
cells = axes(mesh_other, 2)
599
else # other_orientation == 2
600
cells = axes(mesh_other, 1)
601
end
602
603
# Copy solution data to the coupled boundary using "delayed indexing" with
604
# a start value and a step size to get the correct face and orientation.
605
node_index_range = eachnode(solver_other)
606
i_node_start, i_node_step = index_to_start_step_2d(indices[1], node_index_range)
607
j_node_start, j_node_step = index_to_start_step_2d(indices[2], node_index_range)
608
609
i_cell_start, i_cell_step = index_to_start_step_2d(indices[1], axes(mesh_other, 1))
610
j_cell_start, j_cell_step = index_to_start_step_2d(indices[2], axes(mesh_other, 2))
611
612
# We need indices starting at 1 for the handling of `i_cell` etc.
613
Base.require_one_based_indexing(cells)
614
615
@threaded for i in eachindex(cells)
616
cell = cells[i]
617
i_cell = i_cell_start + (i - 1) * i_cell_step
618
j_cell = j_cell_start + (i - 1) * j_cell_step
619
620
i_node = i_node_start
621
j_node = j_node_start
622
element_id = linear_indices[i_cell, j_cell]
623
624
for element_id in eachnode(solver_other)
625
x_other = get_node_coords(node_coordinates_other, equations_other,
626
solver_other,
627
i_node, j_node, linear_indices[i_cell, j_cell])
628
u_node_other = get_node_vars(u_other, equations_other, solver_other, i_node,
629
j_node, linear_indices[i_cell, j_cell])
630
u_node_converted = coupling_converter(x_other, u_node_other,
631
equations_other,
632
equations_own)
633
634
for i in eachindex(u_node_converted)
635
u_boundary[i, element_id, cell] = u_node_converted[i]
636
end
637
638
i_node += i_node_step
639
j_node += j_node_step
640
end
641
end
642
643
return nothing
644
end
645
646
################################################################################
647
### DGSEM/structured
648
################################################################################
649
650
@inline function calc_boundary_flux_by_direction!(surface_flux_values, t,
651
orientation,
652
boundary_condition::BoundaryConditionCoupled,
653
mesh::Union{StructuredMesh,
654
StructuredMeshView},
655
have_nonconservative_terms::False,
656
equations,
657
surface_integral, dg::DG, cache,
658
direction, node_indices,
659
surface_node_indices, element)
660
@unpack node_coordinates, contravariant_vectors, inverse_jacobian, interfaces_u = cache.elements
661
# Boundary values are for `StructuredMesh` stored in the interface datastructure
662
boundaries_u = interfaces_u
663
@unpack surface_flux = surface_integral
664
665
cell_indices = get_boundary_indices(element, orientation, mesh)
666
667
u_inner = get_node_vars(boundaries_u, equations, dg, surface_node_indices...,
668
direction, element)
669
670
# If the mapping is orientation-reversing, the contravariant vectors' orientation
671
# is reversed as well. The normal vector must be oriented in the direction
672
# from `left_element` to `right_element`, or the numerical flux will be computed
673
# incorrectly (downwind direction).
674
sign_jacobian = sign(inverse_jacobian[node_indices..., element])
675
676
# Contravariant vector Ja^i is the normal vector
677
normal = sign_jacobian *
678
get_contravariant_vector(orientation, contravariant_vectors,
679
node_indices..., element)
680
681
# If the mapping is orientation-reversing, the normal vector will be reversed (see above).
682
# However, the flux now has the wrong sign, since we need the physical flux in normal direction.
683
flux = sign_jacobian * boundary_condition(u_inner, normal, direction, cell_indices,
684
surface_node_indices, surface_flux, equations)
685
686
for v in eachvariable(equations)
687
surface_flux_values[v, surface_node_indices..., direction, element] = flux[v]
688
end
689
690
return nothing
691
end
692
693
@inline function calc_boundary_flux_by_direction!(surface_flux_values, t,
694
orientation,
695
boundary_condition::BoundaryConditionCoupled,
696
mesh::Union{StructuredMesh,
697
StructuredMeshView},
698
have_nonconservative_terms::True,
699
equations,
700
surface_integral, dg::DG, cache,
701
direction, node_indices,
702
surface_node_indices, element)
703
@unpack node_coordinates, contravariant_vectors, inverse_jacobian, interfaces_u = cache.elements
704
# Boundary values are for `StructuredMesh` stored in the interface datastructure
705
boundaries_u = interfaces_u
706
@unpack surface_flux = surface_integral
707
708
cell_indices = get_boundary_indices(element, orientation, mesh)
709
710
u_inner = get_node_vars(boundaries_u, equations, dg, surface_node_indices...,
711
direction, element)
712
713
# If the mapping is orientation-reversing, the contravariant vectors' orientation
714
# is reversed as well. The normal vector must be oriented in the direction
715
# from `left_element` to `right_element`, or the numerical flux will be computed
716
# incorrectly (downwind direction).
717
sign_jacobian = sign(inverse_jacobian[node_indices..., element])
718
719
# Contravariant vector Ja^i is the normal vector
720
normal = sign_jacobian *
721
get_contravariant_vector(orientation, contravariant_vectors,
722
node_indices..., element)
723
724
# If the mapping is orientation-reversing, the normal vector will be reversed (see above).
725
# However, the flux now has the wrong sign, since we need the physical flux in normal direction.
726
flux, noncons_flux = boundary_condition(u_inner, normal, direction, cell_indices,
727
surface_node_indices, surface_flux,
728
equations)
729
730
for v in eachvariable(equations)
731
surface_flux_values[v, surface_node_indices..., direction, element] = sign_jacobian *
732
(flux[v] +
733
0.5f0 *
734
noncons_flux[v])
735
end
736
737
return nothing
738
end
739
740
function get_boundary_indices(element, orientation,
741
mesh::Union{StructuredMesh{2}, StructuredMeshView{2}})
742
cartesian_indices = CartesianIndices(size(mesh))
743
if orientation == 1
744
# Get index of element in y-direction
745
cell_indices = (cartesian_indices[element][2],)
746
else # orientation == 2
747
# Get index of element in x-direction
748
cell_indices = (cartesian_indices[element][1],)
749
end
750
751
return cell_indices
752
end
753
754
################################################################################
755
### Special elixirs
756
################################################################################
757
758
# Analyze convergence for SemidiscretizationCoupled
759
function analyze_convergence(errors_coupled, iterations,
760
semi_coupled::SemidiscretizationCoupled)
761
# Extract errors: the errors are currently stored as
762
# | iter 1 sys 1 var 1...n | iter 1 sys 2 var 1...n | ... | iter 2 sys 1 var 1...n | ...
763
# but for calling `analyze_convergence` below, we need the following layout
764
# sys n: | iter 1 var 1...n | iter 1 var 1...n | ... | iter 2 var 1...n | ...
765
# That is, we need to extract and join the data for a single system
766
errors = Dict{Symbol, Vector{Float64}}[]
767
for i in eachsystem(semi_coupled)
768
push!(errors, Dict(:l2 => Float64[], :linf => Float64[]))
769
end
770
offset = 0
771
for iter in 1:iterations, i in eachsystem(semi_coupled)
772
# Extract information on current semi
773
semi = semi_coupled.semis[i]
774
_, equations, _, _ = mesh_equations_solver_cache(semi)
775
variablenames = varnames(cons2cons, equations)
776
777
# Compute offset
778
first = offset + 1
779
last = offset + length(variablenames)
780
offset += length(variablenames)
781
782
# Append errors to appropriate storage
783
append!(errors[i][:l2], errors_coupled[:l2][first:last])
784
append!(errors[i][:linf], errors_coupled[:linf][first:last])
785
end
786
787
eocs = Vector{Dict{Symbol, Any}}(undef, nsystems(semi_coupled))
788
errorsmatrix = Vector{Dict{Symbol, Matrix{Float64}}}(undef, nsystems(semi_coupled))
789
for i in eachsystem(semi_coupled)
790
# Use visual cues to separate output from multiple systems
791
println()
792
println("="^100)
793
println("# System $i")
794
println("="^100)
795
796
# Extract information on current semi
797
semi = semi_coupled.semis[i]
798
_, equations, _, _ = mesh_equations_solver_cache(semi)
799
variablenames = varnames(cons2cons, equations)
800
801
eocs[i], errorsmatrix[i] = analyze_convergence(errors[i], iterations,
802
variablenames)
803
end
804
805
return eocs, errorsmatrix
806
end
807
end # @muladd
808
809