MODULE mod_fourdvar
!
!git $Id$
!svn $Id: mod_fourdvar.F 1202 2023-10-24 15:36:07Z arango $
!================================================== Hernan G. Arango ===
!  Copyright (c) 2002-2023 The ROMS/TOMS Group                         !
!    Licensed under a MIT/X style license                              !
!    See License_ROMS.md                                               !
!=======================================================================
!                                                                      !
!  Variational data assimilation variables:                            !
!                                                                      !
!  ADmodVal      Adjoint model values at observation locations.        !
!  BackCost      Current background cost function misfit (mean squared !
!                  difference) between model and background state, Jb. !
!  CostFun       Current iteration total cost function (background     !
!                  plus observations), J.                              !
!  CostFunOld    Previous iteration total cost function (background    !
!                  plus observations), J.                              !
!  CostNorm      Total cost function normalization scales              !
!                  (minimization starting value, Jb+Jo).               !
!  Cost0         The total cost function at the beggining of the inner !
!                  loop (inner=0) for each outer loop.                 !
!  DTsizeH       Horizontal diffusion time-step size for spatial       !
!                  convolutions.                                       !
!  DTsizeV       Vertical diffusion time-step size for spatial         !
!                  convolutions.                                       !
!  GradErr       Upper bound on relatice error of the gradient.        !
!  HevecErr      Maximum error bound on Hessian eigenvectors.          !
!  KhMax         Maximum  horizontal diffusion coefficient.            !
!  KhMin         Minimum  horizontal diffusion coefficient.            !
!  KvMax         Maximum  vertical diffusion coefficient.              !
!  KvMin         Minimum  vertical diffusion coefficient.              !
!  Load_Zobs     Logical switch indicating that input Zobs is negative !
!                  and fractional vertical positions are computed in   !
!                  "extract_obs3d" and written to observation NetCDF   !
!                  file for latter use.                                !
!  LhessianEV    Logical switch to compute Hessian eigenvectors.       !
!  LhotStart     Logical switch to activate hot start of subsquent     !
!                  outer loops in the weak-constraint algorithms.      !
!  Lprecond      Logical switch to activate conjugate gradient         !
!                  preconditioning.                                    !
!  Lritz         Logical switch to activw Ritz limited-memory          !
!                  preconditioning.                                    !
!  LaugWeak      Logical switch for computing augmented model error    !
!                terms in the routine "error_covariance".              !
!  nConvRitz     Number of converged Ritz eigenvalues.                 !
!  Nimpact       Outer loop to consider for observations impact and    !
!                  observations sensitivity.                           !
!  NLmodVal      Nonlinear model values at observation locations.      !
!  NHsteps       Full number of horizontal diffusion steps for spatial !
!                  convolutions.                                       !
!  NLobsCost     Current NLM observation cost function misfit (mean    !
!                  squared difference) between NLM and observations.   !
!  NpostI        Number of Lanczos iterations used for the posterior   !
!                  analysis error covariance matrix estimation.        !
!  NritzEV       If preconditioning, number of eigenpairs to use.      !
!  NVsteps       Full number of vertical diffusion steps for spatial   !
!                  convolutions.                                       !
!  ObsAngler     Observation vector rotation angle at RHO-points.      !
!  ObsCost       Current observation cost function misfit (mean        !
!                  squared difference) between model and observations. !
!  ObsCount      Current observation counts per state variable.        !
!  ObsErr        Observation error.                                    !
!  ObsMeta       Observation meta values.                              !
!  ObsName       String describing the observation types.              !
!  ObsProv       Observation provenance flags.                         !
!  ObsReject     Current rejected observation counts per state         !
!                  variable.                                           !
!  ObsScale      Observation screening and quality control scale,      !
!                  0: reject 1: accept.                                !
!  ObsState2Type Mapping indices from state variable to observation    !
!                  type.                                               !
!  ObsType2State Mapping indices from observation type to state        !
!                  variable.                                           !
!  ObsType       Observation type identifier.                          !
!  ObsVal        Observation values.                                   !
!  ObsVetting    Processing flag used to reject (zero) or accept       !
!                  (unity) observations.                               !
!  Optimality    normalized, optimal cost function minimum.            !
!  Ritz          Ritz eigenvalues to compute approximated Hessian.     !
!  RitzMaxErr    Ritz values maximum error limit.                      !
!  TLmodVal      Tangent linear model values at observation locations. !
!  Vdecay        Covariance vertical decorrelation scale (m).          !
!  Tobs          Observations time (days).                             !
!  Xobs          Observations X-locations (grid coordinates).          !
!  Yobs          Observations Y-locations (grid coordinates).          !
!  Zobs          Observations Z-locations (grid coordinates).          !
!  cg_Gnorm      Initial gradient vector normalization factor.         !
!  cg_Greduc     Reduction in the gradient norm; excess cost function. !
!  cg_QG         Lanczos vector normalization factor.                  !
!  cg_Ritz       Eigenvalues of the Lanczos recurrence term Q(k)T(k).  !
!  cg_RitzErr    Eigenvalues relative error.                           !
!  cg_Tmatrix    Lanczos recurrence symmetric, tridiagonal matrix.     !
!  cg_alpha      Conjugate gradient coefficient.                       !
!  cg_beta       Conjugate gradient coefficient.                       !
!  cg_delta      Lanczos algorithm coefficient.                        !
!  cg_gamma      Lanczos algorithm coefficient.                        !
!  cg_tau        Conjugate gradient coefficient.                       !
!  cg_zu         Lanczos tridiagonal matrix, upper diagonal elements.  !
!  cg_zv         Eigenvectors of Lanczos recurrence term Q(k)T(k).     !
!  haveADmod     Logical switch indicating that there is representer   !
!                  coefficients available to process in DAV(ng)%name   !
!                  file.                                               !
!  haveNLmod     Logical switch indicating that there is nonlinear     !
!                  model data available to process in DAV(ng)%name     !
!                  file.                                               !
!  haveTLmod     Logical switch indicating that there is tangent       !
!                  model data available to process in DAV(ng)%name     !
!                  file.                                               !
!  haveObsMeta   Logical switch indicating loading and processing of   !
!                  observations meta field.                            !
!                                                                      !
!  Variables in observation space needed Desroziers et al. (2005)      !
!  statistics at observation locations:                                !
!                                                                      !
!  BgErr         4D-Var Background error standard deviation.           !
!  NLincrement   4D-Var NLM increment, analysis minus background.      !
!  innovation    4D-Var innovation, observations minus background.     !
!  residual      4D-Var residual, observation minus analysis.          !
!                                                                      !
!                                                                      !
!=======================================================================
!
        USE mod_param
!
        implicit none
!
        PUBLIC :: allocate_fourdvar
        PUBLIC :: deallocate_fourdvar
        PUBLIC :: initialize_fourdvar
!
!-----------------------------------------------------------------------
!  Define T_FOURDVAR structure.
!-----------------------------------------------------------------------
!
        TYPE T_FOURDVAR
          integer , allocatable :: NobsSurvey(:)
          integer , allocatable :: ObsCount(:)
          integer , allocatable :: ObsReject(:)
          real(r8), allocatable :: BackCost(:)
          real(r8), allocatable :: Cost0(:)
          real(r8), allocatable :: CostFun(:)
          real(r8), allocatable :: CostFunOld(:)
          real(r8), allocatable :: CostNorm(:)
          real(r8), allocatable :: ObsCost(:)
          real(r8), allocatable :: DataPenalty(:)
          real(r8), allocatable :: NLPenalty(:)
          real(r8), allocatable :: NLobsCost(:)
          real(r8), allocatable :: SurveyTime(:)
          real(r8), allocatable :: cg_pxout(:)
          real(r8), allocatable :: cg_pxsave(:)
          real(r8), allocatable :: cg_pxsum(:)
          real(r8), allocatable :: cg_Dpxsum(:)
          real(r8), allocatable :: tl_cg_pxsave(:)
          real(r8), allocatable :: ad_cg_pxsave(:)
        END TYPE T_FOURDVAR
!
        TYPE (T_FOURDVAR), allocatable :: FOURDVAR(:)
!
!-----------------------------------------------------------------------
!  Define other module variables.
!-----------------------------------------------------------------------
!
        integer,  allocatable :: ObsType(:)
        integer,  allocatable :: ObsState2Type(:)
        integer,  allocatable :: ObsType2State(:)
        integer,  allocatable :: ObsProv(:)
        real(r8), allocatable :: ObsAngler(:)
        real(r8), allocatable :: ObsErr(:)
        real(r8), allocatable :: ObsMeta(:)
        real(r8), allocatable :: ObsScale(:)
        real(r8), allocatable :: ObsVetting(:)
        real(r8), allocatable :: ObsVal(:)
        real(dp), allocatable :: Tobs(:)
        real(r8), allocatable :: Xobs(:)
        real(r8), allocatable :: Yobs(:)
        real(r8), allocatable :: Zobs(:)
        real(r8), allocatable :: ADmodVal(:)
        real(r8), allocatable :: NLmodVal(:)
        real(r8), allocatable :: misfit(:)
        real(r8), allocatable :: unvetted(:)
        real(r8), allocatable :: uradial(:)
        real(r8), allocatable :: vradial(:)
        real(r8), allocatable :: uBgErr(:)
        real(r8), allocatable :: vBgErr(:)
        real(r8), allocatable :: TLmodVal(:)
        real(r8), allocatable :: TLmodVal_S(:,:,:)
        real(r8), allocatable :: BCKmodVal(:)
        real(r8), allocatable :: Hbk(:,:)
        real(r8), allocatable :: zcglwk(:,:,:)
        real(r8), allocatable :: vcglwk(:,:,:)
        real(r8), allocatable :: Jb0(:)
        real(r8), allocatable :: vcglev(:,:,:)
        real(r8), allocatable :: zgrad0(:,:)
        real(r8), allocatable :: vgrad0(:)
        real(r8), allocatable :: vgrad0s(:)
        real(r8), allocatable :: gdgw(:)
        real(r8), allocatable :: vgnorm(:)
        real(r8), allocatable :: cg_innov(:)
        real(r8), allocatable :: cg_dla(:,:)
!
!-----------------------------------------------------------------------
!  Define observations parameters.
!-----------------------------------------------------------------------
!
!  Switches indicating that at input observations vertical position were
!  given in meters (Zobs < 0) so the fractional vertical level position
!  is computed during extraction and written to Observation NetCDF file.
!
        logical, allocatable :: Load_Zobs(:)
        logical, allocatable :: wrote_Zobs(:)
!
!  Maximum number of observations to process.
!
        integer :: Mobs
!
!  Number of model state variables to process.
!
        integer, allocatable :: NstateVar(:)
!
!  Number of observation types and names to process. If the observation
!  operators for a specific application have a 1-to-1 association with
!  the model state variables,  NobsVar = NstateVar.  However, if more
!  that one state variable is needed to evaluate a particular type of
!  observation (HF radials, travel time, pressure, etc.), a new class
!  name is added to the state variable list to assess and differentiate
!  its impact.  Then, NobsVar = NstateVar + NextraObs.
!
        integer, allocatable :: NobsVar(:)
        character(len=40), allocatable :: ObsName(:)
!
! Number of extra-observation classes, extra-observation type indices,
! and extra-observation type names. It is used in observation operators
! that require more than one state variable or not directly associated
! with state variables.
!
        integer :: NextraObs = 0
        integer, allocatable :: ExtraIndex(:)
        character(len=40), allocatable :: ExtraName(:)
!
!  Number of interpolation weights and (I,J,K) indices offsets.
!
        integer, parameter :: Nweights = 8
        integer, parameter, dimension(Nweights) :: Ioffset =            &
     &                      (/ 0, 1, 0, 1, 0, 1, 0, 1 /)
        integer, parameter, dimension(Nweights) :: Joffset =            &
     &                      (/ 0, 0, 1, 1, 0, 0, 1, 1 /)
        integer, parameter, dimension(Nweights) :: Koffset =            &
     &                      (/ 0, 0, 0, 0, 1, 1, 1, 1 /)
!
!  Size of observation NetCDF file unlimited dimension.
!
        integer, allocatable :: Ndatum(:)
!
!  Number of observations surveys available.
!
        integer, allocatable :: Nsurvey(:)
!
!  Observation surveys counter.
!
        integer, allocatable :: ObsSurvey(:)
!
!  Current number of observations processed.
!
        integer, allocatable :: Nobs(:)
!
!  Current starting and ending observation file index processed.
!
        integer, allocatable :: NstrObs(:)
        integer, allocatable :: NendObs(:)
!
!  Background error covariance normalization method:
!
!       [0] Exact, very expensive
!       [1] Approximated, randomization
!
        integer, allocatable :: Nmethod(:)
!
!  Random number generation scheme for randomization:
!
!       [0] Intrinsic F90 routine "randon_number"
!       [1] Gaussian distributed deviates, numerical recipes
!
        integer, allocatable :: Rscheme(:)
!
!  Number of iterations in the randomization ensemble used to
!  compute the background error covariance, B, normalization
!  factors. This factors ensure that the diagonal elements of
!  B are equal to unity (Fisher and Coutier, 1995).
!
        integer :: Nrandom = 1000
!
!  Number of Lanczos iterations used in the posterior analysis
!  error covariance matrix estimation.
!
        integer :: NpostI
!
!  Outer loop to consider in the computatio of the observations
!  impact or observations sensitivity, Nimpact =< Nouter.  This
!  facilitates the computations with multiple outer loop 4D-Var
!  applications.  The observation analysis needs to be computed
!  separately for each outer loop.  The full analysis for all
!  outer loops are combined offline.
!
        integer :: Nimpact
!
!  Parameter to either process the eigenvector of the stabilized
!  representer matrix to whe computing array modes (Nvct=1 less
!  important eigenvector, Nvct=Ninner most important eigenvector)
!  or cut-off eigenvector for the clipped analysis when (only
!  eigenvector Nvct:Ninner to will be processed, others will be
!  disgarded).
!
        integer :: Nvct
!
!  Optimal, normalized cost funtion minimum.  If the statistical
!  hypotheses between the background and observations errors
!  is consistemt, the cost function value at the minimum, Jmin,
!  is idealy equal to half the number of observations assimilated
!  (Optimality=1=2*Jmin/Nobs), for a linear system.
!
        real(r8), allocatable :: Optimality(:)
!
!  Switch to activate the processing of model state at the observation
!  locations.
!
        logical, allocatable :: ProcessObs(:)
!
!  Switch to activate writting of nonlinear model values at
!  observations locations into observations NetCDF file.
!
        logical, allocatable :: wrtNLmod(:)
!
!  Sitch to process and write out observation accept/reject flag used
!  for screening and quality control.
!
        logical, allocatable :: wrtObsScale(:)
!
!  Switch to activate writting of representer model values at
!  observations locations into observations NetCDF file.
!
        logical, allocatable :: wrtRPmod(:)
!
!  Switch to activate writting of tangent linear model values at
!  observations locations into observations NetCDF file.
!
        logical, allocatable :: wrtTLmod(:)
!
!  Switch to activate writting of initial and final model-observation
!  misfit (innovation) vector into 4DVAR output NetCDF file.
!
        logical, allocatable :: wrtMisfit(:)
!
!  Swiches indicating that there is representer coeffiecients,
!  nonlinear, and tangent linear model data available to process
!  in 4DVAR NetCDF output file (DAV(ng)%name).  At the beeginning,
!  there is not data since this file has been just defined.
!
        logical, allocatable :: haveADmod(:)
        logical, allocatable :: haveNLmod(:)
        logical, allocatable :: haveTLmod(:)
!
!  Switch to indicate whether observations are present that
!  require the specific meta field be loaded and processed.
!
        logical, allocatable :: haveObsMeta(:)
!
!-----------------------------------------------------------------------
!  Variables in observation space needed Desroziers et al. (2005)
!  statistics at observation locations:
!-----------------------------------------------------------------------
!
!  4D-Var background error standard deviation [Mobs].
!
        real(r8), allocatable :: BgErr(:)
!
!  4D-Var NLM increment vector, analysis minus background {Mobs].
!
        real(r8), allocatable :: NLincrement(:)
!
!  4D-Var innovation vector, observations minus background [Mobs].
!
        real(r8), allocatable :: innovation(:)
!
!  4D-Var residual vector, observation minus analysis [Mobs].
!
        real(r8), allocatable :: residual(:)
!
!-----------------------------------------------------------------------
!  Spatial convolutions parameters
!-----------------------------------------------------------------------
!
!  Initial conditions, surface forcing and model error covariance: Full
!  number of horizontal and vertical diffusion operator step for spatial
!  convolutions.
!
        integer, allocatable :: NHsteps(:,:)
        integer, allocatable :: NVsteps(:,:)
!
!  Boundary conditions error covariance: Full number of horizontal and
!  vertical diffusion operator step for spatial convolutions.
!
        integer, allocatable :: NHstepsB(:,:)
        integer, allocatable :: NVstepsB(:,:)
!
!  Initial conditions, surface forcing and model error covariance:
!  Horizontal and vertical diffusion operator time-step size for
!  spatial convolutions.
!
        real(r8), allocatable :: DTsizeH(:,:)
        real(r8), allocatable :: DTsizeV(:,:)
!
!  Boundary conditions error covariance: Horizontal and vertical
!  diffusion operator time-step size for spatial convolutions.
!
        real(r8), allocatable :: DTsizeHB(:,:)
        real(r8), allocatable :: DTsizeVB(:,:)
!
!  Minimum and maximum Horizontal and vertical diffusion coefficients
!  used in spatial convolutions.
!
        real(r8), allocatable :: KhMin(:)
        real(r8), allocatable :: KhMax(:)
        real(r8), allocatable :: KvMin(:)
        real(r8), allocatable :: KvMax(:)
!
!-----------------------------------------------------------------------
!  Conjugate gradient parameters.
!-----------------------------------------------------------------------
!
!  Conjugate gradient coefficients.
!
        real(r8), allocatable :: cg_alpha(:,:)
        real(r8), allocatable :: cg_beta(:,:)
        real(r8), allocatable :: cg_tau(:,:)
!
!  Ritz values maximum error limit.
!
        real(r8) :: RitzMaxErr
!
!  Converged Ritz eigenvalues used to approximate Hessian matrix during
!  preconditioning.
!
        real(r8), allocatable :: Ritz(:)
!
!  Orthogonal eigenvectors of Lanczos recurrence term Q(k)T(k).
!
        real(r8), allocatable :: cg_zv(:,:,:)
!
!  Lanczos algorithm coefficients.
!
        real(r8), allocatable :: cg_delta(:,:)
        real(r8), allocatable :: cg_gamma(:,:)
!
!  Initial gradient vector normalization factor.
!
        real(r8), allocatable :: cg_Gnorm(:)
        real(r8), allocatable :: cg_Gnorm_v(:)
        real(r8), allocatable :: cg_Gnorm_y(:)
!
!  Reduction in the gradient norm; excess cost function.
!
        real(r8), allocatable :: cg_Greduc(:,:)
!
!  Lanczos vector normalization factor.
!
        real(r8), allocatable :: cg_QG(:,:)
!
!  Lanczos recurrence symmetric, tridiagonal matrix, T(k).
!
        real(r8), allocatable :: cg_Tmatrix(:,:)
        real(r8), allocatable :: cg_zu(:,:)
!
!  Eigenvalues of the Lanczos recurrence term Q(k)T(k)
!  algorithm and their relative error (accuaracy).
!
        real(r8), allocatable :: cg_Ritz(:,:)
        real(r8), allocatable :: cg_RitzErr(:,:)
!
!-----------------------------------------------------------------------
!  Descent algorithm parameters.
!-----------------------------------------------------------------------
!
!  Switch to compute Hessian approximated eigenvalues and eigenvectors.
!
        logical :: LhessianEV
!
!  Switch switch to activate hot start of subsquent outerloops in the
!  weak-constraint algorithms.
!
        logical :: LhotStart
!
!  Switch to activate conjugate gradient preconditioning.
!
        logical :: Lprecond
!
!  Switch to activate Ritz limited-memory preconditioning using the
!  eigenpairs approximation of the Hessian matrix (Tshimanga et al.,
!  2008).
!
        logical :: Lritz
!
!  Switch that controls computation of the augumented contributions
!  to the model error forcing terms in error_covariance.
!
        logical :: LaugWeak=.FALSE.
!
!
!  Number of converged Ritz eigenvalues. If input parameter NritzEV > 0,
!  then nConvRitz=NritzEV.  Therefore, only nConvRitz eigenpairs will
!  used for preconditioning and the RitzMaxErr threshold is ignored.
!
        integer :: NritzEV
        integer :: nConvRitz = 0
!
!  Weak contraint conjugate gradient norms.
!
        real(r8) :: cg_gammam1 = 0.0_r8
        real(r8) :: cg_sigmam1 = 0.0_r8
        real(r8) :: cg_rnorm = 0.0_r8
!
!  Upper bound on the relative error of the gradient when computing
!  Hessian eigenvectors.
!
        real(r8) :: GradErr
!
!  Maximum error bound on Hessian eigenvectors.  Note that even quite
!  innacurate eigenvectors are usefull for pre-condtioning purposes.
!
        real(r8) :: HevecErr
!
!------------------------------------------------------------------------
!  Dot product parameters.
!------------------------------------------------------------------------
!
!  Dot product between tangent linear and adjoint vectors.
!
        real(r8) :: DotProduct
        real(r8) :: adDotProduct
!
!  Tangent linear model linearization check dot products.
!
        integer :: ig1count            ! counter for g1 dot product
        integer :: ig2count            ! counter for g2 dot product
        real(r8), dimension(1000) :: g1
        real(r8), dimension(1000) :: g2
!
!------------------------------------------------------------------------
!  Weak constraint parameters.
!------------------------------------------------------------------------
!
!  Switch to activate writing of weak constraint forings
!  into the adjoint NetCDF file.
!
        logical, allocatable :: WRTforce(:)
!
!  Weak-constraint forcing time.  A new variable is required since this
!  forcing is delayed by nADJ time-steps.
!
        real(r8), allocatable :: ForceTime(:)
!
      CONTAINS
!
      SUBROUTINE allocate_fourdvar
!
!=======================================================================
!                                                                      !
!  This routine allocates several variables in module "mod_fourdvar"   !
!  for all nested grids.                                               !
!                                                                      !
!=======================================================================
!
!-----------------------------------------------------------------------
!  Allocate module variables due to nested grids.
!-----------------------------------------------------------------------
!
      IF (.not.allocated(Load_Zobs)) THEN
        allocate ( Load_Zobs(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(wrote_Zobs)) THEN
        allocate ( wrote_Zobs(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(NstateVar)) THEN
        allocate ( NstateVar(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(NobsVar)) THEN
        allocate ( NobsVar(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(Ndatum)) THEN
        allocate ( Ndatum(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(Nsurvey)) THEN
        allocate ( Nsurvey(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(ObsSurvey)) THEN
        allocate ( ObsSurvey(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(Nobs)) THEN
        allocate ( Nobs(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(NstrObs)) THEN
        allocate ( NstrObs(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(NendObs)) THEN
        allocate ( NendObs(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(Nmethod)) THEN
        allocate ( Nmethod(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(Rscheme)) THEN
        allocate ( Rscheme(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(Optimality)) THEN
        allocate ( Optimality(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(ProcessObs)) THEN
        allocate ( ProcessObs(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(wrtNLmod)) THEN
        allocate ( wrtNLmod(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(wrtObsScale)) THEN
        allocate ( wrtObsScale(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(wrtRPmod)) THEN
        allocate ( wrtRPmod(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(wrtTLmod)) THEN
        allocate ( wrtTLmod(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(wrtMisfit)) THEN
        allocate ( wrtMisfit(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(haveADmod)) THEN
        allocate ( haveADmod(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(haveNLmod)) THEN
        allocate ( haveNLmod(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(haveTLmod)) THEN
        allocate ( haveTLmod(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(haveObsMeta)) THEN
        allocate ( haveObsMeta(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(KhMin)) THEN
        allocate ( KhMin(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(KhMax)) THEN
        allocate ( KhMax(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(KvMin)) THEN
        allocate ( KvMin(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(KvMax)) THEN
        allocate ( KvMax(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(WRTforce)) THEN
        allocate ( WRTforce(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
      IF (.not.allocated(ForceTime)) THEN
        allocate ( ForceTime(Ngrids) )
        Dmem(1)=Dmem(1)+REAL(Ngrids,r8)
      END IF
!
      RETURN
      END SUBROUTINE allocate_fourdvar
!
      SUBROUTINE deallocate_fourdvar
!
!=======================================================================
!                                                                      !
!  This routine deallocates only the variables in module associated    !
!  with observation dimension parameters Ndatum, Nsurvey, and Mobs.    !
!                                                                      !
!=======================================================================
!
!  Local variable declarations.
!
      integer :: ng
!
!-----------------------------------------------------------------------
!  Deallocate structure variables for each nested grids.
!-----------------------------------------------------------------------
!
      DO ng=1,Ngrids
        IF (allocated(FOURDVAR(ng) % NobsSurvey)) THEN
          deallocate (FOURDVAR(ng) % NobsSurvey)
        END IF
        IF (allocated(FOURDVAR(ng) % SurveyTime)) THEN
          deallocate (FOURDVAR(ng) % SurveyTime)
        END IF
        IF (allocated(FOURDVAR(ng) % cg_pxsave)) THEN
          deallocate (FOURDVAR(ng) % cg_pxsave)
        END IF
        IF (allocated(FOURDVAR(ng) % cg_pxout)) THEN
          deallocate (FOURDVAR(ng) % cg_pxout)
        END IF
        IF (allocated(FOURDVAR(ng) % cg_pxsum)) THEN
          deallocate (FOURDVAR(ng) % cg_pxsum)
        END IF
        IF (allocated(FOURDVAR(ng) % cg_Dpxsum)) THEN
          deallocate (FOURDVAR(ng) % cg_Dpxsum)
        END IF
        IF (allocated(FOURDVAR(ng) % tl_cg_pxsave)) THEN
          deallocate (FOURDVAR(ng) % tl_cg_pxsave)
        END IF
        IF (allocated(FOURDVAR(ng) % ad_cg_pxsave)) THEN
          deallocate (FOURDVAR(ng) % ad_cg_pxsave)
        END IF
      END DO
!
!-----------------------------------------------------------------------
!  deallocate and initialize model and observation variables.
!-----------------------------------------------------------------------
!
      IF (allocated(ObsAngler)) THEN
        deallocate (ObsAngler)
      END IF
      IF (allocated(ObsType)) THEN
        deallocate (ObsType)
      END IF
      IF (allocated(ObsProv)) THEN
        deallocate (ObsProv)
      END IF
      IF (allocated(ObsErr)) THEN
        deallocate (ObsErr)
      END IF
      IF (allocated(ObsMeta)) THEN
        deallocate (ObsMeta)
      END IF
      IF (allocated(ObsScale)) THEN
        deallocate (ObsScale)
      END IF
      IF (allocated(ObsVetting)) THEN
        deallocate (ObsVetting)
      END IF
      IF (allocated(ObsVal)) THEN
        deallocate (ObsVal)
      END IF
      IF (allocated(Tobs)) THEN
        deallocate (Tobs)
      END IF
      IF (allocated(Xobs)) THEN
        deallocate (Xobs)
      END IF
      IF (allocated(Yobs)) THEN
        deallocate (Yobs)
      END IF
      IF (allocated(Zobs)) THEN
        deallocate (Zobs)
      END IF
      IF (allocated(ADmodVal)) THEN
        deallocate (ADmodVal)
      END IF
      IF (allocated(NLmodVal)) THEN
        deallocate (NLmodVal)
      END IF
      IF (allocated(misfit)) THEN
        deallocate (misfit)
      END IF
      IF (allocated(unvetted)) THEN
        deallocate (unvetted)
      END IF
      IF (allocated(uradial)) THEN
        deallocate (uradial)
      END IF
      IF (allocated(vradial)) THEN
        deallocate (vradial)
      END IF
      IF (allocated(uBgErr)) THEN
        deallocate (uBgErr)
      END IF
      IF (allocated(vBgErr)) THEN
        deallocate (vBgErr)
      END IF
      IF (allocated(BgErr)) THEN
        deallocate (BgErr)
      END IF
      IF (allocated(NLincrement)) THEN
        deallocate (NLincrement)
      END IF
      IF (allocated(innovation)) THEN
        deallocate (innovation)
      END IF
      IF (allocated(residual)) THEN
        deallocate (residual)
      END IF
      IF (allocated(TLmodVal)) THEN
        deallocate (TLmodVal)
      END IF
      IF (allocated(TLmodVal_S)) THEN
        deallocate (TLmodVal_S)
      END IF
      IF (allocated(BCKmodVal)) THEN
        deallocate (BCKmodVal)
      END IF
      IF (allocated(Hbk)) THEN
        deallocate (Hbk)
      END IF
!
!  Deallocate and initialize weak constraint conjugate gradient vectors.
!
      IF (allocated(zcglwk)) THEN
        deallocate (zcglwk)
      END IF
      IF (allocated(vcglwk)) THEN
        deallocate (vcglwk)
      END IF
      IF (allocated(vcglev)) THEN
        deallocate (vcglev)
      END IF
      IF (allocated(zgrad0)) THEN
        deallocate (zgrad0)
      END IF
      IF (allocated(vgrad0)) THEN
        deallocate (vgrad0)
      END IF
      IF (allocated(vgrad0s)) THEN
        deallocate (vgrad0s)
      END IF
      IF (allocated(cg_innov)) THEN
        deallocate (cg_innov)
      END IF
!
      RETURN
      END SUBROUTINE deallocate_fourdvar
!
      SUBROUTINE initialize_fourdvar
!
!=======================================================================
!                                                                      !
!  This routine initializes several variables in module "mod_fourdvar" !
!  for all nested grids.                                               !
!                                                                      !
!=======================================================================
!
      USE mod_parallel
      USE mod_scalars
      USE mod_iounits
      USE mod_ncparam
      USE mod_netcdf
!
      USE strings_mod, ONLY : FoundError
      USE strings_mod, ONLY : uppercase
!
!  Local variable declarations.
!
      integer :: my_NobsVar, i, icount, ng
      real(r8), parameter :: IniVal = 0.0_r8
!
      character (len=*), parameter :: MyFile =                          &
     &  "ROMS/Modules/mod_fourdvar.F"//", initialize_fourdvar"
      SourceFile=MyFile
!
!-----------------------------------------------------------------------
!  Inquire observations NetCDF and determine the maximum dimension of
!  several observations arrays.
!-----------------------------------------------------------------------
!
!  Inquire about the size of the "datum" unlimitted dimension and
!  "survey" dimension.
!
      DO ng=1,Ngrids
        SELECT CASE (OBS(ng)%IOtype)
          CASE (io_nf90)
            CALL netcdf_get_dim (ng, iNLM, TRIM(OBS(ng)%name))
          CASE DEFAULT
            IF (Master) WRITE (stdout,10) OBS(ng)%IOtype
            exit_flag=3
        END SELECT
        IF (FoundError(exit_flag, NoError, 1642, MyFile)) RETURN
!
        Ndatum(ng)=0
        Nsurvey(ng)=0
        DO i=1,n_dim
          IF (TRIM(dim_name(i)).eq.'datum') then
            Ndatum(ng)=dim_size(i)
          ELSE IF (TRIM(dim_name(i)).eq.'survey') THEN
            Nsurvey(ng)=dim_size(i)
          ELSE IF (TRIM(dim_name(i)).eq.'state_variable') THEN
            my_NobsVar=dim_size(i)
          END IF
        END DO
        IF (Ndatum(ng).eq.0) THEN
          WRITE (stdout,20) 'datum', TRIM(OBS(ng)%name)
          exit_flag=2
          RETURN
        END IF
        IF (Nsurvey(ng).eq.0) THEN
          WRITE (stdout,20) 'survey', TRIM(OBS(ng)%name)
          exit_flag=2
          RETURN
        END IF
      END DO
!
!-----------------------------------------------------------------------
!  Allocate module variables.
!-----------------------------------------------------------------------
!
!
!  Allocate structure (1:Ngrids).
!
      IF (.not.allocated( FOURDVAR)) THEN
        allocate ( FOURDVAR(Ngrids) )
      END IF
!
!  Allocate variables in structure.
!
      DO ng=1,Ngrids
!
!  Number of state variables associated with data assimilation.
!
!    zeta, ubar, vbar, Uvel, Vvel, Tvar(1:MT)
!    Ustr, Vstr, Tsur(1:MT)
!
!  The rest are ignored.
!
        NstateVar(ng)=5+NT(ng)
        NobsVar(ng)=NstateVar(ng)+NextraObs
        IF (.not.allocated(FOURDVAR(ng) % NobsSurvey)) THEN
          allocate ( FOURDVAR(ng) % NobsSurvey(Nsurvey(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(Nsurvey(ng),r8)
          FOURDVAR(ng) % NobsSurvey = 0
        END IF
        IF (.not.allocated(FOURDVAR(ng) % SurveyTime)) THEN
          allocate ( FOURDVAR(ng) % SurveyTime(Nsurvey(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(Nsurvey(ng),r8)
          FOURDVAR(ng) % SurveyTime = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % BackCost)) THEN
          allocate ( FOURDVAR(ng) % BackCost(0:NstateVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NstateVar(ng)+1,r8)
          FOURDVAR(ng) % BackCost = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % Cost0)) THEN
          allocate ( FOURDVAR(ng) % Cost0(Nouter) )
          Dmem(ng)=Dmem(ng)+REAL(Nouter,r8)
          FOURDVAR(ng) % Cost0 = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % CostFun)) THEN
          allocate ( FOURDVAR(ng) % CostFun(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % CostFun = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % CostFunOld)) THEN
          allocate ( FOURDVAR(ng) % CostFunOld(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % CostFunOld = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % CostNorm)) THEN
          allocate ( FOURDVAR(ng) % CostNorm(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % CostNorm = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % ObsCost)) THEN
          allocate ( FOURDVAR(ng) % ObsCost(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % ObsCost = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % DataPenalty)) THEN
          allocate ( FOURDVAR(ng) % DataPenalty(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % DataPenalty = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % NLPenalty)) THEN
          allocate ( FOURDVAR(ng) % NLPenalty(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % NLPenalty = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % NLobsCost)) THEN
          allocate ( FOURDVAR(ng) % NLobsCost(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % NLobsCost = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % ObsCount)) THEN
          allocate ( FOURDVAR(ng) % ObsCount(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % ObsCount = 0
        END IF
        IF (.not.allocated(FOURDVAR(ng) % ObsReject)) THEN
          allocate ( FOURDVAR(ng) % ObsReject(0:NobsVar(ng)) )
          Dmem(ng)=Dmem(ng)+REAL(NobsVar(ng)+1,r8)
          FOURDVAR(ng) % ObsReject = 0
        END IF
        IF (.not.allocated(FOURDVAR(ng) % cg_pxout)) THEN
          allocate ( FOURDVAR(ng) % cg_pxout(Nouter) )
          Dmem(ng)=Dmem(ng)+REAL(Nouter)
          FOURDVAR(ng) % cg_pxout = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % cg_pxsave)) THEN
          allocate ( FOURDVAR(ng) % cg_pxsave(Ndatum(ng)+1) )
          Dmem(ng)=Dmem(ng)+REAL(Ndatum(ng)+1,r8)
          FOURDVAR(ng) % cg_pxsave = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % cg_pxsum)) THEN
          allocate ( FOURDVAR(ng) % cg_pxsum(Ndatum(ng)+1) )
          Dmem(ng)=Dmem(ng)+REAL(Ndatum(ng)+1,r8)
          FOURDVAR(ng) % cg_pxsum = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % cg_Dpxsum)) THEN
          allocate ( FOURDVAR(ng) % cg_Dpxsum(Ndatum(ng)+1) )
          Dmem(ng)=Dmem(ng)+REAL(Ndatum(ng)+1,r8)
          FOURDVAR(ng) % cg_Dpxsum = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % tl_cg_pxsave)) THEN
          allocate ( FOURDVAR(ng) % tl_cg_pxsave(Ndatum(ng)+1) )
          Dmem(ng)=Dmem(ng)+REAL(Ndatum(ng)+1,r8)
          FOURDVAR(ng) % tl_cg_pxsave = IniVal
        END IF
        IF (.not.allocated(FOURDVAR(ng) % ad_cg_pxsave)) THEN
          allocate ( FOURDVAR(ng) % ad_cg_pxsave(Ndatum(ng)+1) )
          Dmem(ng)=Dmem(ng)+REAL(Ndatum(ng)+1,r8)
          FOURDVAR(ng) % ad_cg_pxsave = IniVal
        END IF
      END DO
!
!-----------------------------------------------------------------------
!  Read in number of observations available per survey at their times.
!-----------------------------------------------------------------------
!
      DO ng=1,Ngrids
!
!  Read in number of observations available per survey and the time of
!  each observation survey.
!
        SELECT CASE (OBS(ng)%IOtype)
          CASE (io_nf90)
            CALL netcdf_get_ivar (ng, iNLM, TRIM(OBS(ng)%name),         &
     &                            TRIM(Vname(1,idNobs)),                &
     &                            FOURDVAR(ng)%NobsSurvey,              &
     &                            start = (/1/),                        &
     &                            total = (/Nsurvey(ng)/))
            IF (FoundError(exit_flag, NoError, 1976, MyFile)) RETURN
!
            CALL netcdf_get_fvar (ng, iNLM, TRIM(OBS(ng)%name),         &
     &                            TRIM(Vname(1,idOday)),                &
     &                            FOURDVAR(ng)%SurveyTime,              &
     &                            start = (/1/),                        &
     &                            total = (/Nsurvey(ng)/))
            IF (FoundError(exit_flag, NoError, 1983, MyFile)) RETURN
        END SELECT
!
!  Determine maximum size of observation arrays.
!
        Mobs=MAXVAL(Ndatum)
      END DO
!
!-----------------------------------------------------------------------
!  Allocate and initialize model and observation variables.
!-----------------------------------------------------------------------
!
      IF (.not.allocated(ObsAngler)) THEN
        allocate ( ObsAngler(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ObsAngler = IniVal
      IF (.not.allocated(ObsType)) THEN
        allocate ( ObsType(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ObsType = 0
      IF (.not.allocated(ObsState2Type)) THEN
        allocate ( ObsState2Type(0:MAXVAL(NobsVar)) )
        Dmem(1)=Dmem(1)+REAL(MAXVAL(NobsVar)+1,r8)
      END IF
      ObsState2Type = 0
      IF (.not.allocated(ObsProv)) THEN
        allocate ( ObsProv(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ObsProv = 0
      IF (.not.allocated(ObsErr)) THEN
        allocate ( ObsErr(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ObsErr = IniVal
      IF (.not.allocated(ObsMeta)) THEN
        allocate ( ObsMeta(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ObsMeta = IniVal
      IF (.not.allocated(ObsScale)) THEN
        allocate ( ObsScale(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ObsScale = IniVal
      IF (.not.allocated(ObsVetting)) THEN
        allocate ( ObsVetting(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ObsVetting = IniVal
      IF (.not.allocated(ObsVal)) THEN
        allocate ( ObsVal(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ObsVal = IniVal
      IF (.not.allocated(Tobs)) THEN
        allocate ( Tobs(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      Tobs = 0.0_dp
      IF (.not.allocated(Xobs)) THEN
        allocate ( Xobs(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      Xobs = IniVal
      IF (.not.allocated(Yobs)) THEN
        allocate ( Yobs(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      Yobs = IniVal
      IF (.not.allocated(Zobs)) THEN
        allocate ( Zobs(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      Zobs = IniVal
      IF (.not.allocated(ADmodVal)) THEN
        allocate ( ADmodVal(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      ADmodVal = IniVal
      IF (.not.allocated(NLmodVal)) THEN
        allocate ( NLmodVal(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      NLmodVal = IniVal
      IF (.not.allocated(misfit)) THEN
        allocate ( misfit(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      misfit = IniVal
      IF (.not.allocated(unvetted)) THEN
        allocate ( unvetted(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      unvetted = IniVal
      IF (.not.allocated(uradial)) THEN
        allocate ( uradial(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      uradial = IniVal
      IF (.not.allocated(vradial)) THEN
        allocate ( vradial(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      vradial = IniVal
      IF (.not.allocated(uBgErr)) THEN
        allocate ( uBgErr(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      uBgErr = IniVal
      IF (.not.allocated(vBgErr)) THEN
        allocate ( vBgErr(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      vBgErr = IniVal
      IF (.not.allocated(BgErr)) THEN
        allocate ( BgErr(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      BgErr = IniVal
      IF (.not.allocated(NLincrement)) THEN
        allocate ( NLincrement(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      NLincrement = IniVal
      IF (.not.allocated(innovation)) THEN
        allocate ( innovation(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      innovation = IniVal
      IF (.not.allocated(residual)) THEN
        allocate ( residual(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      residual = IniVal
      IF (.not.allocated(TLmodVal)) THEN
        allocate ( TLmodVal(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      TLmodVal = IniVal
!
!  The following arrays are only needed and used by the master node.
!  In order to avoid hogging memory penalties in the other nodes, they
!  are only allocated by the master node.
!
      IF (.not.allocated(TLmodVal_S)) THEN
        allocate ( TLmodVal_S(Mobs,Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Mobs*Ninner*Nouter,r8)
      END IF
      TLmodVal_S = IniVal
      IF (.not.allocated(BCKmodVal)) THEN
        allocate ( BCKmodVal(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      BCKmodVal = IniVal
      IF (.not.allocated(Hbk)) THEN
        allocate ( Hbk(Mobs,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Mobs*Nouter,r8)
      END IF
      Hbk = IniVal
!
!  Allocate and initialize observation types names and indices.
!  Notice that a mapping from state-to-type (ObsState2Type) and its
!  inverse type-to-state (ObsType2State) indices are needed because
!  the User is allowed to add extra-observation operators with
!  nonsequential type enumeration.
!
!  Both mapping arrays ObsState2Type and ObsType2State have a zero
!  array element to allow applications with no extra-observations
!  to work with their zero associated state index (as initialized
!  in mod_ncparam.F).  For example, if the index "isRadial" is not
!  redefined below, the following assignment
!
!        ObsState2Type(isRadial)=ObsState2Type(0)=0
!        ObsType2State(isRadial)=ObsType2State(0)=0
!
!  is still legal with isRadial=0. It avoids a Fortran segmentation
!  violation (i.e., subscript #1 of the array ObsState2Type has
!  value 0 which is less than the lower bound of 1).  Sorry for
!  the awkward logic but we need a generic way to specify extra-
!  observation operators.
!
      IF (.not.allocated(ObsName)) THEN
        allocate ( ObsName(MAXVAL(NobsVar)) )
        Dmem(1)=Dmem(1)+REAL(MAXVAL(NobsVar),r8)
      END IF
      icount=MAXVAL(NstateVar)
      ObsState2Type(0)=0
      DO i=1,icount                               ! 5+NT
        ObsState2Type(i)=i
        ObsName(i)=TRIM(Vname(1,idSvar(i)))
      END DO
      IF (NextraObs.gt.0) THEN
        DO i=1,NextraObs
          icount=icount+1
          ObsName(icount)=TRIM(ExtraName(i))
          SELECT CASE (TRIM(uppercase(ExtraName(i))))
            CASE ('RADIAL')
              isRadial=icount
              ObsState2Type(icount)=ExtraIndex(i)
          END SELECT
        END DO
      END IF
!
!  Set inverse mapping type-to-state.
!
      IF (.not.allocated(ObsType2State)) THEN
        allocate ( ObsType2State(0:MAXVAL(ObsState2Type)) )
        Dmem(1)=Dmem(1)+REAL(MAXVAL(ObsState2Type)+1,r8)
      END IF
      ObsType2State(0)=0
      ObsType2State(1:MAXVAL(ObsState2Type))=-1
      DO i=1,MAXVAL(NstateVar)
       ObsType2State(i)=i
      END DO
      IF (NextraObs.gt.0) THEN
        DO i=1,NextraObs
          icount=ExtraIndex(i)
          SELECT CASE (TRIM(uppercase(ExtraName(i))))
            CASE ('RADIAL')
              ObsType2State(icount)=isRadial
          END SELECT
        END DO
      END IF
!
!  Allocate and initialize weak constraint conjugate gradient vectors.
!
      IF (.not.allocated(zcglwk)) THEN
        allocate ( zcglwk(Mobs+1,Ninner+1,Nouter) )
        Dmem(1)=Dmem(1)+REAL((Mobs+1)*(Ninner+1)*Nouter,r8)
      END IF
      zcglwk = IniVal
      IF (.not.allocated(vcglwk)) THEN
        allocate ( vcglwk(Mobs+1,Ninner+1,Nouter) )
        Dmem(1)=Dmem(1)+REAL((Mobs+1)*(Ninner+1)*Nouter,r8)
      END IF
      vcglwk = IniVal
      IF (.not.allocated(Jb0)) THEN
        allocate ( Jb0(0:Nouter) )
        Dmem(1)=Dmem(1)+REAL(Nouter+1,r8)
      END IF
      Jb0 = IniVal
      IF (.not.allocated(vcglev)) THEN
        allocate ( vcglev(Mobs+1,Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL((Mobs+1)*Ninner*Nouter,r8)
      END IF
      vcglev = IniVal
      IF (.not.allocated(zgrad0)) THEN
        allocate ( zgrad0(Mobs+1,Nouter) )
        Dmem(1)=Dmem(1)+REAL((Mobs+1)*Nouter,r8)
      END IF
      zgrad0 = IniVal
      IF (.not.allocated(vgrad0)) THEN
        allocate ( vgrad0(Mobs+1) )
        Dmem(1)=Dmem(1)+REAL(Mobs+1,r8)
      END IF
      vgrad0 = IniVal
     IF (.not.allocated(vgrad0s)) THEN
        allocate ( vgrad0s(Mobs+1) )
        Dmem(1)=Dmem(1)+REAL(Mobs+1,r8)
      END IF
      vgrad0s = IniVal
      IF (.not.allocated(gdgw)) THEN
        allocate ( gdgw(Mobs) )
        Dmem(1)=Dmem(1)+REAL(Mobs,r8)
      END IF
      gdgw = IniVal
      IF (.not.allocated(vgnorm)) THEN
        allocate ( vgnorm(Nouter) )
        Dmem(1)=Dmem(1)+REAL(Nouter,r8)
      END IF
      vgnorm = IniVal
      IF (.not.allocated(cg_innov)) THEN
        allocate ( cg_innov(Mobs+1) )
        Dmem(1)=Dmem(1)+REAL(Mobs+1,r8)
      END IF
      cg_innov = IniVal
      IF (.not.allocated(cg_dla)) THEN
        allocate ( cg_dla(Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Ninner*Nouter,r8)
      END IF
      cg_dla = IniVal
!
!  Allocate convolution parameters.
!
      IF (.not.allocated(NHsteps)) THEN
        allocate ( NHsteps(2,MstateVar) )
        Dmem(1)=Dmem(1)+2.0_r8*REAL(MstateVar,r8)
      END IF
      NHsteps = 0
      IF (.not.allocated(NVsteps)) THEN
        allocate ( NVsteps(2,MstateVar) )
        Dmem(1)=Dmem(1)+2.0_r8*REAL(MstateVar,r8)
      END IF
      NVsteps = 0
      IF (.not.allocated(DTsizeH)) THEN
        allocate ( DTsizeH(2,MstateVar) )
        Dmem(1)=Dmem(1)+2.0_r8*REAL(MstateVar,r8)
      END IF
      DTsizeH = IniVal
      IF (.not.allocated(DTsizeV)) THEN
        allocate ( DTsizeV(2,MstateVar) )
        Dmem(1)=Dmem(1)+2.0_r8*REAL(MstateVar,r8)
      END IF
      DTsizeV = IniVal
      IF (.not.allocated(NVstepsB)) THEN
        allocate ( NVstepsB(4,MstateVar) )
        Dmem(1)=Dmem(1)+4.0_r8*REAL(MstateVar,r8)
      END IF
      NVstepsB = 0
      IF (.not.allocated(NHstepsB)) THEN
        allocate ( NHstepsB(4,MstateVar) )
        Dmem(1)=Dmem(1)+4.0_r8*REAL(MstateVar,r8)
      END IF
      NHstepsB = 0
      IF (.not.allocated(DTsizeHB)) THEN
        allocate ( DTsizeHB(4,MstateVar) )
        Dmem(1)=Dmem(1)+4.0_r8*REAL(MstateVar,r8)
      END IF
      DTsizeHB = IniVal
      IF (.not.allocated(DTsizeVB)) THEN
        allocate ( DTsizeVB(4,MstateVar) )
        Dmem(1)=Dmem(1)+4.0_r8*REAL(MstateVar,r8)
      END IF
      DTsizeVB = IniVal
!
!  Allocate conjugate gradient variables.
!
      IF (.not.allocated(cg_alpha)) THEN
        allocate ( cg_alpha(0:Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL((Ninner+1)*Nouter,r8)
      END IF
      cg_alpha = IniVal
      IF (.not.allocated(cg_beta)) THEN
        allocate ( cg_beta(Ninner+1,Nouter) )
        Dmem(1)=Dmem(1)+REAL((Ninner+1)*Nouter,r8)
      END IF
      cg_beta = IniVal
      IF (.not.allocated(cg_tau)) THEN
        allocate ( cg_tau(0:Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL((Ninner+1)*Nouter,r8)
      END IF
      cg_tau = IniVal
      IF (.not.allocated(Ritz)) THEN
        allocate ( Ritz(Ninner+1) )
        Dmem(1)=Dmem(1)+REAL(Ninner+1,r8)
      END IF
      Ritz = IniVal
      IF (.not.allocated(cg_zv)) THEN
        allocate ( cg_zv(Ninner,Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Ninner*Ninner*Nouter,r8)
      END IF
      cg_zv = IniVal
      IF (.not.allocated(cg_delta)) THEN
        allocate ( cg_delta(Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Ninner*Nouter,r8)
      END IF
      cg_delta = IniVal
      IF (.not.allocated(cg_gamma)) THEN
        allocate ( cg_gamma(Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Ninner*Nouter,r8)
      END IF
      cg_gamma = IniVal
      IF (.not.allocated(cg_Gnorm)) THEN
        allocate ( cg_Gnorm(Nouter) )
        Dmem(1)=Dmem(1)+REAL(Nouter,r8)
      END IF
      cg_Gnorm = IniVal
      IF (.not.allocated(cg_Gnorm_v)) THEN
        allocate ( cg_Gnorm_v(Nouter) )
        Dmem(1)=Dmem(1)+REAL(Nouter,r8)
      END IF
      cg_Gnorm_v = IniVal
      IF (.not.allocated(cg_Gnorm_y)) THEN
        allocate ( cg_Gnorm_y(Nouter) )
        Dmem(1)=Dmem(1)+REAL(Nouter,r8)
      END IF
      cg_Gnorm_y = IniVal
      IF (.not.allocated(cg_Greduc)) THEN
        allocate ( cg_Greduc(Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Ninner*Nouter,r8)
      END IF
      cg_Greduc = IniVal
      IF (.not.allocated(cg_QG)) THEN
        allocate ( cg_QG(Ninner+1,Nouter) )
        Dmem(1)=Dmem(1)+REAL((Ninner+1)*Nouter,r8)
      END IF
      cg_QG = IniVal
      IF (.not.allocated(cg_Tmatrix)) THEN
        allocate ( cg_Tmatrix(Ninner,3) )
        Dmem(1)=Dmem(1)+3.0_r8*REAL(Ninner,r8)
      END IF
      cg_Tmatrix = IniVal
      IF (.not.allocated(cg_Ritz)) THEN
        allocate ( cg_Ritz(Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Ninner*Nouter,r8)
      END IF
      cg_Ritz = IniVal
      IF (.not.allocated(cg_RitzErr)) THEN
        allocate ( cg_RitzErr(Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Ninner*Nouter,r8)
      END IF
      cg_RitzErr = IniVal
      IF (.not.allocated(cg_zu)) THEN
        allocate ( cg_zu(Ninner,Nouter) )
        Dmem(1)=Dmem(1)+REAL(Ninner*Nouter,r8)
      END IF
      cg_zu = IniVal
!
!-----------------------------------------------------------------------
!  Initialize various variables.
!-----------------------------------------------------------------------
!
      DO ng=1,Ngrids
        Load_Zobs(ng)=.FALSE.
        ProcessObs(ng)=.FALSE.
        haveObsMeta(ng)=.FALSE.
        wrote_Zobs(ng)=.FALSE.
        wrtMisfit(ng)=.FALSE.
        wrtNLmod(ng)=.FALSE.
        wrtObsScale(ng)=.FALSE.
        wrtRPmod(ng)=.FALSE.
        wrtTLmod(ng)=.FALSE.
        KhMin(ng)=1.0_r8
        KhMax(ng)=1.0_r8
        KvMin(ng)=1.0_r8
        KvMax(ng)=1.0_r8
        Optimality(ng)=0.0_r8
        ForceTime(ng)=0.0_r8
      END DO
!
 10   FORMAT (/,' INITIALIZE_FOURDVAR - Illegal output type,',          &
     &        ' io_type = ',i0)
 20   FORMAT (/,' INITIALIZE_FOURDVAR - error inquiring dimension:',    &
     &        1x,a,2x,'in input NetCDF file: ',a)
      RETURN
      END SUBROUTINE initialize_fourdvar
      END MODULE mod_fourdvar