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