Path: blob/main/src/semidiscretization/semidiscretization_coupled.jl
5586 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67"""8SemidiscretizationCoupled910A struct used to bundle multiple semidiscretizations.11[`semidiscretize`](@ref) will return an `ODEProblem` that synchronizes time steps between the semidiscretizations.12Each call of `rhs!` will call `rhs!` for each semidiscretization individually.13The semidiscretizations can be coupled by gluing meshes together using [`BoundaryConditionCoupled`](@ref).1415!!! warning "Experimental code"16This is an experimental feature and can change any time.17"""18mutable struct SemidiscretizationCoupled{S, Indices} <:19AbstractSemidiscretization20semis::S21u_indices::Indices # u_ode[u_indices[i]] is the part of u_ode corresponding to semis[i]22performance_counter::PerformanceCounter23end24# We assume some properties of the fields of the semidiscretization, e.g.,25# the `equations` and the `mesh` should have the same dimension. We check these26# properties in the outer constructor defined below. While we could ensure27# them even better in an inner constructor, we do not use this approach to28# simplify the integration with Adapt.jl for GPU usage, see29# https://github.com/trixi-framework/Trixi.jl/pull/2677#issuecomment-35917899213031"""32SemidiscretizationCoupled(semis...)3334Create a coupled semidiscretization that consists of the semidiscretizations passed as arguments.35"""36function SemidiscretizationCoupled(semis...)37@assert all(semi -> ndims(semi) == ndims(semis[1]), semis) "All semidiscretizations must have the same dimension!"3839# Number of coefficients for each semidiscretization40n_coefficients = zeros(Int, length(semis))41for i in 1:length(semis)42_, equations, _, _ = mesh_equations_solver_cache(semis[i])43n_coefficients[i] = ndofs(semis[i]) * nvariables(equations)44end4546# Compute range of coefficients associated with each semidiscretization and allocate coupled BCs47u_indices = Vector{UnitRange{Int}}(undef, length(semis))48for i in 1:length(semis)49offset = sum(n_coefficients[1:(i - 1)]) + 150u_indices[i] = range(offset, length = n_coefficients[i])5152allocate_coupled_boundary_conditions(semis[i])53end5455performance_counter = PerformanceCounter()5657return SemidiscretizationCoupled{typeof(semis), typeof(u_indices)}(semis, u_indices,58performance_counter)59end6061function Base.show(io::IO, semi::SemidiscretizationCoupled)62@nospecialize semi # reduce precompilation time6364print(io, "SemidiscretizationCoupled($(semi.semis))")65return nothing66end6768function Base.show(io::IO, ::MIME"text/plain", semi::SemidiscretizationCoupled)69@nospecialize semi # reduce precompilation time7071if get(io, :compact, false)72show(io, semi)73else74summary_header(io, "SemidiscretizationCoupled")75summary_line(io, "#spatial dimensions", ndims(semi.semis[1]))76summary_line(io, "#systems", nsystems(semi))77for i in eachsystem(semi)78summary_line(io, "system", i)79mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i])80summary_line(increment_indent(io), "mesh", mesh |> typeof |> nameof)81summary_line(increment_indent(io), "equations",82equations |> typeof |> nameof)83summary_line(increment_indent(io), "initial condition",84semi.semis[i].initial_condition)85# no boundary conditions since that could be too much86summary_line(increment_indent(io), "source terms",87semi.semis[i].source_terms)88summary_line(increment_indent(io), "solver", solver |> typeof |> nameof)89end90summary_line(io, "total #DOFs per field", ndofsglobal(semi))91summary_footer(io)92end93end9495function print_summary_semidiscretization(io::IO, semi::SemidiscretizationCoupled)96show(io, MIME"text/plain"(), semi)97println(io, "\n")98for i in eachsystem(semi)99mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i])100summary_header(io, "System #$i")101102summary_line(io, "mesh", mesh |> typeof |> nameof)103show(increment_indent(io), MIME"text/plain"(), mesh)104105summary_line(io, "equations", equations |> typeof |> nameof)106show(increment_indent(io), MIME"text/plain"(), equations)107108summary_line(io, "solver", solver |> typeof |> nameof)109show(increment_indent(io), MIME"text/plain"(), solver)110111summary_footer(io)112println(io, "\n")113end114end115116@inline Base.ndims(semi::SemidiscretizationCoupled) = ndims(semi.semis[1])117118@inline nsystems(semi::SemidiscretizationCoupled) = length(semi.semis)119120@inline eachsystem(semi::SemidiscretizationCoupled) = Base.OneTo(nsystems(semi))121122@inline Base.real(semi::SemidiscretizationCoupled) = promote_type(real.(semi.semis)...)123124@inline function Base.eltype(semi::SemidiscretizationCoupled)125return promote_type(eltype.(semi.semis)...)126end127128@inline function ndofs(semi::SemidiscretizationCoupled)129return sum(ndofs, semi.semis)130end131132"""133ndofsglobal(semi::SemidiscretizationCoupled)134135Return the global number of degrees of freedom associated with each scalar variable across all MPI ranks, and summed up over all coupled systems.136This is the same as [`ndofs`](@ref) for simulations running in serial or137parallelized via threads. It will in general be different for simulations138running in parallel with MPI.139"""140@inline function ndofsglobal(semi::SemidiscretizationCoupled)141return sum(ndofsglobal, semi.semis)142end143144function compute_coefficients(t, semi::SemidiscretizationCoupled)145@unpack u_indices = semi146147u_ode = Vector{real(semi)}(undef, u_indices[end][end])148149for i in eachsystem(semi)150# Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl`151u_ode[u_indices[i]] .= compute_coefficients(t, semi.semis[i])152end153154return u_ode155end156157@inline function get_system_u_ode(u_ode, index, semi::SemidiscretizationCoupled)158@view u_ode[semi.u_indices[index]]159end160161# Same as `foreach(enumerate(something))`, but without allocations.162#163# Note that compile times may increase if this is used with big tuples.164@inline foreach_enumerate(func, collection) = foreach_enumerate(func, collection, 1)165@inline foreach_enumerate(func, collection::Tuple{}, index) = nothing166167@inline function foreach_enumerate(func, collection, index)168element = first(collection)169remaining_collection = Base.tail(collection)170171func((index, element))172173# Process remaining collection174return foreach_enumerate(func, remaining_collection, index + 1)175end176177function rhs!(du_ode, u_ode, semi::SemidiscretizationCoupled, t)178@unpack u_indices = semi179180time_start = time_ns()181182@trixi_timeit timer() "copy to coupled boundaries" begin183foreach(semi.semis) do semi_184return copy_to_coupled_boundary!(semi_.boundary_conditions, u_ode, semi,185semi_)186end187end188189# Call rhs! for each semidiscretization190foreach_enumerate(semi.semis) do (i, semi_)191u_loc = get_system_u_ode(u_ode, i, semi)192du_loc = get_system_u_ode(du_ode, i, semi)193return rhs!(du_loc, u_loc, semi_, t)194end195196runtime = time_ns() - time_start197put!(semi.performance_counter, runtime)198199return nothing200end201202################################################################################203### AnalysisCallback204################################################################################205206"""207AnalysisCallbackCoupled(semi, callbacks...)208209Combine multiple analysis callbacks for coupled simulations with a210[`SemidiscretizationCoupled`](@ref). For each coupled system, an indididual211[`AnalysisCallback`](@ref) **must** be created and passed to the `AnalysisCallbackCoupled` **in212order**, i.e., in the same sequence as the indidvidual semidiscretizations are stored in the213`SemidiscretizationCoupled`.214215!!! warning "Experimental code"216This is an experimental feature and can change any time.217"""218struct AnalysisCallbackCoupled{CB}219callbacks::CB220end221222function Base.show(io::IO, ::MIME"text/plain",223cb_coupled::DiscreteCallback{<:Any, <:AnalysisCallbackCoupled})224@nospecialize cb_coupled # reduce precompilation time225226if get(io, :compact, false)227show(io, cb_coupled)228else229analysis_callback_coupled = cb_coupled.affect!230231summary_header(io, "AnalysisCallbackCoupled")232for (i, cb) in enumerate(analysis_callback_coupled.callbacks)233summary_line(io, "Callback #$i", "")234show(increment_indent(io), MIME"text/plain"(), cb)235end236summary_footer(io)237end238end239240# Convenience constructor for the coupled callback that gets called directly from the elixirs241function AnalysisCallbackCoupled(semi_coupled, callbacks...)242if length(callbacks) != nsystems(semi_coupled)243error("an AnalysisCallbackCoupled requires one AnalysisCallback for each semidiscretization")244end245246analysis_callback_coupled = AnalysisCallbackCoupled{typeof(callbacks)}(callbacks)247248# This callback is triggered if any of its subsidiary callbacks' condition is triggered249condition = (u, t, integrator) -> any(callbacks) do callback250return callback.condition(u, t, integrator)251end252253return DiscreteCallback(condition, analysis_callback_coupled,254save_positions = (false, false),255initialize = initialize!)256end257258# This method gets called during initialization from OrdinaryDiffEq's `solve(...)`259function initialize!(cb_coupled::DiscreteCallback{Condition, Affect!}, u_ode_coupled, t,260integrator) where {Condition, Affect! <: AnalysisCallbackCoupled}261analysis_callback_coupled = cb_coupled.affect!262semi_coupled = integrator.p263du_ode_coupled = first(get_tmp_cache(integrator))264265# Loop over coupled systems' callbacks and initialize them individually266for i in eachsystem(semi_coupled)267cb = analysis_callback_coupled.callbacks[i]268semi = semi_coupled.semis[i]269u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)270du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled)271initialize!(cb, u_ode, du_ode, t, integrator, semi)272end273end274275# This method gets called from OrdinaryDiffEq's `solve(...)`276function (analysis_callback_coupled::AnalysisCallbackCoupled)(integrator)277semi_coupled = integrator.p278u_ode_coupled = integrator.u279du_ode_coupled = first(get_tmp_cache(integrator))280281# Loop over coupled systems' callbacks and call them individually282for i in eachsystem(semi_coupled)283@unpack condition = analysis_callback_coupled.callbacks[i]284analysis_callback = analysis_callback_coupled.callbacks[i].affect!285u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)286287# Check condition and skip callback if it is not yet its turn288if !condition(u_ode, integrator.t, integrator)289continue290end291292semi = semi_coupled.semis[i]293du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled)294analysis_callback(u_ode, du_ode, integrator, semi)295end296end297298# used for error checks and EOC analysis299function (cb::DiscreteCallback{Condition, Affect!})(sol) where {Condition,300Affect! <:301AnalysisCallbackCoupled}302semi_coupled = sol.prob.p303u_ode_coupled = sol.u[end]304@unpack callbacks = cb.affect!305306uEltype = real(semi_coupled)307l2_error_collection = uEltype[]308linf_error_collection = uEltype[]309for i in eachsystem(semi_coupled)310analysis_callback = callbacks[i].affect!311@unpack analyzer = analysis_callback312cache_analysis = analysis_callback.cache313314semi = semi_coupled.semis[i]315u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)316317l2_error, linf_error = calc_error_norms(u_ode, sol.t[end], analyzer, semi,318cache_analysis)319append!(l2_error_collection, l2_error)320append!(linf_error_collection, linf_error)321end322323return (; l2 = l2_error_collection, linf = linf_error_collection)324end325326################################################################################327### SaveSolutionCallback328################################################################################329330# Save mesh for a coupled semidiscretization, which contains multiple meshes internally331function save_mesh(semi::SemidiscretizationCoupled, output_directory, timestep = 0)332for i in eachsystem(semi)333mesh, _, _, _ = mesh_equations_solver_cache(semi.semis[i])334335if mesh.unsaved_changes336mesh.current_filename = save_mesh_file(mesh, output_directory; system = i,337timestep = timestep)338mesh.unsaved_changes = false339end340end341end342343@inline function save_solution_file(semi::SemidiscretizationCoupled, u_ode,344solution_callback,345integrator)346@unpack semis = semi347348for i in eachsystem(semi)349u_ode_slice = get_system_u_ode(u_ode, i, semi)350save_solution_file(semis[i], u_ode_slice, solution_callback, integrator,351system = i)352end353end354355################################################################################356### StepsizeCallback357################################################################################358# In case of coupled system, use minimum timestep over all systems359function calculate_dt(u_ode, t, cfl_hyperbolic, cfl_parabolic,360semi::SemidiscretizationCoupled)361dt = minimum(eachsystem(semi)) do i362u_ode_slice = get_system_u_ode(u_ode, i, semi)363return calculate_dt(u_ode_slice, t, cfl_hyperbolic, cfl_parabolic,364semi.semis[i])365end366367return dt368end369370function update_cleaning_speed!(semi_coupled::SemidiscretizationCoupled,371glm_speed_callback, dt, t)372@unpack glm_scale, cfl, semi_indices = glm_speed_callback373374if length(semi_indices) == 0375throw("Since you have more than one semidiscretization you need to specify the 'semi_indices' for which the GLM speed needs to be calculated.")376end377378# Check that all MHD semidiscretizations received a GLM cleaning speed update.379for (semi_index, semi) in enumerate(semi_coupled.semis)380if (typeof(semi.equations) <: AbstractIdealGlmMhdEquations &&381!(semi_index in semi_indices))382error("Equation of semidiscretization $semi_index needs to be included in 'semi_indices' of 'GlmSpeedCallback'.")383end384end385386for semi_index in semi_indices387semi = semi_coupled.semis[semi_index]388mesh, equations, solver, cache = mesh_equations_solver_cache(semi)389390# compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR)391c_h_deltat = calc_dt_for_cleaning_speed(cfl(t),392mesh, equations, solver, cache)393394# c_h is proportional to its own time step divided by the complete MHD time step395# We use @reset here since the equations are immutable (to work on GPUs etc.).396# Thus, we need to modify the equations field of the semidiscretization.397@reset equations.c_h = glm_scale * c_h_deltat / dt398semi.equations = equations399end400401return semi_coupled402end403404################################################################################405### Equations406################################################################################407408"""409BoundaryConditionCoupled(other_semi_index, indices, uEltype, coupling_converter)410411Boundary condition to glue two meshes together. Solution values at the boundary412of another mesh will be used as boundary values. This requires the use413of [`SemidiscretizationCoupled`](@ref). The other mesh is specified by `other_semi_index`,414which is the index of the mesh in the tuple of semidiscretizations.415416Note that the elements and nodes of the two meshes at the coupled boundary must coincide.417This is currently only implemented for [`StructuredMesh`](@ref).418419# Arguments420- `other_semi_index`: the index in `SemidiscretizationCoupled` of the semidiscretization421from which the values are copied422- `indices::Tuple`: node/cell indices at the boundary of the mesh in the other423semidiscretization. See examples below.424- `uEltype::Type`: element type of solution425- `coupling_converter::CouplingConverter`: function to call for converting the solution426state of one system to the other system427428# Examples429```julia430# Connect the left boundary of mesh 2 to our boundary such that our positive431# boundary direction will match the positive y direction of the other boundary432BoundaryConditionCoupled(2, (:begin, :i), Float64, fun)433434# Connect the same two boundaries oppositely oriented435BoundaryConditionCoupled(2, (:begin, :i_backwards), Float64, fun)436437# Using this as y_neg boundary will connect `our_cells[i, 1, j]` to `other_cells[j, end-i, end]`438BoundaryConditionCoupled(2, (:j, :i_backwards, :end), Float64, fun)439```440441!!! warning "Experimental code"442This is an experimental feature and can change any time.443"""444mutable struct BoundaryConditionCoupled{NDIMS,445# Store the other semi index as type parameter,446# so that retrieving the other semidiscretization447# is type-stable.448# x-ref: https://github.com/trixi-framework/Trixi.jl/pull/1979449other_semi_index, NDIMST2M1,450uEltype <: Real, Indices, CouplingConverter}451# NDIMST2M1 == NDIMS * 2 - 1452# Buffer for boundary values: [variable, nodes_i, nodes_j, cell_i, cell_j]453u_boundary :: Array{uEltype, NDIMST2M1} # NDIMS * 2 - 1454const other_orientation :: Int455const indices :: Indices456const coupling_converter :: CouplingConverter457458function BoundaryConditionCoupled(other_semi_index, indices, uEltype,459coupling_converter)460NDIMS = length(indices)461u_boundary = Array{uEltype, NDIMS * 2 - 1}(undef, ntuple(_ -> 0, NDIMS * 2 - 1))462463if indices[1] in (:begin, :end)464other_orientation = 1465elseif indices[2] in (:begin, :end)466other_orientation = 2467else # indices[3] in (:begin, :end)468other_orientation = 3469end470471return new{NDIMS, other_semi_index, NDIMS * 2 - 1, uEltype, typeof(indices),472typeof(coupling_converter)}(u_boundary,473other_orientation,474indices, coupling_converter)475end476end477478function Base.eltype(boundary_condition::BoundaryConditionCoupled)479return eltype(boundary_condition.u_boundary)480end481482function (boundary_condition::BoundaryConditionCoupled)(u_inner, orientation, direction,483cell_indices,484surface_node_indices,485surface_flux_function,486equations)487# get_node_vars(boundary_condition.u_boundary, equations, solver, surface_node_indices..., cell_indices...),488# but we don't have a solver here489u_boundary = SVector(ntuple(v -> boundary_condition.u_boundary[v,490surface_node_indices...,491cell_indices...],492Val(nvariables(equations))))493494# Calculate boundary flux495if surface_flux_function isa Tuple496# In case of conservative (index 1) and non-conservative (index 2) fluxes,497# add the non-conservative one with a factor of 1/2.498if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary499flux = (surface_flux_function[1](u_inner, u_boundary, orientation,500equations),501surface_flux_function[2](u_inner, u_boundary, orientation,502equations))503else # u_boundary is "left" of boundary, u_inner is "right" of boundary504flux = (surface_flux_function[1](u_boundary, u_inner, orientation,505equations),506surface_flux_function[2](u_boundary, u_inner, orientation,507equations))508end509else510if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary511flux = surface_flux_function(u_inner, u_boundary, orientation, equations)512else # u_boundary is "left" of boundary, u_inner is "right" of boundary513flux = surface_flux_function(u_boundary, u_inner, orientation, equations)514end515end516517return flux518end519520function allocate_coupled_boundary_conditions(semi::AbstractSemidiscretization)521n_boundaries = 2 * ndims(semi)522mesh, equations, solver, _ = mesh_equations_solver_cache(semi)523524for direction in 1:n_boundaries525boundary_condition = semi.boundary_conditions[direction]526527allocate_coupled_boundary_condition(boundary_condition, direction, mesh,528equations,529solver)530end531end532533# Don't do anything for other BCs than BoundaryConditionCoupled534function allocate_coupled_boundary_condition(boundary_condition, direction, mesh,535equations,536solver)537return nothing538end539540# In 2D541function allocate_coupled_boundary_condition(boundary_condition::BoundaryConditionCoupled{2},542direction, mesh, equations, dg::DGSEM)543if direction in (1, 2)544cell_size = size(mesh, 2)545else546cell_size = size(mesh, 1)547end548549uEltype = eltype(boundary_condition)550return boundary_condition.u_boundary = Array{uEltype, 3}(undef,551nvariables(equations),552nnodes(dg),553cell_size)554end555556# Don't do anything for other BCs than BoundaryConditionCoupled557function copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi)558return nothing559end560561function copy_to_coupled_boundary!(u_ode, semi_coupled, semi, i, n_boundaries,562boundary_condition, boundary_conditions...)563copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi)564if i < n_boundaries565copy_to_coupled_boundary!(u_ode, semi_coupled, semi, i + 1, n_boundaries,566boundary_conditions...)567end568end569570function copy_to_coupled_boundary!(boundary_conditions::Union{Tuple, NamedTuple}, u_ode,571semi_coupled, semi)572return copy_to_coupled_boundary!(u_ode, semi_coupled, semi, 1,573length(boundary_conditions),574boundary_conditions...)575end576577# In 2D578function copy_to_coupled_boundary!(boundary_condition::BoundaryConditionCoupled{2,579other_semi_index},580u_ode, semi_coupled, semi) where {other_semi_index}581@unpack u_indices = semi_coupled582@unpack other_orientation, indices = boundary_condition583@unpack coupling_converter, u_boundary = boundary_condition584585mesh_own, equations_own, solver_own, cache_own = mesh_equations_solver_cache(semi)586other_semi = semi_coupled.semis[other_semi_index]587mesh_other, equations_other, solver_other, cache_other = mesh_equations_solver_cache(other_semi)588589node_coordinates_other = cache_other.elements.node_coordinates590u_ode_other = get_system_u_ode(u_ode, other_semi_index, semi_coupled)591u_other = wrap_array(u_ode_other, mesh_other, equations_other, solver_other,592cache_other)593594linear_indices = LinearIndices(size(mesh_other))595596if other_orientation == 1597cells = axes(mesh_other, 2)598else # other_orientation == 2599cells = axes(mesh_other, 1)600end601602# Copy solution data to the coupled boundary using "delayed indexing" with603# a start value and a step size to get the correct face and orientation.604node_index_range = eachnode(solver_other)605i_node_start, i_node_step = index_to_start_step_2d(indices[1], node_index_range)606j_node_start, j_node_step = index_to_start_step_2d(indices[2], node_index_range)607608i_cell_start, i_cell_step = index_to_start_step_2d(indices[1], axes(mesh_other, 1))609j_cell_start, j_cell_step = index_to_start_step_2d(indices[2], axes(mesh_other, 2))610611# We need indices starting at 1 for the handling of `i_cell` etc.612Base.require_one_based_indexing(cells)613614@threaded for i in eachindex(cells)615cell = cells[i]616i_cell = i_cell_start + (i - 1) * i_cell_step617j_cell = j_cell_start + (i - 1) * j_cell_step618619i_node = i_node_start620j_node = j_node_start621element_id = linear_indices[i_cell, j_cell]622623for element_id in eachnode(solver_other)624x_other = get_node_coords(node_coordinates_other, equations_other,625solver_other,626i_node, j_node, linear_indices[i_cell, j_cell])627u_node_other = get_node_vars(u_other, equations_other, solver_other, i_node,628j_node, linear_indices[i_cell, j_cell])629u_node_converted = coupling_converter(x_other, u_node_other,630equations_other,631equations_own)632633for i in eachindex(u_node_converted)634u_boundary[i, element_id, cell] = u_node_converted[i]635end636637i_node += i_node_step638j_node += j_node_step639end640end641642return nothing643end644645################################################################################646### DGSEM/structured647################################################################################648649@inline function calc_boundary_flux_by_direction!(surface_flux_values, t,650orientation,651boundary_condition::BoundaryConditionCoupled,652mesh::Union{StructuredMesh,653StructuredMeshView},654have_nonconservative_terms::False,655equations,656surface_integral, dg::DG, cache,657direction, node_indices,658surface_node_indices, element)659@unpack node_coordinates, contravariant_vectors, inverse_jacobian, interfaces_u = cache.elements660# Boundary values are for `StructuredMesh` stored in the interface datastructure661boundaries_u = interfaces_u662@unpack surface_flux = surface_integral663664cell_indices = get_boundary_indices(element, orientation, mesh)665666u_inner = get_node_vars(boundaries_u, equations, dg, surface_node_indices...,667direction, element)668669# If the mapping is orientation-reversing, the contravariant vectors' orientation670# is reversed as well. The normal vector must be oriented in the direction671# from `left_element` to `right_element`, or the numerical flux will be computed672# incorrectly (downwind direction).673sign_jacobian = sign(inverse_jacobian[node_indices..., element])674675# Contravariant vector Ja^i is the normal vector676normal = sign_jacobian *677get_contravariant_vector(orientation, contravariant_vectors,678node_indices..., element)679680# If the mapping is orientation-reversing, the normal vector will be reversed (see above).681# However, the flux now has the wrong sign, since we need the physical flux in normal direction.682flux = sign_jacobian * boundary_condition(u_inner, normal, direction, cell_indices,683surface_node_indices, surface_flux, equations)684685for v in eachvariable(equations)686surface_flux_values[v, surface_node_indices..., direction, element] = flux[v]687end688689return nothing690end691692@inline function calc_boundary_flux_by_direction!(surface_flux_values, t,693orientation,694boundary_condition::BoundaryConditionCoupled,695mesh::Union{StructuredMesh,696StructuredMeshView},697have_nonconservative_terms::True,698equations,699surface_integral, dg::DG, cache,700direction, node_indices,701surface_node_indices, element)702@unpack node_coordinates, contravariant_vectors, inverse_jacobian, interfaces_u = cache.elements703# Boundary values are for `StructuredMesh` stored in the interface datastructure704boundaries_u = interfaces_u705@unpack surface_flux = surface_integral706707cell_indices = get_boundary_indices(element, orientation, mesh)708709u_inner = get_node_vars(boundaries_u, equations, dg, surface_node_indices...,710direction, element)711712# If the mapping is orientation-reversing, the contravariant vectors' orientation713# is reversed as well. The normal vector must be oriented in the direction714# from `left_element` to `right_element`, or the numerical flux will be computed715# incorrectly (downwind direction).716sign_jacobian = sign(inverse_jacobian[node_indices..., element])717718# Contravariant vector Ja^i is the normal vector719normal = sign_jacobian *720get_contravariant_vector(orientation, contravariant_vectors,721node_indices..., element)722723# 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.725flux, noncons_flux = boundary_condition(u_inner, normal, direction, cell_indices,726surface_node_indices, surface_flux,727equations)728729for v in eachvariable(equations)730surface_flux_values[v, surface_node_indices..., direction, element] = sign_jacobian *731(flux[v] +7320.5f0 *733noncons_flux[v])734end735736return nothing737end738739function get_boundary_indices(element, orientation,740mesh::Union{StructuredMesh{2}, StructuredMeshView{2}})741cartesian_indices = CartesianIndices(size(mesh))742if orientation == 1743# Get index of element in y-direction744cell_indices = (cartesian_indices[element][2],)745else # orientation == 2746# Get index of element in x-direction747cell_indices = (cartesian_indices[element][1],)748end749750return cell_indices751end752753################################################################################754### Special elixirs755################################################################################756757# Analyze convergence for SemidiscretizationCoupled758function analyze_convergence(errors_coupled, iterations,759semi_coupled::SemidiscretizationCoupled)760# Extract errors: the errors are currently stored as761# | 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 layout763# 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 system765errors = Dict{Symbol, Vector{Float64}}[]766for i in eachsystem(semi_coupled)767push!(errors, Dict(:l2 => Float64[], :linf => Float64[]))768end769offset = 0770for iter in 1:iterations, i in eachsystem(semi_coupled)771# Extract information on current semi772semi = semi_coupled.semis[i]773_, equations, _, _ = mesh_equations_solver_cache(semi)774variablenames = varnames(cons2cons, equations)775776# Compute offset777first = offset + 1778last = offset + length(variablenames)779offset += length(variablenames)780781# Append errors to appropriate storage782append!(errors[i][:l2], errors_coupled[:l2][first:last])783append!(errors[i][:linf], errors_coupled[:linf][first:last])784end785786eocs = Vector{Dict{Symbol, Any}}(undef, nsystems(semi_coupled))787errorsmatrix = Vector{Dict{Symbol, Matrix{Float64}}}(undef, nsystems(semi_coupled))788for i in eachsystem(semi_coupled)789# Use visual cues to separate output from multiple systems790println()791println("="^100)792println("# System $i")793println("="^100)794795# Extract information on current semi796semi = semi_coupled.semis[i]797_, equations, _, _ = mesh_equations_solver_cache(semi)798variablenames = varnames(cons2cons, equations)799800eocs[i], errorsmatrix[i] = analyze_convergence(errors[i], iterations,801variablenames)802end803804return eocs, errorsmatrix805end806end # @muladd807808809