! *************************************** ! * Module pvqc * ! * R. J. Purser, NOAA/NCEP/EMC 2017 * ! * jim.purser@noaa.gov * ! * * ! *************************************** ! ! Utility routines for tasks related to the specification of appropriate ! weight-factors to modulate the "standard" observation weight in a ! variational assimilation. The observations are taken to be subject to the ! forms of variational ("nonlinear") quality control based on the super-logistic ! family of distributions described in Office Note 468, or the simplified ! Huber-like approximate idealizations of these distributions (whose inner ! segments of O-A are taken to be exactly Gaussian and whose tails are ! taken to be the pure asymptote functions assumed by the corresponding ! super-logistic distributions). ! ! DIRECT DEPENDENCIES: ! Modules: kinds, pietc, pvqc_tables !============================================================================== module pvqc !============================================================================== use kinds, only: dp,i_kind use pietc, only: T,F,u0,u1,u2,o2,pi,pih implicit none private public:: atote,wipevqctables,writevqcascfile,readvqcascfile,& writevqcdatfile,readvqcdatfile, & vqch,vqcs interface atote; module procedure atote,atoted,atotedd; end interface interface wipevqctables; module procedure wipevqctables; end interface interface writevqcascfile; module procedure writevqcascfile; end interface interface readvqcascfile; module procedure readvqcascfile; end interface interface writevqcdatfile; module procedure writevqcdatfile; end interface interface readvqcdatfile; module procedure readvqcdatfile; end interface interface vqch module procedure vqch_iii,vqch_ii,vqch_i, vqch_r; end interface interface vqcs module procedure vqcs_iii,vqcs_ii,vqcs_i, vqcs_r; end interface contains !============================================================================= subroutine atote(alpha,beta,kappa,x,y)! [atote] !============================================================================= ! For shape parameters alpha, beta, kappa, and standardardized residual ! x, reurn the value, y, of the standard asymptote at x of the superlogistic. !============================================================================= implicit none real(dp),intent(in ):: alpha,beta,kappa,x real(dp),intent(out):: y !----------------------------------------------------------------------------- real(dp),parameter:: pio4=pi/4_dp real(dp) :: b,c,bap,bac !============================================================================= c=kappa+1_dp b=tan(pio4*(u2-beta))/c bap=b*(u1+alpha) bac=b*(u1-alpha) if(x<0_dp)then; y=-bap*(-x)**c else; y=-bac*( x)**c endif end subroutine atote !============================================================================= subroutine atoted(alpha,beta,kappa,x,y,yd)! [atote] !============================================================================= ! Like atote, but also return the 1st derivative, yd=dy/dx, at x. !============================================================================= implicit none real(dp),intent(in ):: alpha,beta,kappa,x real(dp),intent(out):: y,yd !----------------------------------------------------------------------------- real(dp),parameter:: pio4=pi/4_dp real(dp) :: b,c,bap,bac !============================================================================= c=kappa+1_dp b=tan(pio4*(u2-beta))/c bap=b*(u1+alpha) bac=b*(u1-alpha) if(x<0_dp)then; y=-bap*(-x)**c; yd=c*y/x else; y=-bac*( x)**c; yd=c*y/x endif end subroutine atoted !============================================================================= subroutine atotedd(alpha,beta,kappa,x,y,yd,ydd)! [atote] !============================================================================= ! Like atoted, but also return the 2nd derivative, ydd, at x. !============================================================================= implicit none real(dp),intent(in ):: alpha,beta,kappa,x real(dp),intent(out):: y,yd,ydd !----------------------------------------------------------------------------- real(dp),parameter:: pio4=pi/4_dp real(dp) :: b,c,bap,bac !============================================================================= c=kappa+1_dp b=tan(pio4*(u2-beta))/c bap=b*(u1+alpha) bac=b*(u1-alpha) if(x<0_dp)then; y=-bap*(-x)**c; yd=c*y/x; ydd=(c-1_dp)*yd/x else; y=-bac*( x)**c; yd=c*y/x; ydd=(c-1_dp)*yd/x endif end subroutine atotedd !============================================================================= subroutine wipevqctables! [wipevqctables] !============================================================================= use pvqc_tables, only: sgt,swt,x1t,x2t,xat,yat,linitvqc implicit none if(allocated(sgt))deallocate(sgt) if(allocated(swt))deallocate(swt) if(allocated(x1t))deallocate(x1t) if(allocated(x2t))deallocate(x2t) if(allocated(xat))deallocate(xat) if(allocated(yat))deallocate(yat) linitvqc=.false. end subroutine wipevqctables !============================================================================== subroutine writevqcascfile(vqcascfile,&! [writevqcascfile] npx_a,npa_a,npb_a,npk_a,nx_a,na_a) !============================================================================== ! If VQC parameters and tables have been created, or read in, and now exist in ! the module, pvqc_tables.mod, with its "initialized" flag, linitvqc indicated ! (=.true.), this routine will write an ascii copy of the entire table to the ! file specified by the 12-character filename given by the character argument, ! vqcascfile, provided that the specification parameters in pvqc_tables ! match those listed in the rest of the argument list. !============================================================================== use pvqc_tables, only: sgt,swt,x1t,x2t,xat,yat, & npx,npa,npb,npk,nx,na, linitvqc implicit none character(len=12),intent(in ):: vqcascfile integer(i_kind), intent(in ):: npx_a,npa_a,npb_a,npk_a,nx_a,na_a !------------------------------------------------------------------------------ integer(i_kind),parameter:: lunit=11,nunit=99 integer(i_kind) :: iunit logical :: ex,op !============================================================================== if(.not.linitvqc)& stop 'In writevqcascfile; VQC parameters and tables are not yet initialized' if(npx_a/=npx)stop 'In writevqcascfile; mismatched specified npx' if(npa_a/=npa)stop 'In writevqcascfile; mismatched specified npa' if(npb_a/=npb)stop 'In writevqcascfile; mismatched specified npb' if(npk_a/=npk)stop 'In writevqcascfile; mismatched specified npk' if(nx_a /=nx )stop 'In writevqcascfile; mismatched specified nx' if(na_a /=na )stop 'In writevqcascfile; mismatched specified na' do iunit=lunit,nunit inquire(unit=iunit, exist=ex, opened=op) if(.not.ex)exit if(.not.op)exit enddo if(.not.ex .or. iunit>nunit)& stop 'In writevqcascfile; No available unit number for writing' open(unit=iunit,file=vqcascfile,access='sequential',form='formatted') write(iunit,600)npx,npa,npb,npk,nx,na write(iunit,601)sgt write(iunit,601)swt write(iunit,601)x1t write(iunit,601)x2t write(iunit,601)xat write(iunit,601)yat close(iunit) 600 format(6i10) 601 format(4(e19.12,1x)) end subroutine writevqcascfile !============================================================================== subroutine readvqcascfile(vqcascfile,&! [readvqcascfile] npx_a,npa_a,npb_a,npk_a,nx_a,na_a) !============================================================================== ! If VQC parameters already exist in the module, pvqc_tables.mod, wipe them ! clean with a call to witevqctables; then read in the integer records from ! the ascii data set given by its 12-character name, vqcascfile, and check ! whether these parameters for the tables match those specified in the ! argument list and if not, stop. Assuming the parameters match, ! allocate sufficient space in module pvqc_tables and read in the real-valued ! tables from the rest of the dataset and close it. Compute the resolutions ! dx,da,db,dk, from the given integer parameters and set the "initialized" ! flag, linitvqc, to .true. !============================================================================== use pvqc_tables, only: sgt,swt,x1t,x2t,xat,yat, dx,da,db,dk, & npx,npa,npb,npk,nx,na,npb2, linitvqc implicit none character(len=12),intent(in ):: vqcascfile integer(i_kind), intent(in ):: npx_a,npa_a,npb_a,npk_a,nx_a,na_a !------------------------------------------------------------------------------ integer(i_kind),parameter:: lunit=11,nunit=99 integer(i_kind) :: iunit,nkm logical :: ex,op !============================================================================== if(linitvqc)call wipevqctables do iunit=lunit,nunit inquire(unit=iunit, exist=ex, opened=op) if(.not.ex)exit if(.not.op)exit enddo if(.not.ex .or. iunit>nunit)& stop 'In readvqcascfile; No available unit number for reading' open(unit=iunit,file=vqcascfile,access='sequential',form='formatted') read(iunit,600)npx,npa,npb,npk,nx,na if(npx_a/=npx)stop 'In readvqcascfile; mismatched specified npx' if(npa_a/=npa)stop 'In readvqcascfile; mismatched specified npa' if(npb_a/=npb)stop 'In readvqcascfile; mismatched specified npb' if(npk_a/=npk)stop 'In readvqcascfile; mismatched specified npk' if(nx_a /=nx )stop 'In readvqcascfile; mismatched specified nx' if(na_a /=na )stop 'In readvqcascfile; mismatched specified na' nkm=npk-1 npb2=npb*2 allocate(sgt(-nx:nx,0:na,-nkm:nkm),swt(-nx:nx,0:na,-nkm:nkm),& x1t(0:na,-nkm:nkm),x2t(0:na,-nkm:nkm),& xat(0:na,-nkm:nkm),yat(0:na,-nkm:nkm)) read(iunit,601)sgt read(iunit,601)swt read(iunit,601)x1t read(iunit,601)x2t read(iunit,601)xat read(iunit,601)yat close(iunit) dx=u1/npx da=u1/npa db=u1/npb dk=u1/npk linitvqc=T 600 format(6i10) 601 format(4(e19.12,1x)) end subroutine readvqcascfile !============================================================================== subroutine writevqcdatfile(vqcdatfile,&! [writevqcdatfile] npx_a,npa_a,npb_a,npk_a,nx_a,na_a) !============================================================================== ! If VQC parameters and tables have been created, or read in, and now exist in ! the module, pvqc_tables.mod, with its "initialized" flag, linitvqc indicated ! (=.true.), this routine will write a binary copy of the entire table to the ! file specified by the 12-character filename given by the character argument, ! vqcdatfile, provided that the specification parameters in pvqc_tables ! match those listed in the rest of the argument list. !============================================================================== use pvqc_tables, only: sgt,swt,x1t,x2t,xat,yat, & npx,npa,npb,npk,nx,na, linitvqc implicit none character(len=12),intent(in ):: vqcdatfile integer(i_kind), intent(in ):: npx_a,npa_a,npb_a,npk_a,nx_a,na_a !------------------------------------------------------------------------------ integer(i_kind),parameter:: lunit=11,nunit=99 integer(i_kind) :: iunit logical :: ex,op !============================================================================== if(.not.linitvqc)& stop 'In writevqcdatfile; VQC parameters and tables are not yet initialized' if(npx_a/=npx)stop 'In writevqcdatfile; mismatched specified npx' if(npa_a/=npa)stop 'In writevqcdatfile; mismatched specified npa' if(npb_a/=npb)stop 'In writevqcdatfile; mismatched specified npb' if(npk_a/=npk)stop 'In writevqcdatfile; mismatched specified npk' if(nx_a /=nx )stop 'In writevqcdatfile; mismatched specified nx' if(na_a /=na )stop 'In writevqcdatfile; mismatched specified na' do iunit=lunit,nunit inquire(unit=iunit, exist=ex, opened=op) if(.not.ex)exit if(.not.op)exit enddo if(.not.ex .or. iunit>nunit)& stop 'In writevqcdatfile; No available unit number for writing' open(unit=iunit,file=vqcdatfile,access='sequential',form='unformatted') write(unit=iunit)npx,npa,npb,npk,nx,na write(iunit)sgt write(iunit)swt write(iunit)x1t write(iunit)x2t write(iunit)xat write(iunit)yat close(iunit) end subroutine writevqcdatfile !============================================================================== subroutine readvqcdatfile(vqcdatfile,&! [readvqcdatfile] npx_a,npa_a,npb_a,npk_a,nx_a,na_a) !============================================================================== ! If VQC parameters already exist in the module, pvqc_tables.mod, wipe them ! clean with a call to witevqctables; then read in the integer records from ! the binary data set given by its 12-character name, vqcdatfile, and check ! whether these parameters for the tables match those specified in the ! argument list and if not, stop. Assuming the parameters match, ! allocate sufficient space in module pvqc_tables and read in the real-valued ! tables from the rest of the dataset and close it. Compute the resolutions ! dx,da,db,dk, from the given integer parameters and set the "initialized" ! flag, linitvqc, to .true. !============================================================================== use pvqc_tables, only: sgt,swt,x1t,x2t,xat,yat, dx,da,db,dk, & npx,npa,npb,npk,nx,na,npb2, linitvqc character(len=12),intent(in ):: vqcdatfile integer(i_kind), intent(in ):: npx_a,npa_a,npb_a,npk_a,nx_a,na_a !------------------------------------------------------------------------------ integer(i_kind),parameter:: lunit=11,nunit=99 integer(i_kind) :: iunit,nkm logical :: ex,op !============================================================================== if(linitvqc)call wipevqctables do iunit=lunit,nunit inquire(unit=iunit, exist=ex, opened=op) if(.not.ex)exit if(.not.op)exit enddo if(.not.ex .or. iunit>nunit)& stop 'In readvqcdatfile; No available unit number for reading' open(unit=iunit,file=vqcdatfile,access='sequential',form='unformatted') read(iunit)npx,npa,npb,npk,nx,na if(npx_a/=npx)stop 'In readvqcdatfile; mismatched specified npx' if(npa_a/=npa)stop 'In readvqcdatfile; mismatched specified npa' if(npb_a/=npb)stop 'In readvqcdatfile; mismatched specified npb' if(npk_a/=npk)stop 'In readvqcdatfile; mismatched specified npk' if(nx_a /=nx )stop 'In readvqcdatfile; mismatched specified nx' if(na_a /=na )stop 'In readvqcdatfile; mismatched specified na' nkm=npk-1 npb2=npb*2 allocate(sgt(-nx:nx,0:na,-nkm:nkm),swt(-nx:nx,0:na,-nkm:nkm),& x1t(0:na,-nkm:nkm),x2t(0:na,-nkm:nkm),& xat(0:na,-nkm:nkm),yat(0:na,-nkm:nkm)) read(iunit)sgt read(iunit)swt read(iunit)x1t read(iunit)x2t read(iunit)xat read(iunit)yat close(iunit) dx=u1/npx da=u1/npa db=u1/npb dk=u1/npk linitvqc=T end subroutine readvqcdatfile !============================================================================== subroutine vqch_iii(ia,ib,ik,x,g,w)! [vqch] !============================================================================== ! Huber-like analog of the superlogistic routine vqcs. The results, g and w, ! in the central section are ezactly as if the density is a standardized ! Gaussian; outside points where this Gaussian becomes tangent to the ! idealized superlogistic's asymptotes, the results returned are those that ! take these asymptotes to be the effective log-probability (with suitable ! small nudges, xa in x, and ya in the g direction, to ensure the tengential ! intersections occur when the log-gaussian (parabola) is properly centered ! and peaks at g=0). !============================================================================== use pvqc_tables, only: x1t,x2t,xat,yat,da,db,dk,& npk,na,npb2,linitvqc implicit none integer(i_kind), intent(in ):: ia,ib,ik real(dp), intent(in ):: x real(dp), intent(out):: g,w !------------------------------------------------------------------------------ real(dp),parameter:: pio4=pi/4_dp real(dp) :: bc,p,q,qx,sx,alpha,beta,kappa, & x1,x2,xa,ya,xx integer(i_kind) :: ja !============================================================================== if(.not.linitvqc)stop 'In vqch; VQC tables are not initialized' if(ia<0)then; sx=-x; ja=-ia else; sx= x; ja= ia endif if(ja>na )stop 'In vqch; ia out of bounds' if(ib<=0.or.ib>=npb2 )stop 'In vqch; ib out of bounds' if(ik<=-npk.or.ik>=npk)stop 'In vqch; ik out of bounds' x1=x1t(ja,ik) x2=x2t(ja,ik) xa=xat(ja,ik) ya=yat(ja,ik) alpha=ja*da beta =ib*db kappa=ik*dk bc=tan(pio4*(u2-beta)) p=bc**(u2/(u1-kappa)) q=u1/sqrt(p) qx=q*sx xx=qx+xa if(qxx2)then call atote(alpha,u1,kappa,xx,g,w) g=g-ya w=-w/xx else g=-qx**2/2 w=1 endif g=p*g end subroutine vqch_iii !============================================================================== subroutine vqch_ii(ib,ik,x,g,w)! [vqch] !============================================================================== ! Wrapper for the symmetric restriction (no alpha index) of the Huber-like ! analogs of the superlogistic family. !============================================================================== implicit none integer(i_kind), intent(in ):: ib,ik real(dp), intent(in ):: x real(dp), intent(out):: g,w !------------------------------------------------------------------------------ call vqch(0,ib,ik,x,g,w) end subroutine vqch_ii !============================================================================== subroutine vqch_i(ib,x,g,w)! [vqch] !============================================================================== ! Wrapper for the symmetric (no alpha index) and neutrally-convex ! (no kappa index) restriction of the Huber-like analogs of the ! superlogistic family. !============================================================================== integer(i_kind), intent(in ):: ib real(dp), intent(in ):: x real(dp), intent(out):: g,w !------------------------------------------------------------------------------ call vqch(0,ib,0,x,g,w) end subroutine vqch_i !============================================================================== subroutine vqch_r(beta,x,g,w)! [vqch] !============================================================================== ! Huber-norm analog of the simplest versions of the superlogistic ! (symmetric, neutral convexity), with tail broadness specified by a real ! parameter, beta. !============================================================================== use pvqc_tables, only: x1t,x2t,yat,linitvqc implicit none real(dp),intent(in ):: beta,x real(dp),intent(out):: g,w !------------------------------------------------------------------------------ real(dp),parameter:: pio4=pi/4_dp real(dp) :: bc,p,q,qx,x1,x2,ya,xx !============================================================================== if(.not.linitvqc)stop 'In vqch; VQC tables are not initialized' if(beta<=u0.or.beta>=u2)stop 'In vqch; beta out of bounds' x1=x1t(0,0) x2=x2t(0,0) ya=yat(0,0) bc=tan(pio4*(u2-beta)) p=bc**2 q=u1/bc qx=q*x xx=qx if(qxx2)then call atote(u0,u1,u0,xx,g,w) g=g-ya w=-w/xx else g=-qx**2/2_dp w=1_dp endif g=p*g end subroutine vqch_r !============================================================================== subroutine vqcs_iii(ia,ib,ik,x,g,w)! [vqcs] !============================================================================== ! By specifying the tail-shape parameters, Alpha (Asymmetry), Beta (Broadness) ! and Konvexity (Kappa) by their integer indices, ia, ib, ik, subject to the ! understood convention that there are npa index steps per unit alpha, npb ! per unit beta, npk per unit kappa, this routine will return the ! log-probability, g (which is the negative of the cost function contribution) ! and the weight-factor, w, appropriate to the standardized O-A given by x. ! Provided that the the (x,beta) combination maintains the rescaling ! of this x, namely qx, within the span of the prepared tables, the g and w ! are evaluated by Hermite interpolation (in qx) from the superlogistic ! function table, but when the rescaled residual is too large to fit within ! the span of the table, the returned values are those of the almost ! equivalent Huber-like analog contrived to possess exactly the same ! asymptotic form in the limit |x| --> infinity, and slightly adjusted to ! ensure continuity of g at the transition. !============================================================================== use pvqc_tables, only: sgt,swt,dx,db,dk,npb,npk,nx,na,npb2,linitvqc implicit none integer(i_kind), intent(in ):: ia,ib,ik real(dp), intent(in ):: x real(dp), intent(out):: g,w !------------------------------------------------------------------------------ real(dp),parameter:: pio4=pi/4 real(dp) :: bc,p,q,qx,sx,w1,w2,xodx,beta,kappa,xe,ge,we,& ww,dfa,fl,dfl,f1,f2,df1,df2,g1,g2 integer(i_kind) :: ix1,ix2,ja !============================================================================== if(.not.linitvqc)stop 'In vqcs; VQC tables are not initialized' if(ia<0)then; sx=-x; ja=-ia else; sx= x; ja= ia endif if(ja>na )stop 'In vqcs; ia out of bounds' if(ib<=0.or.ib>=npb2 )stop 'In vqcs; ib out of bounds' if(ik<=-npk.or.ik>=npk)stop 'In vqcs; ik out of bounds' beta =ib*db kappa=ik*dk bc=tan(pio4*(u2-beta)) p=bc**(u2/(u1-kappa)) q=u1/sqrt(p) qx=q*sx xodx=qx/dx ix1=floor(xodx); ix2=ix1+1 if (ix1<-nx)then xe=-nx*dx call vqch(ja,npb,ik,qx,g, w ) call vqch(ja,npb,ik,xe,ge,we) g=p*(sgt(-nx,ja,ik)+g-ge) elseif(ix2>nx)then xe= nx*dx call vqch(ja,npb,ik,qx,g, w ) call vqch(ja,npb,ik,xe,ge,we) g=p*(sgt( nx,ja,ik)+g-ge) else w1=ix2-xodx w2=xodx-ix1 f1=-sgt(ix1,ja,ik) f2=-sgt(ix2,ja,ik) df1=swt(ix1,ja,ik) df2=swt(ix2,ja,ik) fl=w1*f1+w2*f2 ww=w1*w2 if(ww==u0)then g=-p*fl w= w1*df1+w2*df2 else ! Hermite interpolation for g and its consistent derivative used for w: df1=df1*ix1*dx df2=df2*ix2*dx dfl=w1*df1+w2*df2 dfa=(f2-f1)/dx g1=df1-dfa g2=df2-dfa g=-p*(fl+ww*dx*(w1*g1-w2*g2)) w=(dfl-3*ww*(g1+g2))/qx endif endif end subroutine vqcs_iii !============================================================================== subroutine vqcs_ii(ib,ik,x,g,w)! [vqcs] !============================================================================== ! On the assumption that asymmetry (alpha) is rarely needed, this wrapper ! dispenses with the associated index parameter ia for the convenience of the ! user, but retains broadness and convexity indices, ib and ik. !============================================================================== implicit none integer(i_kind), intent(in ):: ib,ik real(dp), intent(in ):: x real(dp), intent(out):: g,w !------------------------------------------------------------------------------ call vqcs(0,ib,ik,x,g,w) end subroutine vqcs_ii !============================================================================== subroutine vqcs_i(ib,x,g,w)! [vqcs] !============================================================================== ! For the restricted case of the symmetric powers of the classical logistic ! this wrapper routine takes only the beta parameter index, ib, to define ! the broadness. !============================================================================== implicit none integer(i_kind), intent(in ):: ib real(dp), intent(in ):: x real(dp), intent(out):: g,w !------------------------------------------------------------------------------ call vqcs(0,ib,0,x,g,w) end subroutine vqcs_i !============================================================================== subroutine vqcs_r(beta,x,g,w)! [vqcs] !============================================================================== ! A special version of vqcs with vanishing asymmetry and neutral convexity, ! but accepting a real parameter, beta, for broadness in order to make it ! more compatible with the older version of VQC that uses these simpler forms ! of the superlogistic family. !============================================================================== use pvqc_tables, only: sgt,swt,dx,npb,nx,linitvqc implicit none real(dp),intent(in ):: beta,x real(dp),intent(out):: g,w !------------------------------------------------------------------------------ real(dp),parameter:: pio4=pi/4_dp real(dp) :: bc,p,q,qx,w1,w2,xodx,xe,ge,we,& ww,dfa,fl,dfl,f1,f2,df1,df2,g1,g2 integer(i_kind) :: ix1,ix2 !============================================================================== if(.not.linitvqc)stop 'In vqcs; VQC tables are not initialized' bc=tan(pio4*(u2-beta)) p=bc**2 q=u1/bc qx=q*x xodx=qx/dx ix1=floor(xodx); ix2=ix1+1 if(ix1<-nx)then xe=-nx*dx call vqch(npb,qx,g, w ) call vqch(npb,xe,ge,we) g=p*(sgt(-nx,0,0)+g-ge) elseif(ix2>nx)then xe= nx*dx call vqch(npb,qx,g, w ) call vqch(npb,xe,ge,we) g=p*(sgt( nx,0,0)+g-ge) else w1=ix2-xodx w2=xodx-ix1 f1=-sgt(ix1,0,0) f2=-sgt(ix2,0,0) df1=swt(ix1,0,0) df2=swt(ix2,0,0) fl=w1*f1+w2*f2 ww=w1*w2 if(ww==u0)then g=-p*fl w= w1*df1+w2*df2 else ! Hermite interpolation for g and its consistent derivative used for w: df1=df1*ix1*dx df2=df2*ix2*dx dfl=w1*df1+w2*df2 dfa=(f2-f1)/dx g1=df1-dfa g2=df2-dfa g=-p*(fl+ww*dx*(w1*g1-w2*g2)) w=(dfl-3*ww*(g1+g2))/qx endif endif end subroutine vqcs_r end module pvqc