include file: modgps09bend.cdk90
! -*- F90 -*-

!
! Bending angle operator, with derivatives. This is still an
! experimental implementation, and efficiency must be verified.
!
! Josep M. Aparicio, 2006-2012.
! ARMA/ASTD
! Environment Canada
!


module modgps09bend 4,6
  use modgps00base     , only : i4, dp, ngpssize, ngpsxlow
  use modgps03diff
  use modgps04profile  , only : gpsprofile

  implicit none
contains


  subroutine gpsbend(prf) 2
    type(gpsprofile)     :: prf

    type(gpsdiff)                     :: sum,ta,tb,tm,trap,simp,boole,num,fa,fb,fm,nm,alpha_B
    type(gpsdiff)                     :: sa,sb,sm,ra,rm,rb,dlnndra,dlnndrb,dlnndrm
    type(gpsdiff)                     :: s1,s2,s3,s4,s5,r1,r2,r3,r4,r5
    type(gpsdiff)                     :: nu1,nu2,nu3,nu4,nu5,n1,n2,n3,n4,n5
    type(gpsdiff)                     :: t1,t2,t3,t4,t5,dlnndr1,dlnndr2,dlnndr3,dlnndr4,dlnndr5
    type(gpsdiff)                     :: f1,f2,f3,f4,f5
    type(gpsdiff)                     :: r  (ngpssize)
    type(gpsdiff)                     :: ref(ngpssize)
    type(gpsdiff)                     :: nu (ngpssize)
    type(gpsdiff)                     :: lnu(ngpssize)
    type(gpsdiff)                     :: n  (ngpssize)
    type(gpsdiff)                     :: dlgnudr(ngpssize-1)
    type(gpsdiff)                     :: rsq(ngpssize)
    type(gpsdiff)                     :: nsq(ngpssize)
    type(gpsdiff)                     :: x  (-ngpsxlow+1:ngpssize)
    type(gpsdiff)                     :: xsq(-ngpsxlow+1:ngpssize)
    type(gpsdiff)                     :: s(ngpssize),t(ngpssize)
    integer                           :: i,j,ngpslev
    logical                           :: lok

    if (.not. prf%bbst) then
       ngpslev=prf%ngpslev

       ! Radial distances and impact parameters:
       do i=1,ngpslev
          prf%dst(i)= (prf%Rad+prf%geoid+prf%gst(i))
          prf%ast(i)= prf%dst(i) * (1._dp+1.e-6_dp*prf%rst(i))
       enddo
       ! Extended lower levels:
       do i=ngpslev+1,ngpslev+ngpsxlow
          prf%ast(i)= prf%ast(i-1)-50._dp
       enddo

       ! Standard levels:
       do i=1,ngpslev
          r  (i)=prf%dst(ngpslev-i+1)
          ref(i)=prf%rst(ngpslev-i+1)
          !ref(i)=300._dp*exp((-1._dp/7000._dp)*(r(i)%Var-prf%Rad))
       enddo
       ! Extended upper levels:
       do i=ngpslev+1,ngpssize
          r  (i)=r  (i-1)+1000._dp
          ref(i)=ref(i-1)*exp(-1000._dp/7000_dp)
       enddo

       ! log n and x:
       do i=1,ngpssize
          nu(i)=1.e-6_dp*ref(i)
          lnu(i)=log(nu(i))
          n (i)=1._dp+nu(i)
          x (i)=n(i)*r(i)
          rsq(i)=r(i)**2
          nsq(i)=n(i)**2
          xsq(i)=x(i)**2
       enddo
       do i=0,-ngpsxlow+1,-1
          x  (i)=x(i+1)-50._dp
          xsq(i)=x(i)**2
       enddo

       ! Radial derivatives of log refractivity.
       ! Refractivity will be assumed exponential within each shell.
       ! We store the derivative of log(nu).
       ! dn/dr = nu * dlgnudr
       do i=1,ngpssize-1
          dlgnudr(i)=(lnu(i+1)-lnu(i))/(r(i+1)-r(i))
       enddo

       ! Evaluation of complete bending for ray tangent at r(i):
       do i=1,ngpslev
          ! Check that ray is not trapped
          lok=.true.
          do j = i+1,ngpssize
             lok= lok .and. (x(j)%Var .gt. x(i)%Var)
          enddo
          if (lok) then
             s(i)=0._dp
             t(i)=1._dp
             do j=i+1,ngpssize
                s(j)=sqrt(nsq(i)*rsq(j)-xsq(i))
                t(j)=s(j)/sqrt(xsq(j)-xsq(i))
             enddo

             ! Trapezoid integration:
             sum=0._dp
             do j=i, ngpssize-1
                sa=s(j)
                sb=s(j+1)
                ta=t(j)
                tb=t(j+1)
                dlnndra=dlgnudr(j)*nu(j  )/n(j  )
                dlnndrb=dlgnudr(j)*nu(j+1)/n(j+1)
                fa=dlnndra*ta/sqrt(xsq(i)+sa*sa)
                fb=dlnndrb*tb/sqrt(xsq(i)+sb*sb)
                sum=sum+(1._dp/2._dp)*(fa+fb)*(sb-sa)
             enddo
             trap=(-2)*r(i)*sum

             ! Simpson 1/3 integration:
             sum=0._dp
             do j=i, ngpssize-1
                sa=s(j)
                sb=s(j+1)
                sm=0.5_dp*(sa+sb)
                !
                ra=r(j)
                rb=r(j+1)
                rm=sqrt(xsq(i)+sm*sm)/n(i)
                !
                num=nu(j)*exp(dlgnudr(j)*(rm-ra))
                nm=(1._dp+num)
                !
                ta=t(j)
                tb=t(j+1)
                tm=sm/sqrt(nm*nm*rm*rm-xsq(i))
                !
                dlnndra=dlgnudr(j)*nu(j  )/n(j  )
                dlnndrb=dlgnudr(j)*nu(j+1)/n(j+1)
                dlnndrm=dlgnudr(j)*num    /nm
                !
                fa=dlnndra*ta/sqrt(xsq(i)+sa*sa)
                fb=dlnndrb*tb/sqrt(xsq(i)+sb*sb)
                fm=dlnndrm*tm/sqrt(xsq(i)+sm*sm)
                !
                sum=sum+(1._dp/6._dp)*(fa+4*fm+fb)*(sb-sa)
             enddo
             simp=(-2)*r(i)*sum

             ! Boole 2/45 integration:
             sum=0._dp
             do j=i, ngpssize-1
                s1=s(j)
                s5=s(j+1)
                s2=0.75_dp*s1+0.25_dp*s5
                s3=0.50_dp*s1+0.50_dp*s5
                s4=0.25_dp*s1+0.75_dp*s5
                !
                r1=r(j)
                r5=r(j+1)
                r2=sqrt(xsq(i)+s2*s2)/n(i)
                r3=sqrt(xsq(i)+s3*s3)/n(i)
                r4=sqrt(xsq(i)+s4*s4)/n(i)
                !
                nu1=nu(j)
                nu2=nu(j)*exp(dlgnudr(j)*(r2-r1))
                nu3=nu(j)*exp(dlgnudr(j)*(r3-r1))
                nu4=nu(j)*exp(dlgnudr(j)*(r4-r1))
                nu5=nu(j+1)
                n1=n(j)
                n2=(1._dp+nu2)
                n3=(1._dp+nu3)
                n4=(1._dp+nu4)
                n5=n(j+1)
                !
                t1=t(j)
                t2=s2/sqrt(n2*n2*r2*r2-xsq(i))
                t3=s3/sqrt(n3*n3*r3*r3-xsq(i))
                t4=s4/sqrt(n4*n4*r4*r4-xsq(i))
                t5=t(j+1)
                !
                dlnndr1=dlgnudr(j)*nu(j  )/n(j  )
                dlnndr5=dlgnudr(j)*nu(j+1)/n(j+1)
                dlnndr2=dlgnudr(j)*nu2    /n2
                dlnndr3=dlgnudr(j)*nu3    /n3
                dlnndr4=dlgnudr(j)*nu4    /n4
                !
                f1=dlnndr1*t1/sqrt(xsq(i)+s1*s1)
                f2=dlnndr2*t2/sqrt(xsq(i)+s2*s2)
                f3=dlnndr3*t3/sqrt(xsq(i)+s3*s3)
                f4=dlnndr4*t4/sqrt(xsq(i)+s4*s4)
                f5=dlnndr5*t5/sqrt(xsq(i)+s5*s5)
                !
                sum=sum+(1._dp/90._dp)*(7*f1+32*f2+12*f3+32*f4+7*f5)*(s5-s1)
             enddo
             boole=(-2)*r(i)*sum

             prf%bst(ngpslev-i+1)=boole
          else
             prf%bst(ngpslev-i+1)=-10._dp
          endif
       enddo

       ! Extended low levels:
       do i=0,-ngpsxlow+1,-1
          lok=.true.
          do j = 1,ngpssize
             lok= lok .and. (x(j)%Var .gt. x(i)%Var)
          enddo
          if (lok) then
             do j=1,ngpssize
                s(j)=sqrt(nsq(1)*rsq(j)-xsq(i))
                t(j)=s(j)/sqrt(xsq(j)-xsq(i))
             enddo

             ! Simpson integration:
             sum=0._dp
             do j=1, ngpssize-1
                sa=s(j)
                sb=s(j+1)
                sm=0.5_dp*(sa+sb)
                !
                ra=r(j)
                rb=r(j+1)
                rm=sqrt(xsq(i)+sm*sm)/n(1)
                !
                num=nu(j)*exp(dlgnudr(j)*(rm-ra))
                nm=(1._dp+num)
                !
                ta=t(j)
                tb=t(j+1)
                tm=sm/sqrt(nm*nm*rm*rm-xsq(i))
                !
                dlnndra=dlgnudr(j)*nu(j  )/n(j  )
                dlnndrb=dlgnudr(j)*nu(j+1)/n(j+1)
                dlnndrm=dlgnudr(j)*num/nm
                !
                fa=dlnndra*ta/sqrt(xsq(i)+sa*sa)
                fb=dlnndrb*tb/sqrt(xsq(i)+sb*sb)
                fm=dlnndrm*tm/sqrt(xsq(i)+sm*sm)
                !
                sum=sum+(1._dp/6._dp)*(fa+4*fm+fb)*(sb-sa)
             enddo
             simp=(-2)*(x(i)/n(1))*sum
             alpha_B=acos(x(i)/x(1))
             prf%bst(ngpslev-i+1)=simp-2*alpha_B
          else
             prf%bst(ngpslev-i+1)=-10._dp
          endif
       enddo

       prf%bbst=.true.
    endif
  end subroutine gpsbend


  subroutine gpsbend1(prf) 2,2
    type(gpsprofile)     :: prf

    type(gpsdiff)                     :: r  (ngpssize)
    type(gpsdiff)                     :: ref(ngpssize)
    type(gpsdiff)                     :: nu (ngpssize)
    type(gpsdiff)                     :: lnu(ngpssize)
    type(gpsdiff)                     :: n  (ngpssize)
    type(gpsdiff)                     :: dlgnudr(ngpssize-1)
    type(gpsdiff)                     :: x  (-ngpsxlow+1:ngpssize)

    type(gpsdiff)                     :: angle0,angle,angleB,bend,nu0,th,sum,nexp
    real(dp)                          :: dxn
    integer                           :: ngpslev,i,j,jmin
    logical                           :: lok, lok2

    if (.not. prf%bbst) then
       ngpslev=prf%ngpslev

       ! Radial distances and impact parameters:
       do i=1,ngpslev
          prf%dst(i)= (prf%Rad+prf%geoid+prf%gst(i))
          prf%ast(i)= prf%dst(i) * (1._dp+1.e-6_dp*prf%rst(i))
       enddo
       ! Extended lower levels:
       do i=ngpslev+1,ngpslev+ngpsxlow
          prf%ast(i)= prf%ast(i-1)-50._dp
       enddo

       ! Standard levels:
       do i=1,ngpslev
          r  (i)=prf%dst(ngpslev-i+1)
          ref(i)=prf%rst(ngpslev-i+1)
       enddo
       ! Extended upper levels:
       do i=ngpslev+1,ngpssize
          r  (i)=r  (i-1)+1000._dp
          ref(i)=ref(i-1)*exp(-1000._dp/7000_dp)
       enddo

       ! log n and x:
       do i=1,ngpssize
          nu(i) = 1.e-6_dp*ref(i)
          lnu(i)= log(nu(i))
          n (i) = 1._dp+nu(i)
          x (i) = n(i)*r(i)
       enddo
       dxn=20._dp
       do i=0,-ngpsxlow+1,-1
          x (i) = x(i+1)-dxn
       enddo

       ! Radial derivatives of log refractivity.
       ! Refractivity will be assumed exponential within each shell.
       ! We store the derivative of log(nu).
       ! dn/dr = nu * dlgnudr
       do i=1,ngpssize-1
          dlgnudr(i)=(lnu(i+1)-lnu(i))/(r(i+1)-r(i))
       enddo

       ! Evaluation of complete bending for ray tangent at r(i):
       do i=-ngpsxlow+1,ngpslev
          lok=.true.
          lok2=.false.
          ! Check that the ray is not trapped
          ! For low impact (reflected, jmin<1) rays, begin at the surface
          jmin = i
          if (jmin < 1) jmin=1
          do j = jmin+1,ngpssize
             lok= lok .and. (x(j)%Var .gt. x(i)%Var)
          enddo
          if (lok) then
             ! Integration:
             sum=0._dp
             if (i.ge.1) then
                ! Direct rays
                angleB=0._dp
             else
                ! Reflected
                angleB=sqrt(2*(-i+1)*dxn/x(1))
             endif
             angle0=angleB
             do j=jmin, ngpssize-1
                th=r(j+1)-r(j)
                nu0=nu(j)
                nexp=dlgnudr(j)
                call gpsbendlayer(r(j), th, nu0, nexp, angle0, angle, bend, lok2)
                sum=sum+bend
                angle0=angle
             enddo
          endif
          if (lok2) then
             prf%bst(ngpslev-i+1)=(-2)*(sum+angleB)
          else
             prf%bst(ngpslev-i+1)=-10._dp
          endif
       enddo
    endif
  end subroutine gpsbend1


  subroutine gpsbendlayer(ra, th, nu0, nexp, angle0, angle, bend, lok) 2,2
    type(gpsdiff)        , intent(in) :: ra, th     ! Radius of inner shell (ra) and shell thickness (th)   (m)
    type(gpsdiff)        , intent(in) :: nu0, nexp  ! Refraction index coefs: n=1+nu0*exp(nexp*(r-ra)); nexp in 1/m
    type(gpsdiff)        , intent(in) :: angle0     ! Ray angle above horizon at ra
    type(gpsdiff)        , intent(out):: angle      ! Ray angle above horizon at rb
    type(gpsdiff)        , intent(out):: bend       ! Accumulated bending over the layer
    logical              , intent(out):: lok

    type(gpsdiff) :: rb,angle0i,dh,hi,rai,nu0i,anglei
    integer :: i,numunits
    
    lok=.false.
    if (th%Var.lt.0._dp) return

    ! Radius of the outer shell:
    rb = ra + th

    ! Divide layer in smaller layers:
    numunits=10
    dh =th/(1._dp*numunits)
    angle0i=angle0
    bend   =0._dp
    do i = 1, numunits
       hi =(i-1)*dh
       rai=ra+(i-1)*dh
       nu0i=nu0*exp(nexp*hi)
       call gpsbendunit(rai, dh, nu0i, nexp, angle0i, anglei, bend, lok)
       angle0i=anglei
       if (.not.lok) return
    enddo
    angle=anglei
  end subroutine gpsbendlayer


  subroutine gpsbendunit(ra, th, nu0, nexp, angle0, angle, bend, lok) 2
    type(gpsdiff)        , intent(in) :: ra, th     ! Radius of inner shell (ra) and shell thickness (th)  (m)
    type(gpsdiff)        , intent(in) :: nu0, nexp  ! Refraction index coefs: n=1+nu0*exp(nexp*(r-ra)); nexp in 1/m
    type(gpsdiff)        , intent(in) :: angle0     ! Ray angle above horizon at ra
    type(gpsdiff)        , intent(out):: angle      ! Ray angle above horizon at rb
    type(gpsdiff)        , intent(inout):: bend       ! Accumulated bending over the layer
    logical              , intent(out):: lok

    type(gpsdiff) :: rb, nu, dlnndh, g0,g1,g2,f0,f1,f2,x,a,b,c,disc,ds,bendi,g1av

    lok=.false.
    if (th%Var.lt.0._dp) return

    ! Radius of the outer shell:
    rb = ra + th

    ! Excess refraction index:
    !    at ra:    nu0
    !    at rb:    nu0*exp(nexp*h)
    nu     = nu0*exp(0.5_dp*nexp*th)
    dlnndh = nexp*nu/(1._dp+nu)

    ! Geometric trajectory:
    ! g(x) = g0+g1*x+g2*x^2
    !
    g0=0._dp
    g1=tan(angle0)
    g2=0.5_dp*dlnndh*cos(angle0)

    ! Outer circle:
    ! f(x) = f0+f1*x+f2*x^2
    !
    f0=th
    f1=0._dp
    f2=(-0.5_dp)/rb

    ! Difference:
    a=f2-g2
    b=f1-g1
    c=f0-g0

    ! Discriminant:
    disc=b*b-4*a*c
    if (disc%Var.lt.0._dp) then
       lok=.false.
       return
    else
       x =((-1)*b-sqrt(disc))/(2*a)
       g1av=g1+g2*x
       ds=x*(1._dp+(g2*x)**2)
       bendi = 2 * g2 * ds
       angle = angle0+atan(x/rb)+bendi
       bend  = bend + bendi
       if (angle%Var .gt. 0) lok=.true.
    endif
  end subroutine gpsbendunit


  subroutine gpsbndopv(impv, azmv, nval, prf, bstv),2
    real(dp)             , intent(in) :: impv(:), azmv(:)
    integer(i4)          , intent(in) :: nval
    type(gpsprofile)                  :: prf
    type(gpsdiff)        , intent(out):: bstv(:)
    
    integer                           :: iSize, i, j, ngpslev, jlocm, jlocp
    real(dp)                          :: imp1,azm1,rad, rad0
    real(dp)                          :: imp(ngpssize+ngpsxlow)
    type(gpsdiff)                     :: am, ap, da, dam, dap

    call gpsbend(prf)
    ngpslev=prf%ngpslev
    iSize = size(impv)
    if (nval < iSize) iSize=nval
    rad0=prf%rad
    !
    ! Given an impact
    !
    do i = 1, iSize
       imp1 = impv(i)
       azm1 = azmv(i)
       rad=1._dp/(cos(azm1)**2/prf%radM+sin(azm1)**2/prf%radN)
       do j=1, ngpslev+ngpsxlow
          imp(j)=prf%ast(j)%Var
       enddo
       !
       ! Search where it is located
       !
       jlocm = -1000
       jlocp = -1000
       if (imp1 > imp(1)) then
          jlocm = 1
          jlocp = 2
       endif

       do j=1, ngpslev+ngpsxlow-1
          if ((imp1 <= imp(j)) .and. (abs(prf%bst(j)%Var) < 1._dp)) then
             jlocm = j
          endif
       enddo

       do j=jlocm+1, ngpslev+ngpsxlow
          if ((imp1 >  imp(j)) .and. (abs(prf%bst(j)%Var) < 1._dp)) then
             jlocp = j
             exit
          endif
       enddo
      
       if (jlocm == -1000) jlocm = ngpslev+ngpsxlow-1
       if (jlocp == -1000) jlocp = ngpslev+ngpsxlow

       !
       ! Find properties in that band
       !
       am = prf%ast(jlocm)
       ap = prf%ast(jlocp)
       
       da = am - ap
       dam = (imp1-ap) / da
       dap = (am-imp1) / da
       
       ! Use loglinear interpolation for most data (notably direct rays)
       if (prf%bst(jlocm)%Var > 1.e-6_dp .and. prf%bst(jlocp)%Var > 1.e-6_dp) then
          bstv(i)=exp(dam*log(prf%bst(jlocm))+dap*log(prf%bst(jlocp)))*(rad/rad0)
       else
          ! Use linear interpolation for near-zero or negative bending (most reflected rays)
          bstv(i)=(dam*prf%bst(jlocm)+dap*prf%bst(jlocp))*(rad/rad0)
       endif
    enddo
  end subroutine gpsbndopv


  subroutine gpsbndopv1(impv, azmv, nval, prf, bstv) 4,2
    real(dp)             , intent(in) :: impv(:), azmv(:)
    integer(i4)          , intent(in) :: nval
    type(gpsprofile)                  :: prf
    type(gpsdiff)        , intent(out):: bstv(:)
    
    integer                           :: iSize, i, j, ngpslev, jlocm, jlocp
    real(dp)                          :: imp1,azm1,rad, rad0
    real(dp)                          :: imp(ngpssize+ngpsxlow)
    type(gpsdiff)                     :: am, ap, da, dam, dap

    call gpsbend1(prf)
    ngpslev=prf%ngpslev
    iSize = size(impv)
    if (nval < iSize) iSize=nval
    rad0=prf%rad
    !
    ! Given an impact
    !
    do i = 1, iSize
       imp1 = impv(i)
       azm1 = azmv(i)
       rad=1._dp/(cos(azm1)**2/prf%radM+sin(azm1)**2/prf%radN)
       do j=1, ngpslev+ngpsxlow
          imp(j)=prf%ast(j)%Var
       enddo
       !
       ! Search where it is located
       !
       jlocm = -1000
       jlocp = -1000
       if (imp1 > imp(1)) then
          jlocm = 1
          jlocp = 2
       endif

       do j=1, ngpslev+ngpsxlow-1
          if ((imp1 <= imp(j)) .and. (abs(prf%bst(j)%Var) < 1._dp)) then
             jlocm = j
          endif
       enddo

       do j=jlocm+1, ngpslev+ngpsxlow
          if ((imp1 >  imp(j)) .and. (abs(prf%bst(j)%Var) < 1._dp)) then
             jlocp = j
             exit
          endif
       enddo
       
       if (jlocm == -1000) jlocm = ngpslev+ngpsxlow-1
       if (jlocp == -1000) jlocp = ngpslev+ngpsxlow

       !
       ! Find properties in that band
       !
       am = prf%ast(jlocm)
       ap = prf%ast(jlocp)
       
       da = am - ap
       dam = (imp1-ap) / da
       dap = (am-imp1) / da
       
       ! Use loglinear interpolation for most data (notably direct rays)
       if (prf%bst(jlocm)%Var > 1.e-6_dp .and. prf%bst(jlocp)%Var > 1.e-6_dp) then
          bstv(i)=exp(dam*log(prf%bst(jlocm))+dap*log(prf%bst(jlocp)))*(rad/rad0)
       else
          ! Use linear interpolation for near-zero or negative bending (most reflected rays)
          bstv(i)=(dam*prf%bst(jlocm)+dap*prf%bst(jlocp))*(rad/rad0)
       endif
    enddo
  end subroutine gpsbndopv1
  
end module modgps09bend