! interpol.f90
! module to hold routines for spatial interpolation
! contains: CrudeInterpol,FlatSurf,CrudeSmoo,Dupli90s
! requires higher up in call: SortMod, FileNames, GrimFiles, Grid, Regress 

module Interpol

implicit none

contains

!*******************************************************************************
! manages crude interpolation from one grid to another, minimising array sizes while loading all at once
! approach:
! 1. initially loads all data and defines grids
! 2. iterates through each time step and transforms spatial field into high-res spatial field
! 3. saves high-res data set as new grim
! steps in iteration:
! a. places all raw data from RAW vector into larger vector (same LOw res) padded with missing boxes
! b. by growing the valid domain we give values to all the LOw res missing boxes (using NearNeigh)
! c. we smooth out the LOw res data into HIgh res data (using CrudeSmoo)
! d. we SHRINK the bounds of the HIgh res data to match the target (InterPOLated) data
! e. we reduce the domain of the SHRINK data into that of the target (InterPOLated) data

subroutine CrudeInterpol (LoadFile,SaveFile,ExeMulti,WyeMulti,Extent,IpolGrid,IpolBounds,IpolName)

use FileNames
use GrimFiles
use Grid

real, pointer, dimension (:,:,:)	:: BriefRawData, RawData,IpolData
real, pointer, dimension (:)		:: LoVec,IterVec,HiVec,ShrinkVec

integer, pointer, dimension (:,:)	:: RawGrid,IpolGrid
integer, pointer, dimension (:,:)	:: LoGrid,HiGrid,LoToHi,ShrinkGrid,HiToShrink
integer, pointer, dimension (:)		:: BriefYearAD,YearAD

real, dimension (4) 			:: RawBounds,IpolBounds

real, intent(in)			:: ExeMulti, WyeMulti
integer, intent(in)			:: Extent

real, parameter :: MissVal = -999.0

integer :: AllocStat
integer :: YearN,MonthN,XMonth,XYear
integer :: RawExeN, RawWyeN, RawBoxN, RawCellN, XRawExe, XRawWye
integer :: LoExeN, LoWyeN, LoBoxN, XLoExe, XLoWye
integer :: IpolExeN,IpolWyeN,IpolBoxN,XIpolExe,XIpolWye
integer :: ShrinkBoxN,HiExeN,HiWyeN,HiCellN,XIter,CountMiss
integer :: ValidShrink

character (len=80) :: RawInfo,IpolInfo,IpolName
character (len=80) :: LoadFile,SaveFile
character (len= 4) :: RawSuffix,IpolSuffix

!***************************************
! get initial data and all grids
								! load Raw data+grid
call LoadGrim (RawData,RawGrid,YearAD,RawBounds,RawInfo,LoadFile,"    ",RawSuffix)

!call LoadGrim (BriefRawData,RawGrid,BriefYearAD,RawBounds,RawInfo,LoadFile,"    ",RawSuffix)
!print*, "  > Duplicating into 1996-2000..."
!call Dupli90s (BriefRawData,RawData,BriefYearAD,YearAD)
!deallocate (BriefRawData,BriefYearAD,stat=AllocStat)
!if (AllocStat.NE.0) print*, "  > ##### ERROR: CrudeInterpol: Allocation failure: Brief* #####"

print*, "  > Initialising..."

IpolInfo = trim(RawInfo) // ' ->' // trim(IpolName) 

YearN   = size(RawData,1) ; MonthN  = size(RawData,2) ; RawBoxN = size(RawData,3)
RawExeN = size(RawGrid,1) ; RawWyeN = size(RawGrid,2) ; RawCellN = RawExeN * RawWyeN
LoExeN = RawExeN ; LoWyeN = RawWyeN ; LoBoxN = RawCellN

IpolExeN = size (IpolGrid,1) ; IpolWyeN = size (IpolGrid,2) ; IpolBoxN = maxval(IpolGrid)

allocate (LoGrid    (LoExeN,LoWyeN),     &
	  ShrinkGrid(IpolExeN,IpolWyeN), stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: CrudeInterpol: Allocation failure: LoGrid #####"

do XLoExe = 1, LoExeN						! get Lo grid
  do XLoWye = 1, LoWyeN
    LoGrid(XLoExe,XLoWye) = ((XLoExe-1)*LoWyeN) + XLoWye
  end do
end do

call MakeHiResGrid (ExeMulti,WyeMulti,LoGrid,HiGrid,LoToHi)	! get Hi and LoToHi grids
HiExeN = size (HiGrid,1) ; HiWyeN = size (HiGrid,2) ; HiCellN = HiExeN * HiWyeN

do XIpolExe = 1, IpolExeN					! get Shrink grid
  do XIpolWye = 1, IpolWyeN
    ShrinkGrid(XIpolExe,XIpolWye) = ((XIpolExe-1)*IpolWyeN) + XIpolWye
  end do 
end do
ShrinkBoxN = IpolExeN * IpolWyeN
    								! get HiToShrink grid
call DefineShrinking (RawBounds,IpolBounds,HiGrid,ShrinkGrid,HiToShrink,ValidShrink)
    
allocate (IpolData (YearN,MonthN,IpolBoxN), &
	  HiVec    (HiCellN),               &
	  ShrinkVec(ShrinkBoxN),            stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: CrudeInterpol: Allocation failure: ID,HV,SV #####"
IpolData = MissVal ; HiVec = MissVal ; ShrinkVec = MissVal
    
!***************************************
! iterate a month at a time

if (ValidShrink.EQ.0) then
 print*, "  > Iterating by time step..."

 do XYear = 1, YearN
  do XMonth = 1, MonthN
    allocate (LoVec(RawCellN),  &
    	      IterVec(RawCellN),stat=AllocStat)
    if (AllocStat.NE.0) print*, "  > ##### ERROR: CrudeInterpol: Allocation failure: LV #####"
    LoVec = MissVal ; IterVec = MissVal
    
    do XRawExe = 1, RawExeN					! get all Lo vector info available from Raw
      do XRawWye = 1, RawWyeN
        if (RawGrid(XRawExe,XRawWye).NE.MissVal) &
        		LoVec(LoGrid(XRawExe,XRawWye)) = RawData(XYear,XMonth,RawGrid(XRawExe,XRawWye))
      end do
    end do
    
    do								! get rest of Lo vector
      if (maxval(LoVec).GT.MissVal) then			! if there are non-missing values present...
        if (minval(LoVec).EQ.MissVal) then			! and there are also missing values present
          call NearNeigh (1,LoGrid,LoVec,IterVec)      		! method is to iterate through
          LoVec = IterVec					! on each iteration we grow the edges of valid
        end if
      else
        print "(a,2i4)", "   > *** WARNING: month is missing: year,month = ", YearAD(XYear), XMonth
      end if
      
      if (maxval(LoVec).EQ.MissVal) exit			! unless the entire vector is missing	
      if (minval(LoVec).GT.MissVal) exit			! or until the entire vector is valid	
    end do
    
    deallocate (IterVec,stat=AllocStat)
    if (AllocStat.NE.0) print*, "  > ##### ERROR: CrudeInterpol: Deallocation failure: IV #####"

    HiVec = MissVal
    call CrudeSmoo (Extent,HiGrid,LoToHi,LoVec,HiVec)		! get Hi vector by local smoothing
    
    nullify (LoVec)
    
    ShrinkVec = MissVal
    do XIpolExe = 1, IpolExeN					! shrink Hi vector into Shrink vector
      do XIpolWye = 1, IpolWyeN					
        if (ShrinkGrid(XIpolExe,XIpolWye).NE.MissVal) then
          if (HiToShrink(XIpolExe,XIpolWye).NE.MissVal) then
              ShrinkVec(ShrinkGrid(XIpolExe,XIpolWye)) = HiVec(HiToShrink(XIpolExe,XIpolWye))
          end if
        end if
      end do
    end do
    
    do XIpolExe = 1, IpolExeN					! redomain into IpolData
      do XIpolWye = 1, IpolWyeN
        if (IpolGrid(XIpolExe,XIpolWye).NE.MissVal) then
          if (ShrinkGrid(XIpolExe,XIpolWye).NE.MissVal) then
            IpolData(XYear,XMonth,IpolGrid(XIpolExe,XIpolWye)) = ShrinkVec(ShrinkGrid(XIpolExe,XIpolWye))
          end if
        end if
      end do
    end do
  end do
 end do
 
 deallocate (RawData,stat=AllocStat)
 if (AllocStat.NE.0) print*, "  > ##### ERROR: CrudeInterpol: Deallocation failure: RawData #####"    

 print*, "  > Saving..."

 call SaveGrim (IpolData,IpolGrid,YearAD,IpolBounds,IpolInfo,SaveFile,RawSuffix,IpolSuffix)
 
 deallocate (LoGrid,HiGrid,LoToHi,ShrinkGrid,HiToShrink,IpolData,YearAD,stat=AllocStat)
 if (AllocStat.NE.0) print*, "  > ##### ERROR: CrudeInterpol: Deallocation failure: final #####"
end if

deallocate (HiVec,    &
	    ShrinkVec,stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: CrudeInterpol: Deallocation failure: SV #####"    

end subroutine CrudeInterpol

!*******************************************************************************
! (ideally) takes 2D array with missing values and returns flat surface without missing values
! appears to produce the desired output in some circumstances, but not all
! the potential problem may lie in v. large data sets, sparse data sets, or high contrast data sets
! Data is supplied in the call, Surf is returned from the call

subroutine FlatSurf (Data,Surf)

use Regress

real, pointer, dimension (:,:) 	:: Data,Surf
real, pointer, dimension (:) 	:: DataVec,DataRef,DataFit,VecCept,VecGrad

real, parameter :: MissVal = -999.0

real :: VecTot,VecEn
real :: ExeCept,ExeGrad,WyeCept,WyeGrad
real :: Correl

integer :: AllocStat
integer :: Iter
integer :: ExeN, WyeN
integer :: XExe, XWye

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

ExeN = size(Data,1) ; WyeN = size(Data,2)
ExeCept = MissVal ; ExeGrad = MissVal ; WyeCept = MissVal ; WyeGrad = MissVal 

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

allocate (DataVec(WyeN), &			! calc equation for each set of Wye
	  DataRef(WyeN), &
	  VecCept(ExeN), &
	  VecGrad(ExeN), stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: FlatSurf: Allocation failure: Exe #####"

do XWye = 1, WyeN
  DataRef(XWye) = XWye
end do

do XExe = 1, ExeN  
  do XWye = 1, WyeN
    DataVec(XWye) = Data(XExe,XWye)    
  end do
  
  call LinearLSRVec (DataRef,DataVec,VecCept(XExe),VecGrad(XExe),Correl)
  
!  call RobustLSR (WyeN,DataRef,DataVec,DataFit,VecCept(XExe),VecGrad(XExe),Iter)
  
!  deallocate(DataFit,stat=AllocStat)
!  if (AllocStat.NE.0) print "(a,i8)", "   > ##### ERROR: FlatSurf: Deallocation failure: ", XExe
end do

VecTot = 0.0 ; VecEn = 0.0			! calc average gradient from all sets of Wye
do XExe = 1, ExeN
  if (VecGrad(XExe).NE.MissVal) then
    VecTot = VecTot + VecGrad(XExe)
    VecEn  = VecEn  + 1.0
  end if
end do
if (VecEn.GE.1) ExeGrad = VecTot / VecEn

VecTot = 0.0 ; VecEn = 0.0			! calc average intercept from all sets of Wye
do XExe = 1, ExeN
  if (VecCept(XExe).NE.MissVal) then
    VecTot = VecTot + VecCept(XExe)
    VecEn  = VecEn  + 1.0
  end if
end do
if (VecEn.GE.1) ExeCept = VecTot / VecEn

deallocate (DataVec,DataRef,VecCept,VecGrad, stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: FlatSurf: Deallocation failure: Exe #####"

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

allocate (DataVec(ExeN), &			! calc equation for each set of Exe
	  DataRef(ExeN), &
	  VecCept(WyeN), &
	  VecGrad(WyeN), stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: FlatSurf: Allocation failure: Wye #####"

do XExe = 1, ExeN
  DataRef(XExe) = XExe
end do

do XWye = 1, WyeN  
  do XExe = 1, ExeN
    DataVec(XExe) = Data(XExe,XWye)    
  end do
  
  call LinearLSRVec (DataRef,DataVec,VecCept(XExe),VecGrad(XExe),Correl)
  
!  call RobustLSR (ExeN,DataRef,DataVec,DataFit,VecCept(XWye),VecGrad(XWye),Iter)
  
!  deallocate(DataFit,stat=AllocStat)
!  if (AllocStat.NE.0) print "(a,i8)", "   > ##### ERROR: FlatSurf: Deallocation failure: ", XWye
end do

VecTot = 0.0 ; VecEn = 0.0			! calc average gradient from all sets of Exe
do XWye = 1, WyeN
  if (VecGrad(XWye).NE.MissVal) then
    VecTot = VecTot + VecGrad(XWye)
    VecEn  = VecEn  + 1.0
  end if
end do
if (VecEn.GE.1) WyeGrad = VecTot / VecEn

VecTot = 0.0 ; VecEn = 0.0			! calc average intercept from all sets of Exe
do XWye = 1, WyeN
  if (VecCept(XWye).NE.MissVal) then
    VecTot = VecTot + VecCept(XWye)
    VecEn  = VecEn  + 1.0
  end if
end do
if (VecEn.GE.1) WyeCept = VecTot / VecEn

deallocate (DataVec,DataRef,VecCept,VecGrad, stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: FlatSurf: Deallocation failure: Wye #####"

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

allocate (Surf(ExeN,WyeN), stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: FlatSurf: Allocation failure: Surf #####"

if (ExeGrad.NE.MissVal.AND.ExeCept.NE.MissVal.AND.WyeGrad.NE.MissVal.AND.WyeCept.NE.MissVal) then
  do XExe = 1, ExeN
    do XWye = 1, WyeN
      Surf (XExe,XWye) = (ExeCept+(ExeGrad*real(XExe))+WyeCept+(WyeGrad*real(XWye))) / 2.0
    end do
  end do
else
  Surf = MissVal
end if

end subroutine FlatSurf

!*******************************************************************************
! calculates the average of surrounding grid boxes
! all are already defined and filled
! nothing in HiVec is made MissVal, all is either filled or else returned as it is

subroutine CrudeSmoo (Extent,HiGrid,LoToHi,LoVec,HiVec)

real, pointer, dimension (:)		:: LoVec,HiVec

integer, pointer, dimension (:,:) 	:: HiGrid,LoToHi

integer, intent (in) :: Extent			! distance (boxes) away from box-to-fill to incorporate

real, parameter :: MissVal = -999.0

real :: OpTot, OpEn

integer :: AllocStat
integer :: ThisHiExe,ThisHiWye,SquareHiExe,SquareHiWye
integer :: HiExeN,HiWyeN,HiCellN

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

HiExeN = size (HiGrid,1) ; HiWyeN = size (HiGrid,2) ; HiCellN = HiExeN * HiWyeN

do ThisHiExe = 1, HiExeN
  do ThisHiWye = 1, HiWyeN
    OpTot = 0.0 ; OpEn  = 0.0
    
    do SquareHiExe = max(1,(ThisHiExe-Extent)), min(HiExeN,(ThisHiExe+Extent))
      do SquareHiWye = max(1,(ThisHiWye-Extent)), min(HiWyeN,(ThisHiWye+Extent))
        if (LoToHi(SquareHiExe,SquareHiWye).NE.MissVal) then
          if (LoVec(LoToHi(SquareHiExe,SquareHiWye)).NE.MissVal) then
            OpTot = OpTot + LoVec(LoToHi(SquareHiExe,SquareHiWye))
            OpEn  = OpEn  + 1.0
          end if
        end if
      end do
    end do
    
    if (OpEn.GT.0) HiVec(HiGrid(ThisHiExe,ThisHiWye)) = OpTot / OpEn
  end do
end do

end subroutine CrudeSmoo

!*******************************************************************************
! calculates the average of surrounding grid boxes
! all are already defined and filled
! if a cell in InVec is...
! ...filled, OutVec = InVec
! ...unfilled, we try to fill it based on surrounding cells, otherwise set to missing

subroutine NearNeigh (Extent,Grid,InVec,OutVec)

real, pointer, dimension (:)		:: InVec,OutVec

integer, pointer, dimension (:,:) 	:: Grid

integer, intent (in) :: Extent			! distance (boxes) away from box-to-fill to incorporate

real, parameter :: MissVal = -999.0

real :: OpTot, OpEn

integer :: AllocStat
integer :: ThisExe,ThisWye,SquareExe,SquareWye
integer :: ExeN,WyeN
integer :: ExeSqMin,ExeSqMax,WyeSqMin,WyeSqMax

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

ExeN = size (Grid,1) ; WyeN = size (Grid,2)

do ThisExe = 1, ExeN
  do ThisWye = 1, WyeN
   if (Grid(ThisExe,ThisWye).NE.MissVal) then
    if (InVec(Grid(ThisExe,ThisWye)).EQ.MissVal) then
     OpTot = 0.0 ; OpEn  = 0.0
     ExeSqMin = max(1,(ThisExe-Extent)) ; ExeSqMax = min(ExeN,(ThisExe+Extent))
     WyeSqMin = max(1,(ThisWye-Extent)) ; WyeSqMax = min(WyeN,(ThisWye+Extent))
     
     do SquareExe = ExeSqMin, ExeSqMax
      do SquareWye = WyeSqMin, WyeSqMax
        if (Grid(SquareExe,SquareWye).NE.MissVal) then
          if (InVec(Grid(SquareExe,SquareWye)).NE.MissVal) then
            OpTot = OpTot + InVec(Grid(SquareExe,SquareWye))
            OpEn  = OpEn  + 1.0
          end if
        end if
      end do
     end do
     
     if (OpEn.GE.1) then
     	OutVec(Grid(ThisExe,ThisWye)) = OpTot / OpEn
     else
        OutVec(Grid(ThisExe,ThisWye)) = MissVal
     end if 
    else
        OutVec(Grid(ThisExe,ThisWye)) = InVec(Grid(ThisExe,ThisWye))
    end if
   end if
   
  end do
end do

end subroutine NearNeigh

!*******************************************************************************
! extends a grim data set (1901-1995) to cover 20th century (1901-2000) by duplication

subroutine Dupli90s (DataIn,DataOut,YearADIn,YearADOut)

real, pointer, dimension (:,:,:) 	:: DataIn,DataOut

integer, pointer, dimension (:) 	:: YearADIn,YearADOut

integer :: AllocStat
integer :: YearInN, YearOutN, MonthN, BoxN
integer :: XYearIn,XYearOut,XMonth,XBox

YearInN = size (DataIn,1) ; MonthN = size (DataIn,2) ; BoxN = size (DataIn,3)

if (YearInN.EQ.95.AND.size(YearADIn).EQ.95.AND.YearADIn(1).EQ.1901) then
  YearOutN = 100
  
  allocate (DataOut  (YearOutN,MonthN,BoxN), &
  	    YearADOut(YearOutN),             stat=AllocStat)
  if (AllocStat.NE.0) print*, "  > ##### ERROR: Dupli90s: Allocation failure #####"

  do XYearOut = 1, YearOutN
    YearADOut(XYearOut) = 1900 + XYearOut

    if (XYearOut.LE.95) XYearIn = XYearOut
    if (XYearOut.GT.95) XYearIn = XYearOut - 5
    
    do XMonth = 1, MonthN
      do XBox = 1, BoxN
         DataOut(XYearOut,XMonth,XBox) = DataIn(XYearIn,XMonth,XBox)
      end do
    end do
  end do
  
  print*, "  > Duplicated 1991-1995 into 1996-2000." 
else
  print*, "  > Dupli90s: wrong period length. No duplication."
end if

end subroutine Dupli90s

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

end module Interpol
