teno5.f90 Source File


Source Code

!> @file teno5.f90
!> @brief TENO5 (Targeted ENO, 5th-order) reconstruction (Fu, Hu & Adams 2016).
!!
!! Uses the same 5-point stencil and substencil approximations as WENO5-JS
!! (provided by weno_family), but replaces smooth nonlinear weights with a hard
!! threshold that eliminates stencils containing discontinuities before combining
!! the rest using the optimal (linear) weights.  This gives sharper shock
!! resolution than WENO5 while maintaining 5th-order accuracy in smooth regions.
!!
!! Algorithm (Fu, Hu & Adams 2016, Eq. 3.5-3.7):
!!   1. Compute substencil reconstructions vp0..vp2 and smoothness beta0..beta2
!!      (both shared with WENO5-JS via weno_family).
!!   2. Compute global indicator tau5 = |beta0 - beta2| (same as WENO5-Z).
!!   3. Candidate sensitivity: gamma_k = (1 + tau5/(eps+beta_k))^q, q = q_teno.
!!   4. Normalise: chi_k = gamma_k / sum(gamma).
!!   5. Hard cutoff: delta_k = 0 if chi_k < ct_teno, else 1.
!!   6. Weights: alpha_k = delta_k * d_k  (d_k are the WENO5 optimal weights).
!!   7. Fallback (sum(alpha) = 0): use the stencil with the smallest beta_k (ENO).
!!   8. f_hat = sum(alpha_k/sum(alpha) * vp_k).
!!
!! References:
!!   [1] L. Fu, X. Y. Hu, and N. A. Adams, "A family of high-order targeted ENO
!!       schemes for compressible-fluid simulations," J. Comput. Phys.,
!!       305:333-359, 2016.

module teno5
  use precision, only: wp
  use solver_state, only: neq
  use weno_family, only: eps_weno, d0, d1, d2, weno5_substencils, weno5_smoothness
  implicit none
  private

  !> Exponent for the TENO-5 candidate-stencil sensitivity.
  !! q = 6 following Fu, Hu & Adams (2016), Eq. (3.7).
  integer, parameter :: q_teno = 6

  !> Hard-threshold parameter for TENO-5 stencil elimination.
  !! Stencil k is discarded when chi_k < ct_teno.
  !! Fu, Hu & Adams (2016) recommend 1e-5 to 1e-6; smaller values yield
  !! sharper shock resolution but less aggressive oscillation suppression.
  real(wp), parameter :: ct_teno = 1.0e-5_wp

  public :: teno5_reconstruct

contains

  ! ---------------------------------------------------------------------------
  !> Perform TENO5 reconstruction on the supplied stencil.
  !!
  !! @param[in]  f_stencil  Stencil at 5 points (neq x 5)
  !! @param[out] f_hat      Reconstructed value at the interface
  ! ---------------------------------------------------------------------------
  pure subroutine teno5_reconstruct(f_stencil, f_hat)
    real(wp), intent(in) :: f_stencil(:, :)
    real(wp), intent(out) :: f_hat(:)

    real(wp) :: vp0(neq), vp1(neq), vp2(neq)
    real(wp) :: beta0(neq), beta1(neq), beta2(neq)
    real(wp) :: tau5(neq)
    real(wp) :: gamma_0(neq), gamma_1(neq), gamma_2(neq), gamma_sum(neq)  ! candidate sensitivities
    real(wp) :: chi_0(neq), chi_1(neq), chi_2(neq)                        ! normalised sensitivities
    real(wp) :: delta_0(neq), delta_1(neq), delta_2(neq)                  ! binary stencil-active flags
    real(wp) :: alpha_0(neq), alpha_1(neq), alpha_2(neq), alpha_sum(neq)  ! final weights
    integer :: i

    call weno5_substencils(f_stencil, vp0, vp1, vp2)
    call weno5_smoothness(f_stencil, beta0, beta1, beta2)

    ! Global 5th-order smoothness indicator (same as WENO5-Z).
    tau5 = abs(beta0 - beta2)

    ! Candidate stencil sensitivities (Fu et al. 2016, Eq. 3.5).
    do i = 1, neq
      gamma_0(i) = (1.0_wp + tau5(i) / (eps_weno + beta0(i)))**q_teno
      gamma_1(i) = (1.0_wp + tau5(i) / (eps_weno + beta1(i)))**q_teno
      gamma_2(i) = (1.0_wp + tau5(i) / (eps_weno + beta2(i)))**q_teno
    end do
    gamma_sum = gamma_0 + gamma_1 + gamma_2

    ! Normalise sensitivities (Fu et al. 2016, Eq. 3.6).
    chi_0 = gamma_0 / gamma_sum
    chi_1 = gamma_1 / gamma_sum
    chi_2 = gamma_2 / gamma_sum

    ! Hard cutoff: discard stencils below threshold (Fu et al. 2016, Eq. 3.7).
    where (chi_0 < ct_teno)
      delta_0 = 0.0_wp
    elsewhere
      delta_0 = 1.0_wp
    end where
    where (chi_1 < ct_teno)
      delta_1 = 0.0_wp
    elsewhere
      delta_1 = 1.0_wp
    end where
    where (chi_2 < ct_teno)
      delta_2 = 0.0_wp
    elsewhere
      delta_2 = 1.0_wp
    end where

    ! Assemble weights using optimal WENO5 linear weights.
    alpha_0 = delta_0 * d0
    alpha_1 = delta_1 * d1
    alpha_2 = delta_2 * d2
    alpha_sum = alpha_0 + alpha_1 + alpha_2

    ! Combine stencils; fallback to ENO when all stencils eliminated.
    do i = 1, neq
      if (alpha_sum(i) < tiny(1.0_wp)) then
        ! ENO fallback: pick substencil with minimum smoothness indicator.
        if (beta0(i) <= beta1(i) .and. beta0(i) <= beta2(i)) then
          f_hat(i) = vp0(i)
        else if (beta1(i) <= beta2(i)) then
          f_hat(i) = vp1(i)
        else
          f_hat(i) = vp2(i)
        end if
      else
        f_hat(i) = (alpha_0(i) * vp0(i) + alpha_1(i) * vp1(i) &
                &  + alpha_2(i) * vp2(i)) / alpha_sum(i)
      end if
    end do
  end subroutine teno5_reconstruct

end module teno5