!                               **************************************
!                               *           Module phil2             *
!                               *   R. J. Purser NOAA/NCEP/EMC 2017  *
!                               *       jim.purser@noaa.gov          *
!                               **************************************
!
! Module procedures pertaining to the project, sorting, and density
! estimation by B-spline smoothing the index delta functions of projected
! locations. The particular application that makes this approach potentially
! advantageous compared to conventional alternatives is the estimation
! of the local spatial density of data in those cases where the data tend to be
! inhomogeneously clumped, such as aircraft data, which are typically
! clustered around densely populated areas and along the heavily-trafficked
! flight tracks.
!
! Direct dependencies
! Libraries: psort,pmat
! Modules:   phil,psort,peuc,kinds, pietc
!
!=============================================================================
module phil2
!=============================================================================
! B-spline smoothers of degree 0 and 1 are provided by module procedures
! whose interface names are bsmoo0 and bsmoo1 respectively. The versions
! bsmoo0s and bsmoo1s are special forms that assume that the data locations
! have already been presorted and arranged consecutively; otherwise, the
! versions bsmoo0 and bsmoo1 include an integer index array argument, rank, that
! allows the ordered data to be accessed by indirect addressing.
! The routine, denest, performs such volumetric density estimations in a thin
! spherical shell nrand times and avergaes the results, where nrand can be
! any integerup to 30, or 100, or 300, and uses precomputed pseudo-random
! orientations of the cubic framework within which the hilbert curve is
! constructed over the sphere. The vertical randomization is performed by
! steps that are regular powers of (1/3) times one quarter of the active
! thickness (containing all the data) of the shell. 
! The precomputed random rotations are contained in the ascii file, qsets.asc,
! encoded as quaternions.
!=============================================================================
use kinds, only: dp,i_kind
implicit none
private
public:: bsmoo0,bsmoo1,denest,denest2d,&
         getqset5,getqset7,getqset8,getqset13 ! <- temporarily public

interface bsmoo0;   module procedure bsmoo0,bsmoo0s;     end interface
interface bsmoo1;   module procedure bsmoo1,bsmoo1s;     end interface
interface denest;   module procedure denest,denestx;     end interface
interface denest2d; module procedure denest2d,denest2dx; end interface
interface getqset5; module procedure getqset5;           end interface
interface getqset7; module procedure getqset7;           end interface
interface getqset8; module procedure getqset8;           end interface
interface getqset13;module procedure getqset13;          end interface

contains

!=============================================================================
subroutine bsmoo0(nob,span,sob,rank,dob)!                             [bsmoo0]
!=============================================================================
! Perform a smoothing of an irregular one-dimensional distribution of nob unit
! impulses at parameter locations, sob, using a boxcar function of total width, 
! span. The resulting 'density' at each datum location is dob. Note that this
! is the trivial instance of B-spline smoothing where the degree of the spline
! is zero.
!=============================================================================
implicit none
integer(i_kind),                intent(in   ):: nob
real(dp),                       intent(in   ):: span
real(dp),        dimension(nob),intent(in   ):: sob
integer(i_kind) ,dimension(nob),intent(in   ):: rank
real(dp),        dimension(nob),intent(inout):: dob
!-----------------------------------------------------------------------------
real(dp):: spanh,st,s1,s2
integer(i_kind) :: i,j,i1,i2,L1,L2
!=============================================================================
spanh=span/2.0_dp
i1=1
i2=1
do i=1,nob
   j=rank(i)
   st=sob(j); s1=st-spanh; s2=st+spanh
   L1=i1; L2=i2
   do i1=l1,nob; if(sob(rank(i1))>s1)exit; enddo
   do i2=l2,nob; if(sob(rank(i2))>s2)exit; enddo
   dob(j)=dob(j)+(i2-i1)/span
enddo
end subroutine bsmoo0

!=============================================================================
subroutine bsmoo0s(nob,span,sob,dob)!                                 [bsmoo0]
!=============================================================================
implicit none
integer(i_kind),        intent(in   ):: nob
real(dp),               intent(in   ):: span
real(dp),dimension(nob),intent(in   ):: sob
real(dp),dimension(nob),intent(inout):: dob
!-----------------------------------------------------------------------------
real(dp):: spanh,st,s1,s2
integer(i_kind) :: i,i1,i2,L1,L2
!=============================================================================
spanh=span/2.0_dp
i1=1
i2=1
do i=1,nob
   st=sob(i); s1=st-spanh; s2=st+spanh
   L1=i1; L2=i2
   do i1=l1,nob; if(sob(i1)>s1)exit; enddo
   do i2=l2,nob; if(sob(i2)>s2)exit; enddo
   dob(i)=dob(i)+(i2-i1)/span
enddo
end subroutine bsmoo0s

!=============================================================================
subroutine bsmoo1(nob,span,sob,rank,dob)!                             [bsmoo1]
!=============================================================================
! Perform a smoothing of an irregular one-dimensional distribution of nob unit
! impulses at parameter locations, sob, using a hat function of half-width, 
! span. The resulting 'density' at each datum location is dob. Note that this
! is the instance of a B-spline smoothing where the degree of the spline
! is one.
!
! The general idea of this algorithm is that a piecewise linear spline p(s) 
! continuously links nodes at each sb=sob(rank(ib)), 
! where the slope changes by +1.
! The distribution of delta functions smoothed by the B-spline of half-width
! span leads to a smooth estimate, dob, at each location, s=sb, given by 
!      dob(ib) = [p(sb-span) -2*p(sb) +p(sb+span)]/span**2
!
! However, because this formula, as stated, would tend to involve a "small"
! result coming from differences between approximately equally "large" numbers
! when the count, nob, of data is very large, it is preferable to 
! transform the algorithm into a better-conditioned equivalent one in order
! to keep the round-off errors small. We do this by exploiting the fact that
! the result is formally unchanged by adding ANY linear polynomial uniformly
! to the whole spline function, p(s). We choose this polynomial, at each
! stage, to maintain the adjusted spline function p just left of each targetted 
! datum, ib, the zero function, and therefore the spline function just right
! of it equal to p(s) =  (s-sb), where sb denotes the location of datum ib.
! This keeps the pairs of numerical polynomial coefficients at sa=sb-span and at
! sc=sb+span relatively small. The "slope" coefficient of each linear
! polynomial is always integral, and is therefore not subject to any round-off
! error, but the floating-point constant coefficients, pa0 and pc0, are
! in principle subject to cumulative round-off in cases where the number, nob,
! of data is very large. As a safeguard, we shrink the new pa0 and pc0 values 
! towards zero by a very tiny amount at each new datum, but by a degree that 
! should be enough to counteract any tendency for cumulative round-off to cause
! their values to spuriously diverge.
!=============================================================================
use pietc, only: u1
use kinds, only: dp,i_kind
implicit none
integer(i_kind),                intent(in   ):: nob
real(dp),                       intent(in   ):: span
real(dp),        dimension(nob),intent(in   ):: sob
integer(i_kind), dimension(nob),intent(in   ):: rank
real(dp),        dimension(nob),intent(inout):: dob
!-----------------------------------------------------------------------------
real(dp),parameter:: eps=1.0e-13_dp,shrink=1.0_dp-eps
real(dp)          :: sa,sb,sc,pa0,pc0,sbold,ds,soba,sobc,spansi
integer(i_kind)   :: ia,ib,ic,jb,La,Lc,pa1,pb1,pc1
!=============================================================================
spansi=u1/span**2
ia=1
ic=1
sbold=0
! Initially, all the polynomial coefficients vanish:
pa0=0.0_dp
pa1=0
pb1=0
pc0=0.0_dp
pc1=0
do ib=1,nob
   jb=rank(ib)
   sb=sob(jb); sa=sb-span; sc=sb+span
   ds=sb-sbold; sbold=sb
! Move the new coordinate origin to location, sb=sob(rank(ib)) and
! subtract from polynomials A and C the (old) left-polynomial at b:
   pa1=pa1-pb1
   pc1=pc1-pb1
   pa0=(pa0+ds*pa1)*shrink ! Shrink by imperceptible amount to stabilize
   pc0=(pc0+ds*pc1)*shrink ! huge computations against cumulative round-off
!  New pb0 is always taken implicitly to be zero and new pb1 becomes simply:   
   pb1=1! < (new) rt-polynomial p(s) at B is trivially p=(s-sob(rank(ib)))
! Update the new locations of A and C (straddling B by distance, span) and
! the corresponding polynomial coefficients, {pa0,pa1} and {pc0,pc1} valid 
! there:
   La=ia; Lc=ic
   do ia=La,nob
      soba=sob(rank(ia)); if(soba>sa)exit; pa0=pa0+sb-soba; pa1=pa1+1
   enddo
   do ic=Lc,nob
      sobc=sob(rank(ic)); if(sobc>sc)exit; pc0=pc0+sb-sobc; pc1=pc1+1
   enddo
! The formula, [p(sb-span) -2*p(sb) +p(sb+span)], simplies to: 
   dob(jb)=dob(jb)+(pa0+pc0+span*(pc1-pa1))*spansi
enddo
end subroutine bsmoo1
!=============================================================================
subroutine bsmoo1s(nob,span,sob,dob)!                                 [bsmoo1]
!=============================================================================
use pietc, only: u1
use kinds, only: dp,i_kind
implicit none
integer(i_kind),        intent(in   ):: nob
real(dp),               intent(in   ):: span
real(dp),dimension(nob),intent(in   ):: sob
real(dp),dimension(nob),intent(inout):: dob
!-----------------------------------------------------------------------------
real(dp),parameter:: eps=1.e-13_dp,shrink=1.0_dp-eps
real(dp)          :: sa,sb,sc,pa0,pc0,sbold,ds,spansi
integer(i_kind)           :: ia,ib,ic,La,Lc,pa1,pb1,pc1
!=============================================================================
spansi=u1/span**2
ia=1
ic=1
sbold=0.0_dp
! Initially, all the polynomial coefficients vanish:
pa0=0.0_dp
pa1=0
pb1=0
pc0=0.0_dp
pc1=0
do ib=1,nob
   sb=sob(ib); sa=sb-span; sc=sb+span
   ds=sb-sbold; sbold=sb
! Move the new coordinate origin to location, sb=sob(ib) and
! subtract from polynomials A and C the (old) left-polynomial at b:
   pa1=pa1-pb1
   pc1=pc1-pb1
   pa0=(pa0+ds*pa1)*shrink ! Shrink by imperceptible amount to stabilize
   pc0=(pc0+ds*pc1)*shrink ! huge computations against cumulative round-off
!  New pb0 is always taken implicitly to be zero and new pb1 becomes simply:   
   pb1=1! < (new) rt-polynomial p(s) at B is trivially p=(s-sob(ib))
! Update the new locations of A and C (straddling B by distance, span) and
! the corresponding polynomial coefficients, {pa0,pa1} and {pc0,pc1} valid 
! there:
   La=ia; Lc=ic
   do ia=La,nob; if(sob(ia)>sa)exit; pa0=pa0+sb-sob(ia); pa1=pa1+1; enddo
   do ic=Lc,nob; if(sob(ic)>sc)exit; pc0=pc0+sb-sob(ic); pc1=pc1+1; enddo
! The formula, [p(sb-span) -2*p(sb) +p(sb+span)], simplies to: 
   dob(ib)=dob(ib)+(pa0+pc0+span*(pc1-pa1))*spansi
enddo
end subroutine bsmoo1s

!=============================================================================
subroutine denest(nob,nrand,nor, &!                                   [denest]
                  re,dentrip,hscale,vscale,vmin,vmax,&
                  latob,lonob,altob,wtob)
!=============================================================================
! Use nrand randomized Hilbert curves over a sufficiently thick spherical
! shell to estimate the density of data relative to the "unit" volume
! implied by the vertical smoothing scale time the square of the horizontal
! smoothing scale. 
!
! nob:    the number of data
! nrand:  the number of distinct pseudo-random Hilbert curves
! nor:    the order of the B-spline smoother (either 0 or 1)
! re:     the nominal radius of the earth
! dentrip:density criterion tripping the downward adjustment to the wt factor
! hscale: (in the same units) the characteristic horizontal scale of averaging
! vscale: (in the same units) the characteristic vertical scale of averaging
! vmin:   (in same units) lowest altitude bound for valid data
! vmax:   (in same units) greatest altitude bound for valid data 
! latob:  array of observation latitudes (degrees north)
! lonob:  array of observation longitudes (degrees east)
! altob:  array of observation altitudes (same units as other distance scales)
! troflg: logic parameter to determin wether dentrip in tropic different from
!         other region
! wtob:   Weight factor implied by the estimated data density and dentrip
!
! Internally, it is convenient to relate all distance units to "Hilbert
! parameter units" where 24 of these units fill the 24 fundamental 
! quadrilateral panels that form the horizontal footprint
! of the spherical shell. This means that one Hilbert unit corresponds to
! a horizontal distance of sqrt(pi/6)*re, or about Uh=4610 km. The thickness,
! in Hilbert distance, of the valid vertical range is
! (vmax-vmin)*hscale/(uh*vscale), which must be less than one for the
! present "thin shell" Hilbert curve construction. We inflate this thickness
! by a small margin to leave room for vertical location randomization of
! the Hilbert curves, and round up to the next integer(i_kind) power, ngen4, of 1/2.
! Ngen4 effectively determines the generation at which the Hilbert curve
! transitions from being 2-dimensional (at coarse scales) to being 
! 3-dimensional (at and below the scale of the effective thickness of the 
! shell that the curve fills). For generations, 1--ngen4, the expansion is
! at base-4; at ngen4--ngen the expansion is base-8.
!=============================================================================
use pietc, only: u0,u1,u2,o2,pi,dtor
use kinds, only: dp,i_kind
use peuc,  only: qtorot,mulqq
use phil0, only: xs_to_hil48,hil8_to_r,hil4_to_r
use psort, only: sort, invertperm
use qcmod, only:troflg,lat_c
implicit none
integer(i_kind),        intent(in ):: nob,nrand,nor
real(dp),               intent(in ):: re,dentrip,hscale,vscale,vmin,vmax
real(dp),dimension(nob),intent(in ):: latob,lonob,altob
real(dp),dimension(nob),intent(out):: wtob
!-----------------------------------------------------------------------------
integer(i_kind),parameter         :: ngen=14
real(dp),       dimension(3,nob)  :: xob
real(dp),       dimension(nob)    :: rob,sob,latobc
real(dp),       dimension(0:3)    :: qset
real(dp),       dimension(0:3,3)  :: qset3
real(dp),       dimension(0:3,5)  :: qset5
real(dp),       dimension(0:3,7)  :: qset7
real(dp),       dimension(0:3,13) :: qset13
real(dp),       dimension(3,3)    :: rot,rotnew,rotold
real(dp)                          :: span,uv,uh,vh,vhq,vhp,v,vrand,&
                                     rlat,clat,slat,rlon,clon,slon
integer(i_kind),dimension(nob)    :: rank
integer(i_kind),dimension(0:ngen) :: hilr
integer(i_kind)                   :: i,irand,j,k,L,ngen4,ntri,dentripc
!=============================================================================
uh=sqrt(pi/6.0_dp)*re
uv=(uh*vscale)/hscale
vh=(vmax-vmin)/uv
vhq=vh/4.0_dp
vhp=vh+vhq
if(vhp>=u1)then
   print&
    '("In denest; vmax-vmin too large for thin-shell assumption to be valid")'
   print'("Make this vertical range smaller, or vscale/hscale larger")'
   stop
endif

v=o2
do ngen4=0,ngen-1
   if(v<vhp)exit
   v=v/2.0_dp
enddo
v=v*2.0_dp ! <- Shell thickness in Hilbert units
!print'('' ngen4 = '',i4)',ngen4
! Find the Hilbert curve parameter span that corresponds to the characteristic
! smoothing volumne, hscale**2*vscale:
span=(u2**ngen4)*(hscale/uh)**3

! Find the first power of three that equals or exceeds nrand:
do ntri=0,8
   i=3**ntri
   if(i>=nrand)exit
enddo
vrand=vhq/i
!vrand=3*vhq/i

! Convert lat and lon to more convenient unit cartesian vectors:
do L=1,nob
   rlat=latob(L)*dtor; clat=cos(rlat); slat=sin(rlat)
   if(troflg) latobc(L)=latob(L)
   rlon=lonob(L)*dtor; clon=cos(rlon); slon=sin(rlon)
   xob(:,L)=(/clat*clon,clat*slon,slon/)
   rob(L)=(altob(L)-vmin)/uv-vrand! <- altitudes in hilbert vertical units
enddo
if(nrand<1 .or. nrand>273)stop 'nrand is invalid'
if(nrand>5)then; call getqset7(      qset7); if(nrand>7)call getqset13(qset13)
else;            call getqset5(nrand,qset5)
endif
if(nrand>91)     call getqset5(3,qset3(:,:))


! Project the data onto nrand differently-oriented Hilbert curves and sum
! the different estimated density estimates at the ob points in array wtob:
rotnew=u0; do i=1,3; rotnew(i,i)=u1; enddo ! <- Identity matrix.
wtob=0.0_dp
do irand=1,nrand
   rotold=rotnew
   select case(nrand)
      case(1:5)
         qset=qset5(:,irand)
      case(6:7)
         qset=qset7(:,irand)
      case(8:13)
         qset=qset13(:,irand)
      case(14:91)
         i=1+mod(irand-1,13)
         j=1+(irand-1)/13
         qset=mulqq(qset7(:,j),qset13(:,i))
      case(92:273)
         i=1+mod(irand-1,13)
         j=1+(irand-1)/13
         k=1+(j-1)/7
         j=1+mod(j-1,7)
         qset=mulqq(mulqq(qset13(:,i),qset3(:,k)),qset7(:,j))
   end select

   call qtorot(qset,rotnew) ! convert to an orthogonal matrix
! Form the relative rotation, rot (relative to previous irand iteration):
   rot=matmul(rotnew,transpose(rotold))

! Get a new fraction of vh/4, based on ternary subdivisions such that the
! binary expansions (in units of vh) are non-terminating. This helps ensure
! that the resulting Hilbert curve projections are not accidentally similar.
   do L=1,nob
      xob(:,L)=matmul(rot,xob(:,L))
      rob(L) =rob(L)+vrand ! Randomize by new vertical displacement
   enddo
   do L=1,nob
      sob(L)=0_dp
      call xs_to_hil48(ngen4,ngen,xob(:,L),rob(L), hilr)
      call hil8_to_r(ngen4+1,ngen,hilr(ngen4+1:ngen),sob(L))
      call hil4_to_r(1,ngen4,hilr(1:ngen4),sob(L))
      sob(L)=sob(L)+hilr(0)
   enddo! L
! Sort the data implicitly by assigning each a "rank":
   call sort(sob,rank); call invertperm(rank)
   select case(nor)
      case(0)
         call bsmoo0(nob,span,sob,rank,wtob)
      case(1)
         call bsmoo1(nob,span,sob,rank,wtob)
      case default
         stop 'In denest; this value of B-spline order, nor, is not supported'
   end select
enddo! irand
! Convert the sum of Hilbert-parameter-relative densities to an average,
! and convert the units of density to number-per-span:
wtob=wtob*span/nrand
do L=1,nob
   dentripc=dentrip
   if(troflg) then
      if(abs(latobc(L)) <lat_c) then
         dentripc=2*dentrip
      else
         dentripc=dentrip
      endif
   endif   
   wtob(L)=min(u1,dentripc/wtob(L))
enddo

end subroutine denest
!=============================================================================
subroutine denestx(nob,nrand,nor, &!                                  [denest]
     re,dentrip,hscale,vscale,vmin,vmax,&
     latob,lonob,altob,wtob,denob)
!=============================================================================
! Like denest, but also returns the diagnosed density at all ob points
! denob:  array of observation densities, per unit scale volume, where the
!         scale volume is simply hscale**2*vscale.
!=============================================================================
use pietc, only: u0,u1,u2,o2,pi,dtor
use kinds, only: dp,i_kind
use peuc,  only: qtorot,mulqq
use phil0, only: xs_to_hil48,hil8_to_r,hil4_to_r
use psort, only: sort, invertperm
use qcmod, only: troflg,lat_c
implicit none
integer(i_kind),        intent(in ):: nob,nrand,nor
real(dp),               intent(in ):: re,dentrip,hscale,vscale,vmin,vmax
real(dp),dimension(nob),intent(in ):: latob,lonob,altob
real(dp),dimension(nob),intent(out):: wtob,denob
!-----------------------------------------------------------------------------
integer(i_kind),parameter        :: ngen=14
real(dp),       dimension(3,nob) :: xob
real(dp),       dimension(nob)   :: rob,sob,latobc
real(dp),       dimension(0:3)   :: qset
real(dp),       dimension(0:3,5) :: qset5
real(dp),       dimension(0:3,8) :: qset8
real(dp),       dimension(0:3,13):: qset13
real(dp),       dimension(3,3)   :: rot,rotnew,rotold
real(dp)                         :: span,uv,uh,vh,vhq,vhp,v,vrand,&
                                    rlat,clat,slat,rlon,clon,slon
integer(i_kind),dimension(nob)   :: rank
integer(i_kind),dimension(0:ngen):: hilr
integer(i_kind)                  :: i,irand,j,L,ngen4,ntri,dentripc
!=============================================================================
uh=sqrt(pi/6.0_dp)*re
uv=(uh*vscale)/hscale
vh=(vmax-vmin)/uv
vhq=vh/4.0_dp
vhp=vh+vhq
if(vhp>=u1)then
   print&
    '("In denest; vmax-vmin too large for thin-shell assumption to be valid")'
   print'("Make this vertical range smaller, or vscale/hscale larger")'
   stop
endif

v=o2
do ngen4=0,ngen-1
   if(v<vhp)exit
   v=v/2.0_dp
enddo
v=v*2.0_dp ! <- Shell thickness in Hilbert units
!print'('' ngen4 = '',i4)',ngen4
! Find the Hilbert curve parameter span that corresponds to the characteristic
! smoothing volumne, hscale**2*vscale:
span=(u2**ngen4)*(hscale/uh)**3

! Find the first power of three that equals or exceeds nrand:
do ntri=0,8
   i=3**ntri
   if(i>=nrand)exit
enddo
vrand=3.0_dp*vhq/i

! Convert lat and lon to more convenient unit cartesian vectors:
do L=1,nob
   if(troflg) latobc(L)=latob(L)
   rlat=latob(L)*dtor; clat=cos(rlat); slat=sin(rlat)
   rlon=lonob(L)*dtor; clon=cos(rlon); slon=sin(rlon)
   xob(:,L)=(/clat*clon,clat*slon,slon/)
   rob(L)=(altob(L)-vmin)/uv-vrand! <- altitudes in hilbert vertical units
enddo
if(nrand<1 .or. nrand>104)stop 'nrand is invalid'
if(nrand>5)then; call getqset8(      qset8); if(nrand>8)call getqset13(qset13)
else;            call getqset5(nrand,qset5)
endif

! Project the data onto nrand differently-oriented Hilbert curves and sum
! the different estimated density estimates at the ob points in array denob:
rotnew=u0; do i=1,3; rotnew(i,i)=u1; enddo ! <- Identity matrix.
denob=0_dp
do irand=1,nrand
   rotold=rotnew
   select case(nrand)
      case(1:5)
         qset=qset5(:,irand)
      case(6:8)
         qset=qset8(:,irand)
      case(9:13)
         qset=qset13(:,irand)
      case(14:104)
         i=1+mod(irand-1,13)
         j=1+(irand-1)/13
         qset=mulqq(qset8(:,j),qset13(:,i))
   end select

   call qtorot(qset,rotnew) ! convert to an orthogonal matrix
! Form the relative rotation, rot (relative to previous irand iteration):
   rot=matmul(rotnew,transpose(rotold))

! Get a new fraction of vh/4, based on ternary subdivisions such that the
! binary expansions (in units of vh) are non-terminating. This helps ensure
! that the resulting Hilbert curve projections are not accidentally similar.
   do L=1,nob
      xob(:,L)=matmul(rot,xob(:,L))
      rob(L) =rob(L)+vrand ! Randomize by new vertical displacement
   enddo
   do L=1,nob
      sob(L)=0_dp
      call xs_to_hil48(ngen4,ngen,xob(:,L),rob(L), hilr)
      call hil8_to_r(ngen4+1,ngen,hilr(ngen4+1:ngen),sob(L))
      call hil4_to_r(1,ngen4,hilr(1:ngen4),sob(L))
      sob(L)=sob(L)+hilr(0)
   enddo! L
! Sort the data implicitly by assigning each a "rank":
   call sort(sob,rank); call invertperm(rank)
   select case(nor)
      case(0)
         call bsmoo0(nob,span,sob,rank,denob)
      case(1)
         call bsmoo1(nob,span,sob,rank,denob)
      case default
         stop 'In denest; this value of B-spline order, nor, is not supported'
   end select
enddo! irand
! Convert the sum of Hilbert-parameter-relative densities to an average,
! and convert the units of density to number-per-span:
denob=denob*span/nrand
do L=1,nob
   dentripc=dentrip
   if(troflg) then
      if(abs(latobc(L)) <lat_c) then
         dentripc=2*dentrip
      else
         dentripc=dentrip
      endif
   endif   
   wtob(L)=min(u1,dentripc/wtob(L))
enddo

end subroutine denestx

!=============================================================================
subroutine denest2d(nob,nrand,nor,dentrip,scale,& !                 [denest2d]
                    xmin,xmax,ymin,ymax, &
                    xob,yob,wtob)
!=============================================================================
! Use nrand randomized Hilbert curves over a sufficiently large square tile
! such that, regardless of its orientation when rotated about an axis 
! through a point one third the distance along the tiles diagonal, and through
! the center of the rectangular domain, the entire domain is covered by the 
! tile. The "randomization" comprises rotations of the tile by equal angular
! intervals of 90/nrand degrees. The density of data is estimated by
! B-spline smoothing the index delta-functions of the projected data on
! each tile-inscribed Hilbert curve, averaged over all nrand hilbert curves,
! with the scale of smoothing on the hilbert curves calibrated to match
! the intended spatial averaging scale. In a final step, the implied 
! weighting factor is computed such that, when the estimated data (obs per
! smoothing area, scale**2) is less than dentrip, this weighting factor
! wtob=1, but when the density is greater than dentrip, wtob is proportionately
! reduced.
!
! nob:     the number of data
! nrand:   the number of distinct pseudo-random Hilbert curves
! nor:     the order of the B-spline smoother (either 0 or 1)
! dentrip: the critical density that trips the weight factor reduction 
! scale:   the characteristic horizontal scale of averaging
! xmin,xmax: domain limits in x
! ymin,ymax: domain limits in y
! xob,yob  : arrays of observation locations in x and y
! wtob     : array of implied weight factors 
!=============================================================================
use pietc, only: u1,o3,pih
use kinds, only: dp,i_kind
use phil0,  only: xy_to_hil4,hil4_to_r
use psort, only: sort, invertperm
implicit none
integer(i_kind),        intent(in ):: nob,nrand,nor
real(dp),               intent(in ):: dentrip,scale,xmin,xmax,ymin,ymax
real(dp),dimension(nob),intent(in ):: xob,yob
real(dp),dimension(nob),intent(out):: wtob
!-----------------------------------------------------------------------------
integer(i_kind),parameter       :: ngen=14
real(dp),       parameter       :: eps=1.0e-11_dp
real(dp),       dimension(2,nob):: hob
real(dp),       dimension(nob)  :: sob
real(dp),       dimension(2,2)  :: rot
real(dp),       dimension(2)    :: vo3
real(dp)                        :: xcen,ycen,rad,rad3,span,ang,cang,sang
integer(i_kind),dimension(ngen) :: hilr
integer(i_kind),dimension(nob)  :: rank
integer(i_kind)                  :: irand,L
!==============================================================================
vo3=(/o3,o3/)! Tile-relative axis of rotation is at (1/3, 1/3)
xcen=(xmin+xmax)/2.0_dp; ycen=(ymin+ymax)/2.0_dp
rad=eps+sqrt((xmax-xmin)**2+(ymax-ymin)**2)/2.0_dp
rad3=rad*3.0_dp ! Size of the edge of the Hilbert curve square tile
ang=pih/nrand; cang=cos(ang); sang=sin(ang)
rot(:,1)=(/ cang,sang/)
rot(:,2)=(/-sang,cang/)
span=(scale/rad3)**2 
do L=1,nob
   hob(1,L)=o3+(xob(L)-xcen)/rad3
   hob(2,L)=o3+(yob(L)-ycen)/rad3
enddo

! Project the data onto nrand differently-oriented Hilbert curves and sum
! the different estimated density estimates at the ob points in array wtob:
wtob=0
do irand=1,nrand
   do L=1,nob
      sob(L)=0_dp
      call xy_to_hil4(hob(1,L),hob(2,L),hilr)
      call hil4_to_r(1,ngen,hilr,sob(L))
   enddo
! Sort the data implicitly by assigning each a "rank":
   call sort(sob,rank); call invertperm(rank)
   select case(nor)
      case(0)
         call bsmoo0(nob,span,sob,rank,wtob)
      case(1)
         call bsmoo1(nob,span,sob,rank,wtob)
      case default
         stop 'In denest; this value of B-spline order, nor, is not supported'
   end select
! Rotate the Hilbert tile by a pi/(2*nrand) about its axis at (1/3,1/3):
   if(L<nob)then
      do L=1,nob
         hob(:,L)=vo3+matmul(rot,hob(:,L)-vo3)
      enddo
   endif
enddo! irand
wtob=wtob*span/nrand
do L=1,nob
   wtob(L)=min(u1,dentrip/wtob(L))
enddo
end subroutine denest2d
!=============================================================================
subroutine denest2dx(nob,nrand,nor,dentrip,scale, &!                [denest2d]
     xmin,xmax,ymin,ymax, &
     xob,yob,wtob,denob)
!=============================================================================
! Like denest, but also returns the diagnosed density at all ob points
! denob    : array of estimated data densities (units per scale**2)
!=============================================================================
use pietc, only: u1,o3,pih
use kinds, only: dp,i_kind
use phil0, only: xy_to_hil4,hil4_to_r
use psort, only: sort, invertperm
implicit none
integer(i_kind),                intent(in ):: nob,nrand,nor
real(dp),               intent(in ):: dentrip,scale,xmin,xmax,ymin,ymax
real(dp),dimension(nob),intent(in ):: xob,yob
real(dp),dimension(nob),intent(out):: wtob,denob
!-----------------------------------------------------------------------------
integer(i_kind), parameter       :: ngen=14
real(dp),parameter       :: eps=1.e-11_dp
real(dp),dimension(2,nob):: hob
real(dp),dimension(nob)  :: sob
real(dp),dimension(2,2)  :: rot
real(dp),dimension(2)    :: vo3
real(dp)                 :: xcen,ycen,rad,rad3,span,ang,cang,sang
integer(i_kind),dimension(ngen)  :: hilr
integer(i_kind),dimension(nob)   :: rank
integer(i_kind)                  :: irand,L
!==============================================================================
vo3=(/o3,o3/)! Tile-relative axis of rotation is at (1/3, 1/3)
xcen=(xmin+xmax)/2.0_dp; ycen=(ymin+ymax)/2.0_dp
rad=eps+sqrt((xmax-xmin)**2+(ymax-ymin)**2)/2.0_dp
rad3=rad*3.0_dp ! Size of the edge of the Hilbert curve square tile
ang=pih/nrand; cang=cos(ang); sang=sin(ang)
rot(:,1)=(/ cang,sang/)
rot(:,2)=(/-sang,cang/)
span=(scale/rad3)**2 
do L=1,nob
   hob(1,L)=o3+(xob(L)-xcen)/rad3
   hob(2,L)=o3+(yob(L)-ycen)/rad3
enddo

! Project the data onto nrand differently-oriented Hilbert curves and sum
! the different estimated density estimates at the ob points in array denob:
denob=0_dp
do irand=1,nrand
   do L=1,nob
      sob(L)=0
      call xy_to_hil4(hob(1,L),hob(2,L),hilr)
      call hil4_to_r(1,ngen,hilr,sob(L))
   enddo
! Sort the data implicitly by assigning each a "rank":
   call sort(sob,rank); call invertperm(rank)
   select case(nor)
      case(0)
         call bsmoo0(nob,span,sob,rank,denob)
      case(1)
         call bsmoo1(nob,span,sob,rank,denob)
      case default
         stop 'In denest; this value of B-spline order, nor, is not supported'
   end select
! Rotate the Hilbert tile by a pi/(2*nrand) about its axis at (1/3,1/3):
   if(L<nob)then
      do L=1,nob
         hob(:,L)=vo3+matmul(rot,hob(:,L)-vo3)
      enddo
   endif
enddo! irand
denob=denob*span/nrand
do L=1,nob
   wtob(L)=min(u1,dentrip/denob(L))
enddo
end subroutine denest2dx

!=============================================================================
subroutine getqset5(n,qset5)!                                       [getqset5]
!=============================================================================
! Fill the rows of the n-row array, qset5, with the quaternions that
! represent, for that n, the optimally diverse rotations of a cube in the
! sense that the minimum angular distance between members of the set is as
! large as possible. In each case, the first row contains the identity and
! represents the cube in its standard Cartesian orientation.
!=============================================================================
use kinds, only: dp,i_kind
use pietc, only: u0,u1,u3,o2,r2,or2,phi
implicit none
integer(i_kind),          intent(in ):: n
real(dp),dimension(0:3,n),intent(out):: qset5
!-----------------------------------------------------------------------------
real(dp),parameter:: u8=8.0_dp,or8=u1/sqrt(u8),sig=u1/phi,chi=r2-u1
real(dp)          :: term1,term2,ce,cf,cg,ch,cj,ck,cl
!=============================================================================
qset5(:,1)=(/u1,u0,u0,u0/)
select case(n); case default; stop 'In getqset5; n is outside the valid range'
   case(1)
   case(2:3)
      term1=o2+or8
      term2=-o2+or8
      qset5        (:,2)=(/term1,-or8,term2, or8/)
      if(n==3)qset5(:,3)=(/term1, or8,term2,-or8/)
   case(4)
      term1=(4.0_dp+6.0_dp*r2)/14.0_dp
      term2=(4.0_dp-  r2)/14.0_dp
      qset5(:,2)=(/term1,2_dp*term2,    -term2,     term2/)
      qset5(:,3)=(/term1,     term2,2_dp*term2,    -term2/)
      qset5(:,4)=(/term1,    -term2,     term2,2_dp*term2/)
   case(5)
      ce=u1/(2.0_dp*sig+chi-sqrt(4.0_dp*sig+2_dp*chi-u3))
      cf=chi*ce
      cg=-u1+(-u1+r2*phi)*ce
      ch=-phi+(u1+r2*sig)*ce
      cj=sig/r2+(-u1+r2*sig)*ce
      ck=or2+(-u1+r2*chi*sig)*ce
      cl=phi*or2-ce
      qset5(:,2)=(/ce, cf,-cg, ch/)
      qset5(:,3)=(/ce,-cj, ck, cl/)
      qset5(:,4)=(/ce, ck, cj,-cl/)
      qset5(:,5)=(/ce, cg,-ch,-cf/)
end select
end subroutine getqset5

!==============================================================================
subroutine getqset7(qset7)!                                          [get1set7]
!==============================================================================
! Return a set of 7 quaternions that represent a configuration of cubic
! rotations that are as far (in terms of minimum relative rotation angle)
! from each other as possible. The configuration is formed by rotations of
! the initial standard cube by integer(i_kind) multiples of the angle pi*2/7 about
! an oblique axis, though the angle separating non-adjacent memmbers of this
! cycle is slightly smaller than pi*2/7.
!=============================================================================
use kinds, only: dp,i_kind
use pietc, only: u0,u1,r2,pi
use peuc,  only: mulqq
implicit none
real(dp),dimension(0:3,7),intent(out):: qset7
!-----------------------------------------------------------------------------
real(dp),parameter:: pio7=pi/7
real(dp)          :: k1,k2,k3,a,b,w,x,y,z
!=============================================================================
k1=2.0_dp*cos(  pio7)
k2=2.0_dp*cos(2.0_dp*pio7)
k3=2.0_dp*cos(3.0_dp*pio7)
a=4.0_dp*(5.0_dp-2.0_dp*k3-4.0_dp*k3**2-r2*(k3-1.0_dp))
a=4.0_dp*(6.0_dp*k1-5.0_dp-2.0_dp*k2+r2*(k1-k2-2.0_dp))
b=(u1/k3+r2*k3)
w=(b+sqrt(b**2-a))/a
x=r2*w-k2/2.0_dp
y=(k1-2.0_dp)*r2*w+k2/2.0_dp
z=(2.0_dp+(1.0_dp-k1)*r2)*w-k2/2.0_dp
qset7(:,1)=(/u1,   u0,          u0,             u0             /)
qset7(:,2)=(/k1/2_dp,-z/k1,       -x/k1,           y/k1           /)
qset7(:,3)=(/w,   -(y+z)/r2,    (k2/2_dp-x)/r2,    (y-z)/r2       /)
qset7(:,4)=(/w,    k2*(x-y)/r2,-(k2*z-k3/2_dp)/r2,-(k2*z+k3/2_dp)/r2 /)
qset7(:,5)=(/w,    k2*(x-y)/r2,-(k2*z+k3/2_dp)/r2,-(k2*z-k3/2_dp)/r2 /)
qset7(:,6)=(/w,    (z-y)/r2,   -(k2/2_dp-x)/r2,   -(y+z)/r2       /)
qset7(:,7)=(/k1/2_dp, z/k1,         x/k1,         -y/k1           /)
end subroutine getqset7

!==============================================================================
subroutine getqset8(qset8)!                                          [getqset8]
!==============================================================================
! Return a set of 8 quaternions that represent a configuration of cubically-
! symmetric rotations, from an orientation halfway between qset8(:,1) and 
! qset8(:,2), by an equal angle through axes through the long diagonal of
! that intermediate initial cube. The angle is optimized to make the angular
! distance between the eight resulting cubes as large as possible.
! The initial cube's oriention is chosen to make one of the eight rotations
! of be the standard orientation, coded by qset8(:,1) = the identity.
!==============================================================================
use kinds, only: dp,i_kind
use pietc, only: u1,or2
implicit none
real(dp),dimension(0:3,8),intent(out):: qset8
!------------------------------------------------------------------------------
real(dp),parameter:: o14=u1/14_dp,or98=or2/7_dp
!==============================================================================
qset8(:,1)=(/1_dp,0_dp,0_dp,0_dp/)
qset8(:,2)=(/13_dp,3_dp,3_dp,3_dp/)  *o14
qset8(:,3)=(/13_dp,-5_dp,1_dp,-1_dp/)*o14
qset8(:,4)=(/13_dp,-1_dp,-5_dp,1_dp/)*o14
qset8(:,5)=(/13_dp,1_dp,-1_dp,-5_dp/)*o14
qset8(:,6)=(/9_dp,3_dp,-2_dp,1_dp/)*or98
qset8(:,7)=(/9_dp,2_dp,3_dp,-2_dp/)*or98
qset8(:,8)=(/9_dp,-2_dp,2_dp,3_dp/)*or98
end subroutine getqset8

!=============================================================================
subroutine getqset13(qset13)!                                      [getqset13]
!=============================================================================
! Return a set of 13 quaternions that represent a configuration of
! the identity, together with 12 cubically-symmetric rotations of the
! cube in the identity orientation by an angle about axes through opposite
! edge-midpoints of that original cube. The angle is chosen to maximize the
! minimum angular distance between the resulting configurations.
!=============================================================================
use kinds, only: dp,i_kind
use pietc, only: u0,u1,r2
implicit none
real(dp),dimension(0:3,13),intent(out):: qset13
!-----------------------------------------------------------------------------
real(dp),parameter:: term1=29.0_dp*r2-u1,term2=(11.0_dp+9.0_dp*r2+2.0_dp*sqrt(term1))/41.0_dp,&
                     term3=(30.0_dp-9.0_dp*r2-2.0_dp*sqrt(term1))/82.0_dp,&
                     compa=sqrt(term2),compb=sqrt(term3)
!=============================================================================
qset13(:, 1)=(/u1,u0,u0,u0/)

qset13(:, 2)=(/compa,u0, compb, compb/)
qset13(:, 3)=(/compa,u0, compb,-compb/)
qset13(:, 4)=(/compa,u0,-compb, compb/)
qset13(:, 5)=(/compa,u0,-compb,-compb/)

qset13(:, 6)=(/compa, compb,u0, compb/)
qset13(:, 7)=(/compa,-compb,u0, compb/)
qset13(:, 8)=(/compa, compb,u0,-compb/)
qset13(:, 9)=(/compa,-compb,u0,-compb/)

qset13(:,10)=(/compa, compb, compb,u0/)
qset13(:,11)=(/compa, compb,-compb,u0/)
qset13(:,12)=(/compa,-compb, compb,u0/)
qset13(:,13)=(/compa,-compb,-compb,u0/)
end subroutine getqset13

end module phil2