Repository: trixi-framework/Trixi.jl
Pull request #2315:  Refactoring some `TreeMesh` parabolic code
Author: login: jlchan
Labels: name: refactoring
description: Refactoring code without functional changes
Created: 2025-03-22T15:29:08Z | Merged: 2025-03-25T19:40:56Z

Some minor refactoring in preparation for adding different parabolic solvers. Analogous to https://github.com/trixi-framework/Trixi.jl/pull/2314. 

The `parabolic` CI failures should be resolved after #2314 is merged.


Diff:
diff --git a/src/solvers/dgsem_tree/dg_1d_parabolic.jl b/src/solvers/dgsem_tree/dg_1d_parabolic.jl
index 034b1b73b82..c6455cf25f6 100644
--- a/src/solvers/dgsem_tree/dg_1d_parabolic.jl
+++ b/src/solvers/dgsem_tree/dg_1d_parabolic.jl
@@ -29,7 +29,8 @@ function rhs_parabolic!(du, u, t, mesh::TreeMesh{1},
     # Compute the gradients of the transformed variables
     @trixi_timeit timer() "calculate gradient" begin
         calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic,
-                       boundary_conditions_parabolic, dg, cache, cache_parabolic)
+                       boundary_conditions_parabolic, dg, parabolic_scheme,
+                       cache, cache_parabolic)
     end
 
     # Compute and store the viscous fluxes
@@ -68,8 +69,9 @@ function rhs_parabolic!(du, u, t, mesh::TreeMesh{1},
 
     # Calculate interface fluxes
     @trixi_timeit timer() "interface flux" begin
-        calc_interface_flux!(cache_parabolic.elements.surface_flux_values, mesh,
-                             equations_parabolic, dg, cache_parabolic)
+        calc_interface_flux!(cache_parabolic.elements.surface_flux_values,
+                             mesh, equations_parabolic, dg, parabolic_scheme,
+                             cache_parabolic)
     end
 
     # Prolong solution to boundaries
@@ -170,7 +172,8 @@ end
 # This is the version used when calculating the divergence of the viscous fluxes
 function calc_interface_flux!(surface_flux_values,
                               mesh::TreeMesh{1}, equations_parabolic,
-                              dg::DG, cache_parabolic)
+                              dg::DG, parabolic_scheme::ViscousFormulationBassiRebay1,
+                              cache_parabolic)
     @unpack neighbor_ids, orientations = cache_parabolic.interfaces
 
     @threaded for interface in eachinterface(dg, cache_parabolic)
@@ -188,8 +191,7 @@ function calc_interface_flux!(surface_flux_values,
                                                  equations_parabolic,
                                                  dg, interface)
 
-        # Compute interface flux as mean of left and right viscous fluxes
-        # TODO: parabolic; only BR1 at the moment
+        # Compute BR1 interface flux as mean of left and right viscous fluxes
         flux = 0.5f0 * (flux_ll + flux_rr)
 
         # Copy flux to left and right element storage
@@ -402,10 +404,39 @@ function calc_boundary_flux_by_direction_divergence!(surface_flux_values::Abstra
     return nothing
 end
 
+function calc_gradient_interface_flux!(surface_flux_values,
+                                       mesh::TreeMesh{1}, equations_parabolic, dg::DG,
+                                       parabolic_scheme::ViscousFormulationBassiRebay1,
+                                       cache, cache_parabolic)
+    @unpack neighbor_ids, orientations = cache_parabolic.interfaces
+
+    @threaded for interface in eachinterface(dg, cache_parabolic)
+        # Get neighboring elements
+        left_id = neighbor_ids[1, interface]
+        right_id = neighbor_ids[2, interface]
+
+        # Determine interface direction with respect to elements:
+        # orientation = 1: left -> 2, right -> 1
+        left_direction = 2 * orientations[interface]
+        right_direction = 2 * orientations[interface] - 1
+
+        # Call pointwise Riemann solver
+        u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
+                                           equations_parabolic, dg, interface)
+        flux = 0.5f0 * (u_ll + u_rr) # Bassi-Rebay 1 (BR1)
+
+        # Copy flux to left and right element storage
+        for v in eachvariable(equations_parabolic)
+            surface_flux_values[v, left_direction, left_id] = flux[v]
+            surface_flux_values[v, right_direction, right_id] = flux[v]
+        end
+    end
+end
+
 # Calculate the gradient of the transformed variables
-function calc_gradient!(gradients, u_transformed, t,
-                        mesh::TreeMesh{1}, equations_parabolic,
-                        boundary_conditions_parabolic, dg::DG, cache, cache_parabolic)
+function calc_gradient!(gradients, u_transformed, t, mesh::TreeMesh{1},
+                        equations_parabolic, boundary_conditions_parabolic,
+                        dg::DG, parabolic_scheme, cache, cache_parabolic)
 
     # Reset du
     @trixi_timeit timer() "reset gradients" begin
@@ -441,29 +472,9 @@ function calc_gradient!(gradients, u_transformed, t,
     # Calculate interface fluxes
     @trixi_timeit timer() "interface flux" begin
         @unpack surface_flux_values = cache_parabolic.elements
-        @unpack neighbor_ids, orientations = cache_parabolic.interfaces
-
-        @threaded for interface in eachinterface(dg, cache_parabolic)
-            # Get neighboring elements
-            left_id = neighbor_ids[1, interface]
-            right_id = neighbor_ids[2, interface]
-
-            # Determine interface direction with respect to elements:
-            # orientation = 1: left -> 2, right -> 1
-            left_direction = 2 * orientations[interface]
-            right_direction = 2 * orientations[interface] - 1
-
-            # Call pointwise Riemann solver
-            u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
-                                               equations_parabolic, dg, interface)
-            flux = 0.5f0 * (u_ll + u_rr)
-
-            # Copy flux to left and right element storage
-            for v in eachvariable(equations_parabolic)
-                surface_flux_values[v, left_direction, left_id] = flux[v]
-                surface_flux_values[v, right_direction, right_id] = flux[v]
-            end
-        end
+        calc_gradient_interface_flux!(surface_flux_values,
+                                      mesh, equations_parabolic, dg, parabolic_scheme,
+                                      cache, cache_parabolic)
     end
 
     # Prolong solution to boundaries
diff --git a/src/solvers/dgsem_tree/dg_2d_parabolic.jl b/src/solvers/dgsem_tree/dg_2d_parabolic.jl
index ce14da8d205..d44676bb04d 100644
--- a/src/solvers/dgsem_tree/dg_2d_parabolic.jl
+++ b/src/solvers/dgsem_tree/dg_2d_parabolic.jl
@@ -28,8 +28,9 @@ function rhs_parabolic!(du, u, t, mesh::Union{TreeMesh{2}, TreeMesh{3}},
 
     # Compute the gradients of the transformed variables
     @trixi_timeit timer() "calculate gradient" begin
-        calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic,
-                       boundary_conditions_parabolic, dg, cache, cache_parabolic)
+        calc_gradient!(gradients, u_transformed, t, mesh,
+                       equations_parabolic, boundary_conditions_parabolic,
+                       dg, parabolic_scheme, cache, cache_parabolic)
     end
 
     # Compute and store the viscous fluxes
@@ -68,8 +69,9 @@ function rhs_parabolic!(du, u, t, mesh::Union{TreeMesh{2}, TreeMesh{3}},
 
     # Calculate interface fluxes
     @trixi_timeit timer() "interface flux" begin
-        calc_interface_flux!(cache_parabolic.elements.surface_flux_values, mesh,
-                             equations_parabolic, dg, cache_parabolic)
+        calc_interface_flux!(cache_parabolic.elements.surface_flux_values,
+                             mesh, equations_parabolic,
+                             dg, parabolic_scheme, cache_parabolic)
     end
 
     # Prolong solution to boundaries
@@ -94,9 +96,9 @@ function rhs_parabolic!(du, u, t, mesh::Union{TreeMesh{2}, TreeMesh{3}},
 
     # Calculate mortar fluxes
     @trixi_timeit timer() "mortar flux" begin
-        calc_mortar_flux!(cache_parabolic.elements.surface_flux_values, mesh,
-                          equations_parabolic,
-                          dg.mortar, dg.surface_integral, dg, cache)
+        calc_mortar_flux!(cache_parabolic.elements.surface_flux_values,
+                          mesh, equations_parabolic, dg.mortar, dg.surface_integral,
+                          dg, parabolic_scheme, cache)
     end
 
     # Calculate surface integrals
@@ -204,9 +206,11 @@ function prolong2interfaces!(cache_parabolic, flux_viscous,
 end
 
 # This is the version used when calculating the divergence of the viscous fluxes
-function calc_interface_flux!(surface_flux_values,
-                              mesh::TreeMesh{2}, equations_parabolic,
-                              dg::DG, cache_parabolic)
+function calc_interface_flux!(surface_flux_values, mesh::TreeMesh{2},
+                              equations_parabolic,
+                              dg::DG,
+                              parabolic_scheme::ViscousFormulationBassiRebay1,
+                              cache_parabolic)
     @unpack neighbor_ids, orientations = cache_parabolic.interfaces
 
     @threaded for interface in eachinterface(dg, cache_parabolic)
@@ -620,7 +624,8 @@ function calc_mortar_flux!(surface_flux_values,
                            mesh::TreeMesh{2},
                            equations_parabolic::AbstractEquationsParabolic,
                            mortar_l2::LobattoLegendreMortarL2,
-                           surface_integral, dg::DG, cache)
+                           surface_integral, dg::DG, parabolic_scheme,
+                           cache)
     @unpack surface_flux = surface_integral
     @unpack u_lower, u_upper, orientations = cache.mortars
     @unpack fstar_primary_upper_threaded, fstar_primary_lower_threaded = cache
@@ -632,9 +637,11 @@ function calc_mortar_flux!(surface_flux_values,
 
         # Calculate fluxes
         orientation = orientations[mortar]
-        calc_fstar!(fstar_upper, equations_parabolic, surface_flux, dg, u_upper, mortar,
+        calc_fstar!(fstar_upper, equations_parabolic, surface_flux,
+                    dg, parabolic_scheme, u_upper, mortar,
                     orientation)
-        calc_fstar!(fstar_lower, equations_parabolic, surface_flux, dg, u_lower, mortar,
+        calc_fstar!(fstar_lower, equations_parabolic, surface_flux,
+                    dg, parabolic_scheme, u_lower, mortar,
                     orientation)
 
         mortar_fluxes_to_elements!(surface_flux_values,
@@ -648,13 +655,14 @@ end
 @inline function calc_fstar!(destination::AbstractArray{<:Any, 2},
                              equations_parabolic::AbstractEquationsParabolic,
                              surface_flux, dg::DGSEM,
+                             parabolic_scheme::ViscousFormulationBassiRebay1,
                              u_interfaces, interface, orientation)
     for i in eachnode(dg)
         # Call pointwise two-point numerical flux function
         u_ll, u_rr = get_surface_node_vars(u_interfaces, equations_parabolic, dg, i,
                                            interface)
-        # TODO: parabolic; only BR1 at the moment
-        flux = 0.5f0 * (u_ll + u_rr)
+
+        flux = 0.5f0 * (u_ll + u_rr) # Bassi-Rebay 1 (BR1)
 
         # Copy flux to left and right element storage
         set_node_vars!(destination, flux, equations_parabolic, dg, i)
@@ -737,10 +745,44 @@ end
     return nothing
 end
 
+function calc_gradient_interface_flux!(surface_flux_values,
+                                       mesh::TreeMesh{2}, equations, dg::DG,
+                                       parabolic_scheme::ViscousFormulationBassiRebay1,
+                                       cache, cache_parabolic)
+    @unpack neighbor_ids, orientations = cache_parabolic.interfaces
+
+    @threaded for interface in eachinterface(dg, cache_parabolic)
+        # Get neighboring elements
+        left_id = neighbor_ids[1, interface]
+        right_id = neighbor_ids[2, interface]
+
+        # Determine interface direction with respect to elements:
+        # orientation = 1: left -> 2, right -> 1
+        # orientation = 2: left -> 4, right -> 3
+        left_direction = 2 * orientations[interface]
+        right_direction = 2 * orientations[interface] - 1
+
+        for i in eachnode(dg)
+            # Call pointwise Riemann solver
+            u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
+                                               equations, dg, i,
+                                               interface)
+            flux = 0.5f0 * (u_ll + u_rr) # Bassi-Rebay 1 (BR1)
+
+            # Copy flux to left and right element storage
+            for v in eachvariable(equations)
+                surface_flux_values[v, i, left_direction, left_id] = flux[v]
+                surface_flux_values[v, i, right_direction, right_id] = flux[v]
+            end
+        end
+    end
+end
+
 # Calculate the gradient of the transformed variables
 function calc_gradient!(gradients, u_transformed, t,
                         mesh::TreeMesh{2}, equations_parabolic,
-                        boundary_conditions_parabolic, dg::DG, cache, cache_parabolic)
+                        boundary_conditions_parabolic, dg::DG, parabolic_scheme,
+                        cache, cache_parabolic)
     gradients_x, gradients_y = gradients
 
     # Reset du
@@ -783,33 +825,9 @@ function calc_gradient!(gradients, u_transformed, t,
     # Calculate interface fluxes
     @trixi_timeit timer() "interface flux" begin
         @unpack surface_flux_values = cache_parabolic.elements
-        @unpack neighbor_ids, orientations = cache_parabolic.interfaces
-
-        @threaded for interface in eachinterface(dg, cache_parabolic)
-            # Get neighboring elements
-            left_id = neighbor_ids[1, interface]
-            right_id = neighbor_ids[2, interface]
-
-            # Determine interface direction with respect to elements:
-            # orientation = 1: left -> 2, right -> 1
-            # orientation = 2: left -> 4, right -> 3
-            left_direction = 2 * orientations[interface]
-            right_direction = 2 * orientations[interface] - 1
-
-            for i in eachnode(dg)
-                # Call pointwise Riemann solver
-                u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
-                                                   equations_parabolic, dg, i,
-                                                   interface)
-                flux = 0.5f0 * (u_ll + u_rr)
-
-                # Copy flux to left and right element storage
-                for v in eachvariable(equations_parabolic)
-                    surface_flux_values[v, i, left_direction, left_id] = flux[v]
-                    surface_flux_values[v, i, right_direction, right_id] = flux[v]
-                end
-            end
-        end
+        calc_gradient_interface_flux!(surface_flux_values, mesh, equations_parabolic,
+                                      dg, parabolic_scheme,
+                                      cache, cache_parabolic)
     end
 
     # Prolong solution to boundaries
@@ -835,10 +853,9 @@ function calc_gradient!(gradients, u_transformed, t,
 
     # Calculate mortar fluxes
     @trixi_timeit timer() "mortar flux" begin
-        calc_mortar_flux!(surface_flux_values,
-                          mesh,
-                          equations_parabolic,
-                          dg.mortar, dg.surface_integral, dg, cache)
+        calc_mortar_flux!(surface_flux_values, mesh, equations_parabolic,
+                          dg.mortar, dg.surface_integral, dg, parabolic_scheme,
+                          cache)
     end
 
     # Calculate surface integrals
diff --git a/src/solvers/dgsem_tree/dg_3d_parabolic.jl b/src/solvers/dgsem_tree/dg_3d_parabolic.jl
index 445a40e3a48..f1b310e8a7f 100644
--- a/src/solvers/dgsem_tree/dg_3d_parabolic.jl
+++ b/src/solvers/dgsem_tree/dg_3d_parabolic.jl
@@ -116,9 +116,10 @@ function prolong2interfaces!(cache_parabolic, flux_viscous,
 end
 
 # This is the version used when calculating the divergence of the viscous fluxes
-function calc_interface_flux!(surface_flux_values,
-                              mesh::TreeMesh{3}, equations_parabolic,
-                              dg::DG, cache_parabolic)
+function calc_interface_flux!(surface_flux_values, mesh::TreeMesh{3},
+                              equations_parabolic,
+                              dg::DG, parabolic_scheme::ViscousFormulationBassiRebay1,
+                              cache_parabolic)
     @unpack neighbor_ids, orientations = cache_parabolic.interfaces
 
     @threaded for interface in eachinterface(dg, cache_parabolic)
@@ -719,11 +720,10 @@ end
 # NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms.
 # Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for
 # hyperbolic terms with conserved terms only, i.e., no nonconservative terms.
-function calc_mortar_flux!(surface_flux_values,
-                           mesh::TreeMesh{3},
+function calc_mortar_flux!(surface_flux_values, mesh::TreeMesh{3},
                            equations_parabolic::AbstractEquationsParabolic,
-                           mortar_l2::LobattoLegendreMortarL2,
-                           surface_integral, dg::DG, cache)
+                           mortar_l2::LobattoLegendreMortarL2, surface_integral,
+                           dg::DG, parabolic_scheme, cache)
     @unpack surface_flux = surface_integral
     @unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations = cache.mortars
     @unpack (fstar_primary_upper_left_threaded, fstar_primary_upper_right_threaded,
@@ -740,13 +740,17 @@ function calc_mortar_flux!(surface_flux_values,
 
         # Calculate fluxes
         orientation = orientations[mortar]
-        calc_fstar!(fstar_upper_left, equations_parabolic, surface_flux, dg,
+        calc_fstar!(fstar_upper_left, equations_parabolic,
+                    surface_flux, dg, parabolic_scheme,
                     u_upper_left, mortar, orientation)
-        calc_fstar!(fstar_upper_right, equations_parabolic, surface_flux, dg,
+        calc_fstar!(fstar_upper_right, equations_parabolic,
+                    surface_flux, dg, parabolic_scheme,
                     u_upper_right, mortar, orientation)
-        calc_fstar!(fstar_lower_left, equations_parabolic, surface_flux, dg,
+        calc_fstar!(fstar_lower_left, equations_parabolic,
+                    surface_flux, dg, parabolic_scheme,
                     u_lower_left, mortar, orientation)
-        calc_fstar!(fstar_lower_right, equations_parabolic, surface_flux, dg,
+        calc_fstar!(fstar_lower_right, equations_parabolic,
+                    surface_flux, dg, parabolic_scheme,
                     u_lower_right, mortar, orientation)
 
         mortar_fluxes_to_elements!(surface_flux_values,
@@ -762,12 +766,12 @@ end
 @inline function calc_fstar!(destination::AbstractArray{<:Any, 3},
                              equations_parabolic::AbstractEquationsParabolic,
                              surface_flux, dg::DGSEM,
+                             parabolic_scheme::ViscousFormulationBassiRebay1,
                              u_interfaces, interface, orientation)
     for j in eachnode(dg), i in eachnode(dg)
         # Call pointwise two-point numerical flux function
         u_ll, u_rr = get_surface_node_vars(u_interfaces, equations_parabolic, dg, i, j,
                                            interface)
-        # TODO: parabolic; only BR1 at the moment
         flux = 0.5f0 * (u_ll + u_rr)
 
         # Copy flux to left and right element storage
@@ -869,7 +873,8 @@ end
 # Calculate the gradient of the transformed variables
 function calc_gradient!(gradients, u_transformed, t,
                         mesh::TreeMesh{3}, equations_parabolic,
-                        boundary_conditions_parabolic, dg::DG, cache, cache_parabolic)
+                        boundary_conditions_parabolic, dg::DG, parabolic_scheme,
+                        cache, cache_parabolic)
     gradients_x, gradients_y, gradients_z = gradients
 
     # Reset du
@@ -972,9 +977,9 @@ function calc_gradient!(gradients, u_transformed, t,
     # Calculate mortar fluxes
     @trixi_timeit timer() "mortar flux" begin
         calc_mortar_flux!(surface_flux_values,
-                          mesh,
-                          equations_parabolic,
-                          dg.mortar, dg.surface_integral, dg, cache)
+                          mesh, equations_parabolic,
+                          dg.mortar, dg.surface_integral,
+                          dg, parabolic_scheme, cache)
     end
 
     # Calculate surface integrals


Reviews:
user: login: DanielDoehring
body: Thanks for tackling this! I have some very minor suggestions regarding comments & formatting.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-25T14:06:59Z
user: login: DanielDoehring
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-25T14:51:20Z
user: login: DanielDoehring
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-25T15:15:45Z
user: login: ranocha
body: Thanks!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-25T19:40:30Z

Review comments:
diff_hunk: @@ -402,10 +404,39 @@ function calc_boundary_flux_by_direction_divergence!(surface_flux_values::Abstra
     return nothing
 end
 
+function calc_gradient_interface_flux!(surface_flux_values,
+                                       mesh::TreeMesh{1}, equations_parabolic, dg::DG,
+                                       parabolic_scheme::ViscousFormulationBassiRebay1,
+                                       cache, cache_parabolic)
+    @unpack neighbor_ids, orientations = cache_parabolic.interfaces
+
+    @threaded for interface in eachinterface(dg, cache_parabolic)
+        # Get neighboring elements
+        left_id = neighbor_ids[1, interface]
+        right_id = neighbor_ids[2, interface]
+
+        # Determine interface direction with respect to elements:
+        # orientation = 1: left -> 2, right -> 1
+        left_direction = 2 * orientations[interface]
+        right_direction = 2 * orientations[interface] - 1
+
+        # Call pointwise Riemann solver
+        u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
+                                           equations_parabolic, dg, interface)
+        flux = 0.5f0 * (u_ll + u_rr)
path: src/solvers/dgsem_tree/dg_1d_parabolic.jl
user: login: DanielDoehring
body: ```suggestion
        flux = 0.5f0 * (u_ll + u_rr) # Bassi-Rebay 1 (BR1)
```

Maybe be somewhat explicit where the specialization "really" happens?
created_at: 2025-03-25T13:52:48Z
author_association: MEMBER
diff_hunk: @@ -648,12 +655,13 @@ end
 @inline function calc_fstar!(destination::AbstractArray{<:Any, 2},
                              equations_parabolic::AbstractEquationsParabolic,
                              surface_flux, dg::DGSEM,
+                             parabolic_scheme::ViscousFormulationBassiRebay1,
                              u_interfaces, interface, orientation)
     for i in eachnode(dg)
         # Call pointwise two-point numerical flux function
         u_ll, u_rr = get_surface_node_vars(u_interfaces, equations_parabolic, dg, i,
                                            interface)
-        # TODO: parabolic; only BR1 at the moment
+
path: src/solvers/dgsem_tree/dg_2d_parabolic.jl
user: login: DanielDoehring
body: See above's comment on explicitly mentioning BR1 ?
created_at: 2025-03-25T13:53:47Z
author_association: MEMBER
diff_hunk: @@ -737,10 +745,44 @@ end
     return nothing
 end
 
+function calc_gradient_interface_flux!(surface_flux_values,
+                                       mesh::TreeMesh{2}, equations, dg::DG,
+                                       parabolic_scheme::ViscousFormulationBassiRebay1,
+                                       cache, cache_parabolic)
+    @unpack neighbor_ids, orientations = cache_parabolic.interfaces
+
+    @threaded for interface in eachinterface(dg, cache_parabolic)
+        # Get neighboring elements
+        left_id = neighbor_ids[1, interface]
+        right_id = neighbor_ids[2, interface]
+
+        # Determine interface direction with respect to elements:
+        # orientation = 1: left -> 2, right -> 1
+        # orientation = 2: left -> 4, right -> 3
+        left_direction = 2 * orientations[interface]
+        right_direction = 2 * orientations[interface] - 1
+
+        for i in eachnode(dg)
+            # Call pointwise Riemann solver
+            u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
+                                               equations, dg, i,
+                                               interface)
+            flux = 0.5f0 * (u_ll + u_rr)
path: src/solvers/dgsem_tree/dg_2d_parabolic.jl
user: login: DanielDoehring
body: ```suggestion
            flux = 0.5f0 * (u_ll + u_rr) # Bassi-Rebay 1 (BR1)
```

Maybe be somewhat explicit where the specialization "really" happens?
created_at: 2025-03-25T14:04:26Z
author_association: MEMBER
diff_hunk: @@ -737,10 +745,44 @@ end
     return nothing
 end
 
+function calc_gradient_interface_flux!(surface_flux_values,
+                                       mesh::TreeMesh{2}, equations, dg::DG,
+                                       parabolic_scheme::ViscousFormulationBassiRebay1,
+                                       cache, cache_parabolic)
+    @unpack neighbor_ids, orientations = cache_parabolic.interfaces
+
+    @threaded for interface in eachinterface(dg, cache_parabolic)
+        # Get neighboring elements
+        left_id = neighbor_ids[1, interface]
+        right_id = neighbor_ids[2, interface]
+
+        # Determine interface direction with respect to elements:
+        # orientation = 1: left -> 2, right -> 1
+        # orientation = 2: left -> 4, right -> 3
+        left_direction = 2 * orientations[interface]
+        right_direction = 2 * orientations[interface] - 1
+
+        for i in eachnode(dg)
+            # Call pointwise Riemann solver
+            u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
+                                               equations, dg, i,
+                                               interface)
+            flux = 0.5f0 * (u_ll + u_rr)
+
+            # Copy flux to left and right element storage
+            for v in eachvariable(equations)
+                surface_flux_values[v, i, left_direction, left_id] = flux[v]
+                surface_flux_values[v, i, right_direction, right_id] = flux[v]
+            end
+        end
+    end
+end
+
 # Calculate the gradient of the transformed variables
 function calc_gradient!(gradients, u_transformed, t,
                         mesh::TreeMesh{2}, equations_parabolic,
-                        boundary_conditions_parabolic, dg::DG, cache, cache_parabolic)
+                        boundary_conditions_parabolic, dg::DG, parabolic_scheme, cache,
+                        cache_parabolic)
path: src/solvers/dgsem_tree/dg_2d_parabolic.jl
user: login: DanielDoehring
body: ```suggestion
                        boundary_conditions_parabolic, dg::DG, parabolic_scheme, 
                        cache, cache_parabolic)
```

maybe this is more semantic formatting?
created_at: 2025-03-25T14:05:11Z
author_association: MEMBER
diff_hunk: @@ -783,33 +825,9 @@ function calc_gradient!(gradients, u_transformed, t,
     # Calculate interface fluxes
     @trixi_timeit timer() "interface flux" begin
         @unpack surface_flux_values = cache_parabolic.elements
-        @unpack neighbor_ids, orientations = cache_parabolic.interfaces
-
-        @threaded for interface in eachinterface(dg, cache_parabolic)
-            # Get neighboring elements
-            left_id = neighbor_ids[1, interface]
-            right_id = neighbor_ids[2, interface]
-
-            # Determine interface direction with respect to elements:
-            # orientation = 1: left -> 2, right -> 1
-            # orientation = 2: left -> 4, right -> 3
-            left_direction = 2 * orientations[interface]
-            right_direction = 2 * orientations[interface] - 1
-
-            for i in eachnode(dg)
-                # Call pointwise Riemann solver
-                u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
-                                                   equations_parabolic, dg, i,
-                                                   interface)
-                flux = 0.5f0 * (u_ll + u_rr)
-
-                # Copy flux to left and right element storage
-                for v in eachvariable(equations_parabolic)
-                    surface_flux_values[v, i, left_direction, left_id] = flux[v]
-                    surface_flux_values[v, i, right_direction, right_id] = flux[v]
-                end
-            end
-        end
+        calc_gradient_interface_flux!(surface_flux_values, mesh, equations_parabolic,
+                                      dg, parabolic_scheme, cache,
+                                      cache_parabolic)
path: src/solvers/dgsem_tree/dg_2d_parabolic.jl
user: login: DanielDoehring
body: ```suggestion
                                      dg, parabolic_scheme, 
                                      cache, cache_parabolic)
```

Semantic formatting?
created_at: 2025-03-25T14:05:31Z
author_association: MEMBER
diff_hunk: @@ -869,7 +873,8 @@ end
 # Calculate the gradient of the transformed variables
 function calc_gradient!(gradients, u_transformed, t,
                         mesh::TreeMesh{3}, equations_parabolic,
-                        boundary_conditions_parabolic, dg::DG, cache, cache_parabolic)
+                        boundary_conditions_parabolic, dg::DG, parabolic_scheme, cache,
+                        cache_parabolic)
path: src/solvers/dgsem_tree/dg_3d_parabolic.jl
user: login: DanielDoehring
body: ```suggestion
                        boundary_conditions_parabolic, dg::DG, parabolic_scheme, 
                        cache, cache_parabolic)
```

Semantic formatting?
created_at: 2025-03-25T14:06:01Z
author_association: MEMBER
diff_hunk: @@ -441,29 +472,10 @@ function calc_gradient!(gradients, u_transformed, t,
     # Calculate interface fluxes
     @trixi_timeit timer() "interface flux" begin
         @unpack surface_flux_values = cache_parabolic.elements
-        @unpack neighbor_ids, orientations = cache_parabolic.interfaces
-
-        @threaded for interface in eachinterface(dg, cache_parabolic)
-            # Get neighboring elements
-            left_id = neighbor_ids[1, interface]
-            right_id = neighbor_ids[2, interface]
-
-            # Determine interface direction with respect to elements:
-            # orientation = 1: left -> 2, right -> 1
-            left_direction = 2 * orientations[interface]
-            right_direction = 2 * orientations[interface] - 1
-
-            # Call pointwise Riemann solver
-            u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u,
-                                               equations_parabolic, dg, interface)
-            flux = 0.5f0 * (u_ll + u_rr)
-
-            # Copy flux to left and right element storage
-            for v in eachvariable(equations_parabolic)
-                surface_flux_values[v, left_direction, left_id] = flux[v]
-                surface_flux_values[v, right_direction, right_id] = flux[v]
-            end
-        end
+        calc_gradient_interface_flux!(surface_flux_values,
+                                      mesh, equations_parabolic, dg, parabolic_scheme,
+                                      cache,
+                                      cache_parabolic)
path: src/solvers/dgsem_tree/dg_1d_parabolic.jl
user: login: DanielDoehring
body: ```suggestion
                                      cache, cache_parabolic)
```
created_at: 2025-03-25T14:06:26Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-22T15:29:20Z
body: ### Review checklist

This checklist is meant to assist creators of PRs (to let them know what reviewers will typically look for) and reviewers (to guide them in a structured review process). Items do not need to be checked explicitly for a PR to be eligible for merging.

#### Purpose and scope
- [ ] The PR has a single goal that is clear from the PR title and/or description.
- [ ] All code changes represent a single set of modifications that logically belong together.
- [ ] No more than 500 lines of code are changed or there is no obvious way to split the PR into multiple PRs.

#### Code quality
- [ ] The code can be understood easily.
- [ ] Newly introduced names for variables etc. are self-descriptive and consistent with existing naming conventions.
- [ ] There are no redundancies that can be removed by simple modularization/refactoring.
- [ ] There are no leftover debug statements or commented code sections.
- [ ] The code adheres to our [conventions](https://trixi-framework.github.io/Trixi.jl/stable/conventions/) and [style guide](https://trixi-framework.github.io/Trixi.jl/stable/styleguide/), and to the [Julia guidelines](https://docs.julialang.org/en/v1/manual/style-guide/).

#### Documentation
- [ ] New functions and types are documented with a docstring or top-level comment.
- [ ] Relevant publications are referenced in docstrings (see [example](https://github.com/trixi-framework/Trixi.jl/blob/7f83a1a938eecd9b841efe215a6e482e67cfdcc1/src/equations/compressible_euler_2d.jl#L601-L615) for formatting).
- [ ] Inline comments are used to document longer or unusual code sections.
- [ ] Comments describe intent ("why?") and not just functionality ("what?").
- [ ] If the PR introduces a significant change or new feature, it is documented in `NEWS.md` with its PR number.

#### Testing
- [ ] The PR passes all tests.
- [ ] New or modified lines of code are covered by tests.
- [ ] New or modified tests run in less then 10 seconds.

#### Performance
- [ ] There are no type instabilities or memory allocations in performance-critical parts.
- [ ] If the PR intent is to improve performance, before/after [time measurements](https://trixi-framework.github.io/Trixi.jl/stable/performance/#Manual-benchmarking) are posted in the PR.

#### Verification
- [ ] The correctness of the code was verified using appropriate tests.
- [ ] If new equations/methods are added, a convergence test has been run and the results
  are posted in the PR.

*Created with :heart: by the Trixi.jl community.*

author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-22T15:46:42Z
body: ## [Codecov](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) Report
Attention: Patch coverage is `95.83333%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 96.89%. Comparing base [(`70db77d`)](https://app.codecov.io/gh/trixi-framework/Trixi.jl/commit/70db77d82343f97dee388bcdfbc9620ee7d02d96?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) to head [(`769813f`)](https://app.codecov.io/gh/trixi-framework/Trixi.jl/commit/769813f773dec7e8bb1c3a8d2f066a89996e47bc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) | Patch % | Lines |
|---|---|---|
| [src/solvers/dgsem\_tree/dg\_1d\_parabolic.jl](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315?src=pr&el=tree&filepath=src%2Fsolvers%2Fdgsem_tree%2Fdg_1d_parabolic.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework#diff-c3JjL3NvbHZlcnMvZGdzZW1fdHJlZS9kZ18xZF9wYXJhYm9saWMuamw=) | 94.12% | [1 Missing :warning: ](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) |
| [src/solvers/dgsem\_tree/dg\_2d\_parabolic.jl](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315?src=pr&el=tree&filepath=src%2Fsolvers%2Fdgsem_tree%2Fdg_2d_parabolic.jl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework#diff-c3JjL3NvbHZlcnMvZGdzZW1fdHJlZS9kZ18yZF9wYXJhYm9saWMuamw=) | 96.00% | [1 Missing :warning: ](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #2315   +/-   ##
=======================================
  Coverage   96.89%   96.89%           
=======================================
  Files         496      496           
  Lines       40753    40757    +4     
=======================================
+ Hits        39484    39488    +4     
  Misses       1269     1269           
```

| [Flag](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework) | `96.89% <95.83%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/trixi-framework/Trixi.jl/pull/2315?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=trixi-framework).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: jlchan
created_at: 2025-03-22T22:39:43Z
body: The parabolic CI failures should be fixed after https://github.com/trixi-framework/Trixi.jl/pull/2314 is merged.
author_association: CONTRIBUTOR
user: login: ranocha
created_at: 2025-03-25T08:11:18Z
body: Please ping me for a review when you have resolved the conflicts, @jlchan 
author_association: MEMBER
user: login: jlchan
created_at: 2025-03-25T15:11:02Z
body: @DanielDoehring sorry, forgot to format. Would you mind approving again?
author_association: CONTRIBUTOR
user: login: DanielDoehring
created_at: 2025-03-26T12:09:28Z
body: @jlchan somehow all of us missed the 3D part, right? :O

https://github.com/trixi-framework/Trixi.jl/blob/8f941194b8c1a66a44cb280811a94d0abad79f7a/src/solvers/dgsem_tree/dg_3d_parabolic.jl#L925-L955
author_association: MEMBER
user: login: jlchan
created_at: 2025-03-26T14:26:26Z
body: > @jlchan somehow all of us missed the 3D part, right? :O

😅 I guess I didn't notice it since the tests passed...

Thanks for catching that!
author_association: CONTRIBUTOR