Path: blob/main/src/semidiscretization/semidiscretization_coupled.jl
2055 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, EquationList} <:19AbstractSemidiscretization20semis::S21u_indices::Indices # u_ode[u_indices[i]] is the part of u_ode corresponding to semis[i]22performance_counter::PerformanceCounter23end2425"""26SemidiscretizationCoupled(semis...)2728Create a coupled semidiscretization that consists of the semidiscretizations passed as arguments.29"""30function SemidiscretizationCoupled(semis...)31@assert all(semi -> ndims(semi) == ndims(semis[1]), semis) "All semidiscretizations must have the same dimension!"3233# Number of coefficients for each semidiscretization34n_coefficients = zeros(Int, length(semis))35for i in 1:length(semis)36_, equations, _, _ = mesh_equations_solver_cache(semis[i])37n_coefficients[i] = ndofs(semis[i]) * nvariables(equations)38end3940# Compute range of coefficients associated with each semidiscretization and allocate coupled BCs41u_indices = Vector{UnitRange{Int}}(undef, length(semis))42for i in 1:length(semis)43offset = sum(n_coefficients[1:(i - 1)]) + 144u_indices[i] = range(offset, length = n_coefficients[i])4546allocate_coupled_boundary_conditions(semis[i])47end4849performance_counter = PerformanceCounter()5051SemidiscretizationCoupled{typeof(semis), typeof(u_indices),52typeof(performance_counter)}(semis, u_indices,53performance_counter)54end5556function Base.show(io::IO, semi::SemidiscretizationCoupled)57@nospecialize semi # reduce precompilation time5859print(io, "SemidiscretizationCoupled($(semi.semis))")60end6162function Base.show(io::IO, ::MIME"text/plain", semi::SemidiscretizationCoupled)63@nospecialize semi # reduce precompilation time6465if get(io, :compact, false)66show(io, semi)67else68summary_header(io, "SemidiscretizationCoupled")69summary_line(io, "#spatial dimensions", ndims(semi.semis[1]))70summary_line(io, "#systems", nsystems(semi))71for i in eachsystem(semi)72summary_line(io, "system", i)73mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i])74summary_line(increment_indent(io), "mesh", mesh |> typeof |> nameof)75summary_line(increment_indent(io), "equations",76equations |> typeof |> nameof)77summary_line(increment_indent(io), "initial condition",78semi.semis[i].initial_condition)79# no boundary conditions since that could be too much80summary_line(increment_indent(io), "source terms",81semi.semis[i].source_terms)82summary_line(increment_indent(io), "solver", solver |> typeof |> nameof)83end84summary_line(io, "total #DOFs per field", ndofsglobal(semi))85summary_footer(io)86end87end8889function print_summary_semidiscretization(io::IO, semi::SemidiscretizationCoupled)90show(io, MIME"text/plain"(), semi)91println(io, "\n")92for i in eachsystem(semi)93mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i])94summary_header(io, "System #$i")9596summary_line(io, "mesh", mesh |> typeof |> nameof)97show(increment_indent(io), MIME"text/plain"(), mesh)9899summary_line(io, "equations", equations |> typeof |> nameof)100show(increment_indent(io), MIME"text/plain"(), equations)101102summary_line(io, "solver", solver |> typeof |> nameof)103show(increment_indent(io), MIME"text/plain"(), solver)104105summary_footer(io)106println(io, "\n")107end108end109110@inline Base.ndims(semi::SemidiscretizationCoupled) = ndims(semi.semis[1])111112@inline nsystems(semi::SemidiscretizationCoupled) = length(semi.semis)113114@inline eachsystem(semi::SemidiscretizationCoupled) = Base.OneTo(nsystems(semi))115116@inline Base.real(semi::SemidiscretizationCoupled) = promote_type(real.(semi.semis)...)117118@inline function Base.eltype(semi::SemidiscretizationCoupled)119promote_type(eltype.(semi.semis)...)120end121122@inline function ndofs(semi::SemidiscretizationCoupled)123sum(ndofs, semi.semis)124end125126"""127ndofsglobal(semi::SemidiscretizationCoupled)128129Return the global number of degrees of freedom associated with each scalar variable across all MPI ranks, and summed up over all coupled systems.130This is the same as [`ndofs`](@ref) for simulations running in serial or131parallelized via threads. It will in general be different for simulations132running in parallel with MPI.133"""134@inline function ndofsglobal(semi::SemidiscretizationCoupled)135sum(ndofsglobal, semi.semis)136end137138function compute_coefficients(t, semi::SemidiscretizationCoupled)139@unpack u_indices = semi140141u_ode = Vector{real(semi)}(undef, u_indices[end][end])142143for i in eachsystem(semi)144# Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl`145u_ode[u_indices[i]] .= compute_coefficients(t, semi.semis[i])146end147148return u_ode149end150151@inline function get_system_u_ode(u_ode, index, semi::SemidiscretizationCoupled)152@view u_ode[semi.u_indices[index]]153end154155# Same as `foreach(enumerate(something))`, but without allocations.156#157# Note that compile times may increase if this is used with big tuples.158@inline foreach_enumerate(func, collection) = foreach_enumerate(func, collection, 1)159@inline foreach_enumerate(func, collection::Tuple{}, index) = nothing160161@inline function foreach_enumerate(func, collection, index)162element = first(collection)163remaining_collection = Base.tail(collection)164165func((index, element))166167# Process remaining collection168foreach_enumerate(func, remaining_collection, index + 1)169end170171function rhs!(du_ode, u_ode, semi::SemidiscretizationCoupled, t)172@unpack u_indices = semi173174time_start = time_ns()175176@trixi_timeit timer() "copy to coupled boundaries" begin177foreach(semi.semis) do semi_178copy_to_coupled_boundary!(semi_.boundary_conditions, u_ode, semi, semi_)179end180end181182# Call rhs! for each semidiscretization183foreach_enumerate(semi.semis) do (i, semi_)184u_loc = get_system_u_ode(u_ode, i, semi)185du_loc = get_system_u_ode(du_ode, i, semi)186rhs!(du_loc, u_loc, semi_, t)187end188189runtime = time_ns() - time_start190put!(semi.performance_counter, runtime)191192return nothing193end194195################################################################################196### AnalysisCallback197################################################################################198199"""200AnalysisCallbackCoupled(semi, callbacks...)201202Combine multiple analysis callbacks for coupled simulations with a203[`SemidiscretizationCoupled`](@ref). For each coupled system, an indididual204[`AnalysisCallback`](@ref) **must** be created and passed to the `AnalysisCallbackCoupled` **in205order**, i.e., in the same sequence as the indidvidual semidiscretizations are stored in the206`SemidiscretizationCoupled`.207208!!! warning "Experimental code"209This is an experimental feature and can change any time.210"""211struct AnalysisCallbackCoupled{CB}212callbacks::CB213end214215function Base.show(io::IO, ::MIME"text/plain",216cb_coupled::DiscreteCallback{<:Any, <:AnalysisCallbackCoupled})217@nospecialize cb_coupled # reduce precompilation time218219if get(io, :compact, false)220show(io, cb_coupled)221else222analysis_callback_coupled = cb_coupled.affect!223224summary_header(io, "AnalysisCallbackCoupled")225for (i, cb) in enumerate(analysis_callback_coupled.callbacks)226summary_line(io, "Callback #$i", "")227show(increment_indent(io), MIME"text/plain"(), cb)228end229summary_footer(io)230end231end232233# Convenience constructor for the coupled callback that gets called directly from the elixirs234function AnalysisCallbackCoupled(semi_coupled, callbacks...)235if length(callbacks) != nsystems(semi_coupled)236error("an AnalysisCallbackCoupled requires one AnalysisCallback for each semidiscretization")237end238239analysis_callback_coupled = AnalysisCallbackCoupled{typeof(callbacks)}(callbacks)240241# This callback is triggered if any of its subsidiary callbacks' condition is triggered242condition = (u, t, integrator) -> any(callbacks) do callback243callback.condition(u, t, integrator)244end245246DiscreteCallback(condition, analysis_callback_coupled,247save_positions = (false, false),248initialize = initialize!)249end250251# This method gets called during initialization from OrdinaryDiffEq's `solve(...)`252function initialize!(cb_coupled::DiscreteCallback{Condition, Affect!}, u_ode_coupled, t,253integrator) where {Condition, Affect! <: AnalysisCallbackCoupled}254analysis_callback_coupled = cb_coupled.affect!255semi_coupled = integrator.p256du_ode_coupled = first(get_tmp_cache(integrator))257258# Loop over coupled systems' callbacks and initialize them individually259for i in eachsystem(semi_coupled)260cb = analysis_callback_coupled.callbacks[i]261semi = semi_coupled.semis[i]262u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)263du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled)264initialize!(cb, u_ode, du_ode, t, integrator, semi)265end266end267268# This method gets called from OrdinaryDiffEq's `solve(...)`269function (analysis_callback_coupled::AnalysisCallbackCoupled)(integrator)270semi_coupled = integrator.p271u_ode_coupled = integrator.u272du_ode_coupled = first(get_tmp_cache(integrator))273274# Loop over coupled systems' callbacks and call them individually275for i in eachsystem(semi_coupled)276@unpack condition = analysis_callback_coupled.callbacks[i]277analysis_callback = analysis_callback_coupled.callbacks[i].affect!278u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)279280# Check condition and skip callback if it is not yet its turn281if !condition(u_ode, integrator.t, integrator)282continue283end284285semi = semi_coupled.semis[i]286du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled)287analysis_callback(u_ode, du_ode, integrator, semi)288end289end290291# used for error checks and EOC analysis292function (cb::DiscreteCallback{Condition, Affect!})(sol) where {Condition,293Affect! <:294AnalysisCallbackCoupled}295semi_coupled = sol.prob.p296u_ode_coupled = sol.u[end]297@unpack callbacks = cb.affect!298299uEltype = real(semi_coupled)300l2_error_collection = uEltype[]301linf_error_collection = uEltype[]302for i in eachsystem(semi_coupled)303analysis_callback = callbacks[i].affect!304@unpack analyzer = analysis_callback305cache_analysis = analysis_callback.cache306307semi = semi_coupled.semis[i]308u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled)309310l2_error, linf_error = calc_error_norms(u_ode, sol.t[end], analyzer, semi,311cache_analysis)312append!(l2_error_collection, l2_error)313append!(linf_error_collection, linf_error)314end315316(; l2 = l2_error_collection, linf = linf_error_collection)317end318319################################################################################320### SaveSolutionCallback321################################################################################322323# Save mesh for a coupled semidiscretization, which contains multiple meshes internally324function save_mesh(semi::SemidiscretizationCoupled, output_directory, timestep = 0)325for i in eachsystem(semi)326mesh, _, _, _ = mesh_equations_solver_cache(semi.semis[i])327328if mesh.unsaved_changes329mesh.current_filename = save_mesh_file(mesh, output_directory; system = i,330timestep = timestep)331mesh.unsaved_changes = false332end333end334end335336@inline function save_solution_file(semi::SemidiscretizationCoupled, u_ode,337solution_callback,338integrator)339@unpack semis = semi340341for i in eachsystem(semi)342u_ode_slice = get_system_u_ode(u_ode, i, semi)343save_solution_file(semis[i], u_ode_slice, solution_callback, integrator,344system = i)345end346end347348################################################################################349### StepsizeCallback350################################################################################351352# In case of coupled system, use minimum timestep over all systems353# Case for constant `cfl_number`.354function calculate_dt(u_ode, t, cfl_number::Real, semi::SemidiscretizationCoupled)355dt = minimum(eachsystem(semi)) do i356u_ode_slice = get_system_u_ode(u_ode, i, semi)357calculate_dt(u_ode_slice, t, cfl_number, semi.semis[i])358end359360return dt361end362# Case for `cfl_number` as a function of time `t`.363function calculate_dt(u_ode, t, cfl_number, semi::SemidiscretizationCoupled)364cfl_number_ = cfl_number(t)365dt = minimum(eachsystem(semi)) do i366u_ode_slice = get_system_u_ode(u_ode, i, semi)367calculate_dt(u_ode_slice, t, cfl_number_, semi.semis[i])368end369end370371function update_cleaning_speed!(semi_coupled::SemidiscretizationCoupled,372glm_speed_callback, dt, t)373@unpack glm_scale, cfl, semi_indices = glm_speed_callback374375if length(semi_indices) == 0376throw("Since you have more than one semidiscretization you need to specify the 'semi_indices' for which the GLM speed needs to be calculated.")377end378379# Check that all MHD semidiscretizations received a GLM cleaning speed update.380for (semi_index, semi) in enumerate(semi_coupled.semis)381if (typeof(semi.equations) <: AbstractIdealGlmMhdEquations &&382!(semi_index in semi_indices))383error("Equation of semidiscretization $semi_index needs to be included in 'semi_indices' of 'GlmSpeedCallback'.")384end385end386387if cfl isa Real # Case for constant CFL388cfl_number = cfl389else # Variable CFL390cfl_number = cfl(t)391end392393for semi_index in semi_indices394semi = semi_coupled.semis[semi_index]395mesh, equations, solver, cache = mesh_equations_solver_cache(semi)396397# compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR)398c_h_deltat = calc_dt_for_cleaning_speed(cfl_number,399mesh, equations, solver, cache)400401# c_h is proportional to its own time step divided by the complete MHD time step402# We use @reset here since the equations are immutable (to work on GPUs etc.).403# Thus, we need to modify the equations field of the semidiscretization.404@reset equations.c_h = glm_scale * c_h_deltat / dt405semi.equations = equations406end407408return semi_coupled409end410411################################################################################412### Equations413################################################################################414415"""416BoundaryConditionCoupled(other_semi_index, indices, uEltype, coupling_converter)417418Boundary condition to glue two meshes together. Solution values at the boundary419of another mesh will be used as boundary values. This requires the use420of [`SemidiscretizationCoupled`](@ref). The other mesh is specified by `other_semi_index`,421which is the index of the mesh in the tuple of semidiscretizations.422423Note that the elements and nodes of the two meshes at the coupled boundary must coincide.424This is currently only implemented for [`StructuredMesh`](@ref).425426# Arguments427- `other_semi_index`: the index in `SemidiscretizationCoupled` of the semidiscretization428from which the values are copied429- `indices::Tuple`: node/cell indices at the boundary of the mesh in the other430semidiscretization. See examples below.431- `uEltype::Type`: element type of solution432- `coupling_converter::CouplingConverter`: function to call for converting the solution433state of one system to the other system434435# Examples436```julia437# Connect the left boundary of mesh 2 to our boundary such that our positive438# boundary direction will match the positive y direction of the other boundary439BoundaryConditionCoupled(2, (:begin, :i), Float64, fun)440441# Connect the same two boundaries oppositely oriented442BoundaryConditionCoupled(2, (:begin, :i_backwards), Float64, fun)443444# Using this as y_neg boundary will connect `our_cells[i, 1, j]` to `other_cells[j, end-i, end]`445BoundaryConditionCoupled(2, (:j, :i_backwards, :end), Float64, fun)446```447448!!! warning "Experimental code"449This is an experimental feature and can change any time.450"""451mutable struct BoundaryConditionCoupled{NDIMS,452# Store the other semi index as type parameter,453# so that retrieving the other semidiscretization454# is type-stable.455# x-ref: https://github.com/trixi-framework/Trixi.jl/pull/1979456other_semi_index, NDIMST2M1,457uEltype <: Real, Indices, CouplingConverter}458# NDIMST2M1 == NDIMS * 2 - 1459# Buffer for boundary values: [variable, nodes_i, nodes_j, cell_i, cell_j]460u_boundary :: Array{uEltype, NDIMST2M1} # NDIMS * 2 - 1461other_orientation :: Int462indices :: Indices463coupling_converter :: CouplingConverter464465function BoundaryConditionCoupled(other_semi_index, indices, uEltype,466coupling_converter)467NDIMS = length(indices)468u_boundary = Array{uEltype, NDIMS * 2 - 1}(undef, ntuple(_ -> 0, NDIMS * 2 - 1))469470if indices[1] in (:begin, :end)471other_orientation = 1472elseif indices[2] in (:begin, :end)473other_orientation = 2474else # indices[3] in (:begin, :end)475other_orientation = 3476end477478new{NDIMS, other_semi_index, NDIMS * 2 - 1, uEltype, typeof(indices),479typeof(coupling_converter)}(u_boundary,480other_orientation,481indices, coupling_converter)482end483end484485function Base.eltype(boundary_condition::BoundaryConditionCoupled)486eltype(boundary_condition.u_boundary)487end488489function (boundary_condition::BoundaryConditionCoupled)(u_inner, orientation, direction,490cell_indices,491surface_node_indices,492surface_flux_function,493equations)494# get_node_vars(boundary_condition.u_boundary, equations, solver, surface_node_indices..., cell_indices...),495# but we don't have a solver here496u_boundary = SVector(ntuple(v -> boundary_condition.u_boundary[v,497surface_node_indices...,498cell_indices...],499Val(nvariables(equations))))500501# Calculate boundary flux502if surface_flux_function isa Tuple503# In case of conservative (index 1) and non-conservative (index 2) fluxes,504# add the non-conservative one with a factor of 1/2.505if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary506flux = (surface_flux_function[1](u_inner, u_boundary, orientation,507equations),508surface_flux_function[2](u_inner, u_boundary, orientation,509equations))510else # u_boundary is "left" of boundary, u_inner is "right" of boundary511flux = (surface_flux_function[1](u_boundary, u_inner, orientation,512equations),513surface_flux_function[2](u_boundary, u_inner, orientation,514equations))515end516else517if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary518flux = surface_flux_function(u_inner, u_boundary, orientation, equations)519else # u_boundary is "left" of boundary, u_inner is "right" of boundary520flux = surface_flux_function(u_boundary, u_inner, orientation, equations)521end522end523524return flux525end526527function allocate_coupled_boundary_conditions(semi::AbstractSemidiscretization)528n_boundaries = 2 * ndims(semi)529mesh, equations, solver, _ = mesh_equations_solver_cache(semi)530531for direction in 1:n_boundaries532boundary_condition = semi.boundary_conditions[direction]533534allocate_coupled_boundary_condition(boundary_condition, direction, mesh,535equations,536solver)537end538end539540# Don't do anything for other BCs than BoundaryConditionCoupled541function allocate_coupled_boundary_condition(boundary_condition, direction, mesh,542equations,543solver)544return nothing545end546547# In 2D548function allocate_coupled_boundary_condition(boundary_condition::BoundaryConditionCoupled{2},549direction, mesh, equations, dg::DGSEM)550if direction in (1, 2)551cell_size = size(mesh, 2)552else553cell_size = size(mesh, 1)554end555556uEltype = eltype(boundary_condition)557boundary_condition.u_boundary = Array{uEltype, 3}(undef, nvariables(equations),558nnodes(dg),559cell_size)560end561562# Don't do anything for other BCs than BoundaryConditionCoupled563function copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi)564return nothing565end566567function copy_to_coupled_boundary!(u_ode, semi_coupled, semi, i, n_boundaries,568boundary_condition, boundary_conditions...)569copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi)570if i < n_boundaries571copy_to_coupled_boundary!(u_ode, semi_coupled, semi, i + 1, n_boundaries,572boundary_conditions...)573end574end575576function copy_to_coupled_boundary!(boundary_conditions::Union{Tuple, NamedTuple}, u_ode,577semi_coupled, semi)578copy_to_coupled_boundary!(u_ode, semi_coupled, semi, 1, length(boundary_conditions),579boundary_conditions...)580end581582# In 2D583function copy_to_coupled_boundary!(boundary_condition::BoundaryConditionCoupled{2,584other_semi_index},585u_ode, semi_coupled, semi) where {other_semi_index}586@unpack u_indices = semi_coupled587@unpack other_orientation, indices = boundary_condition588@unpack coupling_converter, u_boundary = boundary_condition589590mesh_own, equations_own, solver_own, cache_own = mesh_equations_solver_cache(semi)591other_semi = semi_coupled.semis[other_semi_index]592mesh_other, equations_other, solver_other, cache_other = mesh_equations_solver_cache(other_semi)593594node_coordinates_other = cache_other.elements.node_coordinates595u_ode_other = get_system_u_ode(u_ode, other_semi_index, semi_coupled)596u_other = wrap_array(u_ode_other, mesh_other, equations_other, solver_other,597cache_other)598599linear_indices = LinearIndices(size(mesh_other))600601if other_orientation == 1602cells = axes(mesh_other, 2)603else # other_orientation == 2604cells = axes(mesh_other, 1)605end606607# Copy solution data to the coupled boundary using "delayed indexing" with608# a start value and a step size to get the correct face and orientation.609node_index_range = eachnode(solver_other)610i_node_start, i_node_step = index_to_start_step_2d(indices[1], node_index_range)611j_node_start, j_node_step = index_to_start_step_2d(indices[2], node_index_range)612613i_cell_start, i_cell_step = index_to_start_step_2d(indices[1], axes(mesh_other, 1))614j_cell_start, j_cell_step = index_to_start_step_2d(indices[2], axes(mesh_other, 2))615616# We need indices starting at 1 for the handling of `i_cell` etc.617Base.require_one_based_indexing(cells)618619@threaded for i in eachindex(cells)620cell = cells[i]621i_cell = i_cell_start + (i - 1) * i_cell_step622j_cell = j_cell_start + (i - 1) * j_cell_step623624i_node = i_node_start625j_node = j_node_start626element_id = linear_indices[i_cell, j_cell]627628for element_id in eachnode(solver_other)629x_other = get_node_coords(node_coordinates_other, equations_other,630solver_other,631i_node, j_node, linear_indices[i_cell, j_cell])632u_node_other = get_node_vars(u_other, equations_other, solver_other, i_node,633j_node, linear_indices[i_cell, j_cell])634u_node_converted = coupling_converter(x_other, u_node_other,635equations_other,636equations_own)637638for i in eachindex(u_node_converted)639u_boundary[i, element_id, cell] = u_node_converted[i]640end641642i_node += i_node_step643j_node += j_node_step644end645end646647return nothing648end649650################################################################################651### DGSEM/structured652################################################################################653654@inline function calc_boundary_flux_by_direction!(surface_flux_values, u, t,655orientation,656boundary_condition::BoundaryConditionCoupled,657mesh::Union{StructuredMesh,658StructuredMeshView},659nonconservative_terms::False,660equations,661surface_integral, dg::DG, cache,662direction, node_indices,663surface_node_indices, element)664@unpack node_coordinates, contravariant_vectors, inverse_jacobian = cache.elements665@unpack surface_flux = surface_integral666667cell_indices = get_boundary_indices(element, orientation, mesh)668669u_inner = get_node_vars(u, equations, dg, node_indices..., element)670671# If the mapping is orientation-reversing, the contravariant vectors' orientation672# is reversed as well. The normal vector must be oriented in the direction673# from `left_element` to `right_element`, or the numerical flux will be computed674# incorrectly (downwind direction).675sign_jacobian = sign(inverse_jacobian[node_indices..., element])676677# Contravariant vector Ja^i is the normal vector678normal = sign_jacobian *679get_contravariant_vector(orientation, contravariant_vectors,680node_indices..., element)681682# If the mapping is orientation-reversing, the normal vector will be reversed (see above).683# However, the flux now has the wrong sign, since we need the physical flux in normal direction.684flux = sign_jacobian * boundary_condition(u_inner, normal, direction, cell_indices,685surface_node_indices, surface_flux, equations)686687for v in eachvariable(equations)688surface_flux_values[v, surface_node_indices..., direction, element] = flux[v]689end690691return nothing692end693694@inline function calc_boundary_flux_by_direction!(surface_flux_values, u, t,695orientation,696boundary_condition::BoundaryConditionCoupled,697mesh::Union{StructuredMesh,698StructuredMeshView},699nonconservative_terms::True,700equations,701surface_integral, dg::DG, cache,702direction, node_indices,703surface_node_indices, element)704@unpack node_coordinates, contravariant_vectors, inverse_jacobian = cache.elements705@unpack surface_flux = surface_integral706707cell_indices = get_boundary_indices(element, orientation, mesh)708709u_inner = get_node_vars(u, equations, dg, node_indices..., element)710711# If the mapping is orientation-reversing, the contravariant vectors' orientation712# is reversed as well. The normal vector must be oriented in the direction713# from `left_element` to `right_element`, or the numerical flux will be computed714# incorrectly (downwind direction).715sign_jacobian = sign(inverse_jacobian[node_indices..., element])716717# Contravariant vector Ja^i is the normal vector718normal = sign_jacobian *719get_contravariant_vector(orientation, contravariant_vectors,720node_indices..., element)721722# If the mapping is orientation-reversing, the normal vector will be reversed (see above).723# However, the flux now has the wrong sign, since we need the physical flux in normal direction.724flux, noncons_flux = boundary_condition(u_inner, normal, direction, cell_indices,725surface_node_indices, surface_flux,726equations)727728for v in eachvariable(equations)729surface_flux_values[v, surface_node_indices..., direction, element] = sign_jacobian *730(flux[v] +7310.5f0 *732noncons_flux[v])733end734735return nothing736end737738function get_boundary_indices(element, orientation,739mesh::Union{StructuredMesh{2}, StructuredMeshView{2}})740cartesian_indices = CartesianIndices(size(mesh))741if orientation == 1742# Get index of element in y-direction743cell_indices = (cartesian_indices[element][2],)744else # orientation == 2745# Get index of element in x-direction746cell_indices = (cartesian_indices[element][1],)747end748749return cell_indices750end751752################################################################################753### Special elixirs754################################################################################755756# Analyze convergence for SemidiscretizationCoupled757function analyze_convergence(errors_coupled, iterations,758semi_coupled::SemidiscretizationCoupled)759# Extract errors: the errors are currently stored as760# | iter 1 sys 1 var 1...n | iter 1 sys 2 var 1...n | ... | iter 2 sys 1 var 1...n | ...761# but for calling `analyze_convergence` below, we need the following layout762# sys n: | iter 1 var 1...n | iter 1 var 1...n | ... | iter 2 var 1...n | ...763# That is, we need to extract and join the data for a single system764errors = Dict{Symbol, Vector{Float64}}[]765for i in eachsystem(semi_coupled)766push!(errors, Dict(:l2 => Float64[], :linf => Float64[]))767end768offset = 0769for iter in 1:iterations, i in eachsystem(semi_coupled)770# Extract information on current semi771semi = semi_coupled.semis[i]772_, equations, _, _ = mesh_equations_solver_cache(semi)773variablenames = varnames(cons2cons, equations)774775# Compute offset776first = offset + 1777last = offset + length(variablenames)778offset += length(variablenames)779780# Append errors to appropriate storage781append!(errors[i][:l2], errors_coupled[:l2][first:last])782append!(errors[i][:linf], errors_coupled[:linf][first:last])783end784785eoc_mean_values = Vector{Dict{Symbol, Any}}(undef, nsystems(semi_coupled))786for i in eachsystem(semi_coupled)787# Use visual cues to separate output from multiple systems788println()789println("="^100)790println("# System $i")791println("="^100)792793# Extract information on current semi794semi = semi_coupled.semis[i]795_, equations, _, _ = mesh_equations_solver_cache(semi)796variablenames = varnames(cons2cons, equations)797798eoc_mean_values[i] = analyze_convergence(errors[i], iterations, variablenames)799end800801return eoc_mean_values802end803end # @muladd804805806