PROGRAM BELLHOP3D
!! Gaussian beam tracing for ocean acoustics in three dimensions
! Gaussian beam tracing in three dimensions
! Michael B. Porter
! Copyright (C) 2009 Michael B. Porter
! This program is free software: you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
! This program is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! GNU General Public License for more details.
! You should have received a copy of the GNU General Public License
! along with this program. If not, see <http://www.gnu.org/licenses/>.
! Original version done at NRL in 1986
! Published at SACLANT Undersea Research Center 1989
!
! Converted to Fortran 2003 and greatly enhanced for outside users in 2010
! with the support of the Agency for Defense Development, Republic of Korea
! Supported also by the U.S. Office of Naval Research
!
! Changes included:
! Use of standard BELLHOP input files
! Inclusion of multiple sources and receivers
! Integrated option for Nx2D or full 3D
! Implementation of several standard beam options for both the Nx2D and 3D cases
! Reading in:
! oceanography from a SSP file
! bathymetry or altimetry from a BTY or ATI file
! a reflection coefficient from a BRC or TRC file
! a source beam pattern from a SBP file
! Loose ends:
! Nx2D version does not handle jumps in cx, cy (routine step2d)
! cannot specify isingle( 2 ) for alpha and beta
! Trilinear interpolation (hex) ignores cxy values.
! If the water depth for the lower half space is much larger than that for the SSP
! in the water column, it will use a step that is too large and exit the ray box.
! Cerveny beams (rarely used):
! influenceC calls SSP; need to select EvaluateSSP2D or EvaluateSSP3D for that to work in BELLHOP3D
! efficiency changes for Cerveny beams as well (and in BELLHOP)
! space filling or minimum width options are applied to both alpha and beta--- often only want space filling in azimuth
! Influend3DGeoHat writes no eigenray info if number of receiver ranges NR=1
! r( 1 ) = 1 m in BELLHOP plus logic for reversing
! geogaussian should pre-calculate dtauds and dqds like geohat?
! fix automatic deltas selection
! detect Sz below bottom with bty file
! Should probably use a Structure of Arrays instead of an Array of Structures for speed
! Desired additional features:
! Terrain following option for receiver depth
! Variable bottom type vs.lat/long
USE bellhopMod
USE ReadEnvironmentBell
USE RefCoef
USE bdry3DMod
USE BeamPattern
USE sspMod
USE influence
USE Influence3D
USE FatalError
IMPLICIT NONE
CHARACTER ( LEN=80 ) :: FileRoot
ThreeD = .TRUE.
! get the file root for naming all input and output files
! should add some checks here ...
CALL GET_COMMAND_ARGUMENT( 1, FileRoot )
! Open the print file
OPEN( UNIT = PRTFile, FILE = TRIM( FileRoot ) // '.prt', STATUS = 'UNKNOWN', IOSTAT = iostat )
! Read in control data
CALL ReadEnvironment( FileRoot, ThreeD )
CALL ReadATI3D( FileRoot, Bdry%Top%HS%Opt( 5 : 5 ), Bdry%Top%HS%Depth, PRTFile ) ! AlTImetry
CALL ReadBTY3D( FileRoot, Bdry%Bot%HS%Opt( 2 : 2 ), Bdry%Bot%HS%Depth, PRTFile ) ! BaThYmetry
CALL ReadReflectionCoefficient( FileRoot, Bdry%Bot%HS%Opt( 1 : 1 ), Bdry%Top%HS%Opt( 2 : 2 ), PRTFile ) ! (top and bottom)
SBPFlag = Beam%RunType( 3 : 3 )
CALL ReadPAT( FileRoot, PRTFile ) ! Source Beam Pattern
CALL OpenOutputFiles( FileRoot, ThreeD )
CALL BellhopCore
CONTAINS
! **********************************************************************!
SUBROUTINE BellhopCore
!! Core subroutine to run Bellhop algorithm
USE angleMod
USE SourceReceiverPositions
USE ArrMod
USE WriteRay
! @note "LP"
! Was 400M; this translates to 16 GB of arrivals, which is half my computer's
! memory, so I can't run this and the C++/CUDA version in parallel. This large
! a size should not be necessary for most runs anyway, and it's not great for
! a program to allocate this kind of memory just as a default (rather than in
! response to the user specifying a very large problem size). So, cut in half.
! @endnote
INTEGER, PARAMETER :: ArrivalsStorage = 200000000
INTEGER :: IBPvec( 1 ), ibp, iBeamWindow2, irz, itheta, isx, isy, isz, iRec, ir
REAL ( KIND=8 ) :: Tstart, Tstop
REAL ( KIND=8 ) :: Amp0, RadMax, S
REAL ( KIND=8 ) :: c0, cimag0, gradc( 3 ), cxx, cyy, czz, cxy, cxz, cyz, rho
REAL ( KIND=8 ) :: tdummy( 3 )
REAL ( KIND=8 ), ALLOCATABLE :: x_rcvrMat( :, :, : ), t_rcvr( :, : )
COMPLEX ( KIND=8 ) :: epsilon( 2 )
COMPLEX, ALLOCATABLE :: P( :, :, : ), U( :, : )
CALL CPU_TIME( Tstart )
omega = 2.0 * pi * freq
IF ( Beam%deltas == 0.0 ) Beam%deltas = ( Bdry%Bot%HS%Depth - Bdry%Top%HS%Depth ) / 10.0 ! Automatic step size selection
Angles%alpha = DegRad * Angles%alpha ! convert to radians
Angles%Dalpha = 0.0
IF ( Angles%Nalpha /= 1 ) &
Angles%Dalpha = ( Angles%alpha( Angles%Nalpha ) - Angles%alpha( 1 ) ) / ( Angles%Nalpha - 1 ) ! angular spacing between beams
SELECT CASE ( Beam%RunType( 5 : 5 ) )
CASE ( 'I' )
NRz_per_range = 1 ! irregular grid
CASE ( 'R' )
NRz_per_range = Pos%NRz ! rectilinear grid
END SELECT
! for a TL calculation, allocate space for the pressure matrix
SELECT CASE ( Beam%RunType( 1 : 1 ) )
CASE ( 'C', 'S', 'I' ) ! TL calculation
ALLOCATE ( P( Pos%Ntheta, NRz_per_range, Pos%NRr ), Stat = IAllocStat )
ALLOCATE ( U( NRz_per_range, Pos%NRr ), Stat = IAllocStat ) ! used for 2D option
IF ( IAllocStat /= 0 ) &
CALL ERROUT( 'BELLHOP', 'Insufficient memory for TL matrix: reduce Nr * NRz' )
CASE ( 'A', 'a', 'R', 'E' ) ! Arrivals calculation
ALLOCATE ( P( 1, 1, 1 ), Stat = IAllocStat ) ! open a dummy variable
ALLOCATE ( U( 1, 1 ), Stat = IAllocStat ) ! open a dummy variable
END SELECT
! for an arrivals run, allocate space for arrivals matrices
SELECT CASE ( Beam%RunType( 1 : 1 ) )
CASE ( 'A', 'a' )
MaxNArr = MAX( ArrivalsStorage / ( Pos%Ntheta * NRz_per_range * Pos%NRr ), 10 ) ! allow space for at least 10 arrivals
WRITE( PRTFile, * )
WRITE( PRTFile, * ) '( Maximum # of arrivals = ', MaxNArr, ')'
ALLOCATE ( Arr3D( Pos%Ntheta, NRz_per_range, Pos%NRr, MaxNArr ), &
NArr3D( Pos%Ntheta, NRz_per_range, Pos%NRr ), Stat = IAllocStat )
IF ( IAllocStat /= 0 ) CALL ERROUT( 'BELLHOP', &
'Insufficient memory to allocate arrivals matrix; reduce parameter ArrivalsStorage' )
! For a 2D Arrivals run, also need to allocate a structure for that
IF ( Beam%RunType( 6 : 6 ) == '2' ) THEN
ALLOCATE ( Arr( NRz_per_range, Pos%NRr, MaxNArr ), &
NArr( NRz_per_range, Pos%NRr ), Stat = IAllocStat )
IF ( IAllocStat /= 0 ) CALL ERROUT( 'BELLHOP', &
'Insufficient memory to allocate arrivals matrix; reduce parameter ArrivalsStorage' )
NArr( 1 : NRz_per_range, 1 : Pos%NRr ) = 0
END IF
CASE DEFAULT
MaxNArr = 1
ALLOCATE ( Arr3D( Pos%Ntheta, NRz_per_range, Pos%NRr, 1 ), NArr3D( Pos%Ntheta, NRz_per_range, Pos%NRr ), Stat = IAllocStat )
END SELECT
ALLOCATE( x_rcvrMat( 2, Pos%Ntheta, Pos%NRr ), t_rcvr( 2, Pos%Ntheta ), Stat = IAllocStat )
IF ( IAllocStat /= 0 ) CALL ERROUT( 'BELLHOP', &
'Insufficient memory to x_rcvrMat; reduce Nr or Ntheta' )
! tangent along receiver bearing line
t_rcvr( 1, : ) = COS( DegRad * Pos%theta( 1 : Pos%Ntheta ) )
t_rcvr( 2, : ) = SIN( DegRad * Pos%theta( 1 : Pos%Ntheta ) )
WRITE( PRTFile, * )
Source_z: DO isz = 1, Pos%NSz ! loop over source z-coordinate
Source_x: DO isx = 1, Pos%Nsx ! loop over source x-coordinate
Source_y: DO isy = 1, Pos%Nsy ! loop over source y-coordinate
P = 0.0 ! Zero out field matrix
U = 0.0
NArr3D = 0 ! LP: Zero out 3D arrival matrix
! IF ( r( 1 ) == 0.0 ) r( 1 ) = 1.0
xs_3D = [ Pos%sx( isx ), Pos%sy( isy ), Pos%sz( isz ) ]
WRITE( PRTFile, * )
WRITE( PRTFile, "( 'xs = ', G11.3, 2X, G11.3, 2X, G11.3 )" ) xs_3D
! positions of rcvrs in the x-y plane; this is pre-calculated for InfluenceGeoHatCart
! It is not clear that the pre-calculation saves time ...
DO ir = 1, Pos%NRr
DO itheta = 1, Pos%Ntheta
x_rcvrMat( 1 : 2, itheta, ir ) = xs_3D( 1 : 2 ) + Pos%Rr( ir ) * t_rcvr( :, itheta ) ! x-y coordinate of the receiver
END DO
END DO
! *** Compute 'optimal' beam constant ***
tdummy = [ 0.0, 0.0, 1.0 ]
CALL EvaluateSSP3D( xs_3D, tdummy, c0, cimag0, gradc, cxx, cyy, czz, cxy, cxz, cyz, rho, freq, 'TAB' )
ray2D( 1 )%c = c0
ray3D( 1 )%c = c0
CALL PickEpsilon( Beam%Type( 1 : 2 ), omega, c0, Angles%Dalpha, Angles%Dbeta, Beam%rLoop, Beam%epsMultiplier, epsilon ) ! beam constant
! *** Trace successive beams ***
AzimuthalAngle: DO ibeta = 1, Angles%Nbeta ! this is also the receiver bearing angle for a 2D run
SrcAzimAngle = RadDeg * Angles%beta( ibeta ) ! take-off azimuthal angle in degrees
!if ( ibeta /= 134 ) cycle AzimuthalAngle
IF ( Angles%iSingle_beta == 0 .OR. ibeta == Angles%iSingle_beta ) THEN ! Single beam run?
!IF ( ibeta == 2 ) THEN ! Single beam run?
!IF ( mod( ibeta+1, 2 ) == 0 ) THEN ! Single beam run?
WRITE( PRTFile, FMT = "( 'Tracing azimuthal beam ', I4, F10.2 )" ) ibeta, SrcAzimAngle
FLUSH( PRTFile )
! WRITE( *, FMT = "( 'Tracing beam ', I4, F10.2 )" ) ibeta, RadDeg * Angles%beta( ibeta )
DeclinationAngle: DO ialpha = 1, Angles%Nalpha
SrcDeclAngle = RadDeg *Angles%alpha( ialpha ) ! take-off declination angle in degrees
IF ( Angles%iSingle_alpha == 0 .OR. ialpha == Angles%iSingle_alpha ) THEN ! Single beam run?
!IF ( ialpha == 11 .or. ialpha == 12 ) THEN ! Single beam run?
IF ( Angles%iSingle_alpha > 0 .OR. Angles%iSingle_beta > 0 .OR. &
Angles%Nalpha * Angles%Nbeta <= 20000 .OR. MOD( ialpha, 20 ) == 1 ) THEN
WRITE( PRTFile, FMT = "( ' Tracing declination beam ', I4, F10.2 )" ) ialpha, SrcDeclAngle
END IF
!flush( prtfile )
IBPvec = maxloc( SrcBmPat( :, 1 ), mask = SrcBmPat( :, 1 ) < SrcDeclAngle ) ! index of ray angle in beam pattern
IBP = IBPvec( 1 )
IBP = MAX( IBP, 1 ) ! don't go before beginning of table
IBP = MIN( IBP, NSBPPts - 1 ) ! don't go past end of table
! linear interpolation to get amplitude
s = ( SrcDeclAngle - SrcBmPat( IBP, 1 ) ) / ( SrcBmPat( IBP + 1, 1 ) - SrcBmPat( IBP, 1 ) )
Amp0 = ( 1 - s ) * SrcBmPat( IBP, 2 ) + s * SrcBmPat( IBP + 1, 2 )
! Lloyd mirror pattern for semi-coherent option
IF ( Beam%RunType( 1 : 1 ) == 'S' ) &
Amp0 = Amp0 * SQRT( 2.0 ) * ABS( SIN( omega / c0 * xs_3D( 3 ) * SIN( Angles%alpha( ialpha ) ) ) )
SELECT CASE ( Beam%RunType( 6 : 6 ) ) ! flag for 2D or 3D calculation
CASE ( '2' ) ! Nx2D calculation, neglecting horizontal refraction
CALL TraceRay2D( Angles%alpha( ialpha ), Angles%beta( ibeta ), Amp0 )
IF ( Beam%RunType( 1 : 1 ) /= 'R' ) THEN ! If not a ray trace run, calculate the field
SELECT CASE ( Beam%Type( 1 : 1 ) )
CASE ( 'R' )
iBeamWindow2 = Beam%iBeamWindow ** 2
RadMax = 50 * c0 / freq ! 50 wavelength max radius
CALL InfluenceCervenyRayCen( U, epsilon( 1 ), Angles%alpha( ialpha ), IBeamWindow2, RadMax )
CASE ( 'C' )
CALL ERROUT( 'BELLHOP3D', 'Run Type ''C'' not supported at this time' )
iBeamWindow2 = Beam%iBeamWindow ** 2
RadMax = 50 * c0 / freq ! 50 wavelength max radius
CALL InfluenceCervenyCart( U, epsilon( 1 ), Angles%alpha( ialpha ), IBeamWindow2, RadMax )
CASE ( 'g' )
CALL InfluenceGeoHatRayCen( U, Angles%alpha( ialpha ), Angles%Dalpha )
CASE ( 'S' )
RadMax = 50 * c0 / freq ! LP: Was missing / uninitialized.
CALL InfluenceSGB( U, Angles%alpha( ialpha ), Angles%Dalpha, RadMax )
CASE ( 'B' )
CALL InfluenceGeoGaussianCart( U, Angles%alpha( ialpha ), Angles%Dalpha )
CASE ( 'G', '^', ' ' )
CALL InfluenceGeoHatCart( U, Angles%alpha( ialpha ), Angles%Dalpha )
CASE DEFAULT
CALL ERROUT( 'BELLHOP3D', 'Invalid Run Type' )
END SELECT
END IF
CASE ( '3' ) ! full 3D calculation
CALL TraceRay3D( Angles%alpha( ialpha ), Angles%beta( ibeta ), epsilon, Amp0 )
IF ( Beam%RunType( 1 : 1 ) /= 'R' ) THEN ! If not a ray trace run, calculate the field
SELECT CASE ( Beam%RunType( 2 : 2 ) )
CASE ( 'C' ) ! Cerveny style beams
CALL ERROUT( 'BELLHOP3D', 'Run Type ''C'' not supported at this time' )
! option: assemble f, g, h from p-q
!ray3D( 1 : Beam%Nsteps )%f = ray3D( 1 : Beam%Nsteps )%p_tilde( 1 ) * &
! ray3D( 1 : Beam%Nsteps )%q_hat( 2 ) - &
! ray3D( 1 : Beam%Nsteps )%q_tilde( 2 ) * &
! ray3D( 1 : Beam%Nsteps )%p_hat( 1 )
!ray3D( 1 : Beam%Nsteps )%g = -( ray3D( 1 : Beam%Nsteps )%p_tilde( 2 ) * &
! ray3D( 1 : Beam%Nsteps )%q_hat( 1 ) - &
! ray3D( 1 : Beam%Nsteps )%q_tilde( 1 ) * &
! ray3D( 1 : Beam%Nsteps )%p_hat( 2 ) )
!ray3D( 1 : Beam%Nsteps )%h = ray3D( 1 : Beam%Nsteps )%p_tilde( 2 ) * &
! ray3D( 1 : Beam%Nsteps )%q_hat( 2 ) - &
! ray3D( 1 : Beam%Nsteps )%q_tilde( 2 ) * &
! ray3D( 1 : Beam%Nsteps )%p_hat( 2 )
!ray3D( 1 : Beam%Nsteps )%DetP = ray3D( 1 : Beam%Nsteps )%p_tilde( 1 ) * &
! ray3D( 1 : Beam%Nsteps )%p_hat( 2 ) - &
! ray3D( 1 : Beam%Nsteps )%p_tilde( 2 ) * &
! ray3D( 1 : Beam%Nsteps )%p_hat( 1 )
!ray3D( 1 : Beam%Nsteps )%DetQ = ray3D( 1 : Beam%Nsteps )%q_tilde( 1 ) * &
! ray3D( 1 : Beam%Nsteps )%q_hat( 2 ) - &
! ray3D( 1 : Beam%Nsteps )%q_tilde( 2 ) * &
! ray3D( 1 : Beam%Nsteps )%q_hat( 1 )
!CALL Influence3D_3D( xs_3D, Angles%alpha( ialpha ), iBeamWindow, P )
CASE ( 'g' ) ! Geometric-beams with hat-shape
CALL Influence3DGeoHatRayCen( Angles%alpha( ialpha ), Angles%beta( ibeta ), &
Angles%Dalpha, Angles%Dbeta, P )
CASE ( 'G', '^', ' ' ) ! Geometric-beams with hat-shape in Cartesian coordinates
CALL Influence3DGeoHatCart( Angles%alpha( ialpha ), Angles%beta( ibeta ), &
Angles%Dalpha, Angles%Dbeta, P, x_rcvrMat, t_rcvr )
CASE ( 'b' ) ! Geometric-beams with Gaussian-shape
CALL Influence3DGeoGaussianRayCen( Angles%alpha( ialpha ), Angles%beta( ibeta ), &
Angles%Dalpha, Angles%Dbeta, P )
CASE ( 'B' ) ! Geometric-beams with Gaussian-shape in Cartesian coordiantes
CALL Influence3DGeoGaussianCart( Angles%alpha( ialpha ), Angles%beta( ibeta ), &
Angles%Dalpha, Angles%Dbeta, P, x_rcvrMat, t_rcvr )
CASE DEFAULT
CALL ERROUT( 'BELLHOP3D', 'Invalid Run Type' )
END SELECT
END IF
END SELECT
! Optionally dump rays to a disk file
IF ( Beam%RunType( 1 : 1 ) == 'R' ) THEN
CALL WriteRay3D( Angles%alpha( ialpha ), Angles%beta( ibeta ), Beam%Nsteps )
ENDIF
ENDIF ! closes iSingle test
END DO DeclinationAngle
! for a 2D TL run, scale the pressure and copy the 2D slice into the radial of the 3D field
IF ( Beam%RunType( 6 : 6 ) == '2' ) THEN ! 2D calculation
SELECT CASE ( Beam%RunType( 1 : 1 ) )
CASE ( 'C', 'S', 'I' ) ! TL calculation
CALL ScalePressure( Angles%Dalpha, ray2D( 1 )%c, Pos%Rr, U, NRz_per_range, Pos%NRr, Beam%RunType, freq )
P( ibeta, :, : ) = U
U = 0 ! clear out the pressure field on the radial in prep for next radial
CASE ( 'A' ) ! arrivals calculation, ascii
NArr3D( ibeta, :, : ) = NArr( :, : )
Arr3D( ibeta, :, :, : ) = Arr( :, :, : )
Arr3D( ibeta, :, :, : )%SrcAzimAngle = SNGL( SrcAzimAngle ) ! angle
Arr3D( ibeta, :, :, : )%RcvrAzimAngle = SNGL( SrcAzimAngle ) ! angle (rcvr angle is same as source angle)
Narr = 0 ! this clears out the 2D arrival structure
CASE ( 'a' ) ! arrivals calculation, binary
NArr3D( ibeta, :, : ) = NArr( :, : )
Arr3D( ibeta, :, :, : ) = Arr( :, :, : )
Arr3D( ibeta, :, :, : )%SrcAzimAngle = SNGL( SrcAzimAngle ) ! angle
Arr3D( ibeta, :, :, : )%RcvrAzimAngle = SNGL( SrcAzimAngle ) ! angle (rcvr angle is same as source angle)
Narr = 0 ! this clears out the 2D arrival structure
END SELECT
END IF
ENDIF ! closes iSingle test
END DO AzimuthalAngle
! *** Scale the complex pressure field ***
IF ( Beam%RunType( 6 : 6 ) == '3' ) THEN ! 3D calculation
SELECT CASE ( Beam%RunType( 1 : 1 ) )
CASE ( 'C', 'S', 'I' ) ! TL calculation
CALL ScalePressure3D( Angles%Dalpha, Angles%Dbeta, ray2D( 1 )%c, epsilon, P, &
Pos%Ntheta, NRz_per_range, Pos%NRr, Beam%RunType, freq )
END SELECT
END IF
! Write out the field
SELECT CASE ( Beam%RunType( 1 : 1 ) )
CASE ( 'C', 'S', 'I' ) ! TL calculation
DO irz = 1, Pos%NRz
RcvrBearing: DO itheta = 1, Pos%Ntheta
iRec = 10 + ( isx - 1 ) * Pos%Nsy * Pos%Ntheta * Pos%NSz * Pos%NRz + &
( isy - 1 ) * Pos%Ntheta * Pos%NSz * Pos%NRz + &
( itheta - 1 ) * Pos%NSz * Pos%NRz + &
( isz - 1 ) * Pos%NRz + irz
WRITE( SHDFile, REC = IRec ) P( itheta, irz, 1 : Pos%NRr )
END DO RcvrBearing
END DO
CASE ( 'A' ) ! arrivals calculation, ascii
CALL WriteArrivalsASCII3D( Pos%Rr, Pos%Ntheta, NRz_per_range, Pos%NRr )
CASE ( 'a' ) ! arrivals calculation, binary
CALL WriteArrivalsBinary3D( Pos%Rr, Pos%Ntheta, NRz_per_range, Pos%NRr )
END SELECT
END DO Source_y
END DO Source_x
END DO Source_z
! close all files
SELECT CASE ( Beam%RunType( 1 : 1 ) )
CASE ( 'C', 'S', 'I' ) ! TL calculation
CLOSE( SHDFile )
CASE ( 'A', 'a' ) ! arrivals calculation
CLOSE( ARRFile )
CASE ( 'R' ) ! ray trace
CLOSE( RAYFile )
END SELECT
! Display run time
CALL CPU_TIME( Tstop )
WRITE( PRTFile, "( /, ' CPU Time = ', G15.3, 's' )" ) Tstop - Tstart
END SUBROUTINE BellhopCore
! **********************************************************************!
SUBROUTINE PickEpsilon( BeamType, omega, c, Dalpha, Dbeta, rLoop, EpsMultiplier, epsilon )
! Picks the optimum value for epsilon
REAL (KIND=8), INTENT( IN ) :: omega, c ! angular frequency, sound speed
REAL (KIND=8), INTENT( IN ) :: Dalpha, Dbeta ! angular spacing for ray fan
REAL (KIND=8), INTENT( IN ) :: epsMultiplier, Rloop ! multiplier, loop range
COMPLEX (KIND=8), INTENT( OUT ) :: epsilon( 2 ) ! beam initial conditions
CHARACTER (LEN= 2), INTENT( IN ) :: BeamType
LOGICAL, SAVE :: INIFlag = .TRUE.
REAL (KIND=8), SAVE :: HalfWidth( 2 ) = [ 0.0, 0.0 ]
COMPLEX (KIND=8) :: epsilonOpt( 2 )
CHARACTER (LEN=80) :: TAG
! LP: BUG: Multiple codepaths do not set epsilonOpt, leads to UB
SELECT CASE ( BeamType( 1 : 1 ) )
CASE ( 'C', 'R' ) ! Cerveny beams
TAG = 'Cerveny style beam'
SELECT CASE ( BeamType( 2 : 2 ) )
CASE ( 'F' )
TAG = 'Space filling beams'
HalfWidth( 1 ) = 2.0 / ( ( omega / c ) * Dalpha )
HalfWidth( 2 ) = 0.0
IF ( Dbeta /= 0.0 ) HalfWidth( 2 ) = 2.0 / ( ( omega / c ) * Dbeta )
epsilonOpt = i * 0.5 * omega * HalfWidth ** 2
CASE ( 'M' )
TAG = 'Minimum width beams'
HalfWidth( 1 ) = SQRT( 2.0 * c * 1000.0 * rLoop / omega )
HalfWidth( 2 ) = HalfWidth( 1 )
epsilonOPT = i * 0.5 * omega * HalfWidth **2
CASE ( 'C' )
TAG = 'Cerveny style beam'
END SELECT
CASE ( 'g' )
TAG = 'Geometric beam, hat-shaped, Ray coord.'
epsilonOPT = 0.0
CASE ( 'G', '^' )
TAG = 'Geometric beam, hat-shaped, Cart. coord.'
epsilonOPT = 0.0
CASE ( 'b' )
TAG = 'Geometric beam, Gaussian-shaped, Ray coord.'
epsilonOPT = 0.0
CASE ( 'B' )
TAG = 'Geometric beam, Gaussian-shaped, Cart. coord.'
epsilonOPT = 0.0
CASE ( 'S' )
TAG = 'Simple Gaussian beams'
halfwidth = 2.0 / ( ( omega / c ) * Dalpha )
epsilonOpt = i * 0.5 * omega * halfwidth ** 2
END SELECT
epsilon = epsMultiplier * epsilonOPT
! On first call write info to prt file
IF ( INIFlag ) THEN
WRITE( PRTFile, * )
WRITE( PRTFile, * ) TAG
WRITE( PRTFile, * ) 'HalfWidth1 = ', HalfWidth( 1 )
WRITE( PRTFile, * ) 'HalfWidth2 = ', HalfWidth( 2 )
WRITE( PRTFile, * ) 'epsilonOPT1 = ', epsilonOPT( 1 )
WRITE( PRTFile, * ) 'epsilonOPT2 = ', epsilonOPT( 2 )
WRITE( PRTFile, * ) 'EpsMult = ', EpsMultiplier
WRITE( PRTFile, * )
INIFlag = .FALSE.
END IF
END SUBROUTINE PickEpsilon
! **********************************************************************!
SUBROUTINE TraceRay2D( alpha, beta, Amp0 )
! Traces the beam corresponding to a particular take-off angle
USE ReflectMod
REAL (KIND=8), INTENT( IN ) :: alpha, beta, Amp0 ! initial angles, amplitude
INTEGER :: is, is1 ! index for a step along the ray
REAL (KIND=8) :: x( 3 ) ! ray coordinate
REAL (KIND=8) :: t_o( 3 ) ! tangent in ocean space
!REAL (KIND=8) :: c, cimag, gradc( 2 ), crr, crz, czz, rho
REAL (KIND=8) :: DistBegTop, DistEndTop, DistBegBot, DistEndBot ! Distances from ray beginning, end to top and bottom
REAL (KIND=8) :: tinit( 2 ), tradial( 2 ), BotnInt( 3 ), TopnInt( 3 ), s1, s2
REAL (KIND=8) :: z_xx, z_xy, z_yy, kappa_xx, kappa_xy, kappa_yy
REAL (KIND=8) :: term_minx, term_miny, term_maxx, term_maxy
LOGICAL :: topRefl, botRefl
! *** Initial conditions ***
iSmallStepCtr = 0
tinit = [ COS( alpha ), SIN( alpha ) ]
tradial = [ COS( beta ), SIN( beta ) ]
ray2D( 1 )%x = [ 0.0D0, xs_3D( 3 ) ]
! CALL EvaluateSSP2D( ray2D( 1 )%x, tinit, c, cimag, gradc, crr, crz, czz, rho, xs_3D, tradial, freq )
ray2D( 1 )%t = tinit / ray2D( 1 )%c ! LP: Set before PickEpsilon independent of ray angles; never overwritten
ray2D( 1 )%p = [ 1.0, 0.0 ]
ray2D( 1 )%q = [ 0.0, 1.0 ]
ray2D( 1 )%tau = 0.0
ray2D( 1 )%Amp = Amp0
ray2D( 1 )%Phase = 0.0
ray2D( 1 )%NumTopBnc = 0
ray2D( 1 )%NumBotBnc = 0
IsegTopx = 1
IsegTopy = 1
IsegBotx = 1
IsegBoty = 1
t_o = RayToOceanT( ray2D( 1 )%t, tradial )
CALL GetTopSeg3D( xs_3D, t_o, .TRUE. ) ! identify the top segment above the source
CALL GetBotSeg3D( xs_3D, t_o, .TRUE. ) ! identify the bottom segment below the source
! Trace the beam (note that Reflect alters the step index is)
is = 0
CALL Distances3D( xs_3D, Topx, Botx, Topn, Botn, DistBegTop, DistBegBot )
IF ( DistBegTop <= 0 .OR. DistBegBot <= 0 ) THEN
Beam%Nsteps = 1
RETURN ! source must be within the medium
END IF
Stepping: DO WHILE ( .TRUE. )
is = is + 1
is1 = is + 1
CALL Step2D( ray2D( is ), ray2D( is1 ), tradial, topRefl, botRefl )
! convert polar coordinate of ray to x-y coordinate
x = RayToOceanX( ray2D( is1 )%x, xs_3D, tradial )
t_o = RayToOceanT( ray2D( is1 )%t, tradial )
CALL GetTopSeg3D( x, t_o, .FALSE. ) ! identify the top segment above the source
CALL GetBotSeg3D( x, t_o, .FALSE. ) ! identify the bottom segment below the source
IF ( IsegTopx == 0 .OR. IsegTopy == 0 .OR. IsegBotx == 0 .OR. IsegBoty == 0 ) THEN ! we escaped the box
Beam%Nsteps = is
EXIT Stepping
END IF
! Reflections?
! Tests that ray at step is is inside, and ray at step is+1 is outside
! to detect only a crossing from inside to outside
! DistBeg is the distance at step is, which is saved
! DistEnd is the distance at step is+1, which needs to be calculated
CALL Distances3D( x, Topx, Botx, Topn, Botn, DistEndTop, DistEndBot )
IF ( topRefl ) THEN
IF ( atiType == 'C' ) THEN
! LP: This is superfluous, it's the same x calculated above.
x = RayToOceanX( ray2D( is + 1 )%x, xs_3D, tradial )
s1 = ( x( 1 ) - Topx( 1 ) ) / ( xTopSeg( 2 ) - xTopSeg( 1 ) ) ! proportional distance along segment
s2 = ( x( 2 ) - Topx( 2 ) ) / ( yTopSeg( 2 ) - yTopSeg( 1 ) ) ! proportional distance along segment
TopnInt = Top( IsegTopx, IsegTopy )%Noden * ( 1 - s1 ) * ( 1 - s2 ) + &
Top( IsegTopx + 1, IsegTopy )%Noden * ( s1 ) * ( 1 - s2 ) + &
Top( IsegTopx + 1, IsegTopy + 1 )%Noden * ( s1 ) * ( s2 ) + &
Top( IsegTopx, IsegTopy + 1 )%Noden * ( 1 - s1 ) * ( s2 )
z_xx = Top( IsegTopx, IsegTopy )%z_xx
z_xy = Top( IsegTopx, IsegTopy )%z_xy
z_yy = Top( IsegTopx, IsegTopy )%z_yy
kappa_xx = Top( IsegTopx, IsegTopy )%kappa_xx
kappa_xy = Top( IsegTopx, IsegTopy )%kappa_xy
kappa_yy = Top( IsegTopx, IsegTopy )%kappa_yy
ELSE
TopnInt = Topn ! normal is constant in a segment
z_xx = 0
z_xy = 0
z_yy = 0
kappa_xx = 0
kappa_xy = 0
kappa_yy = 0
END IF
CALL Reflect2D( is, Bdry%Top%HS, 'TOP', TopnInt, z_xx, z_xy, z_yy, kappa_xx, kappa_xy, kappa_yy, RTop, NTopPTS, tradial)
ray2D( is + 1 )%NumTopBnc = ray2D( is )%NumTopBnc + 1
x = RayToOceanX( ray2D( is + 1 )%x, xs_3D, tradial )
CALL Distances3D( x, Topx, Botx, Topn, Botn, DistEndTop, DistEndBot )
ELSE IF ( botRefl ) THEN
! write( *, * ) 'Reflecting', x, Botx
! write( *, * ) 'Botn', Botn
! write( *, * ) 'Distances', DistEndTop, DistEndBot
IF ( btyType == 'C' ) THEN
x = RayToOceanX( ray2D( is + 1 )%x, xs_3D, tradial )
s1 = ( x( 1 ) - Botx( 1 ) ) / ( xBotSeg( 2 ) - xBotSeg( 1 ) ) ! proportional distance along segment
s2 = ( x( 2 ) - Botx( 2 ) ) / ( yBotSeg( 2 ) - yBotSeg( 1 ) ) ! proportional distance along segment
BotnInt = Bot( IsegBotx, IsegBoty )%Noden * ( 1 - s1 ) * ( 1 - s2 ) + &
Bot( IsegBotx + 1, IsegBoty )%Noden * ( s1 ) * ( 1 - s2 ) + &
Bot( IsegBotx + 1, IsegBoty + 1 )%Noden * ( s1 ) * ( s2 ) + &
Bot( IsegBotx, IsegBoty + 1 )%Noden * ( 1 - s1 ) * ( s2 )
z_xx = Bot( IsegBotx, IsegBoty )%z_xx
z_xy = Bot( IsegBotx, IsegBoty )%z_xy
z_yy = Bot( IsegBotx, IsegBoty )%z_yy
kappa_xx = Bot( IsegBotx, IsegBoty )%kappa_xx
kappa_xy = Bot( IsegBotx, IsegBoty )%kappa_xy
kappa_yy = Bot( IsegBotx, IsegBoty )%kappa_yy
ELSE
BotnInt = Botn ! normal is constant in a segment
z_xx = 0
z_xy = 0
z_yy = 0
kappa_xx = 0
kappa_xy = 0
kappa_yy = 0
END IF
CALL Reflect2D( is, Bdry%Bot%HS, 'BOT', BotnInt, z_xx, z_xy, z_yy, kappa_xx, kappa_xy, kappa_yy, RBot, NBotPTS, tradial)
ray2D( is + 1 )%NumBotBnc = ray2D( is )%NumBotBnc + 1
x = RayToOceanX( ray2D( is + 1 )%x, xs_3D, tradial )
CALL Distances3D( x, Topx, Botx, Topn, Botn, DistEndTop, DistEndBot )
END IF
! Has the ray left the box, lost its energy, escaped the boundaries, or exceeded storage limit?
! LP: See explanation for changes in bdry3DMod: GetTopSeg3D.
t_o = RayToOceanT( ray2D( is + 1 )%t, tradial )
term_minx = MAX( Bot( 1, 1 )%x( 1 ), Top( 1, 1 )%x( 1 ) )
term_miny = MAX( Bot( 1, 1 )%x( 2 ), Top( 1, 1 )%x( 2 ) )
term_maxx = MIN( Bot( NBTYPts( 1 ), 1 )%x( 1 ), Top( NATIPts( 1 ), 1 )%x( 1 ) )
term_maxy = MIN( Bot( 1, NBTYPts( 2 ) )%x( 2 ), Top( 1, NATIPts( 2 ) )%x( 2 ) )
! LP: The Beam%Box conditions were inexplicably commented out in 2022 revision of Nx2D, see README.
IF ( ABS( x( 1 ) - xs_3D( 1 ) ) >= Beam%Box%x .OR. &
ABS( x( 2 ) - xs_3D( 2 ) ) >= Beam%Box%y .OR. &
ABS( x( 3 ) ) >= Beam%Box%z .OR. & ! LP: Removed xs( 3 ) for consistency
x( 1 ) < term_minx .OR. &
x( 2 ) < term_miny .OR. &
x( 1 ) > term_maxx .OR. &
x( 2 ) > term_maxy .OR. &
( x( 1 ) == term_minx .AND. t_o( 1 ) < 0.0 ) .OR. &
( x( 2 ) == term_miny .AND. t_o( 2 ) < 0.0 ) .OR. &
( x( 1 ) == term_maxx .AND. t_o( 1 ) > 0.0 ) .OR. &
( x( 2 ) == term_maxy .AND. t_o( 2 ) > 0.0 ) .OR. &
ray2D( is + 1 )%Amp < 0.005 .OR. &
! ray2D( is + 1 )%t( 1 ) < 0 .OR. & ! kills off a backward traveling ray
iSmallStepCtr > 50 ) THEN
Beam%Nsteps = is + 1
EXIT Stepping
ELSE IF ( is >= MaxN - 3 ) THEN
WRITE( PRTFile, * ) 'Warning in TraceRay2D : Insufficient storage for ray trajectory'
WRITE( PRTFile, * ) 'Angles are alpha = ', alpha * RadDeg, ' beta = ', beta * RadDeg
Beam%Nsteps = is
EXIT Stepping
END IF
DistBegTop = DistEndTop
DistBegBot = DistEndBot
END DO Stepping
END SUBROUTINE TraceRay2D
! **********************************************************************!
SUBROUTINE Step2D( ray0, ray2, tradial, topRefl, botRefl )
! Does a single step along the ray
! x denotes the ray coordinate, (r,z)
! t denotes the scaled tangent to the ray (previously (rho, zeta))
! c * t would be the unit tangent
USE Step3DMod
REAL (KIND=8), INTENT( IN ) :: tradial( 2 ) ! coordinate of source and ray bearing angle
LOGICAL, INTENT( OUT ) :: topRefl, botRefl
TYPE( ray2DPt ) :: ray1
TYPE( ray2DPt ), INTENT( IN ) :: ray0
TYPE( ray2DPt ), INTENT( INOUT ) :: ray2
INTEGER :: iSegx0, iSegy0, iSegz0, snapDim
REAL (KIND=8 ) :: gradc0( 2 ), gradc1( 2 ), gradc2( 2 ), rho, &
c0, cimag0, crr0, crz0, czz0, csq0, cnn0_csq0, &
c1, cimag1, crr1, crz1, czz1, csq1, cnn1_csq1, &
c2, cimag2, crr2, crz2, czz2, urayt0( 2 ), urayt1( 2 ), urayt2( 2 ), &
h, halfh, hw0, hw1, ray2n( 2 ), RM, RN, gradcjump( 2 ), cnjump, csjump, w0, w1, &
rayx3D( 3 ), rayt3D( 3 ), x2_o( 3 ), x2_o_out( 3 )
IF ( STEP_DEBUGGING ) THEN
WRITE( PRTFile, * )
WRITE( PRTFile, * ) 'ray0 x t', ray0%x, ray0%t
WRITE( PRTFile, * ) 'iSegr iSegz', iSegr, iSegz
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)
CALL EvaluateSSP2D( ray0%x, ray0%t, c0, cimag0, gradc0, crr0, crz0, czz0, rho, xs_3D, tradial, freq )
csq0 = c0 * c0
cnn0_csq0 = crr0 * ray0%t( 2 )**2 - 2.0 * crz0 * ray0%t( 1 ) * ray0%t( 2 ) + czz0 * ray0%t( 1 )**2
iSegx0 = iSegx ! make note of current layer
iSegy0 = iSegy
iSegz0 = iSegz
h = Beam%deltas ! initially set the step h, to the basic one, deltas
urayt0 = c0 * ray0%t ! unit tangent
rayx3D = RayToOceanX( ray0%x, xs_3D, tradial )
rayt3D = RayToOceanT( urayt0, tradial )
CALL ReduceStep3D( rayx3D, rayt3D, 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
ray1%p = ray0%p - halfh * cnn0_csq0 * ray0%q
ray1%q = ray0%q + halfh * c0 * ray0%p
! *** Phase 2
CALL EvaluateSSP2D( ray1%x, ray1%t, c1, cimag1, gradc1, crr1, crz1, czz1, rho, xs_3D, tradial, freq )
csq1 = c1 * c1
cnn1_csq1 = crr1 * ray1%t( 2 )**2 - 2.0 * crz1 * ray1%t( 1 ) * ray1%t( 2 ) + czz1 * ray1%t( 1 )**2
! The Munk test case with a horizontally launched ray caused problems.
! The ray vertexes on an interface and can ping-pong around that interface.
! Have to be careful in that case about big changes to the stepsize (that invalidate the leap-frog scheme) in phase II.
! A modified Heun or Box method could also work.
urayt1 = c1 * ray1%t ! unit tangent
rayt3D = RayToOceanT( urayt1, tradial )
CALL ReduceStep3D( rayx3D, rayt3D, 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
rayt3D = RayToOceanT( urayt2, tradial )
! 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( rayx3D, x2_o, rayt3D, iSegx0, iSegy0, iSegz0, h, &
topRefl, botRefl, snapDim )
ray2%x = OceanToRayX( x2_o, xs_3D, tradial, urayt2, snapDim )
IF ( STEP_DEBUGGING ) THEN
x2_o_out = RayToOceanX( ray2%x, xs_3D, tradial )
WRITE( PRTFile, * ) 'OceanToRayX in ', x2_o
WRITE( PRTFile, * ) ' ==> ', ray2%x
WRITE( PRTFile, * ) ' ==> ', x2_o_out
END IF
!write( *, * ) 'final coord ', x2_o, ray2%x, w0, w1
hw0 = h * w0
hw1 = h * w1
ray2%t = ray0%t - hw0 * gradc0 / csq0 - hw1 * gradc1 / csq1
ray2%p = ray0%p - hw0 * cnn0_csq0 * ray0%q - hw1 * cnn1_csq1 * ray1%q
ray2%q = ray0%q + hw0 * c0 * ray0%p + hw1 * c1 * ray1%p
ray2%tau = ray0%tau + hw0 / CMPLX( c0, cimag0, KIND=8 ) + hw1 / CMPLX( c1, cimag1, KIND=8 )
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 EvaluateSSP2D( ray2%x, ray2%t, c2, cimag2, gradc2, crr2, crz2, czz2, rho, xs_3D, tradial, freq )
ray2%c = c2
!!! this needs modifying like the full 3D version to handle jumps in the x-y direction
IF ( iSegz /= iSegz0 ) THEN
gradcjump = gradc2 - gradc0 ! this is precise only for c-linear layers
ray2n = [ -ray2%t( 2 ), ray2%t( 1 ) ]
cnjump = DOT_PRODUCT( gradcjump, ray2n )
csjump = DOT_PRODUCT( gradcjump, ray2%t )
RM = ray2%t( 1 ) / ray2%t( 2 )
RN = RM * ( 2 * cnjump - RM * csjump ) / c2
RN = -RN
ray2%p = ray2%p + ray2%q * RN
END IF
END SUBROUTINE Step2D
FUNCTION RayToOceanX( x, xs, tradial )
!! Transform ray coordinates to ocean coordinates
REAL ( KIND=8 ) :: RayToOceanX( 3 )
REAL ( KIND=8 ), INTENT( IN ) :: x( 2 ), xs( 3 ), tradial( 2 )
RayToOceanX = [ xs( 1 ) + x( 1 ) * tradial( 1 ), &
xs( 2 ) + x( 1 ) * tradial( 2 ), &
x( 2 ) ]
END FUNCTION RayToOceanX
FUNCTION RayToOceanT( t, tradial )
!! Transform ray tangent to ocean tangent coordinates
REAL ( KIND=8 ) :: RayToOceanT( 3 )
REAL ( KIND=8 ), INTENT( IN ) :: t( 2 ), tradial( 2 )
RayToOceanT = [ t( 1 ) * tradial( 1 ), &
t( 1 ) * tradial( 2 ), &
t( 2 ) ]
END FUNCTION RayToOceanT
FUNCTION OceanToRayX( x, xs, tradial, t, snapDim )
!! Transform ocean coordinates to ray coordinates
! LP: Going back and forth through the coordinate transform won't
! always keep the precise value, so we may have to finesse the floats.
! Valid values of snapDim:
! -2: Snap to X or Y unspecified
! -1: No snap
! 0: Snap to X
! 1: Snap to Y
! 2: Snap to Z
REAL ( KIND=8 ) :: OceanToRayX( 2 )
REAL ( KIND=8 ), INTENT( IN ) :: x( 3 ), xs( 3 ), tradial( 2 ), t( 2 )
INTEGER, INTENT( IN ) :: snapDim
REAL ( KIND=8 ) :: ret( 2 ), x_back( 3 ), wantdir( 2 ), errdir( 2 )
LOGICAL :: correctdir( 2 )
INTEGER :: i
! Depth always transfers perfectly--not changed.
ret( 2 ) = x( 3 )
! For range, use larger dimension--this avoids divide-by-zero or divide
! by a small number causing accuracy problems.
IF ( ABS( tradial( 1 ) ) >= ABS( tradial( 2 ) ) ) THEN
ret( 1 ) = ( x( 1 ) - xs( 1 ) ) / tradial( 1 )
ELSE
ret( 1 ) = ( x( 2 ) - xs( 2 ) ) / tradial( 2 )
END IF
IF ( snapDim < -2 .OR. snapDim == -1 .OR. snapDim >= 2 ) THEN
! Either:
! snapDim out of range (won't happen, but want to help compiler)
! No snap selected--this is the best estimate
! Snap to Z--Z already perfect, this is the best estimate
OceanToRayX = ret
RETURN
END IF
! Only do this iteration a few times, then give up.
DO i = 1, 4
! Go back from 2D to 3D, compare to original x.
x_back = RayToOceanX( ret, xs, tradial )
! If we can't be on the boundary, we want to be slightly forward of
! the boundary, measured in terms of the ray tangent range. This also
! encompasses cases where one component exactly matches (errdir.x_or_y
! == RL(0.0)). For both of these values, only the sign matters.
wantdir = tradial * t( 1 )
errdir = x_back( 1 : 2 ) - x( 1 : 2 )
correctdir = ( wantdir * errdir ) >= 0.0D0
IF ( ( snapDim == 0 .AND. correctdir( 1 ) ) .OR. &
( snapDim == 1 .AND. correctdir( 2 ) ) .OR. &
( correctdir( 1 ) .AND. correctdir( 2 ) ) ) THEN
OceanToRayX = ret
RETURN
END IF
! Move to the next floating point value for ret, in the direction
! of the ray tangent. How do we know this will actually produce a
! new value for x_back? If the scale of the floating point steps
! around x_back is larger than that of ret (several values of ret
! map to the same value of x_back after the transform), there
! should be no problem in finding a value that maps exactly, so
! we would not get here.
ret( 1 ) = NEAREST( ret( 1 ), t( 1 ) )
END DO
WRITE( PRTFile, * ) 'Warning in OceanToRayX: Failed to transform 3D -> 2D -> 3D consistently'
OceanToRayX = ret
END FUNCTION OceanToRayX
! **********************************************************************!
SUBROUTINE TraceRay3D( alpha, beta, epsilon, Amp0 )
!! Traces the beam corresponding to a particular take off angle
USE Step3DMod
USE Reflect3DMod
REAL ( KIND=8 ), INTENT( IN ) :: Amp0 ! initial amplitude
REAL ( KIND=8 ), INTENT( IN ) :: alpha, beta ! take-off angles of the ray
COMPLEX ( KIND=8 ), INTENT( IN ) :: epsilon( 2 ) ! beam initial conditions
INTEGER :: is, is1
REAL ( KIND=8 ) :: DistBegTop, DistEndTop, DistBegBot, DistEndBot!, &
!c, cimag, gradc( 3 ), cxx, cyy, czz, cxy, cxz, cyz, rho ! soundspeed derivatives
REAL (KIND=8) :: TopnInt( 3 ), BotnInt( 3 )
REAL (KIND=8) :: s1, s2
REAL (KIND=8) :: z_xx, z_xy, z_yy, kappa_xx, kappa_xy, kappa_yy
REAL (KIND=8) :: tinit( 3 )
LOGICAL :: topRefl, botRefl
REAL (KIND=8) :: term_minx, term_miny, term_maxx, term_maxy, term_x( 3 ), term_t( 3 )
! *** Initial conditions ***
iSmallStepCtr = 0
ray3D( 1 )%x = xs_3D
tinit = [ COS( alpha ) * COS( beta ), COS( alpha ) * SIN( beta ), SIN( alpha ) ]
!CALL EvaluateSSP3D( ray3D( 1 )%x, tinit, c, cimag, gradc, cxx, cyy, czz, cxy, cxz, cyz, rho, freq, 'TAB' )
ray3D( 1 )%t = tinit / ray3D( 1 )%c ! LP: Set before PickEpsilon independent of ray angles; never overwritten
!ray3D( 1 )%f = epsilon( 2 )
!ray3D( 1 )%g = epsilon( 1 )
!ray3D( 1 )%h = 0.0
!ray3D( 1 )%DetP = 1.0
!ray3D( 1 )%DetQ = epsilon( 1 ) * epsilon( 2 )
!ray3D( 1 )%c = c ! LP
ray3D( 1 )%phi = 0.0
ray3D( 1 )%tau = 0.0
ray3D( 1 )%Amp = Amp0
ray3D( 1 )%Phase = 0.0
ray3D( 1 )%NumTopBnc = 0
ray3D( 1 )%NumBotBnc = 0
!ray3D( 1 )%p_tilde = [ 1.0, 0.0 ] use these for complex Cerveny beams
!ray3D( 1 )%q_tilde = [ epsilon( 1 ), CMPLX( 0.0, KIND=8 ) ]
!ray3D( 1 )%p_hat = [ 0.0, 1.0 ]
!ray3D( 1 )%q_hat = [ CMPLX( 0.0, KIND=8 ), epsilon( 2 ) ]
ray3D( 1 )%p_tilde = [ 1.0, 0.0 ]
ray3D( 1 )%q_tilde = [ 0.0, 0.0 ]
ray3D( 1 )%p_hat = [ 0.0, 1.0 ]
ray3D( 1 )%q_hat = [ 0.0, 0.0 ]
! dummy BotSeg info to force GetBotSeg to search for the active segment on first call
xTopSeg = [ +big, -big ]
yTopSeg = [ +big, -big ]
xBotSeg = [ +big, -big ]
yBotSeg = [ +big, -big ]
CALL GetTopSeg3D( xs_3D, ray3D( 1 )%t, .TRUE. ) ! identify the top segment above the source
CALL GetBotSeg3D( xs_3D, ray3D( 1 )%t, .TRUE. ) ! identify the bottom segment below the source
! Trace the beam (note that Reflect alters the step index is)
is = 0
CALL Distances3D( ray3D( 1 )%x, Topx, Botx, Topn, Botn, DistBegTop, DistBegBot )
IF ( DistBegTop <= 0 .OR. DistBegBot <= 0 ) THEN
Beam%Nsteps = 1
WRITE( PRTFile, * ) 'Terminating the ray trace because the source is on or outside the boundaries'
RETURN ! source must be within the medium
END IF
Stepping: DO WHILE ( .TRUE. )
is = is + 1
is1 = is + 1
CALL Step3D( ray3D( is ), ray3D( is1 ), topRefl, botRefl )
CALL GetTopSeg3D( ray3D( is1 )%x, ray3D( is1 )%t, .FALSE. ) ! identify the top segment above the source
CALL GetBotSeg3D( ray3D( is1 )%x, ray3D( is1 )%t, .FALSE. ) ! identify the bottom segment below the source
IF ( IsegTopx == 0 .OR. IsegTopy == 0 .OR. IsegBotx == 0 .OR. IsegBoty == 0 ) THEN ! we escaped the box
Beam%Nsteps = is
EXIT Stepping
END IF
! Reflections?
! Tests that ray at step is is inside, and ray at step is+1 is outside
! to detect only a crossing from inside to outside
! DistBeg is the distance at step is, which is saved
! DistEnd is the distance at step is+1, which needs to be calculated
CALL Distances3D( ray3D( is1 )%x, Topx, Botx, Topn, Botn, DistEndTop, DistEndBot )
IF ( topRefl ) THEN
IF ( STEP_DEBUGGING ) &
WRITE( PRTFile, * ) 'Top reflecting'
IF ( atiType == 'C' ) THEN
s1 = ( ray3D( is1 )%x( 1 ) - Topx( 1 ) ) / ( xTopSeg( 2 ) - xTopSeg( 1 ) ) ! proportional distance along segment
s2 = ( ray3D( is1 )%x( 2 ) - Topx( 2 ) ) / ( yTopSeg( 2 ) - yTopSeg( 1 ) ) ! proportional distance along segment
TopnInt = Top( IsegTopx, IsegTopy )%Noden * ( 1 - s1 ) * ( 1 - s2 ) + &
Top( IsegTopx + 1, IsegTopy )%Noden * ( s1 ) * ( 1 - s2 ) + &
Top( IsegTopx + 1, IsegTopy + 1 )%Noden * ( s1 ) * ( s2 ) + &
Top( IsegTopx, IsegTopy + 1 )%Noden * ( 1 - s1 ) * ( s2 )
z_xx = Top( IsegTopx, IsegTopy )%z_xx
z_xy = Top( IsegTopx, IsegTopy )%z_xy
z_yy = Top( IsegTopx, IsegTopy )%z_yy
kappa_xx = Top( IsegBotx, IsegBoty )%kappa_xx
kappa_xy = Top( IsegBotx, IsegBoty )%kappa_xy
kappa_yy = Top( IsegBotx, IsegBoty )%kappa_yy
ELSE
TopnInt = Topn ! normal is constant in a segment
z_xx = 0
z_xy = 0
z_yy = 0
kappa_xx = 0
kappa_xy = 0
kappa_yy = 0
END IF
CALL Reflect3D( is, Bdry%Top%HS, 'TOP', TopnInt, z_xx, z_xy, z_yy, kappa_xx, kappa_xy, kappa_yy, RTop, NTopPTS )
ray3D( is + 1 )%NumTopBnc = ray3D( is )%NumTopBnc + 1
CALL Distances3D( ray3D( is + 1 )%x, Topx, Botx, Topn, Botn, DistEndTop, DistEndBot )
ELSE IF ( botRefl ) THEN
IF ( STEP_DEBUGGING ) &
WRITE( PRTFile, * ) 'Bottom reflecting'
IF ( btyType == 'C' ) THEN
s1 = ( ray3D( is1 )%x( 1 ) - Botx( 1 ) ) / ( xBotSeg( 2 ) - xBotSeg( 1 ) ) ! proportional distance along segment
s2 = ( ray3D( is1 )%x( 2 ) - Botx( 2 ) ) / ( yBotSeg( 2 ) - yBotSeg( 1 ) ) ! proportional distance along segment
BotnInt = Bot( IsegBotx, IsegBoty )%Noden * ( 1 - s1 ) * ( 1 - s2 ) + &
Bot( IsegBotx + 1, IsegBoty )%Noden * ( s1 ) * ( 1 - s2 ) + &
Bot( IsegBotx + 1, IsegBoty + 1 )%Noden * ( s1 ) * ( s2 ) + &
Bot( IsegBotx, IsegBoty + 1 )%Noden * ( 1 - s1 ) * ( s2 )
z_xx = Bot( IsegBotx, IsegBoty )%z_xx
z_xy = Bot( IsegBotx, IsegBoty )%z_xy
z_yy = Bot( IsegBotx, IsegBoty )%z_yy
kappa_xx = Bot( IsegBotx, IsegBoty )%kappa_xx
kappa_xy = Bot( IsegBotx, IsegBoty )%kappa_xy
kappa_yy = Bot( IsegBotx, IsegBoty )%kappa_yy
ELSE
BotnInt = Botn ! normal is constant in a segment
z_xx = 0
z_xy = 0
z_yy = 0
kappa_xx = 0
kappa_xy = 0
kappa_yy = 0
END IF
CALL Reflect3D( is, Bdry%Bot%HS, 'BOT', BotnInt, z_xx, z_xy, z_yy, kappa_xx, kappa_xy, kappa_yy, RBot, NBotPTS )
ray3D( is + 1 )%NumBotBnc = ray3D( is )%NumBotBnc + 1
CALL Distances3D( ray3D( is + 1 )%x, Topx, Botx, Topn, Botn, DistEndTop, DistEndBot )
END IF
! Has the ray exited the beam box, lost its energy, escaped the BTY, ATI boundaries, or exceeded storage limit?
! LP: See explanation for changes in bdry3DMod: GetTopSeg3D.
term_x = ray3D( is + 1 )%x
term_t = ray3D( is + 1 )%t
term_minx = MAX( Bot( 1, 1 )%x( 1 ), Top( 1, 1 )%x( 1 ) )
term_miny = MAX( Bot( 1, 1 )%x( 2 ), Top( 1, 1 )%x( 2 ) )
term_maxx = MIN( Bot( NBTYPts( 1 ), 1 )%x( 1 ), Top( NATIPts( 1 ), 1 )%x( 1 ) )
term_maxy = MIN( Bot( 1, NBTYPts( 2 ) )%x( 2 ), Top( 1, NATIPts( 2 ) )%x( 2 ) )
IF ( &
ABS( term_x( 1 ) - xs_3D( 1 ) ) >= Beam%Box%x .OR. &
ABS( term_x( 2 ) - xs_3D( 2 ) ) >= Beam%Box%y .OR. &
ABS( term_x( 3 ) ) >= Beam%Box%z .OR. & ! box is centered at z=0
term_x( 1 ) < term_minx .OR. &
term_x( 2 ) < term_miny .OR. &
term_x( 1 ) > term_maxx .OR. &
term_x( 2 ) > term_maxy .OR. &
( term_x( 1 ) == term_minx .AND. term_t( 1 ) < 0.0 ) .OR. &
( term_x( 2 ) == term_miny .AND. term_t( 2 ) < 0.0 ) .OR. &
( term_x( 1 ) == term_maxx .AND. term_t( 1 ) > 0.0 ) .OR. &
( term_x( 2 ) == term_maxy .AND. term_t( 2 ) > 0.0 ) .OR. &
ray3D( is + 1 )%Amp < 0.005 .OR. &
iSmallStepCtr > 50 ) THEN
Beam%Nsteps = is + 1
EXIT Stepping
ELSE IF ( is >= MaxN - 3 ) THEN
Beam%Nsteps = is
WRITE( PRTFile, * ) 'Warning in TraceRay3D : Insufficient storage for ray trajectory'
WRITE( PRTFile, * ) 'Angles are alpha = ', alpha * RadDeg, ' beta = ', beta * RadDeg
EXIT Stepping
END IF
DistBegTop = DistEndTop
DistBegBot = DistEndBot
END DO Stepping
END SUBROUTINE TraceRay3D
! **********************************************************************!
SUBROUTINE Distances3D( rayx, Topx, Botx, Topn, Botn, DistTop, DistBot )
!! Computes distances from ray to boundaries
! Formula differs from JKPS because code uses outward pointing normals
! Note that Topx, Botx, just need to be any node on the diagonal that divides each square into triangles
! In bdry3DMod, the node is selected as the one at the lowest x, y, index and that defines the triangles
REAL (KIND=8), INTENT( IN ) :: rayx( 3 ) ! ray coordinate
REAL (KIND=8), INTENT( IN ) :: Topx( 3 ), Botx( 3 ) ! top, bottom boundary coordinate for node
REAL (KIND=8), INTENT( IN ) :: Topn( 3 ), Botn( 3 ) ! top, bottom boundary normal
REAL (KIND=8), INTENT( OUT ) :: DistTop, DistBot ! distance from the ray to top, bottom boundaries
REAL (KIND=8) :: dTop( 3 ), dBot( 3 )
dTop = rayx - Topx ! vector pointing from top to ray
dBot = rayx - Botx ! vector pointing from bottom to ray
DistTop = -DOT_PRODUCT( Topn, dTop )
DistBot = -DOT_PRODUCT( Botn, dBot )
!!$ write( *, * )
!!$ write( *, * ) 'Distances3D', DistBot
!!$ write( *, * ) 'rayx', rayx
!!$ write( *, * ) 'Botx', Botx
!!$ write( *, * ) 'dBot', dBot
!!$ write( *, * ) 'Normal', Botn
!!$ write( *, * )
END SUBROUTINE Distances3D
END PROGRAM BELLHOP3D