Step3DMod.f90 Source File

Three-dimensional ray stepping with adaptive step control


This file depends on

sourcefile~~step3dmod.f90~~EfferentGraph sourcefile~step3dmod.f90 Step3DMod.f90 sourcefile~bdry3dmod.f90 bdry3DMod.f90 sourcefile~step3dmod.f90->sourcefile~bdry3dmod.f90 sourcefile~bellhopmod.f90 bellhopMod.f90 sourcefile~step3dmod.f90->sourcefile~bellhopmod.f90 sourcefile~cross_products.f90 cross_products.f90 sourcefile~step3dmod.f90->sourcefile~cross_products.f90 sourcefile~raynormals.f90 RayNormals.f90 sourcefile~step3dmod.f90->sourcefile~raynormals.f90 sourcefile~sspmod.f90 sspMod.f90 sourcefile~step3dmod.f90->sourcefile~sspmod.f90 sourcefile~fatalerror.f90 FatalError.f90 sourcefile~bdry3dmod.f90->sourcefile~fatalerror.f90 sourcefile~monotonicmod.f90 monotonicMod.f90 sourcefile~bdry3dmod.f90->sourcefile~monotonicmod.f90 sourcefile~subtabulate.f90 subtabulate.f90 sourcefile~bdry3dmod.f90->sourcefile~subtabulate.f90 sourcefile~mathconstants.f90 MathConstants.f90 sourcefile~bellhopmod.f90->sourcefile~mathconstants.f90 sourcefile~attenmod.f90 AttenMod.f90 sourcefile~sspmod.f90->sourcefile~attenmod.f90 sourcefile~sspmod.f90->sourcefile~fatalerror.f90 sourcefile~sspmod.f90->sourcefile~monotonicmod.f90 sourcefile~pchipmod.f90 pchipMod.f90 sourcefile~sspmod.f90->sourcefile~pchipmod.f90 sourcefile~splinec.f90 splinec.f90 sourcefile~sspmod.f90->sourcefile~splinec.f90 sourcefile~attenmod.f90->sourcefile~fatalerror.f90 sourcefile~attenmod.f90->sourcefile~mathconstants.f90 sourcefile~pchipmod.f90->sourcefile~splinec.f90

Files dependent on this one

sourcefile~~step3dmod.f90~~AfferentGraph sourcefile~step3dmod.f90 Step3DMod.f90 sourcefile~bellhop3d.f90 bellhop3D.f90 sourcefile~bellhop3d.f90->sourcefile~step3dmod.f90

Source Code

!! Three-dimensional ray stepping with adaptive step control
MODULE Step3DMod
  !! 3D ray propagation with adaptive step control and complex boundary interactions

  USE bellhopMod
  USE Bdry3DMod
  USE sspMod
  USE RayNormals
  USE cross_products

  IMPLICIT NONE
  PUBLIC

  REAL (KIND=8), PARAMETER, PRIVATE :: INFINITESIMAL_STEP_SIZE = 1.0d-6

CONTAINS

  SUBROUTINE Step3D( ray0, ray2, topRefl, botRefl )

    ! Does a single step along the ray
    ! x denotes the ray coordinate, ( x, y, z )
    ! t denotes the scaled tangent to the ray (previously (xi, eta, zeta) )
    ! c * t would be the unit tangent

    ! rays
    TYPE( ray3DPt ), INTENT( INOUT ) :: ray0, ray2
    TYPE( ray3DPt ) :: ray1

    LOGICAL, INTENT( OUT ) :: topRefl, botRefl
    INTEGER         :: iSegx0, iSegy0, iSegz0, snapDim
    REAL  (KIND=8 ) :: gradc0( 3 ), gradc1( 3 ), gradc2( 3 ), &
         c0, cimag0, csq0, cxx0, cyy0, czz0, cxy0, cxz0, cyz0, cnn0, cmn0, cmm0, &
         c1, cimag1, csq1, cxx1, cyy1, czz1, cxy1, cxz1, cyz1, cnn1, cmn1, cmm1, &
         c2, cimag2,       cxx2, cyy2, czz2, cxy2, cxz2, cyz2, c_mat1( 2, 2 ), &
         urayt0( 3 ), urayt1( 3 ), urayt2( 3 ), h, halfh, hw0, hw1, w0, w1, rho
    COMPLEX( KIND=8 ) :: d_phi0, d_phi1
    REAL  (KIND=8 ) :: d_p_tilde0( 2 ), d_p_hat0( 2 ), d_q_tilde0( 2 ), d_q_hat0( 2 ), &
                       d_p_tilde1( 2 ), d_p_hat1( 2 ), d_q_tilde1( 2 ), d_q_hat1( 2 )
    REAL  (KIND=8 ) :: gradcjump( 3 ), csjump, cn1jump, cn2jump, tBdry( 3 ), nBdry( 3 ), RM, R1, R2, Tg, Th, &
         rayt( 3 ), rayn1( 3 ), rayn2( 3 )
    REAL  (KIND=8 ) :: e1( 3 ), e2( 3 )                              ! ray normals for ray-centered coordinates
    REAL  (KIND=8 ) :: p_tilde_in(  2 ), p_hat_in(  2 ), q_tilde_in(  2 ), q_hat_in(  2 ), &
         p_tilde_out( 2 ), p_hat_out( 2 ), RotMat( 2, 2 )

    IF ( STEP_DEBUGGING ) THEN
       WRITE( PRTFile, * )
       WRITE( PRTFile, * ) 'ray0 x t', ray0%x, ray0%t
       WRITE( PRTFile, * ) 'ray0 p q', ray0%p_tilde, ray0%q_tilde
       WRITE( PRTFile, * ) '        ', ray0%p_hat,   ray0%q_hat
       WRITE( PRTFile, * ) 'iSegx iSegy iSegz Top_td_side Bot_td_side', iSegx, iSegy, iSegz, Top_td_side, Bot_td_side
    END IF

    ! The numerical integrator used here is a version of the polygon (a.k.a. midpoint, leapfrog, or Box method), and similar
    ! to the Heun (second order Runge-Kutta method).
    ! However, it's modified to allow for a dynamic step change, while preserving the second-order accuracy).

    ! *** Phase 1 (an Euler step)

    !write( *, * ) '______________________________________________________________'
    !write( *, * ) 'in segment ', ISegBoty
    !write( *, * ) 'current coord ', ray0%x

    CALL EvaluateSSP3D( ray0%x, ray0%t, c0, cimag0, gradc0, cxx0, cyy0, czz0, cxy0, cxz0, cyz0, rho, freq, 'TAB' )
    CALL RayNormal( ray0%t, ray0%phi, c0, e1, e2 ) ! Compute ray normals e1 and e2
    CALL Get_c_partials( cxx0, cxy0, cxz0, cyy0, cyz0, czz0, e1, e2, cnn0, cmn0, cmm0 ) ! Compute second partials of c along ray normals
    CALL ComputeDeltaPQ( ray0, c0, gradc0, cnn0, cmn0, cmm0, d_phi0, d_p_tilde0, d_p_hat0, d_q_tilde0, d_q_hat0)


    iSegx0 = iSegx     ! make note of current layer
    iSegy0 = iSegy
    iSegz0 = iSegz

    csq0   = c0 * c0
    urayt0 = c0 * ray0%t  ! unit tangent
    h = Beam%deltas       ! initially set the step h, to the basic one, deltas

    CALL ReduceStep3D( ray0%x, urayt0, iSegx0, iSegy0, iSegz0, h ) ! reduce h to land on boundary
    halfh = 0.5 * h       ! first step of the modified polygon method is a half step

    ray1%x = ray0%x    + halfh * urayt0
    ray1%t = ray0%t    - halfh * gradc0 / csq0
    CALL UpdateRayPQ ( ray1, ray0, halfh, d_phi0, d_p_tilde0, d_p_hat0, d_q_tilde0, d_q_hat0 )

    ! *** Phase 2

    CALL EvaluateSSP3D( ray1%x, ray1%t, c1, cimag1, gradc1, cxx1, cyy1, czz1, cxy1, cxz1, cyz1, rho, freq, 'TAB' )
    ! LP: Fixed; should be ray1%phi; ray2%phi would be uninitialized memory or
    ! left over from the previous ray
    CALL RayNormal( ray1%t, ray1%phi, c1, e1, e2 )
    CALL Get_c_partials( cxx1, cxy1, cxz1, cyy1, cyz1, czz1, e1, e2, cnn1, cmn1, cmm1 ) ! Compute second partials of c along ray normals
    CALL ComputeDeltaPQ( ray1, c1, gradc1, cnn1, cmn1, cmm1, d_phi1, d_p_tilde1, d_p_hat1, d_q_tilde1, d_q_hat1)

    csq1   = c1 * c1
    urayt1 = c1 * ray1%t   ! unit tangent

    CALL ReduceStep3D( ray0%x, urayt1, iSegx0, iSegy0, iSegz0, h ) ! reduce h to land on boundary

    ! use blend of f' based on proportion of a full step used.
    w1  = h / ( 2.0d0 * halfh )
    w0  = 1.0d0 - w1
    urayt2 = w0 * urayt0 + w1 * urayt1
    ! Take the blended ray tangent ( urayt2 ) and find the minimum step size ( h )
    ! to put this on a boundary, and ensure that the resulting position
    ! ( ray2%x ) gets put precisely on the boundary.
    CALL StepToBdry3D( ray0%x, ray2%x, urayt2, iSegx0, iSegy0, iSegz0, h, &
       topRefl, botRefl, snapDim )
    !write( *, * ) 'final coord ', ray2%x, w0, w1

    ! Update other variables with this new h
    ! LP: Fixed: ray2%phi now depends on hw0 & hw1 like the other parameters,
    ! originally only depended on h and ray1 vars
    hw0 = h * w0
    hw1 = h * w1
    ray2%t   = ray0%t   - hw0 * gradc0 / csq0 - hw1 * gradc1 / csq1
    ray2%tau = ray0%tau + hw0 / CMPLX( c0, cimag0, KIND=8 ) + hw1 / CMPLX( c1, cimag1, KIND=8 )
    CALL UpdateRayPQ ( ray2, ray0, hw0, d_phi0, d_p_tilde0, d_p_hat0, d_q_tilde0, d_q_hat0 )
    CALL UpdateRayPQ ( ray2, ray2, hw1, d_phi1, d_p_tilde1, d_p_hat1, d_q_tilde1, d_q_hat1 ) ! Not a typo, accumulating into 2

    IF ( STEP_DEBUGGING ) &
       WRITE( PRTFile, * ) 'ray2%t', ray2%t

    ray2%Amp       = ray0%Amp
    ray2%Phase     = ray0%Phase
    ray2%NumTopBnc = ray0%NumTopBnc
    ray2%NumBotBnc = ray0%NumBotBnc

    ! *** If we crossed an interface, apply jump condition ***

    CALL EvaluateSSP3D( ray2%x, ray2%t, c2, cimag2, gradc2, cxx2, cyy2, czz2, cxy2, cxz2, cyz2, rho, freq, 'TAB' )

    ray2%c = c2

    IF ( iSegx /= iSegx0 .OR. &
         iSegy /= iSegy0 .OR. &
         iSegz /= iSegz0 ) THEN

       gradcjump =  gradc2 - gradc0  ! this is precise only for c-linear layers

!!! what if we cross isegx, isegy, or isegz at the same time?
       IF ( iSegz /= iSegz0 ) THEN
          nBdry = [ 0D0, 0D0, -SIGN( 1.0D0, ray2%t( 3 ) ) ]   ! inward normal to layer
       ELSE IF ( iSegx /= iSegx0 ) THEN
          nBdry = [ -SIGN( 1.0D0, ray2%t( 1 ) ), 0D0, 0D0 ]   ! inward normal to x-segment
       ELSE
          nBdry = [ 0D0, -SIGN( 1.0D0, ray2%t( 2 ) ), 0D0 ]   ! inward normal to y-segment
       END IF
       CALL CurvatureCorrection

    END IF

    ! WRITE( PRTFile, * ) 'ray2 p q', ray2%p_tilde, ray2%q_tilde
    ! WRITE( PRTFile, * ) '        ', ray2%p_hat,   ray2%q_hat

  CONTAINS
    SUBROUTINE CurvatureCorrection

      ! correct p-q due to jumps in the gradient of the sound speed

      Th    = DOT_PRODUCT( ray2%t, nBdry )   ! component of ray tangent, normal to boundary
      tBdry = ray2%t - Th * nBdry            ! tangent, along the boundary, in the reflection plane
      tBdry = tBdry / NORM2( tBdry )         ! unit boundary tangent
      Tg    = DOT_PRODUCT( ray2%t, tBdry )   ! component of ray tangent, along the boundary

      rayt = c2 * ray2%t                     ! unit tangent to ray

      rayn2 = cross_product( rayt, nBdry )   ! ray tangent x boundary normal gives refl. plane normal
      rayn2 = rayn2 / NORM2( rayn2 )         ! unit normal
      rayn1 = -cross_product( rayt, rayn2 )  ! ray tangent x refl. plane normal is first ray normal

      ! normal and tangential derivatives of the sound speed
      cn1jump = DOT_PRODUCT( gradcjump, rayn1 )
      cn2jump = DOT_PRODUCT( gradcjump, rayn2 )
      csjump  = DOT_PRODUCT( gradcjump, rayt  )

      ! WRITE( PRTFile, * ) 'cn1 cn2 cs jumps', cn1jump, cn2jump, csjump

      RM = Tg / Th   ! this is tan( alpha ) where alpha is the angle of incidence
      R1 = RM * ( 2 * cn1jump - RM * csjump ) / c2 ** 2
      R2 = RM * cn2jump / c2 ** 2

      ! *** curvature correction ***

      CALL RayNormal_unit( rayt, ray2%phi, e1, e2 )   ! Compute ray normals e1 and e2

      RotMat( 1, 1 ) = DOT_PRODUCT( rayn1, e1 )
      RotMat( 1, 2 ) = DOT_PRODUCT( rayn1, e2 )
      RotMat( 2, 1 ) = -RotMat( 1, 2 )             ! DOT_PRODUCT( rayn2, e1 )
      RotMat( 2, 2 ) = DOT_PRODUCT( rayn2, e2 )

      ! rotate p-q values in e1, e2 system, onto rayn1, rayn2 system

      p_tilde_in = RotMat( 1, 1 ) * ray2%p_tilde + RotMat( 1, 2 ) * ray2%p_hat
      p_hat_in   = RotMat( 2, 1 ) * ray2%p_tilde + RotMat( 2, 2 ) * ray2%p_hat

      q_tilde_in = RotMat( 1, 1 ) * ray2%q_tilde + RotMat( 1, 2 ) * ray2%q_hat
      q_hat_in   = RotMat( 2, 1 ) * ray2%q_tilde + RotMat( 2, 2 ) * ray2%q_hat

      ! here's the actual curvature change

      p_tilde_out = p_tilde_in - q_tilde_in * R1 - q_hat_in * R2
      p_hat_out   = p_hat_in   - q_tilde_in * R2

      ! rotate p back to e1, e2 system, q does not change
      ! Note RotMat^(-1) = RotMat^T

      ray2%p_tilde = RotMat( 1, 1 ) * p_tilde_out + RotMat( 2, 1 ) * p_hat_out
      ray2%p_hat   = RotMat( 1, 2 ) * p_tilde_out + RotMat( 2, 2 ) * p_hat_out

    END SUBROUTINE CurvatureCorrection

    ! **********************************************************************!

    SUBROUTINE Get_c_partials( cxx, cxy, cxz, cyy, cyz, czz, e1, e2, cnn, cmn, cmm )

      ! Computes the second partials of c along ray normals

      REAL (KIND=8), INTENT( IN  ) :: cxx, cxy, cxz, cyy, cyz, czz  ! curvature of sound speed (cartesian)
      REAL (KIND=8), INTENT( IN  ) :: e1( 3 ), e2( 3 )              ! principal normals
      REAL (KIND=8), INTENT( OUT ) :: cnn, cmn, cmm                 ! curvature of sound speed (ray-centered)

      cnn = cxx * e1( 1 )**2 + cyy * e1( 2 )**2 + czz * e1( 3 )**2 + 2.0 * cxy * e1( 1 ) * e1( 2 ) + &
           2.0 * cxz * e1( 1 ) * e1( 3 ) + 2.0 * cyz * e1( 2 ) * e1( 3 )

      cmn = cxx * e1( 1 ) * e2( 1 ) + cyy * e1( 2 ) * e2( 2 ) + czz * e1( 3 ) * e2( 3 ) + &
           cxy * ( e1( 1 ) * e2( 2 ) + e2( 1 ) * e1( 2 ) ) + cxz * ( e1( 1 ) * e2( 3 ) + e2( 1 ) * e1( 3 ) ) +  &
           cyz * ( e1( 2 ) * e2( 3 ) + e2( 2 ) * e1( 3 ) )

      cmm = cxx * e2( 1 )**2 + cyy * e2( 2 )**2 + czz * e2( 3 )**2 + 2.0 * cxy * e2( 1 ) * e2( 2 ) + &
           2.0 * cxz * e2( 1 ) * e2( 3 ) + 2.0 * cyz * e2( 2 ) * e2( 3 )

      RETURN
    END SUBROUTINE Get_c_partials
  END SUBROUTINE Step3D

  SUBROUTINE ComputeDeltaPQ( ray, c, gradc, cnn, cmn, cmm, d_phi, d_p_tilde, d_p_hat, d_q_tilde, d_q_hat )
    TYPE(    ray3DPt ), INTENT( IN )  :: ray
    REAL(    KIND=8  ), INTENT( IN )  :: c, gradc( 3 ), cnn, cmn, cmm
    COMPLEX( KIND=8  ), INTENT( OUT ) :: d_phi
    REAL(    KIND=8  ), INTENT( OUT ) :: d_p_tilde( 2 ), d_p_hat( 2 ), d_q_tilde( 2 ), d_q_hat( 2 )
    REAL(    KIND=8  )                :: c_mat( 2, 2 ), csq

    d_phi = ( 1.0 / c ) * ray%t( 3 ) * &
         ( ray%t( 2 ) * gradc( 1 ) - ray%t( 1 ) * gradc( 2 ) ) / &
         ( ray%t( 1 ) ** 2 + ray%t( 2 ) ** 2 )

    csq = c ** 2
    c_mat( 1, : ) = -[ cnn, cmn ] / csq
    c_mat( 2, : ) = -[ cmn, cmm ] / csq

    d_p_tilde = MATMUL( c_mat, ray%q_tilde )
    d_p_hat   = MATMUL( c_mat, ray%q_hat )

    d_q_tilde =         c    * ray%p_tilde
    d_q_hat   =         c    * ray%p_hat

    ! d_f    = ( c0 * ray0%DetP - cnn0 / csq0 * ray0%DetQ )
    ! d_g    = ( c0 * ray0%DetP - cmm0 / csq0 * ray0%DetQ )
    ! d_h    = (                - cmn0 / csq0 * ray0%DetQ )
    ! d_DetP = 1.0D0 / csq0 * ( -cmm0 * ray0%f - cnn0 * ray0%g + 2.0 * cmn0 * ray0%h )
    ! d_DetQ = c0 * ( ray0%f + ray0%g )

  END SUBROUTINE ComputeDeltaPQ

  SUBROUTINE UpdateRayPQ ( ray1, ray0, h, d_phi, d_p_tilde, d_p_hat, d_q_tilde, d_q_hat )
    TYPE(    ray3DPt ), INTENT( INOUT ) :: ray1
    TYPE(    ray3DPt ), INTENT( IN ) :: ray0
    REAL(    KIND=8  ), INTENT( IN ) :: h
    COMPLEX( KIND=8  ), INTENT( IN ) :: d_phi
    REAL(    KIND=8  ), INTENT( IN ) :: d_p_tilde( 2 ), d_p_hat( 2 ), d_q_tilde( 2 ), d_q_hat( 2 )

    ray1%phi = ray0%phi + h * d_phi
    ray1%p_tilde = ray0%p_tilde + h * d_p_tilde
    ray1%q_tilde = ray0%q_tilde + h * d_q_tilde
    ray1%p_hat   = ray0%p_hat   + h * d_p_hat
    ray1%q_hat   = ray0%q_hat   + h * d_q_hat

    ! LP: no longer missing the hw0 / hw1 blend
    ! ray1%f    = ray0%f    + h * d_f
    ! ray1%g    = ray0%g    + h * d_g
    ! ray1%h    = ray0%h    + h * d_h
    ! ray1%DetP = ray0%DetP + h * d_DetP
    ! ray1%DetQ = ray0%DetQ + h * d_DetQ

  END SUBROUTINE UpdateRayPQ

  ! **********************************************************************!

  SUBROUTINE ReduceStep3D( x0, urayt, iSegx0, iSegy0, iSegz0, h )

    INTEGER,       INTENT( IN    ) :: iSegx0, iSegy0, iSegz0
    REAL (KIND=8), INTENT( IN    ) :: x0( 3 ), urayt( 3 )  ! ray coordinate and tangent
    REAL (KIND=8), INTENT( INOUT ) :: h                    ! reduced step size
    REAL (KIND=8) :: hInt, hBoxx, hBoxy, hBoxz, hTop, hBot, hxSeg, hySeg, hTopDiag, hBotDiag ! step sizes
    REAL (KIND=8) :: d( 3 ), d0( 3 ), tri_n( 3 )
    REAL (KIND=8) :: x( 3 )                                ! ray coordinate after full trial step
    REAL (KIND=8) :: xSeg( 2 ), ySeg( 2 )
    REAL (KIND=8) :: over_diag_amount
    LOGICAL       :: newSide, newOnEdge

    IF ( STEP_DEBUGGING ) &
       WRITE( PRTFile, * ) 'ReduceStep3D'

    ! Detect SSP interface or boundary crossing and reduce step, if necessary, to land on that crossing.
    ! Keep in mind possibility that user put source right on an interface
    ! and that multiple events can occur (crossing interface, top, and bottom in a single step).

    x = x0 + h * urayt ! make a trial step

    ! interface crossing in depth
    ! Step reduction is not done for the top or bottom layer
    ! Instead the SSP is extrapolated
    ! This prevents problems when the boundaries are outside the domain of the SSP
    hInt = huge( hInt )
    IF ( ABS( urayt( 3 ) ) > EPSILON( hInt ) ) THEN
       IF        ( SSP%z( iSegz0     ) > x(  3 ) .AND. iSegz0     > 1  ) THEN
          hInt = ( SSP%z( iSegz0     ) - x0( 3 ) ) / urayt( 3 )
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'Shallower bound SSP Z > z; hInt', SSP%z( iSegz0     ), x( 3 ), hInt
       ELSE IF   ( SSP%z( iSegz0 + 1 ) < x(  3 ) .AND. iSegz0 + 1 < SSP%Nz ) THEN
          hInt = ( SSP%z( iSegz0 + 1 ) - x0( 3 ) ) / urayt( 3 )
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'Deeper bound SSP Z < z; hInt', SSP%z( iSegz0 + 1 ), x( 3 ), hInt
       END IF
    END IF

    ! ray mask using a box centered at ( xs_3D( 1 ), xs_3D( 2 ), 0 )
    hBoxx    = huge( hBoxx )
    IF ( ABS( x( 1 ) - xs_3D( 1 ) ) > Beam%Box%x ) THEN
       hBoxx = ( Beam%Box%x - ABS( ( x0( 1 ) - xs_3D( 1 ) ) ) ) / ABS( urayt( 1 ) )
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'Beam box crossing X, hBoxx', Beam%Box%x, hBoxx
    END IF

    hBoxy    = huge( hBoxy )
    IF ( ABS( x( 2 ) - xs_3D( 2 ) ) > Beam%Box%y ) THEN
       hBoxy = ( Beam%Box%y - ABS( ( x0( 2 ) - xs_3D( 2 ) ) ) ) / ABS( urayt( 2 ) )
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'Beam box crossing Y, hBoxy', Beam%Box%y, hBoxy
    END IF

    hBoxz    = huge( hBoxz )
    IF ( ABS( x( 3 )              ) > Beam%Box%z ) THEN
       hBoxz = ( Beam%Box%z - ABS(   x0( 3 )                ) ) / ABS( urayt( 3 ) )
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'Beam box crossing Z, hBoxz', Beam%Box%z, hBoxz
    END IF

    ! top crossing
    hTop = huge( hTop )
    d    = x - Topx       ! vector from top to ray
    ! LP: Changed from EPSILON( hTop ) to 0 in conjunction with StepToBdry3D change here.
    IF ( DOT_PRODUCT( Topn, d )  >= 0.0D0 ) THEN
       d0   = x0 - Topx   ! vector from top    node to ray origin
       hTop = -DOT_PRODUCT( d0, Topn ) / DOT_PRODUCT( urayt, Topn )
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'Top crossing hTop', hTop
    END IF

    ! bottom crossing
    hBot = huge( hBot )
    d    = x - Botx       ! vector from bottom to ray
    ! LP: Changed from EPSILON( h1 ) (should have been h3!) to 0 in conjunction
    ! with StepToBdry3D change here.
    IF ( DOT_PRODUCT( Botn, d ) >= 0.0D0 ) THEN
       d0   = x0 - Botx   ! vector from bottom node to ray origin
       hBot = -DOT_PRODUCT( d0, Botn ) / DOT_PRODUCT( urayt, Botn )
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'Bottom crossing hBot', hBot
    END IF

    ! top/bottom/ocean segment crossing in x
    hxSeg = huge( hxSeg )
    xSeg( 1 ) = MAX( xTopSeg( 1 ), xBotSeg( 1 ) )
    xSeg( 2 ) = MIN( xTopSeg( 2 ), xBotSeg( 2 ) )

    IF ( SSP%Type == 'H' ) THEN   ! ocean segment
       xSeg( 1 ) = MAX( xSeg( 1 ), SSP%Seg%x( iSegx0     ) )
       xSeg( 2 ) = MIN( xSeg( 2 ), SSP%Seg%x( iSegx0 + 1 ) )
    END IF

    IF ( ABS( urayt( 1 ) ) > EPSILON( hxSeg ) ) THEN
       IF          ( x(  1 ) < xSeg( 1 ) ) THEN
          hxSeg = -( x0( 1 ) - xSeg( 1 ) ) / urayt( 1 )
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'Min bound SSP X > x; hxSeg', xSeg( 1 ), x( 1 ), hxSeg
       ELSE IF     ( x(  1 ) > xSeg( 2 ) ) THEN
          hxSeg = -( x0( 1 ) - xSeg( 2 ) ) / urayt( 1 )
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'Max bound SSP X < x; hxSeg', xSeg( 2 ), x( 1 ), hxSeg
       END IF
    END IF

    ! top/bottom/ocean segment crossing in y
    hySeg = huge( hySeg )
    ySeg( 1 ) = MAX( yTopSeg( 1 ), yBotSeg( 1 ) )
    ySeg( 2 ) = MIN( yTopSeg( 2 ), yBotSeg( 2 ) )

    IF ( SSP%Type == 'H' ) THEN   ! ocean segment
       ySeg( 1 ) = MAX( ySeg( 1 ), SSP%Seg%y( iSegy0     ) )
       ySeg( 2 ) = MIN( ySeg( 2 ), SSP%Seg%y( iSegy0 + 1 ) )
    END IF

    ! LP: removed 1000 * epsilon which mbp had comment questioning also
    IF ( ABS( urayt( 2 ) ) > EPSILON( hySeg ) ) THEN
       IF          ( x(  2 ) < ySeg( 1 ) ) THEN
          hySeg = -( x0( 2 ) - ySeg( 1 ) ) / urayt( 2 )
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'Min bound SSP Y > y; hySeg', ySeg( 1 ), x( 2 ), hySeg
       ELSE IF     ( x(  2 ) > ySeg( 2 ) ) THEN
          hySeg = -( x0( 2 ) - ySeg( 2 ) ) / urayt( 2 )
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'Max bound SSP Y < y; hySeg', ySeg( 2 ), x( 2 ), hySeg
       END IF
    END IF

    ! triangle crossing within a top segment
    hTopDiag = huge( hTopDiag )
    IF ( .NOT. Top_td_onEdge ) THEN
       d     = x  - Topxmid   ! vector from top    center to ray end
       d0    = x0 - Topxmid   ! vector from top    center to ray origin
       tri_n = [ -( yTopSeg( 2 ) - yTopSeg( 1 ) ), xTopSeg( 2 ) - xTopSeg( 1 ), 0.0d0 ]
       tri_n = tri_n / NORM2( tri_n )
       over_diag_amount = DOT_PRODUCT( d, tri_n )
       newSide = ( over_diag_amount >= 0.0D0 )
       IF ( newSide .NEQV. Top_td_side ) THEN
          newOnEdge = ( ABS( over_diag_amount ) < TRIDIAG_THRESH )
          IF ( newOnEdge ) THEN
             IF ( STEP_DEBUGGING ) &
                WRITE( PRTFile, * ) 'ReduceStep3D Top naturally stepped to tri diag edge, h over_diag_amount', &
                   over_diag_amount
          ELSE
             hTopDiag = -DOT_PRODUCT( d0, tri_n ) / DOT_PRODUCT( urayt, tri_n )
             IF ( hTopDiag < 0.0D0 ) THEN
                WRITE( PRTFile, * ) 'ReduceStep3D Top BHC_WARN_TRIDIAG_H_NEGATIVE'
                hTopDiag = 0.0D0
             END IF
             IF ( STEP_DEBUGGING ) &
                WRITE( PRTFile, * ) 'Top tri diag crossing hTopDiag dot(n, d0) dot(n, d)', &
                   hTopDiag, DOT_PRODUCT( tri_n, d0 ), over_diag_amount
          END IF
       END IF
    END IF

    ! triangle crossing within a bottom segment
    hBotDiag = huge( hBotDiag )
    IF ( .NOT. Bot_td_onEdge ) THEN
       d     = x  - Botxmid   ! vector from bottom center to ray end
       d0    = x0 - Botxmid   ! vector from bottom center to ray origin
       tri_n = [ -( yBotSeg( 2 ) - yBotSeg( 1 ) ), xBotSeg( 2 ) - xBotSeg( 1 ), 0.0d0 ]
       tri_n = tri_n / NORM2( tri_n )
       over_diag_amount = DOT_PRODUCT( d, tri_n )
       newSide = ( over_diag_amount >= 0.0D0 )
       IF ( newSide .NEQV. Bot_td_side ) THEN
          newOnEdge = ( ABS( over_diag_amount ) < TRIDIAG_THRESH )
          IF ( newOnEdge ) THEN
             IF ( STEP_DEBUGGING ) &
                WRITE( PRTFile, * ) 'ReduceStep3D Bot naturally stepped to tri diag edge, h over_diag_amount', &
                   over_diag_amount
          ELSE
             hBotDiag = -DOT_PRODUCT( d0, tri_n ) / DOT_PRODUCT( urayt, tri_n )
             IF ( hBotDiag < 0.0D0 ) THEN
                WRITE( PRTFile, * ) 'ReduceStep3D Bot BHC_WARN_TRIDIAG_H_NEGATIVE'
                hBotDiag = 0.0D0
             END IF
             IF ( STEP_DEBUGGING ) &
                WRITE( PRTFile, * ) 'Bot tri diag crossing hBotDiag dot(n, d0) dot(n, d)', &
                   hBotDiag, DOT_PRODUCT( tri_n, d0 ), over_diag_amount
          END IF
       END IF
    END IF

    h = MIN( h, hInt, hBoxx, hBoxy, hBoxz, hTop, hBot, hxSeg, hySeg, hTopDiag, hBotDiag )  ! take limit set by shortest distance to a crossing
    IF ( h < -1d-4 ) THEN
       WRITE( PRTFile, * ) 'ReduceStep3D: negative h'
       ! CALL ERROUT( 'ReduceStep3D', 'negative h' )
    END IF
    IF ( h < INFINITESIMAL_STEP_SIZE * Beam%deltas ) THEN        ! is it taking an infinitesimal step?
       h = INFINITESIMAL_STEP_SIZE * Beam%deltas                 ! make sure we make some motion
       iSmallStepCtr = iSmallStepCtr + 1   ! keep a count of the number of sequential small steps
    ELSE
       iSmallStepCtr = 0                   ! didn't do a small step so reset the counter
    END IF
  END SUBROUTINE ReduceStep3D

  ! **********************************************************************!

  SUBROUTINE StepToBdry3D( x0, x2, urayt, iSegx0, iSegy0, iSegz0, h, &
    topRefl, botRefl, snapDim )

    INTEGER,       INTENT( IN    ) :: iSegx0, iSegy0, iSegz0
    REAL (KIND=8), INTENT( IN    ) :: x0( 3 ), urayt( 3 )  ! ray coordinate and tangent
    REAL (KIND=8), INTENT( INOUT ) :: x2( 3 ), h           ! output coord, reduced step size
    LOGICAL,       INTENT( OUT   ) :: topRefl, botRefl
    INTEGER,       INTENT( OUT   ) :: snapDim
    REAL (KIND=8) :: d( 3 ), d0( 3 ), tri_n( 3 )
    REAL (KIND=8) :: xSeg( 2 ), ySeg( 2 )                  ! boundary limits
    INTEGER                        :: k
    REAL (KIND=8) :: hnew, over_diag_amount
    LOGICAL       :: newSide, newOnEdge

    ! Original step due to maximum step size
    h = Beam%deltas
    x2 = x0 + h * urayt
    snapDim = -1

    ! interface crossing in depth
    IF ( ABS( urayt( 3 ) ) > EPSILON( h ) ) THEN
       IF      ( SSP%z( iSegz0     ) > x2( 3 ) .AND. iSegz0     > 1  ) THEN
          h  = ( SSP%z( iSegz0     ) - x0( 3 ) ) / urayt( 3 )
          x2 = x0 + h * urayt
          x2( 3 ) = SSP%z( iSegz0 )
          snapDim = 2
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'StepToBdry3D shallower h to', h, x2
       ELSE IF ( SSP%z( iSegz0 + 1 ) < x2( 3 ) .AND. iSegz0 + 1 < SSP%Nz ) THEN
          h  = ( SSP%z( iSegz0 + 1 ) - x0( 3 ) ) / urayt( 3 )
          x2 = x0 + h * urayt
          x2( 3 ) = SSP%z( iSegz0 + 1 )
          snapDim = 2
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'StepToBdry3D deeper h to', h, x2
       END IF
    END IF

    ! ray mask using a box centered at ( xs_3D( 1 ), xs_3D( 2 ), 0 )
    IF ( ABS( x2( 1 ) - xs_3D( 1 ) ) > Beam%Box%x ) THEN
       h = ( Beam%Box%x - ABS( ( x0( 1 ) - xs_3D( 1 ) ) ) ) / ABS( urayt( 1 ) )
       x2 = x0 + h * urayt
       x2( 1 ) = xs_3D( 1 ) + SIGN( Beam%Box%x, x0( 1 ) - xs_3D( 1 ) )
       snapDim = 0
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'StepToBdry3D beam box crossing X h to', h, x2
    END IF
    IF ( ABS( x2( 2 ) - xs_3D( 2 ) ) > Beam%Box%y ) THEN
       h = ( Beam%Box%y - ABS( ( x0( 2 ) - xs_3D( 2 ) ) ) ) / ABS( urayt( 2 ) )
       x2 = x0 + h * urayt
       x2( 2 ) = xs_3D( 2 ) + SIGN( Beam%Box%y, x0( 2 ) - xs_3D( 2 ) )
       snapDim = 1
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'StepToBdry3D beam box crossing Y h to', h, x2
    END IF
    IF ( ABS( x2( 3 )              ) > Beam%Box%z ) THEN
       h = ( Beam%Box%z - ABS(   x0( 3 )                ) ) / ABS( urayt( 3 ) )
       x2 = x0 + h * urayt
       x2( 3 ) =              SIGN( Beam%Box%z, x0( 3 )              )
       snapDim = 2
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'StepToBdry3D beam box crossing Y h to', h, x2
    END IF

    ! top crossing
    d  = x2 - Topx ! vector from top to ray
    ! LP: Originally, this value had to be > a small positive number, meaning the
    ! new step really had to be outside the boundary, not just to the boundary.
    ! Also, this is not missing a normalization factor, Topn is normalized so
    ! this is actually the distance above the top in meters.
    IF ( DOT_PRODUCT( Topn, d )  > -INFINITESIMAL_STEP_SIZE ) THEN
       d0 = x0 - Topx   ! vector from top    node to ray origin
       h  = -DOT_PRODUCT( d0, Topn ) / DOT_PRODUCT( urayt, Topn )
       x2 = x0 + h * urayt
       ! Snap to exact top depth value if it's flat
       IF ( ABS( Topn( 1 ) ) < EPSILON( Topn( 1 ) ) .AND. ABS( Topn( 2 ) ) < EPSILON( Topn( 2 ) ) ) THEN
          x2( 3 ) = Topx( 3 )
       END IF
       snapDim = 2 ! Even if not flat, exactness in Z most important
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'StepToBdry3D top crossing h to', h, x2
       topRefl = .TRUE.
    ELSE
       topRefl = .FALSE.
    END IF

    ! bottom crossing
    d  = x2 - Botx ! vector from bottom to ray
    ! LP: See comment above for top case.
    IF ( DOT_PRODUCT( Botn, d ) > -INFINITESIMAL_STEP_SIZE ) THEN
       d0 = x0 - Botx   ! vector from bottom node to ray origin
       h  = -DOT_PRODUCT( d0, Botn ) / DOT_PRODUCT( urayt, Botn )
       x2 = x0 + h * urayt
       ! Snap to exact bottom depth value if it's flat
       IF ( ABS( Botn( 1 ) ) < EPSILON( Botn( 1 ) ) .AND. ABS( Botn( 2 ) ) < EPSILON( Botn( 2 ) ) ) THEN
          x2( 3 ) = Botx( 3 )
       END IF
       snapDim = 2 ! Even if not flat, exactness in Z most important
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'StepToBdry3D bottom crossing h to', h, x2
       botRefl = .TRUE.
       ! Should not ever be able to cross both, but in case it does, make sure
       ! only the crossing we exactly landed on is active
       topRefl = .FALSE.
    ELSE
       botRefl = .FALSE.
    END IF

    ! top/bottom segment crossing in x
    xSeg( 1 ) = MAX( xTopSeg( 1 ), xBotSeg( 1 ) ) ! LP: lower range bound (not an x value)
    xSeg( 2 ) = MIN( xTopSeg( 2 ), xBotSeg( 2 ) ) ! LP: upper range bound (not a y value)

    IF ( SSP%Type == 'H' ) THEN   ! ocean segment
       xSeg( 1 ) = MAX( xSeg( 1 ), SSP%Seg%x( iSegx0     ) )
       xSeg( 2 ) = MIN( xSeg( 2 ), SSP%Seg%x( iSegx0 + 1 ) )
    END IF

    IF ( ABS( urayt( 1 ) ) > EPSILON( h ) ) THEN
       IF       ( x2( 1 ) < xSeg( 1 ) ) THEN
          h  = -( x0( 1 ) - xSeg( 1 ) ) / urayt( 1 )
          x2 = x0 + h * urayt
          x2( 1 ) = xSeg( 1 )
          snapDim = 0
          topRefl = .FALSE.
          botRefl = .FALSE.
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'StepToBdry3D X min bound h to', h, x2
       ELSE IF  ( x2( 1 ) > xSeg( 2 ) ) THEN
          h  = -( x0( 1 ) - xSeg( 2 ) ) / urayt( 1 )
          x2 = x0 + h * urayt
          x2( 1 ) = xSeg( 2 )
          snapDim = 0
          topRefl = .FALSE.
          botRefl = .FALSE.
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'StepToBdry3D X max bound h to', h, x2
       END IF
    END IF

    ! top/bottom segment crossing in y
    ySeg( 1 ) = MAX( yTopSeg( 1 ), yBotSeg( 1 ) )
    ySeg( 2 ) = MIN( yTopSeg( 2 ), yBotSeg( 2 ) )

    IF ( SSP%Type == 'H' ) THEN   ! ocean segment
       ySeg( 1 ) = MAX( ySeg( 1 ), SSP%Seg%y( iSegy0     ) )
       ySeg( 2 ) = MIN( ySeg( 2 ), SSP%Seg%y( iSegy0 + 1 ) )
    END IF

    ! LP: removed 1000 * epsilon which mbp had comment questioning also
    IF ( ABS( urayt( 2 ) ) > EPSILON( h ) ) THEN
       IF       ( x2( 2 ) < ySeg( 1 ) ) THEN
          h  = -( x0( 2 ) - ySeg( 1 ) ) / urayt( 2 )
          x2 = x0 + h * urayt
          x2( 2 ) = ySeg( 1 )
          snapDim = 1
          topRefl = .FALSE.
          botRefl = .FALSE.
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'StepToBdry3D Y min bound h to', h, x2
       ELSE IF  ( x2( 2 ) > ySeg( 2 ) ) THEN
          h  = -( x0( 2 ) - ySeg( 2 ) ) / urayt( 2 )
          x2 = x0 + h * urayt
          x2( 2 ) = ySeg( 2 )
          snapDim = 1
          topRefl = .FALSE.
          botRefl = .FALSE.
          IF ( STEP_DEBUGGING ) &
             WRITE( PRTFile, * ) 'StepToBdry3D Y max bound h to', h, x2
       END IF
    END IF

    ! triangle crossing within a top segment
    IF ( .NOT. Top_td_onEdge ) THEN
       d     = x2 - Topxmid   ! vector from top    center to ray end
       d0    = x0 - Topxmid   ! vector from top    center to ray origin
       tri_n = [ -( yTopSeg( 2 ) - yTopSeg( 1 ) ), xTopSeg( 2 ) - xTopSeg( 1 ), 0.0d0 ]
       tri_n = tri_n / NORM2( tri_n )
       over_diag_amount = DOT_PRODUCT( d, tri_n )
       newSide = ( over_diag_amount >= 0.0D0 )
       IF ( newSide .NEQV. Top_td_side ) THEN
          newOnEdge = ( ABS( over_diag_amount ) < TRIDIAG_THRESH )
          IF ( newOnEdge ) THEN
             IF ( STEP_DEBUGGING ) &
                WRITE( PRTFile, * ) 'StepToBdry3D Top naturally stepped to tri diag edge, h over_diag_amount', &
                   over_diag_amount
          ELSE
             hnew = -DOT_PRODUCT( d0, tri_n ) / DOT_PRODUCT( urayt, tri_n )
             IF ( hnew < 0.0D0 ) THEN
                WRITE( PRTFile, * ) 'StepToBdry3D Top BHC_WARN_TRIDIAG_H_NEGATIVE'
                h = 0.0D0
             ELSE
                IF ( hnew >= h ) &
                   WRITE( PRTFile, * ) 'StepToBdry3D Top BHC_WARN_TRIDIAG_H_GROWING'
                h = hnew
             END IF
             IF ( STEP_DEBUGGING ) &
                WRITE( PRTFile, * ) 'StepToBdry3D Top tri diag crossing hnew dot(n, d0) dot(n, d)', &
                   hnew, DOT_PRODUCT( tri_n, d0 ), over_diag_amount
             x2 = x0 + h * urayt
             Top_td_justSteppedTo = .TRUE.
             Top_td_outgoingSide = newSide
             snapDim = -2
             topRefl = .FALSE.
             botRefl = .FALSE.
          END IF
       END IF
    END IF

    ! triangle crossing within a bottom segment
    IF ( .NOT. Bot_td_onEdge ) THEN
       d     = x2 - Botxmid   ! vector from bottom center to ray end
       d0    = x0 - Botxmid   ! vector from bottom center to ray origin
       tri_n = [ -( yBotSeg( 2 ) - yBotSeg( 1 ) ), xBotSeg( 2 ) - xBotSeg( 1 ), 0.0d0 ]
       tri_n = tri_n / NORM2( tri_n )
       over_diag_amount = DOT_PRODUCT( d, tri_n )
       newSide = ( over_diag_amount >= 0.0D0 )
       IF ( newSide .NEQV. Bot_td_side ) THEN
          newOnEdge = ( ABS( over_diag_amount ) < TRIDIAG_THRESH )
          IF ( newOnEdge ) THEN
             IF ( STEP_DEBUGGING ) &
                WRITE( PRTFile, * ) 'StepToBdry3D Bot naturally stepped to tri diag edge, h over_diag_amount', &
                   over_diag_amount
          ELSE
             hnew = -DOT_PRODUCT( d0, tri_n ) / DOT_PRODUCT( urayt, tri_n )
             IF ( hnew < 0.0D0 ) THEN
                WRITE( PRTFile, * ) 'StepToBdry3D Bot BHC_WARN_TRIDIAG_H_NEGATIVE'
                h = 0.0D0
             ELSE
                IF ( hnew >= h ) &
                   WRITE( PRTFile, * ) 'StepToBdry3D Bot BHC_WARN_TRIDIAG_H_GROWING'
                h = hnew
             END IF
             IF ( STEP_DEBUGGING ) &
                WRITE( PRTFile, * ) 'StepToBdry3D Bot tri diag crossing hnew dot(n, d0) dot(n, d)', &
                   hnew, DOT_PRODUCT( tri_n, d0 ), over_diag_amount
             x2 = x0 + h * urayt
             Bot_td_justSteppedTo = .TRUE.
             Bot_td_outgoingSide = newSide
             snapDim = -2
             topRefl = .FALSE.
             botRefl = .FALSE.
          END IF
       END IF
    END IF

    IF ( h < INFINITESIMAL_STEP_SIZE * Beam%deltas ) THEN        ! is it taking an infinitesimal step?
       h = INFINITESIMAL_STEP_SIZE * Beam%deltas                 ! make sure we make some motion
       x2 = x0 + h * urayt
       snapDim = -1
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'StepToBdry3D small step forced h to ', h, x2
       ! Recheck reflection conditions
       d = x2 - Topx ! vector from top to ray
       IF ( DOT_PRODUCT( Topn, d ) > EPSILON( d( 1 ) ) ) THEN
          topRefl = .TRUE.
          Top_td_justSteppedTo = .FALSE.
          Bot_td_justSteppedTo = .FALSE.
          snapDim = 2
       ELSE
          topRefl = .FALSE.
       END IF
       d = x2 - Botx ! vector from bottom to ray
       IF ( DOT_PRODUCT( Botn, d ) > EPSILON( d( 1 ) ) ) THEN
          botRefl = .TRUE.
          topRefl = .FALSE.
          Top_td_justSteppedTo = .FALSE.
          Bot_td_justSteppedTo = .FALSE.
          snapDim = 2
       ELSE
          botRefl = .FALSE.
       END IF
    ELSE
       IF ( STEP_DEBUGGING ) &
          WRITE( PRTFile, * ) 'StepToBdry3D regular h to ', h, x2
    END IF
  END SUBROUTINE StepToBdry3D

END MODULE Step3DMod