From bc821d7d3eeb4539dc17b4255b61dfea8208da1b Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 7 Mar 2025 18:02:06 +0100 Subject: [PATCH 001/102] Add idp mortar and calculation of surface integral for mortar --- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 116 +++++++ src/solvers/dgsem/basis_lobatto_legendre.jl | 32 ++ src/solvers/dgsem_tree/containers_2d.jl | 126 +++++++ .../dgsem_tree/dg_2d_subcell_limiters.jl | 311 ++++++++++++++++++ 4 files changed, 585 insertions(+) create mode 100644 examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl new file mode 100644 index 00000000000..957410a2e88 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -0,0 +1,116 @@ +# We use time integration methods implemented in Trixi.jl, but we need the `CallbackSet` +using OrdinaryDiffEq: CallbackSet +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +""" + initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) + +A medium blast wave taken from +- Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) +""" +function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) + # Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave" + # Set up polar coordinates + RealT = eltype(x) + inicenter = SVector(0, 0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + sin_phi, cos_phi = sincos(phi) + + # Calculate primitive variables + rho = r > 0.5f0 ? one(RealT) : RealT(1.1691) + v1 = r > 0.5f0 ? zero(RealT) : RealT(0.1882) * cos_phi + v2 = r > 0.5f0 ? zero(RealT) : RealT(0.1882) * sin_phi + # p = r > 0.5f0 ? RealT(1.0E-3) : RealT(1.245) + p = r > 0.5f0 ? RealT(1.0E-1) : RealT(1.245) + + return prim2cons(SVector(rho, v1, v2, p), equations) +end +initial_condition = initial_condition_blast_wave + +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha +basis = LobattoLegendreBasis(3) +limiter_idp = SubcellLimiterIDP(equations, basis; + positivity_variables_cons = ["rho"], + positivity_correction_factor = 0.5, + positivity_variables_nonlinear = [pressure], + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_math, + max)], + # Default parameters are not sufficient to fulfill bounds properly. + max_iterations_newton = 70, + newton_tolerances = (1.0e-13, 1.0e-14)) +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +mortar = Trixi.MortarIDP(basis) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) + +coordinates_min = (-2.0, -2.0) +coordinates_max = (2.0, 2.0) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +amr_indicator = IndicatorMax(semi, variable=first) + +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = 4, + med_level = 5, med_threshold = 1.01, + max_level = 6, max_threshold = 1.1) + +amr_callback = AMRCallback(semi, amr_controller, + interval = 10, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = false) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + amr_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = true)) + +sol = Trixi.solve(ode, + Trixi.SimpleEuler(stage_callbacks = stage_callbacks); + # Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 0ba32565fbb..8b808c1841f 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -145,6 +145,38 @@ end left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights) right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) +struct LobattoLegendreMortarIDP{RealT <: Real, NNODES} <: AbstractMortarL2{RealT} +end + +function MortarIDP(basis::LobattoLegendreBasis) + RealT = real(basis) + nnodes_ = nnodes(basis) + + LobattoLegendreMortarIDP{RealT, nnodes_}() +end + +function Base.show(io::IO, mortar::LobattoLegendreMortarIDP) + @nospecialize mortar # reduce precompilation time + # TODO + print(io, "LobattoLegendreMortarIDP{", real(mortar), "}(polydeg=", polydeg(mortar), + ")") +end +function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarIDP) + @nospecialize mortar # reduce precompilation time + # TODO + print(io, "LobattoLegendreMortarIDP{", real(mortar), "} with polynomials of degree ", + polydeg(mortar)) +end + +@inline Base.real(mortar::LobattoLegendreMortarIDP{RealT}) where {RealT} = RealT + +@inline function nnodes(mortar::LobattoLegendreMortarIDP{RealT, NNODES}) where {RealT, + NNODES} + NNODES +end + +@inline polydeg(mortar::LobattoLegendreMortarIDP) = nnodes(mortar) - 1 + struct LobattoLegendreMortarL2{RealT <: Real, NNODES, ForwardMatrix <: AbstractMatrix{RealT}, ReverseMatrix <: AbstractMatrix{RealT}} <: diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index f0d66fd2353..1d54e9b5300 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -611,6 +611,132 @@ function init_mortars(cell_ids, mesh::TreeMesh2D, return mortars end +# Container data structure (structure-of-arrays style) for DG mortars for IDP AMR +# Positions/directions for orientations = 1, large_sides = 2: +# mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar +# | | +# upper = 2 | | +# | | +# | 3 = large side +# | | +# lower = 1 | | +# | | +mutable struct IDPMortarContainer2D{uEltype <: Real} <: AbstractContainer + u_upper::Array{uEltype, 3} # [variables, i, mortars] + u_lower::Array{uEltype, 3} # [variables, i, mortars] + u_large::Array{uEltype, 3} # [variables, i, mortars] + neighbor_ids::Array{Int, 2} # [position, mortars] + # Large sides: left -> 1, right -> 2 + large_sides::Vector{Int} # [mortars] + orientations::Vector{Int} # [mortars] + # internal `resize!`able storage + _u_upper::Vector{uEltype} + _u_lower::Vector{uEltype} + _u_large::Vector{uEltype} + _neighbor_ids::Vector{Int} +end + +nvariables(mortars::IDPMortarContainer2D) = size(mortars.u_upper, 1) +nnodes(mortars::IDPMortarContainer2D) = size(mortars.u_upper, 2) +Base.eltype(mortars::IDPMortarContainer2D) = eltype(mortars.u_upper) + +# See explanation of Base.resize! for the element container +function Base.resize!(mortars::IDPMortarContainer2D, capacity) + n_nodes = nnodes(mortars) + n_variables = nvariables(mortars) + @unpack _u_upper, _u_lower, _u_large, _neighbor_ids, + large_sides, orientations = mortars + + resize!(_u_upper, n_variables * n_nodes * capacity) + mortars.u_upper = unsafe_wrap(Array, pointer(_u_upper), + (n_variables, n_nodes, capacity)) + + resize!(_u_lower, n_variables * n_nodes * capacity) + mortars.u_lower = unsafe_wrap(Array, pointer(_u_lower), + (n_variables, n_nodes, capacity)) + + resize!(_u_large, n_variables * n_nodes * capacity) + mortars.u_large = unsafe_wrap(Array, pointer(_u_large), + (n_variables, n_nodes, capacity)) + + resize!(_neighbor_ids, 3 * capacity) + mortars.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), + (3, capacity)) + + resize!(large_sides, capacity) + + resize!(orientations, capacity) + + return nothing +end + +function IDPMortarContainer2D{uEltype}(capacity::Integer, n_variables, + n_nodes) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u_upper = fill(nan, n_variables * n_nodes * capacity) + u_upper = unsafe_wrap(Array, pointer(_u_upper), + (n_variables, n_nodes, capacity)) + + _u_lower = fill(nan, n_variables * n_nodes * capacity) + u_lower = unsafe_wrap(Array, pointer(_u_lower), + (n_variables, n_nodes, capacity)) + + _u_large = fill(nan, n_variables * n_nodes * capacity) + u_large = unsafe_wrap(Array, pointer(_u_large), + (n_variables, n_nodes, capacity)) + + _neighbor_ids = fill(typemin(Int), 3 * capacity) + neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), + (3, capacity)) + + large_sides = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + return IDPMortarContainer2D{uEltype}(u_upper, u_lower, u_large, neighbor_ids, large_sides, + orientations, + _u_upper, _u_lower, _u_large, _neighbor_ids) +end + +# Return number of IDP mortars +@inline nmortars(l2mortars::IDPMortarContainer2D) = length(l2mortars.orientations) + +# Allow printing container contents +function Base.show(io::IO, ::MIME"text/plain", c::IDPMortarContainer2D) + @nospecialize c # reduce precompilation time + + println(io, '*'^20) + for idx in CartesianIndices(c.u_upper) + println(io, "c.u_upper[$idx] = $(c.u_upper[idx])") + end + for idx in CartesianIndices(c.u_lower) + println(io, "c.u_lower[$idx] = $(c.u_lower[idx])") + end + for idx in CartesianIndices(c.u_large) + println(io, "c.u_large[$idx] = $(c.u_large[idx])") + end + println(io, "transpose(c.neighbor_ids) = $(transpose(c.neighbor_ids))") + println(io, "c.large_sides = $(c.large_sides)") + println(io, "c.orientations = $(c.orientations)") + print(io, '*'^20) +end + +# Create mortar container and initialize mortar data in `elements`. +function init_mortars(cell_ids, mesh::TreeMesh2D, + elements::ElementContainer2D, + ::LobattoLegendreMortarIDP) + # Initialize containers + n_mortars = count_required_mortars(mesh, cell_ids) + mortars = IDPMortarContainer2D{eltype(elements)}(n_mortars, nvariables(elements), + nnodes(elements)) + + # Connect elements with mortars + init_mortars!(mortars, elements, mesh) + return mortars +end + # Count the number of mortars that need to be created function count_required_mortars(mesh::TreeMesh2D, cell_ids) count = 0 diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index c27327be8ad..3af3b2db768 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -56,6 +56,28 @@ function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, flux_temp_threaded, fhat_temp_threaded) end +# The methods below are specialized on the mortar type +# and called from the basic `create_cache` method at the top. +function create_cache(mesh::TreeMesh{2}, + equations, mortar_idp::LobattoLegendreMortarIDP, uEltype) + # TODO: Taal performance using different types + + # TODO: What do I really need? + MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_idp)}, uEltype, 2, + nvariables(equations) * nnodes(mortar_idp)} + fstar_primary_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + fstar_primary_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + fstar_secondary_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + fstar_secondary_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + + # A2d = Array{uEltype, 2} + # fstar_upper_threaded = [A2d(undef, nvariables(equations), nnodes(mortar_idp)) for _ in 1:Threads.nthreads()] + # fstar_lower_threaded = [A2d(undef, nvariables(equations), nnodes(mortar_idp)) for _ in 1:Threads.nthreads()] + + (; fstar_primary_upper_threaded, fstar_primary_lower_threaded, + fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) +end + function calc_volume_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, @@ -471,6 +493,295 @@ end return nothing end +function prolong2mortars!(cache, u, + mesh::TreeMesh{2}, equations, + mortar_l2::LobattoLegendreMortarIDP, + dg::DGSEM) + @threaded for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Copy solutions + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if cache.mortars.orientations[mortar] == 1 + # IDP mortars in x-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper[v, l, mortar] = u[v, 1, l, upper_element] + cache.mortars.u_lower[v, l, mortar] = u[v, 1, l, lower_element] + cache.mortars.u_large[v, l, mortar] = u[v, nnodes(dg), l, large_element] + end + end + else + # IDP mortars in y-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper[v, l, mortar] = u[v, l, 1, upper_element] + cache.mortars.u_lower[v, l, mortar] = u[v, l, 1, lower_element] + cache.mortars.u_large[v, l, mortar] = u[v, l, nnodes(dg), large_element] + end + end + end + else # large_sides[mortar] == 2 -> small elements on left side + if cache.mortars.orientations[mortar] == 1 + # IDP mortars in x-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper[v, l, mortar] = u[v, nnodes(dg), l, upper_element] + cache.mortars.u_lower[v, l, mortar] = u[v, nnodes(dg), l, lower_element] + cache.mortars.u_large[v, l, mortar] = u[v, 1, l, large_element] + end + end + else + # IDP mortars in y-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper[v, l, mortar] = u[v, l, nnodes(dg), upper_element] + cache.mortars.u_lower[v, l, mortar] = u[v, l, nnodes(dg), lower_element] + cache.mortars.u_large[v, l, mortar] = u[v, l, 1, large_element] + end + end + end + end + end + + return nothing +end + +function calc_mortar_flux!(surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + mortar_idp::LobattoLegendreMortarIDP, + surface_integral, dg::DG, cache) + @unpack surface_flux = surface_integral + @unpack u_lower, u_upper, u_large, orientations = cache.mortars + @unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded, + fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache + (; weights) = dg.basis + + #=@threaded =#for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Choose thread-specific pre-allocated container + # fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()] + # fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()] + # fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()] + # fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()] + + # Calculate fluxes + orientation = orientations[mortar] + + lambda1_max = zero(eltype(surface_flux_values)) + lambda2_max = zero(eltype(surface_flux_values)) + for i in eachnode(dg) + u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) + u_upper_local = get_node_vars(u_upper, equations, dg, i, mortar) + u_large_local = get_node_vars(u_large, equations, dg, i, mortar) + + lambda1_lower, lambda2_lower = max_abs_speeds(u_lower_local, equations) + lambda1_upper, lambda2_upper = max_abs_speeds(u_upper_local, equations) + lambda1_large, lambda2_large = max_abs_speeds(u_large_local, equations) + lambda1_max = max(lambda1_max, lambda1_lower, lambda1_upper, lambda1_large) + lambda2_max = max(lambda2_max, lambda2_lower, lambda2_upper, lambda2_large) + end + + if orientation == 1 + lambda_max = lambda1_max + else + lambda_max = lambda2_max + end + + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientation == 1 + # L2 mortars in x-direction + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + direction_small = 3 + direction_large = 4 + end + + # Lower element + for i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) # u_rr + + # flux part + f_lower = flux(u_lower_local, orientation, equations) # f_rr + + own_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_small, lower_element) + + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll + f_large = flux(u_large_local, orientation, equations) # f_ll + + other_part = 0.5f0 * (f_large + lambda_max * u_large_local) + add_to_node_vars!(surface_flux_values, (0.5f0 * weights[j]) * other_part, + equations, dg, i, direction_small, lower_element) + end + end + # Upper element + for i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_upper_local = get_node_vars(u_upper, equations, dg, i, mortar) # u_rr + + # flux part + f_upper = flux(u_upper_local, orientation, equations) # f_rr + + own_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_small, upper_element) + + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll + f_large = flux(u_large_local, orientation, equations) # f_ll + + other_part = 0.5f0 * (f_large + lambda_max * u_large_local) + add_to_node_vars!(surface_flux_values, (0.5f0 * weights[j]) * other_part, + equations, dg, i, direction_small, upper_element) + end + end + # Large element + for i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_large_local = get_node_vars(u_large, equations, dg, i, mortar) # u_ll + + # flux part + f_large = flux(u_large_local, orientation, equations) # f_ll + + own_part = 0.5f0 * (f_large + lambda_max * u_large_local) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_large, large_element) + + for j in eachnode(dg) + # lower + u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_rr + f_lower = flux(u_lower_local, orientation, equations) # f_rr + + other_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) + add_to_node_vars!(surface_flux_values, (0.25f0 * weights[j]) * other_part, + equations, dg, i, direction_large, large_element) + + # upper + u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_rr + f_upper = flux(u_upper_local, orientation, equations) # f_rr + + other_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) + add_to_node_vars!(surface_flux_values, (0.25f0 * weights[j]) * other_part, + equations, dg, i, direction_large, large_element) + end + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientation == 1 + # L2 mortars in x-direction + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + direction_small = 4 + direction_large = 3 + end + + # Lower element + for i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) # u_ll + + # flux part + f_lower = flux(u_lower_local, orientation, equations) + + own_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_small, lower_element) + + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr + f_large = flux(u_large_local, orientation, equations) + + other_part = 0.5f0 * (f_large - lambda_max * u_large_local) + add_to_node_vars!(surface_flux_values, (0.5f0 * weights[j]) * other_part, equations, dg, i, direction_small, lower_element) + end + end + # Upper element + for i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_upper_local = get_node_vars(u_upper, equations, dg, i, mortar) # u_ll + + # flux part + f_upper = flux(u_upper_local, orientation, equations) + + own_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_small, upper_element) + + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr + f_large = flux(u_large_local, orientation, equations) + + other_part = 0.5f0 * (f_large - lambda_max * u_large_local) + add_to_node_vars!(surface_flux_values, (0.5f0 * weights[j]) * other_part, equations, dg, i, direction_small, upper_element) + end + end + # Large element + for i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_large_local = get_node_vars(u_large, equations, dg, i, mortar) # u_rr + + # flux part + f_large = flux(u_large_local, orientation, equations) + + own_part = 0.5f0 * (f_large - lambda_max * u_large_local) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_large, large_element) + + for j in eachnode(dg) + # lower + u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_ll + f_lower = flux(u_lower_local, orientation, equations) + + other_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) + add_to_node_vars!(surface_flux_values, (0.25 * weights[j]) * other_part, + equations, dg, i, direction_large, large_element) + + # upper + u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_ll + f_upper = flux(u_upper_local, orientation, equations) + + other_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) + add_to_node_vars!(surface_flux_values, (0.25 * weights[j]) * other_part, + equations, dg, i, direction_large, large_element) + end + end + end + end + + # #=@threaded =#for mortar in eachmortar(dg, cache) + # # Choose thread-specific pre-allocated container + # # fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()] + # # fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()] + # # fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()] + # # fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()] + + # # Calculate fluxes + # orientation = orientations[mortar] + # calc_fstar_IDP_AMR!(surface_flux_values, equations, surface_flux, dg, u_lower, u_upper, u_large, mortar, + # orientation) + # # calc_fstar_IDP_AMR!(fstar_primary_lower, equations, surface_flux, dg, u_lower, mortar, + # # orientation) + # # calc_fstar_IDP_AMR!(fstar_secondary_upper, equations, surface_flux, dg, u_upper, mortar, + # # orientation) + # # calc_fstar_IDP_AMR!(fstar_secondary_lower, equations, surface_flux, dg, u_lower, mortar, + # # orientation) + + # # mortar_fluxes_to_elements!(surface_flux_values, + # # mesh, equations, mortar_idp, dg, cache, + # # mortar, fstar_primary_upper, fstar_primary_lower, + # # fstar_secondary_upper, fstar_secondary_lower) + # end + + return nothing +end + """ get_boundary_outer_state(u_inner, t, boundary_condition::BoundaryConditionDirichlet, From 62eb28fc5d801881e56a4e63d97ce3aa55df8d0f Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 7 Mar 2025 18:02:37 +0100 Subject: [PATCH 002/102] Allow SimpleEuler --- src/time_integration/methods_SSP.jl | 70 ++++++++++++++++++----------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/src/time_integration/methods_SSP.jl b/src/time_integration/methods_SSP.jl index ee01ae58589..8e79f0049f8 100644 --- a/src/time_integration/methods_SSP.jl +++ b/src/time_integration/methods_SSP.jl @@ -20,35 +20,51 @@ The third-order SSP Runge-Kutta method of Shu and Osher. "Efficient Implementation of Essentially Non-oscillatory Shock-Capturing Schemes" (Eq. 2.18) [DOI: 10.1016/0021-9991(88)90177-5](https://doi.org/10.1016/0021-9991(88)90177-5) """ -struct SimpleSSPRK33{StageCallbacks} <: SimpleAlgorithmSSP - numerator_a::SVector{3, Float64} - numerator_b::SVector{3, Float64} - denominator::SVector{3, Float64} - c::SVector{3, Float64} +struct SimpleSSPRK{NStages, StageCallbacks} <: SimpleAlgorithmSSP + numerator_a::SVector{NStages, Float64} + numerator_b::SVector{NStages, Float64} + denominator::SVector{NStages, Float64} + c::SVector{NStages, Float64} stage_callbacks::StageCallbacks +end - function SimpleSSPRK33(; stage_callbacks = ()) - # Mathematically speaking, it is not necessary for the algorithm to split the factors - # into numerator and denominator. Otherwise, however, rounding errors of the order of - # the machine accuracy will occur, which will add up over time and thus endanger the - # conservation of the simulation. - # See also https://github.com/trixi-framework/Trixi.jl/pull/1640. - numerator_a = SVector(0.0, 3.0, 1.0) # a = numerator_a / denominator - numerator_b = SVector(1.0, 1.0, 2.0) # b = numerator_b / denominator - denominator = SVector(1.0, 4.0, 3.0) - c = SVector(0.0, 1.0, 1 / 2) - - # Butcher tableau - # c | A - # 0 | - # 1 | 1 - # 1/2 | 1/4 1/4 - # -------------------- - # b | 1/6 1/6 2/3 - - new{typeof(stage_callbacks)}(numerator_a, numerator_b, denominator, c, - stage_callbacks) - end +function SimpleSSPRK33(; stage_callbacks = ()) + # Mathematically speaking, it is not necessary for the algorithm to split the factors + # into numerator and denominator. Otherwise, however, rounding errors of the order of + # the machine accuracy will occur, which will add up over time and thus endanger the + # conservation of the simulation. + # See also https://github.com/trixi-framework/Trixi.jl/pull/1640. + numerator_a = SVector(0.0, 3.0, 1.0) # a = numerator_a / denominator + numerator_b = SVector(1.0, 1.0, 2.0) # b = numerator_b / denominator + denominator = SVector(1.0, 4.0, 3.0) + c = SVector(0.0, 1.0, 1 / 2) + + # Butcher tableau + # c | A + # 0 | + # 1 | 1 + # 1/2 | 1/4 1/4 + # -------------------- + # b | 1/6 1/6 2/3 + + SimpleSSPRK{length(c), typeof(stage_callbacks)}(numerator_a, numerator_b, denominator, c, + stage_callbacks) +end + +function SimpleEuler(; stage_callbacks = ()) + numerator_a = SVector(0.0) # a = numerator_a / denominator + numerator_b = SVector(1.0) # b = numerator_b / denominator + denominator = SVector(1.0) + c = SVector(0.0) + + # Butcher tableau + # c | A + # 0 | + # -------------------- + # b | 1 + + SimpleSSPRK{length(c), typeof(stage_callbacks)}(numerator_a, numerator_b, denominator, c, + stage_callbacks) end # This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L1 From ed465d2824a0736635f45700e2e99c3031452915 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 7 Mar 2025 18:09:21 +0100 Subject: [PATCH 003/102] Add test --- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 2 +- test/test_tree_2d_euler.jl | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 957410a2e88..9d8ef814f87 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -106,7 +106,7 @@ callbacks = CallbackSet(summary_callback, ############################################################################### # run the simulation -stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = true)) +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = false)) sol = Trixi.solve(ode, Trixi.SimpleEuler(stage_callbacks = stage_callbacks); diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 4f75d3c2a45..060a35ec246 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -381,6 +381,36 @@ end end end +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_blast_wave_amr_sc_subcell.jl"), + l2=[ + 0.4838330697699074, + 0.20294827086694647, + 0.20294682285149304, + 0.681270812289248 + ], + linf=[ + 1.4714902074597642, + 0.9511042179938388, + 0.9510612394156363, + 2.9106904796599298 + ], + tspan=(0.0, 1.0),) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + @trixi_testset "elixir_euler_sedov_blast_wave.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), l2=[ From 97b87d7e4992178ee3bf647e7908503282ebc733 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 7 Mar 2025 18:14:11 +0100 Subject: [PATCH 004/102] fmt --- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 4 +- src/solvers/dgsem/basis_lobatto_legendre.jl | 5 +- src/solvers/dgsem_tree/containers_2d.jl | 8 +-- .../dgsem_tree/dg_2d_subcell_limiters.jl | 64 +++++++++++++------ src/time_integration/methods_SSP.jl | 8 +-- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 9d8ef814f87..ef39f5d87c2 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -83,7 +83,7 @@ save_solution = SaveSolutionCallback(interval = 1, save_final_solution = true, solution_variables = cons2prim) -amr_indicator = IndicatorMax(semi, variable=first) +amr_indicator = IndicatorMax(semi, variable = first) amr_controller = ControllerThreeLevel(semi, amr_indicator, base_level = 4, @@ -110,7 +110,7 @@ stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_error sol = Trixi.solve(ode, Trixi.SimpleEuler(stage_callbacks = stage_callbacks); - # Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + # Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback save_everystep = false, callback = callbacks); summary_callback() # print the timer summary diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 8b808c1841f..e65434a784b 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -164,14 +164,15 @@ end function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarIDP) @nospecialize mortar # reduce precompilation time # TODO - print(io, "LobattoLegendreMortarIDP{", real(mortar), "} with polynomials of degree ", + print(io, "LobattoLegendreMortarIDP{", real(mortar), + "} with polynomials of degree ", polydeg(mortar)) end @inline Base.real(mortar::LobattoLegendreMortarIDP{RealT}) where {RealT} = RealT @inline function nnodes(mortar::LobattoLegendreMortarIDP{RealT, NNODES}) where {RealT, - NNODES} + NNODES} NNODES end diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index 1d54e9b5300..6744f62bc9f 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -671,7 +671,7 @@ function Base.resize!(mortars::IDPMortarContainer2D, capacity) end function IDPMortarContainer2D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} + n_nodes) where {uEltype <: Real} nan = convert(uEltype, NaN) # Initialize fields with defaults @@ -695,9 +695,9 @@ function IDPMortarContainer2D{uEltype}(capacity::Integer, n_variables, orientations = fill(typemin(Int), capacity) - return IDPMortarContainer2D{uEltype}(u_upper, u_lower, u_large, neighbor_ids, large_sides, - orientations, - _u_upper, _u_lower, _u_large, _neighbor_ids) + return IDPMortarContainer2D{uEltype}(u_upper, u_lower, u_large, neighbor_ids, + large_sides, orientations, + _u_upper, _u_lower, _u_large, _neighbor_ids) end # Return number of IDP mortars diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 3af3b2db768..798a7c9f434 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -510,7 +510,8 @@ function prolong2mortars!(cache, u, for v in eachvariable(equations) cache.mortars.u_upper[v, l, mortar] = u[v, 1, l, upper_element] cache.mortars.u_lower[v, l, mortar] = u[v, 1, l, lower_element] - cache.mortars.u_large[v, l, mortar] = u[v, nnodes(dg), l, large_element] + cache.mortars.u_large[v, l, mortar] = u[v, nnodes(dg), l, + large_element] end end else @@ -519,7 +520,8 @@ function prolong2mortars!(cache, u, for v in eachvariable(equations) cache.mortars.u_upper[v, l, mortar] = u[v, l, 1, upper_element] cache.mortars.u_lower[v, l, mortar] = u[v, l, 1, lower_element] - cache.mortars.u_large[v, l, mortar] = u[v, l, nnodes(dg), large_element] + cache.mortars.u_large[v, l, mortar] = u[v, l, nnodes(dg), + large_element] end end end @@ -528,8 +530,10 @@ function prolong2mortars!(cache, u, # IDP mortars in x-direction for l in eachnode(dg) for v in eachvariable(equations) - cache.mortars.u_upper[v, l, mortar] = u[v, nnodes(dg), l, upper_element] - cache.mortars.u_lower[v, l, mortar] = u[v, nnodes(dg), l, lower_element] + cache.mortars.u_upper[v, l, mortar] = u[v, nnodes(dg), l, + upper_element] + cache.mortars.u_lower[v, l, mortar] = u[v, nnodes(dg), l, + lower_element] cache.mortars.u_large[v, l, mortar] = u[v, 1, l, large_element] end end @@ -537,8 +541,10 @@ function prolong2mortars!(cache, u, # IDP mortars in y-direction for l in eachnode(dg) for v in eachvariable(equations) - cache.mortars.u_upper[v, l, mortar] = u[v, l, nnodes(dg), upper_element] - cache.mortars.u_lower[v, l, mortar] = u[v, l, nnodes(dg), lower_element] + cache.mortars.u_upper[v, l, mortar] = u[v, l, nnodes(dg), + upper_element] + cache.mortars.u_lower[v, l, mortar] = u[v, l, nnodes(dg), + lower_element] cache.mortars.u_large[v, l, mortar] = u[v, l, 1, large_element] end end @@ -560,7 +566,7 @@ function calc_mortar_flux!(surface_flux_values, fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache (; weights) = dg.basis - #=@threaded =#for mortar in eachmortar(dg, cache) + for mortar in eachmortar(dg, cache) #=@threaded =# large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] lower_element = cache.mortars.neighbor_ids[1, mortar] @@ -614,14 +620,16 @@ function calc_mortar_flux!(surface_flux_values, f_lower = flux(u_lower_local, orientation, equations) # f_rr own_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_small, lower_element) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, + direction_small, lower_element) for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll f_large = flux(u_large_local, orientation, equations) # f_ll other_part = 0.5f0 * (f_large + lambda_max * u_large_local) - add_to_node_vars!(surface_flux_values, (0.5f0 * weights[j]) * other_part, + add_to_node_vars!(surface_flux_values, + (0.5f0 * weights[j]) * other_part, equations, dg, i, direction_small, lower_element) end end @@ -634,14 +642,16 @@ function calc_mortar_flux!(surface_flux_values, f_upper = flux(u_upper_local, orientation, equations) # f_rr own_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_small, upper_element) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, + direction_small, upper_element) for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll f_large = flux(u_large_local, orientation, equations) # f_ll other_part = 0.5f0 * (f_large + lambda_max * u_large_local) - add_to_node_vars!(surface_flux_values, (0.5f0 * weights[j]) * other_part, + add_to_node_vars!(surface_flux_values, + (0.5f0 * weights[j]) * other_part, equations, dg, i, direction_small, upper_element) end end @@ -654,7 +664,8 @@ function calc_mortar_flux!(surface_flux_values, f_large = flux(u_large_local, orientation, equations) # f_ll own_part = 0.5f0 * (f_large + lambda_max * u_large_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_large, large_element) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, + direction_large, large_element) for j in eachnode(dg) # lower @@ -662,7 +673,8 @@ function calc_mortar_flux!(surface_flux_values, f_lower = flux(u_lower_local, orientation, equations) # f_rr other_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) - add_to_node_vars!(surface_flux_values, (0.25f0 * weights[j]) * other_part, + add_to_node_vars!(surface_flux_values, + (0.25f0 * weights[j]) * other_part, equations, dg, i, direction_large, large_element) # upper @@ -670,7 +682,8 @@ function calc_mortar_flux!(surface_flux_values, f_upper = flux(u_upper_local, orientation, equations) # f_rr other_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) - add_to_node_vars!(surface_flux_values, (0.25f0 * weights[j]) * other_part, + add_to_node_vars!(surface_flux_values, + (0.25f0 * weights[j]) * other_part, equations, dg, i, direction_large, large_element) end end @@ -694,14 +707,17 @@ function calc_mortar_flux!(surface_flux_values, f_lower = flux(u_lower_local, orientation, equations) own_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_small, lower_element) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, + direction_small, lower_element) for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr f_large = flux(u_large_local, orientation, equations) other_part = 0.5f0 * (f_large - lambda_max * u_large_local) - add_to_node_vars!(surface_flux_values, (0.5f0 * weights[j]) * other_part, equations, dg, i, direction_small, lower_element) + add_to_node_vars!(surface_flux_values, + (0.5f0 * weights[j]) * other_part, equations, dg, + i, direction_small, lower_element) end end # Upper element @@ -713,14 +729,17 @@ function calc_mortar_flux!(surface_flux_values, f_upper = flux(u_upper_local, orientation, equations) own_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_small, upper_element) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, + direction_small, upper_element) for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr f_large = flux(u_large_local, orientation, equations) other_part = 0.5f0 * (f_large - lambda_max * u_large_local) - add_to_node_vars!(surface_flux_values, (0.5f0 * weights[j]) * other_part, equations, dg, i, direction_small, upper_element) + add_to_node_vars!(surface_flux_values, + (0.5f0 * weights[j]) * other_part, equations, dg, + i, direction_small, upper_element) end end # Large element @@ -732,7 +751,8 @@ function calc_mortar_flux!(surface_flux_values, f_large = flux(u_large_local, orientation, equations) own_part = 0.5f0 * (f_large - lambda_max * u_large_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, direction_large, large_element) + set_node_vars!(surface_flux_values, own_part, equations, dg, i, + direction_large, large_element) for j in eachnode(dg) # lower @@ -740,7 +760,8 @@ function calc_mortar_flux!(surface_flux_values, f_lower = flux(u_lower_local, orientation, equations) other_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) - add_to_node_vars!(surface_flux_values, (0.25 * weights[j]) * other_part, + add_to_node_vars!(surface_flux_values, + (0.25 * weights[j]) * other_part, equations, dg, i, direction_large, large_element) # upper @@ -748,7 +769,8 @@ function calc_mortar_flux!(surface_flux_values, f_upper = flux(u_upper_local, orientation, equations) other_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) - add_to_node_vars!(surface_flux_values, (0.25 * weights[j]) * other_part, + add_to_node_vars!(surface_flux_values, + (0.25 * weights[j]) * other_part, equations, dg, i, direction_large, large_element) end end diff --git a/src/time_integration/methods_SSP.jl b/src/time_integration/methods_SSP.jl index 8e79f0049f8..65e96ea29d1 100644 --- a/src/time_integration/methods_SSP.jl +++ b/src/time_integration/methods_SSP.jl @@ -47,8 +47,8 @@ function SimpleSSPRK33(; stage_callbacks = ()) # -------------------- # b | 1/6 1/6 2/3 - SimpleSSPRK{length(c), typeof(stage_callbacks)}(numerator_a, numerator_b, denominator, c, - stage_callbacks) + SimpleSSPRK{length(c), typeof(stage_callbacks)}(numerator_a, numerator_b, + denominator, c, stage_callbacks) end function SimpleEuler(; stage_callbacks = ()) @@ -63,8 +63,8 @@ function SimpleEuler(; stage_callbacks = ()) # -------------------- # b | 1 - SimpleSSPRK{length(c), typeof(stage_callbacks)}(numerator_a, numerator_b, denominator, c, - stage_callbacks) + SimpleSSPRK{length(c), typeof(stage_callbacks)}(numerator_a, numerator_b, + denominator, c, stage_callbacks) end # This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L1 From 07d978a25196e5159479d440c930c4bb65d66736 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 10 Mar 2025 11:34:02 +0100 Subject: [PATCH 005/102] Fix documentation --- .../src/files/subcell_shock_capturing.jl | 4 +-- src/time_integration/methods_SSP.jl | 27 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/literate/src/files/subcell_shock_capturing.jl b/docs/literate/src/files/subcell_shock_capturing.jl index eaa763a887f..fbff75ccc52 100644 --- a/docs/literate/src/files/subcell_shock_capturing.jl +++ b/docs/literate/src/files/subcell_shock_capturing.jl @@ -33,8 +33,8 @@ # Trixi.solve(ode, method(stage_callbacks = stage_callbacks); ...) # ```` #- -# Right now, only the canonical three-stage, third-order SSPRK method (Shu-Osher) -# [`Trixi.SimpleSSPRK33`](@ref) is implemented. +# Right now, only the basic explicit Euler method [`Trixi.SimpleEuler`](@ref) and the canonical +# three-stage, third-order SSPRK method (Shu-Osher) [`Trixi.SimpleSSPRK33`](@ref) are implemented. # # [IDP Limiting](@id IDPLimiter) # The implementation of the invariant domain preserving (IDP) limiting approach ([`SubcellLimiterIDP`](@ref)) diff --git a/src/time_integration/methods_SSP.jl b/src/time_integration/methods_SSP.jl index c9c0fb9a8c5..0092dc880f2 100644 --- a/src/time_integration/methods_SSP.jl +++ b/src/time_integration/methods_SSP.jl @@ -10,15 +10,10 @@ abstract type SimpleAlgorithmSSP end """ - SimpleSSPRK33(; stage_callbacks=()) - -The third-order SSP Runge-Kutta method of Shu and Osher. + SimpleSSPRK(; stage_callbacks=()) -## References - -- Shu, Osher (1988) - "Efficient Implementation of Essentially Non-oscillatory Shock-Capturing Schemes" (Eq. 2.18) - [DOI: 10.1016/0021-9991(88)90177-5](https://doi.org/10.1016/0021-9991(88)90177-5) +The explicit strong stability-preserving (SSP) Runge-Kutta (RK) methods. These methods can be +written and are implemented as a convex combination of forward Euler steps. """ struct SimpleSSPRK{NStages, StageCallbacks} <: SimpleAlgorithmSSP numerator_a::SVector{NStages, Float64} @@ -28,6 +23,17 @@ struct SimpleSSPRK{NStages, StageCallbacks} <: SimpleAlgorithmSSP stage_callbacks::StageCallbacks end +""" + SimpleSSPRK33(; stage_callbacks=()) + +The third-order SSP Runge-Kutta method of Shu and Osher. + +## References + +- Shu, Osher (1988) + "Efficient Implementation of Essentially Non-oscillatory Shock-Capturing Schemes" (Eq. 2.18) + [DOI: 10.1016/0021-9991(88)90177-5](https://doi.org/10.1016/0021-9991(88)90177-5) +""" function SimpleSSPRK33(; stage_callbacks = ()) # Mathematically speaking, it is not necessary for the algorithm to split the factors # into numerator and denominator. Otherwise, however, rounding errors of the order of @@ -51,6 +57,11 @@ function SimpleSSPRK33(; stage_callbacks = ()) denominator, c, stage_callbacks) end +""" + SimpleEuler(; stage_callbacks=()) + +The basic explicit Euler method. +""" function SimpleEuler(; stage_callbacks = ()) numerator_a = SVector(0.0) # a = numerator_a / denominator numerator_b = SVector(1.0) # b = numerator_b / denominator From 53a7eab21a2ca73f1a114244f41eccd01b16f126 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 21 Mar 2025 15:50:43 +0100 Subject: [PATCH 006/102] Reformulate flux calculation --- .../dgsem_tree/dg_2d_subcell_limiters.jl | 94 +++++++++---------- test/test_tree_2d_euler.jl | 16 ++-- 2 files changed, 50 insertions(+), 60 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 798a7c9f434..07d1d5f4a75 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -611,6 +611,9 @@ function calc_mortar_flux!(surface_flux_values, direction_large = 4 end + surface_flux_values[:, :, direction_small, lower_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_small, upper_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) # Call pointwise two-point numerical flux function @@ -618,19 +621,16 @@ function calc_mortar_flux!(surface_flux_values, # flux part f_lower = flux(u_lower_local, orientation, equations) # f_rr - - own_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, - direction_small, lower_element) - for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll f_large = flux(u_large_local, orientation, equations) # f_ll + own_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) other_part = 0.5f0 * (f_large + lambda_max * u_large_local) - add_to_node_vars!(surface_flux_values, - (0.5f0 * weights[j]) * other_part, - equations, dg, i, direction_small, lower_element) + local_term = 0.5f0 * weights[j] * (own_part + other_part) + + add_to_node_vars!(surface_flux_values, local_term, equations, dg, + i, direction_small, lower_element) end end # Upper element @@ -640,19 +640,16 @@ function calc_mortar_flux!(surface_flux_values, # flux part f_upper = flux(u_upper_local, orientation, equations) # f_rr - - own_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, - direction_small, upper_element) - for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll f_large = flux(u_large_local, orientation, equations) # f_ll + own_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) other_part = 0.5f0 * (f_large + lambda_max * u_large_local) - add_to_node_vars!(surface_flux_values, - (0.5f0 * weights[j]) * other_part, - equations, dg, i, direction_small, upper_element) + local_term = 0.5f0 * weights[j] * (own_part + other_part) + + add_to_node_vars!(surface_flux_values, local_term, equations, dg, + i, direction_small, upper_element) end end # Large element @@ -662,29 +659,27 @@ function calc_mortar_flux!(surface_flux_values, # flux part f_large = flux(u_large_local, orientation, equations) # f_ll - - own_part = 0.5f0 * (f_large + lambda_max * u_large_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, - direction_large, large_element) - for j in eachnode(dg) # lower u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_rr f_lower = flux(u_lower_local, orientation, equations) # f_rr + own_part = 0.5f0 * (f_large + lambda_max * u_large_local) other_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) - add_to_node_vars!(surface_flux_values, - (0.25f0 * weights[j]) * other_part, - equations, dg, i, direction_large, large_element) + local_term = 0.25f0 * weights[j] * (own_part + other_part) + + add_to_node_vars!(surface_flux_values, local_term, equations, dg, + i, direction_large, large_element) # upper u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_rr f_upper = flux(u_upper_local, orientation, equations) # f_rr other_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) - add_to_node_vars!(surface_flux_values, - (0.25f0 * weights[j]) * other_part, - equations, dg, i, direction_large, large_element) + local_term = 0.25f0 * weights[j] * (own_part + other_part) + + add_to_node_vars!(surface_flux_values, local_term, equations, dg, + i, direction_large, large_element) end end else # large_sides[mortar] == 2 -> small elements on left side @@ -698,6 +693,9 @@ function calc_mortar_flux!(surface_flux_values, direction_large = 3 end + surface_flux_values[:, :, direction_small, lower_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_small, upper_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) # Call pointwise two-point numerical flux function @@ -705,18 +703,15 @@ function calc_mortar_flux!(surface_flux_values, # flux part f_lower = flux(u_lower_local, orientation, equations) - - own_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, - direction_small, lower_element) - for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr f_large = flux(u_large_local, orientation, equations) + own_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) other_part = 0.5f0 * (f_large - lambda_max * u_large_local) - add_to_node_vars!(surface_flux_values, - (0.5f0 * weights[j]) * other_part, equations, dg, + local_term = 0.5f0 * weights[j] * (own_part + other_part) + + add_to_node_vars!(surface_flux_values, local_term, equations, dg, i, direction_small, lower_element) end end @@ -727,18 +722,15 @@ function calc_mortar_flux!(surface_flux_values, # flux part f_upper = flux(u_upper_local, orientation, equations) - - own_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, - direction_small, upper_element) - for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr f_large = flux(u_large_local, orientation, equations) + own_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) other_part = 0.5f0 * (f_large - lambda_max * u_large_local) - add_to_node_vars!(surface_flux_values, - (0.5f0 * weights[j]) * other_part, equations, dg, + local_term = 0.5f0 * weights[j] * (own_part + other_part) + + add_to_node_vars!(surface_flux_values, local_term, equations, dg, i, direction_small, upper_element) end end @@ -749,29 +741,27 @@ function calc_mortar_flux!(surface_flux_values, # flux part f_large = flux(u_large_local, orientation, equations) - - own_part = 0.5f0 * (f_large - lambda_max * u_large_local) - set_node_vars!(surface_flux_values, own_part, equations, dg, i, - direction_large, large_element) - for j in eachnode(dg) # lower u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_ll f_lower = flux(u_lower_local, orientation, equations) + own_part = 0.5f0 * (f_large - lambda_max * u_large_local) other_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) - add_to_node_vars!(surface_flux_values, - (0.25 * weights[j]) * other_part, - equations, dg, i, direction_large, large_element) + local_term = 0.25f0 * weights[j] * (own_part + other_part) + + add_to_node_vars!(surface_flux_values, local_term, equations, dg, + i, direction_large, large_element) # upper u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_ll f_upper = flux(u_upper_local, orientation, equations) other_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) - add_to_node_vars!(surface_flux_values, - (0.25 * weights[j]) * other_part, - equations, dg, i, direction_large, large_element) + local_term = 0.25f0 * weights[j] * (own_part + other_part) + + add_to_node_vars!(surface_flux_values, local_term, equations, dg, + i, direction_large, large_element) end end end diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 2183a1b1824..00a18b186c1 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -414,16 +414,16 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), l2=[ - 0.4838330697699074, - 0.20294827086694647, - 0.20294682285149304, - 0.681270812289248 + 0.4838325440094526, + 0.2029479225933254, + 0.20294681546592347, + 0.6812706682590678 ], linf=[ - 1.4714902074597642, - 0.9511042179938388, - 0.9510612394156363, - 2.9106904796599298 + 1.471481633419633, + 0.9511132534001044, + 0.9510605162063165, + 2.9106862490599004 ], tspan=(0.0, 1.0),) # Ensure that we do not have excessive memory allocations From ffe01ace9d8144952e46e551e28ae1f5483a2158 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 21 Mar 2025 16:39:52 +0100 Subject: [PATCH 007/102] Use local lambdas --- .../dgsem_tree/dg_2d_subcell_limiters.jl | 74 ++++++++++--------- test/test_tree_2d_euler.jl | 16 ++-- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 07d1d5f4a75..59fc645bc26 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -580,26 +580,6 @@ function calc_mortar_flux!(surface_flux_values, # Calculate fluxes orientation = orientations[mortar] - lambda1_max = zero(eltype(surface_flux_values)) - lambda2_max = zero(eltype(surface_flux_values)) - for i in eachnode(dg) - u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) - u_upper_local = get_node_vars(u_upper, equations, dg, i, mortar) - u_large_local = get_node_vars(u_large, equations, dg, i, mortar) - - lambda1_lower, lambda2_lower = max_abs_speeds(u_lower_local, equations) - lambda1_upper, lambda2_upper = max_abs_speeds(u_upper_local, equations) - lambda1_large, lambda2_large = max_abs_speeds(u_large_local, equations) - lambda1_max = max(lambda1_max, lambda1_lower, lambda1_upper, lambda1_large) - lambda2_max = max(lambda2_max, lambda2_lower, lambda2_upper, lambda2_large) - end - - if orientation == 1 - lambda_max = lambda1_max - else - lambda_max = lambda2_max - end - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side if orientation == 1 # L2 mortars in x-direction @@ -625,8 +605,11 @@ function calc_mortar_flux!(surface_flux_values, u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll f_large = flux(u_large_local, orientation, equations) # f_ll - own_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) - other_part = 0.5f0 * (f_large + lambda_max * u_large_local) + lambda_max_local = max_abs_speed_naive(u_large_local, u_lower_local, + orientation, equations) + + own_part = 0.5f0 * (f_lower - lambda_max_local * u_lower_local) + other_part = 0.5f0 * (f_large + lambda_max_local * u_large_local) local_term = 0.5f0 * weights[j] * (own_part + other_part) add_to_node_vars!(surface_flux_values, local_term, equations, dg, @@ -644,8 +627,11 @@ function calc_mortar_flux!(surface_flux_values, u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll f_large = flux(u_large_local, orientation, equations) # f_ll - own_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) - other_part = 0.5f0 * (f_large + lambda_max * u_large_local) + lambda_max_local = max_abs_speed_naive(u_large_local, u_upper_local, + orientation, equations) + + own_part = 0.5f0 * (f_upper - lambda_max_local * u_upper_local) + other_part = 0.5f0 * (f_large + lambda_max_local * u_large_local) local_term = 0.5f0 * weights[j] * (own_part + other_part) add_to_node_vars!(surface_flux_values, local_term, equations, dg, @@ -664,8 +650,11 @@ function calc_mortar_flux!(surface_flux_values, u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_rr f_lower = flux(u_lower_local, orientation, equations) # f_rr - own_part = 0.5f0 * (f_large + lambda_max * u_large_local) - other_part = 0.5f0 * (f_lower - lambda_max * u_lower_local) + lambda_max_local = max_abs_speed_naive(u_large_local, u_lower_local, + orientation, equations) + + own_part = 0.5f0 * (f_large + lambda_max_local * u_large_local) + other_part = 0.5f0 * (f_lower - lambda_max_local * u_lower_local) local_term = 0.25f0 * weights[j] * (own_part + other_part) add_to_node_vars!(surface_flux_values, local_term, equations, dg, @@ -675,7 +664,11 @@ function calc_mortar_flux!(surface_flux_values, u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_rr f_upper = flux(u_upper_local, orientation, equations) # f_rr - other_part = 0.5f0 * (f_upper - lambda_max * u_upper_local) + lambda_max_local = max_abs_speed_naive(u_large_local, u_upper_local, + orientation, equations) + + own_part = 0.5f0 * (f_large + lambda_max_local * u_large_local) + other_part = 0.5f0 * (f_upper - lambda_max_local * u_upper_local) local_term = 0.25f0 * weights[j] * (own_part + other_part) add_to_node_vars!(surface_flux_values, local_term, equations, dg, @@ -707,8 +700,11 @@ function calc_mortar_flux!(surface_flux_values, u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr f_large = flux(u_large_local, orientation, equations) - own_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) - other_part = 0.5f0 * (f_large - lambda_max * u_large_local) + lambda_max_local = max_abs_speed_naive(u_lower_local, u_large_local, + orientation, equations) + + own_part = 0.5f0 * (f_lower + lambda_max_local * u_lower_local) + other_part = 0.5f0 * (f_large - lambda_max_local * u_large_local) local_term = 0.5f0 * weights[j] * (own_part + other_part) add_to_node_vars!(surface_flux_values, local_term, equations, dg, @@ -726,8 +722,11 @@ function calc_mortar_flux!(surface_flux_values, u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr f_large = flux(u_large_local, orientation, equations) - own_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) - other_part = 0.5f0 * (f_large - lambda_max * u_large_local) + lambda_max_local = max_abs_speed_naive(u_upper_local, u_large_local, + orientation, equations) + + own_part = 0.5f0 * (f_upper + lambda_max_local * u_upper_local) + other_part = 0.5f0 * (f_large - lambda_max_local * u_large_local) local_term = 0.5f0 * weights[j] * (own_part + other_part) add_to_node_vars!(surface_flux_values, local_term, equations, dg, @@ -746,8 +745,11 @@ function calc_mortar_flux!(surface_flux_values, u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_ll f_lower = flux(u_lower_local, orientation, equations) - own_part = 0.5f0 * (f_large - lambda_max * u_large_local) - other_part = 0.5f0 * (f_lower + lambda_max * u_lower_local) + lambda_max_local = max_abs_speed_naive(u_lower_local, u_large_local, + orientation, equations) + + own_part = 0.5f0 * (f_large - lambda_max_local * u_large_local) + other_part = 0.5f0 * (f_lower + lambda_max_local * u_lower_local) local_term = 0.25f0 * weights[j] * (own_part + other_part) add_to_node_vars!(surface_flux_values, local_term, equations, dg, @@ -757,7 +759,11 @@ function calc_mortar_flux!(surface_flux_values, u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_ll f_upper = flux(u_upper_local, orientation, equations) - other_part = 0.5f0 * (f_upper + lambda_max * u_upper_local) + lambda_max_local = max_abs_speed_naive(u_upper_local, u_large_local, + orientation, equations) + + own_part = 0.5f0 * (f_large - lambda_max_local * u_large_local) + other_part = 0.5f0 * (f_upper + lambda_max_local * u_upper_local) local_term = 0.25f0 * weights[j] * (own_part + other_part) add_to_node_vars!(surface_flux_values, local_term, equations, dg, diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 00a18b186c1..6317ce3ab69 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -414,16 +414,16 @@ end @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), l2=[ - 0.4838325440094526, - 0.2029479225933254, - 0.20294681546592347, - 0.6812706682590678 + 0.48372925569187175, + 0.2029506811452194, + 0.20294966678670903, + 0.681359590384874 ], linf=[ - 1.471481633419633, - 0.9511132534001044, - 0.9510605162063165, - 2.9106862490599004 + 1.4741183426277105, + 0.9512566363281697, + 0.951208106377221, + 2.9073697783624435 ], tspan=(0.0, 1.0),) # Ensure that we do not have excessive memory allocations From 2e0ac848cff904c80377ac06ea4dde854467b30f Mon Sep 17 00:00:00 2001 From: bennibolm Date: Sun, 23 Mar 2025 12:00:21 +0100 Subject: [PATCH 008/102] Use surface_flux --- .../dgsem_tree/dg_2d_subcell_limiters.jl | 128 +++++------------- 1 file changed, 32 insertions(+), 96 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 59fc645bc26..9e182ab5ff6 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -596,83 +596,51 @@ function calc_mortar_flux!(surface_flux_values, surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) - # Call pointwise two-point numerical flux function u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) # u_rr - - # flux part - f_lower = flux(u_lower_local, orientation, equations) # f_rr for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll - f_large = flux(u_large_local, orientation, equations) # f_ll - - lambda_max_local = max_abs_speed_naive(u_large_local, u_lower_local, - orientation, equations) - own_part = 0.5f0 * (f_lower - lambda_max_local * u_lower_local) - other_part = 0.5f0 * (f_large + lambda_max_local * u_large_local) - local_term = 0.5f0 * weights[j] * (own_part + other_part) + flux = surface_flux(u_large_local, u_lower_local, orientation, + equations) - add_to_node_vars!(surface_flux_values, local_term, equations, dg, - i, direction_small, lower_element) + add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j] * flux, + equations, dg, i, direction_small, lower_element) end end # Upper element for i in eachnode(dg) - # Call pointwise two-point numerical flux function u_upper_local = get_node_vars(u_upper, equations, dg, i, mortar) # u_rr - - # flux part - f_upper = flux(u_upper_local, orientation, equations) # f_rr for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll - f_large = flux(u_large_local, orientation, equations) # f_ll - - lambda_max_local = max_abs_speed_naive(u_large_local, u_upper_local, - orientation, equations) - own_part = 0.5f0 * (f_upper - lambda_max_local * u_upper_local) - other_part = 0.5f0 * (f_large + lambda_max_local * u_large_local) - local_term = 0.5f0 * weights[j] * (own_part + other_part) + flux = surface_flux(u_large_local, u_upper_local, orientation, + equations) - add_to_node_vars!(surface_flux_values, local_term, equations, dg, - i, direction_small, upper_element) + add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j] * flux, + equations, dg, i, direction_small, upper_element) end end # Large element for i in eachnode(dg) - # Call pointwise two-point numerical flux function u_large_local = get_node_vars(u_large, equations, dg, i, mortar) # u_ll - - # flux part - f_large = flux(u_large_local, orientation, equations) # f_ll for j in eachnode(dg) # lower u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_rr - f_lower = flux(u_lower_local, orientation, equations) # f_rr - - lambda_max_local = max_abs_speed_naive(u_large_local, u_lower_local, - orientation, equations) - own_part = 0.5f0 * (f_large + lambda_max_local * u_large_local) - other_part = 0.5f0 * (f_lower - lambda_max_local * u_lower_local) - local_term = 0.25f0 * weights[j] * (own_part + other_part) + flux = surface_flux(u_large_local, u_lower_local, orientation, + equations) - add_to_node_vars!(surface_flux_values, local_term, equations, dg, - i, direction_large, large_element) + add_to_node_vars!(surface_flux_values, 0.25f0 * weights[j] * flux, + equations, dg, i, direction_large, large_element) # upper u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_rr - f_upper = flux(u_upper_local, orientation, equations) # f_rr - - lambda_max_local = max_abs_speed_naive(u_large_local, u_upper_local, - orientation, equations) - own_part = 0.5f0 * (f_large + lambda_max_local * u_large_local) - other_part = 0.5f0 * (f_upper - lambda_max_local * u_upper_local) - local_term = 0.25f0 * weights[j] * (own_part + other_part) + flux = surface_flux(u_large_local, u_upper_local, orientation, + equations) - add_to_node_vars!(surface_flux_values, local_term, equations, dg, - i, direction_large, large_element) + add_to_node_vars!(surface_flux_values, 0.25f0 * weights[j] * flux, + equations, dg, i, direction_large, large_element) end end else # large_sides[mortar] == 2 -> small elements on left side @@ -691,83 +659,51 @@ function calc_mortar_flux!(surface_flux_values, surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) - # Call pointwise two-point numerical flux function u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) # u_ll - - # flux part - f_lower = flux(u_lower_local, orientation, equations) for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr - f_large = flux(u_large_local, orientation, equations) - - lambda_max_local = max_abs_speed_naive(u_lower_local, u_large_local, - orientation, equations) - own_part = 0.5f0 * (f_lower + lambda_max_local * u_lower_local) - other_part = 0.5f0 * (f_large - lambda_max_local * u_large_local) - local_term = 0.5f0 * weights[j] * (own_part + other_part) + flux = surface_flux(u_lower_local, u_large_local, orientation, + equations) - add_to_node_vars!(surface_flux_values, local_term, equations, dg, - i, direction_small, lower_element) + add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j] * flux, + equations, dg, i, direction_small, lower_element) end end # Upper element for i in eachnode(dg) - # Call pointwise two-point numerical flux function u_upper_local = get_node_vars(u_upper, equations, dg, i, mortar) # u_ll - - # flux part - f_upper = flux(u_upper_local, orientation, equations) for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr - f_large = flux(u_large_local, orientation, equations) - - lambda_max_local = max_abs_speed_naive(u_upper_local, u_large_local, - orientation, equations) - own_part = 0.5f0 * (f_upper + lambda_max_local * u_upper_local) - other_part = 0.5f0 * (f_large - lambda_max_local * u_large_local) - local_term = 0.5f0 * weights[j] * (own_part + other_part) + flux = surface_flux(u_upper_local, u_large_local, orientation, + equations) - add_to_node_vars!(surface_flux_values, local_term, equations, dg, - i, direction_small, upper_element) + add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j] * flux, + equations, dg, i, direction_small, upper_element) end end # Large element for i in eachnode(dg) - # Call pointwise two-point numerical flux function u_large_local = get_node_vars(u_large, equations, dg, i, mortar) # u_rr - - # flux part - f_large = flux(u_large_local, orientation, equations) for j in eachnode(dg) # lower u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_ll - f_lower = flux(u_lower_local, orientation, equations) - - lambda_max_local = max_abs_speed_naive(u_lower_local, u_large_local, - orientation, equations) - own_part = 0.5f0 * (f_large - lambda_max_local * u_large_local) - other_part = 0.5f0 * (f_lower + lambda_max_local * u_lower_local) - local_term = 0.25f0 * weights[j] * (own_part + other_part) + flux = surface_flux(u_lower_local, u_large_local, orientation, + equations) - add_to_node_vars!(surface_flux_values, local_term, equations, dg, - i, direction_large, large_element) + add_to_node_vars!(surface_flux_values, 0.25f0 * weights[j] * flux, + equations, dg, i, direction_large, large_element) # upper u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_ll - f_upper = flux(u_upper_local, orientation, equations) - - lambda_max_local = max_abs_speed_naive(u_upper_local, u_large_local, - orientation, equations) - own_part = 0.5f0 * (f_large - lambda_max_local * u_large_local) - other_part = 0.5f0 * (f_upper + lambda_max_local * u_upper_local) - local_term = 0.25f0 * weights[j] * (own_part + other_part) + flux = surface_flux(u_upper_local, u_large_local, orientation, + equations) - add_to_node_vars!(surface_flux_values, local_term, equations, dg, - i, direction_large, large_element) + add_to_node_vars!(surface_flux_values, 0.25f0 * weights[j] * flux, + equations, dg, i, direction_large, large_element) end end end From 38e669686fc8d58ca3b10ab023bdb41320263289 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 30 Apr 2025 11:26:05 +0200 Subject: [PATCH 009/102] Revise implementation --- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 38 +- ...elixir_euler_convergence_amr_sc_subcell.jl | 95 +++++ src/solvers/dgsem/basis_lobatto_legendre.jl | 69 +++- src/solvers/dgsem/l2projection.jl | 74 ++++ src/solvers/dgsem_tree/containers_2d.jl | 12 +- src/solvers/dgsem_tree/dg_2d.jl | 116 +++++- .../dgsem_tree/dg_2d_subcell_limiters.jl | 363 +++++++++++++++--- test/test_tree_2d_euler.jl | 238 +++++++++++- 8 files changed, 909 insertions(+), 96 deletions(-) create mode 100644 examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 4c7047b8caa..17804ea62bb 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -28,8 +28,8 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation rho = r > 0.5f0 ? one(RealT) : RealT(1.1691) v1 = r > 0.5f0 ? zero(RealT) : RealT(0.1882) * cos_phi v2 = r > 0.5f0 ? zero(RealT) : RealT(0.1882) * sin_phi - # p = r > 0.5f0 ? RealT(1.0E-3) : RealT(1.245) - p = r > 0.5f0 ? RealT(1.0E-1) : RealT(1.245) + p = r > 0.5f0 ? RealT(1.0E-3) : RealT(1.245) + # p = r > 0.5f0 ? RealT(1.0E-1) : RealT(1.245) return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -51,14 +51,17 @@ limiter_idp = SubcellLimiterIDP(equations, basis; volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = Trixi.MortarIDP(basis) +mortar = Trixi.MortarIDP(basis, alternative = true, local_factor = true, first_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) +refinement_patches = ((type = "box", coordinates_min = (0.0, -1.0), + coordinates_max = (1.0, 1.0)),) mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level = 4, n_cells_max = 10_000, + refinement_patches = refinement_patches, periodicity = true) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -72,32 +75,33 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,)) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1, +save_solution = SaveSolutionCallback(interval = 100, save_initial_solution = true, save_final_solution = true, solution_variables = cons2prim) -amr_indicator = IndicatorMax(semi, variable = first) +# amr_indicator = IndicatorMax(semi, variable = first) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - med_level = 5, med_threshold = 1.01, - max_level = 6, max_threshold = 1.1) +# amr_controller = ControllerThreeLevel(semi, amr_indicator, +# base_level = 4, +# med_level = 5, med_threshold = 1.01, +# max_level = 6, max_threshold = 1.1) -amr_callback = AMRCallback(semi, amr_controller, - interval = 10, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = false) +# amr_callback = AMRCallback(semi, amr_controller, +# interval = 10, +# adapt_initial_condition = true, +# adapt_initial_condition_only_refine = false) stepsize_callback = StepsizeCallback(cfl = 0.5) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - amr_callback, + # amr_callback, save_solution, stepsize_callback) @@ -107,8 +111,8 @@ callbacks = CallbackSet(summary_callback, stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = false)) sol = Trixi.solve(ode, - Trixi.SimpleEuler(stage_callbacks = stage_callbacks); - # Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + # Trixi.SimpleEuler(stage_callbacks = stage_callbacks); + Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback ode_default_options()..., callback = callbacks); diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl new file mode 100644 index 00000000000..04c0a8c7046 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -0,0 +1,95 @@ +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +initial_condition = initial_condition_convergence_test +# initial_condition = Trixi.initial_condition_density_wave_highdensity +# initial_condition = Trixi.initial_condition_density_wave +# initial_condition = initial_condition_constant + +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) +limiter_idp = SubcellLimiterIDP(equations, basis; + positivity_variables_cons = ["rho"], + # positivity_correction_factor = 0.5, + positivity_variables_nonlinear = [pressure], + # local_twosided_variables_cons = ["rho"], + # local_onesided_variables_nonlinear = [(Trixi.entropy_math, + # max)] + ) +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) + +mortar = Trixi.MortarIDP(basis; alternative = true, local_factor = true, first_order = true) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +refinement_patches = ((type = "box", coordinates_min = (0.0, -0.5), + coordinates_max = (0.5, 0.5)),) +initial_refinement_level = 4 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = initial_refinement_level, + n_cells_max = 10_000, + refinement_patches = refinement_patches, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +# amr_indicator = IndicatorMax(semi, variable = first) + +# amr_controller = ControllerThreeLevel(semi, amr_indicator, +# base_level = initial_refinement_level, +# med_level = initial_refinement_level + 1, med_threshold = 2.0, +# max_level = initial_refinement_level + 2, max_threshold = 2.05) + +# amr_callback = AMRCallback(semi, amr_controller, +# interval = 1, +# adapt_initial_condition = true, +# adapt_initial_condition_only_refine = false) + +stepsize_callback = StepsizeCallback(cfl = 0.8) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + # amr_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = true)) + +sol = Trixi.solve(ode, + # Trixi.SimpleEuler(stage_callbacks = stage_callbacks); + Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., + callback = callbacks); diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index e65434a784b..dc7793e9ff9 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -145,14 +145,51 @@ end left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights) right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) -struct LobattoLegendreMortarIDP{RealT <: Real, NNODES} <: AbstractMortarL2{RealT} +struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, + ForwardMatrix <: AbstractMatrix{RealT}, + ReverseMatrix <: AbstractMatrix{RealT}} <: + AbstractMortarL2{RealT} + alternative::Bool + local_factor::Bool + forward_upper::ForwardMatrix + forward_lower::ForwardMatrix + reverse_upper::ReverseMatrix + reverse_lower::ReverseMatrix + local_mortar_weights::Matrix{RealT} + forward_upper_low_order::ForwardMatrix + forward_lower_low_order::ForwardMatrix + reverse_upper_low_order::ReverseMatrix + reverse_lower_low_order::ReverseMatrix end -function MortarIDP(basis::LobattoLegendreBasis) +function MortarIDP(basis::LobattoLegendreBasis; local_factor = false, + alternative = false, first_order = true) RealT = real(basis) nnodes_ = nnodes(basis) - LobattoLegendreMortarIDP{RealT, nnodes_}() + forward_upper = calc_forward_upper(nnodes_, RealT) + forward_lower = calc_forward_lower(nnodes_, RealT) + reverse_upper = calc_reverse_upper(nnodes_, Val(:gauss), RealT) + reverse_lower = calc_reverse_lower(nnodes_, Val(:gauss), RealT) + + local_mortar_weights = calc_mortar_weights(basis, RealT; first_order = first_order) + + forward_upper_low_order = calc_forward_upper_low_order(nnodes_, RealT) + forward_lower_low_order = calc_forward_lower_low_order(nnodes_, RealT) + reverse_upper_low_order = calc_reverse_upper_low_order(nnodes_, Val(:gauss_lobatto), + RealT) + reverse_lower_low_order = calc_reverse_lower_low_order(nnodes_, Val(:gauss_lobatto), + RealT) + + LobattoLegendreMortarIDP{RealT, nnodes_, typeof(forward_upper), + typeof(reverse_upper)}(alternative, local_factor, + forward_upper, forward_lower, + reverse_upper, reverse_lower, + local_mortar_weights, + forward_upper_low_order, + forward_lower_low_order, + reverse_upper_low_order, + reverse_lower_low_order) end function Base.show(io::IO, mortar::LobattoLegendreMortarIDP) @@ -570,6 +607,32 @@ function lagrange_interpolating_polynomials(x, nodes, wbary) return polynomials end +function low_order_interpolating_polynomials(x, nodes) + n_nodes = length(nodes) + polynomials = zeros(eltype(nodes), n_nodes) + + for i in 1:n_nodes + # Avoid division by zero when `x` is close to node by using + # the Kronecker-delta property at nodes + # of the Lagrange interpolation polynomials. + if isapprox(x, nodes[i], rtol = eps(x)) + polynomials[i] = 1 + return polynomials + end + end + + for i in 1:(n_nodes - 1) + if nodes[i] < x < nodes[i + 1] + diff = nodes[i + 1] - nodes[i] + polynomials[i] = (nodes[i + 1] - x) / diff + polynomials[i + 1] = (x - nodes[i]) / diff + break + end + end + + return polynomials +end + """ gauss_lobatto_nodes_weights(n_nodes::Integer, RealT = Float64) diff --git a/src/solvers/dgsem/l2projection.jl b/src/solvers/dgsem/l2projection.jl index db9fe5ac0ad..11f3626831c 100644 --- a/src/solvers/dgsem/l2projection.jl +++ b/src/solvers/dgsem/l2projection.jl @@ -40,6 +40,20 @@ function calc_forward_upper(n_nodes, RealT = Float64) return operator end +function calc_forward_upper_low_order(n_nodes, RealT) + nodes, _ = gauss_lobatto_nodes_weights(n_nodes, RealT) + forward_upper = zeros(RealT, n_nodes, n_nodes) + + for j in 1:n_nodes + poly = low_order_interpolating_polynomials(0.5f0 * (nodes[j] + 1), nodes) + for i in 1:n_nodes + forward_upper[j, i] = poly[i] + end + end + + return forward_upper +end + # Calculate forward projection matrix for discrete L2 projection from large to lower # # Note: This is actually an interpolation. @@ -60,6 +74,20 @@ function calc_forward_lower(n_nodes, RealT = Float64) return operator end +function calc_forward_lower_low_order(n_nodes, RealT) + nodes, _ = gauss_lobatto_nodes_weights(n_nodes, RealT) + forward_lower = zeros(RealT, n_nodes, n_nodes) + + for j in 1:n_nodes + poly = low_order_interpolating_polynomials(0.5f0 * (nodes[j] - 1), nodes) + for i in 1:n_nodes + forward_lower[j, i] = poly[i] + end + end + + return forward_lower +end + # Calculate reverse projection matrix for discrete L2 projection from upper to large (Gauss version) # # Note: To not make the L2 projection exact, first convert to Gauss nodes, @@ -133,6 +161,29 @@ function calc_reverse_upper(n_nodes, ::Val{:gauss_lobatto}, RealT = Float64) return operator end +function calc_reverse_upper_low_order(n_nodes, ::Val{:gauss_lobatto}, RealT = Float64) + # Calculate nodes, weights, and barycentric weights + nodes, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) + + # Calculate projection matrix (actually: discrete L2 projection with errors) + operator = zeros(RealT, n_nodes, n_nodes) + for j in 1:n_nodes + poly = low_order_interpolating_polynomials(nodes[j], 0.5f0 * (nodes .+ 1)) + if !iseven(n_nodes) + poly[1] *= 0.5f0 + end + for i in 1:n_nodes + operator[j, i] = poly[i] + end + # poly = low_order_interpolating_polynomials(0.5f0 * (nodes[j] + 1), nodes) + # for i in 1:n_nodes + # operator[i, j] = 0.5f0 * poly[i] * weights[j] / weights[i] + # end + end + + return operator +end + # Calculate reverse projection matrix for discrete L2 projection from lower to large (Gauss-Lobatto # version) function calc_reverse_lower(n_nodes, ::Val{:gauss_lobatto}, RealT = Float64) @@ -151,4 +202,27 @@ function calc_reverse_lower(n_nodes, ::Val{:gauss_lobatto}, RealT = Float64) return operator end + +function calc_reverse_lower_low_order(n_nodes, ::Val{:gauss_lobatto}, RealT = Float64) + # Calculate nodes, weights, and barycentric weights + nodes, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) + + # Calculate projection matrix (actually: discrete L2 projection with errors) + operator = zeros(RealT, n_nodes, n_nodes) + for j in 1:n_nodes + poly = low_order_interpolating_polynomials(nodes[j], 0.5f0 * (nodes .- 1)) + if !iseven(n_nodes) + poly[end] *= 0.5f0 + end + for i in 1:n_nodes + operator[j, i] = poly[i] + end + # poly = low_order_interpolating_polynomials(0.5f0 * (nodes[j] - 1), nodes) + # for i in 1:n_nodes + # operator[i, j] = 0.5f0 * poly[i] * weights[j] / weights[i] + # end + end + + return operator +end end # @muladd diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index 6744f62bc9f..9260da1b198 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -726,11 +726,17 @@ end # Create mortar container and initialize mortar data in `elements`. function init_mortars(cell_ids, mesh::TreeMesh2D, elements::ElementContainer2D, - ::LobattoLegendreMortarIDP) + mortar::LobattoLegendreMortarIDP) # Initialize containers n_mortars = count_required_mortars(mesh, cell_ids) - mortars = IDPMortarContainer2D{eltype(elements)}(n_mortars, nvariables(elements), - nnodes(elements)) + if mortar.alternative + mortars = IDPMortarContainer2D{eltype(elements)}(n_mortars, + nvariables(elements), + nnodes(elements)) + else + mortars = L2MortarContainer2D{eltype(elements)}(n_mortars, nvariables(elements), + nnodes(elements)) + end # Connect elements with mortars init_mortars!(mortars, elements, mesh) diff --git a/src/solvers/dgsem_tree/dg_2d.jl b/src/solvers/dgsem_tree/dg_2d.jl index 84c789099ce..6664c8c6f60 100644 --- a/src/solvers/dgsem_tree/dg_2d.jl +++ b/src/solvers/dgsem_tree/dg_2d.jl @@ -91,7 +91,9 @@ end # and called from the basic `create_cache` method at the top. function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, - equations, mortar_l2::LobattoLegendreMortarL2, uEltype) + equations, + mortar_l2::Union{LobattoLegendreMortarL2, + LobattoLegendreMortarIDP}, uEltype) # TODO: Taal performance using different types MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)}, uEltype, 2, nvariables(equations) * nnodes(mortar_l2)} @@ -150,16 +152,32 @@ function rhs!(du, u, t, end # Prolong solution to mortars - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u, mesh, equations, - dg.mortar, dg) + if dg.mortar isa LobattoLegendreMortarIDP && dg.mortar.alternative + @trixi_timeit timer() "prolong2mortars_alternative" begin + prolong2mortars_alternative!(cache, u, mesh, equations, + dg.mortar, dg) + end + else + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!(cache, u, mesh, equations, + dg.mortar, dg) + end end # Calculate mortar fluxes - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.mortar, dg.surface_integral, dg, cache) + if dg.mortar isa LobattoLegendreMortarIDP && dg.mortar.alternative + @trixi_timeit timer() "mortar flux alternative" begin + calc_mortar_flux_alternative!(cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), + equations, + dg.mortar, dg.surface_integral, dg, cache) + end + else + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!(cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.mortar, dg.surface_integral, dg, cache) + end end # Calculate surface integrals @@ -795,7 +813,8 @@ end function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarL2, + mortar_l2::Union{LobattoLegendreMortarL2, + LobattoLegendreMortarIDP}, dg::DGSEM) @threaded for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] @@ -896,7 +915,8 @@ end function calc_mortar_flux!(surface_flux_values, mesh::TreeMesh{2}, nonconservative_terms::False, equations, - mortar_l2::LobattoLegendreMortarL2, + mortar_l2::Union{LobattoLegendreMortarL2, + LobattoLegendreMortarIDP}, surface_integral, dg::DG, cache) @unpack surface_flux = surface_integral @unpack u_lower, u_upper, orientations = cache.mortars @@ -1127,6 +1147,82 @@ end return nothing end +@inline function mortar_fluxes_to_elements!(surface_flux_values, + mesh::TreeMesh{2}, equations, + mortar_l2::LobattoLegendreMortarIDP, + dg::DGSEM, cache, + mortar, fstar_primary_upper, + fstar_primary_lower, + fstar_secondary_upper, + fstar_secondary_lower) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Copy flux small to small + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + direction = 1 + else + # L2 mortars in y-direction + direction = 3 + end + else # large_sides[mortar] == 2 -> small elements on left side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + direction = 2 + else + # L2 mortars in y-direction + direction = 4 + end + end + surface_flux_values[:, :, direction, upper_element] .= fstar_primary_upper + surface_flux_values[:, :, direction, lower_element] .= fstar_primary_lower + + # Project small fluxes to large element + if cache.mortars.large_sides[mortar] == 1 # -> large element on left side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + direction = 2 + else + # L2 mortars in y-direction + direction = 4 + end + else # large_sides[mortar] == 2 -> large element on right side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + direction = 1 + else + # L2 mortars in y-direction + direction = 3 + end + end + + # TODO: Taal performance + # for v in eachvariable(equations) + # # The code below is semantically equivalent to + # # surface_flux_values[v, :, direction, large_element] .= + # # (mortar_l2.reverse_upper * fstar_upper[v, :] + mortar_l2.reverse_lower * fstar_lower[v, :]) + # # but faster and does not allocate. + # # Note that `true * some_float == some_float` in Julia, i.e. `true` acts as + # # a universal `one`. Hence, the second `mul!` means "add the matrix-vector + # # product to the current value of the destination". + # @views mul!(surface_flux_values[v, :, direction, large_element], + # mortar_l2.reverse_upper, fstar_upper[v, :]) + # @views mul!(surface_flux_values[v, :, direction, large_element], + # mortar_l2.reverse_lower, fstar_lower[v, :], true, true) + # end + # The code above could be replaced by the following code. However, the relative efficiency + # depends on the types of fstar_upper/fstar_lower and dg.l2mortar_reverse_upper. + # Using StaticArrays for both makes the code above faster for common test cases. + multiply_dimensionwise!(view(surface_flux_values, :, :, direction, large_element), + mortar_l2.reverse_upper_low_order, fstar_secondary_upper, + mortar_l2.reverse_lower_low_order, fstar_secondary_lower) + + return nothing +end + function calc_surface_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}}, diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 9e182ab5ff6..851b66098d6 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -78,6 +78,182 @@ function create_cache(mesh::TreeMesh{2}, fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) end +function calc_mortar_weights(basis, RealT; first_order = true) + n_nodes = nnodes(basis) + # Saving the sum over row/column in entries with last index. + weights = zeros(RealT, n_nodes + 1, 2 * n_nodes + 1) + + if first_order + calc_mortar_weights_first_order!(weights, n_nodes, RealT) + else + calc_mortar_weights_second_order!(weights, basis) + end + + for i in 1:n_nodes + for j in 1:n_nodes + # Row + weights[i, end] += weights[i, j] + weights[i, end] += weights[i, j + n_nodes] + # Columns + weights[end, i] += weights[j, i] + weights[end, i + n_nodes] += weights[j, i + n_nodes] + end + end + + return weights +end + +function calc_mortar_weights_first_order!(mortar_weights, n_nodes, RealT) + _, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) + cum_weights = [zero(RealT); cumsum(weights)] .- 1.0 + + cum_weights_lower = 0.5f0 * cum_weights .- 0.5f0 + cum_weights_upper = cum_weights_lower .+ 1.0 + + for i in 1:n_nodes + for j in 1:n_nodes + # basis function of left element + interval_left = max(cum_weights[i], cum_weights_lower[j]) + interval_right = min(cum_weights[i + 1], cum_weights_lower[j + 1]) + mortar_weights[i, j] = max(zero(RealT), interval_right - interval_left) + + # basis function of right element + interval_left = max(cum_weights[i], cum_weights_upper[j]) + interval_right = min(cum_weights[i + 1], cum_weights_upper[j + 1]) + mortar_weights[i, n_nodes + j] = max(zero(RealT), + interval_right - interval_left) + end + end + + return mortar_weights +end + +function calc_mortar_weights_second_order!(weights, basis) + (; nodes) = basis + n_nodes = nnodes(basis) + nodes_lower = 0.5f0 * nodes .- 0.5f0 + nodes_upper = nodes_lower .+ 1.0f0 + + function_product(xi, ii, i, jj, j, nodes_u_l) = (xi^3 / 3 - + xi^2 / 2 * + (nodes[ii] + nodes_u_l[jj]) + + nodes[ii] * nodes_u_l[jj] * xi) / + ((nodes[ii] - nodes[i]) * + (nodes_u_l[jj] - nodes_u_l[j])) + for i in eachnode(basis) + for j in eachnode(basis) + # left part of large element function + if i > 1 + # left part of small element function + if j > 1 + # basis function of left element + interval_left = max(nodes[i - 1], nodes_lower[j - 1]) + interval_right = min(nodes[i], nodes_lower[j]) + if interval_left < interval_right + weights[i, j] += function_product(interval_right, + i - 1, i, j - 1, j, + nodes_lower) - + function_product(interval_left, + i - 1, i, j - 1, j, + nodes_lower) + end + # basis function of right element + interval_left = max(nodes[i - 1], nodes_upper[j - 1]) + interval_right = min(nodes[i], nodes_upper[j]) + if interval_left < interval_right + weights[i, n_nodes + j] += function_product(interval_right, + i - 1, i, j - 1, j, + nodes_upper) - + function_product(interval_left, + i - 1, i, j - 1, j, + nodes_upper) + end + end + # right part of small element function + if j < n_nodes + # basis function of left element + interval_left = max(nodes[i - 1], nodes_lower[j]) + interval_right = min(nodes[i], nodes_lower[j + 1]) + if interval_left < interval_right + weights[i, j] += function_product(interval_right, + i - 1, i, j + 1, j, + nodes_lower) - + function_product(interval_left, + i - 1, i, j + 1, j, + nodes_lower) + end + # basis function of right element + interval_left = max(nodes[i - 1], nodes_upper[j]) + interval_right = min(nodes[i], nodes_upper[j + 1]) + if interval_left < interval_right + weights[i, n_nodes + j] += function_product(interval_right, + i - 1, i, j + 1, j, + nodes_upper) - + function_product(interval_left, + i - 1, i, j + 1, j, + nodes_upper) + end + end + end + # right part of large element function + if i < n_nodes + # left part of small element function + if j > 1 + # basis function of left element + interval_left = max(nodes[i], nodes_lower[j - 1]) + interval_right = min(nodes[i + 1], nodes_lower[j]) + if interval_left < interval_right + weights[i, j] += function_product(interval_right, + i + 1, i, j - 1, j, + nodes_lower) - + function_product(interval_left, + i + 1, i, j - 1, j, + nodes_lower) + end + # basis function of right element + interval_left = max(nodes[i], nodes_upper[j - 1]) + interval_right = min(nodes[i + 1], nodes_upper[j]) + if interval_left < interval_right + weights[i, n_nodes + j] += function_product(interval_right, + i + 1, i, j - 1, j, + nodes_upper) - + function_product(interval_left, + i + 1, i, j - 1, j, + nodes_upper) + end + end + # right part of small element function + if j < n_nodes + # basis function of left element + interval_left = max(nodes[i], nodes_lower[j]) + interval_right = min(nodes[i + 1], nodes_lower[j + 1]) + if interval_left < interval_right + weights[i, j] += function_product(interval_right, + i + 1, i, j + 1, j, + nodes_lower) - + function_product(interval_left, + i + 1, i, j + 1, j, + nodes_lower) + end + # basis function of right element + interval_left = max(nodes[i], nodes_upper[j]) + interval_right = min(nodes[i + 1], nodes_upper[j + 1]) + if interval_left < interval_right + weights[i, n_nodes + j] += function_product(interval_right, + i + 1, i, j + 1, j, + nodes_upper) - + function_product(interval_left, + i + 1, i, j + 1, j, + nodes_upper) + end + end + end + end + end + + return weights +end + function calc_volume_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, @@ -493,10 +669,10 @@ end return nothing end -function prolong2mortars!(cache, u, - mesh::TreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarIDP, - dg::DGSEM) +function prolong2mortars_alternative!(cache, u, + mesh::TreeMesh{2}, equations, + mortar_l2::LobattoLegendreMortarIDP, + dg::DGSEM) @threaded for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] @@ -555,28 +731,23 @@ function prolong2mortars!(cache, u, return nothing end -function calc_mortar_flux!(surface_flux_values, - mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - mortar_idp::LobattoLegendreMortarIDP, - surface_integral, dg::DG, cache) +function calc_mortar_flux_alternative!(surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + mortar_idp::LobattoLegendreMortarIDP, + surface_integral, dg::DG, cache) @unpack surface_flux = surface_integral @unpack u_lower, u_upper, u_large, orientations = cache.mortars @unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded, fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache (; weights) = dg.basis + (; local_mortar_weights, local_factor) = mortar_idp for mortar in eachmortar(dg, cache) #=@threaded =# large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] lower_element = cache.mortars.neighbor_ids[1, mortar] - # Choose thread-specific pre-allocated container - # fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()] - # fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()] - # fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()] - # fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()] - # Calculate fluxes orientation = orientations[mortar] @@ -596,6 +767,7 @@ function calc_mortar_flux!(surface_flux_values, surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) + # TODO: Efficiency: Calculate flux only once. u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) # u_rr for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll @@ -603,8 +775,20 @@ function calc_mortar_flux!(surface_flux_values, flux = surface_flux(u_large_local, u_lower_local, orientation, equations) - add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j] * flux, - equations, dg, i, direction_small, lower_element) + if local_factor + factor = local_mortar_weights[j, i] + if !isapprox(factor, zero(typeof(factor))) + factor = factor / local_mortar_weights[end, i] + multiply_add_to_node_vars!(surface_flux_values, factor, + flux, equations, dg, i, + direction_small, lower_element) + end + else + multiply_add_to_node_vars!(surface_flux_values, + 0.5f0 * weights[j], flux, + equations, dg, i, direction_small, + lower_element) + end end end # Upper element @@ -616,8 +800,20 @@ function calc_mortar_flux!(surface_flux_values, flux = surface_flux(u_large_local, u_upper_local, orientation, equations) - add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j] * flux, - equations, dg, i, direction_small, upper_element) + if local_factor + factor = local_mortar_weights[j, i + nnodes(dg)] + if !isapprox(factor, zero(typeof(factor))) + factor = factor / local_mortar_weights[end, i + nnodes(dg)] + multiply_add_to_node_vars!(surface_flux_values, factor, + flux, equations, dg, i, + direction_small, upper_element) + end + else + multiply_add_to_node_vars!(surface_flux_values, + 0.5f0 * weights[j], flux, + equations, dg, i, direction_small, + upper_element) + end end end # Large element @@ -630,8 +826,20 @@ function calc_mortar_flux!(surface_flux_values, flux = surface_flux(u_large_local, u_lower_local, orientation, equations) - add_to_node_vars!(surface_flux_values, 0.25f0 * weights[j] * flux, - equations, dg, i, direction_large, large_element) + if local_factor + factor = local_mortar_weights[i, j] + if !isapprox(factor, zero(typeof(factor))) + factor = factor / local_mortar_weights[i, end] + multiply_add_to_node_vars!(surface_flux_values, factor, + flux, equations, dg, i, + direction_large, large_element) + end + else + multiply_add_to_node_vars!(surface_flux_values, + 0.25f0 * weights[j], flux, + equations, dg, i, direction_large, + large_element) + end # upper u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_rr @@ -639,8 +847,20 @@ function calc_mortar_flux!(surface_flux_values, flux = surface_flux(u_large_local, u_upper_local, orientation, equations) - add_to_node_vars!(surface_flux_values, 0.25f0 * weights[j] * flux, - equations, dg, i, direction_large, large_element) + if local_factor + factor = local_mortar_weights[i, j + nnodes(dg)] + if !isapprox(factor, zero(typeof(factor))) + factor = factor / local_mortar_weights[i, end] + multiply_add_to_node_vars!(surface_flux_values, factor, + flux, equations, dg, i, + direction_large, large_element) + end + else + multiply_add_to_node_vars!(surface_flux_values, + 0.25f0 * weights[j], flux, + equations, dg, i, direction_large, + large_element) + end end end else # large_sides[mortar] == 2 -> small elements on left side @@ -666,8 +886,20 @@ function calc_mortar_flux!(surface_flux_values, flux = surface_flux(u_lower_local, u_large_local, orientation, equations) - add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j] * flux, - equations, dg, i, direction_small, lower_element) + if local_factor + factor = local_mortar_weights[j, i] + if !isapprox(factor, zero(typeof(factor))) + factor = factor / local_mortar_weights[end, i] + multiply_add_to_node_vars!(surface_flux_values, factor, + flux, equations, dg, i, + direction_small, lower_element) + end + else + multiply_add_to_node_vars!(surface_flux_values, + 0.5f0 * weights[j], flux, + equations, dg, i, direction_small, + lower_element) + end end end # Upper element @@ -679,8 +911,20 @@ function calc_mortar_flux!(surface_flux_values, flux = surface_flux(u_upper_local, u_large_local, orientation, equations) - add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j] * flux, - equations, dg, i, direction_small, upper_element) + if local_factor + factor = local_mortar_weights[j, i + nnodes(dg)] + if !isapprox(factor, zero(typeof(factor))) + factor = factor / local_mortar_weights[end, i + nnodes(dg)] + multiply_add_to_node_vars!(surface_flux_values, factor, + flux, equations, dg, i, + direction_small, upper_element) + end + else + multiply_add_to_node_vars!(surface_flux_values, + 0.5f0 * weights[j], flux, + equations, dg, i, direction_small, + upper_element) + end end end # Large element @@ -693,8 +937,20 @@ function calc_mortar_flux!(surface_flux_values, flux = surface_flux(u_lower_local, u_large_local, orientation, equations) - add_to_node_vars!(surface_flux_values, 0.25f0 * weights[j] * flux, - equations, dg, i, direction_large, large_element) + if local_factor + factor = local_mortar_weights[i, j] + if !isapprox(factor, zero(typeof(factor))) + factor = factor / local_mortar_weights[i, end] + multiply_add_to_node_vars!(surface_flux_values, factor, + flux, equations, dg, i, + direction_large, large_element) + end + else + multiply_add_to_node_vars!(surface_flux_values, + 0.25f0 * weights[j], flux, + equations, dg, i, direction_large, + large_element) + end # upper u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_ll @@ -702,37 +958,36 @@ function calc_mortar_flux!(surface_flux_values, flux = surface_flux(u_upper_local, u_large_local, orientation, equations) - add_to_node_vars!(surface_flux_values, 0.25f0 * weights[j] * flux, - equations, dg, i, direction_large, large_element) + if local_factor + factor = local_mortar_weights[i, j + nnodes(dg)] + if !isapprox(factor, zero(typeof(factor))) + factor = factor / local_mortar_weights[i, end] + multiply_add_to_node_vars!(surface_flux_values, factor, + flux, equations, dg, i, + direction_large, large_element) + end + else + multiply_add_to_node_vars!(surface_flux_values, + 0.25f0 * weights[j], flux, + equations, dg, i, direction_large, + large_element) + end end end end end - # #=@threaded =#for mortar in eachmortar(dg, cache) - # # Choose thread-specific pre-allocated container - # # fstar_primary_upper = fstar_primary_upper_threaded[Threads.threadid()] - # # fstar_primary_lower = fstar_primary_lower_threaded[Threads.threadid()] - # # fstar_secondary_upper = fstar_secondary_upper_threaded[Threads.threadid()] - # # fstar_secondary_lower = fstar_secondary_lower_threaded[Threads.threadid()] - - # # Calculate fluxes - # orientation = orientations[mortar] - # calc_fstar_IDP_AMR!(surface_flux_values, equations, surface_flux, dg, u_lower, u_upper, u_large, mortar, - # orientation) - # # calc_fstar_IDP_AMR!(fstar_primary_lower, equations, surface_flux, dg, u_lower, mortar, - # # orientation) - # # calc_fstar_IDP_AMR!(fstar_secondary_upper, equations, surface_flux, dg, u_upper, mortar, - # # orientation) - # # calc_fstar_IDP_AMR!(fstar_secondary_lower, equations, surface_flux, dg, u_lower, mortar, - # # orientation) - - # # mortar_fluxes_to_elements!(surface_flux_values, - # # mesh, equations, mortar_idp, dg, cache, - # # mortar, fstar_primary_upper, fstar_primary_lower, - # # fstar_secondary_upper, fstar_secondary_lower) - # end + return nothing +end +@inline function element_solutions_to_mortars!(mortars, + mortar_idp::LobattoLegendreMortarIDP, + leftright, mortar, + u_large::AbstractArray{<:Any, 2}) + multiply_dimensionwise!(view(mortars.u_upper, leftright, :, :, mortar), + mortar_idp.forward_upper_low_order, u_large) + multiply_dimensionwise!(view(mortars.u_lower, leftright, :, :, mortar), + mortar_idp.forward_lower_low_order, u_large) return nothing end diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 6317ce3ab69..96f749b4b22 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -58,6 +58,129 @@ end end end +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_convergence_amr_sc_subcell.jl"), + alternative=false, + l2=[ + 6.792436492682374e-5, + 6.120716506968713e-5, + 6.055380339405702e-5, + 0.00014030467383463677 + ], + linf=[ + 0.0005470069686710488, + 0.0004685919341520517, + 0.00047891795883758803, + 0.0015576243185071448 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + # TODO: `view()` within `prolong2mortars` and `mortar flux` creates allocations + # This also happens for standard DG simulations with AMR. + end +end + +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl - alternative: global factor" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_convergence_amr_sc_subcell.jl"), + alternative=true, local_factor=false, + l2=[ + 0.006254682457071628, + 0.004999416955608761, + 0.0048141964689589105, + 0.009972111077049216 + ], + linf=[ + 0.04931632769799399, + 0.03556213775967221, + 0.033495743728496175, + 0.07783921049430154 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl - alternative: local factor, first order" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_convergence_amr_sc_subcell.jl"), + alternative=true, local_factor=true, first_order=true, + l2=[ + 0.001058120516341185, + 0.0009133976065639385, + 0.0008810204004781092, + 0.001891369160213655 + ], + linf=[ + 0.017886304148701182, + 0.012224853173556927, + 0.015594531468539952, + 0.03165281612069837 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl - alternative: local factor, second order" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_convergence_amr_sc_subcell.jl"), + alternative=true, local_factor=true, first_order=false, + # Note: Not conservative + l2=[ + 0.001315104732325498, + 0.0011661998948194854, + 0.0010984957991275826, + 0.0023024968589529945 + ], + linf=[ + 0.01058518982128076, + 0.010040635205911919, + 0.009834772741424747, + 0.02053636644232526 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + @trixi_testset "elixir_euler_density_wave.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), l2=[ @@ -410,20 +533,84 @@ end end end -@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl" begin +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (alternative implementation - local factor, first order)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_blast_wave_amr_sc_subcell.jl"), + alternative=true, local_factor=true, first_order=true, + l2=[ + 0.5658115528437391, + 0.23410168927488026, + 0.2343838211239537, + 0.7048618230676461 + ], + linf=[ + 2.3264681212817635, + 1.1830218103562824, + 1.1879149624171517, + 2.976938299678714 + ], + tspan=(0.0, 1.0),) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (alternative implementation - local factor, second order)" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_blast_wave_amr_sc_subcell.jl"), + alternative=true, local_factor=true, first_order=false, + # Note: Not conservative + l2=[ + 0.5656993054935289, + 0.23405091429718172, + 0.23415036917169094, + 0.7046511610343784 + ], + linf=[ + 2.3271194860448245, + 1.182587461440544, + 1.1870640954349436, + 2.9753866073069686 + ], + tspan=(0.0, 1.0),) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (alternative implementation - global factor)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), + alternative=true, local_factor=false, l2=[ - 0.48372925569187175, - 0.2029506811452194, - 0.20294966678670903, - 0.681359590384874 + 0.5597349371368964, + 0.23236224182845033, + 0.23140311168434288, + 0.7034630477001529 ], linf=[ - 1.4741183426277105, - 0.9512566363281697, - 0.951208106377221, - 2.9073697783624435 + 2.336082787232411, + 1.1774421459033773, + 1.1850364642054037, + 2.97064686684438 ], tspan=(0.0, 1.0),) # Ensure that we do not have excessive memory allocations @@ -440,6 +627,39 @@ end end end +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_blast_wave_amr_sc_subcell.jl"), + alternative=false, + l2=[ + 0.5661809201412991, + 0.23414173977241073, + 0.23448622905595354, + 0.7049402156739262 + ], + linf=[ + 2.323103591026165, + 1.191484161047329, + 1.1977284286748358, + 2.9758435679082553 + ], + tspan=(0.0, 1.0)) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + # TODO: `view()` within `prolong2mortars` and `mortar flux` creates allocations + # This also happens for standard DG simulations with AMR. + end +end + @trixi_testset "elixir_euler_sedov_blast_wave.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), l2=[ From f2f5e9f1c3c81fbba391c2a82031b3628742c276 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 30 Apr 2025 12:07:26 +0200 Subject: [PATCH 010/102] Adapt tests --- test/test_tree_2d_euler.jl | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 96f749b4b22..54f3cc75619 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -85,8 +85,6 @@ end # OrdinaryDiffEq.jl # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 - # TODO: `view()` within `prolong2mortars` and `mortar flux` creates allocations - # This also happens for standard DG simulations with AMR. end end @@ -602,7 +600,7 @@ end alternative=true, local_factor=false, l2=[ 0.5597349371368964, - 0.23236224182845033, + 0.23236224656270477, 0.23140311168434288, 0.7034630477001529 ], @@ -632,16 +630,16 @@ end "elixir_euler_blast_wave_amr_sc_subcell.jl"), alternative=false, l2=[ - 0.5661809201412991, - 0.23414173977241073, - 0.23448622905595354, - 0.7049402156739262 + 0.5661811293770421, + 0.23414189161032448, + 0.234486278634814, + 0.7049405559250281 ], linf=[ - 2.323103591026165, - 1.191484161047329, - 1.1977284286748358, - 2.9758435679082553 + 2.3231040236059366, + 1.1914843441075436, + 1.1977285198376395, + 2.9758323627646512 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations @@ -655,8 +653,6 @@ end # OrdinaryDiffEq.jl # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 - # TODO: `view()` within `prolong2mortars` and `mortar flux` creates allocations - # This also happens for standard DG simulations with AMR. end end From df08dce69aa5607b9d3c118bcb50fba275327bbd Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 30 Apr 2025 17:33:18 +0200 Subject: [PATCH 011/102] Speed up by not calculating same flux twice --- .../dgsem_tree/dg_2d_subcell_limiters.jl | 161 +++++++----------- 1 file changed, 62 insertions(+), 99 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 851b66098d6..88baafd4113 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -767,7 +767,6 @@ function calc_mortar_flux_alternative!(surface_flux_values, surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) - # TODO: Efficiency: Calculate flux only once. u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) # u_rr for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll @@ -778,16 +777,30 @@ function calc_mortar_flux_alternative!(surface_flux_values, if local_factor factor = local_mortar_weights[j, i] if !isapprox(factor, zero(typeof(factor))) - factor = factor / local_mortar_weights[end, i] - multiply_add_to_node_vars!(surface_flux_values, factor, + # Lower element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[end, i], flux, equations, dg, i, direction_small, lower_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[j, end], + flux, equations, dg, j, + direction_large, large_element) end else + # Lower element multiply_add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j], flux, equations, dg, i, direction_small, lower_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + 0.25f0 * weights[i], flux, + equations, dg, j, direction_large, + large_element) end end end @@ -803,62 +816,30 @@ function calc_mortar_flux_alternative!(surface_flux_values, if local_factor factor = local_mortar_weights[j, i + nnodes(dg)] if !isapprox(factor, zero(typeof(factor))) - factor = factor / local_mortar_weights[end, i + nnodes(dg)] - multiply_add_to_node_vars!(surface_flux_values, factor, + # Upper element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[end, + i + nnodes(dg)], flux, equations, dg, i, direction_small, upper_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[j, end], + flux, equations, dg, j, + direction_large, large_element) end else + # Upper element multiply_add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j], flux, equations, dg, i, direction_small, upper_element) - end - end - end - # Large element - for i in eachnode(dg) - u_large_local = get_node_vars(u_large, equations, dg, i, mortar) # u_ll - for j in eachnode(dg) - # lower - u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_rr - - flux = surface_flux(u_large_local, u_lower_local, orientation, - equations) - - if local_factor - factor = local_mortar_weights[i, j] - if !isapprox(factor, zero(typeof(factor))) - factor = factor / local_mortar_weights[i, end] - multiply_add_to_node_vars!(surface_flux_values, factor, - flux, equations, dg, i, - direction_large, large_element) - end - else - multiply_add_to_node_vars!(surface_flux_values, - 0.25f0 * weights[j], flux, - equations, dg, i, direction_large, - large_element) - end - - # upper - u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_rr - - flux = surface_flux(u_large_local, u_upper_local, orientation, - equations) - - if local_factor - factor = local_mortar_weights[i, j + nnodes(dg)] - if !isapprox(factor, zero(typeof(factor))) - factor = factor / local_mortar_weights[i, end] - multiply_add_to_node_vars!(surface_flux_values, factor, - flux, equations, dg, i, - direction_large, large_element) - end - else + # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.25f0 * weights[j], flux, - equations, dg, i, direction_large, + 0.25f0 * weights[i], flux, + equations, dg, j, direction_large, large_element) end end @@ -889,16 +870,30 @@ function calc_mortar_flux_alternative!(surface_flux_values, if local_factor factor = local_mortar_weights[j, i] if !isapprox(factor, zero(typeof(factor))) - factor = factor / local_mortar_weights[end, i] - multiply_add_to_node_vars!(surface_flux_values, factor, + # Lower element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[end, i], flux, equations, dg, i, direction_small, lower_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[j, end], + flux, equations, dg, j, + direction_large, large_element) end else + # Lower element multiply_add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j], flux, equations, dg, i, direction_small, lower_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + 0.25f0 * weights[i], flux, + equations, dg, j, direction_large, + large_element) end end end @@ -914,62 +909,30 @@ function calc_mortar_flux_alternative!(surface_flux_values, if local_factor factor = local_mortar_weights[j, i + nnodes(dg)] if !isapprox(factor, zero(typeof(factor))) - factor = factor / local_mortar_weights[end, i + nnodes(dg)] - multiply_add_to_node_vars!(surface_flux_values, factor, + # Upper element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[end, + i + nnodes(dg)], flux, equations, dg, i, direction_small, upper_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[j, end], + flux, equations, dg, j, + direction_large, large_element) end else + # Upper element multiply_add_to_node_vars!(surface_flux_values, 0.5f0 * weights[j], flux, equations, dg, i, direction_small, upper_element) - end - end - end - # Large element - for i in eachnode(dg) - u_large_local = get_node_vars(u_large, equations, dg, i, mortar) # u_rr - for j in eachnode(dg) - # lower - u_lower_local = get_node_vars(u_lower, equations, dg, j, mortar) # u_ll - - flux = surface_flux(u_lower_local, u_large_local, orientation, - equations) - - if local_factor - factor = local_mortar_weights[i, j] - if !isapprox(factor, zero(typeof(factor))) - factor = factor / local_mortar_weights[i, end] - multiply_add_to_node_vars!(surface_flux_values, factor, - flux, equations, dg, i, - direction_large, large_element) - end - else - multiply_add_to_node_vars!(surface_flux_values, - 0.25f0 * weights[j], flux, - equations, dg, i, direction_large, - large_element) - end - - # upper - u_upper_local = get_node_vars(u_upper, equations, dg, j, mortar) # u_ll - - flux = surface_flux(u_upper_local, u_large_local, orientation, - equations) - - if local_factor - factor = local_mortar_weights[i, j + nnodes(dg)] - if !isapprox(factor, zero(typeof(factor))) - factor = factor / local_mortar_weights[i, end] - multiply_add_to_node_vars!(surface_flux_values, factor, - flux, equations, dg, i, - direction_large, large_element) - end - else + # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.25f0 * weights[j], flux, - equations, dg, i, direction_large, + 0.25f0 * weights[i], flux, + equations, dg, j, direction_large, large_element) end end From 8bf59abded81d4b02ae5e7abf00e920fd0d70bc5 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 30 Apr 2025 18:01:42 +0200 Subject: [PATCH 012/102] Fix test --- test/test_tree_2d_euler.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 54f3cc75619..866c8dcfcca 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -600,7 +600,7 @@ end alternative=true, local_factor=false, l2=[ 0.5597349371368964, - 0.23236224656270477, + 0.2323622389350034, 0.23140311168434288, 0.7034630477001529 ], From da8c3f8c2fd6a323db33224b9e49303edc6018d1 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 6 May 2025 14:36:30 +0200 Subject: [PATCH 013/102] Add support of blending mortar fluxes --- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 3 +- ...elixir_euler_convergence_amr_sc_subcell.jl | 3 +- src/solvers/dgsem/basis_lobatto_legendre.jl | 56 +++-- src/solvers/dgsem_tree/containers_2d.jl | 78 +++++-- src/solvers/dgsem_tree/dg_2d.jl | 39 +--- .../dgsem_tree/dg_2d_subcell_limiters.jl | 217 +++++++++++++++--- test/test_tree_2d_euler.jl | 32 +-- 7 files changed, 308 insertions(+), 120 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 17804ea62bb..f009c760c37 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -51,7 +51,8 @@ limiter_idp = SubcellLimiterIDP(equations, basis; volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = Trixi.MortarIDP(basis, alternative = true, local_factor = true, first_order = true) +mortar = Trixi.MortarIDP(basis, alternative = false, local_factor = true, + first_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index 04c0a8c7046..0bba9b31b71 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -26,7 +26,8 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = Trixi.MortarIDP(basis; alternative = true, local_factor = true, first_order = true) +mortar = Trixi.MortarIDP(basis; alternative = false, local_factor = true, + first_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index dc7793e9ff9..50bf9338328 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -145,32 +145,30 @@ end left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights) right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) -struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, - ForwardMatrix <: AbstractMatrix{RealT}, - ReverseMatrix <: AbstractMatrix{RealT}} <: +struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: AbstractMortarL2{RealT} - alternative::Bool local_factor::Bool - forward_upper::ForwardMatrix - forward_lower::ForwardMatrix - reverse_upper::ReverseMatrix - reverse_lower::ReverseMatrix + mortar_l2::Mortar local_mortar_weights::Matrix{RealT} +end + +struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, Mortar, + ForwardMatrix <: AbstractMatrix{RealT}, + ReverseMatrix <: AbstractMatrix{RealT}} <: + AbstractMortarL2{RealT} + mortar_l2::Mortar forward_upper_low_order::ForwardMatrix forward_lower_low_order::ForwardMatrix reverse_upper_low_order::ReverseMatrix reverse_lower_low_order::ReverseMatrix end -function MortarIDP(basis::LobattoLegendreBasis; local_factor = false, - alternative = false, first_order = true) +function MortarIDP(basis::LobattoLegendreBasis; alternative = false, + local_factor = true, first_order = true) RealT = real(basis) nnodes_ = nnodes(basis) - forward_upper = calc_forward_upper(nnodes_, RealT) - forward_lower = calc_forward_lower(nnodes_, RealT) - reverse_upper = calc_reverse_upper(nnodes_, Val(:gauss), RealT) - reverse_lower = calc_reverse_lower(nnodes_, Val(:gauss), RealT) + mortar_l2 = MortarL2(basis) local_mortar_weights = calc_mortar_weights(basis, RealT; first_order = first_order) @@ -181,15 +179,19 @@ function MortarIDP(basis::LobattoLegendreBasis; local_factor = false, reverse_lower_low_order = calc_reverse_lower_low_order(nnodes_, Val(:gauss_lobatto), RealT) - LobattoLegendreMortarIDP{RealT, nnodes_, typeof(forward_upper), - typeof(reverse_upper)}(alternative, local_factor, - forward_upper, forward_lower, - reverse_upper, reverse_lower, - local_mortar_weights, - forward_upper_low_order, - forward_lower_low_order, - reverse_upper_low_order, - reverse_lower_low_order) + if alternative + LobattoLegendreMortarIDPAlternative{RealT, nnodes_, typeof(mortar_l2), + typeof(forward_upper_low_order), + typeof(reverse_upper_low_order)}(mortar_l2, + forward_upper_low_order, + forward_lower_low_order, + reverse_upper_low_order, + reverse_lower_low_order) + else + LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(local_factor, + mortar_l2, + local_mortar_weights) + end end function Base.show(io::IO, mortar::LobattoLegendreMortarIDP) @@ -213,7 +215,15 @@ end NNODES end +@inline function nnodes(mortar::LobattoLegendreMortarIDPAlternative{RealT, NNODES}) where { + RealT, + NNODES + } + NNODES +end + @inline polydeg(mortar::LobattoLegendreMortarIDP) = nnodes(mortar) - 1 +@inline polydeg(mortar::LobattoLegendreMortarIDPAlternative) = nnodes(mortar) - 1 struct LobattoLegendreMortarL2{RealT <: Real, NNODES, ForwardMatrix <: AbstractMatrix{RealT}, diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index 9260da1b198..882a51f1aa4 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -10,10 +10,12 @@ mutable struct ElementContainer2D{RealT <: Real, uEltype <: Real} <: AbstractCon inverse_jacobian::Vector{RealT} # [elements] node_coordinates::Array{RealT, 4} # [orientation, i, j, elements] surface_flux_values::Array{uEltype, 4} # [variables, i, direction, elements] + surface_flux_values_high_order::Array{uEltype, 4} # [variables, i, direction, elements] cell_ids::Vector{Int} # [elements] # internal `resize!`able storage _node_coordinates::Vector{RealT} _surface_flux_values::Vector{uEltype} + _surface_flux_values_high_order::Vector{uEltype} end nvariables(elements::ElementContainer2D) = size(elements.surface_flux_values, 1) @@ -28,7 +30,7 @@ Base.eltype(elements::ElementContainer2D) = eltype(elements.surface_flux_values) function Base.resize!(elements::ElementContainer2D, capacity) n_nodes = nnodes(elements) n_variables = nvariables(elements) - @unpack _node_coordinates, _surface_flux_values, + @unpack _node_coordinates, _surface_flux_values, _surface_flux_values_high_order, inverse_jacobian, cell_ids = elements resize!(inverse_jacobian, capacity) @@ -41,6 +43,12 @@ function Base.resize!(elements::ElementContainer2D, capacity) elements.surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), (n_variables, n_nodes, 2 * 2, capacity)) + resize!(_surface_flux_values_high_order, n_variables * n_nodes * 2 * 2 * capacity) + elements.surface_flux_values_high_order = unsafe_wrap(Array, + pointer(_surface_flux_values_high_order), + (n_variables, n_nodes, 2 * 2, + capacity)) + resize!(cell_ids, capacity) return nothing @@ -63,11 +71,21 @@ function ElementContainer2D{RealT, uEltype}(capacity::Integer, n_variables, surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), (n_variables, n_nodes, 2 * 2, capacity)) + _surface_flux_values_high_order = fill(nan_uEltype, + n_variables * n_nodes * 2 * 2 * capacity) + surface_flux_values_high_order = unsafe_wrap(Array, + pointer(_surface_flux_values_high_order), + (n_variables, n_nodes, 2 * 2, + capacity)) + cell_ids = fill(typemin(Int), capacity) return ElementContainer2D{RealT, uEltype}(inverse_jacobian, node_coordinates, - surface_flux_values, cell_ids, - _node_coordinates, _surface_flux_values) + surface_flux_values, + surface_flux_values_high_order, + cell_ids, + _node_coordinates, _surface_flux_values, + _surface_flux_values_high_order) end # Return number of elements @@ -622,13 +640,14 @@ end # lower = 1 | | # | | mutable struct IDPMortarContainer2D{uEltype <: Real} <: AbstractContainer - u_upper::Array{uEltype, 3} # [variables, i, mortars] - u_lower::Array{uEltype, 3} # [variables, i, mortars] + u_upper::Array{uEltype, 4} # [leftright, variables, i, mortars] + u_lower::Array{uEltype, 4} # [leftright, variables, i, mortars] u_large::Array{uEltype, 3} # [variables, i, mortars] neighbor_ids::Array{Int, 2} # [position, mortars] # Large sides: left -> 1, right -> 2 large_sides::Vector{Int} # [mortars] orientations::Vector{Int} # [mortars] + limiting_factor::Vector{uEltype} # [mortars] # internal `resize!`able storage _u_upper::Vector{uEltype} _u_lower::Vector{uEltype} @@ -636,8 +655,8 @@ mutable struct IDPMortarContainer2D{uEltype <: Real} <: AbstractContainer _neighbor_ids::Vector{Int} end -nvariables(mortars::IDPMortarContainer2D) = size(mortars.u_upper, 1) -nnodes(mortars::IDPMortarContainer2D) = size(mortars.u_upper, 2) +nvariables(mortars::IDPMortarContainer2D) = size(mortars.u_upper, 2) +nnodes(mortars::IDPMortarContainer2D) = size(mortars.u_upper, 3) Base.eltype(mortars::IDPMortarContainer2D) = eltype(mortars.u_upper) # See explanation of Base.resize! for the element container @@ -645,13 +664,13 @@ function Base.resize!(mortars::IDPMortarContainer2D, capacity) n_nodes = nnodes(mortars) n_variables = nvariables(mortars) @unpack _u_upper, _u_lower, _u_large, _neighbor_ids, - large_sides, orientations = mortars + large_sides, orientations, limiting_factor = mortars - resize!(_u_upper, n_variables * n_nodes * capacity) + resize!(_u_upper, 2 * n_variables * n_nodes * capacity) mortars.u_upper = unsafe_wrap(Array, pointer(_u_upper), (n_variables, n_nodes, capacity)) - resize!(_u_lower, n_variables * n_nodes * capacity) + resize!(_u_lower, 2 * n_variables * n_nodes * capacity) mortars.u_lower = unsafe_wrap(Array, pointer(_u_lower), (n_variables, n_nodes, capacity)) @@ -667,6 +686,8 @@ function Base.resize!(mortars::IDPMortarContainer2D, capacity) resize!(orientations, capacity) + resize!(limiting_factor, capacity) + return nothing end @@ -675,13 +696,13 @@ function IDPMortarContainer2D{uEltype}(capacity::Integer, n_variables, nan = convert(uEltype, NaN) # Initialize fields with defaults - _u_upper = fill(nan, n_variables * n_nodes * capacity) + _u_upper = fill(nan, 2 * n_variables * n_nodes * capacity) u_upper = unsafe_wrap(Array, pointer(_u_upper), - (n_variables, n_nodes, capacity)) + (2, n_variables, n_nodes, capacity)) - _u_lower = fill(nan, n_variables * n_nodes * capacity) + _u_lower = fill(nan, 2 * n_variables * n_nodes * capacity) u_lower = unsafe_wrap(Array, pointer(_u_lower), - (n_variables, n_nodes, capacity)) + (2, n_variables, n_nodes, capacity)) _u_large = fill(nan, n_variables * n_nodes * capacity) u_large = unsafe_wrap(Array, pointer(_u_large), @@ -695,8 +716,10 @@ function IDPMortarContainer2D{uEltype}(capacity::Integer, n_variables, orientations = fill(typemin(Int), capacity) + limiting_factor = fill(typemin(Int), capacity) + return IDPMortarContainer2D{uEltype}(u_upper, u_lower, u_large, neighbor_ids, - large_sides, orientations, + large_sides, orientations, limiting_factor, _u_upper, _u_lower, _u_large, _neighbor_ids) end @@ -729,14 +752,23 @@ function init_mortars(cell_ids, mesh::TreeMesh2D, mortar::LobattoLegendreMortarIDP) # Initialize containers n_mortars = count_required_mortars(mesh, cell_ids) - if mortar.alternative - mortars = IDPMortarContainer2D{eltype(elements)}(n_mortars, - nvariables(elements), - nnodes(elements)) - else - mortars = L2MortarContainer2D{eltype(elements)}(n_mortars, nvariables(elements), - nnodes(elements)) - end + mortars = IDPMortarContainer2D{eltype(elements)}(n_mortars, + nvariables(elements), + nnodes(elements)) + + # Connect elements with mortars + init_mortars!(mortars, elements, mesh) + return mortars +end + +# Create mortar container and initialize mortar data in `elements`. +function init_mortars(cell_ids, mesh::TreeMesh2D, + elements::ElementContainer2D, + mortar::LobattoLegendreMortarIDPAlternative) + # Initialize containers + n_mortars = count_required_mortars(mesh, cell_ids) + mortars = L2MortarContainer2D{eltype(elements)}(n_mortars, nvariables(elements), + nnodes(elements)) # Connect elements with mortars init_mortars!(mortars, elements, mesh) diff --git a/src/solvers/dgsem_tree/dg_2d.jl b/src/solvers/dgsem_tree/dg_2d.jl index d751ffd4ea9..39fbdfe70aa 100644 --- a/src/solvers/dgsem_tree/dg_2d.jl +++ b/src/solvers/dgsem_tree/dg_2d.jl @@ -93,7 +93,8 @@ function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMe P4estMesh{2}, P4estMeshView{2}, T8codeMesh{2}}, equations, mortar_l2::Union{LobattoLegendreMortarL2, - LobattoLegendreMortarIDP}, uEltype) + LobattoLegendreMortarIDP, + LobattoLegendreMortarIDPAlternative}, uEltype) # TODO: Taal performance using different types MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)}, uEltype, 2, nvariables(equations) * nnodes(mortar_l2)} @@ -153,32 +154,16 @@ function rhs!(du, u, t, end # Prolong solution to mortars - if dg.mortar isa LobattoLegendreMortarIDP && dg.mortar.alternative - @trixi_timeit timer() "prolong2mortars_alternative" begin - prolong2mortars_alternative!(cache, u, mesh, equations, - dg.mortar, dg) - end - else - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u, mesh, equations, - dg.mortar, dg) - end + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!(cache, u, mesh, equations, + dg.mortar, dg) end # Calculate mortar fluxes - if dg.mortar isa LobattoLegendreMortarIDP && dg.mortar.alternative - @trixi_timeit timer() "mortar flux alternative" begin - calc_mortar_flux_alternative!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), - equations, - dg.mortar, dg.surface_integral, dg, cache) - end - else - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.mortar, dg.surface_integral, dg, cache) - end + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!(cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.mortar, dg.surface_integral, dg, cache) end # Calculate surface integrals @@ -816,7 +801,7 @@ end function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, mortar_l2::Union{LobattoLegendreMortarL2, - LobattoLegendreMortarIDP}, + LobattoLegendreMortarIDPAlternative}, dg::DGSEM) @threaded for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] @@ -918,7 +903,7 @@ function calc_mortar_flux!(surface_flux_values, mesh::TreeMesh{2}, nonconservative_terms::False, equations, mortar_l2::Union{LobattoLegendreMortarL2, - LobattoLegendreMortarIDP}, + LobattoLegendreMortarIDPAlternative}, surface_integral, dg::DG, cache) @unpack surface_flux = surface_integral @unpack u_lower, u_upper, orientations = cache.mortars @@ -1151,7 +1136,7 @@ end @inline function mortar_fluxes_to_elements!(surface_flux_values, mesh::TreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarIDP, + mortar_l2::LobattoLegendreMortarIDPAlternative, dg::DGSEM, cache, mortar, fstar_primary_upper, fstar_primary_lower, diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 88baafd4113..47b63c51478 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -59,7 +59,9 @@ end # The methods below are specialized on the mortar type # and called from the basic `create_cache` method at the top. function create_cache(mesh::TreeMesh{2}, - equations, mortar_idp::LobattoLegendreMortarIDP, uEltype) + equations, + mortar_idp::Union{LobattoLegendreMortarIDP, + LobattoLegendreMortarIDPAlternative}, uEltype) # TODO: Taal performance using different types # TODO: What do I really need? @@ -669,14 +671,14 @@ end return nothing end -function prolong2mortars_alternative!(cache, u, - mesh::TreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarIDP, - dg::DGSEM) +function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, + mortar_idp::LobattoLegendreMortarIDP, dg::DGSEM) + prolong2mortars!(cache, u, mesh, equations, mortar_idp.mortar_l2, dg) + @threaded for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] - upper_element = cache.mortars.neighbor_ids[2, mortar] - lower_element = cache.mortars.neighbor_ids[1, mortar] + # upper_element = cache.mortars.neighbor_ids[2, mortar] + # lower_element = cache.mortars.neighbor_ids[1, mortar] # Copy solutions if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side @@ -684,8 +686,8 @@ function prolong2mortars_alternative!(cache, u, # IDP mortars in x-direction for l in eachnode(dg) for v in eachvariable(equations) - cache.mortars.u_upper[v, l, mortar] = u[v, 1, l, upper_element] - cache.mortars.u_lower[v, l, mortar] = u[v, 1, l, lower_element] + # cache.mortars.u_upper[v, l, mortar] = u[v, 1, l, upper_element] + # cache.mortars.u_lower[v, l, mortar] = u[v, 1, l, lower_element] cache.mortars.u_large[v, l, mortar] = u[v, nnodes(dg), l, large_element] end @@ -694,8 +696,8 @@ function prolong2mortars_alternative!(cache, u, # IDP mortars in y-direction for l in eachnode(dg) for v in eachvariable(equations) - cache.mortars.u_upper[v, l, mortar] = u[v, l, 1, upper_element] - cache.mortars.u_lower[v, l, mortar] = u[v, l, 1, lower_element] + # cache.mortars.u_upper[v, l, mortar] = u[v, l, 1, upper_element] + # cache.mortars.u_lower[v, l, mortar] = u[v, l, 1, lower_element] cache.mortars.u_large[v, l, mortar] = u[v, l, nnodes(dg), large_element] end @@ -706,10 +708,10 @@ function prolong2mortars_alternative!(cache, u, # IDP mortars in x-direction for l in eachnode(dg) for v in eachvariable(equations) - cache.mortars.u_upper[v, l, mortar] = u[v, nnodes(dg), l, - upper_element] - cache.mortars.u_lower[v, l, mortar] = u[v, nnodes(dg), l, - lower_element] + # cache.mortars.u_upper[v, l, mortar] = u[v, nnodes(dg), l, + # upper_element] + # cache.mortars.u_lower[v, l, mortar] = u[v, nnodes(dg), l, + # lower_element] cache.mortars.u_large[v, l, mortar] = u[v, 1, l, large_element] end end @@ -717,10 +719,10 @@ function prolong2mortars_alternative!(cache, u, # IDP mortars in y-direction for l in eachnode(dg) for v in eachvariable(equations) - cache.mortars.u_upper[v, l, mortar] = u[v, l, nnodes(dg), - upper_element] - cache.mortars.u_lower[v, l, mortar] = u[v, l, nnodes(dg), - lower_element] + # cache.mortars.u_upper[v, l, mortar] = u[v, l, nnodes(dg), + # upper_element] + # cache.mortars.u_lower[v, l, mortar] = u[v, l, nnodes(dg), + # lower_element] cache.mortars.u_large[v, l, mortar] = u[v, l, 1, large_element] end end @@ -731,11 +733,164 @@ function prolong2mortars_alternative!(cache, u, return nothing end -function calc_mortar_flux_alternative!(surface_flux_values, - mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - mortar_idp::LobattoLegendreMortarIDP, - surface_integral, dg::DG, cache) +function calc_mortar_flux!(surface_flux_values, mesh, + nonconservative_terms, equations, + mortar_idp::LobattoLegendreMortarIDP, surface_integral, + dg::DG, cache) + # Compute blending factor + (; limiting_factor) = cache.mortars + limiting_factor .= zeros(eltype(limiting_factor)) # 0 => full FV + + # low order fluxes + @trixi_timeit timer() "calc_mortar_flux_low_order!" calc_mortar_flux_low_order!(surface_flux_values, + mesh, + nonconservative_terms, + equations, + mortar_idp, + surface_integral, + dg, + cache) + + (; surface_flux_values_high_order) = cache.elements + @trixi_timeit timer() "calc_mortar_flux!" calc_mortar_flux!(surface_flux_values_high_order, + mesh, + nonconservative_terms, + equations, + mortar_idp.mortar_l2, + dg.surface_integral, dg, + cache) + + @trixi_timeit timer() "blend_mortar_flux!" blend_mortar_flux!(surface_flux_values, + surface_flux_values_high_order, + limiting_factor, + equations, dg, cache) + + return nothing +end + +@inline function blend_mortar_flux!(surface_flux_values, surface_flux_values_high_order, + limiting_factor, equations, dg, cache) + @unpack orientations = cache.mortars + + for mortar in eachmortar(dg, cache) + if isapprox(limiting_factor[mortar], zero(eltype(limiting_factor))) + continue + end + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Calculate fluxes + orientation = orientations[mortar] + + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientation == 1 + # L2 mortars in x-direction + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + direction_small = 3 + direction_large = 4 + end + + for i in eachnode(dg) + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_small, lower_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_small, + lower_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_small, lower_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_small, lower_element) + + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_small, upper_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_small, + upper_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_small, upper_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_small, upper_element) + + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_large, large_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_large, + large_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_large, large_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_large, large_element) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientation == 1 + # L2 mortars in x-direction + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + direction_small = 4 + direction_large = 3 + end + + for i in eachnode(dg) + # @info "" flux_difference_local + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_small, lower_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_small, + lower_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_small, lower_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_small, lower_element) + + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_small, upper_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_small, + upper_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_small, upper_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_small, upper_element) + + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_large, large_element) + flux_local_low_order = view(surface_flux_values_high_order, :, i, + direction_large, large_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_large, large_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_large, large_element) + end + end + end + + return nothing +end + +function calc_mortar_flux_low_order!(surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + mortar_idp::LobattoLegendreMortarIDP, + surface_integral, dg::DG, cache) @unpack surface_flux = surface_integral @unpack u_lower, u_upper, u_large, orientations = cache.mortars @unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded, @@ -767,7 +922,8 @@ function calc_mortar_flux_alternative!(surface_flux_values, surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) - u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) # u_rr + _, u_lower_local = get_surface_node_vars(u_lower, equations, dg, i, + mortar) # u_rr for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll @@ -806,7 +962,8 @@ function calc_mortar_flux_alternative!(surface_flux_values, end # Upper element for i in eachnode(dg) - u_upper_local = get_node_vars(u_upper, equations, dg, i, mortar) # u_rr + _, u_upper_local = get_surface_node_vars(u_upper, equations, dg, i, + mortar) # u_rr for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll @@ -860,7 +1017,8 @@ function calc_mortar_flux_alternative!(surface_flux_values, surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) - u_lower_local = get_node_vars(u_lower, equations, dg, i, mortar) # u_ll + u_lower_local, _ = get_surface_node_vars(u_lower, equations, dg, i, + mortar) # u_ll for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr @@ -899,7 +1057,8 @@ function calc_mortar_flux_alternative!(surface_flux_values, end # Upper element for i in eachnode(dg) - u_upper_local = get_node_vars(u_upper, equations, dg, i, mortar) # u_ll + u_upper_local, _ = get_surface_node_vars(u_upper, equations, dg, i, + mortar) # u_ll for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr @@ -944,7 +1103,7 @@ function calc_mortar_flux_alternative!(surface_flux_values, end @inline function element_solutions_to_mortars!(mortars, - mortar_idp::LobattoLegendreMortarIDP, + mortar_idp::LobattoLegendreMortarIDPAlternative, leftright, mortar, u_large::AbstractArray{<:Any, 2}) multiply_dimensionwise!(view(mortars.u_upper, leftright, :, :, mortar), diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 866c8dcfcca..152853cfd0e 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -58,10 +58,10 @@ end end end -@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl" begin +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (alternative implementation)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=false, + alternative=true, l2=[ 6.792436492682374e-5, 6.120716506968713e-5, @@ -88,10 +88,10 @@ end end end -@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl - alternative: global factor" begin +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (global factor)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=true, local_factor=false, + alternative=false, local_factor=false, l2=[ 0.006254682457071628, 0.004999416955608761, @@ -118,10 +118,10 @@ end end end -@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl - alternative: local factor, first order" begin +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (local factor, first order)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=true, local_factor=true, first_order=true, + alternative=false, local_factor=true, first_order=true, l2=[ 0.001058120516341185, 0.0009133976065639385, @@ -148,10 +148,10 @@ end end end -@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl - alternative: local factor, second order" begin +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (local factor, second order)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=true, local_factor=true, first_order=false, + alternative=false, local_factor=true, first_order=false, # Note: Not conservative l2=[ 0.001315104732325498, @@ -531,10 +531,10 @@ end end end -@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (alternative implementation - local factor, first order)" begin +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (local factor, first order)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=true, local_factor=true, first_order=true, + alternative=false, local_factor=true, first_order=true, l2=[ 0.5658115528437391, 0.23410168927488026, @@ -562,10 +562,10 @@ end end end -@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (alternative implementation - local factor, second order)" begin +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (local factor, second order)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=true, local_factor=true, first_order=false, + alternative=false, local_factor=true, first_order=false, # Note: Not conservative l2=[ 0.5656993054935289, @@ -594,10 +594,10 @@ end end end -@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (alternative implementation - global factor)" begin +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (global factor)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=true, local_factor=false, + alternative=false, local_factor=false, l2=[ 0.5597349371368964, 0.2323622389350034, @@ -625,10 +625,10 @@ end end end -@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl" begin +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (alternative implementation)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=false, + alternative=true, l2=[ 0.5661811293770421, 0.23414189161032448, From c0cd9539cd7fab30f70feda65bf258a1ea1171f6 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 9 May 2025 10:43:30 +0200 Subject: [PATCH 014/102] Small change --- src/equations/equations.jl | 3 + .../dgsem_tree/dg_2d_subcell_limiters.jl | 114 ++++++------------ 2 files changed, 41 insertions(+), 76 deletions(-) diff --git a/src/equations/equations.jl b/src/equations/equations.jl index c83908cd7a9..347c8a12463 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -46,6 +46,9 @@ function varnames end # Otherwise, throw an error. function get_variable_index(varname, equations; solution_variables = cons2cons) + if varname == "first" + return 1 + end index = findfirst(==(varname), varnames(solution_variables, equations)) if isnothing(index) throw(ArgumentError("$varname is no valid variable.")) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 47b63c51478..0fd146c4f25 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -793,44 +793,6 @@ end direction_small = 3 direction_large = 4 end - - for i in eachnode(dg) - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_small, lower_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_small, - lower_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_small, lower_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_small, lower_element) - - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_small, upper_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_small, - upper_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_small, upper_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_small, upper_element) - - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_large, large_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_large, - large_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_large, large_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_large, large_element) - end else # large_sides[mortar] == 2 -> small elements on left side if orientation == 1 # L2 mortars in x-direction @@ -841,45 +803,45 @@ end direction_small = 4 direction_large = 3 end + end - for i in eachnode(dg) - # @info "" flux_difference_local - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_small, lower_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_small, - lower_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_small, lower_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_small, lower_element) - - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_small, upper_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_small, - upper_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_small, upper_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_small, upper_element) - - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_large, large_element) - flux_local_low_order = view(surface_flux_values_high_order, :, i, - direction_large, large_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_large, large_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_large, large_element) - end + for i in eachnode(dg) + # @info "" flux_difference_local + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_small, lower_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_small, + lower_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_small, lower_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_small, lower_element) + + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_small, upper_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_small, + upper_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_small, upper_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_small, upper_element) + + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_large, large_element) + flux_local_low_order = view(surface_flux_values_high_order, :, i, + direction_large, large_element) + multiply_add_to_node_vars!(surface_flux_values, + -limiting_factor[mortar], + flux_local_low_order, equations, dg, i, + direction_large, large_element) + multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], + flux_local_high_order, equations, dg, i, + direction_large, large_element) end end From 20bea41a3c91000c18ea770adbdc7b50a97bc29b Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 9 May 2025 11:16:29 +0200 Subject: [PATCH 015/102] Small fix --- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 0fd146c4f25..fdcc45609e1 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -751,6 +751,7 @@ function calc_mortar_flux!(surface_flux_values, mesh, dg, cache) + # high order fluxes (; surface_flux_values_high_order) = cache.elements @trixi_timeit timer() "calc_mortar_flux!" calc_mortar_flux!(surface_flux_values_high_order, mesh, @@ -806,7 +807,6 @@ end end for i in eachnode(dg) - # @info "" flux_difference_local flux_local_high_order = view(surface_flux_values_high_order, :, i, direction_small, lower_element) flux_local_low_order = view(surface_flux_values, :, i, direction_small, @@ -833,8 +833,8 @@ end flux_local_high_order = view(surface_flux_values_high_order, :, i, direction_large, large_element) - flux_local_low_order = view(surface_flux_values_high_order, :, i, - direction_large, large_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_large, + large_element) multiply_add_to_node_vars!(surface_flux_values, -limiting_factor[mortar], flux_local_low_order, equations, dg, i, From b022085ba313d7ec4951171fb542f592fdf95c79 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 16 May 2025 09:53:41 +0200 Subject: [PATCH 016/102] Move blending to correction stage --- .../subcell_limiter_idp_correction.jl | 6 + .../subcell_limiter_idp_correction_2d.jl | 104 +++++++++++ .../dgsem_tree/dg_2d_subcell_limiters.jl | 173 +++++++++--------- 3 files changed, 197 insertions(+), 86 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction.jl b/src/callbacks_stage/subcell_limiter_idp_correction.jl index d234a6a5507..efe78c9a25d 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction.jl @@ -55,6 +55,12 @@ function (limiter!::SubcellLimiterIDPCorrection)(u_ode, semi, t, dt, perform_idp_correction!(u, dt, mesh, equations, solver, cache) + if solver.mortar isa Trixi.LobattoLegendreMortarIDP + @trixi_timeit timer() "blend_mortar_flux!" blend_mortar_flux!(u, semi, + equations, solver, + t, dt) + end + return nothing end diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index e3b5616d5e5..a7ac1e79dc0 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -45,4 +45,108 @@ function perform_idp_correction!(u, dt, return nothing end + +@inline function blend_mortar_flux!(u, semi, equations, dg, t, dt) + (; mesh, cache) = semi + (; orientations) = cache.mortars + + (; surface_flux_values, surface_flux_values_high_order) = cache.elements + (; boundary_interpolation) = dg.basis + + ############################ + # TODO: Calculate blending factor for mortar fluxes + (; limiting_factor) = cache.mortars + limiting_factor .= zero(eltype(limiting_factor)) + # limiting_factor = 1 => full DG + # limiting_factor = 0 => full FV + ####################### + + for mortar in eachmortar(dg, cache) + if isapprox(limiting_factor[mortar], zero(eltype(limiting_factor))) + continue + end + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + direction_small = 3 + direction_large = 4 + end + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + direction_small = 4 + direction_large = 3 + end + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end + inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + upper_element) + inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + lower_element) + inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_large..., + large_element) + + # lower element + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_small, lower_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_small, + lower_element) + + for v in eachvariable(equations) + u[v, indices_small..., lower_element] += dt * inverse_jacobian_lower * + (factor_small * limiting_factor[mortar] * + (flux_local_high_order[v] - flux_local_low_order[v])) + end + + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_small, upper_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_small, + upper_element) + for v in eachvariable(equations) + u[v, indices_small..., upper_element] += dt * inverse_jacobian_upper * + (factor_small * limiting_factor[mortar] * + (flux_local_high_order[v] - flux_local_low_order[v])) + end + + flux_local_high_order = view(surface_flux_values_high_order, :, i, + direction_large, large_element) + flux_local_low_order = view(surface_flux_values, :, i, direction_large, + large_element) + for v in eachvariable(equations) + u[v, indices_large..., large_element] += dt * inverse_jacobian_large * + (factor_large * limiting_factor[mortar] * + (flux_local_high_order[v] - flux_local_low_order[v])) + end + end + end + + return nothing +end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index fdcc45609e1..9de9a3de863 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -737,10 +737,6 @@ function calc_mortar_flux!(surface_flux_values, mesh, nonconservative_terms, equations, mortar_idp::LobattoLegendreMortarIDP, surface_integral, dg::DG, cache) - # Compute blending factor - (; limiting_factor) = cache.mortars - limiting_factor .= zeros(eltype(limiting_factor)) # 0 => full FV - # low order fluxes @trixi_timeit timer() "calc_mortar_flux_low_order!" calc_mortar_flux_low_order!(surface_flux_values, mesh, @@ -761,93 +757,98 @@ function calc_mortar_flux!(surface_flux_values, mesh, dg.surface_integral, dg, cache) - @trixi_timeit timer() "blend_mortar_flux!" blend_mortar_flux!(surface_flux_values, - surface_flux_values_high_order, - limiting_factor, - equations, dg, cache) - - return nothing -end - -@inline function blend_mortar_flux!(surface_flux_values, surface_flux_values_high_order, - limiting_factor, equations, dg, cache) - @unpack orientations = cache.mortars - - for mortar in eachmortar(dg, cache) - if isapprox(limiting_factor[mortar], zero(eltype(limiting_factor))) - continue - end - large_element = cache.mortars.neighbor_ids[3, mortar] - upper_element = cache.mortars.neighbor_ids[2, mortar] - lower_element = cache.mortars.neighbor_ids[1, mortar] - - # Calculate fluxes - orientation = orientations[mortar] - - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if orientation == 1 - # L2 mortars in x-direction - direction_small = 1 - direction_large = 2 - else - # L2 mortars in y-direction - direction_small = 3 - direction_large = 4 - end - else # large_sides[mortar] == 2 -> small elements on left side - if orientation == 1 - # L2 mortars in x-direction - direction_small = 2 - direction_large = 1 - else - # L2 mortars in y-direction - direction_small = 4 - direction_large = 3 - end - end + # (; limiting_factor) = cache.mortars + # limiting_factor .= zero(eltype(limiting_factor)) + # # limiting_factor = 1 => full DG + # # limiting_factor = 0 => full FV - for i in eachnode(dg) - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_small, lower_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_small, - lower_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_small, lower_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_small, lower_element) - - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_small, upper_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_small, - upper_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_small, upper_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_small, upper_element) - - flux_local_high_order = view(surface_flux_values_high_order, :, i, - direction_large, large_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_large, - large_element) - multiply_add_to_node_vars!(surface_flux_values, - -limiting_factor[mortar], - flux_local_low_order, equations, dg, i, - direction_large, large_element) - multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], - flux_local_high_order, equations, dg, i, - direction_large, large_element) - end - end + # @trixi_timeit timer() "blend_mortar_flux!" blend_mortar_flux!(surface_flux_values, + # surface_flux_values_high_order, + # limiting_factor, + # equations, dg, cache) return nothing end +# @inline function blend_mortar_flux!(surface_flux_values, surface_flux_values_high_order, +# limiting_factor, equations, dg, cache) +# @unpack orientations = cache.mortars + +# for mortar in eachmortar(dg, cache) +# if isapprox(limiting_factor[mortar], zero(eltype(limiting_factor))) +# continue +# end +# large_element = cache.mortars.neighbor_ids[3, mortar] +# upper_element = cache.mortars.neighbor_ids[2, mortar] +# lower_element = cache.mortars.neighbor_ids[1, mortar] + +# # Calculate fluxes +# orientation = orientations[mortar] + +# if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side +# if orientation == 1 +# # L2 mortars in x-direction +# direction_small = 1 +# direction_large = 2 +# else +# # L2 mortars in y-direction +# direction_small = 3 +# direction_large = 4 +# end +# else # large_sides[mortar] == 2 -> small elements on left side +# if orientation == 1 +# # L2 mortars in x-direction +# direction_small = 2 +# direction_large = 1 +# else +# # L2 mortars in y-direction +# direction_small = 4 +# direction_large = 3 +# end +# end + +# for i in eachnode(dg) +# flux_local_high_order = view(surface_flux_values_high_order, :, i, +# direction_small, lower_element) +# flux_local_low_order = view(surface_flux_values, :, i, direction_small, +# lower_element) +# multiply_add_to_node_vars!(surface_flux_values, +# -limiting_factor[mortar], +# flux_local_low_order, equations, dg, i, +# direction_small, lower_element) +# multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], +# flux_local_high_order, equations, dg, i, +# direction_small, lower_element) + +# flux_local_high_order = view(surface_flux_values_high_order, :, i, +# direction_small, upper_element) +# flux_local_low_order = view(surface_flux_values, :, i, direction_small, +# upper_element) +# multiply_add_to_node_vars!(surface_flux_values, +# -limiting_factor[mortar], +# flux_local_low_order, equations, dg, i, +# direction_small, upper_element) +# multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], +# flux_local_high_order, equations, dg, i, +# direction_small, upper_element) + +# flux_local_high_order = view(surface_flux_values_high_order, :, i, +# direction_large, large_element) +# flux_local_low_order = view(surface_flux_values, :, i, direction_large, +# large_element) +# multiply_add_to_node_vars!(surface_flux_values, +# -limiting_factor[mortar], +# flux_local_low_order, equations, dg, i, +# direction_large, large_element) +# multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], +# flux_local_high_order, equations, dg, i, +# direction_large, large_element) +# end +# end + +# return nothing +# end + function calc_mortar_flux_low_order!(surface_flux_values, mesh::TreeMesh{2}, nonconservative_terms::False, equations, From e3bd327739de28f7045d7f98db924aed1e43fa5e Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 16 May 2025 12:03:26 +0200 Subject: [PATCH 017/102] Change orientation of limiting_factor --- .../subcell_limiter_idp_correction_2d.jl | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index a7ac1e79dc0..779fe8d2b60 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -56,13 +56,13 @@ end ############################ # TODO: Calculate blending factor for mortar fluxes (; limiting_factor) = cache.mortars - limiting_factor .= zero(eltype(limiting_factor)) - # limiting_factor = 1 => full DG - # limiting_factor = 0 => full FV + limiting_factor .= one(eltype(limiting_factor)) + # limiting_factor = 1 => full FV + # limiting_factor = 0 => full DG ####################### for mortar in eachmortar(dg, cache) - if isapprox(limiting_factor[mortar], zero(eltype(limiting_factor))) + if isapprox(limiting_factor[mortar], one(eltype(limiting_factor))) continue end large_element = cache.mortars.neighbor_ids[3, mortar] @@ -121,8 +121,10 @@ end for v in eachvariable(equations) u[v, indices_small..., lower_element] += dt * inverse_jacobian_lower * - (factor_small * limiting_factor[mortar] * - (flux_local_high_order[v] - flux_local_low_order[v])) + (factor_small * + (1 - limiting_factor[mortar]) * + (flux_local_high_order[v] - + flux_local_low_order[v])) end flux_local_high_order = view(surface_flux_values_high_order, :, i, @@ -131,8 +133,10 @@ end upper_element) for v in eachvariable(equations) u[v, indices_small..., upper_element] += dt * inverse_jacobian_upper * - (factor_small * limiting_factor[mortar] * - (flux_local_high_order[v] - flux_local_low_order[v])) + (factor_small * + (1 - limiting_factor[mortar]) * + (flux_local_high_order[v] - + flux_local_low_order[v])) end flux_local_high_order = view(surface_flux_values_high_order, :, i, @@ -141,8 +145,10 @@ end large_element) for v in eachvariable(equations) u[v, indices_large..., large_element] += dt * inverse_jacobian_large * - (factor_large * limiting_factor[mortar] * - (flux_local_high_order[v] - flux_local_low_order[v])) + (factor_large * + (1 - limiting_factor[mortar]) * + (flux_local_high_order[v] - + flux_local_low_order[v])) end end end From b792acfbcc1318a63e3a4a2a131d93bff0da20bc Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 20 May 2025 14:41:57 +0200 Subject: [PATCH 018/102] Clearify variable names --- .../subcell_limiter_idp_correction_2d.jl | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 779fe8d2b60..acec0c4b064 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -103,6 +103,8 @@ end factor_large = boundary_interpolation[1, 1] factor_small = -boundary_interpolation[nnodes(dg), 2] end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, mesh, indices_small..., upper_element) @@ -114,41 +116,43 @@ end large_element) # lower element - flux_local_high_order = view(surface_flux_values_high_order, :, i, + flux_lower_high_order = view(surface_flux_values_high_order, :, i, direction_small, lower_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_small, + flux_lower_low_order = view(surface_flux_values, :, i, direction_small, lower_element) + flux_difference_lower = factor_small * + (flux_lower_high_order .- flux_lower_low_order) for v in eachvariable(equations) u[v, indices_small..., lower_element] += dt * inverse_jacobian_lower * - (factor_small * - (1 - limiting_factor[mortar]) * - (flux_local_high_order[v] - - flux_local_low_order[v])) + (1 - limiting_factor[mortar]) * + flux_difference_lower[v] end - flux_local_high_order = view(surface_flux_values_high_order, :, i, + flux_upper_high_order = view(surface_flux_values_high_order, :, i, direction_small, upper_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_small, + flux_upper_low_order = view(surface_flux_values, :, i, direction_small, upper_element) + flux_difference_upper = factor_small * + (flux_upper_high_order .- flux_upper_low_order) + for v in eachvariable(equations) u[v, indices_small..., upper_element] += dt * inverse_jacobian_upper * - (factor_small * - (1 - limiting_factor[mortar]) * - (flux_local_high_order[v] - - flux_local_low_order[v])) + (1 - limiting_factor[mortar]) * + flux_difference_upper[v] end - flux_local_high_order = view(surface_flux_values_high_order, :, i, + flux_large_high_order = view(surface_flux_values_high_order, :, i, direction_large, large_element) - flux_local_low_order = view(surface_flux_values, :, i, direction_large, + flux_large_low_order = view(surface_flux_values, :, i, direction_large, large_element) + flux_difference_large = factor_large * + (flux_large_high_order .- flux_large_low_order) + for v in eachvariable(equations) u[v, indices_large..., large_element] += dt * inverse_jacobian_large * - (factor_large * - (1 - limiting_factor[mortar]) * - (flux_local_high_order[v] - - flux_local_low_order[v])) + (1 - limiting_factor[mortar]) * + flux_difference_large[v] end end end From c710d1dbd5e91376f415d9cb88863a14a34c5887 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 20 May 2025 15:20:28 +0200 Subject: [PATCH 019/102] Add computation of limiting factor --- .../subcell_limiter_idp_correction.jl | 2 + .../subcell_limiter_idp_correction_2d.jl | 172 +++++++++++++++++- .../dgsem_tree/dg_2d_subcell_limiters.jl | 89 --------- 3 files changed, 165 insertions(+), 98 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction.jl b/src/callbacks_stage/subcell_limiter_idp_correction.jl index efe78c9a25d..3fcd566e885 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction.jl @@ -56,6 +56,8 @@ function (limiter!::SubcellLimiterIDPCorrection)(u_ode, semi, t, dt, perform_idp_correction!(u, dt, mesh, equations, solver, cache) if solver.mortar isa Trixi.LobattoLegendreMortarIDP + @trixi_timeit timer() "calc_limiting_factor!" calc_limiting_factor!(u, semi, t, dt) + @trixi_timeit timer() "blend_mortar_flux!" blend_mortar_flux!(u, semi, equations, solver, t, dt) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index acec0c4b064..cfac83c62a3 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -46,21 +46,175 @@ function perform_idp_correction!(u, dt, return nothing end +@inline function calc_limiting_factor!(u, semi, t, dt) + mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + + (; limiting_factor, orientations) = cache.mortars + (; surface_flux_values, surface_flux_values_high_order) = cache.elements + (; boundary_interpolation) = dg.basis + + limiting_factor .= zeros(eltype(limiting_factor)) + + (; positivity_correction_factor) = dg.volume_integral.limiter + + index_rho = 1 # TODO + + for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Calc minimal low-order solution + var_min_upper = typemax(eltype(surface_flux_values)) + var_min_lower = typemax(eltype(surface_flux_values)) + var_min_large = typemax(eltype(surface_flux_values)) + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + end + end + var_upper = u[index_rho, indices_small..., upper_element] + var_lower = u[index_rho, indices_small..., lower_element] + var_large = u[index_rho, indices_large..., large_element] + var_min_upper = min(var_min_upper, var_upper) + var_min_lower = min(var_min_lower, var_lower) + var_min_large = min(var_min_large, var_large) + end + var_min_upper = positivity_correction_factor * var_min_upper + var_min_lower = positivity_correction_factor * var_min_lower + var_min_large = positivity_correction_factor * var_min_large + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + direction_small = 3 + direction_large = 4 + end + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + direction_small = 4 + direction_large = 3 + end + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + + var_upper = u[index_rho, indices_small..., upper_element] + var_lower = u[index_rho, indices_small..., lower_element] + var_large = u[index_rho, indices_large..., large_element] + + if min(var_upper, var_lower, var_large) < 0 + error("Safe low-order method produces negative value for conservative variable rho. Try a smaller time step.") + end + + inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + upper_element) + inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + lower_element) + inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_large..., + large_element) + + # Calculate Pm + flux_lower_high_order = surface_flux_values_high_order[index_rho, i, + direction_small, + lower_element] + flux_lower_low_order = surface_flux_values[index_rho, i, direction_small, + lower_element] + flux_difference_lower = factor_small * + (flux_lower_high_order - flux_lower_low_order) + + flux_upper_high_order = surface_flux_values_high_order[index_rho, i, + direction_small, + upper_element] + flux_upper_low_order = surface_flux_values[index_rho, i, direction_small, + upper_element] + flux_difference_upper = factor_small * + (flux_upper_high_order - flux_upper_low_order) + + flux_large_high_order = surface_flux_values_high_order[index_rho, i, + direction_large, + large_element] + flux_large_low_order = surface_flux_values_high_order[index_rho, i, + direction_large, + large_element] + flux_difference_large = factor_large * + (flux_large_high_order - flux_large_low_order) + + Qm_upper = min(0, var_min_upper - var_upper) + Qm_lower = min(0, var_min_lower - var_lower) + Qm_large = min(0, var_min_large - var_large) + + Pm_upper = min(0, flux_difference_upper) + Pm_lower = min(0, flux_difference_lower) + Pm_large = min(0, flux_difference_large) + + Pm_upper = dt * inverse_jacobian_upper * Pm_upper + Pm_lower = dt * inverse_jacobian_lower * Pm_lower + Pm_large = dt * inverse_jacobian_large * Pm_large + + # Compute blending coefficient avoiding division by zero + # (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8)) + Qm_upper = abs(Qm_upper) / (abs(Pm_upper) + eps(typeof(Qm_upper)) * 100) + Qm_lower = abs(Qm_lower) / (abs(Pm_lower) + eps(typeof(Qm_lower)) * 100) + Qm_large = abs(Qm_large) / (abs(Pm_large) + eps(typeof(Qm_large)) * 100) + + limiting_factor[mortar] = max(limiting_factor[mortar], 1 - Qm_upper, + 1 - Qm_lower, 1 - Qm_large) + end + end + + return nothing +end + @inline function blend_mortar_flux!(u, semi, equations, dg, t, dt) (; mesh, cache) = semi - (; orientations) = cache.mortars + (; orientations, limiting_factor) = cache.mortars (; surface_flux_values, surface_flux_values_high_order) = cache.elements (; boundary_interpolation) = dg.basis - ############################ - # TODO: Calculate blending factor for mortar fluxes - (; limiting_factor) = cache.mortars - limiting_factor .= one(eltype(limiting_factor)) - # limiting_factor = 1 => full FV - # limiting_factor = 0 => full DG - ####################### - for mortar in eachmortar(dg, cache) if isapprox(limiting_factor[mortar], one(eltype(limiting_factor))) continue diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 9de9a3de863..ea5784d2cf9 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -757,98 +757,9 @@ function calc_mortar_flux!(surface_flux_values, mesh, dg.surface_integral, dg, cache) - # (; limiting_factor) = cache.mortars - # limiting_factor .= zero(eltype(limiting_factor)) - # # limiting_factor = 1 => full DG - # # limiting_factor = 0 => full FV - - # @trixi_timeit timer() "blend_mortar_flux!" blend_mortar_flux!(surface_flux_values, - # surface_flux_values_high_order, - # limiting_factor, - # equations, dg, cache) - return nothing end -# @inline function blend_mortar_flux!(surface_flux_values, surface_flux_values_high_order, -# limiting_factor, equations, dg, cache) -# @unpack orientations = cache.mortars - -# for mortar in eachmortar(dg, cache) -# if isapprox(limiting_factor[mortar], zero(eltype(limiting_factor))) -# continue -# end -# large_element = cache.mortars.neighbor_ids[3, mortar] -# upper_element = cache.mortars.neighbor_ids[2, mortar] -# lower_element = cache.mortars.neighbor_ids[1, mortar] - -# # Calculate fluxes -# orientation = orientations[mortar] - -# if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side -# if orientation == 1 -# # L2 mortars in x-direction -# direction_small = 1 -# direction_large = 2 -# else -# # L2 mortars in y-direction -# direction_small = 3 -# direction_large = 4 -# end -# else # large_sides[mortar] == 2 -> small elements on left side -# if orientation == 1 -# # L2 mortars in x-direction -# direction_small = 2 -# direction_large = 1 -# else -# # L2 mortars in y-direction -# direction_small = 4 -# direction_large = 3 -# end -# end - -# for i in eachnode(dg) -# flux_local_high_order = view(surface_flux_values_high_order, :, i, -# direction_small, lower_element) -# flux_local_low_order = view(surface_flux_values, :, i, direction_small, -# lower_element) -# multiply_add_to_node_vars!(surface_flux_values, -# -limiting_factor[mortar], -# flux_local_low_order, equations, dg, i, -# direction_small, lower_element) -# multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], -# flux_local_high_order, equations, dg, i, -# direction_small, lower_element) - -# flux_local_high_order = view(surface_flux_values_high_order, :, i, -# direction_small, upper_element) -# flux_local_low_order = view(surface_flux_values, :, i, direction_small, -# upper_element) -# multiply_add_to_node_vars!(surface_flux_values, -# -limiting_factor[mortar], -# flux_local_low_order, equations, dg, i, -# direction_small, upper_element) -# multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], -# flux_local_high_order, equations, dg, i, -# direction_small, upper_element) - -# flux_local_high_order = view(surface_flux_values_high_order, :, i, -# direction_large, large_element) -# flux_local_low_order = view(surface_flux_values, :, i, direction_large, -# large_element) -# multiply_add_to_node_vars!(surface_flux_values, -# -limiting_factor[mortar], -# flux_local_low_order, equations, dg, i, -# direction_large, large_element) -# multiply_add_to_node_vars!(surface_flux_values, limiting_factor[mortar], -# flux_local_high_order, equations, dg, i, -# direction_large, large_element) -# end -# end - -# return nothing -# end - function calc_mortar_flux_low_order!(surface_flux_values, mesh::TreeMesh{2}, nonconservative_terms::False, equations, From 777ce3908b52542862efc969f69780737f0b1c92 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 20 May 2025 15:55:34 +0200 Subject: [PATCH 020/102] Use `get_node_vars` and `multiply_add_to_node_vars!` --- .../subcell_limiter_idp_correction.jl | 3 +- .../subcell_limiter_idp_correction_2d.jl | 61 ++++++++++--------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction.jl b/src/callbacks_stage/subcell_limiter_idp_correction.jl index 3fcd566e885..42366326aa1 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction.jl @@ -56,7 +56,8 @@ function (limiter!::SubcellLimiterIDPCorrection)(u_ode, semi, t, dt, perform_idp_correction!(u, dt, mesh, equations, solver, cache) if solver.mortar isa Trixi.LobattoLegendreMortarIDP - @trixi_timeit timer() "calc_limiting_factor!" calc_limiting_factor!(u, semi, t, dt) + @trixi_timeit timer() "calc_limiting_factor!" calc_limiting_factor!(u, semi, + t, dt) @trixi_timeit timer() "blend_mortar_flux!" blend_mortar_flux!(u, semi, equations, solver, diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index cfac83c62a3..5d2d4b84334 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -270,44 +270,47 @@ end large_element) # lower element - flux_lower_high_order = view(surface_flux_values_high_order, :, i, - direction_small, lower_element) - flux_lower_low_order = view(surface_flux_values, :, i, direction_small, - lower_element) + flux_lower_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_small, + lower_element) + flux_lower_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_small, lower_element) flux_difference_lower = factor_small * (flux_lower_high_order .- flux_lower_low_order) - for v in eachvariable(equations) - u[v, indices_small..., lower_element] += dt * inverse_jacobian_lower * - (1 - limiting_factor[mortar]) * - flux_difference_lower[v] - end - - flux_upper_high_order = view(surface_flux_values_high_order, :, i, - direction_small, upper_element) - flux_upper_low_order = view(surface_flux_values, :, i, direction_small, - upper_element) + multiply_add_to_node_vars!(u, + dt * inverse_jacobian_lower * + (1 - limiting_factor[mortar]), + flux_difference_lower, equations, dg, + indices_small..., lower_element) + + flux_upper_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_small, + upper_element) + flux_upper_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_small, upper_element) flux_difference_upper = factor_small * (flux_upper_high_order .- flux_upper_low_order) - for v in eachvariable(equations) - u[v, indices_small..., upper_element] += dt * inverse_jacobian_upper * - (1 - limiting_factor[mortar]) * - flux_difference_upper[v] - end - - flux_large_high_order = view(surface_flux_values_high_order, :, i, - direction_large, large_element) - flux_large_low_order = view(surface_flux_values, :, i, direction_large, - large_element) + multiply_add_to_node_vars!(u, + dt * inverse_jacobian_upper * + (1 - limiting_factor[mortar]), + flux_difference_upper, equations, dg, + indices_small..., upper_element) + + flux_large_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_large, + large_element) + flux_large_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_large, large_element) flux_difference_large = factor_large * (flux_large_high_order .- flux_large_low_order) - for v in eachvariable(equations) - u[v, indices_large..., large_element] += dt * inverse_jacobian_large * - (1 - limiting_factor[mortar]) * - flux_difference_large[v] - end + multiply_add_to_node_vars!(u, + dt * inverse_jacobian_large * + (1 - limiting_factor[mortar]), + flux_difference_large, equations, dg, + indices_large..., large_element) end end From 96b10d53b49418d4161dabe85a97205750840a0e Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 21 May 2025 08:47:14 +0200 Subject: [PATCH 021/102] Disabled limiting to ensure tests are passing --- src/callbacks_stage/subcell_limiter_idp_correction_2d.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 5d2d4b84334..fa9f2c6dd93 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -215,6 +215,13 @@ end (; surface_flux_values, surface_flux_values_high_order) = cache.elements (; boundary_interpolation) = dg.basis + limiting_factor .= one(eltype(limiting_factor)) + + ############################ + # limiting_factor = 1 => full FV + # limiting_factor = 0 => full DG + ####################### + for mortar in eachmortar(dg, cache) if isapprox(limiting_factor[mortar], one(eltype(limiting_factor))) continue From d28919ae18d8a8414e6b192a9e443e21de1b205d Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 26 May 2025 17:41:38 +0200 Subject: [PATCH 022/102] fmt --- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index ea5784d2cf9..d805e9f5b15 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -744,8 +744,7 @@ function calc_mortar_flux!(surface_flux_values, mesh, equations, mortar_idp, surface_integral, - dg, - cache) + dg, cache) # high order fluxes (; surface_flux_values_high_order) = cache.elements From 4318c6c139eee7bd3a11e403a959316908ad9154 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 27 May 2025 17:50:29 +0200 Subject: [PATCH 023/102] Add el diablo elixir --- ...elixir_euler_convergence_amr_sc_subcell.jl | 3 - ...lixir_euler_density_wave_amr_sc_subcell.jl | 89 +++++++++++++++++++ .../subcell_limiter_idp_correction_2d.jl | 1 - .../dgsem_tree/dg_2d_subcell_limiters.jl | 3 +- test/test_tree_2d_euler.jl | 25 ++++++ 5 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index 0bba9b31b71..465d003c92b 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -6,8 +6,6 @@ using Trixi equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test -# initial_condition = Trixi.initial_condition_density_wave_highdensity -# initial_condition = Trixi.initial_condition_density_wave # initial_condition = initial_condition_constant surface_flux = flux_lax_friedrichs @@ -16,7 +14,6 @@ polydeg = 3 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], - # positivity_correction_factor = 0.5, positivity_variables_nonlinear = [pressure], # local_twosided_variables_cons = ["rho"], # local_onesided_variables_nonlinear = [(Trixi.entropy_math, diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl new file mode 100644 index 00000000000..a7028b77257 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl @@ -0,0 +1,89 @@ +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +# initial_condition = Trixi.initial_condition_density_wave_highdensity +initial_condition = Trixi.initial_condition_density_wave + +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) +limiter_idp = SubcellLimiterIDP(equations, basis; + positivity_variables_cons = ["rho"], + # positivity_variables_nonlinear = [pressure], + ) +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) + +mortar = Trixi.MortarIDP(basis; alternative = false, local_factor = true, + first_order = true) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +refinement_patches = ((type = "box", coordinates_min = (0.0, -0.5), + coordinates_max = (0.5, 0.5)),) +initial_refinement_level = 4 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = initial_refinement_level, + n_cells_max = 10_000, + refinement_patches = refinement_patches, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +# amr_indicator = IndicatorMax(semi, variable = first) + +# amr_controller = ControllerThreeLevel(semi, amr_indicator, +# base_level = initial_refinement_level, +# med_level = initial_refinement_level + 1, med_threshold = 2.0, +# max_level = initial_refinement_level + 2, max_threshold = 2.05) + +# amr_callback = AMRCallback(semi, amr_controller, +# interval = 1, +# adapt_initial_condition = true, +# adapt_initial_condition_only_refine = false) + +stepsize_callback = StepsizeCallback(cfl = 0.8) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + # amr_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = true)) + +sol = Trixi.solve(ode, + # Trixi.SimpleEuler(stage_callbacks = stage_callbacks); + Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., + callback = callbacks); diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index fa9f2c6dd93..eb0b244d2e9 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -216,7 +216,6 @@ end (; boundary_interpolation) = dg.basis limiting_factor .= one(eltype(limiting_factor)) - ############################ # limiting_factor = 1 => full FV # limiting_factor = 0 => full DG diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index d805e9f5b15..ea5784d2cf9 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -744,7 +744,8 @@ function calc_mortar_flux!(surface_flux_values, mesh, equations, mortar_idp, surface_integral, - dg, cache) + dg, + cache) # high order fluxes (; surface_flux_values_high_order) = cache.elements diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 152853cfd0e..36205aefdf3 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -204,6 +204,31 @@ end end end +@trixi_testset "elixir_euler_density_wave_amr_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave_amr_sc_subcell.jl"), + initial_refinement_level=2, + l2=[ + 0.10807450110533318, + 0.010807450110533516, + 0.021614900221066703, + 0.0027018625276321187 + ], + linf=[ + 0.5774808056100666, + 0.057748080561012716, + 0.11549616112202077, + 0.014437020140242396 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end +end + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic.jl"), From ceb47e33549f63a9142c602a8349f34e2a45b97f Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 27 May 2025 18:15:37 +0200 Subject: [PATCH 024/102] Activate limiting --- src/callbacks_stage/subcell_limiter_idp_correction_2d.jl | 2 +- test/test_tree_2d_euler.jl | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index eb0b244d2e9..fa511f6cf86 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -215,7 +215,7 @@ end (; surface_flux_values, surface_flux_values_high_order) = cache.elements (; boundary_interpolation) = dg.basis - limiting_factor .= one(eltype(limiting_factor)) + # limiting_factor .= one(eltype(limiting_factor)) ############################ # limiting_factor = 1 => full FV # limiting_factor = 0 => full DG diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 36205aefdf3..125e82b0456 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -205,7 +205,8 @@ end end @trixi_testset "elixir_euler_density_wave_amr_sc_subcell.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave_amr_sc_subcell.jl"), + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_density_wave_amr_sc_subcell.jl"), initial_refinement_level=2, l2=[ 0.10807450110533318, From 8b9f22304a100c415436166e047d377cfc5198de Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 27 May 2025 18:16:50 +0200 Subject: [PATCH 025/102] Add advection elixir --- .../elixir_advection_sc_subcell.jl | 75 +++++++++++++++++++ test/test_tree_2d_advection.jl | 20 +++++ 2 files changed, 95 insertions(+) create mode 100644 examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl diff --git a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl new file mode 100644 index 00000000000..e5dc6490be0 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl @@ -0,0 +1,75 @@ +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +advection_velocity = (0.2, -0.7) +equations = LinearScalarAdvectionEquation2D(advection_velocity) + +initial_condition = initial_condition_convergence_test + +surface_flux = flux_lax_friedrichs +volume_flux = flux_godunov +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) +limiter_idp = SubcellLimiterIDP(equations, basis; + positivity_variables_cons = ["first"], + ) +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) + +mortar = Trixi.MortarIDP(basis; alternative = false, local_factor = true, + first_order = true) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +refinement_patches = ((type = "box", coordinates_min = (0.0, -0.5), + coordinates_max = (0.5, 0.5)),) +initial_refinement_level = 4 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = initial_refinement_level, + n_cells_max = 10_000, + refinement_patches = refinement_patches, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = true)) + +sol = Trixi.solve(ode, + # Trixi.SimpleEuler(stage_callbacks = stage_callbacks); + Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., + callback = callbacks); diff --git a/test/test_tree_2d_advection.jl b/test/test_tree_2d_advection.jl index f0ec24ccca2..fbf0ac3c38c 100644 --- a/test/test_tree_2d_advection.jl +++ b/test/test_tree_2d_advection.jl @@ -78,6 +78,26 @@ end end end +@trixi_testset "elixir_advection_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_sc_subcell.jl"), + initial_refinement_level=2, + l2=[0.11844732532829574], + linf=[0.18555183309197099]) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + @trixi_testset "elixir_advection_amr.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), # Expected errors are exactly the same as in the parallel test! From 5d2152b385a310373f9d2557f794f2eb3ef0a8a1 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 27 May 2025 18:45:42 +0200 Subject: [PATCH 026/102] Adapt tests; add option to use pure low order mortars --- .../elixir_advection_sc_subcell.jl | 3 +- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 2 +- .../subcell_limiter_idp_correction_2d.jl | 12 +++-- src/solvers/dgsem/basis_lobatto_legendre.jl | 6 ++- test/test_tree_2d_euler.jl | 50 +++++++++---------- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl index e5dc6490be0..5b8d7466974 100644 --- a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl @@ -13,8 +13,7 @@ volume_flux = flux_godunov polydeg = 3 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; - positivity_variables_cons = ["first"], - ) + positivity_variables_cons = ["first"],) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index f009c760c37..535182c8eb1 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -52,7 +52,7 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) mortar = Trixi.MortarIDP(basis, alternative = false, local_factor = true, - first_order = true) + first_order = true, pure_low_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index fa511f6cf86..e822b888a9a 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -215,11 +215,13 @@ end (; surface_flux_values, surface_flux_values_high_order) = cache.elements (; boundary_interpolation) = dg.basis - # limiting_factor .= one(eltype(limiting_factor)) - ############################ - # limiting_factor = 1 => full FV - # limiting_factor = 0 => full DG - ####################### + if semi.solver.mortar.pure_low_order + limiting_factor .= one(eltype(limiting_factor)) + ############################ + # limiting_factor = 1 => full FV + # limiting_factor = 0 => full DG + ####################### + end for mortar in eachmortar(dg, cache) if isapprox(limiting_factor[mortar], one(eltype(limiting_factor))) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 27c9a02e3aa..14056d93ed9 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -147,6 +147,7 @@ right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: AbstractMortarL2{RealT} + pure_low_order::Bool local_factor::Bool mortar_l2::Mortar local_mortar_weights::Matrix{RealT} @@ -164,7 +165,7 @@ struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, Mortar, end function MortarIDP(basis::LobattoLegendreBasis; alternative = false, - local_factor = true, first_order = true) + local_factor = true, first_order = true, pure_low_order = false) RealT = real(basis) nnodes_ = nnodes(basis) @@ -188,7 +189,8 @@ function MortarIDP(basis::LobattoLegendreBasis; alternative = false, reverse_upper_low_order, reverse_lower_low_order) else - LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(local_factor, + LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(pure_low_order, + local_factor, mortar_l2, local_mortar_weights) end diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 125e82b0456..07e2ddd2010 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -93,16 +93,16 @@ end "elixir_euler_convergence_amr_sc_subcell.jl"), alternative=false, local_factor=false, l2=[ - 0.006254682457071628, - 0.004999416955608761, - 0.0048141964689589105, - 0.009972111077049216 + 2.4110239842278583e-6, + 2.154630267786356e-6, + 2.1283762418750586e-6, + 6.113773134527389e-6 ], linf=[ - 0.04931632769799399, - 0.03556213775967221, - 0.033495743728496175, - 0.07783921049430154 + 1.696740325396462e-5, + 1.695721640926351e-5, + 1.656799716243107e-5, + 5.129734239561756e-5 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -123,16 +123,16 @@ end "elixir_euler_convergence_amr_sc_subcell.jl"), alternative=false, local_factor=true, first_order=true, l2=[ - 0.001058120516341185, - 0.0009133976065639385, - 0.0008810204004781092, - 0.001891369160213655 + 2.411023984333364e-6, + 2.154630267960894e-6, + 2.1283762418214893e-6, + 6.113773134665568e-6 ], linf=[ - 0.017886304148701182, - 0.012224853173556927, - 0.015594531468539952, - 0.03165281612069837 + 1.6967403261958225e-5, + 1.6957216406598974e-5, + 1.6567997161320847e-5, + 5.1297342376521726e-5 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -154,16 +154,16 @@ end alternative=false, local_factor=true, first_order=false, # Note: Not conservative l2=[ - 0.001315104732325498, - 0.0011661998948194854, - 0.0010984957991275826, - 0.0023024968589529945 + 2.411023984473762e-6, + 2.154630268012045e-6, + 2.128376241902082e-6, + 6.113773134790818e-6 ], linf=[ - 0.01058518982128076, - 0.010040635205911919, - 0.009834772741424747, - 0.02053636644232526 + 1.696740326551094e-5, + 1.6957216412816223e-5, + 1.6567997168426274e-5, + 5.129734239517347e-5 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -226,7 +226,7 @@ end t = sol.t[end] u_ode = sol.u[end] du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 end end From 6dce7edb69b18e0e1b10c6119c38246a10756a8d Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 28 May 2025 10:59:24 +0200 Subject: [PATCH 027/102] Add limiting factor to solution callback --- examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl | 3 ++- .../tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl | 3 ++- .../tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl | 3 ++- .../tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl index 5b8d7466974..520280fc42c 100644 --- a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl @@ -52,7 +52,8 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) save_solution = SaveSolutionCallback(interval = 1, save_initial_solution = true, save_final_solution = true, - solution_variables = cons2prim) + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) stepsize_callback = StepsizeCallback(cfl = 0.5) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 535182c8eb1..c3d206866f1 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -84,7 +84,8 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) save_solution = SaveSolutionCallback(interval = 100, save_initial_solution = true, save_final_solution = true, - solution_variables = cons2prim) + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) # amr_indicator = IndicatorMax(semi, variable = first) diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index 465d003c92b..5ffe3e09cba 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -58,7 +58,8 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) save_solution = SaveSolutionCallback(interval = 100, save_initial_solution = true, save_final_solution = true, - solution_variables = cons2prim) + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) # amr_indicator = IndicatorMax(semi, variable = first) diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl index a7028b77257..f184f7bd3c3 100644 --- a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl @@ -54,7 +54,8 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) save_solution = SaveSolutionCallback(interval = 100, save_initial_solution = true, save_final_solution = true, - solution_variables = cons2prim) + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) # amr_indicator = IndicatorMax(semi, variable = first) From 1750fc485ff1ac479dc3b43188c2a0f6bc5d9cc1 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 28 May 2025 12:25:46 +0200 Subject: [PATCH 028/102] Small changes --- .../subcell_limiter_idp_correction.jl | 3 ++- .../subcell_limiter_idp_correction_2d.jl | 8 ------- src/solvers/dgsem/basis_lobatto_legendre.jl | 21 +++++++++++-------- .../dgsem_tree/dg_2d_subcell_limiters.jl | 13 +----------- 4 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction.jl b/src/callbacks_stage/subcell_limiter_idp_correction.jl index 42366326aa1..e6d06c84bc2 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction.jl @@ -55,7 +55,8 @@ function (limiter!::SubcellLimiterIDPCorrection)(u_ode, semi, t, dt, perform_idp_correction!(u, dt, mesh, equations, solver, cache) - if solver.mortar isa Trixi.LobattoLegendreMortarIDP + if solver.mortar isa Trixi.LobattoLegendreMortarIDP && + !(solver.mortar.pure_low_order) @trixi_timeit timer() "calc_limiting_factor!" calc_limiting_factor!(u, semi, t, dt) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index e822b888a9a..5d2d4b84334 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -215,14 +215,6 @@ end (; surface_flux_values, surface_flux_values_high_order) = cache.elements (; boundary_interpolation) = dg.basis - if semi.solver.mortar.pure_low_order - limiting_factor .= one(eltype(limiting_factor)) - ############################ - # limiting_factor = 1 => full FV - # limiting_factor = 0 => full DG - ####################### - end - for mortar in eachmortar(dg, cache) if isapprox(limiting_factor[mortar], one(eltype(limiting_factor))) continue diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 14056d93ed9..d062c56fba7 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -171,16 +171,16 @@ function MortarIDP(basis::LobattoLegendreBasis; alternative = false, mortar_l2 = MortarL2(basis) - local_mortar_weights = calc_mortar_weights(basis, RealT; first_order = first_order) - - forward_upper_low_order = calc_forward_upper_low_order(nnodes_, RealT) - forward_lower_low_order = calc_forward_lower_low_order(nnodes_, RealT) - reverse_upper_low_order = calc_reverse_upper_low_order(nnodes_, Val(:gauss_lobatto), - RealT) - reverse_lower_low_order = calc_reverse_lower_low_order(nnodes_, Val(:gauss_lobatto), - RealT) - if alternative + forward_upper_low_order = calc_forward_upper_low_order(nnodes_, RealT) + forward_lower_low_order = calc_forward_lower_low_order(nnodes_, RealT) + reverse_upper_low_order = calc_reverse_upper_low_order(nnodes_, + Val(:gauss_lobatto), + RealT) + reverse_lower_low_order = calc_reverse_lower_low_order(nnodes_, + Val(:gauss_lobatto), + RealT) + LobattoLegendreMortarIDPAlternative{RealT, nnodes_, typeof(mortar_l2), typeof(forward_upper_low_order), typeof(reverse_upper_low_order)}(mortar_l2, @@ -189,6 +189,9 @@ function MortarIDP(basis::LobattoLegendreBasis; alternative = false, reverse_upper_low_order, reverse_lower_low_order) else + local_mortar_weights = calc_mortar_weights(basis, RealT; + first_order = first_order) + LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(pure_low_order, local_factor, mortar_l2, diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index ea5784d2cf9..812bfceec94 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -675,6 +675,7 @@ function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, mortar_idp::LobattoLegendreMortarIDP, dg::DGSEM) prolong2mortars!(cache, u, mesh, equations, mortar_idp.mortar_l2, dg) + # The data of both small elements were already copied to the mortar cache @threaded for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] # upper_element = cache.mortars.neighbor_ids[2, mortar] @@ -686,8 +687,6 @@ function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, # IDP mortars in x-direction for l in eachnode(dg) for v in eachvariable(equations) - # cache.mortars.u_upper[v, l, mortar] = u[v, 1, l, upper_element] - # cache.mortars.u_lower[v, l, mortar] = u[v, 1, l, lower_element] cache.mortars.u_large[v, l, mortar] = u[v, nnodes(dg), l, large_element] end @@ -696,8 +695,6 @@ function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, # IDP mortars in y-direction for l in eachnode(dg) for v in eachvariable(equations) - # cache.mortars.u_upper[v, l, mortar] = u[v, l, 1, upper_element] - # cache.mortars.u_lower[v, l, mortar] = u[v, l, 1, lower_element] cache.mortars.u_large[v, l, mortar] = u[v, l, nnodes(dg), large_element] end @@ -708,10 +705,6 @@ function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, # IDP mortars in x-direction for l in eachnode(dg) for v in eachvariable(equations) - # cache.mortars.u_upper[v, l, mortar] = u[v, nnodes(dg), l, - # upper_element] - # cache.mortars.u_lower[v, l, mortar] = u[v, nnodes(dg), l, - # lower_element] cache.mortars.u_large[v, l, mortar] = u[v, 1, l, large_element] end end @@ -719,10 +712,6 @@ function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, # IDP mortars in y-direction for l in eachnode(dg) for v in eachvariable(equations) - # cache.mortars.u_upper[v, l, mortar] = u[v, l, nnodes(dg), - # upper_element] - # cache.mortars.u_lower[v, l, mortar] = u[v, l, nnodes(dg), - # lower_element] cache.mortars.u_large[v, l, mortar] = u[v, l, 1, large_element] end end From 80936899f31d8dea6d406b40e962a1e8650279a7 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 5 Jun 2025 12:13:23 +0200 Subject: [PATCH 029/102] Small fix --- src/solvers/dgsem_tree/containers_2d.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index 882a51f1aa4..3c6afd158af 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -668,11 +668,11 @@ function Base.resize!(mortars::IDPMortarContainer2D, capacity) resize!(_u_upper, 2 * n_variables * n_nodes * capacity) mortars.u_upper = unsafe_wrap(Array, pointer(_u_upper), - (n_variables, n_nodes, capacity)) + (2, n_variables, n_nodes, capacity)) resize!(_u_lower, 2 * n_variables * n_nodes * capacity) mortars.u_lower = unsafe_wrap(Array, pointer(_u_lower), - (n_variables, n_nodes, capacity)) + (2, n_variables, n_nodes, capacity)) resize!(_u_large, n_variables * n_nodes * capacity) mortars.u_large = unsafe_wrap(Array, pointer(_u_large), From ec6d6858f8f46230f4d9e0562411309639adb6d6 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 16 Jun 2025 10:25:35 +0200 Subject: [PATCH 030/102] Export MortarIDP --- .../tree_2d_dgsem/elixir_advection_sc_subcell.jl | 4 ++-- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 4 ++-- .../elixir_euler_convergence_amr_sc_subcell.jl | 4 ++-- .../elixir_euler_density_wave_amr_sc_subcell.jl | 4 ++-- src/Trixi.jl | 2 +- src/solvers/dgsem_p4est/subcell_limiters_2d.jl | 4 ++-- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 16 +++++++--------- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl index 520280fc42c..b7738988926 100644 --- a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl @@ -18,8 +18,8 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = Trixi.MortarIDP(basis; alternative = false, local_factor = true, - first_order = true) +mortar = MortarIDP(basis; alternative = false, local_factor = true, + first_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index c3d206866f1..5dd5077b425 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -51,8 +51,8 @@ limiter_idp = SubcellLimiterIDP(equations, basis; volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = Trixi.MortarIDP(basis, alternative = false, local_factor = true, - first_order = true, pure_low_order = true) +mortar = MortarIDP(basis, alternative = false, local_factor = true, + first_order = true, pure_low_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index 5ffe3e09cba..aa7107cbb88 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -23,8 +23,8 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = Trixi.MortarIDP(basis; alternative = false, local_factor = true, - first_order = true) +mortar = MortarIDP(basis; alternative = false, local_factor = true, + first_order = true, pure_low_order = false,) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl index f184f7bd3c3..d87a5aef75b 100644 --- a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl @@ -20,8 +20,8 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = Trixi.MortarIDP(basis; alternative = false, local_factor = true, - first_order = true) +mortar = MortarIDP(basis; alternative = false, local_factor = true, + first_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) diff --git a/src/Trixi.jl b/src/Trixi.jl index 75d2d739f59..63415c1f783 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -259,7 +259,7 @@ export DG, VolumeIntegralUpwind, SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, SurfaceIntegralUpwind, - MortarL2 + MortarL2, MortarIDP export VolumeIntegralSubcellLimiting, BoundsCheckCallback, SubcellLimiterIDP, SubcellLimiterIDPCorrection diff --git a/src/solvers/dgsem_p4est/subcell_limiters_2d.jl b/src/solvers/dgsem_p4est/subcell_limiters_2d.jl index 95267f387f9..f0e45ba475b 100644 --- a/src/solvers/dgsem_p4est/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_p4est/subcell_limiters_2d.jl @@ -114,7 +114,7 @@ end u_inner = get_node_vars(u, equations, dg, i_node, j_node, element) u_outer = get_boundary_outer_state(u_inner, t, boundary_condition, - normal_direction, + normal_direction, 1, equations, dg, cache, i_node, j_node, element) var_outer = u_outer[variable] @@ -237,7 +237,7 @@ end u_inner = get_node_vars(u, equations, dg, i_node, j_node, element) u_outer = get_boundary_outer_state(u_inner, t, boundary_condition, - normal_direction, + normal_direction, 1, equations, dg, cache, i_node, j_node, element) var_outer = variable(u_outer, equations) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 812bfceec94..dffe0644adc 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -91,15 +91,13 @@ function calc_mortar_weights(basis, RealT; first_order = true) calc_mortar_weights_second_order!(weights, basis) end - for i in 1:n_nodes - for j in 1:n_nodes - # Row - weights[i, end] += weights[i, j] - weights[i, end] += weights[i, j + n_nodes] - # Columns - weights[end, i] += weights[j, i] - weights[end, i + n_nodes] += weights[j, i + n_nodes] - end + for i in eachnode(basis), j in eachnode(basis) + # Row + weights[i, end] += weights[i, j] + weights[i, end] += weights[i, j + n_nodes] + # Columns + weights[end, i] += weights[j, i] + weights[end, i + n_nodes] += weights[j, i + n_nodes] end return weights From 6c90a4f487455946638f41cbd76c411785c230c2 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 20 Jun 2025 12:11:06 +0200 Subject: [PATCH 031/102] Implement unstable check and provisional alpha analysis --- src/callbacks_stage/positivity_zhang_shu.jl | 8 ++++++++ .../subcell_limiter_idp_correction_2d.jl | 15 +++++++++++++++ src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 2 -- src/time_integration/methods_SSP.jl | 10 ++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/callbacks_stage/positivity_zhang_shu.jl b/src/callbacks_stage/positivity_zhang_shu.jl index 92141c4b26e..528f0868d25 100644 --- a/src/callbacks_stage/positivity_zhang_shu.jl +++ b/src/callbacks_stage/positivity_zhang_shu.jl @@ -65,6 +65,14 @@ function limiter_zhang_shu!(u, thresholds::Tuple{}, variables::Tuple{}, nothing end +# Allow use of limiter as a stage callback in costum SSP integrators +function (limiter!::PositivityPreservingLimiterZhangShu)(u_ode, integrator, stage) + return limiter!(u_ode, integrator, integrator.p, integrator.t) +end +init_callback(limiter!::PositivityPreservingLimiterZhangShu, semi) = nothing +finalize_callback(limiter!::PositivityPreservingLimiterZhangShu, semi) = nothing + + include("positivity_zhang_shu_dg1d.jl") include("positivity_zhang_shu_dg2d.jl") include("positivity_zhang_shu_dg3d.jl") diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 5d2d4b84334..9b5d34f6509 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -204,6 +204,21 @@ end 1 - Qm_lower, 1 - Qm_large) end end + # Provisional analysis of limiting factor + if length(limiting_factor) > 0 + open(joinpath("out", "mortar_limiting_factor.txt"), "a") do f + print(f, t) + print(f, ", ", minimum(limiting_factor), ", ", maximum(limiting_factor), + ", ", sum(limiting_factor) / length(limiting_factor)) + println(f) + end + else + open(joinpath("out", "mortar_limiting_factor.txt"), "a") do f + print(f, t) + print(f, ", ", 0.0, ", ", 0.0, ", ", 0.0) + println(f) + end + end return nothing end diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 744b1e55df3..d32dfdd31bc 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -676,8 +676,6 @@ function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, # The data of both small elements were already copied to the mortar cache @threaded for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] - # upper_element = cache.mortars.neighbor_ids[2, mortar] - # lower_element = cache.mortars.neighbor_ids[1, mortar] # Copy solutions if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side diff --git a/src/time_integration/methods_SSP.jl b/src/time_integration/methods_SSP.jl index 0092dc880f2..bce51d88c66 100644 --- a/src/time_integration/methods_SSP.jl +++ b/src/time_integration/methods_SSP.jl @@ -247,6 +247,8 @@ function solve!(integrator::SimpleIntegratorSSP) end end + unstable_check(integrator.dt, integrator.u, integrator) + # respect maximum number of iterations if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep @warn "Interrupted. Larger maxiters is needed." @@ -285,6 +287,14 @@ function get_proposed_dt(integrator::SimpleIntegratorSSP) return ifelse(integrator.opts.adaptive, integrator.dt, integrator.dtcache) end +function unstable_check(dt, u_ode, integrator::SimpleIntegratorSSP) + if !isfinite(dt) || isnan(dt) || !all(isfinite.(u_ode)) || any(isnan.(u_ode)) + @warn "Instability detected. Aborting" + terminate!(integrator) + end + return nothing +end + # stop the time integration function terminate!(integrator::SimpleIntegratorSSP) integrator.finalstep = true From b76aa13b6d0343256db178748ba1d0cee4855fb4 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 20 Jun 2025 12:12:12 +0200 Subject: [PATCH 032/102] typo --- src/callbacks_stage/positivity_zhang_shu.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/callbacks_stage/positivity_zhang_shu.jl b/src/callbacks_stage/positivity_zhang_shu.jl index 528f0868d25..2b24c388fb3 100644 --- a/src/callbacks_stage/positivity_zhang_shu.jl +++ b/src/callbacks_stage/positivity_zhang_shu.jl @@ -65,7 +65,7 @@ function limiter_zhang_shu!(u, thresholds::Tuple{}, variables::Tuple{}, nothing end -# Allow use of limiter as a stage callback in costum SSP integrators +# Allow use of limiter as a stage callback in custom SSP integrators function (limiter!::PositivityPreservingLimiterZhangShu)(u_ode, integrator, stage) return limiter!(u_ode, integrator, integrator.p, integrator.t) end From ac02ccf8749c29ece5b85a2e29617bc63749c699 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 20 Jun 2025 12:13:18 +0200 Subject: [PATCH 033/102] fmt --- src/callbacks_stage/positivity_zhang_shu.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/callbacks_stage/positivity_zhang_shu.jl b/src/callbacks_stage/positivity_zhang_shu.jl index 2b24c388fb3..0b1273b1766 100644 --- a/src/callbacks_stage/positivity_zhang_shu.jl +++ b/src/callbacks_stage/positivity_zhang_shu.jl @@ -72,7 +72,6 @@ end init_callback(limiter!::PositivityPreservingLimiterZhangShu, semi) = nothing finalize_callback(limiter!::PositivityPreservingLimiterZhangShu, semi) = nothing - include("positivity_zhang_shu_dg1d.jl") include("positivity_zhang_shu_dg2d.jl") include("positivity_zhang_shu_dg3d.jl") From 033940d545f253643f19731bc8195221bd64c5fa Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 26 Jun 2025 10:38:44 +0200 Subject: [PATCH 034/102] Use real sedov blast elixir --- ..._euler_sedov_blast_wave_amr_sc_subcell.jl} | 72 ++++++++++--------- src/time_integration/methods_SSP.jl | 4 +- 2 files changed, 40 insertions(+), 36 deletions(-) rename examples/tree_2d_dgsem/{elixir_euler_blast_wave_amr_sc_subcell.jl => elixir_euler_sedov_blast_wave_amr_sc_subcell.jl} (60%) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl similarity index 60% rename from examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl rename to examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl index 5dd5077b425..0fb4a7321e0 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl @@ -6,37 +6,39 @@ using Trixi equations = CompressibleEulerEquations2D(1.4) """ - initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) + initial_condition_sedov_blast_wave(x, t, equations::CompressibleEulerEquations2D) -A medium blast wave taken from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) +The Sedov blast wave setup based on Flash +- https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114000000000000000 """ -function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) - # Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave" +function initial_condition_sedov_blast_wave(x, t, equations::CompressibleEulerEquations2D) # Set up polar coordinates RealT = eltype(x) inicenter = SVector(0, 0) x_norm = x[1] - inicenter[1] y_norm = x[2] - inicenter[2] r = sqrt(x_norm^2 + y_norm^2) - phi = atan(y_norm, x_norm) - sin_phi, cos_phi = sincos(phi) + + # Setup based on https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114000000000000000 + r0 = 0.21875f0 # = 3.5 * smallest dx (for domain length=4 and max-ref=6) + # r0 = 0.5 # = more reasonable setup + E = 1 + p0_inner = 3 * (equations.gamma - 1) * E / (3 * convert(RealT, pi) * r0^2) + p0_outer = convert(RealT, 1.0e-5) # = true Sedov setup + # p0_outer = convert(RealT, 1.0e-3) # = more reasonable setup # Calculate primitive variables - rho = r > 0.5f0 ? one(RealT) : RealT(1.1691) - v1 = r > 0.5f0 ? zero(RealT) : RealT(0.1882) * cos_phi - v2 = r > 0.5f0 ? zero(RealT) : RealT(0.1882) * sin_phi - p = r > 0.5f0 ? RealT(1.0E-3) : RealT(1.245) - # p = r > 0.5f0 ? RealT(1.0E-1) : RealT(1.245) + rho = 1 + v1 = 0 + v2 = 0 + p = r > r0 ? p0_outer : p0_inner return prim2cons(SVector(rho, v1, v2, p), equations) end -initial_condition = initial_condition_blast_wave +initial_condition = initial_condition_sedov_blast_wave surface_flux = flux_lax_friedrichs -volume_flux = flux_ranocha +volume_flux = flux_chandrashekar basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], @@ -46,23 +48,19 @@ limiter_idp = SubcellLimiterIDP(equations, basis; local_onesided_variables_nonlinear = [(Trixi.entropy_math, max)], # Default parameters are not sufficient to fulfill bounds properly. - max_iterations_newton = 70, + max_iterations_newton = 200, newton_tolerances = (1.0e-13, 1.0e-14)) volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = MortarIDP(basis, alternative = false, local_factor = true, - first_order = true, pure_low_order = true) +mortar = MortarIDP(basis, pure_low_order = false) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -refinement_patches = ((type = "box", coordinates_min = (0.0, -1.0), - coordinates_max = (1.0, 1.0)),) mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level = 4, - n_cells_max = 10_000, - refinement_patches = refinement_patches, + n_cells_max = 100_000, periodicity = true) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -87,19 +85,19 @@ save_solution = SaveSolutionCallback(interval = 100, solution_variables = cons2prim, extra_node_variables = (:limiting_coefficient,)) -# amr_indicator = IndicatorMax(semi, variable = first) +amr_indicator = IndicatorMax(semi, variable = density_pressure) -# amr_controller = ControllerThreeLevel(semi, amr_indicator, -# base_level = 4, -# med_level = 5, med_threshold = 1.01, -# max_level = 6, max_threshold = 1.1) +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = 4, + med_level = 6, med_threshold = 1.0, + max_level = 7, max_threshold = 1.05) -# amr_callback = AMRCallback(semi, amr_controller, -# interval = 10, -# adapt_initial_condition = true, -# adapt_initial_condition_only_refine = false) +amr_callback = AMRCallback(semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = false) -stepsize_callback = StepsizeCallback(cfl = 0.5) +stepsize_callback = StepsizeCallback(cfl = 0.1) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, @@ -110,10 +108,14 @@ callbacks = CallbackSet(summary_callback, ############################################################################### # run the simulation -stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = false)) +# positivity limiter necessary for this tough example +stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6,), + variables = (pressure,)) + +stage_callbacks = (SubcellLimiterIDPCorrection(), #stage_limiter!, + BoundsCheckCallback(save_errors = false)) sol = Trixi.solve(ode, - # Trixi.SimpleEuler(stage_callbacks = stage_callbacks); Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback ode_default_options()..., diff --git a/src/time_integration/methods_SSP.jl b/src/time_integration/methods_SSP.jl index bce51d88c66..86240299295 100644 --- a/src/time_integration/methods_SSP.jl +++ b/src/time_integration/methods_SSP.jl @@ -226,6 +226,8 @@ function solve!(integrator::SimpleIntegratorSSP) stage_callback(integrator.u, integrator, stage) end + unstable_check(integrator.dt, integrator.u, integrator) + # perform convex combination @. integrator.u = (alg.numerator_a[stage] * integrator.r0 + alg.numerator_b[stage] * integrator.u) / @@ -288,7 +290,7 @@ function get_proposed_dt(integrator::SimpleIntegratorSSP) end function unstable_check(dt, u_ode, integrator::SimpleIntegratorSSP) - if !isfinite(dt) || isnan(dt) || !all(isfinite.(u_ode)) || any(isnan.(u_ode)) + if !isfinite(dt) || !all(isfinite, u_ode) @warn "Instability detected. Aborting" terminate!(integrator) end From 36ae7140c90b908d8e0ed5aab0c0fefc79166ba6 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 26 Jun 2025 13:16:17 +0200 Subject: [PATCH 035/102] Add blast wave elixir for testing --- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 120 ++++++++++++++++++ src/time_integration/methods_SSP.jl | 2 - 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl new file mode 100644 index 00000000000..5dd5077b425 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -0,0 +1,120 @@ +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +""" + initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) + +A medium blast wave taken from +- Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) +""" +function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquations2D) + # Modified From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) -> "medium blast wave" + # Set up polar coordinates + RealT = eltype(x) + inicenter = SVector(0, 0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + sin_phi, cos_phi = sincos(phi) + + # Calculate primitive variables + rho = r > 0.5f0 ? one(RealT) : RealT(1.1691) + v1 = r > 0.5f0 ? zero(RealT) : RealT(0.1882) * cos_phi + v2 = r > 0.5f0 ? zero(RealT) : RealT(0.1882) * sin_phi + p = r > 0.5f0 ? RealT(1.0E-3) : RealT(1.245) + # p = r > 0.5f0 ? RealT(1.0E-1) : RealT(1.245) + + return prim2cons(SVector(rho, v1, v2, p), equations) +end +initial_condition = initial_condition_blast_wave + +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha +basis = LobattoLegendreBasis(3) +limiter_idp = SubcellLimiterIDP(equations, basis; + positivity_variables_cons = ["rho"], + positivity_correction_factor = 0.5, + positivity_variables_nonlinear = [pressure], + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [(Trixi.entropy_math, + max)], + # Default parameters are not sufficient to fulfill bounds properly. + max_iterations_newton = 70, + newton_tolerances = (1.0e-13, 1.0e-14)) +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +mortar = MortarIDP(basis, alternative = false, local_factor = true, + first_order = true, pure_low_order = true) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) + +coordinates_min = (-2.0, -2.0) +coordinates_max = (2.0, 2.0) +refinement_patches = ((type = "box", coordinates_min = (0.0, -1.0), + coordinates_max = (1.0, 1.0)),) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + refinement_patches = refinement_patches, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) + +# amr_indicator = IndicatorMax(semi, variable = first) + +# amr_controller = ControllerThreeLevel(semi, amr_indicator, +# base_level = 4, +# med_level = 5, med_threshold = 1.01, +# max_level = 6, max_threshold = 1.1) + +# amr_callback = AMRCallback(semi, amr_controller, +# interval = 10, +# adapt_initial_condition = true, +# adapt_initial_condition_only_refine = false) + +stepsize_callback = StepsizeCallback(cfl = 0.5) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + # amr_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = false)) + +sol = Trixi.solve(ode, + # Trixi.SimpleEuler(stage_callbacks = stage_callbacks); + Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., + callback = callbacks); diff --git a/src/time_integration/methods_SSP.jl b/src/time_integration/methods_SSP.jl index 86240299295..36719908ad4 100644 --- a/src/time_integration/methods_SSP.jl +++ b/src/time_integration/methods_SSP.jl @@ -226,8 +226,6 @@ function solve!(integrator::SimpleIntegratorSSP) stage_callback(integrator.u, integrator, stage) end - unstable_check(integrator.dt, integrator.u, integrator) - # perform convex combination @. integrator.u = (alg.numerator_a[stage] * integrator.r0 + alg.numerator_b[stage] * integrator.u) / From 0afdd84d42309ccd8823a0457c6319f3f32e7c40 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 27 Jun 2025 12:29:47 +0200 Subject: [PATCH 036/102] Use low-order flux of high-order contains NaNs --- src/callbacks_stage/subcell_limiter_idp_correction_2d.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 9b5d34f6509..6d61e72516c 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -182,6 +182,14 @@ end flux_difference_large = factor_large * (flux_large_high_order - flux_large_low_order) + # Check if high-order fluxes are finite. Otherwise, use pure low-order fluxes. + if !all(isfinite.(flux_lower_high_order)) || + !all(isfinite(flux_upper_high_order)) || + !all(isfinite.(flux_large_high_order)) + limiting_factor[mortar] = 1 + continue + end + Qm_upper = min(0, var_min_upper - var_upper) Qm_lower = min(0, var_min_lower - var_lower) Qm_large = min(0, var_min_large - var_large) From 8b3a9cbf60c4fc33dd6ab48a866ab925af930d6e Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 27 Jun 2025 12:47:11 +0200 Subject: [PATCH 037/102] Add LimiterZhangShuLocalBounds for positivity --- ...r_euler_sedov_blast_wave_amr_sc_subcell.jl | 4 +- src/Trixi.jl | 3 +- src/callbacks_stage/positivity_zhang_shu.jl | 64 +++++++++++++++++++ .../positivity_zhang_shu_dg2d.jl | 63 ++++++++++++++++++ 4 files changed, 131 insertions(+), 3 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl index 0fb4a7321e0..12a5f4897e4 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl @@ -109,8 +109,8 @@ callbacks = CallbackSet(summary_callback, # run the simulation # positivity limiter necessary for this tough example -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6,), - variables = (pressure,)) +stage_limiter! = LimiterZhangShuLocalBounds(limiters = (:positivity,), + variables = (pressure,)) stage_callbacks = (SubcellLimiterIDPCorrection(), #stage_limiter!, BoundsCheckCallback(save_errors = false)) diff --git a/src/Trixi.jl b/src/Trixi.jl index 63415c1f783..a7c8b2bab7f 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -303,7 +303,8 @@ export load_mesh, load_time, load_timestep, load_timestep!, load_dt, export ControllerThreeLevel, ControllerThreeLevelCombined, IndicatorLöhner, IndicatorLoehner, IndicatorMax -export PositivityPreservingLimiterZhangShu, EntropyBoundedLimiter +export PositivityPreservingLimiterZhangShu, EntropyBoundedLimiter, + LimiterZhangShuLocalBounds export trixi_include, examples_dir, get_examples, default_example, default_example_unstructured, ode_default_options diff --git a/src/callbacks_stage/positivity_zhang_shu.jl b/src/callbacks_stage/positivity_zhang_shu.jl index 0b1273b1766..cd28a270b12 100644 --- a/src/callbacks_stage/positivity_zhang_shu.jl +++ b/src/callbacks_stage/positivity_zhang_shu.jl @@ -72,6 +72,70 @@ end init_callback(limiter!::PositivityPreservingLimiterZhangShu, semi) = nothing finalize_callback(limiter!::PositivityPreservingLimiterZhangShu, semi) = nothing +struct LimiterZhangShuLocalBounds{N, Variables <: NTuple{N, Any}} + variables::Variables + limiters::NTuple{N, Symbol} +end + +function LimiterZhangShuLocalBounds(; variables, + limiters = ntuple(_ -> :positivity, + length(variables))) + if length(variables) != length(limiters) + throw(ArgumentError("The number of variables must match the number of limiters.")) + end + for limiter in limiters + if !(limiter in (:positivity, :minmax)) + throw(ArgumentError("The limiter $limiter must be either :positivity or :minmax.")) + end + end + + LimiterZhangShuLocalBounds(variables, limiters) +end + +function (limiter!::LimiterZhangShuLocalBounds)(u_ode, integrator, + semi::AbstractSemidiscretization, + t) + u = wrap_array(u_ode, semi) + @trixi_timeit timer() "positivity-preserving limiter" begin + limiter_zhang_shu!(u, limiter!.limiters, limiter!.variables, + mesh_equations_solver_cache(semi)...) + end +end + +# Iterate over tuples in a type-stable way using "lispy tuple programming", +# similar to https://stackoverflow.com/a/55849398: +# Iterating over tuples of different functions isn't type-stable in general +# but accessing the first element of a tuple is type-stable. Hence, it's good +# to process one element at a time and replace iteration by recursion here. +# Note that you shouldn't use this with too many elements per tuple since the +# compile times can increase otherwise - but a handful of elements per tuple +# is definitely fine. +function limiter_zhang_shu!(u, limiters::NTuple{N, <:Symbol}, variables::NTuple{N, Any}, + mesh, equations, solver, cache) where {N} + limiter = first(limiters) + remaining_limiters = Base.tail(limiters) + variable = first(variables) + remaining_variables = Base.tail(variables) + + if limiter == :positivity + limiter_zhang_shu_positivity!(u, variable, mesh, equations, solver, cache) + else # limiter == :minmax + error("TODO: Implement the minmax limiter.") + limiter_zhang_shu_minmax!(u, variable, mesh, equations, solver, cache) + end + limiter_zhang_shu!(u, remaining_limiters, remaining_variables, mesh, equations, + solver, cache) + return nothing +end + +# Allow use of limiter as a stage callback in custom SSP integrators +function (limiter!::LimiterZhangShuLocalBounds)(u_ode, integrator, + stage) + return limiter!(u_ode, integrator, integrator.p, integrator.t) +end +init_callback(limiter!::LimiterZhangShuLocalBounds, semi) = nothing +finalize_callback(limiter!::LimiterZhangShuLocalBounds, semi) = nothing + include("positivity_zhang_shu_dg1d.jl") include("positivity_zhang_shu_dg2d.jl") include("positivity_zhang_shu_dg3d.jl") diff --git a/src/callbacks_stage/positivity_zhang_shu_dg2d.jl b/src/callbacks_stage/positivity_zhang_shu_dg2d.jl index 813dd65878b..a6c05dedaef 100644 --- a/src/callbacks_stage/positivity_zhang_shu_dg2d.jl +++ b/src/callbacks_stage/positivity_zhang_shu_dg2d.jl @@ -47,4 +47,67 @@ function limiter_zhang_shu!(u, threshold::Real, variable, return nothing end + +function limiter_zhang_shu_positivity!(u, variable, + mesh::AbstractMesh{2}, equations, dg::DGSEM, + cache) + (; weights) = dg.basis + (; inverse_jacobian) = cache.elements + + (; limiter) = dg.volume_integral + (; variable_bounds) = limiter.cache.subcell_limiter_coefficients + + # TODO + # if variable == density + # bound_min = variable_bounds[Symbol("1_min")] + # else + if variable == pressure + if pressure in dg.volume_integral.limiter.positivity_variables_nonlinear + bound_min = variable_bounds[:pressure_min] + else + error("Positivity limiting for variable $variable by LimiterZhangShuLocalBounds + requires matching limiter of the IDPLimiter.") + end + else + error("Unknown variable $variable for subcell limiter coefficients") + end + + @threaded for element in eachelement(dg, cache) + # determine minimum value + value_min = typemax(eltype(u)) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + value_min = min(value_min, variable(u_node, equations)) + end + + # detect if limiting is necessary + threshold = minimum(view(bound_min, :, :, element)) + value_min < threshold || continue + + # compute mean value + u_mean = zero(get_node_vars(u, equations, dg, 1, 1, element)) + total_volume = zero(eltype(u)) + for j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs(inv(get_inverse_jacobian(inverse_jacobian, mesh, + i, j, element))) + u_node = get_node_vars(u, equations, dg, i, j, element) + u_mean += u_node * weights[i] * weights[j] * volume_jacobian + total_volume += weights[i] * weights[j] * volume_jacobian + end + # normalize with the total volume + u_mean = u_mean / total_volume + + # We compute the value directly with the mean values, as we assume that + # Jensen's inequality holds (e.g. pressure for compressible Euler equations). + value_mean = variable(u_mean, equations) + theta = (value_mean - threshold) / (value_mean - value_min) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + set_node_vars!(u, theta * u_node + (1 - theta) * u_mean, + equations, dg, i, j, element) + end + end + + return nothing +end end # @muladd From cd2c51f3a4ad676ae485671461c122a24f274aab Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 28 Jul 2025 14:17:14 +0200 Subject: [PATCH 038/102] Fix llf use --- examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl | 9 ++++++++- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 9 ++++++++- .../elixir_euler_convergence_amr_sc_subcell.jl | 9 ++++++++- .../elixir_euler_density_wave_amr_sc_subcell.jl | 9 ++++++++- .../elixir_euler_sedov_blast_wave_amr_sc_subcell.jl | 9 ++++++++- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl index b7738988926..9e0cd188694 100644 --- a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl @@ -8,7 +8,14 @@ equations = LinearScalarAdvectionEquation2D(advection_velocity) initial_condition = initial_condition_convergence_test -surface_flux = flux_lax_friedrichs +# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of +# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. +# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. +# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. +# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. +# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the +# `StepsizeCallback` (CFL-Condition) and less diffusion. +surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) volume_flux = flux_godunov polydeg = 3 basis = LobattoLegendreBasis(polydeg) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 5dd5077b425..38301910e93 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -35,7 +35,14 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation end initial_condition = initial_condition_blast_wave -surface_flux = flux_lax_friedrichs +# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of +# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. +# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. +# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. +# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. +# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the +# `StepsizeCallback` (CFL-Condition) and less diffusion. +surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index aa7107cbb88..b4303a87f56 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -8,7 +8,14 @@ equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test # initial_condition = initial_condition_constant -surface_flux = flux_lax_friedrichs +# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of +# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. +# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. +# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. +# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. +# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the +# `StepsizeCallback` (CFL-Condition) and less diffusion. +surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl index d87a5aef75b..ed1e1979886 100644 --- a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl @@ -8,7 +8,14 @@ equations = CompressibleEulerEquations2D(1.4) # initial_condition = Trixi.initial_condition_density_wave_highdensity initial_condition = Trixi.initial_condition_density_wave -surface_flux = flux_lax_friedrichs +# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of +# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. +# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. +# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. +# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. +# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the +# `StepsizeCallback` (CFL-Condition) and less diffusion. +surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl index 12a5f4897e4..5e974f65755 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl @@ -37,7 +37,14 @@ function initial_condition_sedov_blast_wave(x, t, equations::CompressibleEulerEq end initial_condition = initial_condition_sedov_blast_wave -surface_flux = flux_lax_friedrichs +# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of +# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. +# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. +# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. +# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. +# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the +# `StepsizeCallback` (CFL-Condition) and less diffusion. +surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) volume_flux = flux_chandrashekar basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; From 0d6a30e9f01fb64b61000c66d602a5766308714a Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 28 Jul 2025 15:25:49 +0200 Subject: [PATCH 039/102] Use default llf in new elixirs --- .../elixir_advection_sc_subcell.jl | 9 +- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 10 +-- ...elixir_euler_convergence_amr_sc_subcell.jl | 9 +- ...lixir_euler_density_wave_amr_sc_subcell.jl | 9 +- ...r_euler_sedov_blast_wave_amr_sc_subcell.jl | 9 +- test/test_tree_2d_euler.jl | 86 +++++++++---------- 6 files changed, 49 insertions(+), 83 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl index 9e0cd188694..b7738988926 100644 --- a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl @@ -8,14 +8,7 @@ equations = LinearScalarAdvectionEquation2D(advection_velocity) initial_condition = initial_condition_convergence_test -# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of -# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. -# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. -# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. -# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. -# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the -# `StepsizeCallback` (CFL-Condition) and less diffusion. -surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) +surface_flux = flux_lax_friedrichs volume_flux = flux_godunov polydeg = 3 basis = LobattoLegendreBasis(polydeg) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 38301910e93..5f12d0ad188 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -35,14 +35,8 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation end initial_condition = initial_condition_blast_wave -# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of -# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. -# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. -# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. -# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. -# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the -# `StepsizeCallback` (CFL-Condition) and less diffusion. -surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) +# surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) +surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index b4303a87f56..aa7107cbb88 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -8,14 +8,7 @@ equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test # initial_condition = initial_condition_constant -# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of -# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. -# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. -# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. -# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. -# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the -# `StepsizeCallback` (CFL-Condition) and less diffusion. -surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) +surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl index ed1e1979886..d87a5aef75b 100644 --- a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl @@ -8,14 +8,7 @@ equations = CompressibleEulerEquations2D(1.4) # initial_condition = Trixi.initial_condition_density_wave_highdensity initial_condition = Trixi.initial_condition_density_wave -# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of -# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. -# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. -# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. -# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. -# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the -# `StepsizeCallback` (CFL-Condition) and less diffusion. -surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) +surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl index 5e974f65755..12a5f4897e4 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl @@ -37,14 +37,7 @@ function initial_condition_sedov_blast_wave(x, t, equations::CompressibleEulerEq end initial_condition = initial_condition_sedov_blast_wave -# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of -# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. -# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. -# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. -# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. -# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the -# `StepsizeCallback` (CFL-Condition) and less diffusion. -surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) +surface_flux = flux_lax_friedrichs volume_flux = flux_chandrashekar basis = LobattoLegendreBasis(3) limiter_idp = SubcellLimiterIDP(equations, basis; diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 776edeb92ba..be94bc1c926 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -63,16 +63,16 @@ end "elixir_euler_convergence_amr_sc_subcell.jl"), alternative=true, l2=[ - 6.792436492682374e-5, - 6.120716506968713e-5, - 6.055380339405702e-5, - 0.00014030467383463677 + 6.792434932529532e-5, + 6.120715609381622e-5, + 6.055378873143237e-5, + 0.00014030465047211284 ], linf=[ - 0.0005470069686710488, - 0.0004685919341520517, - 0.00047891795883758803, - 0.0015576243185071448 + 0.0005470070709066022, + 0.0004685919440707842, + 0.00047891796273047404, + 0.0015576242650183758 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -562,18 +562,18 @@ end "elixir_euler_blast_wave_amr_sc_subcell.jl"), alternative=false, local_factor=true, first_order=true, l2=[ - 0.5658115528437391, - 0.23410168927488026, - 0.2343838211239537, - 0.7048618230676461 + 0.5661095633829809, + 0.23409350473734566, + 0.23444762919022388, + 0.7046253692202586 ], linf=[ - 2.3264681212817635, - 1.1830218103562824, - 1.1879149624171517, - 2.976938299678714 + 2.327553246065897, + 1.18389063705703, + 1.1882587446238897, + 2.973075034259535 ], - tspan=(0.0, 1.0),) + tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -594,18 +594,18 @@ end alternative=false, local_factor=true, first_order=false, # Note: Not conservative l2=[ - 0.5656993054935289, - 0.23405091429718172, - 0.23415036917169094, - 0.7046511610343784 + 0.5660728669927354, + 0.23401814607355093, + 0.23426327082527165, + 0.7044435028845455 ], linf=[ - 2.3271194860448245, - 1.182587461440544, - 1.1870640954349436, - 2.9753866073069686 + 2.3276786116939987, + 1.1830410764310964, + 1.1873953794481305, + 2.9749500856720905 ], - tspan=(0.0, 1.0),) + tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -625,18 +625,18 @@ end "elixir_euler_blast_wave_amr_sc_subcell.jl"), alternative=false, local_factor=false, l2=[ - 0.5597349371368964, - 0.2323622389350034, - 0.23140311168434288, - 0.7034630477001529 + 0.5600381164624277, + 0.2323340115618057, + 0.23145716308407133, + 0.7031488010819059 ], linf=[ - 2.336082787232411, - 1.1774421459033773, - 1.1850364642054037, - 2.97064686684438 + 2.336405445386398, + 1.178086434026379, + 1.185087035395828, + 2.9660035923836605 ], - tspan=(0.0, 1.0),) + tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -656,16 +656,16 @@ end "elixir_euler_blast_wave_amr_sc_subcell.jl"), alternative=true, l2=[ - 0.5661811293770421, - 0.23414189161032448, - 0.234486278634814, - 0.7049405559250281 + 0.5664551909566969, + 0.2341427667598265, + 0.23453132963657158, + 0.7046830527027006 ], linf=[ - 2.3231040236059366, - 1.1914843441075436, - 1.1977285198376395, - 2.9758323627646512 + 2.3242557645384525, + 1.192819817641742, + 1.1980797336997144, + 2.972294202188677 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations From 70e8f4ac4dabdc916b715a4b30d04435fed5b347 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 28 Jul 2025 16:38:10 +0200 Subject: [PATCH 040/102] Fix test --- .../tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl | 1 - test/test_tree_2d_euler.jl | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 5f12d0ad188..5dd5077b425 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -35,7 +35,6 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation end initial_condition = initial_condition_blast_wave -# surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index be94bc1c926..d0a8feabed3 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -625,9 +625,9 @@ end "elixir_euler_blast_wave_amr_sc_subcell.jl"), alternative=false, local_factor=false, l2=[ - 0.5600381164624277, - 0.2323340115618057, - 0.23145716308407133, + 0.5600381797334268, + 0.23233414830994564, + 0.23145717236645028, 0.7031488010819059 ], linf=[ From 2ef741f88f80c9bb9251f61f11fe28490beffa44 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 31 Jul 2025 12:00:13 +0200 Subject: [PATCH 041/102] Add flexibel output directory for mortar limiting factor --- .../subcell_limiter_idp_correction_2d.jl | 5 +++-- src/solvers/dgsem/basis_lobatto_legendre.jl | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 6d61e72516c..8625511dc7b 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -213,15 +213,16 @@ end end end # Provisional analysis of limiting factor + (; output_directory) = dg.mortar if length(limiting_factor) > 0 - open(joinpath("out", "mortar_limiting_factor.txt"), "a") do f + open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f print(f, t) print(f, ", ", minimum(limiting_factor), ", ", maximum(limiting_factor), ", ", sum(limiting_factor) / length(limiting_factor)) println(f) end else - open(joinpath("out", "mortar_limiting_factor.txt"), "a") do f + open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f print(f, t) print(f, ", ", 0.0, ", ", 0.0, ", ", 0.0) println(f) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 77451a62770..667d9a5f19a 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -177,6 +177,7 @@ struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: local_factor::Bool mortar_l2::Mortar local_mortar_weights::Matrix{RealT} + output_directory::String end struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, Mortar, @@ -188,10 +189,12 @@ struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, Mortar, forward_lower_low_order::ForwardMatrix reverse_upper_low_order::ReverseMatrix reverse_lower_low_order::ReverseMatrix + output_directory::String end function MortarIDP(basis::LobattoLegendreBasis; alternative = false, - local_factor = true, first_order = true, pure_low_order = false) + local_factor = true, first_order = true, pure_low_order = false, + output_directory = "out") RealT = real(basis) nnodes_ = nnodes(basis) @@ -213,7 +216,8 @@ function MortarIDP(basis::LobattoLegendreBasis; alternative = false, forward_upper_low_order, forward_lower_low_order, reverse_upper_low_order, - reverse_lower_low_order) + reverse_lower_low_order, + output_directory) else local_mortar_weights = calc_mortar_weights(basis, RealT; first_order = first_order) @@ -221,7 +225,8 @@ function MortarIDP(basis::LobattoLegendreBasis; alternative = false, LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(pure_low_order, local_factor, mortar_l2, - local_mortar_weights) + local_mortar_weights, + output_directory) end end From e51da59f10eb7d258e39746fd5f33dc9e558a398 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 31 Jul 2025 15:26:56 +0200 Subject: [PATCH 042/102] Move calc of limiting factor to function --- .../subcell_limiter_idp_correction_2d.jl | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 8625511dc7b..5cf3b24d82a 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -47,18 +47,42 @@ function perform_idp_correction!(u, dt, end @inline function calc_limiting_factor!(u, semi, t, dt) + (; limiting_factor) = semi.cache.mortars + limiting_factor .= zeros(eltype(limiting_factor)) + + index_rho = 1 # TODO + limiting_positivity_conservative!(limiting_factor, u, dt, semi, index_rho) + + # Provisional analysis of limiting factor + (; output_directory) = semi.dg.mortar + if length(limiting_factor) > 0 + open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f + print(f, t) + print(f, ", ", minimum(limiting_factor), ", ", maximum(limiting_factor), + ", ", sum(limiting_factor) / length(limiting_factor)) + println(f) + end + else + open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f + print(f, t) + print(f, ", ", 0.0, ", ", 0.0, ", ", 0.0) + println(f) + end + end + + return nothing +end + +@inline function limiting_positivity_conservative!(limiting_factor, u, dt, semi, + var_index) mesh, equations, dg, cache = mesh_equations_solver_cache(semi) - (; limiting_factor, orientations) = cache.mortars + (; orientations) = cache.mortars (; surface_flux_values, surface_flux_values_high_order) = cache.elements (; boundary_interpolation) = dg.basis - limiting_factor .= zeros(eltype(limiting_factor)) - (; positivity_correction_factor) = dg.volume_integral.limiter - index_rho = 1 # TODO - for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] @@ -90,9 +114,9 @@ end indices_large = (i, 1) end end - var_upper = u[index_rho, indices_small..., upper_element] - var_lower = u[index_rho, indices_small..., lower_element] - var_large = u[index_rho, indices_large..., large_element] + var_upper = u[var_index, indices_small..., upper_element] + var_lower = u[var_index, indices_small..., lower_element] + var_large = u[var_index, indices_large..., large_element] var_min_upper = min(var_min_upper, var_upper) var_min_lower = min(var_min_lower, var_lower) var_min_large = min(var_min_large, var_large) @@ -138,9 +162,9 @@ end # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. # This sign switch is directly applied to the boundary interpolation factors here. - var_upper = u[index_rho, indices_small..., upper_element] - var_lower = u[index_rho, indices_small..., lower_element] - var_large = u[index_rho, indices_large..., large_element] + var_upper = u[var_index, indices_small..., upper_element] + var_lower = u[var_index, indices_small..., lower_element] + var_large = u[var_index, indices_large..., large_element] if min(var_upper, var_lower, var_large) < 0 error("Safe low-order method produces negative value for conservative variable rho. Try a smaller time step.") @@ -157,26 +181,26 @@ end large_element) # Calculate Pm - flux_lower_high_order = surface_flux_values_high_order[index_rho, i, + flux_lower_high_order = surface_flux_values_high_order[var_index, i, direction_small, lower_element] - flux_lower_low_order = surface_flux_values[index_rho, i, direction_small, + flux_lower_low_order = surface_flux_values[var_index, i, direction_small, lower_element] flux_difference_lower = factor_small * (flux_lower_high_order - flux_lower_low_order) - flux_upper_high_order = surface_flux_values_high_order[index_rho, i, + flux_upper_high_order = surface_flux_values_high_order[var_index, i, direction_small, upper_element] - flux_upper_low_order = surface_flux_values[index_rho, i, direction_small, + flux_upper_low_order = surface_flux_values[var_index, i, direction_small, upper_element] flux_difference_upper = factor_small * (flux_upper_high_order - flux_upper_low_order) - flux_large_high_order = surface_flux_values_high_order[index_rho, i, + flux_large_high_order = surface_flux_values_high_order[var_index, i, direction_large, large_element] - flux_large_low_order = surface_flux_values_high_order[index_rho, i, + flux_large_low_order = surface_flux_values_high_order[var_index, i, direction_large, large_element] flux_difference_large = factor_large * @@ -212,22 +236,6 @@ end 1 - Qm_lower, 1 - Qm_large) end end - # Provisional analysis of limiting factor - (; output_directory) = dg.mortar - if length(limiting_factor) > 0 - open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f - print(f, t) - print(f, ", ", minimum(limiting_factor), ", ", maximum(limiting_factor), - ", ", sum(limiting_factor) / length(limiting_factor)) - println(f) - end - else - open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f - print(f, t) - print(f, ", ", 0.0, ", ", 0.0, ", ", 0.0) - println(f) - end - end return nothing end From 77da564952f4514a83fe628c7e80674d88d82640 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 1 Aug 2025 12:42:58 +0200 Subject: [PATCH 043/102] Fix last commit --- src/callbacks_stage/subcell_limiter_idp_correction_2d.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 5cf3b24d82a..ab08eacaa76 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -54,7 +54,7 @@ end limiting_positivity_conservative!(limiting_factor, u, dt, semi, index_rho) # Provisional analysis of limiting factor - (; output_directory) = semi.dg.mortar + (; output_directory) = semi.solver.mortar if length(limiting_factor) > 0 open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f print(f, t) From a8f5c8be30b6dc574a26d3acc3f9d8f47150534b Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 7 Aug 2025 11:10:58 +0200 Subject: [PATCH 044/102] Make `newton_loops!` dimendion independent --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 5a2c3ed9f8f..c62ee28bd68 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -512,8 +512,9 @@ end @inline function newton_loops_alpha!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, final_check, - inverse_jacobian, dt, equations, dg, cache, - limiter) + inverse_jacobian, dt, + equations::AbstractEquations{2}, + dg, cache, limiter) (; inverse_weights) = dg.basis (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes @@ -523,7 +524,7 @@ end antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[i] * get_node_vars(antidiffusive_flux1_R, equations, dg, i, j, element) - newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + newton_loop!(alpha, bound, u, (i, j, element), variable, min_or_max, initial_check, final_check, equations, dt, limiter, antidiffusive_flux) # positive xi direction @@ -531,14 +532,14 @@ end inverse_weights[i] * get_node_vars(antidiffusive_flux1_L, equations, dg, i + 1, j, element) - newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + newton_loop!(alpha, bound, u, (i, j, element), variable, min_or_max, initial_check, final_check, equations, dt, limiter, antidiffusive_flux) # negative eta direction antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[j] * get_node_vars(antidiffusive_flux2_R, equations, dg, i, j, element) - newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + newton_loop!(alpha, bound, u, (i, j, element), variable, min_or_max, initial_check, final_check, equations, dt, limiter, antidiffusive_flux) # positive eta direction @@ -546,18 +547,19 @@ end inverse_weights[j] * get_node_vars(antidiffusive_flux2_L, equations, dg, i, j + 1, element) - newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + newton_loop!(alpha, bound, u, (i, j, element), variable, min_or_max, initial_check, final_check, equations, dt, limiter, antidiffusive_flux) return nothing end -@inline function newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, +# TODO: Move to `subcell_limiters.jl` since it is dimension independent +@inline function newton_loop!(alpha, bound, u, alpha_indices, variable, min_or_max, initial_check, final_check, equations, dt, limiter, antidiffusive_flux) newton_reltol, newton_abstol = limiter.newton_tolerances - beta = 1 - alpha[i, j, element] + beta = 1 - alpha[alpha_indices...] beta_L = 0 # alpha = 1 beta_R = beta # No higher beta (lower alpha) than the current one @@ -636,7 +638,7 @@ end end new_alpha = 1 - beta - alpha[i, j, element] = new_alpha + alpha[alpha_indices...] = new_alpha return nothing end From 7b429a28792f2eaa93cc16eaf9e16baf4ac8b0c6 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 7 Aug 2025 12:03:21 +0200 Subject: [PATCH 045/102] Add mortar limiting for pressure (nonlinear variables) --- .../subcell_limiter_idp_correction_2d.jl | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index ab08eacaa76..62585a286e5 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -46,6 +46,7 @@ function perform_idp_correction!(u, dt, return nothing end +# TODO: Move next three functions somewhere else, e.g., to `subcell_limiters.jl` @inline function calc_limiting_factor!(u, semi, t, dt) (; limiting_factor) = semi.cache.mortars limiting_factor .= zeros(eltype(limiting_factor)) @@ -53,6 +54,9 @@ end index_rho = 1 # TODO limiting_positivity_conservative!(limiting_factor, u, dt, semi, index_rho) + variable = pressure # TODO + limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) + # Provisional analysis of limiting factor (; output_directory) = semi.solver.mortar if length(limiting_factor) > 0 @@ -240,6 +244,131 @@ end return nothing end +@inline function limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) + mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + + (; orientations) = cache.mortars + (; surface_flux_values, surface_flux_values_high_order) = cache.elements + (; boundary_interpolation) = dg.basis + + (; limiter) = dg.volume_integral + (; positivity_correction_factor) = limiter + + for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + direction_small = 3 + direction_large = 4 + end + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + direction_small = 4 + direction_large = 3 + end + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + upper_element) + inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + lower_element) + inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_large..., + large_element) + + u_lower = get_node_vars(u, equations, dg, indices_small..., lower_element) + var_lower = variable(u_lower, equations) + u_upper = get_node_vars(u, equations, dg, indices_small..., upper_element) + var_upper = variable(u_upper, equations) + u_large = get_node_vars(u, equations, dg, indices_large..., large_element) + var_large = variable(u_large, equations) + if var_lower < 0 || var_upper < 0 || var_large < 0 + error("Safe low-order method produces negative value for variable $variable. Try a smaller time step.") + end + + var_min_lower = positivity_correction_factor * var_lower + var_min_upper = positivity_correction_factor * var_upper + var_min_large = positivity_correction_factor * var_large + + # lower element + flux_lower_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_small, + lower_element) + flux_lower_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_small, lower_element) + flux_difference_lower = factor_small * + (flux_lower_high_order .- flux_lower_low_order) + antidiffusive_flux_lower = inverse_jacobian_lower * flux_difference_lower + + newton_loop!(limiting_factor, var_min_lower, u_lower, (mortar,), variable, + min, initial_check_nonnegative_newton_idp, + final_check_nonnegative_newton_idp, + equations, dt, limiter, antidiffusive_flux_lower) + + # upper element + flux_upper_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_small, + upper_element) + flux_upper_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_small, upper_element) + flux_difference_upper = factor_small * + (flux_upper_high_order .- flux_upper_low_order) + antidiffusive_flux_upper = inverse_jacobian_upper * flux_difference_upper + + newton_loop!(limiting_factor, var_min_upper, u_upper, (mortar,), variable, + min, initial_check_nonnegative_newton_idp, + final_check_nonnegative_newton_idp, + equations, dt, limiter, antidiffusive_flux_upper) + + # large element + flux_large_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_large, + large_element) + flux_large_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_large, large_element) + flux_difference_large = factor_large * + (flux_large_high_order .- flux_large_low_order) + antidiffusive_flux_large = inverse_jacobian_large * flux_difference_large + + newton_loop!(limiting_factor, var_min_large, u_large, (mortar,), variable, + min, initial_check_nonnegative_newton_idp, + final_check_nonnegative_newton_idp, + equations, dt, limiter, antidiffusive_flux_large) + end + end + + return nothing +end + @inline function blend_mortar_flux!(u, semi, equations, dg, t, dt) (; mesh, cache) = semi (; orientations, limiting_factor) = cache.mortars From 87d6f501a0b9097f0c5985c21f9f9515854f118b Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 8 Aug 2025 10:19:32 +0200 Subject: [PATCH 046/102] Rename `first_order` to `piecewise_constant` etc --- ...ir_euler_supersonic_cylinder_sc_subcell.jl | 166 ------------------ .../elixir_advection_sc_subcell.jl | 2 +- .../elixir_euler_blast_wave_amr_sc_subcell.jl | 5 +- ...elixir_euler_convergence_amr_sc_subcell.jl | 5 +- ...lixir_euler_density_wave_amr_sc_subcell.jl | 4 +- ...r_euler_sedov_blast_wave_amr_sc_subcell.jl | 4 +- src/solvers/dgsem/basis_lobatto_legendre.jl | 5 +- .../dgsem_tree/dg_2d_subcell_limiters.jl | 14 +- 8 files changed, 22 insertions(+), 183 deletions(-) delete mode 100644 examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl diff --git a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl deleted file mode 100644 index 35340ea6251..00000000000 --- a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl +++ /dev/null @@ -1,166 +0,0 @@ -# Channel flow around a cylinder at Mach 3 -# -# Boundary conditions are supersonic Mach 3 inflow at the left portion of the domain -# and supersonic outflow at the right portion of the domain. The top and bottom of the -# channel as well as the cylinder are treated as Euler slip wall boundaries. -# This flow results in strong shock reflections / interactions as well as Kelvin-Helmholtz -# instabilities at later times as two Mach stems form above and below the cylinder. -# -# For complete details on the problem setup see Section 5.7 of the paper: -# - Jean-Luc Guermond, Murtazo Nazarov, Bojan Popov, and Ignacio Tomas (2018) -# Second-Order Invariant Domain Preserving Approximation of the Euler Equations using Convex Limiting. -# [DOI: 10.1137/17M1149961](https://doi.org/10.1137/17M1149961) -# -# Keywords: supersonic flow, shock capturing, AMR, unstructured curved mesh, positivity preservation, compressible Euler, 2D - -using Trixi - -############################################################################### -# semidiscretization of the compressible Euler equations - -equations = CompressibleEulerEquations2D(1.4) - -@inline function initial_condition_mach3_flow(x, t, equations::CompressibleEulerEquations2D) - # set the freestream flow parameters - rho_freestream = 1.4 - v1 = 3.0 - v2 = 0.0 - p_freestream = 1.0 - - prim = SVector(rho_freestream, v1, v2, p_freestream) - return prim2cons(prim, equations) -end - -initial_condition = initial_condition_mach3_flow - -# Supersonic inflow boundary condition. -# Calculate the boundary flux entirely from the external solution state, i.e., set -# external solution state values for everything entering the domain. -@inline function boundary_condition_supersonic_inflow(u_inner, - normal_direction::AbstractVector, - x, t, surface_flux_function, - equations::CompressibleEulerEquations2D) - u_boundary = initial_condition_mach3_flow(x, t, equations) - flux = Trixi.flux(u_boundary, normal_direction, equations) - - return flux -end - -# For subcell limiting, the calculation of local bounds for non-periodic domains requires the -# boundary outer state. Those functions return the boundary value for a specific boundary condition -# at time `t`, for the node with spatial indices `indices` and the given `normal_direction`. -@inline function Trixi.get_boundary_outer_state(u_inner, t, - boundary_condition::typeof(boundary_condition_supersonic_inflow), - normal_direction::AbstractVector, - mesh::P4estMesh, equations, dg, cache, - indices...) - x = Trixi.get_node_coords(cache.elements.node_coordinates, equations, dg, indices...) - - return initial_condition_mach3_flow(x, t, equations) -end - -# Supersonic outflow boundary condition. -# Calculate the boundary flux entirely from the internal solution state. Analogous to supersonic inflow -# except all the solution state values are set from the internal solution as everything leaves the domain -@inline function boundary_condition_outflow(u_inner, normal_direction::AbstractVector, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) - flux = Trixi.flux(u_inner, normal_direction, equations) - - return flux -end - -@inline function Trixi.get_boundary_outer_state(u_inner, t, - boundary_condition::typeof(boundary_condition_outflow), - normal_direction::AbstractVector, - mesh::P4estMesh, equations, dg, cache, - indices...) - return u_inner -end - -@inline function Trixi.get_boundary_outer_state(u_inner, t, - boundary_condition::typeof(boundary_condition_slip_wall), - normal_direction::AbstractVector, - mesh::P4estMesh{2}, - equations::CompressibleEulerEquations2D, - dg, cache, indices...) - factor = (normal_direction[1] * u_inner[2] + normal_direction[2] * u_inner[3]) - u_normal = (factor / sum(normal_direction .^ 2)) * normal_direction - - return SVector(u_inner[1], - u_inner[2] - 2 * u_normal[1], - u_inner[3] - 2 * u_normal[2], - u_inner[4]) -end - -boundary_conditions = Dict(:Bottom => boundary_condition_slip_wall, - :Circle => boundary_condition_slip_wall, - :Top => boundary_condition_slip_wall, - :Right => boundary_condition_outflow, - :Left => boundary_condition_supersonic_inflow) - -volume_flux = flux_ranocha_turbo -# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of -# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. -# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. -# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. -# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. -# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the -# `StepsizeCallback` (CFL-Condition) and less diffusion. -surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) -polydeg = 3 -basis = LobattoLegendreBasis(polydeg) -limiter_idp = SubcellLimiterIDP(equations, basis; - local_twosided_variables_cons = ["rho"], - positivity_variables_nonlinear = [pressure], - local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, - min)], - max_iterations_newton = 50) # Default value of 10 iterations is too low to fulfill bounds. - -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -solver = DGSEM(basis, surface_flux, volume_integral) - -# Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/a08f78f6b185b63c3baeff911a63f628/raw/addac716ea0541f588b9d2bd3f92f643eb27b88f/abaqus_cylinder_in_channel.inp", - joinpath(@__DIR__, "abaqus_cylinder_in_channel.inp")) - -mesh = P4estMesh{2}(mesh_file, initial_refinement_level = 0) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) - -############################################################################### -# ODE solvers - -tspan = (0.0, 2.0) -ode = semidiscretize(semi, tspan) - -# Callbacks - -summary_callback = SummaryCallback() - -analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - -alive_callback = AliveCallback(analysis_interval = analysis_interval) - -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim, - extra_node_variables = (:limiting_coefficient,)) - -stepsize_callback = StepsizeCallback(cfl = 0.8) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) - -stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) - -sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - callback = callbacks); diff --git a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl index b7738988926..cff724ce370 100644 --- a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl @@ -19,7 +19,7 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_fv = surface_flux) mortar = MortarIDP(basis; alternative = false, local_factor = true, - first_order = true) + basis_function = :piecewise_constant) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index 5dd5077b425..df292f69f16 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -52,7 +52,8 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) mortar = MortarIDP(basis, alternative = false, local_factor = true, - first_order = true, pure_low_order = true) + basis_function = :piecewise_constant, + pure_low_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) @@ -103,8 +104,8 @@ stepsize_callback = StepsizeCallback(cfl = 0.5) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - # amr_callback, save_solution, + # amr_callback, stepsize_callback) ############################################################################### diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index aa7107cbb88..05a69f79ff5 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -24,7 +24,8 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_fv = surface_flux) mortar = MortarIDP(basis; alternative = false, local_factor = true, - first_order = true, pure_low_order = false,) + basis_function = :piecewise_constant, + pure_low_order = false) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) @@ -77,8 +78,8 @@ stepsize_callback = StepsizeCallback(cfl = 0.8) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - # amr_callback, save_solution, + # amr_callback, stepsize_callback) ############################################################################### diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl index d87a5aef75b..afd3a17ac77 100644 --- a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl @@ -21,7 +21,7 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_fv = surface_flux) mortar = MortarIDP(basis; alternative = false, local_factor = true, - first_order = true) + basis_function = :piecewise_constant) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) @@ -73,8 +73,8 @@ stepsize_callback = StepsizeCallback(cfl = 0.8) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - # amr_callback, save_solution, + # amr_callback, stepsize_callback) ############################################################################### diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl index 12a5f4897e4..9693e64a33d 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl @@ -53,7 +53,7 @@ limiter_idp = SubcellLimiterIDP(equations, basis; volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = MortarIDP(basis, pure_low_order = false) +mortar = MortarIDP(basis, basis_function = :piecewise_constant, pure_low_order = false) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) @@ -101,8 +101,8 @@ stepsize_callback = StepsizeCallback(cfl = 0.1) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - # amr_callback, save_solution, + # amr_callback, stepsize_callback) ############################################################################### diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 667d9a5f19a..56b56989fae 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -193,7 +193,8 @@ struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, Mortar, end function MortarIDP(basis::LobattoLegendreBasis; alternative = false, - local_factor = true, first_order = true, pure_low_order = false, + local_factor = true, basis_function = :piecewise_constant, + pure_low_order = false, output_directory = "out") RealT = real(basis) nnodes_ = nnodes(basis) @@ -220,7 +221,7 @@ function MortarIDP(basis::LobattoLegendreBasis; alternative = false, output_directory) else local_mortar_weights = calc_mortar_weights(basis, RealT; - first_order = first_order) + basis_function = basis_function) LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(pure_low_order, local_factor, diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 68b502e714c..7eddce3695e 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -83,15 +83,17 @@ function create_cache(mesh::TreeMesh{2}, fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) end -function calc_mortar_weights(basis, RealT; first_order = true) +function calc_mortar_weights(basis, RealT; basis_function = :piecewise_constant) n_nodes = nnodes(basis) # Saving the sum over row/column in entries with last index. weights = zeros(RealT, n_nodes + 1, 2 * n_nodes + 1) - if first_order - calc_mortar_weights_first_order!(weights, n_nodes, RealT) + if basis_function == :piecewise_constant + calc_mortar_weights_piecewise_constant!(weights, n_nodes, RealT) + elseif basis_function == :piecewise_linear + calc_mortar_weights_piecewise_linear!(weights, basis) else - calc_mortar_weights_second_order!(weights, basis) + error("Unsupported basis function type: $basis_function") end for i in eachnode(basis), j in eachnode(basis) @@ -106,7 +108,7 @@ function calc_mortar_weights(basis, RealT; first_order = true) return weights end -function calc_mortar_weights_first_order!(mortar_weights, n_nodes, RealT) +function calc_mortar_weights_piecewise_constant!(mortar_weights, n_nodes, RealT) _, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) cum_weights = [zero(RealT); cumsum(weights)] .- 1.0 @@ -131,7 +133,7 @@ function calc_mortar_weights_first_order!(mortar_weights, n_nodes, RealT) return mortar_weights end -function calc_mortar_weights_second_order!(weights, basis) +function calc_mortar_weights_piecewise_linear!(weights, basis) (; nodes) = basis n_nodes = nnodes(basis) nodes_lower = 0.5f0 * nodes .- 0.5f0 From c0176a50c41d867d3439bcfddef5f431ae99b536 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 8 Aug 2025 11:27:33 +0200 Subject: [PATCH 047/102] Generalize (mortar-)limited variables --- .../subcell_limiter_idp_correction_2d.jl | 11 +++--- src/solvers/dgsem/basis_lobatto_legendre.jl | 36 +++++++++++++------ test/test_p4est_2d.jl | 31 ---------------- test/test_tree_2d_euler.jl | 20 ++++++----- 4 files changed, 45 insertions(+), 53 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 62585a286e5..b6bedce7645 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -48,14 +48,17 @@ end # TODO: Move next three functions somewhere else, e.g., to `subcell_limiters.jl` @inline function calc_limiting_factor!(u, semi, t, dt) + (; positivity_variables_cons, positivity_variables_nonlinear) = semi.solver.mortar (; limiting_factor) = semi.cache.mortars limiting_factor .= zeros(eltype(limiting_factor)) - index_rho = 1 # TODO - limiting_positivity_conservative!(limiting_factor, u, dt, semi, index_rho) + for var_index in positivity_variables_cons + limiting_positivity_conservative!(limiting_factor, u, dt, semi, var_index) + end - variable = pressure # TODO - limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) + for variable in positivity_variables_nonlinear + limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) + end # Provisional analysis of limiting factor (; output_directory) = semi.solver.mortar diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 56b56989fae..22142f1518f 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -171,8 +171,11 @@ end left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights) right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) -struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: +struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, + LimitingVariablesNonlinear, Mortar} <: AbstractMortarL2{RealT} + positivity_variables_cons::Vector{Int} + positivity_variables_nonlinear::LimitingVariablesNonlinear pure_low_order::Bool local_factor::Bool mortar_l2::Mortar @@ -180,10 +183,13 @@ struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: output_directory::String end -struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, Mortar, +struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, + LimitingVariablesNonlinear, Mortar, ForwardMatrix <: AbstractMatrix{RealT}, ReverseMatrix <: AbstractMatrix{RealT}} <: AbstractMortarL2{RealT} + positivity_variables_cons::Vector{Int} + positivity_variables_nonlinear::LimitingVariablesNonlinear mortar_l2::Mortar forward_upper_low_order::ForwardMatrix forward_lower_low_order::ForwardMatrix @@ -192,7 +198,10 @@ struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, Mortar, output_directory::String end -function MortarIDP(basis::LobattoLegendreBasis; alternative = false, +function MortarIDP(basis::LobattoLegendreBasis; + positivity_variables_cons = [1], # TODO: String["rho"] + positivity_variables_nonlinear = [Trixi.pressure], # TODO: [] + alternative = false, local_factor = true, basis_function = :piecewise_constant, pure_low_order = false, output_directory = "out") @@ -211,9 +220,13 @@ function MortarIDP(basis::LobattoLegendreBasis; alternative = false, Val(:gauss_lobatto), RealT) - LobattoLegendreMortarIDPAlternative{RealT, nnodes_, typeof(mortar_l2), + LobattoLegendreMortarIDPAlternative{RealT, nnodes_, + typeof(positivity_variables_nonlinear), + typeof(mortar_l2), typeof(forward_upper_low_order), - typeof(reverse_upper_low_order)}(mortar_l2, + typeof(reverse_upper_low_order)}(positivity_variables_cons, + positivity_variables_nonlinear, + mortar_l2, forward_upper_low_order, forward_lower_low_order, reverse_upper_low_order, @@ -223,11 +236,14 @@ function MortarIDP(basis::LobattoLegendreBasis; alternative = false, local_mortar_weights = calc_mortar_weights(basis, RealT; basis_function = basis_function) - LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(pure_low_order, - local_factor, - mortar_l2, - local_mortar_weights, - output_directory) + LobattoLegendreMortarIDP{RealT, nnodes_, typeof(positivity_variables_nonlinear), + typeof(mortar_l2)}(positivity_variables_cons, + positivity_variables_nonlinear, + pure_low_order, + local_factor, + mortar_l2, + local_mortar_weights, + output_directory) end end diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index f9bb33f32fa..160d00e720f 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -595,37 +595,6 @@ end end end -@trixi_testset "elixir_euler_supersonic_cylinder_sc_subcell.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_supersonic_cylinder_sc_subcell.jl"), - l2=[ - 0.11085870166618325, - 0.23309905989870722, - 0.13505351590735631, - 0.7932047512585592 - ], - linf=[ - 2.9808773737943564, - 4.209364526217892, - 6.265341002817672, - 24.077904874883338 - ], - tspan=(0.0, 0.02), - atol=1e-7) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - # Larger values for allowed allocations due to usage of custom - # integrator which are not *recorded* for the methods from - # OrdinaryDiffEq.jl - # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 - end -end - @trixi_testset "elixir_euler_NACA6412airfoil_mach2.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_NACA6412airfoil_mach2.jl"), l2=[ diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index d0a8feabed3..cfddb8e4bc9 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -118,10 +118,11 @@ end end end -@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (local factor, first order)" begin +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (local factor, piecewise constant)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=false, local_factor=true, first_order=true, + alternative=false, local_factor=true, + basis_function==:piecewise_constant, l2=[ 2.411023984333364e-6, 2.154630267960894e-6, @@ -148,10 +149,11 @@ end end end -@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (local factor, second order)" begin +@trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (local factor, piecewise linear)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=false, local_factor=true, first_order=false, + alternative=false, local_factor=true, + basis_function==:piecewise_linear, # Note: Not conservative l2=[ 2.411023984473762e-6, @@ -557,10 +559,11 @@ end end end -@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (local factor, first order)" begin +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (local factor, piecewise constant)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=false, local_factor=true, first_order=true, + alternative=false, local_factor=true, + basis_function==:piecewise_constant, l2=[ 0.5661095633829809, 0.23409350473734566, @@ -588,10 +591,11 @@ end end end -@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (local factor, second order)" begin +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (local factor, piecewise linear)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=false, local_factor=true, first_order=false, + alternative=false, local_factor=true, + basis_function==:piecewise_linear, # Note: Not conservative l2=[ 0.5660728669927354, From 4ea76d06fa46ad9ffaeaaad09d3f5a33b038922f Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 8 Aug 2025 12:01:51 +0200 Subject: [PATCH 048/102] Fix advection test case and blast wave test results --- examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl index cff724ce370..8d742ca8082 100644 --- a/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_advection_sc_subcell.jl @@ -19,7 +19,9 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_fv = surface_flux) mortar = MortarIDP(basis; alternative = false, local_factor = true, - basis_function = :piecewise_constant) + basis_function = :piecewise_constant, + positivity_variables_cons = [1], + positivity_variables_nonlinear = []) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) From 0651513a251f43b4d03a43f65c6d8b73b8a8a134 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 8 Aug 2025 12:02:22 +0200 Subject: [PATCH 049/102] Fix test results --- test/test_tree_2d_euler.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index cfddb8e4bc9..5730544b0b6 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -598,16 +598,16 @@ end basis_function==:piecewise_linear, # Note: Not conservative l2=[ - 0.5660728669927354, - 0.23401814607355093, - 0.23426327082527165, - 0.7044435028845455 + 0.5661095633725747, + 0.23409350469189413, + 0.23444762918544046, + 0.7046253692254046 ], linf=[ - 2.3276786116939987, - 1.1830410764310964, - 1.1873953794481305, - 2.9749500856720905 + 2.3275532460560795, + 1.1838906370525126, + 1.1882587446136323, + 2.9730750339662264 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations From 1859bcac34de83124cb164168a39ed3211eb7e27 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 8 Aug 2025 14:35:02 +0200 Subject: [PATCH 050/102] Include all values in computation of alpha --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index c62ee28bd68..0c0feb9370b 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -137,6 +137,113 @@ end end end + for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + orientation = cache.mortars.orientations[mortar] + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientation == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientation == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + end + end + u_lower = get_node_vars(u, equations, dg, indices_small..., + lower_element) + u_upper = get_node_vars(u, equations, dg, indices_small..., + upper_element) + u_large = get_node_vars(u, equations, dg, indices_large..., + large_element) + var_lower = u_lower[variable] + var_upper = u_upper[variable] + var_large = u_large[variable] + + for j in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientation == 1 + # L2 mortars in x-direction + indices_small = (1, j) + indices_large = (nnodes(dg), j) + else + # L2 mortars in y-direction + indices_small = (j, 1) + indices_large = (j, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientation == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), j) + indices_large = (1, j) + else + # L2 mortars in y-direction + indices_small = (j, nnodes(dg)) + indices_large = (j, 1) + end + end + + alternative_implementation = dg.mortar isa + LobattoLegendreMortarIDPAlternative + # Inluding neighbor values to computation of bounds if local mortar weights are non-zero + # TODO: Is there a better way to do this? Inluding all neighbor values? Or use a weighted mean? + # For the alternative implementation, we include all values. + if alternative_implementation || + dg.mortar.local_mortar_weights[i, j] > 0 + var_min[indices_small..., lower_element] = min(var_min[indices_small..., + lower_element], + var_large) + var_max[indices_small..., lower_element] = max(var_max[indices_small..., + lower_element], + var_large) + end + if alternative_implementation || + dg.mortar.local_mortar_weights[j, i] > 0 + var_min[indices_large..., large_element] = min(var_min[indices_large..., + large_element], + var_lower) + var_max[indices_large..., large_element] = max(var_max[indices_large..., + large_element], + var_lower) + end + if alternative_implementation || + dg.mortar.local_mortar_weights[i, j + nnodes(dg)] > 0 + var_min[indices_small..., upper_element] = min(var_min[indices_small..., + upper_element], + var_large) + var_max[indices_small..., upper_element] = max(var_max[indices_small..., + upper_element], + var_large) + end + if alternative_implementation || + dg.mortar.local_mortar_weights[j, i + nnodes(dg)] > 0 + var_min[indices_large..., large_element] = min(var_min[indices_large..., + large_element], + var_upper) + var_max[indices_large..., large_element] = max(var_max[indices_large..., + large_element], + var_upper) + end + end + end + end + # Calc bounds at physical boundaries for boundary in eachboundary(dg, cache) element = cache.boundaries.neighbor_ids[boundary] From 7441481d420887a2d20169b7e7b09c0ffc15c3ca Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 8 Aug 2025 14:38:40 +0200 Subject: [PATCH 051/102] Fix typos --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 0c0feb9370b..8b0bb03b32d 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -201,8 +201,8 @@ end alternative_implementation = dg.mortar isa LobattoLegendreMortarIDPAlternative - # Inluding neighbor values to computation of bounds if local mortar weights are non-zero - # TODO: Is there a better way to do this? Inluding all neighbor values? Or use a weighted mean? + # Including neighbor values to computation of bounds if local mortar weights are non-zero + # TODO: Is there a better way to do this? Including all neighbor values? Or use a weighted mean? # For the alternative implementation, we include all values. if alternative_implementation || dg.mortar.local_mortar_weights[i, j] > 0 From 011fdb57769bb5a690ed7309e8384077223957bf Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 8 Aug 2025 15:21:53 +0200 Subject: [PATCH 052/102] Adapt tests accordingly --- test/test_tree_2d_euler.jl | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 5730544b0b6..76b800695cd 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -565,16 +565,16 @@ end alternative=false, local_factor=true, basis_function==:piecewise_constant, l2=[ - 0.5661095633829809, - 0.23409350473734566, - 0.23444762919022388, - 0.7046253692202586 + 0.5672804711967843, + 0.23446716073675575, + 0.23464551822979685, + 0.7045325884303651 ], linf=[ - 2.327553246065897, - 1.18389063705703, - 1.1882587446238897, - 2.973075034259535 + 2.3267777970426997, + 1.190891894691337, + 1.1930488080869828, + 2.9725153118627956 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations @@ -598,16 +598,16 @@ end basis_function==:piecewise_linear, # Note: Not conservative l2=[ - 0.5661095633725747, - 0.23409350469189413, - 0.23444762918544046, - 0.7046253692254046 + 0.5672804711967843, + 0.23446716073675575, + 0.23464551822979685, + 0.7045325884303651 ], linf=[ - 2.3275532460560795, - 1.1838906370525126, - 1.1882587446136323, - 2.9730750339662264 + 2.3267777970426997, + 1.190891894691337, + 1.1930488080869828, + 2.9725153118627956 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations @@ -629,16 +629,16 @@ end "elixir_euler_blast_wave_amr_sc_subcell.jl"), alternative=false, local_factor=false, l2=[ - 0.5600381797334268, - 0.23233414830994564, - 0.23145717236645028, - 0.7031488010819059 + 0.5609186030495756, + 0.23270612561085718, + 0.23163911425308184, + 0.7032187866160636 ], linf=[ - 2.336405445386398, - 1.178086434026379, - 1.185087035395828, - 2.9660035923836605 + 2.3363721096678614, + 1.1832027133169825, + 1.1884862671416094, + 2.967349734856432 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations @@ -660,16 +660,16 @@ end "elixir_euler_blast_wave_amr_sc_subcell.jl"), alternative=true, l2=[ - 0.5664551909566969, - 0.2341427667598265, - 0.23453132963657158, - 0.7046830527027006 + 0.5678682771158178, + 0.23466090276099555, + 0.2348166355050255, + 0.7047719997122295 ], linf=[ - 2.3242557645384525, - 1.192819817641742, - 1.1980797336997144, - 2.972294202188677 + 2.3248016157893123, + 1.2172647506881695, + 1.2206521910262236, + 2.973782261966288 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations From 9130d778b4bd0850f70bbefb4da7192bbdcb134b Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 19 Aug 2025 13:57:09 +0200 Subject: [PATCH 053/102] Add KHI elixir --- ...in_helmholtz_instability_amr_sc_subcell.jl | 119 ++++++++++++++++++ test/test_tree_2d_euler.jl | 30 +++++ 2 files changed, 149 insertions(+) create mode 100644 examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl new file mode 100644 index 00000000000..2c6c829394d --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl @@ -0,0 +1,119 @@ +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations +gamma = 1.4 +equations = CompressibleEulerEquations2D(gamma) + +""" + initial_condition_kelvin_helmholtz_instability(x, t, equations::CompressibleEulerEquations2D) + +A version of the classical Kelvin-Helmholtz instability based on +- Andrés M. Rueda-Ramírez, Gregor J. Gassner (2021) + A Subcell Finite Volume Positivity-Preserving Limiter for DGSEM Discretizations + of the Euler Equations + [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) +""" +function initial_condition_kelvin_helmholtz_instability(x, t, + equations::CompressibleEulerEquations2D) + # change discontinuity to tanh + # typical resolution 128^2, 256^2 + # domain size is [-1,+1]^2 + RealT = eltype(x) + slope = 15 + B = tanh(slope * x[2] + 7.5f0) - tanh(slope * x[2] - 7.5f0) + rho = 0.5f0 + 0.75f0 * B + v1 = 0.5f0 * (B - 1) + v2 = convert(RealT, 0.1) * sinpi(2 * x[1]) + p = 1 + return prim2cons(SVector(rho, v1, v2, p), equations) +end +initial_condition = initial_condition_kelvin_helmholtz_instability + +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha +polydeg = 7 +basis = LobattoLegendreBasis(polydeg) + +limiter_idp = SubcellLimiterIDP(equations, basis; + positivity_variables_cons = ["rho"], + positivity_variables_nonlinear = [pressure]) +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +mortar = MortarIDP(basis; alternative = false, local_factor = true, + basis_function = :piecewise_constant, + # basis_function = :piecewise_linear, + pure_low_order = false) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +initial_refinement_level = 5 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = initial_refinement_level, + n_cells_max = 100_000) +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 3.7) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) + +save_restart = SaveRestartCallback(interval = 1000, + save_final_restart = true) + +positivity_limiter = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, + pressure)) + +amr_indicator = IndicatorHennemannGassner(semi, + alpha_max = 1.0, + alpha_min = 0.0001, + alpha_smooth = false, + variable = Trixi.density) +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = initial_refinement_level - 1, + med_level = 0, med_threshold = 0.0003, # med_level = current level + max_level = initial_refinement_level + 1, + max_threshold = 0.003) +amr_callback = AMRCallback(semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true, + limiter! = positivity_limiter) + +stepsize_callback = StepsizeCallback(cfl = 0.3) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, save_restart, + amr_callback, + stepsize_callback) + +############################################################################### +# run the simulation + +stage_callbacks = (SubcellLimiterIDPCorrection(), + BoundsCheckCallback(save_errors = false, interval = 100)) +# `interval` is used when calling this elixir in the tests with `save_errors=true`. + +sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., + callback = callbacks); diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 76b800695cd..5a965253ad6 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -966,6 +966,36 @@ end end end +@trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl"), + initial_refinement_level=3, + l2=[ + 0.4179145123459485, + 0.15616370558964487, + 0.1419009545896219, + 0.12520502832022837 + ], + linf=[ + 1.4144723336072134, + 0.5393706094054379, + 0.3286822496473434, + 0.3572431552090598 + ]) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + @trixi_testset "elixir_euler_colliding_flow.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_colliding_flow.jl"), l2=[ From e992a62cd3dc699542dbbec797107bb4abcaad2a Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 19 Aug 2025 14:48:44 +0200 Subject: [PATCH 054/102] Adapt default setting of limiting --- ...ir_euler_supersonic_cylinder_sc_subcell.jl | 166 ++++++++++++++++++ .../elixir_euler_blast_wave_amr_sc_subcell.jl | 2 + ...elixir_euler_convergence_amr_sc_subcell.jl | 23 ++- ...lixir_euler_density_wave_amr_sc_subcell.jl | 4 +- ...in_helmholtz_instability_amr_sc_subcell.jl | 7 +- ...r_euler_sedov_blast_wave_amr_sc_subcell.jl | 11 +- src/solvers/dgsem/basis_lobatto_legendre.jl | 4 +- test/test_p4est_2d.jl | 31 ++++ 8 files changed, 226 insertions(+), 22 deletions(-) create mode 100644 examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl diff --git a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl new file mode 100644 index 00000000000..4e2bca5e9af --- /dev/null +++ b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl @@ -0,0 +1,166 @@ +# Channel flow around a cylinder at Mach 3 +# +# Boundary conditions are supersonic Mach 3 inflow at the left portion of the domain +# and supersonic outflow at the right portion of the domain. The top and bottom of the +# channel as well as the cylinder are treated as Euler slip wall boundaries. +# This flow results in strong shock reflections / interactions as well as Kelvin-Helmholtz +# instabilities at later times as two Mach stems form above and below the cylinder. +# +# For complete details on the problem setup see Section 5.7 of the paper: +# - Jean-Luc Guermond, Murtazo Nazarov, Bojan Popov, and Ignacio Tomas (2018) +# Second-Order Invariant Domain Preserving Approximation of the Euler Equations using Convex Limiting. +# [DOI: 10.1137/17M1149961](https://doi.org/10.1137/17M1149961) +# +# Keywords: supersonic flow, shock capturing, AMR, unstructured curved mesh, positivity preservation, compressible Euler, 2D + +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +@inline function initial_condition_mach3_flow(x, t, equations::CompressibleEulerEquations2D) + # set the freestream flow parameters + rho_freestream = 1.4 + v1 = 3.0 + v2 = 0.0 + p_freestream = 1.0 + + prim = SVector(rho_freestream, v1, v2, p_freestream) + return prim2cons(prim, equations) +end + +initial_condition = initial_condition_mach3_flow + +# Supersonic inflow boundary condition. +# Calculate the boundary flux entirely from the external solution state, i.e., set +# external solution state values for everything entering the domain. +@inline function boundary_condition_supersonic_inflow(u_inner, + normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::CompressibleEulerEquations2D) + u_boundary = initial_condition_mach3_flow(x, t, equations) + flux = Trixi.flux(u_boundary, normal_direction, equations) + + return flux +end + +# For subcell limiting, the calculation of local bounds for non-periodic domains requires the +# boundary outer state. Those functions return the boundary value for a specific boundary condition +# at time `t`, for the node with spatial indices `indices` and the given `normal_direction`. +@inline function Trixi.get_boundary_outer_state(u_inner, t, + boundary_condition::typeof(boundary_condition_supersonic_inflow), + normal_direction::AbstractVector, + mesh::P4estMesh, equations, dg, cache, + indices...) + x = Trixi.get_node_coords(cache.elements.node_coordinates, equations, dg, indices...) + + return initial_condition_mach3_flow(x, t, equations) +end + +# Supersonic outflow boundary condition. +# Calculate the boundary flux entirely from the internal solution state. Analogous to supersonic inflow +# except all the solution state values are set from the internal solution as everything leaves the domain +@inline function boundary_condition_outflow(u_inner, normal_direction::AbstractVector, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D) + flux = Trixi.flux(u_inner, normal_direction, equations) + + return flux +end + +@inline function Trixi.get_boundary_outer_state(u_inner, t, + boundary_condition::typeof(boundary_condition_outflow), + normal_direction::AbstractVector, + mesh::P4estMesh, equations, dg, cache, + indices...) + return u_inner +end + +@inline function Trixi.get_boundary_outer_state(u_inner, t, + boundary_condition::typeof(boundary_condition_slip_wall), + normal_direction::AbstractVector, + mesh::P4estMesh{2}, + equations::CompressibleEulerEquations2D, + dg, cache, indices...) + factor = (normal_direction[1] * u_inner[2] + normal_direction[2] * u_inner[3]) + u_normal = (factor / sum(normal_direction .^ 2)) * normal_direction + + return SVector(u_inner[1], + u_inner[2] - 2 * u_normal[1], + u_inner[3] - 2 * u_normal[2], + u_inner[4]) +end + +boundary_conditions = Dict(:Bottom => boundary_condition_slip_wall, + :Circle => boundary_condition_slip_wall, + :Top => boundary_condition_slip_wall, + :Right => boundary_condition_outflow, + :Left => boundary_condition_supersonic_inflow) + +volume_flux = flux_ranocha_turbo +# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of +# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. +# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. +# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. +# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. +# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the +# `StepsizeCallback` (CFL-Condition) and less diffusion. +surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) +limiter_idp = SubcellLimiterIDP(equations, basis; + local_twosided_variables_cons = ["rho"], + positivity_variables_nonlinear = [pressure], + local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, + min)], + max_iterations_newton = 50) # Default value of 10 iterations is too low to fulfill bounds. + +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +solver = DGSEM(basis, surface_flux, volume_integral) + +# Get the unstructured quad mesh from a file (downloads the file if not available locally) +mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/a08f78f6b185b63c3baeff911a63f628/raw/addac716ea0541f588b9d2bd3f92f643eb27b88f/abaqus_cylinder_in_channel.inp", + joinpath(@__DIR__, "abaqus_cylinder_in_channel.inp")) + +mesh = P4estMesh{2}(mesh_file, initial_refinement_level = 0) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +# Callbacks + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) + +stepsize_callback = StepsizeCallback(cfl = 0.8) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) + +sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + callback = callbacks); diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl index df292f69f16..ac40f5b21fd 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr_sc_subcell.jl @@ -53,6 +53,8 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_fv = surface_flux) mortar = MortarIDP(basis, alternative = false, local_factor = true, basis_function = :piecewise_constant, + positivity_variables_cons = [1], + positivity_variables_nonlinear = [pressure], pure_low_order = true) solver = DGSEM(basis, surface_flux, volume_integral, mortar) diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index 05a69f79ff5..d4a2969b2e6 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -25,6 +25,9 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; mortar = MortarIDP(basis; alternative = false, local_factor = true, basis_function = :piecewise_constant, + # basis_function = :piecewise_linear, + positivity_variables_cons = [1], + positivity_variables_nonlinear = [pressure], pure_low_order = false) solver = DGSEM(basis, surface_flux, volume_integral, mortar) @@ -62,17 +65,19 @@ save_solution = SaveSolutionCallback(interval = 100, solution_variables = cons2prim, extra_node_variables = (:limiting_coefficient,)) -# amr_indicator = IndicatorMax(semi, variable = first) +amr_indicator = IndicatorMax(semi, variable = first) -# amr_controller = ControllerThreeLevel(semi, amr_indicator, -# base_level = initial_refinement_level, -# med_level = initial_refinement_level + 1, med_threshold = 2.0, -# max_level = initial_refinement_level + 2, max_threshold = 2.05) +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = initial_refinement_level, + med_level = initial_refinement_level + 1, + med_threshold = 2.0, + max_level = initial_refinement_level + 2, + max_threshold = 2.05) -# amr_callback = AMRCallback(semi, amr_controller, -# interval = 1, -# adapt_initial_condition = true, -# adapt_initial_condition_only_refine = false) +amr_callback = AMRCallback(semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = false) stepsize_callback = StepsizeCallback(cfl = 0.8) diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl index afd3a17ac77..8ed96b8e36f 100644 --- a/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_amr_sc_subcell.jl @@ -21,7 +21,9 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_fv = surface_flux) mortar = MortarIDP(basis; alternative = false, local_factor = true, - basis_function = :piecewise_constant) + basis_function = :piecewise_constant, + positivity_variables_cons = [1], + positivity_variables_nonlinear = [pressure]) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl index 2c6c829394d..24909b6a49c 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl @@ -43,7 +43,8 @@ volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_fv = surface_flux) mortar = MortarIDP(basis; alternative = false, local_factor = true, basis_function = :piecewise_constant, - # basis_function = :piecewise_linear, + positivity_variables_cons = [1], + positivity_variables_nonlinear = [pressure], pure_low_order = false) solver = DGSEM(basis, surface_flux, volume_integral, mortar) @@ -109,9 +110,7 @@ callbacks = CallbackSet(summary_callback, ############################################################################### # run the simulation -stage_callbacks = (SubcellLimiterIDPCorrection(), - BoundsCheckCallback(save_errors = false, interval = 100)) -# `interval` is used when calling this elixir in the tests with `save_errors=true`. +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl index 9693e64a33d..dde1ea40c71 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl @@ -53,7 +53,10 @@ limiter_idp = SubcellLimiterIDP(equations, basis; volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = MortarIDP(basis, basis_function = :piecewise_constant, pure_low_order = false) +mortar = MortarIDP(basis; basis_function = :piecewise_constant, + positivity_variables_cons = [1], + positivity_variables_nonlinear = [pressure], + pure_low_order = false) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) @@ -108,11 +111,7 @@ callbacks = CallbackSet(summary_callback, ############################################################################### # run the simulation -# positivity limiter necessary for this tough example -stage_limiter! = LimiterZhangShuLocalBounds(limiters = (:positivity,), - variables = (pressure,)) - -stage_callbacks = (SubcellLimiterIDPCorrection(), #stage_limiter!, +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = false)) sol = Trixi.solve(ode, diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 22142f1518f..52c63b9fe02 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -199,8 +199,8 @@ struct LobattoLegendreMortarIDPAlternative{RealT <: Real, NNODES, end function MortarIDP(basis::LobattoLegendreBasis; - positivity_variables_cons = [1], # TODO: String["rho"] - positivity_variables_nonlinear = [Trixi.pressure], # TODO: [] + positivity_variables_cons = Int[], # TODO: String[], "rho" instead of 1 + positivity_variables_nonlinear = [], alternative = false, local_factor = true, basis_function = :piecewise_constant, pure_low_order = false, diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index 160d00e720f..f9bb33f32fa 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -595,6 +595,37 @@ end end end +@trixi_testset "elixir_euler_supersonic_cylinder_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_supersonic_cylinder_sc_subcell.jl"), + l2=[ + 0.11085870166618325, + 0.23309905989870722, + 0.13505351590735631, + 0.7932047512585592 + ], + linf=[ + 2.9808773737943564, + 4.209364526217892, + 6.265341002817672, + 24.077904874883338 + ], + tspan=(0.0, 0.02), + atol=1e-7) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + @trixi_testset "elixir_euler_NACA6412airfoil_mach2.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_NACA6412airfoil_mach2.jl"), l2=[ From 9a249e2854bd7c3db0e79bccea0754c7a5ee27d2 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 22 Aug 2025 13:58:31 +0200 Subject: [PATCH 055/102] Clean up; Remove ZhangShu with variable bounds --- src/Trixi.jl | 3 +- src/callbacks_stage/positivity_zhang_shu.jl | 64 ------------------- .../positivity_zhang_shu_dg2d.jl | 63 ------------------ 3 files changed, 1 insertion(+), 129 deletions(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index d038c71b08a..5102aa9b839 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -302,8 +302,7 @@ export load_mesh, load_time, load_timestep, load_timestep!, load_dt, export ControllerThreeLevel, ControllerThreeLevelCombined, IndicatorLöhner, IndicatorLoehner, IndicatorMax -export PositivityPreservingLimiterZhangShu, EntropyBoundedLimiter, - LimiterZhangShuLocalBounds +export PositivityPreservingLimiterZhangShu, EntropyBoundedLimiter export trixi_include, examples_dir, get_examples, default_example, default_example_unstructured, ode_default_options diff --git a/src/callbacks_stage/positivity_zhang_shu.jl b/src/callbacks_stage/positivity_zhang_shu.jl index c29cf2eeb26..a68bb9374a0 100644 --- a/src/callbacks_stage/positivity_zhang_shu.jl +++ b/src/callbacks_stage/positivity_zhang_shu.jl @@ -74,70 +74,6 @@ end init_callback(limiter!::PositivityPreservingLimiterZhangShu, semi) = nothing finalize_callback(limiter!::PositivityPreservingLimiterZhangShu, semi) = nothing -struct LimiterZhangShuLocalBounds{N, Variables <: NTuple{N, Any}} - variables::Variables - limiters::NTuple{N, Symbol} -end - -function LimiterZhangShuLocalBounds(; variables, - limiters = ntuple(_ -> :positivity, - length(variables))) - if length(variables) != length(limiters) - throw(ArgumentError("The number of variables must match the number of limiters.")) - end - for limiter in limiters - if !(limiter in (:positivity, :minmax)) - throw(ArgumentError("The limiter $limiter must be either :positivity or :minmax.")) - end - end - - LimiterZhangShuLocalBounds(variables, limiters) -end - -function (limiter!::LimiterZhangShuLocalBounds)(u_ode, integrator, - semi::AbstractSemidiscretization, - t) - u = wrap_array(u_ode, semi) - @trixi_timeit timer() "positivity-preserving limiter" begin - limiter_zhang_shu!(u, limiter!.limiters, limiter!.variables, - mesh_equations_solver_cache(semi)...) - end -end - -# Iterate over tuples in a type-stable way using "lispy tuple programming", -# similar to https://stackoverflow.com/a/55849398: -# Iterating over tuples of different functions isn't type-stable in general -# but accessing the first element of a tuple is type-stable. Hence, it's good -# to process one element at a time and replace iteration by recursion here. -# Note that you shouldn't use this with too many elements per tuple since the -# compile times can increase otherwise - but a handful of elements per tuple -# is definitely fine. -function limiter_zhang_shu!(u, limiters::NTuple{N, <:Symbol}, variables::NTuple{N, Any}, - mesh, equations, solver, cache) where {N} - limiter = first(limiters) - remaining_limiters = Base.tail(limiters) - variable = first(variables) - remaining_variables = Base.tail(variables) - - if limiter == :positivity - limiter_zhang_shu_positivity!(u, variable, mesh, equations, solver, cache) - else # limiter == :minmax - error("TODO: Implement the minmax limiter.") - limiter_zhang_shu_minmax!(u, variable, mesh, equations, solver, cache) - end - limiter_zhang_shu!(u, remaining_limiters, remaining_variables, mesh, equations, - solver, cache) - return nothing -end - -# Allow use of limiter as a stage callback in custom SSP integrators -function (limiter!::LimiterZhangShuLocalBounds)(u_ode, integrator, - stage) - return limiter!(u_ode, integrator, integrator.p, integrator.t) -end -init_callback(limiter!::LimiterZhangShuLocalBounds, semi) = nothing -finalize_callback(limiter!::LimiterZhangShuLocalBounds, semi) = nothing - include("positivity_zhang_shu_dg1d.jl") include("positivity_zhang_shu_dg2d.jl") include("positivity_zhang_shu_dg3d.jl") diff --git a/src/callbacks_stage/positivity_zhang_shu_dg2d.jl b/src/callbacks_stage/positivity_zhang_shu_dg2d.jl index a6c05dedaef..813dd65878b 100644 --- a/src/callbacks_stage/positivity_zhang_shu_dg2d.jl +++ b/src/callbacks_stage/positivity_zhang_shu_dg2d.jl @@ -47,67 +47,4 @@ function limiter_zhang_shu!(u, threshold::Real, variable, return nothing end - -function limiter_zhang_shu_positivity!(u, variable, - mesh::AbstractMesh{2}, equations, dg::DGSEM, - cache) - (; weights) = dg.basis - (; inverse_jacobian) = cache.elements - - (; limiter) = dg.volume_integral - (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - - # TODO - # if variable == density - # bound_min = variable_bounds[Symbol("1_min")] - # else - if variable == pressure - if pressure in dg.volume_integral.limiter.positivity_variables_nonlinear - bound_min = variable_bounds[:pressure_min] - else - error("Positivity limiting for variable $variable by LimiterZhangShuLocalBounds - requires matching limiter of the IDPLimiter.") - end - else - error("Unknown variable $variable for subcell limiter coefficients") - end - - @threaded for element in eachelement(dg, cache) - # determine minimum value - value_min = typemax(eltype(u)) - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - value_min = min(value_min, variable(u_node, equations)) - end - - # detect if limiting is necessary - threshold = minimum(view(bound_min, :, :, element)) - value_min < threshold || continue - - # compute mean value - u_mean = zero(get_node_vars(u, equations, dg, 1, 1, element)) - total_volume = zero(eltype(u)) - for j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(get_inverse_jacobian(inverse_jacobian, mesh, - i, j, element))) - u_node = get_node_vars(u, equations, dg, i, j, element) - u_mean += u_node * weights[i] * weights[j] * volume_jacobian - total_volume += weights[i] * weights[j] * volume_jacobian - end - # normalize with the total volume - u_mean = u_mean / total_volume - - # We compute the value directly with the mean values, as we assume that - # Jensen's inequality holds (e.g. pressure for compressible Euler equations). - value_mean = variable(u_mean, equations) - theta = (value_mean - threshold) / (value_mean - value_min) - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - set_node_vars!(u, theta * u_node + (1 - theta) * u_mean, - equations, dg, i, j, element) - end - end - - return nothing -end end # @muladd From 43f70426cc2b3159ae9ff5ffc0a525b98871f919 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 25 Aug 2025 13:06:19 +0200 Subject: [PATCH 056/102] Decrease CFL number --- ...elixir_euler_convergence_amr_sc_subcell.jl | 4 +- ...r_euler_sedov_blast_wave_amr_sc_subcell.jl | 17 ++-- test/test_tree_2d_euler.jl | 82 +++++++++---------- 3 files changed, 49 insertions(+), 54 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl index d4a2969b2e6..da47c7974fe 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_amr_sc_subcell.jl @@ -6,7 +6,6 @@ using Trixi equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test -# initial_condition = initial_condition_constant surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha @@ -79,7 +78,7 @@ amr_callback = AMRCallback(semi, amr_controller, adapt_initial_condition = true, adapt_initial_condition_only_refine = false) -stepsize_callback = StepsizeCallback(cfl = 0.8) +stepsize_callback = StepsizeCallback(cfl = 0.5) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, @@ -93,7 +92,6 @@ callbacks = CallbackSet(summary_callback, stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = true)) sol = Trixi.solve(ode, - # Trixi.SimpleEuler(stage_callbacks = stage_callbacks); Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback ode_default_options()..., diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl index dde1ea40c71..71bd976562b 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_amr_sc_subcell.jl @@ -62,7 +62,7 @@ solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, + initial_refinement_level = 6, n_cells_max = 100_000, periodicity = true) @@ -88,17 +88,18 @@ save_solution = SaveSolutionCallback(interval = 100, solution_variables = cons2prim, extra_node_variables = (:limiting_coefficient,)) -amr_indicator = IndicatorMax(semi, variable = density_pressure) - +amr_indicator = IndicatorHennemannGassner(semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure) amr_controller = ControllerThreeLevel(semi, amr_indicator, base_level = 4, - med_level = 6, med_threshold = 1.0, - max_level = 7, max_threshold = 1.05) - + max_level = 6, max_threshold = 0.01) amr_callback = AMRCallback(semi, amr_controller, - interval = 1, + interval = 5, adapt_initial_condition = true, - adapt_initial_condition_only_refine = false) + adapt_initial_condition_only_refine = true) stepsize_callback = StepsizeCallback(cfl = 0.1) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 5a965253ad6..886bd67b2a6 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -63,16 +63,16 @@ end "elixir_euler_convergence_amr_sc_subcell.jl"), alternative=true, l2=[ - 6.792434932529532e-5, - 6.120715609381622e-5, - 6.055378873143237e-5, - 0.00014030465047211284 + 6.792232430676736e-5, + 6.120808404461362e-5, + 6.0553402466469644e-5, + 0.00014031086876416186 ], linf=[ - 0.0005470070709066022, - 0.0004685919440707842, - 0.00047891796273047404, - 0.0015576242650183758 + 0.0005469885653583972, + 0.0004685379311402116, + 0.00047890096347513733, + 0.0015576432846629018 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -91,18 +91,18 @@ end @trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (global factor)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=false, local_factor=false, + local_factor=false, l2=[ - 2.4110239842278583e-6, - 2.154630267786356e-6, - 2.1283762418750586e-6, - 6.113773134527389e-6 + 2.410500253308728e-6, + 2.154017662663813e-6, + 2.1277540989692593e-6, + 6.113495769001695e-6 ], linf=[ - 1.696740325396462e-5, - 1.695721640926351e-5, - 1.656799716243107e-5, - 5.129734239561756e-5 + 1.7009808553680728e-5, + 1.691987991536692e-5, + 1.6530653468738166e-5, + 5.124340149453843e-5 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -121,19 +121,18 @@ end @trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (local factor, piecewise constant)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=false, local_factor=true, - basis_function==:piecewise_constant, + basis_function=:piecewise_constant, l2=[ - 2.411023984333364e-6, - 2.154630267960894e-6, - 2.1283762418214893e-6, - 6.113773134665568e-6 + 2.410500253406329e-6, + 2.1540176628043107e-6, + 2.127754098869624e-6, + 6.113495769130457e-6 ], linf=[ - 1.6967403261958225e-5, - 1.6957216406598974e-5, - 1.6567997161320847e-5, - 5.1297342376521726e-5 + 1.7009808555901174e-5, + 1.6919879904930823e-5, + 1.6530653472068835e-5, + 5.124340147499851e-5 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -152,20 +151,19 @@ end @trixi_testset "elixir_euler_convergence_amr_sc_subcell.jl (local factor, piecewise linear)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_amr_sc_subcell.jl"), - alternative=false, local_factor=true, - basis_function==:piecewise_linear, + basis_function=:piecewise_linear, # Note: Not conservative l2=[ - 2.411023984473762e-6, - 2.154630268012045e-6, - 2.128376241902082e-6, - 6.113773134790818e-6 + 2.410500253787437e-6, + 2.154017662974512e-6, + 2.1277540992738367e-6, + 6.11349576951523e-6 ], linf=[ - 1.696740326551094e-5, - 1.6957216412816223e-5, - 1.6567997168426274e-5, - 5.129734239517347e-5 + 1.7009808543910765e-5, + 1.691987990715127e-5, + 1.6530653453861177e-5, + 5.124340147988349e-5 ]) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -562,8 +560,7 @@ end @trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (local factor, piecewise constant)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=false, local_factor=true, - basis_function==:piecewise_constant, + basis_function=:piecewise_constant, l2=[ 0.5672804711967843, 0.23446716073675575, @@ -594,9 +591,8 @@ end @trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (local factor, piecewise linear)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=false, local_factor=true, - basis_function==:piecewise_linear, - # Note: Not conservative + basis_function=:piecewise_linear, + # TODO: Not conservative l2=[ 0.5672804711967843, 0.23446716073675575, @@ -627,7 +623,7 @@ end @trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (global factor)" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr_sc_subcell.jl"), - alternative=false, local_factor=false, + local_factor=false, l2=[ 0.5609186030495756, 0.23270612561085718, From 48dbebe64062bb128347e3c2b7b49f3277b423d6 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 26 Aug 2025 10:49:36 +0200 Subject: [PATCH 057/102] Fix test --- test/test_tree_2d_euler.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 886bd67b2a6..77abda95565 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -594,16 +594,16 @@ end basis_function=:piecewise_linear, # TODO: Not conservative l2=[ - 0.5672804711967843, - 0.23446716073675575, - 0.23464551822979685, - 0.7045325884303651 + 0.566909163671664, + 0.23421137344169807, + 0.23433798250243137, + 0.704392032534161 ], linf=[ - 2.3267777970426997, - 1.190891894691337, - 1.1930488080869828, - 2.9725153118627956 + 2.326038178632753, + 1.1992748199614607, + 1.2040176700222254, + 2.9731919913455216 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations From 427e6776cdaa0b6ab062ba192c32294068f270cd Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 26 Aug 2025 13:55:18 +0200 Subject: [PATCH 058/102] Add KHI test --- ...in_helmholtz_instability_amr_sc_subcell.jl | 21 +++++-------- test/test_tree_2d_euler.jl | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl index 24909b6a49c..f38e5da31d7 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl @@ -32,7 +32,7 @@ initial_condition = initial_condition_kelvin_helmholtz_instability surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha -polydeg = 7 +polydeg = 3 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; @@ -50,9 +50,8 @@ solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -initial_refinement_level = 5 mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = initial_refinement_level, + initial_refinement_level = 5, n_cells_max = 100_000) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -64,7 +63,7 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() -analysis_interval = 1000 +analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval, extra_analysis_errors = (:conservation_error,)) @@ -79,25 +78,19 @@ save_solution = SaveSolutionCallback(interval = 100, save_restart = SaveRestartCallback(interval = 1000, save_final_restart = true) -positivity_limiter = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), - variables = (Trixi.density, - pressure)) - amr_indicator = IndicatorHennemannGassner(semi, alpha_max = 1.0, alpha_min = 0.0001, alpha_smooth = false, variable = Trixi.density) amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = initial_refinement_level - 1, - med_level = 0, med_threshold = 0.0003, # med_level = current level - max_level = initial_refinement_level + 1, - max_threshold = 0.003) + base_level = 4, + med_level = 0, med_threshold = 0.0003, + max_level = 6, max_threshold = 0.003) amr_callback = AMRCallback(semi, amr_controller, interval = 1, adapt_initial_condition = true, - adapt_initial_condition_only_refine = true, - limiter! = positivity_limiter) + adapt_initial_condition_only_refine = true) stepsize_callback = StepsizeCallback(cfl = 0.3) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 77abda95565..58195b80584 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -962,6 +962,36 @@ end end end +@trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl"), + l2=[ + 0.055724454952595134, + 0.03312233274919345, + 0.05223955492824185, + 0.080116031615237 + ], + linf=[ + 0.2529113255620048, + 0.17296154306769038, + 0.1232191089800711, + 0.2691634973257111 + ], + tspan=(0.0, 0.2)) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + @trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl"), From e2cfe5b76be5e620f78411225270ccb543cecd9e Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 26 Aug 2025 14:20:25 +0200 Subject: [PATCH 059/102] Include all values if `!local_factor` --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 0931b4e7a49..a51f7fec4d8 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -111,6 +111,7 @@ end mesh::TreeMesh2D) _, equations, dg, cache = mesh_equations_solver_cache(semi) (; boundary_conditions) = semi + (local_factor) = dg.mortar # Calc bounds at interfaces and periodic boundaries for interface in eachinterface(dg, cache) # Get neighboring element ids @@ -206,7 +207,7 @@ end # Including neighbor values to computation of bounds if local mortar weights are non-zero # TODO: Is there a better way to do this? Including all neighbor values? Or use a weighted mean? # For the alternative implementation, we include all values. - if alternative_implementation || + if alternative_implementation || !local_factor || dg.mortar.local_mortar_weights[i, j] > 0 var_min[indices_small..., lower_element] = min(var_min[indices_small..., lower_element], @@ -215,7 +216,7 @@ end lower_element], var_large) end - if alternative_implementation || + if alternative_implementation || !local_factor || dg.mortar.local_mortar_weights[j, i] > 0 var_min[indices_large..., large_element] = min(var_min[indices_large..., large_element], @@ -224,7 +225,7 @@ end large_element], var_lower) end - if alternative_implementation || + if alternative_implementation || !local_factor || dg.mortar.local_mortar_weights[i, j + nnodes(dg)] > 0 var_min[indices_small..., upper_element] = min(var_min[indices_small..., upper_element], @@ -233,7 +234,7 @@ end upper_element], var_large) end - if alternative_implementation || + if alternative_implementation || !local_factor || dg.mortar.local_mortar_weights[j, i + nnodes(dg)] > 0 var_min[indices_large..., large_element] = min(var_min[indices_large..., large_element], From 16a7d472988f5a36c760f3cce8165d0f5c8a3a2e Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 26 Aug 2025 14:53:26 +0200 Subject: [PATCH 060/102] Fix last commit --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 2 +- test/test_tree_2d_euler.jl | 30 ------------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index a51f7fec4d8..c86cccffc04 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -111,7 +111,7 @@ end mesh::TreeMesh2D) _, equations, dg, cache = mesh_equations_solver_cache(semi) (; boundary_conditions) = semi - (local_factor) = dg.mortar + (; local_factor) = dg.mortar # Calc bounds at interfaces and periodic boundaries for interface in eachinterface(dg, cache) # Get neighboring element ids diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 58195b80584..b02d5aa7180 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -992,36 +992,6 @@ end end end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl"), - initial_refinement_level=3, - l2=[ - 0.4179145123459485, - 0.15616370558964487, - 0.1419009545896219, - 0.12520502832022837 - ], - linf=[ - 1.4144723336072134, - 0.5393706094054379, - 0.3286822496473434, - 0.3572431552090598 - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - # Larger values for allowed allocations due to usage of custom - # integrator which are not *recorded* for the methods from - # OrdinaryDiffEq.jl - # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 - end -end - @trixi_testset "elixir_euler_colliding_flow.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_colliding_flow.jl"), l2=[ From 235af37e9d8ad723e561f4606bb130e12090d930 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 26 Aug 2025 16:22:16 +0200 Subject: [PATCH 061/102] Fix bug --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 24 +++++++++---------- test/test_tree_2d_euler.jl | 16 ++++++------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index c86cccffc04..aedf12dd2cf 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -111,7 +111,6 @@ end mesh::TreeMesh2D) _, equations, dg, cache = mesh_equations_solver_cache(semi) (; boundary_conditions) = semi - (; local_factor) = dg.mortar # Calc bounds at interfaces and periodic boundaries for interface in eachinterface(dg, cache) # Get neighboring element ids @@ -140,6 +139,10 @@ end end end + # TODO: How to include values at mortar interfaces? + # - For "alternative implementation" and "global factors" include all neighboring values + # - For "local factors" include only values with nonnegative local weights + # - For LobattoLegendreMortarL2: include all neighboring values (TODO?) for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] @@ -202,13 +205,11 @@ end end end - alternative_implementation = dg.mortar isa - LobattoLegendreMortarIDPAlternative - # Including neighbor values to computation of bounds if local mortar weights are non-zero - # TODO: Is there a better way to do this? Including all neighbor values? Or use a weighted mean? - # For the alternative implementation, we include all values. - if alternative_implementation || !local_factor || - dg.mortar.local_mortar_weights[i, j] > 0 + l2_mortars = dg.mortar isa LobattoLegendreMortarL2 + alternative = dg.mortar isa LobattoLegendreMortarIDPAlternative + include_all_values = l2_mortars || alternative || + !(dg.mortar.local_factor) + if include_all_values || dg.mortar.local_mortar_weights[i, j] > 0 var_min[indices_small..., lower_element] = min(var_min[indices_small..., lower_element], var_large) @@ -216,8 +217,7 @@ end lower_element], var_large) end - if alternative_implementation || !local_factor || - dg.mortar.local_mortar_weights[j, i] > 0 + if include_all_values || dg.mortar.local_mortar_weights[j, i] > 0 var_min[indices_large..., large_element] = min(var_min[indices_large..., large_element], var_lower) @@ -225,7 +225,7 @@ end large_element], var_lower) end - if alternative_implementation || !local_factor || + if include_all_values || dg.mortar.local_mortar_weights[i, j + nnodes(dg)] > 0 var_min[indices_small..., upper_element] = min(var_min[indices_small..., upper_element], @@ -234,7 +234,7 @@ end upper_element], var_large) end - if alternative_implementation || !local_factor || + if include_all_values || dg.mortar.local_mortar_weights[j, i + nnodes(dg)] > 0 var_min[indices_large..., large_element] = min(var_min[indices_large..., large_element], diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index b02d5aa7180..53246c29099 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -625,16 +625,16 @@ end "elixir_euler_blast_wave_amr_sc_subcell.jl"), local_factor=false, l2=[ - 0.5609186030495756, - 0.23270612561085718, - 0.23163911425308184, - 0.7032187866160636 + 0.5616573309203616, + 0.23273235124681171, + 0.23197575287152258, + 0.7031938693283342 ], linf=[ - 2.3363721096678614, - 1.1832027133169825, - 1.1884862671416094, - 2.967349734856432 + 2.3375861953330572, + 1.1853869941954542, + 1.1905688094352715, + 2.967225179961728 ], tspan=(0.0, 1.0)) # Ensure that we do not have excessive memory allocations From 7c76caef21ea3226409e1f49f62f962f3af5b885 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 26 Aug 2025 17:08:39 +0200 Subject: [PATCH 062/102] Thread parallel computation of low order mortar flux --- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 4d4d1d74ca8..ed86774160c 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -1023,7 +1023,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, (; weights) = dg.basis (; local_mortar_weights, local_factor) = mortar_idp - for mortar in eachmortar(dg, cache) #=@threaded =# + @threaded for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] lower_element = cache.mortars.neighbor_ids[1, mortar] From 31d162faf969b5f6d6cc1b5485f0fbd9d8c6caa9 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 26 Aug 2025 18:15:55 +0200 Subject: [PATCH 063/102] Remove doubled code --- .../dgsem_tree/dg_2d_subcell_limiters.jl | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index ed86774160c..671d1c8cf56 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -59,30 +59,6 @@ function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, flux_temp_threaded, fhat_temp_threaded) end -# The methods below are specialized on the mortar type -# and called from the basic `create_cache` method at the top. -function create_cache(mesh::TreeMesh{2}, - equations, - mortar_idp::Union{LobattoLegendreMortarIDP, - LobattoLegendreMortarIDPAlternative}, uEltype) - # TODO: Taal performance using different types - - # TODO: What do I really need? - MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_idp)}, uEltype, 2, - nvariables(equations) * nnodes(mortar_idp)} - fstar_primary_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - fstar_primary_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - fstar_secondary_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - fstar_secondary_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - - # A2d = Array{uEltype, 2} - # fstar_upper_threaded = [A2d(undef, nvariables(equations), nnodes(mortar_idp)) for _ in 1:Threads.nthreads()] - # fstar_lower_threaded = [A2d(undef, nvariables(equations), nnodes(mortar_idp)) for _ in 1:Threads.nthreads()] - - (; fstar_primary_upper_threaded, fstar_primary_lower_threaded, - fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) -end - function calc_mortar_weights(basis, RealT; basis_function = :piecewise_constant) n_nodes = nnodes(basis) # Saving the sum over row/column in entries with last index. From 29860839a38e59fc9b8bbe7129c1211b95e9c547 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 27 Aug 2025 12:14:20 +0200 Subject: [PATCH 064/102] Move high order surface_flux_values to container antidiffusive fluxes --- .../subcell_limiter_idp_correction_2d.jl | 9 ++-- src/solvers/dgsem_tree/containers_2d.jl | 42 +++++++++---------- .../dgsem_tree/dg_2d_subcell_limiters.jl | 2 +- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index b6bedce7645..a6c8639b799 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -85,7 +85,8 @@ end mesh, equations, dg, cache = mesh_equations_solver_cache(semi) (; orientations) = cache.mortars - (; surface_flux_values, surface_flux_values_high_order) = cache.elements + (; surface_flux_values) = cache.elements + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes (; boundary_interpolation) = dg.basis (; positivity_correction_factor) = dg.volume_integral.limiter @@ -251,7 +252,8 @@ end mesh, equations, dg, cache = mesh_equations_solver_cache(semi) (; orientations) = cache.mortars - (; surface_flux_values, surface_flux_values_high_order) = cache.elements + (; surface_flux_values) = cache.elements + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes (; boundary_interpolation) = dg.basis (; limiter) = dg.volume_integral @@ -376,7 +378,8 @@ end (; mesh, cache) = semi (; orientations, limiting_factor) = cache.mortars - (; surface_flux_values, surface_flux_values_high_order) = cache.elements + (; surface_flux_values) = cache.elements + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes (; boundary_interpolation) = dg.basis for mortar in eachmortar(dg, cache) diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index 3c6afd158af..3cd2f8a0559 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -10,12 +10,10 @@ mutable struct ElementContainer2D{RealT <: Real, uEltype <: Real} <: AbstractCon inverse_jacobian::Vector{RealT} # [elements] node_coordinates::Array{RealT, 4} # [orientation, i, j, elements] surface_flux_values::Array{uEltype, 4} # [variables, i, direction, elements] - surface_flux_values_high_order::Array{uEltype, 4} # [variables, i, direction, elements] cell_ids::Vector{Int} # [elements] # internal `resize!`able storage _node_coordinates::Vector{RealT} _surface_flux_values::Vector{uEltype} - _surface_flux_values_high_order::Vector{uEltype} end nvariables(elements::ElementContainer2D) = size(elements.surface_flux_values, 1) @@ -30,7 +28,7 @@ Base.eltype(elements::ElementContainer2D) = eltype(elements.surface_flux_values) function Base.resize!(elements::ElementContainer2D, capacity) n_nodes = nnodes(elements) n_variables = nvariables(elements) - @unpack _node_coordinates, _surface_flux_values, _surface_flux_values_high_order, + @unpack _node_coordinates, _surface_flux_values, inverse_jacobian, cell_ids = elements resize!(inverse_jacobian, capacity) @@ -43,12 +41,6 @@ function Base.resize!(elements::ElementContainer2D, capacity) elements.surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), (n_variables, n_nodes, 2 * 2, capacity)) - resize!(_surface_flux_values_high_order, n_variables * n_nodes * 2 * 2 * capacity) - elements.surface_flux_values_high_order = unsafe_wrap(Array, - pointer(_surface_flux_values_high_order), - (n_variables, n_nodes, 2 * 2, - capacity)) - resize!(cell_ids, capacity) return nothing @@ -71,21 +63,12 @@ function ElementContainer2D{RealT, uEltype}(capacity::Integer, n_variables, surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), (n_variables, n_nodes, 2 * 2, capacity)) - _surface_flux_values_high_order = fill(nan_uEltype, - n_variables * n_nodes * 2 * 2 * capacity) - surface_flux_values_high_order = unsafe_wrap(Array, - pointer(_surface_flux_values_high_order), - (n_variables, n_nodes, 2 * 2, - capacity)) - cell_ids = fill(typemin(Int), capacity) return ElementContainer2D{RealT, uEltype}(inverse_jacobian, node_coordinates, surface_flux_values, - surface_flux_values_high_order, cell_ids, - _node_coordinates, _surface_flux_values, - _surface_flux_values_high_order) + _node_coordinates, _surface_flux_values) end # Return number of elements @@ -1442,11 +1425,13 @@ mutable struct ContainerAntidiffusiveFlux2D{uEltype <: Real} antidiffusive_flux1_R::Array{uEltype, 4} # [variables, i, j, elements] antidiffusive_flux2_L::Array{uEltype, 4} # [variables, i, j, elements] antidiffusive_flux2_R::Array{uEltype, 4} # [variables, i, j, elements] + surface_flux_values_high_order::Array{uEltype, 4} # [variables, i, direction, elements] # internal `resize!`able storage _antidiffusive_flux1_L::Vector{uEltype} _antidiffusive_flux1_R::Vector{uEltype} _antidiffusive_flux2_L::Vector{uEltype} _antidiffusive_flux2_R::Vector{uEltype} + _surface_flux_values_high_order::Vector{uEltype} end function ContainerAntidiffusiveFlux2D{uEltype}(capacity::Integer, n_variables, @@ -1472,14 +1457,23 @@ function ContainerAntidiffusiveFlux2D{uEltype}(capacity::Integer, n_variables, antidiffusive_flux2_R = unsafe_wrap(Array, pointer(_antidiffusive_flux2_R), (n_variables, n_nodes, n_nodes + 1, capacity)) + _surface_flux_values_high_order = fill(nan_uEltype, + n_variables * n_nodes * 2 * 2 * capacity) + surface_flux_values_high_order = unsafe_wrap(Array, + pointer(_surface_flux_values_high_order), + (n_variables, n_nodes, 2 * 2, + capacity)) + return ContainerAntidiffusiveFlux2D{uEltype}(antidiffusive_flux1_L, antidiffusive_flux1_R, antidiffusive_flux2_L, antidiffusive_flux2_R, + surface_flux_values_high_order, _antidiffusive_flux1_L, _antidiffusive_flux1_R, _antidiffusive_flux2_L, - _antidiffusive_flux2_R) + _antidiffusive_flux2_R, + _surface_flux_values_high_order) end nvariables(fluxes::ContainerAntidiffusiveFlux2D) = size(fluxes.antidiffusive_flux1_L, 1) @@ -1494,7 +1488,7 @@ function Base.resize!(fluxes::ContainerAntidiffusiveFlux2D, capacity) n_nodes = nnodes(fluxes) n_variables = nvariables(fluxes) - @unpack _antidiffusive_flux1_L, _antidiffusive_flux2_L, _antidiffusive_flux1_R, _antidiffusive_flux2_R = fluxes + @unpack _antidiffusive_flux1_L, _antidiffusive_flux2_L, _antidiffusive_flux1_R, _antidiffusive_flux2_R, _surface_flux_values_high_order = fluxes resize!(_antidiffusive_flux1_L, n_variables * (n_nodes + 1) * n_nodes * capacity) fluxes.antidiffusive_flux1_L = unsafe_wrap(Array, pointer(_antidiffusive_flux1_L), @@ -1513,6 +1507,12 @@ function Base.resize!(fluxes::ContainerAntidiffusiveFlux2D, capacity) (n_variables, n_nodes, n_nodes + 1, capacity)) + resize!(_surface_flux_values_high_order, n_variables * n_nodes * 2 * 2 * capacity) + fluxes.surface_flux_values_high_order = unsafe_wrap(Array, + pointer(_surface_flux_values_high_order), + (n_variables, n_nodes, 2 * 2, + capacity)) + return nothing end diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 671d1c8cf56..b63ec842afb 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -975,7 +975,7 @@ function calc_mortar_flux!(surface_flux_values, mesh, cache) # high order fluxes - (; surface_flux_values_high_order) = cache.elements + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes @trixi_timeit timer() "calc_mortar_flux!" calc_mortar_flux!(surface_flux_values_high_order, mesh, nonconservative_terms, From 60e6567f58355ce2143b8be9a5ebdae40ba2cee4 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 27 Aug 2025 12:39:46 +0200 Subject: [PATCH 065/102] Move code and rename functions --- .../subcell_limiter_idp_correction.jl | 16 +- .../subcell_limiter_idp_correction_2d.jl | 331 +---------------- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 335 ++++++++++++++++++ 3 files changed, 346 insertions(+), 336 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction.jl b/src/callbacks_stage/subcell_limiter_idp_correction.jl index e6d06c84bc2..a36ffa01e7f 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction.jl @@ -57,12 +57,16 @@ function (limiter!::SubcellLimiterIDPCorrection)(u_ode, semi, t, dt, if solver.mortar isa Trixi.LobattoLegendreMortarIDP && !(solver.mortar.pure_low_order) - @trixi_timeit timer() "calc_limiting_factor!" calc_limiting_factor!(u, semi, - t, dt) - - @trixi_timeit timer() "blend_mortar_flux!" blend_mortar_flux!(u, semi, - equations, solver, - t, dt) + @trixi_timeit timer() "mortar blending factors" calc_mortar_limiting_factor!(u, + semi, + t, + dt) + + @trixi_timeit timer() "mortar correction" perform_idp_mortar_correction(u, dt, + mesh, + equations, + solver, + cache) end return nothing diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index a6c8639b799..0e168608c4f 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -46,336 +46,7 @@ function perform_idp_correction!(u, dt, return nothing end -# TODO: Move next three functions somewhere else, e.g., to `subcell_limiters.jl` -@inline function calc_limiting_factor!(u, semi, t, dt) - (; positivity_variables_cons, positivity_variables_nonlinear) = semi.solver.mortar - (; limiting_factor) = semi.cache.mortars - limiting_factor .= zeros(eltype(limiting_factor)) - - for var_index in positivity_variables_cons - limiting_positivity_conservative!(limiting_factor, u, dt, semi, var_index) - end - - for variable in positivity_variables_nonlinear - limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) - end - - # Provisional analysis of limiting factor - (; output_directory) = semi.solver.mortar - if length(limiting_factor) > 0 - open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f - print(f, t) - print(f, ", ", minimum(limiting_factor), ", ", maximum(limiting_factor), - ", ", sum(limiting_factor) / length(limiting_factor)) - println(f) - end - else - open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f - print(f, t) - print(f, ", ", 0.0, ", ", 0.0, ", ", 0.0) - println(f) - end - end - - return nothing -end - -@inline function limiting_positivity_conservative!(limiting_factor, u, dt, semi, - var_index) - mesh, equations, dg, cache = mesh_equations_solver_cache(semi) - - (; orientations) = cache.mortars - (; surface_flux_values) = cache.elements - (; surface_flux_values_high_order) = cache.antidiffusive_fluxes - (; boundary_interpolation) = dg.basis - - (; positivity_correction_factor) = dg.volume_integral.limiter - - for mortar in eachmortar(dg, cache) - large_element = cache.mortars.neighbor_ids[3, mortar] - upper_element = cache.mortars.neighbor_ids[2, mortar] - lower_element = cache.mortars.neighbor_ids[1, mortar] - - # Calc minimal low-order solution - var_min_upper = typemax(eltype(surface_flux_values)) - var_min_lower = typemax(eltype(surface_flux_values)) - var_min_large = typemax(eltype(surface_flux_values)) - for i in eachnode(dg) - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (1, i) - indices_large = (nnodes(dg), i) - else - # L2 mortars in y-direction - indices_small = (i, 1) - indices_large = (i, nnodes(dg)) - end - else # large_sides[mortar] == 2 -> small elements on left side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (nnodes(dg), i) - indices_large = (1, i) - else - # L2 mortars in y-direction - indices_small = (i, nnodes(dg)) - indices_large = (i, 1) - end - end - var_upper = u[var_index, indices_small..., upper_element] - var_lower = u[var_index, indices_small..., lower_element] - var_large = u[var_index, indices_large..., large_element] - var_min_upper = min(var_min_upper, var_upper) - var_min_lower = min(var_min_lower, var_lower) - var_min_large = min(var_min_large, var_large) - end - var_min_upper = positivity_correction_factor * var_min_upper - var_min_lower = positivity_correction_factor * var_min_lower - var_min_large = positivity_correction_factor * var_min_large - - for i in eachnode(dg) - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (1, i) - indices_large = (nnodes(dg), i) - direction_small = 1 - direction_large = 2 - else - # L2 mortars in y-direction - indices_small = (i, 1) - indices_large = (i, nnodes(dg)) - direction_small = 3 - direction_large = 4 - end - factor_small = boundary_interpolation[1, 1] - factor_large = -boundary_interpolation[nnodes(dg), 2] - else # large_sides[mortar] == 2 -> small elements on left side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (nnodes(dg), i) - indices_large = (1, i) - direction_small = 2 - direction_large = 1 - else - # L2 mortars in y-direction - indices_small = (i, nnodes(dg)) - indices_large = (i, 1) - direction_small = 4 - direction_large = 3 - end - factor_large = boundary_interpolation[1, 1] - factor_small = -boundary_interpolation[nnodes(dg), 2] - end - # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. - # This sign switch is directly applied to the boundary interpolation factors here. - - var_upper = u[var_index, indices_small..., upper_element] - var_lower = u[var_index, indices_small..., lower_element] - var_large = u[var_index, indices_large..., large_element] - - if min(var_upper, var_lower, var_large) < 0 - error("Safe low-order method produces negative value for conservative variable rho. Try a smaller time step.") - end - - inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., - upper_element) - inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., - lower_element) - inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_large..., - large_element) - - # Calculate Pm - flux_lower_high_order = surface_flux_values_high_order[var_index, i, - direction_small, - lower_element] - flux_lower_low_order = surface_flux_values[var_index, i, direction_small, - lower_element] - flux_difference_lower = factor_small * - (flux_lower_high_order - flux_lower_low_order) - - flux_upper_high_order = surface_flux_values_high_order[var_index, i, - direction_small, - upper_element] - flux_upper_low_order = surface_flux_values[var_index, i, direction_small, - upper_element] - flux_difference_upper = factor_small * - (flux_upper_high_order - flux_upper_low_order) - - flux_large_high_order = surface_flux_values_high_order[var_index, i, - direction_large, - large_element] - flux_large_low_order = surface_flux_values_high_order[var_index, i, - direction_large, - large_element] - flux_difference_large = factor_large * - (flux_large_high_order - flux_large_low_order) - - # Check if high-order fluxes are finite. Otherwise, use pure low-order fluxes. - if !all(isfinite.(flux_lower_high_order)) || - !all(isfinite(flux_upper_high_order)) || - !all(isfinite.(flux_large_high_order)) - limiting_factor[mortar] = 1 - continue - end - - Qm_upper = min(0, var_min_upper - var_upper) - Qm_lower = min(0, var_min_lower - var_lower) - Qm_large = min(0, var_min_large - var_large) - - Pm_upper = min(0, flux_difference_upper) - Pm_lower = min(0, flux_difference_lower) - Pm_large = min(0, flux_difference_large) - - Pm_upper = dt * inverse_jacobian_upper * Pm_upper - Pm_lower = dt * inverse_jacobian_lower * Pm_lower - Pm_large = dt * inverse_jacobian_large * Pm_large - - # Compute blending coefficient avoiding division by zero - # (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8)) - Qm_upper = abs(Qm_upper) / (abs(Pm_upper) + eps(typeof(Qm_upper)) * 100) - Qm_lower = abs(Qm_lower) / (abs(Pm_lower) + eps(typeof(Qm_lower)) * 100) - Qm_large = abs(Qm_large) / (abs(Pm_large) + eps(typeof(Qm_large)) * 100) - - limiting_factor[mortar] = max(limiting_factor[mortar], 1 - Qm_upper, - 1 - Qm_lower, 1 - Qm_large) - end - end - - return nothing -end - -@inline function limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) - mesh, equations, dg, cache = mesh_equations_solver_cache(semi) - - (; orientations) = cache.mortars - (; surface_flux_values) = cache.elements - (; surface_flux_values_high_order) = cache.antidiffusive_fluxes - (; boundary_interpolation) = dg.basis - - (; limiter) = dg.volume_integral - (; positivity_correction_factor) = limiter - - for mortar in eachmortar(dg, cache) - large_element = cache.mortars.neighbor_ids[3, mortar] - upper_element = cache.mortars.neighbor_ids[2, mortar] - lower_element = cache.mortars.neighbor_ids[1, mortar] - - for i in eachnode(dg) - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (1, i) - indices_large = (nnodes(dg), i) - direction_small = 1 - direction_large = 2 - else - # L2 mortars in y-direction - indices_small = (i, 1) - indices_large = (i, nnodes(dg)) - direction_small = 3 - direction_large = 4 - end - factor_small = boundary_interpolation[1, 1] - factor_large = -boundary_interpolation[nnodes(dg), 2] - else # large_sides[mortar] == 2 -> small elements on left side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (nnodes(dg), i) - indices_large = (1, i) - direction_small = 2 - direction_large = 1 - else - # L2 mortars in y-direction - indices_small = (i, nnodes(dg)) - indices_large = (i, 1) - direction_small = 4 - direction_large = 3 - end - factor_large = boundary_interpolation[1, 1] - factor_small = -boundary_interpolation[nnodes(dg), 2] - end - # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. - # This sign switch is directly applied to the boundary interpolation factors here. - inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., - upper_element) - inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., - lower_element) - inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_large..., - large_element) - - u_lower = get_node_vars(u, equations, dg, indices_small..., lower_element) - var_lower = variable(u_lower, equations) - u_upper = get_node_vars(u, equations, dg, indices_small..., upper_element) - var_upper = variable(u_upper, equations) - u_large = get_node_vars(u, equations, dg, indices_large..., large_element) - var_large = variable(u_large, equations) - if var_lower < 0 || var_upper < 0 || var_large < 0 - error("Safe low-order method produces negative value for variable $variable. Try a smaller time step.") - end - - var_min_lower = positivity_correction_factor * var_lower - var_min_upper = positivity_correction_factor * var_upper - var_min_large = positivity_correction_factor * var_large - - # lower element - flux_lower_high_order = get_node_vars(surface_flux_values_high_order, - equations, dg, i, direction_small, - lower_element) - flux_lower_low_order = get_node_vars(surface_flux_values, equations, dg, i, - direction_small, lower_element) - flux_difference_lower = factor_small * - (flux_lower_high_order .- flux_lower_low_order) - antidiffusive_flux_lower = inverse_jacobian_lower * flux_difference_lower - - newton_loop!(limiting_factor, var_min_lower, u_lower, (mortar,), variable, - min, initial_check_nonnegative_newton_idp, - final_check_nonnegative_newton_idp, - equations, dt, limiter, antidiffusive_flux_lower) - - # upper element - flux_upper_high_order = get_node_vars(surface_flux_values_high_order, - equations, dg, i, direction_small, - upper_element) - flux_upper_low_order = get_node_vars(surface_flux_values, equations, dg, i, - direction_small, upper_element) - flux_difference_upper = factor_small * - (flux_upper_high_order .- flux_upper_low_order) - antidiffusive_flux_upper = inverse_jacobian_upper * flux_difference_upper - - newton_loop!(limiting_factor, var_min_upper, u_upper, (mortar,), variable, - min, initial_check_nonnegative_newton_idp, - final_check_nonnegative_newton_idp, - equations, dt, limiter, antidiffusive_flux_upper) - - # large element - flux_large_high_order = get_node_vars(surface_flux_values_high_order, - equations, dg, i, direction_large, - large_element) - flux_large_low_order = get_node_vars(surface_flux_values, equations, dg, i, - direction_large, large_element) - flux_difference_large = factor_large * - (flux_large_high_order .- flux_large_low_order) - antidiffusive_flux_large = inverse_jacobian_large * flux_difference_large - - newton_loop!(limiting_factor, var_min_large, u_large, (mortar,), variable, - min, initial_check_nonnegative_newton_idp, - final_check_nonnegative_newton_idp, - equations, dt, limiter, antidiffusive_flux_large) - end - end - - return nothing -end - -@inline function blend_mortar_flux!(u, semi, equations, dg, t, dt) - (; mesh, cache) = semi +function perform_idp_mortar_correction(u, dt, mesh, equations, dg, cache) (; orientations, limiting_factor) = cache.mortars (; surface_flux_values) = cache.elements diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index aedf12dd2cf..bf10883102d 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -619,6 +619,341 @@ end return nothing end +############################################################################### +# IDP mortar limiting +############################################################################### + +@inline function calc_mortar_limiting_factor!(u, semi, t, dt) + (; positivity_variables_cons, positivity_variables_nonlinear) = semi.solver.mortar + (; limiting_factor) = semi.cache.mortars + limiting_factor .= zeros(eltype(limiting_factor)) + + for var_index in positivity_variables_cons + limiting_positivity_conservative!(limiting_factor, u, dt, semi, var_index) + end + + for variable in positivity_variables_nonlinear + limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) + end + + # Provisional analysis of limiting factor (TODO) + (; output_directory) = semi.solver.mortar + if length(limiting_factor) > 0 + open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f + print(f, t) + print(f, ", ", minimum(limiting_factor), ", ", maximum(limiting_factor), + ", ", sum(limiting_factor) / length(limiting_factor)) + println(f) + end + else + open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f + print(f, t) + print(f, ", ", 0.0, ", ", 0.0, ", ", 0.0) + println(f) + end + end + + return nothing +end + +############################################################################### +# Local two-sided limiting of conservative variables +@inline function limiting_positivity_conservative!(limiting_factor, u, dt, semi, + var_index) + mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + + (; orientations) = cache.mortars + (; surface_flux_values) = cache.elements + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes + (; boundary_interpolation) = dg.basis + + (; positivity_correction_factor) = dg.volume_integral.limiter + + for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Calc minimal low-order solution + var_min_upper = typemax(eltype(surface_flux_values)) + var_min_lower = typemax(eltype(surface_flux_values)) + var_min_large = typemax(eltype(surface_flux_values)) + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + end + end + var_upper = u[var_index, indices_small..., upper_element] + var_lower = u[var_index, indices_small..., lower_element] + var_large = u[var_index, indices_large..., large_element] + var_min_upper = min(var_min_upper, var_upper) + var_min_lower = min(var_min_lower, var_lower) + var_min_large = min(var_min_large, var_large) + end + var_min_upper = positivity_correction_factor * var_min_upper + var_min_lower = positivity_correction_factor * var_min_lower + var_min_large = positivity_correction_factor * var_min_large + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + direction_small = 3 + direction_large = 4 + end + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + direction_small = 4 + direction_large = 3 + end + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + + var_upper = u[var_index, indices_small..., upper_element] + var_lower = u[var_index, indices_small..., lower_element] + var_large = u[var_index, indices_large..., large_element] + + if min(var_upper, var_lower, var_large) < 0 + error("Safe low-order method produces negative value for conservative variable rho. Try a smaller time step.") + end + + inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + upper_element) + inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + lower_element) + inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_large..., + large_element) + + # Calculate Pm + flux_lower_high_order = surface_flux_values_high_order[var_index, i, + direction_small, + lower_element] + flux_lower_low_order = surface_flux_values[var_index, i, direction_small, + lower_element] + flux_difference_lower = factor_small * + (flux_lower_high_order - flux_lower_low_order) + + flux_upper_high_order = surface_flux_values_high_order[var_index, i, + direction_small, + upper_element] + flux_upper_low_order = surface_flux_values[var_index, i, direction_small, + upper_element] + flux_difference_upper = factor_small * + (flux_upper_high_order - flux_upper_low_order) + + flux_large_high_order = surface_flux_values_high_order[var_index, i, + direction_large, + large_element] + flux_large_low_order = surface_flux_values_high_order[var_index, i, + direction_large, + large_element] + flux_difference_large = factor_large * + (flux_large_high_order - flux_large_low_order) + + # Check if high-order fluxes are finite. Otherwise, use pure low-order fluxes. + if !all(isfinite.(flux_lower_high_order)) || + !all(isfinite(flux_upper_high_order)) || + !all(isfinite.(flux_large_high_order)) + limiting_factor[mortar] = 1 + continue + end + + Qm_upper = min(0, var_min_upper - var_upper) + Qm_lower = min(0, var_min_lower - var_lower) + Qm_large = min(0, var_min_large - var_large) + + Pm_upper = min(0, flux_difference_upper) + Pm_lower = min(0, flux_difference_lower) + Pm_large = min(0, flux_difference_large) + + Pm_upper = dt * inverse_jacobian_upper * Pm_upper + Pm_lower = dt * inverse_jacobian_lower * Pm_lower + Pm_large = dt * inverse_jacobian_large * Pm_large + + # Compute blending coefficient avoiding division by zero + # (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8)) + Qm_upper = abs(Qm_upper) / (abs(Pm_upper) + eps(typeof(Qm_upper)) * 100) + Qm_lower = abs(Qm_lower) / (abs(Pm_lower) + eps(typeof(Qm_lower)) * 100) + Qm_large = abs(Qm_large) / (abs(Pm_large) + eps(typeof(Qm_large)) * 100) + + limiting_factor[mortar] = max(limiting_factor[mortar], 1 - Qm_upper, + 1 - Qm_lower, 1 - Qm_large) + end + end + + return nothing +end + +############################################################################## +# Local one-sided limiting of nonlinear variables +@inline function limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) + mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + + (; orientations) = cache.mortars + (; surface_flux_values) = cache.elements + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes + (; boundary_interpolation) = dg.basis + + (; limiter) = dg.volume_integral + (; positivity_correction_factor) = limiter + + for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + direction_small = 3 + direction_large = 4 + end + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + direction_small = 4 + direction_large = 3 + end + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + upper_element) + inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + lower_element) + inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_large..., + large_element) + + u_lower = get_node_vars(u, equations, dg, indices_small..., lower_element) + var_lower = variable(u_lower, equations) + u_upper = get_node_vars(u, equations, dg, indices_small..., upper_element) + var_upper = variable(u_upper, equations) + u_large = get_node_vars(u, equations, dg, indices_large..., large_element) + var_large = variable(u_large, equations) + if var_lower < 0 || var_upper < 0 || var_large < 0 + error("Safe low-order method produces negative value for variable $variable. Try a smaller time step.") + end + + var_min_lower = positivity_correction_factor * var_lower + var_min_upper = positivity_correction_factor * var_upper + var_min_large = positivity_correction_factor * var_large + + # lower element + flux_lower_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_small, + lower_element) + flux_lower_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_small, lower_element) + flux_difference_lower = factor_small * + (flux_lower_high_order .- flux_lower_low_order) + antidiffusive_flux_lower = inverse_jacobian_lower * flux_difference_lower + + newton_loop!(limiting_factor, var_min_lower, u_lower, (mortar,), variable, + min, initial_check_nonnegative_newton_idp, + final_check_nonnegative_newton_idp, + equations, dt, limiter, antidiffusive_flux_lower) + + # upper element + flux_upper_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_small, + upper_element) + flux_upper_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_small, upper_element) + flux_difference_upper = factor_small * + (flux_upper_high_order .- flux_upper_low_order) + antidiffusive_flux_upper = inverse_jacobian_upper * flux_difference_upper + + newton_loop!(limiting_factor, var_min_upper, u_upper, (mortar,), variable, + min, initial_check_nonnegative_newton_idp, + final_check_nonnegative_newton_idp, + equations, dt, limiter, antidiffusive_flux_upper) + + # large element + flux_large_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_large, + large_element) + flux_large_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_large, large_element) + flux_difference_large = factor_large * + (flux_large_high_order .- flux_large_low_order) + antidiffusive_flux_large = inverse_jacobian_large * flux_difference_large + + newton_loop!(limiting_factor, var_min_large, u_large, (mortar,), variable, + min, initial_check_nonnegative_newton_idp, + final_check_nonnegative_newton_idp, + equations, dt, limiter, antidiffusive_flux_large) + end + end + + return nothing +end + ############################################################################### # Newton-bisection method From 454d19d0b57d63f563160f03886c9e6de2054648 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 27 Aug 2025 12:44:53 +0200 Subject: [PATCH 066/102] fmt --- src/solvers/dgsem_tree/containers_2d.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index 3cd2f8a0559..8a14643d400 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -66,8 +66,7 @@ function ElementContainer2D{RealT, uEltype}(capacity::Integer, n_variables, cell_ids = fill(typemin(Int), capacity) return ElementContainer2D{RealT, uEltype}(inverse_jacobian, node_coordinates, - surface_flux_values, - cell_ids, + surface_flux_values, cell_ids, _node_coordinates, _surface_flux_values) end From c19b913ca8fdf3e77d829fc61236744a8c8cd3ac Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 27 Aug 2025 16:26:28 +0200 Subject: [PATCH 067/102] Add conservation tests --- test/test_tree_2d_euler.jl | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 53246c29099..14244e26f83 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -682,6 +682,64 @@ end end end +@trixi_testset "elixir_euler_blast_wave_amr_sc_subcell.jl (conservation)" begin + @trixi_testset "piecewise constant" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_blast_wave_amr_sc_subcell.jl"), + basis_function=:piecewise_constant, pure_low_order=true, + tspan=(0.0, 1.0)) + state_integrals = Trixi.integrate(sol.u[2], semi) + initial_state_integrals = analysis_callback.affect!.initial_state_integrals + + @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1e-13) + @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1e-13) + @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) + @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) + end + + # Note: Not conservative + @trixi_testset "piecewise linear" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_blast_wave_amr_sc_subcell.jl"), + basis_function=:piecewise_linear, pure_low_order=true, + tspan=(0.0, 1.0)) + state_integrals = Trixi.integrate(sol.u[2], semi) + initial_state_integrals = analysis_callback.affect!.initial_state_integrals + + @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1e-13) + @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1e-13) + @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) + @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) + end + + @trixi_testset "global factor" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_blast_wave_amr_sc_subcell.jl"), + local_factor=false, pure_low_order=true, tspan=(0.0, 1.0)) + state_integrals = Trixi.integrate(sol.u[2], semi) + initial_state_integrals = analysis_callback.affect!.initial_state_integrals + + @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1e-13) + @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1e-13) + @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) + @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) + end + + # Note: Not conservative + @trixi_testset "alternative implementation" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_blast_wave_amr_sc_subcell.jl"), + alternative=true, pure_low_order=true, tspan=(0.0, 1.0)) + state_integrals = Trixi.integrate(sol.u[2], semi) + initial_state_integrals = analysis_callback.affect!.initial_state_integrals + + @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1e-13) + @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1e-13) + @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) + @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) + end +end + @trixi_testset "elixir_euler_sedov_blast_wave.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), l2=[ From f99c4357fa112f44ab6b87fbdda29935dae1f5e4 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 1 Sep 2025 15:05:33 +0200 Subject: [PATCH 068/102] Initial merge (clean version) --- ...in_helmholtz_instability_amr_sc_subcell.jl | 106 +++++++ src/Trixi.jl | 2 +- .../subcell_limiter_idp_correction.jl | 13 + .../subcell_limiter_idp_correction_2d.jl | 109 +++++++ src/solvers/dgsem/basis_lobatto_legendre.jl | 78 ++++- src/solvers/dgsem_tree/containers_2d.jl | 153 ++++++++- src/solvers/dgsem_tree/dg_2d.jl | 3 +- .../dgsem_tree/dg_2d_subcell_limiters.jl | 283 +++++++++++++++++ src/solvers/dgsem_tree/subcell_limiters_2d.jl | 295 +++++++++++++++++- test/test_tree_2d_euler.jl | 30 ++ 10 files changed, 1049 insertions(+), 23 deletions(-) create mode 100644 examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl new file mode 100644 index 00000000000..d43f25f8ea5 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl @@ -0,0 +1,106 @@ +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations +gamma = 1.4 +equations = CompressibleEulerEquations2D(gamma) + +""" + initial_condition_kelvin_helmholtz_instability(x, t, equations::CompressibleEulerEquations2D) + +A version of the classical Kelvin-Helmholtz instability based on +- Andrés M. Rueda-Ramírez, Gregor J. Gassner (2021) + A Subcell Finite Volume Positivity-Preserving Limiter for DGSEM Discretizations + of the Euler Equations + [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) +""" +function initial_condition_kelvin_helmholtz_instability(x, t, + equations::CompressibleEulerEquations2D) + # change discontinuity to tanh + # typical resolution 128^2, 256^2 + # domain size is [-1,+1]^2 + RealT = eltype(x) + slope = 15 + B = tanh(slope * x[2] + 7.5f0) - tanh(slope * x[2] - 7.5f0) + rho = 0.5f0 + 0.75f0 * B + v1 = 0.5f0 * (B - 1) + v2 = convert(RealT, 0.1) * sinpi(2 * x[1]) + p = 1 + return prim2cons(SVector(rho, v1, v2, p), equations) +end +initial_condition = initial_condition_kelvin_helmholtz_instability + +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +limiter_idp = SubcellLimiterIDP(equations, basis; + positivity_variables_cons = ["rho"], + positivity_variables_nonlinear = [pressure]) +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +mortar = MortarIDP(basis; positivity_variables_cons = [1]) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 100_000) +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 3.7) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) + +save_restart = SaveRestartCallback(interval = 1000, + save_final_restart = true) + +amr_indicator = IndicatorHennemannGassner(semi, + alpha_max = 1.0, + alpha_min = 0.0001, + alpha_smooth = false, + variable = Trixi.density) +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = 4, + med_level = 0, med_threshold = 0.0003, + max_level = 6, max_threshold = 0.003) +amr_callback = AMRCallback(semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true) + +stepsize_callback = StepsizeCallback(cfl = 0.3) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, save_restart, + amr_callback, + stepsize_callback) + +############################################################################### +# run the simulation + +stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) + +sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + ode_default_options()..., + callback = callbacks); diff --git a/src/Trixi.jl b/src/Trixi.jl index 9412c33db6f..5102aa9b839 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -263,7 +263,7 @@ export DG, VolumeIntegralUpwind, SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, SurfaceIntegralUpwind, - MortarL2 + MortarL2, MortarIDP export VolumeIntegralSubcellLimiting, BoundsCheckCallback, SubcellLimiterIDP, SubcellLimiterIDPCorrection diff --git a/src/callbacks_stage/subcell_limiter_idp_correction.jl b/src/callbacks_stage/subcell_limiter_idp_correction.jl index e1cb42035d1..ebe5f6747dd 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction.jl @@ -56,6 +56,19 @@ function (limiter!::SubcellLimiterIDPCorrection)(u_ode, semi, t, dt, perform_idp_correction!(u, dt, mesh, equations, solver, cache) + if solver.mortar isa Trixi.LobattoLegendreMortarIDP + @trixi_timeit timer() "mortar blending factors" calc_mortar_limiting_factor!(u, + semi, + t, + dt) + + @trixi_timeit timer() "mortar correction" perform_idp_mortar_correction(u, dt, + mesh, + equations, + solver, + cache) + end + return nothing end diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index e3b5616d5e5..0e168608c4f 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -45,4 +45,113 @@ function perform_idp_correction!(u, dt, return nothing end + +function perform_idp_mortar_correction(u, dt, mesh, equations, dg, cache) + (; orientations, limiting_factor) = cache.mortars + + (; surface_flux_values) = cache.elements + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes + (; boundary_interpolation) = dg.basis + + for mortar in eachmortar(dg, cache) + if isapprox(limiting_factor[mortar], one(eltype(limiting_factor))) + continue + end + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + direction_small = 3 + direction_large = 4 + end + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + direction_small = 4 + direction_large = 3 + end + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + upper_element) + inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + lower_element) + inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_large..., + large_element) + + # lower element + flux_lower_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_small, + lower_element) + flux_lower_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_small, lower_element) + flux_difference_lower = factor_small * + (flux_lower_high_order .- flux_lower_low_order) + + multiply_add_to_node_vars!(u, + dt * inverse_jacobian_lower * + (1 - limiting_factor[mortar]), + flux_difference_lower, equations, dg, + indices_small..., lower_element) + + flux_upper_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_small, + upper_element) + flux_upper_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_small, upper_element) + flux_difference_upper = factor_small * + (flux_upper_high_order .- flux_upper_low_order) + + multiply_add_to_node_vars!(u, + dt * inverse_jacobian_upper * + (1 - limiting_factor[mortar]), + flux_difference_upper, equations, dg, + indices_small..., upper_element) + + flux_large_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, i, direction_large, + large_element) + flux_large_low_order = get_node_vars(surface_flux_values, equations, dg, i, + direction_large, large_element) + flux_difference_large = factor_large * + (flux_large_high_order .- flux_large_low_order) + + multiply_add_to_node_vars!(u, + dt * inverse_jacobian_large * + (1 - limiting_factor[mortar]), + flux_difference_large, equations, dg, + indices_large..., large_element) + end + end + + return nothing +end end # @muladd diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 9647f172e20..ffe13707f38 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -77,13 +77,13 @@ function LobattoLegendreBasis(RealT, polydeg::Integer) derivative_split_transpose = Matrix(derivative_split') derivative_dhat = calc_dhat(nodes_, weights_) - # Type conversions to enable possible optimizations of runtime performance + # Type conversions to enable possible optimizations of runtime performance # and latency nodes = SVector{nnodes_, RealT}(nodes_) weights = SVector{nnodes_, RealT}(weights_) inverse_weights = SVector{nnodes_, RealT}(inverse_weights_) - # We keep the matrices above stored using the standard `Matrix` type + # We keep the matrices above stored using the standard `Matrix` type # since this is usually as fast as `SMatrix` # (when using `let` in the volume integral/`@threaded`) # and reduces latency @@ -139,7 +139,7 @@ end eachnode(basis::LobattoLegendreBasis) Return an iterator over the indices that specify the location in relevant data structures -for the nodes in `basis`. +for the nodes in `basis`. In particular, not the nodes themselves are returned. """ @inline eachnode(basis::LobattoLegendreBasis) = Base.OneTo(nnodes(basis)) @@ -171,6 +171,50 @@ end left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights) right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) +struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: + AbstractMortarL2{RealT} + positivity_variables_cons::Vector{Int} + mortar_l2::Mortar + local_mortar_weights::Matrix{RealT} +end + +function MortarIDP(basis::LobattoLegendreBasis; + positivity_variables_cons = Int[]) # TODO: String[], "rho" instead of 1 + RealT = real(basis) + nnodes_ = nnodes(basis) + + mortar_l2 = MortarL2(basis) + + local_mortar_weights = calc_mortar_weights(basis, RealT) + + LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(positivity_variables_cons, + mortar_l2, + local_mortar_weights) +end + +function Base.show(io::IO, mortar::LobattoLegendreMortarIDP) + @nospecialize mortar # reduce precompilation time + # TODO + print(io, "LobattoLegendreMortarIDP{", real(mortar), "}(polydeg=", polydeg(mortar), + ")") +end +function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarIDP) + @nospecialize mortar # reduce precompilation time + # TODO + print(io, "LobattoLegendreMortarIDP{", real(mortar), + "} with polynomials of degree ", + polydeg(mortar)) +end + +@inline Base.real(mortar::LobattoLegendreMortarIDP{RealT}) where {RealT} = RealT + +@inline function nnodes(mortar::LobattoLegendreMortarIDP{RealT, NNODES}) where {RealT, + NNODES} + NNODES +end + +@inline polydeg(mortar::LobattoLegendreMortarIDP) = nnodes(mortar) - 1 + struct LobattoLegendreMortarL2{RealT <: Real, NNODES, ForwardMatrix <: AbstractMatrix{RealT}, ReverseMatrix <: AbstractMatrix{RealT}} <: @@ -201,7 +245,7 @@ function MortarL2(basis::LobattoLegendreBasis) reverse_upper = calc_reverse_upper(nnodes_, Val(:gauss), RealT) reverse_lower = calc_reverse_lower(nnodes_, Val(:gauss), RealT) - # We keep the matrices above stored using the standard `Matrix` type + # We keep the matrices above stored using the standard `Matrix` type # since this is usually as fast as `SMatrix` # (when using `let` in the volume integral/`@threaded`) # and reduces latency @@ -292,7 +336,7 @@ function SolutionAnalyzer(basis::LobattoLegendreBasis; nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_, RealT) vandermonde = polynomial_interpolation_matrix(get_nodes(basis), nodes_) - # Type conversions to enable possible optimizations of runtime performance + # Type conversions to enable possible optimizations of runtime performance # and latency nodes = SVector{nnodes_, RealT}(nodes_) weights = SVector{nnodes_, RealT}(weights_) @@ -325,7 +369,7 @@ end eachnode(analyzer::LobattoLegendreAnalyzer) Return an iterator over the indices that specify the location in relevant data structures -for the nodes in `analyzer`. +for the nodes in `analyzer`. In particular, not the nodes themselves are returned. """ @inline eachnode(analyzer::LobattoLegendreAnalyzer) = Base.OneTo(nnodes(analyzer)) @@ -531,19 +575,19 @@ function calc_lhat(x, nodes, weights) return lhat end -""" +""" lagrange_interpolating_polynomials(x, nodes, wbary) Calculate Lagrange polynomials for a given node distribution with -associated barycentric weights `wbary` at a given point `x` on the +associated barycentric weights `wbary` at a given point `x` on the reference interval ``[-1, 1]``. This returns all ``l_j(x)``, i.e., the Lagrange polynomials for each node ``x_j``. -Thus, to obtain the interpolating polynomial ``p(x)`` at ``x``, one has to +Thus, to obtain the interpolating polynomial ``p(x)`` at ``x``, one has to multiply the Lagrange polynomials with the nodal values ``u_j`` and sum them up: ``p(x) = \\sum_{j=1}^{n} u_j l_j(x)``. -For details, see e.g. Section 2 of +For details, see e.g. Section 2 of - Jean-Paul Berrut and Lloyd N. Trefethen (2004). Barycentric Lagrange Interpolation. [DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715) @@ -553,7 +597,7 @@ function lagrange_interpolating_polynomials(x, nodes, wbary) polynomials = zeros(eltype(nodes), n_nodes) for i in 1:n_nodes - # Avoid division by zero when `x` is close to node by using + # Avoid division by zero when `x` is close to node by using # the Kronecker-delta property at nodes # of the Lagrange interpolation polynomials. if isapprox(x, nodes[i], rtol = eps(x)) @@ -580,9 +624,9 @@ end Computes nodes ``x_j`` and weights ``w_j`` for the (Legendre-)Gauss-Lobatto quadrature. This implements algorithm 25 "GaussLobattoNodesAndWeights" from the book -- David A. Kopriva, (2009). +- David A. Kopriva, (2009). Implementing spectral methods for partial differential equations: - Algorithms for scientists and engineers. + Algorithms for scientists and engineers. [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) """ function gauss_lobatto_nodes_weights(n_nodes::Integer, RealT = Float64) @@ -686,9 +730,9 @@ end Computes nodes ``x_j`` and weights ``w_j`` for the Gauss-Legendre quadrature. This implements algorithm 23 "LegendreGaussNodesAndWeights" from the book -- David A. Kopriva, (2009). +- David A. Kopriva, (2009). Implementing spectral methods for partial differential equations: - Algorithms for scientists and engineers. + Algorithms for scientists and engineers. [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) """ function gauss_nodes_weights(n_nodes::Integer, RealT = Float64) @@ -756,9 +800,9 @@ end Computes the Legendre polynomial of degree `N` and its derivative at `x`. This implements algorithm 22 "LegendrePolynomialAndDerivative" from the book -- David A. Kopriva, (2009). +- David A. Kopriva, (2009). Implementing spectral methods for partial differential equations: - Algorithms for scientists and engineers. + Algorithms for scientists and engineers. [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) """ function legendre_polynomial_and_derivative(N::Int, x::Real) diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index f0d66fd2353..e49a15e9b2f 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -611,6 +611,138 @@ function init_mortars(cell_ids, mesh::TreeMesh2D, return mortars end +# Container data structure (structure-of-arrays style) for DG mortars for IDP AMR +# Positions/directions for orientations = 1, large_sides = 2: +# mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar +# | | +# upper = 2 | | +# | | +# | 3 = large side +# | | +# lower = 1 | | +# | | +mutable struct IDPMortarContainer2D{uEltype <: Real} <: AbstractContainer + u_upper::Array{uEltype, 4} # [leftright, variables, i, mortars] + u_lower::Array{uEltype, 4} # [leftright, variables, i, mortars] + u_large::Array{uEltype, 3} # [variables, i, mortars] + neighbor_ids::Array{Int, 2} # [position, mortars] + # Large sides: left -> 1, right -> 2 + large_sides::Vector{Int} # [mortars] + orientations::Vector{Int} # [mortars] + limiting_factor::Vector{uEltype} # [mortars] + # internal `resize!`able storage + _u_upper::Vector{uEltype} + _u_lower::Vector{uEltype} + _u_large::Vector{uEltype} + _neighbor_ids::Vector{Int} +end + +nvariables(mortars::IDPMortarContainer2D) = size(mortars.u_upper, 2) +nnodes(mortars::IDPMortarContainer2D) = size(mortars.u_upper, 3) +Base.eltype(mortars::IDPMortarContainer2D) = eltype(mortars.u_upper) + +# See explanation of Base.resize! for the element container +function Base.resize!(mortars::IDPMortarContainer2D, capacity) + n_nodes = nnodes(mortars) + n_variables = nvariables(mortars) + @unpack _u_upper, _u_lower, _u_large, _neighbor_ids, + large_sides, orientations, limiting_factor = mortars + + resize!(_u_upper, 2 * n_variables * n_nodes * capacity) + mortars.u_upper = unsafe_wrap(Array, pointer(_u_upper), + (2, n_variables, n_nodes, capacity)) + + resize!(_u_lower, 2 * n_variables * n_nodes * capacity) + mortars.u_lower = unsafe_wrap(Array, pointer(_u_lower), + (2, n_variables, n_nodes, capacity)) + + resize!(_u_large, n_variables * n_nodes * capacity) + mortars.u_large = unsafe_wrap(Array, pointer(_u_large), + (n_variables, n_nodes, capacity)) + + resize!(_neighbor_ids, 3 * capacity) + mortars.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), + (3, capacity)) + + resize!(large_sides, capacity) + + resize!(orientations, capacity) + + resize!(limiting_factor, capacity) + + return nothing +end + +function IDPMortarContainer2D{uEltype}(capacity::Integer, n_variables, + n_nodes) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u_upper = fill(nan, 2 * n_variables * n_nodes * capacity) + u_upper = unsafe_wrap(Array, pointer(_u_upper), + (2, n_variables, n_nodes, capacity)) + + _u_lower = fill(nan, 2 * n_variables * n_nodes * capacity) + u_lower = unsafe_wrap(Array, pointer(_u_lower), + (2, n_variables, n_nodes, capacity)) + + _u_large = fill(nan, n_variables * n_nodes * capacity) + u_large = unsafe_wrap(Array, pointer(_u_large), + (n_variables, n_nodes, capacity)) + + _neighbor_ids = fill(typemin(Int), 3 * capacity) + neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), + (3, capacity)) + + large_sides = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + limiting_factor = fill(typemin(Int), capacity) + + return IDPMortarContainer2D{uEltype}(u_upper, u_lower, u_large, neighbor_ids, + large_sides, orientations, limiting_factor, + _u_upper, _u_lower, _u_large, _neighbor_ids) +end + +# Return number of IDP mortars +@inline nmortars(l2mortars::IDPMortarContainer2D) = length(l2mortars.orientations) + +# Allow printing container contents +function Base.show(io::IO, ::MIME"text/plain", c::IDPMortarContainer2D) + @nospecialize c # reduce precompilation time + + println(io, '*'^20) + for idx in CartesianIndices(c.u_upper) + println(io, "c.u_upper[$idx] = $(c.u_upper[idx])") + end + for idx in CartesianIndices(c.u_lower) + println(io, "c.u_lower[$idx] = $(c.u_lower[idx])") + end + for idx in CartesianIndices(c.u_large) + println(io, "c.u_large[$idx] = $(c.u_large[idx])") + end + println(io, "transpose(c.neighbor_ids) = $(transpose(c.neighbor_ids))") + println(io, "c.large_sides = $(c.large_sides)") + println(io, "c.orientations = $(c.orientations)") + print(io, '*'^20) +end + +# Create mortar container and initialize mortar data in `elements`. +function init_mortars(cell_ids, mesh::TreeMesh2D, + elements::ElementContainer2D, + mortar::LobattoLegendreMortarIDP) + # Initialize containers + n_mortars = count_required_mortars(mesh, cell_ids) + mortars = IDPMortarContainer2D{eltype(elements)}(n_mortars, + nvariables(elements), + nnodes(elements)) + + # Connect elements with mortars + init_mortars!(mortars, elements, mesh) + return mortars +end + # Count the number of mortars that need to be created function count_required_mortars(mesh::TreeMesh2D, cell_ids) count = 0 @@ -1278,11 +1410,13 @@ mutable struct ContainerAntidiffusiveFlux2D{uEltype <: Real} antidiffusive_flux1_R::Array{uEltype, 4} # [variables, i, j, elements] antidiffusive_flux2_L::Array{uEltype, 4} # [variables, i, j, elements] antidiffusive_flux2_R::Array{uEltype, 4} # [variables, i, j, elements] + surface_flux_values_high_order::Array{uEltype, 4} # [variables, i, direction, elements] # internal `resize!`able storage _antidiffusive_flux1_L::Vector{uEltype} _antidiffusive_flux1_R::Vector{uEltype} _antidiffusive_flux2_L::Vector{uEltype} _antidiffusive_flux2_R::Vector{uEltype} + _surface_flux_values_high_order::Vector{uEltype} end function ContainerAntidiffusiveFlux2D{uEltype}(capacity::Integer, n_variables, @@ -1308,14 +1442,23 @@ function ContainerAntidiffusiveFlux2D{uEltype}(capacity::Integer, n_variables, antidiffusive_flux2_R = unsafe_wrap(Array, pointer(_antidiffusive_flux2_R), (n_variables, n_nodes, n_nodes + 1, capacity)) + _surface_flux_values_high_order = fill(nan_uEltype, + n_variables * n_nodes * 2 * 2 * capacity) + surface_flux_values_high_order = unsafe_wrap(Array, + pointer(_surface_flux_values_high_order), + (n_variables, n_nodes, 2 * 2, + capacity)) + return ContainerAntidiffusiveFlux2D{uEltype}(antidiffusive_flux1_L, antidiffusive_flux1_R, antidiffusive_flux2_L, antidiffusive_flux2_R, + surface_flux_values_high_order, _antidiffusive_flux1_L, _antidiffusive_flux1_R, _antidiffusive_flux2_L, - _antidiffusive_flux2_R) + _antidiffusive_flux2_R, + _surface_flux_values_high_order) end nvariables(fluxes::ContainerAntidiffusiveFlux2D) = size(fluxes.antidiffusive_flux1_L, 1) @@ -1330,7 +1473,7 @@ function Base.resize!(fluxes::ContainerAntidiffusiveFlux2D, capacity) n_nodes = nnodes(fluxes) n_variables = nvariables(fluxes) - @unpack _antidiffusive_flux1_L, _antidiffusive_flux2_L, _antidiffusive_flux1_R, _antidiffusive_flux2_R = fluxes + @unpack _antidiffusive_flux1_L, _antidiffusive_flux2_L, _antidiffusive_flux1_R, _antidiffusive_flux2_R, _surface_flux_values_high_order = fluxes resize!(_antidiffusive_flux1_L, n_variables * (n_nodes + 1) * n_nodes * capacity) fluxes.antidiffusive_flux1_L = unsafe_wrap(Array, pointer(_antidiffusive_flux1_L), @@ -1349,6 +1492,12 @@ function Base.resize!(fluxes::ContainerAntidiffusiveFlux2D, capacity) (n_variables, n_nodes, n_nodes + 1, capacity)) + resize!(_surface_flux_values_high_order, n_variables * n_nodes * 2 * 2 * capacity) + fluxes.surface_flux_values_high_order = unsafe_wrap(Array, + pointer(_surface_flux_values_high_order), + (n_variables, n_nodes, 2 * 2, + capacity)) + return nothing end diff --git a/src/solvers/dgsem_tree/dg_2d.jl b/src/solvers/dgsem_tree/dg_2d.jl index 8b30219d29b..f35f0189b90 100644 --- a/src/solvers/dgsem_tree/dg_2d.jl +++ b/src/solvers/dgsem_tree/dg_2d.jl @@ -90,7 +90,8 @@ end # The methods below are specialized on the mortar type # and called from the basic `create_cache` method at the top. function create_cache(mesh::TreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarL2, uEltype) + mortar_l2::Union{LobattoLegendreMortarL2, + LobattoLegendreMortarIDP}, uEltype) # TODO: Taal performance using different types MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)}, uEltype, 2, diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 03d0cfd33a1..b1a593d6f0e 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -59,6 +59,50 @@ function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, flux_temp_threaded, fhat_temp_threaded) end +function calc_mortar_weights(basis, RealT) + n_nodes = nnodes(basis) + # Saving the sum over row/column in entries with last index. + weights = zeros(RealT, n_nodes + 1, 2 * n_nodes + 1) + + calc_mortar_weights!(weights, n_nodes, RealT) + + for i in eachnode(basis), j in eachnode(basis) + # Row + weights[i, end] += weights[i, j] + weights[i, end] += weights[i, j + n_nodes] + # Columns + weights[end, i] += weights[j, i] + weights[end, i + n_nodes] += weights[j, i + n_nodes] + end + + return weights +end + +function calc_mortar_weights!(mortar_weights, n_nodes, RealT) + _, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) + cum_weights = [zero(RealT); cumsum(weights)] .- 1.0 + + cum_weights_lower = 0.5f0 * cum_weights .- 0.5f0 + cum_weights_upper = cum_weights_lower .+ 1.0f0 + + for i in 1:n_nodes + for j in 1:n_nodes + # basis function of left element + interval_left = max(cum_weights[i], cum_weights_lower[j]) + interval_right = min(cum_weights[i + 1], cum_weights_lower[j + 1]) + mortar_weights[i, j] = max(zero(RealT), interval_right - interval_left) + + # basis function of right element + interval_left = max(cum_weights[i], cum_weights_upper[j]) + interval_right = min(cum_weights[i + 1], cum_weights_upper[j + 1]) + mortar_weights[i, n_nodes + j] = max(zero(RealT), + interval_right - interval_left) + end + end + + return mortar_weights +end + function calc_volume_integral!(du, u, mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, @@ -735,6 +779,245 @@ end return nothing end +function prolong2mortars!(cache, u, mesh::TreeMesh{2}, equations, + mortar_idp::LobattoLegendreMortarIDP, dg::DGSEM) + prolong2mortars!(cache, u, mesh, equations, mortar_idp.mortar_l2, dg) + + # The data of both small elements were already copied to the mortar cache + @threaded for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + + # Copy solutions + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if cache.mortars.orientations[mortar] == 1 + # IDP mortars in x-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_large[v, l, mortar] = u[v, nnodes(dg), l, + large_element] + end + end + else + # IDP mortars in y-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_large[v, l, mortar] = u[v, l, nnodes(dg), + large_element] + end + end + end + else # large_sides[mortar] == 2 -> small elements on left side + if cache.mortars.orientations[mortar] == 1 + # IDP mortars in x-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_large[v, l, mortar] = u[v, 1, l, large_element] + end + end + else + # IDP mortars in y-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_large[v, l, mortar] = u[v, l, 1, large_element] + end + end + end + end + end + + return nothing +end + +function calc_mortar_flux!(surface_flux_values, mesh, + nonconservative_terms, equations, + mortar_idp::LobattoLegendreMortarIDP, surface_integral, + dg::DG, cache) + # low order fluxes + @trixi_timeit timer() "calc_mortar_flux_low_order!" calc_mortar_flux_low_order!(surface_flux_values, + mesh, + nonconservative_terms, + equations, + mortar_idp, + surface_integral, + dg, + cache) + + # high order fluxes + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes + @trixi_timeit timer() "calc_mortar_flux!" calc_mortar_flux!(surface_flux_values_high_order, + mesh, + nonconservative_terms, + equations, + mortar_idp.mortar_l2, + dg.surface_integral, dg, + cache) + + return nothing +end + +function calc_mortar_flux_low_order!(surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + mortar_idp::LobattoLegendreMortarIDP, + surface_integral, dg::DG, cache) + @unpack surface_flux = surface_integral + @unpack u_lower, u_upper, u_large, orientations = cache.mortars + @unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded, + fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache + (; local_mortar_weights) = mortar_idp + + @threaded for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Calculate fluxes + orientation = orientations[mortar] + + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientation == 1 + # L2 mortars in x-direction + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + direction_small = 3 + direction_large = 4 + end + + surface_flux_values[:, :, direction_small, lower_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_small, upper_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) + # Lower element + for i in eachnode(dg) + _, u_lower_local = get_surface_node_vars(u_lower, equations, dg, + i, mortar) # u_rr + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll + + flux = surface_flux(u_large_local, u_lower_local, orientation, + equations) + + factor = local_mortar_weights[j, i] + if !isapprox(factor, zero(typeof(factor))) + # Lower element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[end, i], + flux, equations, dg, + i, direction_small, lower_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[j, end], + flux, equations, dg, + j, direction_large, large_element) + end + end + end + # Upper element + for i in eachnode(dg) + _, u_upper_local = get_surface_node_vars(u_upper, equations, dg, + i, mortar) # u_rr + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll + + flux = surface_flux(u_large_local, u_upper_local, orientation, + equations) + + factor = local_mortar_weights[j, i + nnodes(dg)] + if !isapprox(factor, zero(typeof(factor))) + # Upper element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[end, + i + nnodes(dg)], + flux, equations, dg, + i, direction_small, upper_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[j, end], + flux, equations, dg, + j, direction_large, large_element) + end + end + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientation == 1 + # L2 mortars in x-direction + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + direction_small = 4 + direction_large = 3 + end + + surface_flux_values[:, :, direction_small, lower_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_small, upper_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) + # Lower element + for i in eachnode(dg) + u_lower_local, _ = get_surface_node_vars(u_lower, equations, dg, + i, mortar) # u_ll + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr + + flux = surface_flux(u_lower_local, u_large_local, orientation, + equations) + + factor = local_mortar_weights[j, i] + if !isapprox(factor, zero(typeof(factor))) + # Lower element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[end, i], + flux, equations, dg, + i, direction_small, lower_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[j, end], + flux, equations, dg, + j, direction_large, large_element) + end + end + end + # Upper element + for i in eachnode(dg) + u_upper_local, _ = get_surface_node_vars(u_upper, equations, dg, + i, mortar) # u_ll + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr + + flux = surface_flux(u_upper_local, u_large_local, orientation, + equations) + + factor = local_mortar_weights[j, i + nnodes(dg)] + if !isapprox(factor, zero(typeof(factor))) + # Upper element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[end, + i + nnodes(dg)], + flux, equations, dg, + i, direction_small, upper_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + local_mortar_weights[j, end], + flux, equations, dg, + j, direction_large, large_element) + end + end + end + end + end + + return nothing +end + """ get_boundary_outer_state(u_inner, t, boundary_condition::BoundaryConditionDirichlet, diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 539405f0a1e..6049fa6b403 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -142,6 +142,110 @@ end end end + # TODO: How to include values at mortar interfaces? + # - For "local factors" include only values with nonnegative local weights + # - For LobattoLegendreMortarL2: include all neighboring values (TODO?) + for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + orientation = cache.mortars.orientations[mortar] + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientation == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientation == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + end + end + u_lower = get_node_vars(u, equations, dg, indices_small..., + lower_element) + u_upper = get_node_vars(u, equations, dg, indices_small..., + upper_element) + u_large = get_node_vars(u, equations, dg, indices_large..., + large_element) + var_lower = u_lower[variable] + var_upper = u_upper[variable] + var_large = u_large[variable] + + for j in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientation == 1 + # L2 mortars in x-direction + indices_small = (1, j) + indices_large = (nnodes(dg), j) + else + # L2 mortars in y-direction + indices_small = (j, 1) + indices_large = (j, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientation == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), j) + indices_large = (1, j) + else + # L2 mortars in y-direction + indices_small = (j, nnodes(dg)) + indices_large = (j, 1) + end + end + + l2_mortars = dg.mortar isa LobattoLegendreMortarL2 + if l2_mortars || dg.mortar.local_mortar_weights[i, j] > 0 + var_min[indices_small..., lower_element] = min(var_min[indices_small..., + lower_element], + var_large) + var_max[indices_small..., lower_element] = max(var_max[indices_small..., + lower_element], + var_large) + end + if l2_mortars || dg.mortar.local_mortar_weights[j, i] > 0 + var_min[indices_large..., large_element] = min(var_min[indices_large..., + large_element], + var_lower) + var_max[indices_large..., large_element] = max(var_max[indices_large..., + large_element], + var_lower) + end + if l2_mortars || + dg.mortar.local_mortar_weights[i, j + nnodes(dg)] > 0 + var_min[indices_small..., upper_element] = min(var_min[indices_small..., + upper_element], + var_large) + var_max[indices_small..., upper_element] = max(var_max[indices_small..., + upper_element], + var_large) + end + if l2_mortars || + dg.mortar.local_mortar_weights[j, i + nnodes(dg)] > 0 + var_min[indices_large..., large_element] = min(var_min[indices_large..., + large_element], + var_upper) + var_max[indices_large..., large_element] = max(var_max[indices_large..., + large_element], + var_upper) + end + end + end + end + # Calc bounds at physical boundaries for boundary in eachboundary(dg, cache) element = cache.boundaries.neighbor_ids[boundary] @@ -514,13 +618,200 @@ end return nothing end +############################################################################### +# IDP mortar limiting +############################################################################### + +@inline function calc_mortar_limiting_factor!(u, semi, t, dt) + (; positivity_variables_cons) = semi.solver.mortar + (; limiting_factor) = semi.cache.mortars + @trixi_timeit timer() "reset alpha" limiting_factor.=zeros(eltype(limiting_factor)) + + @trixi_timeit timer() "conservative variables" for var_index in positivity_variables_cons + limiting_positivity_conservative!(limiting_factor, u, dt, semi, var_index) + end + + return nothing +end + +############################################################################### +# Local two-sided limiting of conservative variables +@inline function limiting_positivity_conservative!(limiting_factor, u, dt, semi, + var_index) + mesh, _, dg, cache = mesh_equations_solver_cache(semi) + + (; orientations) = cache.mortars + (; surface_flux_values) = cache.elements + (; surface_flux_values_high_order) = cache.antidiffusive_fluxes + (; boundary_interpolation) = dg.basis + + (; positivity_correction_factor) = dg.volume_integral.limiter + + for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Calc minimal low-order solution + var_min_upper = typemax(eltype(surface_flux_values)) + var_min_lower = typemax(eltype(surface_flux_values)) + var_min_large = typemax(eltype(surface_flux_values)) + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + end + end + var_upper = u[var_index, indices_small..., upper_element] + var_lower = u[var_index, indices_small..., lower_element] + var_large = u[var_index, indices_large..., large_element] + var_min_upper = min(var_min_upper, var_upper) + var_min_lower = min(var_min_lower, var_lower) + var_min_large = min(var_min_large, var_large) + end + var_min_upper = positivity_correction_factor * var_min_upper + var_min_lower = positivity_correction_factor * var_min_lower + var_min_large = positivity_correction_factor * var_min_large + + for i in eachnode(dg) + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + direction_small = 1 + direction_large = 2 + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + direction_small = 3 + direction_large = 4 + end + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + direction_small = 2 + direction_large = 1 + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + direction_small = 4 + direction_large = 3 + end + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + + var_upper = u[var_index, indices_small..., upper_element] + var_lower = u[var_index, indices_small..., lower_element] + var_large = u[var_index, indices_large..., large_element] + + if min(var_upper, var_lower, var_large) < 0 + error("Safe low-order method produces negative value for conservative variable rho. Try a smaller time step.") + end + + inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + upper_element) + inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + lower_element) + inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_large..., + large_element) + + # Calculate Pm + flux_lower_high_order = surface_flux_values_high_order[var_index, i, + direction_small, + lower_element] + flux_lower_low_order = surface_flux_values[var_index, i, direction_small, + lower_element] + flux_difference_lower = factor_small * + (flux_lower_high_order - flux_lower_low_order) + + flux_upper_high_order = surface_flux_values_high_order[var_index, i, + direction_small, + upper_element] + flux_upper_low_order = surface_flux_values[var_index, i, direction_small, + upper_element] + flux_difference_upper = factor_small * + (flux_upper_high_order - flux_upper_low_order) + + flux_large_high_order = surface_flux_values_high_order[var_index, i, + direction_large, + large_element] + flux_large_low_order = surface_flux_values_high_order[var_index, i, + direction_large, + large_element] + flux_difference_large = factor_large * + (flux_large_high_order - flux_large_low_order) + + # Check if high-order fluxes are finite. Otherwise, use pure low-order fluxes. + if !all(isfinite.(flux_lower_high_order)) || + !all(isfinite(flux_upper_high_order)) || + !all(isfinite.(flux_large_high_order)) + limiting_factor[mortar] = 1 + continue + end + + Qm_upper = min(0, var_min_upper - var_upper) + Qm_lower = min(0, var_min_lower - var_lower) + Qm_large = min(0, var_min_large - var_large) + + Pm_upper = min(0, flux_difference_upper) + Pm_lower = min(0, flux_difference_lower) + Pm_large = min(0, flux_difference_large) + + Pm_upper = dt * inverse_jacobian_upper * Pm_upper + Pm_lower = dt * inverse_jacobian_lower * Pm_lower + Pm_large = dt * inverse_jacobian_large * Pm_large + + # Compute blending coefficient avoiding division by zero + # (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8)) + Qm_upper = abs(Qm_upper) / (abs(Pm_upper) + eps(typeof(Qm_upper)) * 100) + Qm_lower = abs(Qm_lower) / (abs(Pm_lower) + eps(typeof(Qm_lower)) * 100) + Qm_large = abs(Qm_large) / (abs(Pm_large) + eps(typeof(Qm_large)) * 100) + + limiting_factor[mortar] = max(limiting_factor[mortar], 1 - Qm_upper, + 1 - Qm_lower, 1 - Qm_large) + end + end + + return nothing +end + ############################################################################### # Newton-bisection method @inline function newton_loops_alpha!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, final_check, - inverse_jacobian, dt, equations, dg, cache, - limiter) + inverse_jacobian, dt, + equations::AbstractEquations{2}, + dg, cache, limiter) (; inverse_weights) = dg.basis (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index d240577ff16..0258da890a9 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -690,6 +690,36 @@ end end end +@trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl"), + l2=[ + 0.055724454952595134, + 0.03312233274919345, + 0.05223955492824185, + 0.080116031615237 + ], + linf=[ + 0.2529113255620048, + 0.17296154306769038, + 0.1232191089800711, + 0.2691634973257111 + ], + tspan=(0.0, 0.2)) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end +end + @trixi_testset "elixir_euler_colliding_flow.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_colliding_flow.jl"), l2=[ From 8fdeb1b2814a73afb1553adf406601743c6162ac Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 2 Sep 2025 10:14:38 +0200 Subject: [PATCH 069/102] Extend test --- ...lvin_helmholtz_instability_amr_sc_subcell.jl | 3 ++- src/solvers/dgsem_tree/containers_2d.jl | 3 ++- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 2 +- test/test_tree_2d_euler.jl | 17 +++++++++-------- test/test_unit.jl | 5 +++++ 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl index d43f25f8ea5..b1e15013cb2 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl @@ -37,7 +37,8 @@ basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], - positivity_variables_nonlinear = [pressure]) + positivity_variables_nonlinear = [pressure], + local_twosided_variables_cons = []) # required for the tests volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index e49a15e9b2f..12130b351f1 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -698,7 +698,7 @@ function IDPMortarContainer2D{uEltype}(capacity::Integer, n_variables, orientations = fill(typemin(Int), capacity) - limiting_factor = fill(typemin(Int), capacity) + limiting_factor = fill(nan, capacity) return IDPMortarContainer2D{uEltype}(u_upper, u_lower, u_large, neighbor_ids, large_sides, orientations, limiting_factor, @@ -725,6 +725,7 @@ function Base.show(io::IO, ::MIME"text/plain", c::IDPMortarContainer2D) println(io, "transpose(c.neighbor_ids) = $(transpose(c.neighbor_ids))") println(io, "c.large_sides = $(c.large_sides)") println(io, "c.orientations = $(c.orientations)") + println(io, "c.limiting_factor = $(c.limiting_factor)") print(io, '*'^20) end diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 6049fa6b403..3a8516516f1 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -143,7 +143,7 @@ end end # TODO: How to include values at mortar interfaces? - # - For "local factors" include only values with nonnegative local weights + # - For LobattoLegendreMortarIDP: include only values of nodes with nonnegative local weights # - For LobattoLegendreMortarL2: include all neighboring values (TODO?) for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 0258da890a9..b4ae2af439f 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -693,17 +693,18 @@ end @trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl"), + local_twosided_variables_cons=["rho"], l2=[ - 0.055724454952595134, - 0.03312233274919345, - 0.05223955492824185, - 0.080116031615237 + 0.05564750007945291, + 0.033057569945024935, + 0.050522960177622006, + 0.07943876567089606 ], linf=[ - 0.2529113255620048, - 0.17296154306769038, - 0.1232191089800711, - 0.2691634973257111 + 0.25319791631865485, + 0.1730628078465537, + 0.14219177093037955, + 0.2672173857115352 ], tspan=(0.0, 0.2)) # Ensure that we do not have excessive memory allocations diff --git a/test/test_unit.jl b/test/test_unit.jl index 0a305a3f0c4..185ec98e0cd 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -446,6 +446,11 @@ end @test isnothing(display(c3d)) end +@timed_testset "DG IDP mortar container debug output" begin + c2d = Trixi.IDPMortarContainer2D{Float64}(1, 1, 1) + @test isnothing(display(c2d)) +end + @timed_testset "Printing indicators/controllers" begin # OBS! Constructing indicators/controllers using the parameters below doesn't make sense. It's # just useful to run basic tests of `show` methods. From c0ab7e96622abbc7f034960e9a092935cb83e23a Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 2 Sep 2025 14:44:27 +0200 Subject: [PATCH 070/102] Change order in file; Add printing formats of mortar --- src/solvers/dgsem/basis_lobatto_legendre.jl | 90 +++++++++++---------- test/test_tree_2d_euler.jl | 19 +++-- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index ffe13707f38..ce7cd2b7130 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -171,50 +171,6 @@ end left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights) right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) -struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: - AbstractMortarL2{RealT} - positivity_variables_cons::Vector{Int} - mortar_l2::Mortar - local_mortar_weights::Matrix{RealT} -end - -function MortarIDP(basis::LobattoLegendreBasis; - positivity_variables_cons = Int[]) # TODO: String[], "rho" instead of 1 - RealT = real(basis) - nnodes_ = nnodes(basis) - - mortar_l2 = MortarL2(basis) - - local_mortar_weights = calc_mortar_weights(basis, RealT) - - LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(positivity_variables_cons, - mortar_l2, - local_mortar_weights) -end - -function Base.show(io::IO, mortar::LobattoLegendreMortarIDP) - @nospecialize mortar # reduce precompilation time - # TODO - print(io, "LobattoLegendreMortarIDP{", real(mortar), "}(polydeg=", polydeg(mortar), - ")") -end -function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarIDP) - @nospecialize mortar # reduce precompilation time - # TODO - print(io, "LobattoLegendreMortarIDP{", real(mortar), - "} with polynomials of degree ", - polydeg(mortar)) -end - -@inline Base.real(mortar::LobattoLegendreMortarIDP{RealT}) where {RealT} = RealT - -@inline function nnodes(mortar::LobattoLegendreMortarIDP{RealT, NNODES}) where {RealT, - NNODES} - NNODES -end - -@inline polydeg(mortar::LobattoLegendreMortarIDP) = nnodes(mortar) - 1 - struct LobattoLegendreMortarL2{RealT <: Real, NNODES, ForwardMatrix <: AbstractMatrix{RealT}, ReverseMatrix <: AbstractMatrix{RealT}} <: @@ -286,6 +242,52 @@ end @inline polydeg(mortar::LobattoLegendreMortarL2) = nnodes(mortar) - 1 +struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: + AbstractMortar{RealT} + positivity_variables_cons::Vector{Int} + mortar_l2::Mortar + local_mortar_weights::Matrix{RealT} +end + +function MortarIDP(basis::LobattoLegendreBasis; + positivity_variables_cons = Int[]) # TODO: String[], "rho" instead of 1 + RealT = real(basis) + nnodes_ = nnodes(basis) + + mortar_l2 = MortarL2(basis) + + local_mortar_weights = calc_mortar_weights(basis, RealT) + + LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(positivity_variables_cons, + mortar_l2, + local_mortar_weights) +end + +function Base.show(io::IO, mortar::LobattoLegendreMortarIDP) + @nospecialize mortar # reduce precompilation time + + print(io, "LobattoLegendreMortarIDP(polydeg=", polydeg(mortar), ", positivity for ", + mortar.positivity_variables_cons, ")") +end +function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarIDP) + @nospecialize mortar # reduce precompilation time + + print(io, "LobattoLegendreMortarIDP{", real(mortar), + "} with polynomials of degree ", + polydeg(mortar), + " and positivity limiting for conservative variables ", + mortar.positivity_variables_cons) +end + +@inline Base.real(mortar::LobattoLegendreMortarIDP{RealT}) where {RealT} = RealT + +@inline function nnodes(mortar::LobattoLegendreMortarIDP{RealT, NNODES}) where {RealT, + NNODES} + NNODES +end + +@inline polydeg(mortar::LobattoLegendreMortarIDP) = nnodes(mortar) - 1 + # TODO: We can create EC mortars along the lines of the following implementation. # abstract type AbstractMortarEC{RealT} <: AbstractMortar{RealT} end diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index b4ae2af439f..3871ddf05d0 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -695,16 +695,16 @@ end "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl"), local_twosided_variables_cons=["rho"], l2=[ - 0.05564750007945291, - 0.033057569945024935, - 0.050522960177622006, - 0.07943876567089606 + 0.05564752104857987, + 0.033057554616374794, + 0.0505245765658428, + 0.07943927352867723 ], linf=[ - 0.25319791631865485, - 0.1730628078465537, - 0.14219177093037955, - 0.2672173857115352 + 0.2529113255620048, + 0.17296154306769038, + 0.14221629583696524, + 0.26721736720894773 ], tspan=(0.0, 0.2)) # Ensure that we do not have excessive memory allocations @@ -719,6 +719,9 @@ end # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 end + + # test long printing format + @test_nowarn display(solver.mortar) end @trixi_testset "elixir_euler_colliding_flow.jl" begin From f1e015c25e9ccab4f0af7ea0dc9ad5c963e37643 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 2 Sep 2025 15:05:01 +0200 Subject: [PATCH 071/102] Fix test --- test/test_tree_2d_euler.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 3871ddf05d0..d949196401b 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -701,8 +701,8 @@ end 0.07943927352867723 ], linf=[ - 0.2529113255620048, - 0.17296154306769038, + 0.2531979194848659, + 0.17306280705055144, 0.14221629583696524, 0.26721736720894773 ], From b5307d203122d4c3ec948ad31ef16afc29eeb27f Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 8 Sep 2025 12:42:51 +0200 Subject: [PATCH 072/102] Small changes --- .../subcell_limiter_idp_correction_2d.jl | 2 +- .../dgsem_tree/dg_2d_subcell_limiters.jl | 4 ++-- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index 0e168608c4f..ad2f9fa957c 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -46,7 +46,7 @@ function perform_idp_correction!(u, dt, return nothing end -function perform_idp_mortar_correction(u, dt, mesh, equations, dg, cache) +function perform_idp_mortar_correction(u, dt, mesh::TreeMesh{2}, equations, dg, cache) (; orientations, limiting_factor) = cache.mortars (; surface_flux_values) = cache.elements diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index b63ec842afb..6fced3be848 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -994,8 +994,8 @@ function calc_mortar_flux_low_order!(surface_flux_values, surface_integral, dg::DG, cache) @unpack surface_flux = surface_integral @unpack u_lower, u_upper, u_large, orientations = cache.mortars - @unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded, - fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache + # @unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded, + # fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache (; weights) = dg.basis (; local_mortar_weights, local_factor) = mortar_idp diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index b37fd189adc..24e553d97a0 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -627,20 +627,21 @@ end ############################################################################### @inline function calc_mortar_limiting_factor!(u, semi, t, dt) - (; positivity_variables_cons, positivity_variables_nonlinear) = semi.solver.mortar - (; limiting_factor) = semi.cache.mortars + mesh, _, solver, cache = mesh_equations_solver_cache(semi) + (; positivity_variables_cons, positivity_variables_nonlinear) = solver.mortar + (; limiting_factor) = cache.mortars limiting_factor .= zeros(eltype(limiting_factor)) for var_index in positivity_variables_cons - limiting_positivity_conservative!(limiting_factor, u, dt, semi, var_index) + limiting_positivity_conservative!(limiting_factor, u, dt, semi, mesh, var_index) end for variable in positivity_variables_nonlinear - limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) + limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, mesh, variable) end # Provisional analysis of limiting factor (TODO) - (; output_directory) = semi.solver.mortar + (; output_directory) = solver.mortar if length(limiting_factor) > 0 open(joinpath(output_directory, "mortar_limiting_factor.txt"), "a") do f print(f, t) @@ -662,8 +663,8 @@ end ############################################################################### # Local two-sided limiting of conservative variables @inline function limiting_positivity_conservative!(limiting_factor, u, dt, semi, - var_index) - mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + mesh::TreeMesh{2}, var_index) + _, _, dg, cache = mesh_equations_solver_cache(semi) (; orientations) = cache.mortars (; surface_flux_values) = cache.elements @@ -831,7 +832,8 @@ end ############################################################################## # Local one-sided limiting of nonlinear variables -@inline function limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, variable) +@inline function limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, + mesh::TreeMesh{2}, variable) mesh, equations, dg, cache = mesh_equations_solver_cache(semi) (; orientations) = cache.mortars From dd46ad3625332239802f5c215a2a7241ab7d40b5 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 11 Sep 2025 13:33:38 +0200 Subject: [PATCH 073/102] Refactor mortar weights --- src/solvers/dgsem/basis_lobatto_legendre.jl | 10 +-- .../dgsem_tree/dg_2d_subcell_limiters.jl | 72 ++++++++----------- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 8 +-- 3 files changed, 41 insertions(+), 49 deletions(-) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 52c63b9fe02..1bf71063a39 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -179,7 +179,8 @@ struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, pure_low_order::Bool local_factor::Bool mortar_l2::Mortar - local_mortar_weights::Matrix{RealT} + mortar_weights::Matrix{RealT} + mortar_weights_sums::Vector{RealT} output_directory::String end @@ -233,8 +234,8 @@ function MortarIDP(basis::LobattoLegendreBasis; reverse_lower_low_order, output_directory) else - local_mortar_weights = calc_mortar_weights(basis, RealT; - basis_function = basis_function) + mortar_weights, mortar_weights_sums = calc_mortar_weights(basis, RealT; + basis_function = basis_function) LobattoLegendreMortarIDP{RealT, nnodes_, typeof(positivity_variables_nonlinear), typeof(mortar_l2)}(positivity_variables_cons, @@ -242,7 +243,8 @@ function MortarIDP(basis::LobattoLegendreBasis; pure_low_order, local_factor, mortar_l2, - local_mortar_weights, + mortar_weights, + mortar_weights_sums, output_directory) end end diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 6fced3be848..41b02f548db 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -61,8 +61,7 @@ end function calc_mortar_weights(basis, RealT; basis_function = :piecewise_constant) n_nodes = nnodes(basis) - # Saving the sum over row/column in entries with last index. - weights = zeros(RealT, n_nodes + 1, 2 * n_nodes + 1) + weights = zeros(RealT, n_nodes, 2 * n_nodes) # [large element, small element] if basis_function == :piecewise_constant calc_mortar_weights_piecewise_constant!(weights, n_nodes, RealT) @@ -72,35 +71,32 @@ function calc_mortar_weights(basis, RealT; basis_function = :piecewise_constant) error("Unsupported basis function type: $basis_function") end - for i in eachnode(basis), j in eachnode(basis) - # Row - weights[i, end] += weights[i, j] - weights[i, end] += weights[i, j + n_nodes] - # Columns - weights[end, i] += weights[j, i] - weights[end, i + n_nodes] += weights[j, i + n_nodes] - end + # Sums of mortar weights for normalization + # Only save for one small element + # The sums of the other small element are identical + # The sums of the large element are twice as large + weights_sum = vec(sum(weights[1:n_nodes, 1:n_nodes], dims = 1)) - return weights + return weights, weights_sum end function calc_mortar_weights_piecewise_constant!(mortar_weights, n_nodes, RealT) _, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) - cum_weights = [zero(RealT); cumsum(weights)] .- 1.0 + cum_LGL_weights = [zero(RealT); cumsum(weights)] .- 1.0 - cum_weights_lower = 0.5f0 * cum_weights .- 0.5f0 - cum_weights_upper = cum_weights_lower .+ 1.0 + cum_LGL_weights_lower = 0.5f0 * cum_LGL_weights .- 0.5f0 + cum_LGL_weights_upper = cum_LGL_weights_lower .+ 1.0 for i in 1:n_nodes for j in 1:n_nodes # basis function of left element - interval_left = max(cum_weights[i], cum_weights_lower[j]) - interval_right = min(cum_weights[i + 1], cum_weights_lower[j + 1]) + interval_left = max(cum_LGL_weights[i], cum_LGL_weights_lower[j]) + interval_right = min(cum_LGL_weights[i + 1], cum_LGL_weights_lower[j + 1]) mortar_weights[i, j] = max(zero(RealT), interval_right - interval_left) # basis function of right element - interval_left = max(cum_weights[i], cum_weights_upper[j]) - interval_right = min(cum_weights[i + 1], cum_weights_upper[j + 1]) + interval_left = max(cum_LGL_weights[i], cum_LGL_weights_upper[j]) + interval_right = min(cum_LGL_weights[i + 1], cum_LGL_weights_upper[j + 1]) mortar_weights[i, n_nodes + j] = max(zero(RealT), interval_right - interval_left) end @@ -997,7 +993,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, # @unpack (fstar_primary_upper_threaded, fstar_primary_lower_threaded, # fstar_secondary_upper_threaded, fstar_secondary_lower_threaded) = cache (; weights) = dg.basis - (; local_mortar_weights, local_factor) = mortar_idp + (; mortar_weights, mortar_weights_sums, local_factor) = mortar_idp @threaded for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] @@ -1032,18 +1028,17 @@ function calc_mortar_flux_low_order!(surface_flux_values, equations) if local_factor - factor = local_mortar_weights[j, i] + factor = mortar_weights[j, i] if !isapprox(factor, zero(typeof(factor))) + factor = factor / mortar_weights_sums[i] # Lower element multiply_add_to_node_vars!(surface_flux_values, - factor / - local_mortar_weights[end, i], + factor, flux, equations, dg, i, direction_small, lower_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - factor / - local_mortar_weights[j, end], + 0.5f0 * factor, flux, equations, dg, j, direction_large, large_element) end @@ -1072,19 +1067,17 @@ function calc_mortar_flux_low_order!(surface_flux_values, equations) if local_factor - factor = local_mortar_weights[j, i + nnodes(dg)] + factor = mortar_weights[j, i + nnodes(dg)] if !isapprox(factor, zero(typeof(factor))) + factor = factor / mortar_weights_sums[i] # Upper element multiply_add_to_node_vars!(surface_flux_values, - factor / - local_mortar_weights[end, - i + nnodes(dg)], + factor, flux, equations, dg, i, direction_small, upper_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - factor / - local_mortar_weights[j, end], + 0.5f0 * factor, flux, equations, dg, j, direction_large, large_element) end @@ -1127,18 +1120,17 @@ function calc_mortar_flux_low_order!(surface_flux_values, equations) if local_factor - factor = local_mortar_weights[j, i] + factor = mortar_weights[j, i] if !isapprox(factor, zero(typeof(factor))) + factor = factor / mortar_weights_sums[i] # Lower element multiply_add_to_node_vars!(surface_flux_values, - factor / - local_mortar_weights[end, i], + factor, flux, equations, dg, i, direction_small, lower_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - factor / - local_mortar_weights[j, end], + 0.5f0 * factor, flux, equations, dg, j, direction_large, large_element) end @@ -1167,19 +1159,17 @@ function calc_mortar_flux_low_order!(surface_flux_values, equations) if local_factor - factor = local_mortar_weights[j, i + nnodes(dg)] + factor = mortar_weights[j, i + nnodes(dg)] if !isapprox(factor, zero(typeof(factor))) + factor = factor / mortar_weights_sums[i] # Upper element multiply_add_to_node_vars!(surface_flux_values, - factor / - local_mortar_weights[end, - i + nnodes(dg)], + factor, flux, equations, dg, i, direction_small, upper_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - factor / - local_mortar_weights[j, end], + 0.5f0 * factor, flux, equations, dg, j, direction_large, large_element) end diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 24e553d97a0..b23f66fc47b 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -212,7 +212,7 @@ end alternative = dg.mortar isa LobattoLegendreMortarIDPAlternative include_all_values = l2_mortars || alternative || !(dg.mortar.local_factor) - if include_all_values || dg.mortar.local_mortar_weights[i, j] > 0 + if include_all_values || dg.mortar.mortar_weights[i, j] > 0 var_min[indices_small..., lower_element] = min(var_min[indices_small..., lower_element], var_large) @@ -220,7 +220,7 @@ end lower_element], var_large) end - if include_all_values || dg.mortar.local_mortar_weights[j, i] > 0 + if include_all_values || dg.mortar.mortar_weights[j, i] > 0 var_min[indices_large..., large_element] = min(var_min[indices_large..., large_element], var_lower) @@ -229,7 +229,7 @@ end var_lower) end if include_all_values || - dg.mortar.local_mortar_weights[i, j + nnodes(dg)] > 0 + dg.mortar.mortar_weights[i, j + nnodes(dg)] > 0 var_min[indices_small..., upper_element] = min(var_min[indices_small..., upper_element], var_large) @@ -238,7 +238,7 @@ end var_large) end if include_all_values || - dg.mortar.local_mortar_weights[j, i + nnodes(dg)] > 0 + dg.mortar.mortar_weights[j, i + nnodes(dg)] > 0 var_min[indices_large..., large_element] = min(var_min[indices_large..., large_element], var_upper) From b496d025f55dd6cb8718764533d0c25e7fd63c65 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 11 Sep 2025 14:13:44 +0200 Subject: [PATCH 074/102] Fix last commit --- .../dgsem_tree/dg_2d_subcell_limiters.jl | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 41b02f548db..38b90200004 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -1030,15 +1030,15 @@ function calc_mortar_flux_low_order!(surface_flux_values, if local_factor factor = mortar_weights[j, i] if !isapprox(factor, zero(typeof(factor))) - factor = factor / mortar_weights_sums[i] # Lower element multiply_add_to_node_vars!(surface_flux_values, - factor, + factor / mortar_weights_sums[i], flux, equations, dg, i, direction_small, lower_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.5f0 * factor, + 0.5f0 * factor / + mortar_weights_sums[j], flux, equations, dg, j, direction_large, large_element) end @@ -1069,15 +1069,15 @@ function calc_mortar_flux_low_order!(surface_flux_values, if local_factor factor = mortar_weights[j, i + nnodes(dg)] if !isapprox(factor, zero(typeof(factor))) - factor = factor / mortar_weights_sums[i] # Upper element multiply_add_to_node_vars!(surface_flux_values, - factor, + factor / mortar_weights_sums[i], flux, equations, dg, i, direction_small, upper_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.5f0 * factor, + 0.5f0 * factor / + mortar_weights_sums[j], flux, equations, dg, j, direction_large, large_element) end @@ -1122,15 +1122,15 @@ function calc_mortar_flux_low_order!(surface_flux_values, if local_factor factor = mortar_weights[j, i] if !isapprox(factor, zero(typeof(factor))) - factor = factor / mortar_weights_sums[i] # Lower element multiply_add_to_node_vars!(surface_flux_values, - factor, + factor / mortar_weights_sums[i], flux, equations, dg, i, direction_small, lower_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.5f0 * factor, + 0.5f0 * factor / + mortar_weights_sums[j], flux, equations, dg, j, direction_large, large_element) end @@ -1161,15 +1161,15 @@ function calc_mortar_flux_low_order!(surface_flux_values, if local_factor factor = mortar_weights[j, i + nnodes(dg)] if !isapprox(factor, zero(typeof(factor))) - factor = factor / mortar_weights_sums[i] # Upper element multiply_add_to_node_vars!(surface_flux_values, - factor, + factor / mortar_weights_sums[i], flux, equations, dg, i, direction_small, upper_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.5f0 * factor, + 0.5f0 * factor / + mortar_weights_sums[j], flux, equations, dg, j, direction_large, large_element) end From 903549ad8e587cf027572197245ac4c61e0ce46f Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 11 Sep 2025 15:13:26 +0200 Subject: [PATCH 075/102] Fix changes --- src/solvers/dgsem/basis_lobatto_legendre.jl | 4 +- .../dgsem_tree/dg_2d_subcell_limiters.jl | 41 +++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 1bf71063a39..d86e83a4c4f 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -179,8 +179,8 @@ struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, pure_low_order::Bool local_factor::Bool mortar_l2::Mortar - mortar_weights::Matrix{RealT} - mortar_weights_sums::Vector{RealT} + mortar_weights::Matrix{RealT} # [large element, small element] + mortar_weights_sums::Matrix{RealT} # [node, left/right/large element] output_directory::String end diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 38b90200004..3e0cf59ccad 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -62,6 +62,7 @@ end function calc_mortar_weights(basis, RealT; basis_function = :piecewise_constant) n_nodes = nnodes(basis) weights = zeros(RealT, n_nodes, 2 * n_nodes) # [large element, small element] + weights_sum = zeros(RealT, n_nodes, 3) # [node, left/right/large element] if basis_function == :piecewise_constant calc_mortar_weights_piecewise_constant!(weights, n_nodes, RealT) @@ -72,10 +73,14 @@ function calc_mortar_weights(basis, RealT; basis_function = :piecewise_constant) end # Sums of mortar weights for normalization - # Only save for one small element - # The sums of the other small element are identical - # The sums of the large element are twice as large - weights_sum = vec(sum(weights[1:n_nodes, 1:n_nodes], dims = 1)) + for i in eachnode(basis) + for j in eachnode(basis) + weights_sum[i, 1] += weights[j, i] # left element + weights_sum[i, 2] += weights[j, i + n_nodes] # right element + weights_sum[i, 3] += weights[i, j] # large element + weights_sum[i, 3] += weights[i, j + n_nodes] + end + end return weights, weights_sum end @@ -1032,13 +1037,14 @@ function calc_mortar_flux_low_order!(surface_flux_values, if !isapprox(factor, zero(typeof(factor))) # Lower element multiply_add_to_node_vars!(surface_flux_values, - factor / mortar_weights_sums[i], + factor / + mortar_weights_sums[i, 1], flux, equations, dg, i, direction_small, lower_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.5f0 * factor / - mortar_weights_sums[j], + factor / + mortar_weights_sums[j, 3], flux, equations, dg, j, direction_large, large_element) end @@ -1071,13 +1077,14 @@ function calc_mortar_flux_low_order!(surface_flux_values, if !isapprox(factor, zero(typeof(factor))) # Upper element multiply_add_to_node_vars!(surface_flux_values, - factor / mortar_weights_sums[i], + factor / + mortar_weights_sums[i, 2], flux, equations, dg, i, direction_small, upper_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.5f0 * factor / - mortar_weights_sums[j], + factor / + mortar_weights_sums[j, 3], flux, equations, dg, j, direction_large, large_element) end @@ -1124,13 +1131,14 @@ function calc_mortar_flux_low_order!(surface_flux_values, if !isapprox(factor, zero(typeof(factor))) # Lower element multiply_add_to_node_vars!(surface_flux_values, - factor / mortar_weights_sums[i], + factor / + mortar_weights_sums[i, 1], flux, equations, dg, i, direction_small, lower_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.5f0 * factor / - mortar_weights_sums[j], + factor / + mortar_weights_sums[j, 3], flux, equations, dg, j, direction_large, large_element) end @@ -1163,13 +1171,14 @@ function calc_mortar_flux_low_order!(surface_flux_values, if !isapprox(factor, zero(typeof(factor))) # Upper element multiply_add_to_node_vars!(surface_flux_values, - factor / mortar_weights_sums[i], + factor / + mortar_weights_sums[i, 2], flux, equations, dg, i, direction_small, upper_element) # Large element multiply_add_to_node_vars!(surface_flux_values, - 0.5f0 * factor / - mortar_weights_sums[j], + factor / + mortar_weights_sums[j, 3], flux, equations, dg, j, direction_large, large_element) end From 3f83aa947510d580e912c6556ddf05db1053d8dc Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 11 Sep 2025 15:37:44 +0200 Subject: [PATCH 076/102] Merge changes from clean branch --- src/solvers/dgsem_tree/containers_2d.jl | 2 +- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 16 ++++++++-------- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 6 +++--- test/test_tree_2d_euler.jl | 3 +++ test/test_unit.jl | 5 +++++ 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index 8a14643d400..f3bb62ad6d2 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -698,7 +698,7 @@ function IDPMortarContainer2D{uEltype}(capacity::Integer, n_variables, orientations = fill(typemin(Int), capacity) - limiting_factor = fill(typemin(Int), capacity) + limiting_factor = fill(nan, capacity) return IDPMortarContainer2D{uEltype}(u_upper, u_lower, u_large, neighbor_ids, large_sides, orientations, limiting_factor, diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 3e0cf59ccad..1289dbc07bf 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -1024,8 +1024,8 @@ function calc_mortar_flux_low_order!(surface_flux_values, surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) - _, u_lower_local = get_surface_node_vars(u_lower, equations, dg, i, - mortar) # u_rr + _, u_lower_local = get_surface_node_vars(u_lower, equations, dg, + i, mortar) # u_rr for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll @@ -1064,8 +1064,8 @@ function calc_mortar_flux_low_order!(surface_flux_values, end # Upper element for i in eachnode(dg) - _, u_upper_local = get_surface_node_vars(u_upper, equations, dg, i, - mortar) # u_rr + _, u_upper_local = get_surface_node_vars(u_upper, equations, dg, + i, mortar) # u_rr for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll @@ -1118,8 +1118,8 @@ function calc_mortar_flux_low_order!(surface_flux_values, surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) # Lower element for i in eachnode(dg) - u_lower_local, _ = get_surface_node_vars(u_lower, equations, dg, i, - mortar) # u_ll + u_lower_local, _ = get_surface_node_vars(u_lower, equations, dg, + i, mortar) # u_ll for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr @@ -1158,8 +1158,8 @@ function calc_mortar_flux_low_order!(surface_flux_values, end # Upper element for i in eachnode(dg) - u_upper_local, _ = get_surface_node_vars(u_upper, equations, dg, i, - mortar) # u_ll + u_upper_local, _ = get_surface_node_vars(u_upper, equations, dg, + i, mortar) # u_ll for j in eachnode(dg) u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index b23f66fc47b..888e0d19261 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -630,13 +630,13 @@ end mesh, _, solver, cache = mesh_equations_solver_cache(semi) (; positivity_variables_cons, positivity_variables_nonlinear) = solver.mortar (; limiting_factor) = cache.mortars - limiting_factor .= zeros(eltype(limiting_factor)) + @trixi_timeit timer() "reset alpha" limiting_factor.=zeros(eltype(limiting_factor)) - for var_index in positivity_variables_cons + @trixi_timeit timer() "conservative variables" for var_index in positivity_variables_cons limiting_positivity_conservative!(limiting_factor, u, dt, semi, mesh, var_index) end - for variable in positivity_variables_nonlinear + @trixi_timeit timer() "nonlinear variables" for variable in positivity_variables_nonlinear limiting_positivity_nonlinear!(limiting_factor, u, dt, semi, mesh, variable) end diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 14244e26f83..d4f6414de9f 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -1048,6 +1048,9 @@ end # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 end + + # test long printing format + @test_nowarn display(solver.mortar) end @trixi_testset "elixir_euler_colliding_flow.jl" begin diff --git a/test/test_unit.jl b/test/test_unit.jl index df87e9d7b07..0d7dab2a232 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -452,6 +452,11 @@ end @test isnothing(display(c3d)) end +@timed_testset "DG IDP mortar container debug output" begin + c2d = Trixi.IDPMortarContainer2D{Float64}(1, 1, 1) + @test isnothing(display(c2d)) +end + @timed_testset "Printing indicators/controllers" begin # OBS! Constructing indicators/controllers using the parameters below doesn't make sense. It's # just useful to run basic tests of `show` methods. From 8bb1c1e4b9a7fe319f4a527cf74b4592a64343b8 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 11 Sep 2025 16:47:38 +0200 Subject: [PATCH 077/102] Clarify comment --- src/solvers/dgsem/basis_lobatto_legendre.jl | 2 +- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index cf3057b6c4f..7b3929dda66 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -246,7 +246,7 @@ struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: AbstractMortar{RealT} positivity_variables_cons::Vector{Int} mortar_l2::Mortar - mortar_weights::Matrix{RealT} # [large element, small element] + mortar_weights::Matrix{RealT} # [node (large element), node (small element)] mortar_weights_sums::Matrix{RealT} # [node, left/right/large element] end diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 62c8a269051..8374364c8b8 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -61,7 +61,7 @@ end function calc_mortar_weights(basis, RealT) n_nodes = nnodes(basis) - weights = zeros(RealT, n_nodes, 2 * n_nodes) # [large element, small element] + weights = zeros(RealT, n_nodes, 2 * n_nodes) # [node (large element), node (small element)] weights_sum = zeros(RealT, n_nodes, 3) # [node, left/right/large element] calc_mortar_weights!(weights, n_nodes, RealT) From 57871516c13154d9f601696cc23e0ec683c919e4 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 22 Sep 2025 12:02:21 +0200 Subject: [PATCH 078/102] Fix name of variable --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 08facae2143..9031eff804c 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -208,7 +208,7 @@ end end l2_mortars = dg.mortar isa LobattoLegendreMortarL2 - if l2_mortars || dg.mortar.local_mortar_weights[i, j] > 0 + if l2_mortars || dg.mortar.mortar_weights[i, j] > 0 var_min[indices_small..., lower_element] = min(var_min[indices_small..., lower_element], var_large) @@ -216,7 +216,7 @@ end lower_element], var_large) end - if l2_mortars || dg.mortar.local_mortar_weights[j, i] > 0 + if l2_mortars || dg.mortar.mortar_weights[j, i] > 0 var_min[indices_large..., large_element] = min(var_min[indices_large..., large_element], var_lower) @@ -224,8 +224,7 @@ end large_element], var_lower) end - if l2_mortars || - dg.mortar.local_mortar_weights[i, j + nnodes(dg)] > 0 + if l2_mortars || dg.mortar.mortar_weights[i, j + nnodes(dg)] > 0 var_min[indices_small..., upper_element] = min(var_min[indices_small..., upper_element], var_large) @@ -233,8 +232,7 @@ end upper_element], var_large) end - if l2_mortars || - dg.mortar.local_mortar_weights[j, i + nnodes(dg)] > 0 + if l2_mortars || dg.mortar.mortar_weights[j, i + nnodes(dg)] > 0 var_min[indices_large..., large_element] = min(var_min[indices_large..., large_element], var_upper) From 58970927accdd1190d80bbef5cff64564368f8bd Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 9 Oct 2025 14:05:27 +0200 Subject: [PATCH 079/102] Rename inner indices; `continue` to `break` --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 81 +++++++++---------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index a5983f3513d..27e89a301a0 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -83,9 +83,11 @@ end end end + # Calc bounds at mortars # TODO: How to include values at mortar interfaces? # - For LobattoLegendreMortarIDP: include only values of nodes with nonnegative local weights # - For LobattoLegendreMortarL2: include all neighboring values (TODO?) + l2_mortars = dg.mortar isa LobattoLegendreMortarL2 for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] @@ -115,71 +117,64 @@ end indices_large = (i, 1) end end - u_lower = get_node_vars(u, equations, dg, indices_small..., - lower_element) - u_upper = get_node_vars(u, equations, dg, indices_small..., - upper_element) - u_large = get_node_vars(u, equations, dg, indices_large..., - large_element) - var_lower = u_lower[variable] - var_upper = u_upper[variable] - var_large = u_large[variable] + var_lower = u[variable, indices_small..., lower_element] + var_upper = u[variable, indices_small..., upper_element] + var_large = u[variable, indices_large..., large_element] for j in eachnode(dg) if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side if orientation == 1 # L2 mortars in x-direction - indices_small = (1, j) - indices_large = (nnodes(dg), j) + indices_small_inner = (1, j) + indices_large_inner = (nnodes(dg), j) else # L2 mortars in y-direction - indices_small = (j, 1) - indices_large = (j, nnodes(dg)) + indices_small_inner = (j, 1) + indices_large_inner = (j, nnodes(dg)) end else # large_sides[mortar] == 2 -> small elements on left side if orientation == 1 # L2 mortars in x-direction - indices_small = (nnodes(dg), j) - indices_large = (1, j) + indices_small_inner = (nnodes(dg), j) + indices_large_inner = (1, j) else # L2 mortars in y-direction - indices_small = (j, nnodes(dg)) - indices_large = (j, 1) + indices_small_inner = (j, nnodes(dg)) + indices_large_inner = (j, 1) end end - l2_mortars = dg.mortar isa LobattoLegendreMortarL2 if l2_mortars || dg.mortar.mortar_weights[i, j] > 0 - var_min[indices_small..., lower_element] = min(var_min[indices_small..., - lower_element], - var_large) - var_max[indices_small..., lower_element] = max(var_max[indices_small..., - lower_element], - var_large) + var_min[indices_small_inner..., lower_element] = min(var_min[indices_small_inner..., + lower_element], + var_large) + var_max[indices_small_inner..., lower_element] = max(var_max[indices_small_inner..., + lower_element], + var_large) end if l2_mortars || dg.mortar.mortar_weights[j, i] > 0 - var_min[indices_large..., large_element] = min(var_min[indices_large..., - large_element], - var_lower) - var_max[indices_large..., large_element] = max(var_max[indices_large..., - large_element], - var_lower) + var_min[indices_large_inner..., large_element] = min(var_min[indices_large_inner..., + large_element], + var_lower) + var_max[indices_large_inner..., large_element] = max(var_max[indices_large_inner..., + large_element], + var_lower) end if l2_mortars || dg.mortar.mortar_weights[i, j + nnodes(dg)] > 0 - var_min[indices_small..., upper_element] = min(var_min[indices_small..., - upper_element], - var_large) - var_max[indices_small..., upper_element] = max(var_max[indices_small..., - upper_element], - var_large) + var_min[indices_small_inner..., upper_element] = min(var_min[indices_small_inner..., + upper_element], + var_large) + var_max[indices_small_inner..., upper_element] = max(var_max[indices_small_inner..., + upper_element], + var_large) end if l2_mortars || dg.mortar.mortar_weights[j, i + nnodes(dg)] > 0 - var_min[indices_large..., large_element] = min(var_min[indices_large..., - large_element], - var_upper) - var_max[indices_large..., large_element] = max(var_max[indices_large..., - large_element], - var_upper) + var_min[indices_large_inner..., large_element] = min(var_min[indices_large_inner..., + large_element], + var_upper) + var_max[indices_large_inner..., large_element] = max(var_max[indices_large_inner..., + large_element], + var_upper) end end end @@ -679,7 +674,7 @@ end !all(isfinite(flux_upper_high_order)) || !all(isfinite.(flux_large_high_order)) limiting_factor[mortar] = 1 - continue + break end Qm_upper = min(0, var_min_upper - var_upper) From f7de47d04fb1f5631769a56421fec5190641ceee Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 13 Oct 2025 15:28:41 +0200 Subject: [PATCH 080/102] Add explanation to computation of local weights --- .../dgsem_tree/dg_2d_subcell_limiters.jl | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 43f4d7f3d66..5dfb83f6484 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -82,24 +82,39 @@ end function calc_mortar_weights!(mortar_weights, n_nodes, RealT) _, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) - # Cumulative LGL weights to construct LGL subgrid - cum_weights = [zero(RealT); cumsum(weights)] .- 1.0 - - cum_weights_lower = 0.5f0 * cum_weights .- 0.5f0 - cum_weights_upper = cum_weights_lower .+ 1.0f0 - - for i in 1:n_nodes - for j in 1:n_nodes - # basis function of left element - interval_left = max(cum_weights[i], cum_weights_lower[j]) - interval_right = min(cum_weights[i + 1], cum_weights_lower[j + 1]) - mortar_weights[i, j] = max(zero(RealT), interval_right - interval_left) - - # basis function of right element - interval_left = max(cum_weights[i], cum_weights_upper[j]) - interval_right = min(cum_weights[i + 1], cum_weights_upper[j + 1]) - mortar_weights[i, n_nodes + j] = max(zero(RealT), - interval_right - interval_left) + # Local mortar weigths are of the form: `w_ij = int_S psi_i phi_j ds`, + # where `phi_i` are the basis functions of the small element and `psi_j` are the basis functions + # of the large element. `S` is the face connecting both elements. + # We use piecewise constant basis functions on the LGL subgrid. So, only focus on interval, + # where both basis functions are non-zero. `interval = [left_bound, right_bound]`. + # `w_ij = int_S psi_i phi_j ds = int_{left_bound}^{right_bound} ds = right_bound - left_bound`. + # `right_bound = min(left_bound_large, left_bound_small)` + # `left_bound = max(right_bound_large, right_bound_small)` + # If `right_bound <= left_bound`, i.e., both intervals don't overlap, then `w_ij = 0`. + + # Due to the LGL subgrid, the interval bounds are cumulative LGL quadrature weights. + cum_weights_large = [zero(RealT); cumsum(weights)] .- 1 # on [-1, 1] + cum_weights_lower = 0.5f0 * cum_weights_large .- 0.5f0 # on [-1, 0] + cum_weights_upper = cum_weights_lower .+ 1 # on [0, 1] + # So, for `w_ij` we have + # `right_bound = min(cum_weights_large[i], cum_weights_small[j])` + # `left_bound = max(cum_weights_large[i+1], cum_weights_small[j+1])` + + for j in 1:n_nodes, i in 1:n_nodes + # lower and large element element + left = max(cum_weights_large[i], cum_weights_lower[j]) + right = min(cum_weights_large[i + 1], cum_weights_lower[j + 1]) + + # Local weight of 0 if intervals do not overlap, i.e., `right <= left` + if right > left + mortar_weights[i, j] = right - left + end + + # upper and large element + left = max(cum_weights_large[i], cum_weights_upper[j]) + right = min(cum_weights_large[i + 1], cum_weights_upper[j + 1]) + if right > left + mortar_weights[i, n_nodes + j] = right - left end end From 40b3955e07da60cd47e684c1366280553599ab1a Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 13 Oct 2025 15:30:38 +0200 Subject: [PATCH 081/102] typo --- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 5dfb83f6484..a9afb3da43d 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -82,7 +82,7 @@ end function calc_mortar_weights!(mortar_weights, n_nodes, RealT) _, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) - # Local mortar weigths are of the form: `w_ij = int_S psi_i phi_j ds`, + # Local mortar weights are of the form: `w_ij = int_S psi_i phi_j ds`, # where `phi_i` are the basis functions of the small element and `psi_j` are the basis functions # of the large element. `S` is the face connecting both elements. # We use piecewise constant basis functions on the LGL subgrid. So, only focus on interval, From 6392064d53846f9af02a048da9ccadc2ac2c45d5 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 14 Oct 2025 14:20:12 +0200 Subject: [PATCH 082/102] Restructure code --- .../subcell_limiter_idp_correction_2d.jl | 103 +++++++++--------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index ed782e09cd9..b93a62c5d8d 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -79,9 +79,32 @@ function perform_idp_mortar_correction(u, dt, mesh::TreeMesh{2}, equations, dg, if isapprox(limiting_factor[mortar], one(eltype(limiting_factor))) continue end - large_element = cache.mortars.neighbor_ids[3, mortar] - upper_element = cache.mortars.neighbor_ids[2, mortar] - lower_element = cache.mortars.neighbor_ids[1, mortar] + + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + direction_small = 1 + direction_large = 2 + else + direction_small = 3 + direction_large = 4 + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + direction_small = 2 + direction_large = 1 + else + direction_small = 4 + direction_large = 3 + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end for i in eachnode(dg) if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side @@ -89,75 +112,51 @@ function perform_idp_mortar_correction(u, dt, mesh::TreeMesh{2}, equations, dg, # L2 mortars in x-direction indices_small = (1, i) indices_large = (nnodes(dg), i) - direction_small = 1 - direction_large = 2 else # L2 mortars in y-direction indices_small = (i, 1) indices_large = (i, nnodes(dg)) - direction_small = 3 - direction_large = 4 end - factor_small = boundary_interpolation[1, 1] - factor_large = -boundary_interpolation[nnodes(dg), 2] else # large_sides[mortar] == 2 -> small elements on left side if orientations[mortar] == 1 # L2 mortars in x-direction indices_small = (nnodes(dg), i) indices_large = (1, i) - direction_small = 2 - direction_large = 1 else # L2 mortars in y-direction indices_small = (i, nnodes(dg)) indices_large = (i, 1) - direction_small = 4 - direction_large = 3 end - factor_large = boundary_interpolation[1, 1] - factor_small = -boundary_interpolation[nnodes(dg), 2] end - # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. - # This sign switch is directly applied to the boundary interpolation factors here. - inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., - upper_element) - inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., - lower_element) + + # small elements + for small_element_index in 1:2 + small_element = cache.mortars.neighbor_ids[small_element_index, mortar] + inverse_jacobian_small = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + small_element) + + flux_small_high_order = get_node_vars(surface_flux_values_high_order, + equations, dg, + i, direction_small, small_element) + flux_small_low_order = get_node_vars(surface_flux_values, equations, dg, + i, direction_small, small_element) + flux_difference_small = factor_small * + (flux_small_high_order .- flux_small_low_order) + + multiply_add_to_node_vars!(u, + dt * inverse_jacobian_small * + (1 - limiting_factor[mortar]), + flux_difference_small, equations, dg, + indices_small..., small_element) + end + + # large element + large_element = cache.mortars.neighbor_ids[3, mortar] inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, mesh, indices_large..., large_element) - # lower element - flux_lower_high_order = get_node_vars(surface_flux_values_high_order, - equations, dg, i, direction_small, - lower_element) - flux_lower_low_order = get_node_vars(surface_flux_values, equations, dg, i, - direction_small, lower_element) - flux_difference_lower = factor_small * - (flux_lower_high_order .- flux_lower_low_order) - - multiply_add_to_node_vars!(u, - dt * inverse_jacobian_lower * - (1 - limiting_factor[mortar]), - flux_difference_lower, equations, dg, - indices_small..., lower_element) - - flux_upper_high_order = get_node_vars(surface_flux_values_high_order, - equations, dg, i, direction_small, - upper_element) - flux_upper_low_order = get_node_vars(surface_flux_values, equations, dg, i, - direction_small, upper_element) - flux_difference_upper = factor_small * - (flux_upper_high_order .- flux_upper_low_order) - - multiply_add_to_node_vars!(u, - dt * inverse_jacobian_upper * - (1 - limiting_factor[mortar]), - flux_difference_upper, equations, dg, - indices_small..., upper_element) - flux_large_high_order = get_node_vars(surface_flux_values_high_order, equations, dg, i, direction_large, large_element) From 627bb4ef52d322993c0dce9d9bed473fddc12649 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 14 Oct 2025 15:32:13 +0200 Subject: [PATCH 083/102] Refactor `mortar_weights` --- src/solvers/dgsem/basis_lobatto_legendre.jl | 8 ++--- .../dgsem_tree/dg_2d_subcell_limiters.jl | 32 +++++++++---------- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 12 ++++--- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 7b3929dda66..afaec73a2ee 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -246,8 +246,8 @@ struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: AbstractMortar{RealT} positivity_variables_cons::Vector{Int} mortar_l2::Mortar - mortar_weights::Matrix{RealT} # [node (large element), node (small element)] - mortar_weights_sums::Matrix{RealT} # [node, left/right/large element] + mortar_weights::Array{RealT, 3} # [node (large element), node (small element), small element] + mortar_weights_sums::Array{RealT, 2} # [node, left/right/large element] end function MortarIDP(basis::LobattoLegendreBasis; @@ -257,11 +257,11 @@ function MortarIDP(basis::LobattoLegendreBasis; mortar_l2 = MortarL2(basis) - local_mortar_weights, mortar_weights_sums = calc_mortar_weights(basis, RealT) + mortar_weights, mortar_weights_sums = calc_mortar_weights(basis, RealT) LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(positivity_variables_cons, mortar_l2, - local_mortar_weights, + mortar_weights, mortar_weights_sums) end diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index a9afb3da43d..df7541ca09b 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -61,22 +61,20 @@ end function calc_mortar_weights(basis, RealT) n_nodes = nnodes(basis) - weights = zeros(RealT, n_nodes, 2 * n_nodes) # [node (large element), node (small element)] - weights_sum = zeros(RealT, n_nodes, 3) # [node, left/right/large element] + mortar_weights = zeros(RealT, n_nodes, n_nodes, 2) # [node (large element), node (small element), small element] + mortar_weights_sums = zeros(RealT, n_nodes, 3) # [node, left/right/large element] - calc_mortar_weights!(weights, n_nodes, RealT) + calc_mortar_weights!(mortar_weights, n_nodes, RealT) # Sums of mortar weights for normalization - for i in eachnode(basis) - for j in eachnode(basis) - weights_sum[i, 1] += weights[j, i] # left element - weights_sum[i, 2] += weights[j, i + n_nodes] # right element - weights_sum[i, 3] += weights[i, j] # large element - weights_sum[i, 3] += weights[i, j + n_nodes] - end + for i in eachnode(basis), j in eachnode(basis) + mortar_weights_sums[i, 1] += mortar_weights[j, i, 1] # left element + mortar_weights_sums[i, 2] += mortar_weights[j, i, 2] # right element + mortar_weights_sums[i, 3] += mortar_weights[i, j, 1] # large element + mortar_weights_sums[i, 3] += mortar_weights[i, j, 2] # large element end - return weights, weights_sum + return mortar_weights, mortar_weights_sums end function calc_mortar_weights!(mortar_weights, n_nodes, RealT) @@ -107,14 +105,14 @@ function calc_mortar_weights!(mortar_weights, n_nodes, RealT) # Local weight of 0 if intervals do not overlap, i.e., `right <= left` if right > left - mortar_weights[i, j] = right - left + mortar_weights[i, j, 1] = right - left end # upper and large element left = max(cum_weights_large[i], cum_weights_upper[j]) right = min(cum_weights_large[i + 1], cum_weights_upper[j + 1]) if right > left - mortar_weights[i, n_nodes + j] = right - left + mortar_weights[i, j, 2] = right - left end end @@ -895,7 +893,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, flux = surface_flux(u_large_local, u_lower_local, orientation, equations) - factor = mortar_weights[j, i] + factor = mortar_weights[j, i, 1] if !isapprox(factor, zero(typeof(factor))) # Lower element multiply_add_to_node_vars!(surface_flux_values, @@ -922,7 +920,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, flux = surface_flux(u_large_local, u_upper_local, orientation, equations) - factor = mortar_weights[j, i + nnodes(dg)] + factor = mortar_weights[j, i, 2] if !isapprox(factor, zero(typeof(factor))) # Upper element multiply_add_to_node_vars!(surface_flux_values, @@ -963,7 +961,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, flux = surface_flux(u_lower_local, u_large_local, orientation, equations) - factor = mortar_weights[j, i] + factor = mortar_weights[j, i, 1] if !isapprox(factor, zero(typeof(factor))) # Lower element multiply_add_to_node_vars!(surface_flux_values, @@ -990,7 +988,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, flux = surface_flux(u_upper_local, u_large_local, orientation, equations) - factor = mortar_weights[j, i + nnodes(dg)] + factor = mortar_weights[j, i, 2] if !isapprox(factor, zero(typeof(factor))) # Upper element multiply_add_to_node_vars!(surface_flux_values, diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 27e89a301a0..9ec9fb781f7 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -144,7 +144,8 @@ end end end - if l2_mortars || dg.mortar.mortar_weights[i, j] > 0 + # from large to lower element + if l2_mortars || dg.mortar.mortar_weights[i, j, 1] > 0 var_min[indices_small_inner..., lower_element] = min(var_min[indices_small_inner..., lower_element], var_large) @@ -152,7 +153,8 @@ end lower_element], var_large) end - if l2_mortars || dg.mortar.mortar_weights[j, i] > 0 + # from lower to large element + if l2_mortars || dg.mortar.mortar_weights[j, i, 1] > 0 var_min[indices_large_inner..., large_element] = min(var_min[indices_large_inner..., large_element], var_lower) @@ -160,7 +162,8 @@ end large_element], var_lower) end - if l2_mortars || dg.mortar.mortar_weights[i, j + nnodes(dg)] > 0 + # from large to upper element + if l2_mortars || dg.mortar.mortar_weights[i, j, 2] > 0 var_min[indices_small_inner..., upper_element] = min(var_min[indices_small_inner..., upper_element], var_large) @@ -168,7 +171,8 @@ end upper_element], var_large) end - if l2_mortars || dg.mortar.mortar_weights[j, i + nnodes(dg)] > 0 + # from upper to large element + if l2_mortars || dg.mortar.mortar_weights[j, i, 2] > 0 var_min[indices_large_inner..., large_element] = min(var_min[indices_large_inner..., large_element], var_upper) From ebdfd07b4ca1c84a5c492bec4905f064c3274c12 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 14 Oct 2025 16:40:08 +0200 Subject: [PATCH 084/102] Refactor `calc_bounds_twosided` --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 62 +++++++------------ 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 9ec9fb781f7..eb138aaf8ed 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -90,8 +90,6 @@ end l2_mortars = dg.mortar isa LobattoLegendreMortarL2 for mortar in eachmortar(dg, cache) large_element = cache.mortars.neighbor_ids[3, mortar] - upper_element = cache.mortars.neighbor_ids[2, mortar] - lower_element = cache.mortars.neighbor_ids[1, mortar] orientation = cache.mortars.orientations[mortar] @@ -117,8 +115,9 @@ end indices_large = (i, 1) end end - var_lower = u[variable, indices_small..., lower_element] - var_upper = u[variable, indices_small..., upper_element] + # Get solution data + var_small = (u[variable, indices_small..., cache.mortars.neighbor_ids[1, mortar]], + u[variable, indices_small..., cache.mortars.neighbor_ids[2, mortar]]) var_large = u[variable, indices_large..., large_element] for j in eachnode(dg) @@ -144,41 +143,26 @@ end end end - # from large to lower element - if l2_mortars || dg.mortar.mortar_weights[i, j, 1] > 0 - var_min[indices_small_inner..., lower_element] = min(var_min[indices_small_inner..., - lower_element], - var_large) - var_max[indices_small_inner..., lower_element] = max(var_max[indices_small_inner..., - lower_element], - var_large) - end - # from lower to large element - if l2_mortars || dg.mortar.mortar_weights[j, i, 1] > 0 - var_min[indices_large_inner..., large_element] = min(var_min[indices_large_inner..., - large_element], - var_lower) - var_max[indices_large_inner..., large_element] = max(var_max[indices_large_inner..., - large_element], - var_lower) - end - # from large to upper element - if l2_mortars || dg.mortar.mortar_weights[i, j, 2] > 0 - var_min[indices_small_inner..., upper_element] = min(var_min[indices_small_inner..., - upper_element], - var_large) - var_max[indices_small_inner..., upper_element] = max(var_max[indices_small_inner..., - upper_element], - var_large) - end - # from upper to large element - if l2_mortars || dg.mortar.mortar_weights[j, i, 2] > 0 - var_min[indices_large_inner..., large_element] = min(var_min[indices_large_inner..., - large_element], - var_upper) - var_max[indices_large_inner..., large_element] = max(var_max[indices_large_inner..., - large_element], - var_upper) + for small_element_index in 1:2 + small_element = cache.mortars.neighbor_ids[small_element_index, mortar] + # from large to small element + if l2_mortars || dg.mortar.mortar_weights[i, j, small_element_index] > 0 + var_min[indices_small_inner..., small_element] = min(var_min[indices_small_inner..., + small_element], + var_large) + var_max[indices_small_inner..., small_element] = max(var_max[indices_small_inner..., + small_element], + var_large) + end + # from small to large element + if l2_mortars || dg.mortar.mortar_weights[j, i, small_element_index] > 0 + var_min[indices_large_inner..., large_element] = min(var_min[indices_large_inner..., + large_element], + var_small[small_element_index]) + var_max[indices_large_inner..., large_element] = max(var_max[indices_large_inner..., + large_element], + var_small[small_element_index]) + end end end end From 2fb7bb830e9b007007f308409faf136cf8a3f336 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 14 Oct 2025 17:16:33 +0200 Subject: [PATCH 085/102] Refactor `calc_mortar_flux_low_order!` --- .../dgsem_tree/dg_2d_subcell_limiters.jl | 164 +++++++----------- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 15 +- 2 files changed, 69 insertions(+), 110 deletions(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index df7541ca09b..deb158d2397 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -879,64 +879,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, direction_small = 3 direction_large = 4 end - - surface_flux_values[:, :, direction_small, lower_element] .= zero(eltype(surface_flux_values)) - surface_flux_values[:, :, direction_small, upper_element] .= zero(eltype(surface_flux_values)) - surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) - # Lower element - for i in eachnode(dg) - _, u_lower_local = get_surface_node_vars(u_lower, equations, dg, - i, mortar) # u_rr - for j in eachnode(dg) - u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll - - flux = surface_flux(u_large_local, u_lower_local, orientation, - equations) - - factor = mortar_weights[j, i, 1] - if !isapprox(factor, zero(typeof(factor))) - # Lower element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[i, 1], - flux, equations, dg, - i, direction_small, lower_element) - # Large element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[j, 3], - flux, equations, dg, - j, direction_large, large_element) - end - end - end - # Upper element - for i in eachnode(dg) - _, u_upper_local = get_surface_node_vars(u_upper, equations, dg, - i, mortar) # u_rr - for j in eachnode(dg) - u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_ll - - flux = surface_flux(u_large_local, u_upper_local, orientation, - equations) - - factor = mortar_weights[j, i, 2] - if !isapprox(factor, zero(typeof(factor))) - # Upper element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[i, 2], - flux, equations, dg, - i, direction_small, upper_element) - # Large element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[j, 3], - flux, equations, dg, - j, direction_large, large_element) - end - end - end + small_side = 2 else # large_sides[mortar] == 2 -> small elements on left side if orientation == 1 # L2 mortars in x-direction @@ -947,62 +890,73 @@ function calc_mortar_flux_low_order!(surface_flux_values, direction_small = 4 direction_large = 3 end + small_side = 1 + end - surface_flux_values[:, :, direction_small, lower_element] .= zero(eltype(surface_flux_values)) - surface_flux_values[:, :, direction_small, upper_element] .= zero(eltype(surface_flux_values)) - surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) - # Lower element - for i in eachnode(dg) - u_lower_local, _ = get_surface_node_vars(u_lower, equations, dg, - i, mortar) # u_ll - for j in eachnode(dg) - u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr - + surface_flux_values[:, :, direction_small, lower_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_small, upper_element] .= zero(eltype(surface_flux_values)) + surface_flux_values[:, :, direction_large, large_element] .= zero(eltype(surface_flux_values)) + # Lower element + for i in eachnode(dg) + u_lower_local = get_surface_node_vars(u_lower, equations, dg, + i, mortar)[small_side] + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) + + if small_side == 2 # -> small elements on right side + flux = surface_flux(u_large_local, u_lower_local, orientation, + equations) + else # small_side == 1 -> small elements on left side flux = surface_flux(u_lower_local, u_large_local, orientation, equations) + end - factor = mortar_weights[j, i, 1] - if !isapprox(factor, zero(typeof(factor))) - # Lower element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[i, 1], - flux, equations, dg, - i, direction_small, lower_element) - # Large element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[j, 3], - flux, equations, dg, - j, direction_large, large_element) - end + factor = mortar_weights[j, i, 1] + if !isapprox(factor, zero(typeof(factor))) + # Lower element + multiply_add_to_node_vars!(surface_flux_values, + factor / + mortar_weights_sums[i, 1], + flux, equations, dg, + i, direction_small, lower_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + mortar_weights_sums[j, 3], + flux, equations, dg, + j, direction_large, large_element) end end - # Upper element - for i in eachnode(dg) - u_upper_local, _ = get_surface_node_vars(u_upper, equations, dg, - i, mortar) # u_ll - for j in eachnode(dg) - u_large_local = get_node_vars(u_large, equations, dg, j, mortar) # u_rr - + end + # Upper element + for i in eachnode(dg) + u_upper_local = get_surface_node_vars(u_upper, equations, dg, + i, mortar)[small_side] + for j in eachnode(dg) + u_large_local = get_node_vars(u_large, equations, dg, j, mortar) + + if small_side == 2 # -> small elements on right side + flux = surface_flux(u_large_local, u_upper_local, orientation, + equations) + else # small_side == 1 -> small elements on left side flux = surface_flux(u_upper_local, u_large_local, orientation, equations) + end - factor = mortar_weights[j, i, 2] - if !isapprox(factor, zero(typeof(factor))) - # Upper element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[i, 2], - flux, equations, dg, - i, direction_small, upper_element) - # Large element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[j, 3], - flux, equations, dg, - j, direction_large, large_element) - end + factor = mortar_weights[j, i, 2] + if !isapprox(factor, zero(typeof(factor))) + # Upper element + multiply_add_to_node_vars!(surface_flux_values, + factor / + mortar_weights_sums[i, 2], + flux, equations, dg, + i, direction_small, upper_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + mortar_weights_sums[j, 3], + flux, equations, dg, + j, direction_large, large_element) end end end diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index eb138aaf8ed..7257cd6cf36 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -116,8 +116,10 @@ end end end # Get solution data - var_small = (u[variable, indices_small..., cache.mortars.neighbor_ids[1, mortar]], - u[variable, indices_small..., cache.mortars.neighbor_ids[2, mortar]]) + var_small = (u[variable, indices_small..., + cache.mortars.neighbor_ids[1, mortar]], + u[variable, indices_small..., + cache.mortars.neighbor_ids[2, mortar]]) var_large = u[variable, indices_large..., large_element] for j in eachnode(dg) @@ -144,9 +146,11 @@ end end for small_element_index in 1:2 - small_element = cache.mortars.neighbor_ids[small_element_index, mortar] + small_element = cache.mortars.neighbor_ids[small_element_index, + mortar] # from large to small element - if l2_mortars || dg.mortar.mortar_weights[i, j, small_element_index] > 0 + if l2_mortars || + dg.mortar.mortar_weights[i, j, small_element_index] > 0 var_min[indices_small_inner..., small_element] = min(var_min[indices_small_inner..., small_element], var_large) @@ -155,7 +159,8 @@ end var_large) end # from small to large element - if l2_mortars || dg.mortar.mortar_weights[j, i, small_element_index] > 0 + if l2_mortars || + dg.mortar.mortar_weights[j, i, small_element_index] > 0 var_min[indices_large_inner..., large_element] = min(var_min[indices_large_inner..., large_element], var_small[small_element_index]) From ea1e116b546b54fb383c64abfe15a16844d6348d Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 15 Oct 2025 11:26:29 +0200 Subject: [PATCH 086/102] Add equations to mortar --- ...ler_kelvin_helmholtz_instability_amr_sc_subcell.jl | 2 +- src/solvers/dgsem/basis_lobatto_legendre.jl | 11 +++++++---- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 8 +++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl index b1e15013cb2..934e33ba466 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl @@ -42,7 +42,7 @@ limiter_idp = SubcellLimiterIDP(equations, basis; volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -mortar = MortarIDP(basis; positivity_variables_cons = [1]) +mortar = MortarIDP(equations, basis; positivity_variables_cons = ["rho"]) solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index afaec73a2ee..07a7ed9f7f7 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -250,16 +250,19 @@ struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: mortar_weights_sums::Array{RealT, 2} # [node, left/right/large element] end -function MortarIDP(basis::LobattoLegendreBasis; - positivity_variables_cons = Int[]) # TODO: String[], "rho" instead of 1 +function MortarIDP(equations, basis::LobattoLegendreBasis; + positivity_variables_cons = String[]) RealT = real(basis) nnodes_ = nnodes(basis) mortar_l2 = MortarL2(basis) - mortar_weights, mortar_weights_sums = calc_mortar_weights(basis, RealT) + mortar_weights, mortar_weights_sums = calc_mortar_weights(equations, basis, RealT) - LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(positivity_variables_cons, + positivity_variables_cons_ = get_variable_index.(positivity_variables_cons, + equations) + + LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(positivity_variables_cons_, mortar_l2, mortar_weights, mortar_weights_sums) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index deb158d2397..41ba75feef2 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -59,12 +59,13 @@ function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, flux_temp_threaded, fhat_temp_threaded) end -function calc_mortar_weights(basis, RealT) +function calc_mortar_weights(equations::AbstractEquations{2}, + basis::LobattoLegendreBasis, RealT) n_nodes = nnodes(basis) mortar_weights = zeros(RealT, n_nodes, n_nodes, 2) # [node (large element), node (small element), small element] mortar_weights_sums = zeros(RealT, n_nodes, 3) # [node, left/right/large element] - calc_mortar_weights!(mortar_weights, n_nodes, RealT) + calc_mortar_weights!(equations, mortar_weights, n_nodes, RealT) # Sums of mortar weights for normalization for i in eachnode(basis), j in eachnode(basis) @@ -77,7 +78,8 @@ function calc_mortar_weights(basis, RealT) return mortar_weights, mortar_weights_sums end -function calc_mortar_weights!(mortar_weights, n_nodes, RealT) +function calc_mortar_weights!(equations::AbstractEquations{2}, + mortar_weights, n_nodes, RealT) _, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) # Local mortar weights are of the form: `w_ij = int_S psi_i phi_j ds`, From 251c1776f2b491210a1fdeb3cead2650c462c406 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 15 Oct 2025 11:26:47 +0200 Subject: [PATCH 087/102] Add comment about type instability --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 7257cd6cf36..c25ca6e3702 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -120,6 +120,13 @@ end cache.mortars.neighbor_ids[1, mortar]], u[variable, indices_small..., cache.mortars.neighbor_ids[2, mortar]]) + # Using the following version with `ntuple` creates allocations due to a type instability of `indices_small`. + # var_small = index -> u[variable, indices_small..., cache.mortars.neighbor_ids[index, mortar]] + # Theoretically, that could be fixed with the following version: + # f = let indices_small = indices_small + # index -> u[variable, indices_small..., cache.mortars.neighbor_ids[index, mortar]] + # end + # var_small = ntuple(f, Val(2)) var_large = u[variable, indices_large..., large_element] for j in eachnode(dg) From 31bad1d19ffaec22bf48af694763edeb416434a8 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 15 Oct 2025 16:58:34 +0200 Subject: [PATCH 088/102] Simplify computation of minimal value at mortar --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index c25ca6e3702..7dc05faa0cb 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -539,7 +539,7 @@ end mesh::TreeMesh{2}, var_index) _, _, dg, cache = mesh_equations_solver_cache(semi) - (; orientations) = cache.mortars + (; orientations, large_sides, u_large, u_upper, u_lower) = cache.mortars (; surface_flux_values) = cache.elements (; surface_flux_values_high_order) = cache.antidiffusive_fluxes (; boundary_interpolation) = dg.basis @@ -556,30 +556,12 @@ end var_min_lower = typemax(eltype(surface_flux_values)) var_min_large = typemax(eltype(surface_flux_values)) for i in eachnode(dg) - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (1, i) - indices_large = (nnodes(dg), i) - else - # L2 mortars in y-direction - indices_small = (i, 1) - indices_large = (i, nnodes(dg)) - end - else # large_sides[mortar] == 2 -> small elements on left side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (nnodes(dg), i) - indices_large = (1, i) - else - # L2 mortars in y-direction - indices_small = (i, nnodes(dg)) - indices_large = (i, 1) - end - end - var_upper = u[var_index, indices_small..., upper_element] - var_lower = u[var_index, indices_small..., lower_element] - var_large = u[var_index, indices_large..., large_element] + # Get small side of mortar + small_side = large_sides[mortar] == 1 ? 2 : 1 + + var_upper = u_upper[small_side, var_index, i, mortar] + var_lower = u_lower[small_side, var_index, i, mortar] + var_large = u_large[var_index, i, mortar] var_min_upper = min(var_min_upper, var_upper) var_min_lower = min(var_min_lower, var_lower) var_min_large = min(var_min_large, var_large) From 482260863e0f89025a958c2d8511db4d30e9f152 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Wed, 15 Oct 2025 17:22:35 +0200 Subject: [PATCH 089/102] Refactor computation of limiting factor --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 122 ++++++++++-------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 7dc05faa0cb..8039c95933c 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -551,7 +551,7 @@ end upper_element = cache.mortars.neighbor_ids[2, mortar] lower_element = cache.mortars.neighbor_ids[1, mortar] - # Calc minimal low-order solution + # Compute minimal bound var_min_upper = typemax(eltype(surface_flux_values)) var_min_lower = typemax(eltype(surface_flux_values)) var_min_large = typemax(eltype(surface_flux_values)) @@ -570,61 +570,44 @@ end var_min_lower = positivity_correction_factor * var_min_lower var_min_large = positivity_correction_factor * var_min_large - for i in eachnode(dg) - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (1, i) - indices_large = (nnodes(dg), i) - direction_small = 1 - direction_large = 2 - else - # L2 mortars in y-direction - indices_small = (i, 1) - indices_large = (i, nnodes(dg)) - direction_small = 3 - direction_large = 4 - end - factor_small = boundary_interpolation[1, 1] - factor_large = -boundary_interpolation[nnodes(dg), 2] - else # large_sides[mortar] == 2 -> small elements on left side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (nnodes(dg), i) - indices_large = (1, i) - direction_small = 2 - direction_large = 1 - else - # L2 mortars in y-direction - indices_small = (i, nnodes(dg)) - indices_large = (i, 1) - direction_small = 4 - direction_large = 3 - end - factor_large = boundary_interpolation[1, 1] - factor_small = -boundary_interpolation[nnodes(dg), 2] + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + direction_small = 1 + direction_large = 2 + else + direction_small = 3 + direction_large = 4 + end + # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. + # This sign switch is directly applied to the boundary interpolation factors here. + factor_small = boundary_interpolation[1, 1] + factor_large = -boundary_interpolation[nnodes(dg), 2] + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + direction_small = 2 + direction_large = 1 + else + direction_small = 4 + direction_large = 3 end # In `apply_jacobian`, `du` is multiplied with inverse jacobian and a negative sign. # This sign switch is directly applied to the boundary interpolation factors here. + factor_large = boundary_interpolation[1, 1] + factor_small = -boundary_interpolation[nnodes(dg), 2] + end + + for i in eachnode(dg) + # Get small side of mortar + small_side = large_sides[mortar] == 1 ? 2 : 1 - var_upper = u[var_index, indices_small..., upper_element] - var_lower = u[var_index, indices_small..., lower_element] - var_large = u[var_index, indices_large..., large_element] + var_upper = u_upper[small_side, var_index, i, mortar] + var_lower = u_lower[small_side, var_index, i, mortar] + var_large = u_large[var_index, i, mortar] if min(var_upper, var_lower, var_large) < 0 error("Safe low-order method produces negative value for conservative variable rho. Try a smaller time step.") end - inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., - upper_element) - inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., - lower_element) - inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_large..., - large_element) - # Calculate Pm flux_lower_high_order = surface_flux_values_high_order[var_index, i, direction_small, @@ -651,14 +634,50 @@ end flux_difference_large = factor_large * (flux_large_high_order - flux_large_low_order) - # Check if high-order fluxes are finite. Otherwise, use pure low-order fluxes. + # Use pure low-order fluxes if high-order fluxes are not finite. if !all(isfinite.(flux_lower_high_order)) || - !all(isfinite(flux_upper_high_order)) || + !all(isfinite.(flux_upper_high_order)) || !all(isfinite.(flux_large_high_order)) limiting_factor[mortar] = 1 break end + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + end + end + inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + upper_element) + inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_small..., + lower_element) + inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, + mesh, indices_large..., + large_element) + + # Real one-sided Zalesak-type limiter + # * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids" + # * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics" + # Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is + # for each mortar, not each node Qm_upper = min(0, var_min_upper - var_upper) Qm_lower = min(0, var_min_lower - var_lower) Qm_large = min(0, var_min_large - var_large) @@ -677,8 +696,9 @@ end Qm_lower = abs(Qm_lower) / (abs(Pm_lower) + eps(typeof(Qm_lower)) * 100) Qm_large = abs(Qm_large) / (abs(Pm_large) + eps(typeof(Qm_large)) * 100) - limiting_factor[mortar] = max(limiting_factor[mortar], 1 - Qm_upper, - 1 - Qm_lower, 1 - Qm_large) + # Calculate limiting factor + limiting_factor[mortar] = max(limiting_factor[mortar], + 1 - Qm_upper, 1 - Qm_lower, 1 - Qm_large) end end From 96920b07e17c786ca8aa93bc6414e3f1c712593b Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 16 Oct 2025 10:54:08 +0200 Subject: [PATCH 090/102] Shorten code --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 8039c95933c..4c5a8c39b23 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -540,7 +540,7 @@ end _, _, dg, cache = mesh_equations_solver_cache(semi) (; orientations, large_sides, u_large, u_upper, u_lower) = cache.mortars - (; surface_flux_values) = cache.elements + (; surface_flux_values, inverse_jacobian) = cache.elements (; surface_flux_values_high_order) = cache.antidiffusive_fluxes (; boundary_interpolation) = dg.basis @@ -551,14 +551,14 @@ end upper_element = cache.mortars.neighbor_ids[2, mortar] lower_element = cache.mortars.neighbor_ids[1, mortar] + # Get small side of mortar + small_side = large_sides[mortar] == 1 ? 2 : 1 + # Compute minimal bound var_min_upper = typemax(eltype(surface_flux_values)) var_min_lower = typemax(eltype(surface_flux_values)) var_min_large = typemax(eltype(surface_flux_values)) for i in eachnode(dg) - # Get small side of mortar - small_side = large_sides[mortar] == 1 ? 2 : 1 - var_upper = u_upper[small_side, var_index, i, mortar] var_lower = u_lower[small_side, var_index, i, mortar] var_large = u_large[var_index, i, mortar] @@ -570,6 +570,7 @@ end var_min_lower = positivity_correction_factor * var_min_lower var_min_large = positivity_correction_factor * var_min_large + # Compute limiting factor if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side if orientations[mortar] == 1 direction_small = 1 @@ -597,9 +598,6 @@ end end for i in eachnode(dg) - # Get small side of mortar - small_side = large_sides[mortar] == 1 ? 2 : 1 - var_upper = u_upper[small_side, var_index, i, mortar] var_lower = u_lower[small_side, var_index, i, mortar] var_large = u_large[var_index, i, mortar] @@ -663,14 +661,14 @@ end indices_large = (i, 1) end end - inverse_jacobian_upper = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., + inverse_jacobian_upper = get_inverse_jacobian(inverse_jacobian, mesh, + indices_small..., upper_element) - inverse_jacobian_lower = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_small..., + inverse_jacobian_lower = get_inverse_jacobian(inverse_jacobian, mesh, + indices_small..., lower_element) - inverse_jacobian_large = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, indices_large..., + inverse_jacobian_large = get_inverse_jacobian(inverse_jacobian, mesh, + indices_large..., large_element) # Real one-sided Zalesak-type limiter From 50cba3caeae8e7817151134eca21063e53e431b3 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 16 Oct 2025 11:18:40 +0200 Subject: [PATCH 091/102] Update comments --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 4c5a8c39b23..c257739ff92 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -570,7 +570,7 @@ end var_min_lower = positivity_correction_factor * var_min_lower var_min_large = positivity_correction_factor * var_min_large - # Compute limiting factor + # Set up correct direction and factors if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side if orientations[mortar] == 1 direction_small = 1 @@ -597,6 +597,7 @@ end factor_small = -boundary_interpolation[nnodes(dg), 2] end + # Compute limiting factor for i in eachnode(dg) var_upper = u_upper[small_side, var_index, i, mortar] var_lower = u_lower[small_side, var_index, i, mortar] @@ -606,7 +607,7 @@ end error("Safe low-order method produces negative value for conservative variable rho. Try a smaller time step.") end - # Calculate Pm + # Compute flux differences flux_lower_high_order = surface_flux_values_high_order[var_index, i, direction_small, lower_element] From b4faba2c6be223b0fcf0b5cfa4f72239c0bda8fc Mon Sep 17 00:00:00 2001 From: bennibolm Date: Thu, 16 Oct 2025 18:48:03 +0200 Subject: [PATCH 092/102] Fix bug --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index c257739ff92..afc93dc5fcc 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -571,7 +571,7 @@ end var_min_large = positivity_correction_factor * var_min_large # Set up correct direction and factors - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if large_sides[mortar] == 1 # -> small elements on right side if orientations[mortar] == 1 direction_small = 1 direction_large = 2 @@ -627,21 +627,20 @@ end flux_large_high_order = surface_flux_values_high_order[var_index, i, direction_large, large_element] - flux_large_low_order = surface_flux_values_high_order[var_index, i, - direction_large, - large_element] + flux_large_low_order = surface_flux_values[var_index, i, direction_large, + large_element] flux_difference_large = factor_large * (flux_large_high_order - flux_large_low_order) # Use pure low-order fluxes if high-order fluxes are not finite. - if !all(isfinite.(flux_lower_high_order)) || - !all(isfinite.(flux_upper_high_order)) || - !all(isfinite.(flux_large_high_order)) + if !isfinite(flux_lower_high_order) || + !isfinite(flux_upper_high_order) || + !isfinite(flux_large_high_order) limiting_factor[mortar] = 1 break end - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if large_sides[mortar] == 1 # -> small elements on right side if orientations[mortar] == 1 # L2 mortars in x-direction indices_small = (1, i) From c32f71cb002f5cc9a57a9bc49926ac4cea5ede22 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 17 Oct 2025 11:10:50 +0200 Subject: [PATCH 093/102] Fix error --- src/solvers/dgsem_tree/subcell_limiters_2d.jl | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index afc93dc5fcc..ad93d4e7b73 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -559,9 +559,30 @@ end var_min_lower = typemax(eltype(surface_flux_values)) var_min_large = typemax(eltype(surface_flux_values)) for i in eachnode(dg) - var_upper = u_upper[small_side, var_index, i, mortar] - var_lower = u_lower[small_side, var_index, i, mortar] - var_large = u_large[var_index, i, mortar] + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + end + end + var_upper = u[var_index, indices_small..., upper_element] + var_lower = u[var_index, indices_small..., lower_element] + var_large = u[var_index, indices_large..., large_element] var_min_upper = min(var_min_upper, var_upper) var_min_lower = min(var_min_lower, var_lower) var_min_large = min(var_min_large, var_large) @@ -599,9 +620,30 @@ end # Compute limiting factor for i in eachnode(dg) - var_upper = u_upper[small_side, var_index, i, mortar] - var_lower = u_lower[small_side, var_index, i, mortar] - var_large = u_large[var_index, i, mortar] + if large_sides[mortar] == 1 # -> small elements on right side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (1, i) + indices_large = (nnodes(dg), i) + else + # L2 mortars in y-direction + indices_small = (i, 1) + indices_large = (i, nnodes(dg)) + end + else # large_sides[mortar] == 2 -> small elements on left side + if orientations[mortar] == 1 + # L2 mortars in x-direction + indices_small = (nnodes(dg), i) + indices_large = (1, i) + else + # L2 mortars in y-direction + indices_small = (i, nnodes(dg)) + indices_large = (i, 1) + end + end + var_upper = u[var_index, indices_small..., upper_element] + var_lower = u[var_index, indices_small..., lower_element] + var_large = u[var_index, indices_large..., large_element] if min(var_upper, var_lower, var_large) < 0 error("Safe low-order method produces negative value for conservative variable rho. Try a smaller time step.") @@ -640,27 +682,6 @@ end break end - if large_sides[mortar] == 1 # -> small elements on right side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (1, i) - indices_large = (nnodes(dg), i) - else - # L2 mortars in y-direction - indices_small = (i, 1) - indices_large = (i, nnodes(dg)) - end - else # large_sides[mortar] == 2 -> small elements on left side - if orientations[mortar] == 1 - # L2 mortars in x-direction - indices_small = (nnodes(dg), i) - indices_large = (1, i) - else - # L2 mortars in y-direction - indices_small = (i, nnodes(dg)) - indices_large = (i, 1) - end - end inverse_jacobian_upper = get_inverse_jacobian(inverse_jacobian, mesh, indices_small..., upper_element) From 4d45dfed3dc466abcae9678df3410876e4bca586 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Fri, 17 Oct 2025 18:19:17 +0200 Subject: [PATCH 094/102] Add density wave example --- ..._density_wave_nonconforming_idp_mortars.jl | 76 +++++++++++++++++++ test/test_tree_2d_euler.jl | 26 +++++++ 2 files changed, 102 insertions(+) create mode 100644 examples/tree_2d_dgsem/elixir_euler_density_wave_nonconforming_idp_mortars.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_nonconforming_idp_mortars.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_nonconforming_idp_mortars.jl new file mode 100644 index 00000000000..bd1250c1b20 --- /dev/null +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_nonconforming_idp_mortars.jl @@ -0,0 +1,76 @@ +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations2D(1.4) + +initial_condition = initial_condition_density_wave + +surface_flux = flux_lax_friedrichs +volume_flux = flux_ranocha +polydeg = 5 +basis = LobattoLegendreBasis(polydeg) + +# No limiting is needed in the volume integral +# It is still required to use `VolumeIntegralSubcellLimiting` in order to use `MortarIDP`. +limiter_idp = SubcellLimiterIDP(equations, basis; + positivity_variables_cons = [], + positivity_variables_nonlinear = []) +volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux) +mortar = MortarIDP(equations, basis; + positivity_variables_cons = ["rho"]) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) + +coordinates_min = (-1.0, -1.0) +coordinates_max = (1.0, 1.0) +refinement_patches = ((type = "box", coordinates_min = (-0.5, -0.5), + coordinates_max = (0.0, 1.0)), + (type = "box", coordinates_min = (0.5, -1.0), + coordinates_max = (1.0, 0.5))) +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 3, + refinement_patches = refinement_patches, + n_cells_max = 10_000, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 500 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 500, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim, + extra_node_variables = (:limiting_coefficient,)) + +stepsize_callback = StepsizeCallback(cfl = 0.4) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +stage_callbacks = (SubcellLimiterIDPCorrection(),) + +sol = Trixi.solve(ode, + Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + callback = callbacks); diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index fc287eff8c7..846f57cfc35 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -68,6 +68,32 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end +@trixi_testset "elixir_euler_density_wave_nonconforming_idp_mortars.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "elixir_euler_density_wave_nonconforming_idp_mortars.jl"), + initial_refinement_level=2, + l2=[ + 0.01853467680355577, + 0.0018534676803570467, + 0.003706935360709665, + 0.0004633669200885599 + ], + linf=[ + 0.3234934074357616, + 0.03234934074339482, + 0.06469868148701247, + 0.008087335185543054 + ], + tspan=(0.0, 0.5)) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + # Larger values for allowed allocations due to usage of custom + # integrator which are not *recorded* for the methods from + # OrdinaryDiffEq.jl + # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 + @test_allocations(Trixi.rhs!, semi, sol, 10_000) +end + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic.jl"), From dd25b6a19cbad1b63d49c0f63928782e26f5fe57 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Sat, 18 Oct 2025 11:52:42 +0200 Subject: [PATCH 095/102] Increase allowed allocations --- .../elixir_euler_density_wave_nonconforming_idp_mortars.jl | 4 ++-- test/test_tree_2d_euler.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave_nonconforming_idp_mortars.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave_nonconforming_idp_mortars.jl index bd1250c1b20..2547c7486a5 100644 --- a/examples/tree_2d_dgsem/elixir_euler_density_wave_nonconforming_idp_mortars.jl +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave_nonconforming_idp_mortars.jl @@ -46,13 +46,13 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() -analysis_interval = 500 +analysis_interval = 1000 analysis_callback = AnalysisCallback(semi, interval = analysis_interval, extra_analysis_errors = (:conservation_error,)) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 500, +save_solution = SaveSolutionCallback(interval = 1000, save_initial_solution = true, save_final_solution = true, solution_variables = cons2prim, diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 846f57cfc35..b50e3d8de74 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -91,7 +91,7 @@ end # integrator which are not *recorded* for the methods from # OrdinaryDiffEq.jl # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 - @test_allocations(Trixi.rhs!, semi, sol, 10_000) + @test_allocations(Trixi.rhs!, semi, sol, 15_000) end @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin From c0a68c4782e34e9fe50243797baf3e4fa3bf61ae Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 20 Oct 2025 12:03:43 +0200 Subject: [PATCH 096/102] Remove KHI amr elixir --- ...in_helmholtz_instability_amr_sc_subcell.jl | 107 ------------------ test/test_tree_2d_euler.jl | 29 ----- 2 files changed, 136 deletions(-) delete mode 100644 examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl deleted file mode 100644 index 934e33ba466..00000000000 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl +++ /dev/null @@ -1,107 +0,0 @@ -using Trixi - -############################################################################### -# semidiscretization of the compressible Euler equations -gamma = 1.4 -equations = CompressibleEulerEquations2D(gamma) - -""" - initial_condition_kelvin_helmholtz_instability(x, t, equations::CompressibleEulerEquations2D) - -A version of the classical Kelvin-Helmholtz instability based on -- Andrés M. Rueda-Ramírez, Gregor J. Gassner (2021) - A Subcell Finite Volume Positivity-Preserving Limiter for DGSEM Discretizations - of the Euler Equations - [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) -""" -function initial_condition_kelvin_helmholtz_instability(x, t, - equations::CompressibleEulerEquations2D) - # change discontinuity to tanh - # typical resolution 128^2, 256^2 - # domain size is [-1,+1]^2 - RealT = eltype(x) - slope = 15 - B = tanh(slope * x[2] + 7.5f0) - tanh(slope * x[2] - 7.5f0) - rho = 0.5f0 + 0.75f0 * B - v1 = 0.5f0 * (B - 1) - v2 = convert(RealT, 0.1) * sinpi(2 * x[1]) - p = 1 - return prim2cons(SVector(rho, v1, v2, p), equations) -end -initial_condition = initial_condition_kelvin_helmholtz_instability - -surface_flux = flux_lax_friedrichs -volume_flux = flux_ranocha -polydeg = 3 -basis = LobattoLegendreBasis(polydeg) - -limiter_idp = SubcellLimiterIDP(equations, basis; - positivity_variables_cons = ["rho"], - positivity_variables_nonlinear = [pressure], - local_twosided_variables_cons = []) # required for the tests -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -mortar = MortarIDP(equations, basis; positivity_variables_cons = ["rho"]) -solver = DGSEM(basis, surface_flux, volume_integral, mortar) - -coordinates_min = (-1.0, -1.0) -coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 100_000) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) - -############################################################################### -# ODE solvers, callbacks etc. - -tspan = (0.0, 3.7) -ode = semidiscretize(semi, tspan) - -summary_callback = SummaryCallback() - -analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - -alive_callback = AliveCallback(analysis_interval = analysis_interval) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim, - extra_node_variables = (:limiting_coefficient,)) - -save_restart = SaveRestartCallback(interval = 1000, - save_final_restart = true) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 1.0, - alpha_min = 0.0001, - alpha_smooth = false, - variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - med_level = 0, med_threshold = 0.0003, - max_level = 6, max_threshold = 0.003) -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -stepsize_callback = StepsizeCallback(cfl = 0.3) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, save_restart, - amr_callback, - stepsize_callback) - -############################################################################### -# run the simulation - -stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) - -sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - ode_default_options()..., - callback = callbacks); diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index b50e3d8de74..1e2499ea726 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -591,35 +591,6 @@ end @test_allocations(Trixi.rhs!, semi, sol, 15000) end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl"), - local_twosided_variables_cons=["rho"], - l2=[ - 0.05564752104857987, - 0.033057554616374794, - 0.0505245765658428, - 0.07943927352867723 - ], - linf=[ - 0.2531979194848659, - 0.17306280705055144, - 0.14221629583696524, - 0.26721736720894773 - ], - tspan=(0.0, 0.2)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - # Larger values for allowed allocations due to usage of custom - # integrator which are not *recorded* for the methods from - # OrdinaryDiffEq.jl - # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 - @test_allocations(Trixi.rhs!, semi, sol, 15000) - - # test long printing format - @test_nowarn display(solver.mortar) -end - @trixi_testset "elixir_euler_colliding_flow.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_colliding_flow.jl"), l2=[ From 56739735a2daf5247a251718a45dd8960c8d0aa8 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 20 Oct 2025 12:14:40 +0200 Subject: [PATCH 097/102] Add AMR to existing KHI elixir; Apply local limiting in test --- ...n_helmholtz_instability_amr_sc_subcell.jl} | 35 ++++++++++++------- test/test_tree_2d_euler.jl | 30 ++++++++-------- 2 files changed, 38 insertions(+), 27 deletions(-) rename examples/tree_2d_dgsem/{elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl => elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl} (74%) diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl similarity index 74% rename from examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl rename to examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl index 5d47dec8f96..5bbd268f28b 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl @@ -30,25 +30,20 @@ function initial_condition_kelvin_helmholtz_instability(x, t, end initial_condition = initial_condition_kelvin_helmholtz_instability -# Up to version 0.13.0, `max_abs_speed_naive` was used as the default wave speed estimate of -# `const flux_lax_friedrichs = FluxLaxFriedrichs(), i.e., `FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive)`. -# In the `StepsizeCallback`, though, the less diffusive `max_abs_speeds` is employed which is consistent with `max_abs_speed`. -# Thus, we exchanged in PR#2458 the default wave speed used in the LLF flux to `max_abs_speed`. -# To ensure that every example still runs we specify explicitly `FluxLaxFriedrichs(max_abs_speed_naive)`. -# We remark, however, that the now default `max_abs_speed` is in general recommended due to compliance with the -# `StepsizeCallback` (CFL-Condition) and less diffusion. -surface_flux = FluxLaxFriedrichs(max_abs_speed_naive) +surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) limiter_idp = SubcellLimiterIDP(equations, basis; positivity_variables_cons = ["rho"], - positivity_variables_nonlinear = [pressure]) + positivity_variables_nonlinear = [pressure], + local_twosided_variables_cons = []) # required for testing volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; volume_flux_dg = volume_flux, volume_flux_fv = surface_flux) -solver = DGSEM(basis, surface_flux, volume_integral) +mortar = MortarIDP(equations, basis; positivity_variables_cons = ["rho"]) +solver = DGSEM(basis, surface_flux, volume_integral, mortar) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) @@ -79,12 +74,27 @@ save_solution = SaveSolutionCallback(interval = 100, save_restart = SaveRestartCallback(interval = 1000, save_final_restart = true) +amr_indicator = IndicatorHennemannGassner(semi, + alpha_max = 1.0, + alpha_min = 0.0001, + alpha_smooth = false, + variable = Trixi.density) +amr_controller = ControllerThreeLevel(semi, amr_indicator, + base_level = 4, + med_level = 0, med_threshold = 0.0003, + max_level = 6, max_threshold = 0.003) +amr_callback = AMRCallback(semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true) + stepsize_callback = StepsizeCallback(cfl = 0.7) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - stepsize_callback, - save_restart, save_solution) + amr_callback, + save_restart, save_solution, + stepsize_callback) ############################################################################### # run the simulation @@ -95,5 +105,4 @@ stage_callbacks = (SubcellLimiterIDPCorrection(), sol = Trixi.solve(ode, Trixi.SimpleSSPRK33(stage_callbacks = stage_callbacks); dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - ode_default_options()..., callback = callbacks); diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 1e2499ea726..a9bfaffb4c2 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -560,28 +560,30 @@ end @test_allocations(Trixi.rhs!, semi, sol, 1000) end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl" begin +@trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl" begin rm(joinpath("out", "deviations.txt"), force = true) @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl"), + "elixir_euler_kelvin_helmholtz_instability_amr_sc_subcell.jl"), + local_twosided_variables_cons=["rho"], + cfl=0.5, l2=[ - 0.42185634563805724, - 0.1686471269704017, - 0.18240674916968103, - 0.17858250604280654 + 0.19666933487754049, + 0.09831853671379577, + 0.14983625881083906, + 0.07468992916864159 ], linf=[ - 1.7012978064377158, - 0.7149714986746726, - 0.5822547982757897, - 0.7300051017382696 + 0.9092699501192213, + 0.48228677149305965, + 0.3547206476201268, + 0.34425987695821103 ], - tspan=(0.0, 2.0), + tspan=(0.0, 1.0), save_errors=true) lines = readlines(joinpath("out", "deviations.txt")) - @test lines[1] == "# iter, simu_time, rho_min, pressure_min" - # Run takes 745 time steps - @test startswith(lines[end], "745") + @test lines[1] == "# iter, simu_time, rho_min, rho_max, pressure_min" + # Run takes 795 time steps + @test startswith(lines[end], "795") # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) # Larger values for allowed allocations due to usage of custom From 01aed6bb9164b6881d5f5f05a165f4b95f8a2553 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 20 Oct 2025 12:44:24 +0200 Subject: [PATCH 098/102] Adapt numbers in test --- test/test_tree_2d_euler.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index a9bfaffb4c2..5cc2994ba9d 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -567,16 +567,16 @@ end local_twosided_variables_cons=["rho"], cfl=0.5, l2=[ - 0.19666933487754049, - 0.09831853671379577, - 0.14983625881083906, - 0.07468992916864159 + 0.19657723128273924, + 0.09819990707800491, + 0.14969902881019737, + 0.07474283620648917 ], linf=[ - 0.9092699501192213, - 0.48228677149305965, - 0.3547206476201268, - 0.34425987695821103 + 0.9087650823657859, + 0.48203285905738114, + 0.35330695561397685, + 0.3443844432700125 ], tspan=(0.0, 1.0), save_errors=true) From cf66623539ec4c08cb8e23f1fe6ad2188b6949c2 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Mon, 20 Oct 2025 13:32:05 +0200 Subject: [PATCH 099/102] Add test for output --- test/test_tree_2d_euler.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index 5cc2994ba9d..8fc43209fa3 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -591,6 +591,9 @@ end # OrdinaryDiffEq.jl # Corresponding issue: https://github.com/trixi-framework/Trixi.jl/issues/1877 @test_allocations(Trixi.rhs!, semi, sol, 15000) + + # test long printing format + @test_nowarn display(solver.mortar) end @trixi_testset "elixir_euler_colliding_flow.jl" begin From 2e106fe77d38bc429c7c07ed538e2ee25935f237 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 28 Oct 2025 11:00:01 +0100 Subject: [PATCH 100/102] Implement suggestions --- src/solvers/dgsem/basis_lobatto_legendre.jl | 17 ++-- .../dgsem_tree/dg_2d_subcell_limiters.jl | 90 ++++++++++--------- 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index 07a7ed9f7f7..e88ed05fc47 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -242,17 +242,19 @@ end @inline polydeg(mortar::LobattoLegendreMortarL2) = nnodes(mortar) - 1 -struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, Mortar} <: +struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, NDIMS, LENGTH, Mortar} <: AbstractMortar{RealT} positivity_variables_cons::Vector{Int} mortar_l2::Mortar - mortar_weights::Array{RealT, 3} # [node (large element), node (small element), small element] - mortar_weights_sums::Array{RealT, 2} # [node, left/right/large element] + # LENGTH = `2 * (NDIMS - 1) + 1` + mortar_weights::Array{RealT, LENGTH} # [node_i (large), node_j (large), node_i (small), node_j (small), small_element] + mortar_weights_sums::Array{RealT, NDIMS} # [node_i, node_j, position] end function MortarIDP(equations, basis::LobattoLegendreBasis; positivity_variables_cons = String[]) RealT = real(basis) + n_dims = ndims(equations) nnodes_ = nnodes(basis) mortar_l2 = MortarL2(basis) @@ -262,10 +264,11 @@ function MortarIDP(equations, basis::LobattoLegendreBasis; positivity_variables_cons_ = get_variable_index.(positivity_variables_cons, equations) - LobattoLegendreMortarIDP{RealT, nnodes_, typeof(mortar_l2)}(positivity_variables_cons_, - mortar_l2, - mortar_weights, - mortar_weights_sums) + LobattoLegendreMortarIDP{RealT, nnodes_, n_dims, + 2 * (n_dims - 1) + 1, typeof(mortar_l2)}(positivity_variables_cons_, + mortar_l2, + mortar_weights, + mortar_weights_sums) end function Base.show(io::IO, mortar::LobattoLegendreMortarIDP) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index fd19b6ea99e..69046f235ca 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -74,11 +74,17 @@ function calc_mortar_weights(equations::AbstractEquations{2}, calc_mortar_weights!(equations, mortar_weights, n_nodes, RealT) # Sums of mortar weights for normalization - for i in eachnode(basis), j in eachnode(basis) - mortar_weights_sums[i, 1] += mortar_weights[j, i, 1] # left element - mortar_weights_sums[i, 2] += mortar_weights[j, i, 2] # right element - mortar_weights_sums[i, 3] += mortar_weights[i, j, 1] # large element - mortar_weights_sums[i, 3] += mortar_weights[i, j, 2] # large element + for small_element in 1:2 + for i in eachnode(basis) + for k in eachnode(basis) + # Add weights from large element to small element + mortar_weights_sums[i, small_element] += mortar_weights[k, i, + small_element] + # Add weights from small element to large element + mortar_weights_sums[i, 3] += mortar_weights[i, k, + small_element] + end + end end return mortar_weights, mortar_weights_sums @@ -89,8 +95,8 @@ function calc_mortar_weights!(equations::AbstractEquations{2}, _, weights = gauss_lobatto_nodes_weights(n_nodes, RealT) # Local mortar weights are of the form: `w_ij = int_S psi_i phi_j ds`, - # where `phi_i` are the basis functions of the small element and `psi_j` are the basis functions - # of the large element. `S` is the face connecting both elements. + # where `psi_i` are the basis functions of the large element and `phi_j` are the basis + # functions of the small element. `S` is the face connecting both elements. # We use piecewise constant basis functions on the LGL subgrid. So, only focus on interval, # where both basis functions are non-zero. `interval = [left_bound, right_bound]`. # `w_ij = int_S psi_i phi_j ds = int_{left_bound}^{right_bound} ds = right_bound - left_bound`. @@ -908,8 +914,12 @@ function calc_mortar_flux_low_order!(surface_flux_values, for i in eachnode(dg) u_lower_local = get_surface_node_vars(u_lower, equations, dg, i, mortar)[small_side] - for j in eachnode(dg) - u_large_local = get_node_vars(u_large, equations, dg, j, mortar) + for k in eachnode(dg) + factor = mortar_weights[k, i, 1] + if isapprox(factor, zero(typeof(factor))) + continue + end + u_large_local = get_node_vars(u_large, equations, dg, k, mortar) if small_side == 2 # -> small elements on right side flux = surface_flux(u_large_local, u_lower_local, orientation, @@ -919,29 +929,30 @@ function calc_mortar_flux_low_order!(surface_flux_values, equations) end - factor = mortar_weights[j, i, 1] - if !isapprox(factor, zero(typeof(factor))) - # Lower element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[i, 1], - flux, equations, dg, - i, direction_small, lower_element) - # Large element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[j, 3], - flux, equations, dg, - j, direction_large, large_element) - end + # Lower element + multiply_add_to_node_vars!(surface_flux_values, + factor / + mortar_weights_sums[i, 1], + flux, equations, dg, + i, direction_small, lower_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + mortar_weights_sums[k, 3], + flux, equations, dg, + k, direction_large, large_element) end end # Upper element for i in eachnode(dg) u_upper_local = get_surface_node_vars(u_upper, equations, dg, i, mortar)[small_side] - for j in eachnode(dg) - u_large_local = get_node_vars(u_large, equations, dg, j, mortar) + for k in eachnode(dg) + factor = mortar_weights[k, i, 2] + if !isapprox(factor, zero(typeof(factor))) + continue + end + u_large_local = get_node_vars(u_large, equations, dg, k, mortar) if small_side == 2 # -> small elements on right side flux = surface_flux(u_large_local, u_upper_local, orientation, @@ -951,21 +962,18 @@ function calc_mortar_flux_low_order!(surface_flux_values, equations) end - factor = mortar_weights[j, i, 2] - if !isapprox(factor, zero(typeof(factor))) - # Upper element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[i, 2], - flux, equations, dg, - i, direction_small, upper_element) - # Large element - multiply_add_to_node_vars!(surface_flux_values, - factor / - mortar_weights_sums[j, 3], - flux, equations, dg, - j, direction_large, large_element) - end + # Upper element + multiply_add_to_node_vars!(surface_flux_values, + factor / + mortar_weights_sums[i, 2], + flux, equations, dg, + i, direction_small, upper_element) + # Large element + multiply_add_to_node_vars!(surface_flux_values, + factor / + mortar_weights_sums[k, 3], + flux, equations, dg, + k, direction_large, large_element) end end end From c73b2b4390f44fa82a7813b0c799ca184519cf82 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 28 Oct 2025 13:16:02 +0100 Subject: [PATCH 101/102] Fix bug --- src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index 69046f235ca..ca3260288a1 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -949,7 +949,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, i, mortar)[small_side] for k in eachnode(dg) factor = mortar_weights[k, i, 2] - if !isapprox(factor, zero(typeof(factor))) + if isapprox(factor, zero(typeof(factor))) continue end u_large_local = get_node_vars(u_large, equations, dg, k, mortar) From b05e909826c682c1a08e814ed9ebbf68708c48f4 Mon Sep 17 00:00:00 2001 From: bennibolm Date: Tue, 28 Oct 2025 13:58:36 +0100 Subject: [PATCH 102/102] Simplify sum over weights --- src/solvers/dgsem/basis_lobatto_legendre.jl | 2 +- .../dgsem_tree/dg_2d_subcell_limiters.jl | 27 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index e88ed05fc47..a62b94f7f72 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -248,7 +248,7 @@ struct LobattoLegendreMortarIDP{RealT <: Real, NNODES, NDIMS, LENGTH, Mortar} <: mortar_l2::Mortar # LENGTH = `2 * (NDIMS - 1) + 1` mortar_weights::Array{RealT, LENGTH} # [node_i (large), node_j (large), node_i (small), node_j (small), small_element] - mortar_weights_sums::Array{RealT, NDIMS} # [node_i, node_j, position] + mortar_weights_sums::Array{RealT, NDIMS} # [node_i, node_j, small (1) / large (2) element] end function MortarIDP(equations, basis::LobattoLegendreBasis; diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index ca3260288a1..b4eccb55a82 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -68,21 +68,20 @@ end function calc_mortar_weights(equations::AbstractEquations{2}, basis::LobattoLegendreBasis, RealT) n_nodes = nnodes(basis) - mortar_weights = zeros(RealT, n_nodes, n_nodes, 2) # [node (large element), node (small element), small element] - mortar_weights_sums = zeros(RealT, n_nodes, 3) # [node, left/right/large element] + mortar_weights = zeros(RealT, n_nodes, n_nodes, 2) # [node_i (large element), node_i (small element), small element] + mortar_weights_sums = zeros(RealT, n_nodes, 2) # [node, left (=1) or large (=2) element] calc_mortar_weights!(equations, mortar_weights, n_nodes, RealT) # Sums of mortar weights for normalization - for small_element in 1:2 - for i in eachnode(basis) - for k in eachnode(basis) - # Add weights from large element to small element - mortar_weights_sums[i, small_element] += mortar_weights[k, i, - small_element] - # Add weights from small element to large element - mortar_weights_sums[i, 3] += mortar_weights[i, k, - small_element] + for i in eachnode(basis) + for k in eachnode(basis) + # Add weights from large element to small element + # Sums for both small elements are equal due to symmetry + mortar_weights_sums[i, 1] += mortar_weights[k, i, 1] + # Add weights from small element to large element + for small_element in 1:2 + mortar_weights_sums[i, 2] += mortar_weights[i, k, small_element] end end end @@ -938,7 +937,7 @@ function calc_mortar_flux_low_order!(surface_flux_values, # Large element multiply_add_to_node_vars!(surface_flux_values, factor / - mortar_weights_sums[k, 3], + mortar_weights_sums[k, 2], flux, equations, dg, k, direction_large, large_element) end @@ -965,13 +964,13 @@ function calc_mortar_flux_low_order!(surface_flux_values, # Upper element multiply_add_to_node_vars!(surface_flux_values, factor / - mortar_weights_sums[i, 2], + mortar_weights_sums[i, 1], flux, equations, dg, i, direction_small, upper_element) # Large element multiply_add_to_node_vars!(surface_flux_values, factor / - mortar_weights_sums[k, 3], + mortar_weights_sums[k, 2], flux, equations, dg, k, direction_large, large_element) end