! ghcndiscon.f90 ! written by Tim Mitchell in April 2003 ! module to correct discontinuities using GHCN method ! contains: MakeContinuous,MakeVersus,FilterOrig,etc module GHCNdiscon use Time use FileNames use PlainPerm use Regress use FDist_K use FDist_BR use FDist use TTest implicit none contains !******************************************************************************* ! Orig is the original data-set, including both stns to be checked (Check) and all ! relevant stns already in the database ! if Adj is absent, Orig is returned untouched, but with discon in Disc ! else if present, Adj has best estimate (missing where unchecked) ! and Orig has the best estimate in periods (+-5yrs) unchecked ! Differ: .TRUE. means (y+1) minus y, .FALSE. (y+1) divide by y ! if set, AdjStn and AdjTS indicate the amount of adjustment made, in standard ! deviations (from the original climate data), in the form of a RMSE ! AdjStn gives the RMSE for each stn, AdjTS gives the RMSE for each year/mon subroutine MakeContinuous (Orig,RefTS,Check,Disc,YearAD,Differ,Verbose, & AdjStn,AdjTS,Adj) logical, pointer, dimension (:,:) :: Disc ! discon? (NY,NS) logical, pointer, dimension (:) :: Check ! check? (NS) logical, pointer, dimension (:) :: Suspects ! suspected discon (NY) logical, pointer, dimension (:) :: Breaks ! confirmed discon (NY) integer, pointer, dimension (:,:,:) :: Orig ! full climate data-set integer, pointer, dimension (:,:,:) :: RefTS ! reference time-series integer, pointer, dimension (:,:,:), optional :: Adj ! adjusted climate data-set integer, pointer, dimension (:) :: YearAD ! only for verbosity integer, dimension (12) :: Buffer real, pointer, dimension (:,:) :: Best ! current best est (NY,NM) real, pointer, dimension (:,:) :: Versus ! Best minus(div-by) RefTS real, pointer, dimension (:,:) :: OneAdj ! adjust for stn (NY,NM) real, pointer, dimension (:,:) :: AllSum ! adj SE sum (NY,NM) real, pointer, dimension (:,:) :: AllEnn ! adj SE enn(NY,NM) real, pointer, dimension (:,:),optional :: AdjTS ! ts of adjustments real, pointer, dimension (:),optional :: AdjStn ! mean adjustment by stn logical, intent (in) :: Differ ! see above logical, intent (in) :: Verbose ! .TRUE. for verbosity real, parameter :: MonFract = 0.2 ! min months for flag (0-1) real, parameter :: MissVal = -999.0 integer, parameter :: DataMissVal = -9999 logical :: QEnd,QRatio real :: OneRMSE integer :: AllocStat integer :: NVal,NYear,NMonth,NStn,NBreak,NSuspect,NDisc integer :: XVal,XYear,XMonth,XStn,XBreak QRatio=.TRUE. ; if (Differ) QRatio=.FALSE. NYear=size(Orig,1) ; NMonth=12 ; NStn=size(Orig,3) if (size(RefTS,1).NE.NYear.OR.size(RefTS,2).NE.12.OR.size(RefTS,3).NE.NStn) & print*, " > ##### MakeContinuous: RefTS* #####" if (size(Disc,1).NE.NYear.OR.size(Disc,2).NE.NStn.OR.size(Check,1).NE.NStn) & print*, " > ##### MakeContinuous: Disc/Check #####" allocate (Best(NYear,NMonth), Versus(NYear,NMonth), & OneAdj(NYear,NMonth), AllSum(NYear,NMonth), AllEnn(NYear,NMonth), & Suspects(NYear), Breaks(NYear), stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### MakeContinuous: Alloc #####" Disc=.FALSE. ; AllSum=0 ; AllEnn=0 ; NDisc=0 do XStn = 1, NStn if (Check(XStn)) then if (Verbose) then write (99,*), "" write (99,"(a8,i4,a)"), "Station ", XStn, " --------" end if Best = MissVal do XYear = 1, NYear ! fill Best do XMonth = 1, NMonth if ( Orig(XYear,XMonth,XStn).NE.DataMissVal.AND. & RefTS(XYear,XMonth,XStn).NE.DataMissVal) & Best(XYear,XMonth) = real(Orig(XYear,XMonth,XStn)) end do end do do ! write (99,"(a)"), "call MakeVersus..." ! ################### call MakeVersus (Versus,Best,RefTS,QRatio,NYear,XStn) ! Versus based on Best ! write (99,"(a)"), "call IdentifySuspects..." ! ################### call IdentifySuspects (Versus,Suspects,YearAD,Verbose,MonFract,QRatio) ! get suspects NSuspect = count(Suspects) if (NSuspect.GT.0) then ! write (99,"(a)"), "call IdentifyBreaks..." ! ################### call IdentifyBreaks (Versus,Suspects,Breaks,YearAD,Verbose,& MonFract,QRatio) ! find definite breaks NBreak = count(Breaks) if (NBreak.GT.0) then ! write (99,"(a)"), "call UpdateDisc..." ! ################### call UpdateDisc (Disc,Breaks,XStn,QEnd,YearAD,Verbose,QRatio) ! update disc list if (.not.QEnd) then ! write (99,"(a)"), "call CorrectStep..." ! ################### call CorrectStep(Disc,Orig,RefTS,XStn,Best,QRatio,YearAD) ! revise best estimate if (Verbose) write (99,*), "" end if end if end if if (QEnd.OR.NSuspect.EQ.0.OR.NBreak.EQ.0) exit end do if (present(AdjStn).OR.present(AdjTS)) then ! measure adj to ts ! write (99,"(a)"), "call MeasureAdjOne..." ! ################### call MeasureAdjOne (Orig,Best,OneAdj,OneRMSE,XStn) if (present(AdjStn)) AdjStn(XStn)=OneRMSE if (present(AdjTS)) then do XYear = 1, NYear do XMonth = 1, NMonth if (OneAdj(XYear,XMonth).NE.MissVal) then AllSum(XYear,XMonth)=AllSum(XYear,XMonth)+OneAdj(XYear,XMonth) AllEnn(XYear,XMonth)=AllEnn(XYear,XMonth)+1 end if end do end do end if end if if (present(Adj)) then do XYear = 1, NYear ! fill Adj where Orig has been checked do XMonth = 1, NMonth if (RefTS(XYear,XMonth,XStn).NE.DataMissVal.AND. & Best(XYear,XMonth).NE.MissVal) & Adj(XYear,XMonth,XStn) = nint(Best(XYear,XMonth)) end do end do else ! do nowt - Disc is sufficient end if end if end do if (present(AdjTS)) then ! create final ts of adj made do XYear=1,NYear do XMonth=1,NMonth if (AllEnn(XYear,XMonth).GT.0) then AdjTS(XYear,XMonth)=(AllSum(XYear,XMonth)/AllEnn(XYear,XMonth)) if (AdjTS(XYear,XMonth).GT.0.001) then AdjTS(XYear,XMonth)=sqrt(AdjTS(XYear,XMonth)) else AdjTS(XYear,XMonth)=0 end if end if end do end do end if do XYear=1,NYear do XStn=1,NStn if (Disc(XYear,XStn)) NDisc=NDisc+1 end do end do deallocate (Best,Versus,OneAdj,AllSum,AllEnn,Suspects,Breaks, stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### MakeContinuous: Dealloc #####" end subroutine MakeContinuous !******************************************************************************* ! measure the size of the adjustment made for all stns ! this is used by homogiter.f90 (directly) ! note that Raw is the array from the Raw file loaded, and =/NStn ! the adj stats are the RMSE, where the error is the adjustment made, in units ! given by the stdev for that calendar month from the RefTS subroutine MeasureAdjAll (Raw,Adj,RefTS,AdjMon,AdjStn,Examine) logical, pointer, dimension (:) :: Examine integer, pointer, dimension (:,:,:) :: Raw,Adj,RefTS integer, pointer, dimension (:,:) :: AdjMonEn real, pointer, dimension (:,:) :: AdjMon real, pointer, dimension (:) :: AdjStn real, dimension (12) :: StDev ! RefTS stdev integer, parameter :: DataMissVal = -9999 real, parameter :: MissVal = -999.0 real :: OpEn,OpTot,OpTotSq, Diff integer :: NYear,NMonth,NStn,XYear,XMonth,XStn, AdjStnEn,AllocStat NYear=size(Raw,1) ; NMonth=size(Raw,2) ; NStn=size(Raw,3) allocate (AdjMonEn(NYear,NMonth), stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### PruneRaw: alloc #####" AdjMonEn=0 ; AdjMon=0 ; AdjStn=0 do XStn=1,NStn if (Examine(XStn)) then ! only include the Raw stns AdjStnEn=0 ; StDev=MissVal do XMonth=1,NMonth ! calc ref ts stdev by month OpEn=0 ; OpTot=0 ; OpTotSq=0 ! (remember: refTS has (x,s) of Raw) do XYear=1,NYear if (RefTS(XYear,XMonth,XStn).NE.DataMissVal) then OpEn=OpEn+1 ; OpTot=OpTot+RefTS(XYear,XMonth,XStn) OpTotSq=OpTotSq+(RefTS(XYear,XMonth,XStn)**2) end if end do if (OpEn.GT.1) then StDev(XMonth)=((OpTotSq/OpEn)-((OpTot/OpEn)**2))*(OpEn/(OpEn-1)) if (StDev(XMonth).LE.0) then StDev(XMonth)=MissVal else StDev(XMonth)=sqrt(StDev(XMonth)) end if end if end do do XMonth=1,NMonth ! calc the adj made at each moment if (StDev(XMonth).NE.MissVal) then do XYear=1,NYear if (Adj(XYear,XMonth,XStn).NE.DataMissVal) then Diff = ((Adj(XYear,XMonth,XStn) - Raw(XYear,XMonth,XStn)) / & StDev(XMonth)) ** 2 AdjStn(XStn) = AdjStn(XStn) + Diff ! calc for stn AdjStnEn = AdjStnEn + 1 ! calc for time AdjMon(XYear,XMonth) = AdjMon(XYear,XMonth) + Diff AdjMonEn(XYear,XMonth) = AdjMonEn(XYear,XMonth) + 1 end if end do end if end do if (AdjStnEn.GT.0) then ! final stat for stn AdjStn(XStn) = sqrt (AdjStn(XStn) / real(AdjStnEn)) else AdjStn(XStn) = MissVal end if end if end do do XYear=1,NYear ! final stat for time do XMonth=1,NMonth if (AdjMonEn(XYear,XMonth).GT.0) then AdjMon(XYear,XMonth) = sqrt(AdjMon(XYear,XMonth) / & real(AdjMonEn(XYear,XMonth))) else AdjMon(XYear,XMonth) = MissVal end if end do end do end subroutine MeasureAdjAll !******************************************************************************* ! measure the size of the adjustment made for this stn, as a time-series ! this is used by homogenise.f90 (indirectly via MakeContinuous) subroutine MeasureAdjOne (Orig,Best,OneAdj,OneRMSE,XStn) integer, pointer, dimension (:,:,:) :: Orig ! full climate data-set real, pointer, dimension (:,:) :: OneAdj ! adjust for stn (NY,NM) real, pointer, dimension (:,:) :: Best ! current best est (NY,NM) real, dimension (12) :: StDev ! original stdev real, intent(out) :: OneRMSE integer, intent(in) :: XStn integer, parameter :: DataMissVal = -9999 real, parameter :: MissVal = -999.0 real :: OpEn,OpTot,OpTotSq, Sum,En integer :: NYear,NMonth,XYear,XMonth NYear=size(Best,1) ; NMonth=size(Best,2) StDev=MissVal ; OneAdj=MissVal ; OneRMSE=MissVal ; Sum=0 ; En=0 do XMonth=1,NMonth ! calc original stdev by month OpEn=0 ; OpTot=0 ; OpTotSq=0 do XYear=1,NYear if (Orig(XYear,XMonth,XStn).NE.DataMissVal) then OpEn=OpEn+1 ; OpTot=OpTot+Orig(XYear,XMonth,XStn) OpTotSq=OpTotSq+(Orig(XYear,XMonth,XStn)**2) end if end do if (OpEn.GT.1) then StDev(XMonth)=((OpTotSq/OpEn)-((OpTot/OpEn)**2))*(OpEn/(OpEn-1)) if (StDev(XMonth).LT.0) then StDev(XMonth)=MissVal else StDev(XMonth)=sqrt(StDev(XMonth)) end if end if end do do XMonth=1,NMonth ! calc OneAdj made (sq abs,in stdev) if (StDev(XMonth).NE.MissVal) then ! as given, calcs for homog+retry do XYear=1,NYear ! if filtered by Orig=/DMV & RefTS=/DMV if (Best(XYear,XMonth).NE.MissVal) then ! this would calc for homog only OneAdj(XYear,XMonth) = ((Best(XYear,XMonth)-real(Orig(XYear,XMonth,XStn))) / & StDev(XMonth)) ** 2 Sum=Sum+OneAdj(XYear,XMonth) ; En=En+1 end if end do end if end do if (En.GT.0) then OneRMSE=(Sum/En) if (OneRMSE.GT.0.001) then OneRMSE=sqrt(OneRMSE) else OneRMSE=0 end if end if end subroutine MeasureAdjOne !******************************************************************************* subroutine CorrectStep (Disc,Orig,RefTS,XStn,Best,QRatio,YearAD) logical, pointer, dimension (:,:) :: Disc ! discon? (NY,NS) integer, pointer, dimension (:,:,:) :: Orig ! full climate data-set integer, pointer, dimension (:,:,:) :: RefTS ! full ref TS integer, pointer, dimension (:) :: YearAD ! only for verbosity real, pointer, dimension (:,:) :: OrigVersus ! original Versus real, pointer, dimension (:,:) :: Best ! current best est (NY,NM) real, dimension (12) :: Correct ! correction to apply logical, intent (in) :: QRatio ! .TRUE. for precip etc, .FALSE. otherwise integer, intent(in) :: XStn integer, parameter :: DataMissVal = -9999 real, parameter :: MissVal = -999.0 logical :: AcceptComp real :: OpTot,OpEn,Before,After,Log0,Log1 integer :: NYear,NMonth,NPane,XYear,XMonth,XExam,XPane, Year0,Year1,Year2,Year3 integer :: LastDisc,ThisDisc,NextDisc,Allocstat,OldLen, YearA,YearB,YearC,YearD, Elastic NYear=size(Disc,1) ; NMonth=12 ; LastDisc=NYear+1 LastDisc=NYear+1 ; ThisDisc=MissVal ; NextDisc=MissVal NPane=12 ; if (QRatio) NPane=24 Elastic=5 ; if (QRatio) Elastic=10 ! min len of any calib stretch Best = MissVal do XYear=1,NYear do XMonth = 1, NMonth if (Orig(XYear,XMonth,XStn).NE.DataMissVal) & Best(XYear,XMonth) = Orig(XYear,XMonth,XStn) end do end do allocate (OrigVersus(NYear,NMonth), stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### CorrectStep: Alloc #####" do XYear = NYear,1,-1 if (Disc(XYear,XStn)) then ! year=disc if (ThisDisc.EQ.MissVal) then ! first disc ThisDisc=XYear else ! later disc NextDisc=XYear end if else if (XYear.EQ.1.AND.ThisDisc.NE.MissVal) then NextDisc=0 ! got suspect, but reached end of period end if if (NextDisc.NE.MissVal) then ! got everything for check, so check Year0=NextDisc+1 ; Year1=ThisDisc ; Year2=ThisDisc+1 ; Year3=LastDisc-1 call MakeVersus (OrigVersus,Best,RefTS,QRatio,NYear,XStn) ! original Versus Correct=MissVal ! Correct(XMonth) contains the adjustment to make do XMonth = 1, NMonth ! calc corrections to make Before=MissVal ; After=MissVal YearA=Year0 ; YearB=Year1 ; YearC=Year2 ; YearD=Year3 ; AcceptComp=.FALSE. OpTot=0 ; OpEn=0 do XExam=YearA,YearB if (OrigVersus(XExam,XMonth).NE.MissVal) then OpTot=OpTot+OrigVersus(XExam,XMonth) ; OpEn=OpEn+1 end if end do if (OpEn.GT.0) Before=OpTot/OpEn OpTot=0 ; OpEn=0 do XExam=YearC,YearD if (OrigVersus(XExam,XMonth).NE.MissVal) then OpTot=OpTot+OrigVersus(XExam,XMonth) ; OpEn=OpEn+1 end if end do if (OpEn.GT.0) After=OpTot/OpEn if (Before.NE.MissVal.AND.After.NE.MissVal) then if (QRatio) then if (Before.EQ.0) then Correct(XMonth)=0.0 else Correct(XMonth)=After/Before end if else Correct(XMonth)=After-Before end if end if end do call SmoothCorrect (Correct,QRatio) ! smooth the corr into annual cycle ! if (.NOT.QRatio) call SmoothCorrect (Correct,QRatio) do XMonth=1,NMonth ! make the corrections if (Correct(XMonth).NE.MissVal) then do XExam = 1,ThisDisc if (Best(XExam,XMonth).NE.MissVal) then if (QRatio) then Best(XExam,XMonth) = real(Best(XExam,XMonth)) * Correct(XMonth) else Best(XExam,XMonth) = real(Best(XExam,XMonth)) + Correct(XMonth) end if end if end do end if end do LastDisc=ThisDisc ; ThisDisc=NextDisc ; NextDisc=MissVal end if end do deallocate (OrigVersus, stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### CorrectStep: Dealloc #####" end subroutine CorrectStep !******************************************************************************* ! this subroutine smooths the corrections by calendar month ! this is advisable, because the correction to make is initially determined ! by a rather small sample for each individual month ! by smoothing we can eliminate some of the potentially erratic corrections ! we can also add estimated corrections for months without an initial calc subroutine SmoothCorrect (Correct,QRatio) real, pointer, dimension (:) :: TSInVec,TSLowVec,TSHighVec ! vectors real, dimension (12) :: Correct ! corr to apply logical, intent (in) :: QRatio ! .TRUE. for precip etc, .FALSE. otherwise real, parameter :: MissVal = -999.0 real :: OpTotSq,OpTot,OpEn, OrigMean,SmooMean,OrigStDev,SmooStDev integer :: NTriple,NMonth,NThree, AllocStat integer :: XTriple,XMonth,XThree NMonth=12 ; NTriple=36 ; NThree=3 allocate (TSInVec(NTriple),TSLowVec(NTriple),TSHighVec(NTriple),stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### SmoothCorrect: alloc #####" do XMonth=1,NMonth ! fill TSInVec with initial corrections do XThree=0,2 TSInVec((XThree*12)+XMonth) = Correct(XMonth) end do end do do XMonth=1,NMonth ! in-fill any missing values if (Correct(XMonth).EQ.MissVal) then OpTot=0 ; OpEn=0 do XTriple=(10+XMonth),(14+XMonth) if (TSInVec(XTriple).NE.MissVal) then OpTot=OpTot+TSInVec(XTriple) ; OpEn=OpEn+1 end if end do if (OpEn.GT.1) then do XThree=0,2 TSInVec((XThree*12)+XMonth) = OpTot/OpEn end do end if end if end do OpTotSq=0 ; OpTot=0 ; OpEn=0 ; OrigMean=MissVal ! find original mean do XMonth=1,NMonth if (TSInVec(12+XMonth).NE.MissVal) then OpEn=OpEn+1 OpTot=OpTot+TSInVec(12+XMonth) OpTotSq=OpTotSq+(TSInVec(12+XMonth)**2) end if end do if (OpEn.EQ.12) then OrigMean=OpTot/OpEn OrigStDev=(OpTotSq/OpEn)-(OrigMean**2) if (OrigStDev.GE.0) then OrigStDev=sqrt(OrigStDev) else OrigStDev=1 end if end if call GaussSmooth (NTriple,9,1,TSInVec,TSLowVec,TSHighVec) if (QRatio) then ! not possible to ensure orig and smoo corr tally else OpTotSq=0 ; OpTot=0 ; OpEn=0 ; SmooMean=MissVal ! find smoothed mean do XMonth=1,NMonth Correct(XMonth) = TSLowVec(12+XMonth) if (TSLowVec(12+XMonth).NE.MissVal) then OpEn=OpEn+1 OpTot=OpTot+TSLowVec(12+XMonth) OpTotSq=OpTotSq+(TSLowVec(12+XMonth)**2) end if end do if (OpEn.EQ.12) then SmooMean=OpTot/OpEn SmooStDev=(OpTotSq/OpEn)-(SmooMean**2) if (SmooStDev.GE.0) then SmooStDev=sqrt(SmooStDev) else SmooStDev=1 end if end if ! ensure orig and smoo corr tally if (SmooMean.NE.MissVal.AND.OrigMean.NE.MissVal) then do XMonth=1,NMonth Correct(XMonth) = Correct(XMonth) + (OrigMean-SmooMean) Correct(XMonth) = OrigMean + & ((Correct(XMonth)-OrigMean) * (OrigStDev/SmooStDev)) end do end if end if deallocate (TSInVec,TSLowVec,TSHighVec,stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### SmoothCorrect: alloc #####" end subroutine SmoothCorrect !******************************************************************************* ! for a given XStn, identify all additional breaks (i.e. those outside the range of an ! already-recognised discontinuity +- 4 years) ! additionals are entered as discontinuities ! QEnd is returned .TRUE. when no additionals are found subroutine UpdateDisc (Disc,Breaks,XStn,QEnd,YearAD,Verbose,QRatio) logical, pointer, dimension (:,:) :: Disc ! discon? (NY,NS) logical, pointer, dimension (:) :: Breaks ! confirmed discon (NY) integer,pointer,dimension(:) :: YearAD ! only include for verbosity logical, intent(in) :: Verbose logical, intent(in) :: QRatio ! .TRUE. for precip etc integer, intent(in) :: XStn logical, intent(out) :: QEnd logical :: QClash integer :: NYear,XDiscYear,XBreakYear,Year0,Year1,Elastic Elastic=5 ; if (QRatio) Elastic=10 ! min len of any calib stretch NYear=size(Breaks) ; QEnd=.TRUE. do XBreakYear = 1, NYear if (Breaks(XBreakYear)) then Year0=XBreakYear-Elastic+1 ; Year1=XBreakYear+Elastic+1 if (Year0.LT.1) Year0=1 if (Year1.GT.NYear) Year1=NYear XDiscYear=Year0-1 ; QClash=.FALSE. do XDiscYear=XDiscYear+1 if (Disc(XDiscYear,XStn)) QClash=.TRUE. if (QClash.OR.XDiscYear.EQ.Year1) exit end do if (.NOT.QClash) then QEnd = .FALSE. Disc(XBreakYear,XStn) = .TRUE. if (Verbose) write (99,"(a8,i4)"), "DISCON ", YearAD(XBreakYear) else Breaks(XBreakYear) = .FALSE. if (Verbose) write (99,"(a8,i4)"), "Refuse ", YearAD(XBreakYear) end if end if end do end subroutine UpdateDisc !******************************************************************************* subroutine IdentifyBreaks (Versus,Suspects,Breaks,YearAD,Verbose,MonFract,QRatio) logical, pointer, dimension (:) :: Breaks ! confirmed discon (NY) logical, pointer, dimension (:) :: Suspects ! suspected discon (NY) real, pointer, dimension (:,:) :: Perm ! NPerm,NYear real, pointer, dimension (:,:) :: Versus ! Best minus(div-by) RefTS real, pointer, dimension (:) :: Single ! monthly version of Versus integer,pointer,dimension(:) :: YearAD ! only include for verbosity logical, intent(in) :: Verbose logical, intent (in) :: QRatio ! .TRUE. for precip etc real, intent(in) :: MonFract ! min months for flag (0-1) real, parameter :: MissVal = -999.0 logical :: QPassMonth real :: TestStat,PermStat integer :: LastSuspect,ThisSuspect,NextSuspect,Year0,Year1,AllocStat,Part integer :: XYear,NYear,XMonth,NMonth,XLimit,NPerm,XPerm integer :: NSample,NSmaller,NThresh,NTest,NFail,NPass NYear=size(Versus,1) ; NMonth=12 ; NPerm=200 Breaks = .FALSE. LastSuspect=0 ; ThisSuspect=0 ; NextSuspect=0 Part=12 ; if (QRatio) Part=24 ! max len of partition allocate (Single(NYear),Perm(NPerm,NYear),stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### IdentifyBreaks: Alloc #####" do XYear = 1, NYear if (Suspects(XYear)) then ! year=suspect if (ThisSuspect.EQ.0) then ! no 'check' year, so fill ThisSuspect=XYear else ! got 'check' year, so make this the next NextSuspect=XYear end if else if (XYear.EQ.NYear.AND.ThisSuspect.NE.0) then NextSuspect=XYear+1 ! got suspect, but reached end of period end if if (NextSuspect.NE.0) then ! got everything for check, so check Year0=(LastSuspect+1) ; Year1=(NextSuspect-1) ; NTest=0 ; NPass=0 if (Year0.LT.(ThisSuspect-Part+1)) Year0=(ThisSuspect-Part+1) if (Year1.GT.(ThisSuspect+Part)) Year1=(ThisSuspect+Part) do XMonth = 1, NMonth ! check each month individually Single=MissVal do XLimit = Year0,Year1 ! fill single with relevant data Single(XLimit)=Versus(XLimit,XMonth) end do ! calc test statistic call ClusterDistance (Single,Year0,ThisSuspect,(ThisSuspect+1),Year1,QRatio,TestStat) if (TestStat.NE.MissVal) then ! get repartitioned sample in Perm - plainperm.f90 call PartitionSample (Single,Single,Perm,Perm, & Year0,ThisSuspect,(ThisSuspect+1),Year1) NSample=NPerm ; NSmaller=0 ; NThresh=nint(real(NSample)*0.05) QPassMonth=.FALSE. ; XPerm=0 do ! each perm... XPerm=XPerm+1 do XLimit = Year0,Year1 ! fill single with permutation Single(XLimit)=Perm(XPerm,XLimit) end do ! calc mean distance within clusters call ClusterDistance (Single,Year0,ThisSuspect,(ThisSuspect+1),Year1,QRatio,PermStat) if (PermStat.EQ.MissVal) then NSample=NSample-1 NThresh=nint(real(NSample)*0.05) else if (PermStat.LT.TestStat) then NSmaller=NSmaller+1 ! smaller than test stat if (NSmaller.GT.NThresh) QPassMonth=.TRUE. end if if (XPerm.EQ.NPerm.OR.QPassMonth) exit end do if (NSmaller.GT.NThresh) NPass=NPass+1 NTest=NTest+1 end if end do NFail=NTest-NPass if (real(NFail).GT.(real(NTest)*MonFract)) then ! confirm discon if (Verbose) write (99,"(a8,3(i4,a),a8,2(i2,a))"), "Confirm ", YearAD(ThisSuspect), & " (", YearAD(Year0), "-", YearAD(Year1), ") ", & "P-test (", NFail, "/", NTest, ")" Breaks(ThisSuspect) = .TRUE. LastSuspect=ThisSuspect ; ThisSuspect=NextSuspect ; NextSuspect=0 else ! reject discon if (Verbose) write (99,"(a8,3(i4,a),a8,2(i2,a))"), "Discard ", YearAD(ThisSuspect), & " (", YearAD(Year0), "-", YearAD(Year1), ") ", & "P-test (", NFail, "/", NTest, ")" ThisSuspect=NextSuspect ; NextSuspect=0 end if end if end do deallocate (Single,Perm,stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### IdentifyBreaks: Dealloc #####" end subroutine IdentifyBreaks !******************************************************************************* ! given a vector with two partitions (A,B) ! this calculates the weighted mean distance between a pair within a partition subroutine ClusterDistance (Single,BegA,EndA,BegB,EndB,QRatio,Distance) real, pointer, dimension (:) :: Single ! (NValue) integer, intent(in) :: BegA,EndA,BegB,EndB ! XValue indices logical, intent (in) :: QRatio ! .TRUE. for precip etc real, intent(out) :: Distance real, parameter :: MissVal = -999.0 real :: OpTot,OpEn integer :: XMem0,XMem1,Elastic Elastic=5 ; if (QRatio) Elastic=10 ! min len of any calib stretch OpTot=0.0 ; OpEn=0.0 ; Distance=MissVal do XMem0 = BegA,(EndA-1) ! look at each pair in partition 1 if (Single(XMem0).NE.MissVal) then do XMem1 = (XMem0+1),EndA if (Single(XMem1).NE.MissVal) then OpEn=OpEn+1 ; OpTot=OpTot+abs(Single(XMem0)-Single(XMem1)) end if end do end if end do if (OpEn.GE.Elastic) then do XMem0 = BegB, (EndB-1) ! look at each pair in partition 2 if (Single(XMem0).NE.MissVal) then do XMem1 = (XMem0+1),EndB if (Single(XMem1).NE.MissVal) then OpEn=OpEn+1 ; OpTot=OpTot+abs(Single(XMem0)-Single(XMem1)) end if end do end if end do if (OpEn.GE.(Elastic*2)) Distance=OpTot/OpEn end if end subroutine ClusterDistance !******************************************************************************* subroutine IdentifySuspects (Versus,Suspects,YearAD,Verbose,MonFract,QRatio) logical, pointer, dimension (:) :: Suspects ! suspected discon (NY) real, pointer, dimension (:,:) :: Versus ! Best minus(div-by) RefTS real, pointer, dimension (:) :: Single ! one calendar month integer, pointer, dimension (:) :: QueryBeg,QueryEnd ! subsections to examine integer,pointer,dimension(:) :: YearAD ! only include for verbosity real, dimension (12) :: RSS1,RSS2 ! 1=whole,2=subsections integer, dimension (12) :: RSS2Enn ! n in subsections logical, intent (in) :: QRatio ! .TRUE. for precip etc logical, intent (in) :: Verbose real, intent (in) :: MonFract ! min months for flag (0-1) real, parameter :: MissVal = -999.0 real (dp) :: FTestStat,Pea,Que real :: Upper integer :: Suspect,QFail,CurrentBeg,CurrentEnd,LastQuery,Err,AllocStat integer :: XMonth,NMonth,XYear,NYear,NTest,NFail NMonth=12 ; NYear=size(Versus,1) allocate (Single(NYear), & QueryBeg(NYear),QueryEnd(NYear),stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### IdentifySuspects: Alloc #####" QueryBeg=0 ; QueryEnd=0 ; Suspects=.FALSE. LastQuery=1 ; QueryBeg(1)=1 ; QueryEnd(1)=size(Suspects) do CurrentBeg = QueryBeg(LastQuery) ; CurrentEnd = QueryEnd(LastQuery) call MostSuspect (Versus,CurrentBeg,CurrentEnd,Suspect,RSS1,RSS2,RSS2Enn,MonFract,& YearAD,QRatio) QFail=0 if (Suspect.NE.MissVal) then NTest=0 ; NFail=0 do XMonth = 1, NMonth if (RSS2Enn(XMonth).GT.4.AND.RSS2(XMonth).GT.0) then FTestStat = ((RSS1(XMonth)-RSS2(XMonth))*(real(RSS2Enn(XMonth))-4.0)) & / (RSS2(XMonth)*3.0) call FProb(3,(RSS2Enn(XMonth)-4),FTestStat,Pea,Que,Err) ! fdist.f90 if (Err.EQ.0) NTest=NTest+1 if (Pea.GT.0.95) NFail=NFail+1 end if end do if (real(NFail).GE.(real(NTest)*MonFract)) then ! fail under F-test QFail = 1 else ! so try t-test NTest=0 ; NFail=0 do XMonth = 1, NMonth Single=MissVal do XYear = CurrentBeg,CurrentEnd Single(XYear)=Versus(XYear,XMonth) end do call DiffMeans (Single,Single,CurrentBeg,Suspect, & (Suspect+1),CurrentEnd,Upper) if (Upper.NE.MissVal) NTest=NTest+1 if (Upper.GT.0.95) NFail=NFail+1 end do if (real(NFail).GT.(real(NTest)*MonFract)) QFail = 2 ! fail under t-test end if end if if (Verbose) then if (Suspect.EQ.MissVal) then write (99,"(a8,4x,a,2(i4,a))"), "Sparse ", & " (", YearAD(CurrentBeg), "-", YearAD(CurrentEnd), ") " else if (QFail.EQ.0) then write (99,"(a8,3(i4,a))"), "Ignore ", YearAD(Suspect), & " (", YearAD(CurrentBeg), "-", YearAD(CurrentEnd), ") " else if (QFail.EQ.1) then write (99,"(a8,3(i4,a),a8,2(i2,a))"), "Suspect ", YearAD(Suspect), & " (", YearAD(CurrentBeg), "-", YearAD(CurrentEnd), ") ", & "F-test (", NFail, "/", NTest, ")" else if (QFail.EQ.2) then write (99,"(a8,3(i4,a),a8,2(i2,a))"), "Suspect ", YearAD(Suspect), & " (", YearAD(CurrentBeg), "-", YearAD(CurrentEnd), ") ", & "t-test (", NFail, "/", NTest, ")" end if end if if (QFail.GT.0) then Suspects(Suspect)=.TRUE. QueryBeg(LastQuery)=CurrentBeg ; QueryEnd(LastQuery)=Suspect-1 LastQuery=LastQuery+1 QueryBeg(LastQuery)=Suspect+1 ; QueryEnd(LastQuery)=CurrentEnd else LastQuery=LastQuery-1 end if if (LastQuery.EQ.0) exit end do deallocate (Single,QueryBeg,QueryEnd,stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### IdentifySuspects: Dealloc #####" end subroutine IdentifySuspects !******************************************************************************* subroutine MostSuspect (Versus,Beg,End,Suspect,RSS1,RSS2,RSS2Enn,MonFract,YearAD,QRatio) real, pointer, dimension (:,:) :: Versus ! Best Minus(div-by) RefTS real, pointer, dimension (:,:) :: Full ! raw version of RSS2 real, pointer, dimension (:,:) :: Stand ! RSS2 / RSS1 real, pointer, dimension (:) :: Exe,Wye ! NYear vectors real, dimension (12) :: RSS1,RSS2 ! 1=whole,2=subsections integer,pointer,dimension (:,:) :: FullEnn ! full n in subsections integer,pointer,dimension(:) :: YearAD ! only include for verbosity integer, dimension (12) :: RSS2Enn ! n in subsections logical, intent (in) :: QRatio ! .TRUE. for precip etc, .FALSE. otherwise integer, intent(in) :: Beg,End ! period of interest to query integer, intent(out) :: Suspect ! most suspicious year index real, intent(in) :: MonFract ! Min months for flag (0-1) real, parameter :: MissVal = -999.0 real :: Aye,Bee,Are,Enn,OpTotSq,OpTot,OpEn,OpMean,OpStDev,RSS,Min,EnnPart1,EnnPart2 integer :: AllocStat,MinYear,Elastic integer :: XYear,XMonth,XQuery,XTestYear integer :: NYear,NMonth,NQuery,NTestYear RSS1=MissVal ; RSS2=MissVal ; RSS2Enn=MissVal ; Suspect=MissVal NYear=size(Versus,1) ; NMonth=12 ; NQuery=End-Beg+1 Min=0-MissVal ; MinYear=MissVal Elastic=5 ; if (QRatio) Elastic=10 ! min len of any calib stretch if (NQuery.GE.(Elastic*2)) then allocate (Exe(NQuery),Wye(NQuery),FullEnn(NYear,NMonth), & Full(NYear,NMonth),Stand(NYear,NMonth), stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### MostSuspect: Alloc #####" FullEnn=MissVal ; Full=MissVal ; Stand=MissVal do XMonth = 1, NMonth OpEn=0 ; OpTot=0 ; OpTotSq=0 ! initialise RSS2 distribution Exe=MissVal ; Wye=MissVal do XYear = Beg, End ! fill Exe,Wye if (Versus(XYear,XMonth).NE.MissVal) then XQuery = XYear-Beg+1 Exe(XQuery) = real(XQuery) Wye(XQuery) = Versus(XYear,XMonth)/100.0 end if end do ! calc RSS1 call RegressVectors (Exe,Wye,Aye,Bee,Are,Enn,Elastic,RSS=RSS1(XMonth),Beg=1,End=NQuery) if (RSS1(XMonth).NE.MissVal.AND.RSS1(XMonth).NE.0) then do XQuery = Elastic,(NQuery-Elastic) ! test each year in turn XTestYear=Beg+XQuery-1 ! calc RSS2 part 1 call RegressVectors (Exe,Wye,Aye,Bee,Are,EnnPart1,Elastic,RSS=RSS,Beg=1,End=XQuery) if (RSS.NE.MissVal) then ! calc RSS2 part 2 call RegressVectors (Exe,Wye,Aye,Bee,Are,EnnPart2,Elastic,RSS=Full(XTestYear,XMonth), & Beg=(XQuery+1),End=NQuery) if (Full(XTestYear,XMonth).NE.MissVal) then ! calc full RSS2 FullEnn(XTestYear,XMonth) = nint(EnnPart1 + EnnPart2) Full(XTestYear,XMonth) = Full(XTestYear,XMonth) + RSS Stand(XTestYear,XMonth) = Full(XTestYear,XMonth) / RSS1(XMonth) end if end if end do end if end do do XTestYear = (Beg+Elastic-1),(End-Elastic) ! find most suspicious year OpEn=0 ; OpTot=0 do XMonth = 1, NMonth if (Stand(XTestYear,XMonth).NE.MissVal) then OpEn=OpEn+1 ; OpTot=OpTot+Stand(XTestYear,XMonth) end if end do if (real(OpEn).GE.(MonFract*12.0)) then if ((OpTot/OpEn).LT.Min) then Min=OpTot/OpEn ; MinYear=XTestYear end if end if end do if (MinYear.NE.MissVal) then ! return most suspicious year Suspect = MinYear do XMonth = 1, NMonth RSS2(XMonth) = Full(Suspect,XMonth) RSS2Enn(XMonth) = FullEnn(Suspect,XMonth) end do end if deallocate (Exe,Wye,Full,FullEnn,Stand, stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### MostSuspect: Alloc #####" end if end subroutine MostSuspect !******************************************************************************* ! calcs Versus for MakeContinuous on the basis of supplied info ! I'm not convinced by the algorithm for precip, esp for dry regions, but better? subroutine MakeVersus (Versus,Best,RefTS,QRatio,NYear,XStn) integer, pointer, dimension (:,:,:) :: RefTS ! reference time-series real, pointer, dimension (:,:) :: Best ! current best est (NY,NM) real, pointer, dimension (:,:) :: Versus ! Best minus(div-by) RefTS logical, intent (in) :: QRatio ! .TRUE. for precip etc, .FALSE. otherwise integer, intent (in) :: XStn ! stn required from RefTS integer, intent (in) :: NYear ! no. years in V,B,R real, parameter :: MissVal = -999.0 integer, parameter :: DataMissVal = -9999 integer :: XMonth,XYear Versus = MissVal if (QRatio) then do XYear = 1, NYear do XMonth = 1, 12 if (Best(XYear,XMonth).NE.MissVal.AND.RefTS(XYear,XMonth,XStn).NE.DataMissVal) then if (Best(XYear,XMonth).NE.0.AND.RefTS(XYear,XMonth,XStn).NE.0) then Versus(XYear,XMonth) = Best(XYear,XMonth) / real(RefTS(XYear,XMonth,XStn)) else if (Best(XYear,XMonth).NE.0) then Versus(XYear,XMonth) = 10.0 ! value / zero else if (RefTS(XYear,XMonth,XStn).NE.0) then Versus(XYear,XMonth) = 0.1 ! zero / value else Versus(XYear,XMonth) = 1.0 ! zero / zero end if end if end do end do else do XYear = 1, NYear do XMonth = 1, 12 if (Best(XYear,XMonth).NE.MissVal.AND.RefTS(XYear,XMonth,XStn).NE.DataMissVal) & Versus(XYear,XMonth) = Best(XYear,XMonth) - real(RefTS(XYear,XMonth,XStn)) end do end do end if end subroutine MakeVersus !******************************************************************************* ! take the three main arrays (Orig,RefTS,Adj) ! use RefTS to filter which data goes in homog and which in retry subroutine FilterOrig (Orig,RefTS,Adj,Check,Differ) logical, pointer, dimension (:) :: Check,Retain integer, pointer, dimension (:,:,:) :: Orig ! full climate data-set integer, pointer, dimension (:,:,:) :: RefTS ! reference time-series integer, pointer, dimension (:,:,:) :: Adj ! adjusted climate data-set logical, intent (in) :: Differ ! .FALSE. for precip etc integer, parameter :: DataMissVal = -9999 logical :: QRetain integer :: NYear,NMonth,NStn,NRetain integer :: XYear,XMonth,XStn,XRetain, Beg,End,AllocStat,Elastic NYear=size(Adj,1) ; NMonth=size(Adj,2) ; NStn=size(Adj,3) Elastic=10 ; if (Differ) Elastic=5 ! min len of any calib stretch allocate (Retain(NYear), stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### PruneOrig: alloc #####" do XStn=1,NStn Retain=.FALSE. do XYear=1,NYear XMonth=0 ; QRetain=.FALSE. do ! decide whether year needs retaining XMonth=XMonth+1 if ( Orig (XYear,XMonth,XStn).NE.DataMissVal .AND. & RefTS(XYear,XMonth,XStn).EQ.DataMissVal) QRetain=.TRUE. if (XMonth.EQ.NMonth.OR.QRetain) exit end do if (QRetain) then ! year needs retaining Beg=XYear-Elastic ; if (Beg.LT.1) Beg=1 End=XYear+Elastic ; if (End.GT.NYear) End=NYear do XRetain=Beg,End Retain(XRetain)=.TRUE. end do end if end do do XYear=1,NYear do XMonth=1,NMonth if (Orig (XYear,XMonth,XStn).NE.DataMissVal) then if (RefTS(XYear,XMonth,XStn).NE.DataMissVal) then Adj(XYear,XMonth,XStn) = Orig(XYear,XMonth,XStn) end if if (.NOT.Retain(XYear)) then Orig(XYear,XMonth,XStn) = DataMissVal end if end if end do end do end do deallocate (Retain, stat=AllocStat) if (AllocStat.NE.0) print*, " > ##### PruneOrig: dealloc #####" end subroutine FilterOrig !******************************************************************************* end module GHCNdiscon