!--------------------------------------- LICENCE BEGIN -----------------------------------
!Environment Canada - Atmospheric Science and Technology License/Disclaimer,
!                     version 3; Last Modified: May 7, 2008.
!This is free but copyrighted software; you can use/redistribute/modify it under the terms
!of the Environment Canada - Atmospheric Science and Technology License/Disclaimer
!version 3 or (at your option) any later version that should be found at:
!http://collaboration.cmc.ec.gc.ca/science/rpn.comm/license.html
!
!This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
!without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
!See the above mentioned License/Disclaimer for more details.
!You should have received a copy of the License/Disclaimer along with this software;
!if not, you can write to: EC-RPN COMM Group, 2121 TransCanada, suite 500, Dorval (Quebec),
!CANADA, H9P 1J3; or send e-mail to service.rpn@ec.gc.ca
!-------------------------------------- LICENCE END --------------------------------------

!--------------------------------------------------------------------------
! > MODULE CalcBMatrix_lam_mod (prefix="calcb_lam")
!
! Purpose: Compute homogeneous and isotropic background error covariances 
!          from forecast error estimate in model variable space
!
! - Subroutines
!    calcb_lam_Setup        (public)
!    calcb_lam_computeStats (public)
!
! - Public variables
!    NONE
!
! - Dependencies
!    lamspectraltransform
!--------------------------------------------------------------------------

module calcbmatrix_lam_mod 1,4
  use gridStateVector_mod
  use LamSpectralTransform_mod
  use LamAnalysisGrid_mod
  use HorizontalCoord_mod
  implicit none
  save
  private

  ! Public Subroutines
  public :: calcb_lam_setup, calcb_lam_computeStats

  type(struct_hco), pointer :: hco_ens ! Ensemble horizontal grid parameters
  type(struct_hco), pointer :: hco_bhi ! B matrix horizontal grid parameters
  type(struct_vco), pointer :: vco_bhi ! B matrix vertical grid parameters
  type(struct_lst)          :: lst_bhi ! Spectral transform Parameters

  character(len=256), allocatable :: cflensin(:)

  integer, parameter :: cv_model = 1
  integer, parameter :: cv_bhi   = 2
  type  :: calcb_cv
    character(len=4)      :: NomVar(2)
    character(len=2)      :: GridType ! TH=Thermo, MM=Momentum, NS=Non-staggered
    integer               :: nlev
    integer               :: kDimStart
    integer               :: kDimEnd
    integer, allocatable  :: ip1(:)
  end type calcb_cv

  integer,parameter      :: nMaxControlVar = 10
  type(calcb_cv)         :: ControlVariable(nMaxControlVar)

  integer                :: nControlVariable
  integer :: nens
  integer :: ntrunc
  integer :: nkgdim

  logical :: initialized = .false.
  logical :: NormByStdDev

  character(len=12) :: WindTransform

  real(8), pointer :: pressureProfile(:)

  contains

!--------------------------------------------------------------------------
! CALCB_LAM_SETUP
!--------------------------------------------------------------------------

    subroutine calcb_lam_setup( nens_in, cflens_in, hco_ens_in, vco_ens_in) 1,6
    use vGrid_Descriptors , only: vgrid_descriptor, vgd_levels, VGD_OK  
    implicit none

    integer,                   intent(in)   :: nens_in
    character(len=*),          intent(in)   :: cflens_in(nens_in)
    type(struct_vco), pointer, intent(in)   :: vco_ens_in
    type(struct_hco), pointer, intent(in)   :: hco_ens_in

    integer :: nulnam, ier, mpiMode, status
    integer :: fclos, fnom, fstouv, fstfrm
    integer :: grd_ext_x, grd_ext_y
    integer :: var, k

    real(8) :: SurfacePressure

    character(len=4) :: cgneed(nMaxControlVar)

    logical :: mask(nMaxControlVar)

    NAMELIST /NAMCALCB_LAM/ntrunc,grd_ext_x,grd_ext_y,NormByStdDev
    NAMELIST /NAMSTATE/CGNEED

    write(*,*)
    write(*,*) 'calcb_lam_setup: Starting...'

    !
    !- 1. Initialized the info on the ensemble
    !
    nens=nens_in

    allocate(cflensin(nens))
    cflensin(:)=cflens_in(:)

    hco_ens => hco_ens_in
    vco_bhi => vco_ens_in

    !
    !- 2. Read namelist NAMCALCB_LAM
    !
    ntrunc        = 75       ! Default value
    grd_ext_x     = 10       ! Default value
    grd_ext_y     = 10       ! Default value
    NormByStdDev  = .true.   ! Default value

    WindTransform = 'PsiChi' ! Hardwired

    nulnam = 0

    ier=fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
    read (nulnam,nml=namcalcb_lam)
    write(*     ,nml=namcalcb_lam)
    ier=fclos(nulnam)

    write(*,*)
    write(*,*) 'Truncation = ', ntrunc

    !
    !- 3. Initialized the extended (bi-periodic) grid
    !

    !- 3.1 Create the extended and non-extended grid prototype file
    call CreateLamTemplateGrids('./analysisgrid',grd_ext_x,grd_ext_y) ! IN

    !- 3.2 Setup the Extended B_HI grid
    call hco_SetupFromFile( './analysisgrid', 'ANALYSIS', 'BHI' ) ! IN
    hco_bhi => hco_Get('BHI')

    !- 3.3 Setup the LAM analysis grid metrics
    call lag_SetupFromHCO( 'BHI', 'Ensemble' ) ! IN

    !- 3.4 Setup the LAM spectral transform
    call lst_Setup( lst_bhi,                    & ! OUT
                    hco_bhi%ni, hco_bhi%nj, & ! IN
                    hco_bhi%dlon, ntrunc,     & ! IN
                    'NoMpi' )                     ! IN

    !
    !- 4.  Setup the control variables (model space and B_hi space)
    !

    !- 4.1 Read NAMELIST NAMSTATE to find which fields are needed
    cgneed(:) = 'NONE'
    ier=fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
    read (nulnam,nml=namstate)
    write(*     ,nml=namstate)
    ier=fclos(nulnam)

    mask = cgneed .ne. 'NONE'
    nControlVariable = count(mask) 
    write(*,*)
    write(*,*) 'Number of Control Variables = ', nControlVariable

    !- 4.2 Set ControlVariable structure
    nkgdim = 0
    do var = 1, nControlVariable

       !- Set variable name
       ControlVariable(var)%nomvar(cv_model)  = trim(cgneed(var))
       if ( ControlVariable(var)%nomvar(cv_model) == 'UU' ) then
          if ( trim(WindTransform) == 'PsiChi') then
             ControlVariable(var)%nomvar(cv_bhi)    = 'PP'
          else
             print*,'Only PsiChi Wind Trasnform is available'
             stop
          end if
       else if ( ControlVariable(var)%nomvar(cv_model) == 'VV' ) then
          if ( trim(WindTransform) == 'PsiChi') then
             ControlVariable(var)%nomvar(cv_bhi)    = 'CC'
          else
             print*,'Only PsiChi Wind Trasnform is available'
             stop
          end if
       else
          ControlVariable(var)%nomvar(cv_bhi)    = ControlVariable(var)%nomvar(cv_model)
       end if

       !- Set Level info
       if ( ControlVariable(var)%nomvar(cv_model) /= 'TG' ) then
          call VarLevInfo(ControlVariable(var)%nlev,             & ! OUT
                          ControlVariable(var)%GridType,         & ! OUT
                          ControlVariable(var)%nomvar(cv_model), & ! IN 
                          cflensin(1) )                            ! IN
       else
          ControlVariable(var)%nlev     = 1
          ControlVariable(var)%GridType = 'NS'
       end if

       write(*,*)
       write(*,*) 'Control Variable Name ', ControlVariable(var)%nomvar(cv_model)
       write(*,*) '   Number of Levels = ', ControlVariable(var)%nlev
       write(*,*) '   Type   of Levels = ', ControlVariable(var)%GridType
       
       allocate( ControlVariable(var)%ip1 (ControlVariable(var)%nlev) )

       if (ControlVariable(var)%nlev /= 1) then
          if (ControlVariable(var)%GridType == 'TH') then
            ControlVariable(var)%ip1(:) = vco_bhi%ip1_T(:)
          else
            ControlVariable(var)%ip1(:) = vco_bhi%ip1_M(:)
          end if
       else
          ControlVariable(var)%ip1(:) = 0
       end if

       ControlVariable(var)%kDimStart = nkgdim + 1
       nkgdim = nkgdim + ControlVariable(var)%nlev
       ControlVariable(var)%kDimEnd   = nkgdim

    end do

    !
    !- 5.  Setup a pressure profile for vertical localization 
    !
    !SurfacePressure = 101000.D0

    !status = vgd_levels( vco_bhi%vgrid, ip1_list=vco_bhi%ip1_M, & ! IN
    !                     levels=pressureProfile,                        & ! OUT
    !                     sfc_field=SurfacePressure, in_log=.false.)       ! IN

    !if ( status /= VGD_OK ) then
    !  write(*,*)
    !  write(*,*) 'calcb_lam_setup: ERROR with vgd_levels for desired levels '
    !  stop
    !else
    !   write(*,*)
    !   write(*,*) 'Pressure profile...'
    !   do k = 1, vco_bhi%nlev_M
    !     write(*,*) k, PressureProfile(k) / 100.d0, ' hPa'
    !   end do
    !endif

    !
    !- 6.  Ending
    !
    initialized = .true.

    write(*,*)
    write(*,*) 'calcb_lam_setup: Done!'

  end subroutine calcb_lam_setup

!--------------------------------------------------------------------------
! CALCB_LAM_ComputeStats
!--------------------------------------------------------------------------

  subroutine calcb_lam_computeStats 1,16
    implicit none

    integer :: ier

    integer :: NumBins2d, NumBins2dGridPoint

    real(4),pointer     :: ensPerturbations(:,:,:,:)

    real(8),allocatable :: ensMean3d(:,:,:)
    real(8),allocatable :: StdDev3d(:,:,:)
    real(8),allocatable :: StdDev3dGridPoint(:,:,:)
    real(8),allocatable :: SpVertCorrel(:,:,:)
    real(8),allocatable :: TotVertCorrel(:,:)
    real(8),allocatable :: NormB(:,:,:)
    real(8),allocatable :: NormBsqrt(:,:,:)
    real(8),allocatable :: PowerSpectrum(:,:)
    real(8),allocatable :: HorizScale(:)
    
    integer,allocatable :: Bin2d(:,:)
    integer,allocatable :: Bin2dGridPoint(:,:)

    allocate(ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens),stat=ier)
    if(ier /= 0) then
      write(*,*) 'Problem allocating ensPerturbations memory!',ier
      call flush(6)
    endif
    allocate(ensMean3d(hco_bhi%ni,hco_bhi%nj,nkgdim),stat=ier)
    if (ier /= 0) then
      write(*,*) 'Problem allocating ensMean3d memory!',ier
      call flush(6)
    end if
    allocate(Bin2d(hco_bhi%ni,hco_bhi%nj),stat=ier)
    if (ier /= 0) then
      write(*,*) 'Problem allocating Bin2d memory!',ier
      call flush(6)
    end if
    allocate(Bin2dGridPoint(hco_bhi%ni,hco_bhi%nj),stat=ier)
    if (ier /= 0) then
      write(*,*) 'Problem allocating Bin2d memory!',ier
      call flush(6)
    end if
    allocate(StdDev3d(hco_bhi%ni,hco_bhi%nj,nkgdim),stat=ier)
    if (ier /= 0) then
      write(*,*) 'Problem allocating StdDev3d memory!',ier
      call flush(6)
    end if
    allocate(StdDev3dGridPoint(hco_bhi%ni,hco_bhi%nj,nkgdim),stat=ier)
    if (ier /= 0) then
      write(*,*) 'Problem allocating StdDev3dGridPoint memory!',ier
      call flush(6)
    end if
    allocate(SpVertCorrel(nkgdim,nkgdim,0:ntrunc),stat=ier)
    if(ier.ne.0) then
      write(*,*) 'Problem allocating SpVertCorrel memory!',ier
      call flush(6)
    endif
    allocate(TotVertCorrel(nkgdim,nkgdim),stat=ier)
    if(ier.ne.0) then
      write(*,*) 'Problem allocating TotVertCorrel memory!',ier
      call flush(6)
    endif
    allocate(PowerSpectrum(nkgdim,0:ntrunc),stat=ier)
    if(ier.ne.0) then
      write(*,*) 'Problem allocating PowerSpectrum memory!',ier
      call flush(6)
    endif
    allocate(NormB(nkgdim,nkgdim,0:ntrunc),stat=ier)
    if(ier.ne.0) then
      write(*,*) 'Problem allocating Bsqrt memory!',ier
      call flush(6)
    endif
    allocate(NormBsqrt(nkgdim,nkgdim,0:ntrunc),stat=ier)
    if(ier.ne.0) then
      write(*,*) 'Problem allocating Bsqrt memory!',ier
      call flush(6)
    endif
    allocate(HorizScale(nkgdim),stat=ier)
    if(ier.ne.0) then
      write(*,*) 'Problem allocating HorizScale memory!',ier
      call flush(6)
    endif

    ensPerturbations(:,:,:,:) = 0.d0

    !
    !- 1.  Read forecast error estimates (all variables, levels, ens. members)
    !
    call ReadEnsemble(ensPerturbations) ! INOUT

    !
    !- 2.  Create periodic fields in X and Y directions
    !
    call BiPeriodization(ensPerturbations) ! INOUT

!    call WriteEnsemble   (ensPerturbations, './EnsWrite_A', cv_model ) ! IN

    !
    !- 3.  Transform u-wind and v-wind to control variables 
    !
    call UVToCtrlVar(ensPerturbations) ! INOUT

!    call WriteEnsemble   (ensPerturbations, './EnsWrite_B', cv_bhi ) ! IN

    !
    !- 4.  Remove the ensemble mean
    !
    call RemoveMean(ensPerturbations, & ! INOUT
                    ensMean3d)          ! OUT

    !
    !- 5.  Calculate the Standard Deviations in grid point space
    !

    !- 5.1 Binned Std. Dev. to be used in the analysis step
    call CreateBins(Bin2d, NumBins2d, & ! OUT
                    'HorizontalMean')   ! IN

    call CalcStdDev3d(ensPerturbations, & ! INOUT
                      StdDev3d,         & ! OUT
                      Bin2d, NumBins2d)   ! IN

    !- 5.2 Non-binned Std. Dev. to be used for normalization and/or
    !      diagnostics
    call CreateBins(Bin2dGridPoint, NumBins2dGridPoint, & ! OUT
                    'GridPoint')                      ! IN

    call CalcStdDev3d(ensPerturbations,             & ! INOUT
                      StdDev3dGridPoint,              & ! OUT
                      Bin2dGridPoint, NumBins2dGridPoint) ! IN

    !- 5.3 Normalization
    if ( NormByStdDev ) then
      call Normalize3d(ensPerturbations, & ! INOUT
                        StdDev3dGridPoint)    ! IN
    end if

    !
    !- 6.  Covariance statistics in Spectral Space
    !

    !- 6.1 Vertical correlations and Power Spectra
    call CalcSpectralStats(ensPerturbations,              & ! IN
                           SpVertCorrel, PowerSpectrum,   & ! OUT
                           NormB)                           ! OUT

    !- 6.2 Calculate the horiontal correlation lenght scales
    call CalcHorizScale(HorizScale, & ! OUT
                        NormB)        ! IN

    !- 6.3 Calculate the total vertical correlation matrix
    call CalcTotVertCorrel(TotVertCorrel, & ! OUT
                           NormB)           ! IN

    !- 6.4 Set cross-correlations
    call SetSpVertCorrel(NormB) ! INOUT

    !- 6.5 Calculate the square-root of the correlation-based B matrix
    call CalcBsqrt(NormBsqrt, & ! OUT
                   NormB   )    ! IN

    !
    !- 7.  Writing statistics to files
    !

    !- 7.1 Statistics needed by VAR in analysis mode
    call WriteVarStats(NormBsqrt, StdDev3d) ! IN

    !- 7.2 Diagnostics fields
    call WriteDiagStats(NormB, SpVertCorrel, TotVertCorrel, EnsMean3d, & ! 
                        StdDev3dGridPoint, PowerSpectrum, HorizScale)    ! IN

    !- 7.3. Statistics for Background Check
    ! Not coded yet

    deallocate(Bin2d)
    deallocate(Bin2dGridPoint)
    deallocate(ensPerturbations)
    deallocate(StdDev3d)
    deallocate(StdDev3dGridPoint)
    deallocate(ensMean3d)
    deallocate(SpVertCorrel)
    deallocate(TotVertCorrel)
    deallocate(PowerSpectrum)
    deallocate(NormB)
    deallocate(NormBsqrt)
    deallocate(HorizScale)

  end subroutine calcb_lam_computeStats

!--------------------------------------------------------------------------
! CreateLamTemplateGrids
!--------------------------------------------------------------------------

  subroutine CreateLamTemplateGrids(TemplateFileName,grd_ext_x,grd_ext_y) 1,7
    use MathPhysConstants_mod, only : MPC_DEGREES_PER_RADIAN_R8
    implicit none

    character(len=*), intent(in) :: TemplateFileName
    integer         , intent(in) :: grd_ext_x
    integer         , intent(in) :: grd_ext_y

    integer :: ni_ext, nj_ext, i, j
    integer :: iun = 0
    integer :: ier, fnom, fstouv, fstfrm, fclos, vfstecr

    real(8), allocatable :: Field2d(:,:)

    real(8), allocatable :: lat_ext(:)
    real(8), allocatable :: lon_ext(:)

    real(8) :: work, dlat, dlon

    integer :: dateo,npak
    integer :: ip1,ip2,ip3,deet,npas,datyp,ig1,ig2,ig3,ig4
    integer :: ig1_tictac,ig2_tictac,ig3_tictac,ig4_tictac

    character(len=1)  :: grtyp
    character(len=2)  :: typvar
    character(len=12) :: etiket

    !
    !- 1.  Opening the output template file
    !
    ier = fnom(iun, trim(TemplateFileName), 'RND', 0)
    ier = fstouv(iun, 'RND')

    npak     = -32

    !
    !- 2.  Writing the core grid (Ensemble) template
    !

    !- 2.1 Tic-Tac
    deet     =  0
    ip1      =  hco_ens%ig1
    ip2      =  hco_ens%ig2
    ip3      =  0
    npas     =  0
    datyp    =  1
    grtyp    = 'E'
    typvar   = 'X'
    etiket   = 'COREGRID'
    dateo =  0

    call cxgaig ( grtyp,                                          & ! IN
                  ig1_tictac, ig2_tictac, ig3_tictac, ig4_tictac, & ! OUT
                  real(hco_ens%xlat1), real(hco_ens%xlon1),   & ! IN
                  real(hco_ens%xlat2), real(hco_ens%xlon2)  )   ! IN

    ig1      =  ig1_tictac
    ig2      =  ig2_tictac
    ig3      =  ig3_tictac
    ig4      =  ig4_tictac

    ier = vfstecr(hco_ens%lon*MPC_DEGREES_PER_RADIAN_R8, work, npak, &
                  iun, dateo, deet, npas, hco_ens%ni, 1, 1, ip1,    &
                  ip2, ip3, typvar, '>>', etiket, grtyp, ig1,          &
                  ig2, ig3, ig4, datyp, .true.)

    ier = vfstecr(hco_ens%lat*MPC_DEGREES_PER_RADIAN_R8, work, npak, &
                  iun, dateo, deet, npas, 1, hco_ens%nj, 1, ip1,    &
                  ip2, ip3, typvar, '^^', etiket, grtyp, ig1,          &
                  ig2, ig3, ig4, datyp, .true.)

    !- 2.2 2D Field
    allocate(Field2d(hco_ens%ni,hco_ens%nj))
    Field2d(:,:) = 10.d0

    deet      =  0
    ip1       =  0
    ip2       =  0
    ip3       =  0
    npas      =  0
    datyp     =  1
    grtyp     =  hco_ens%grtyp
    typvar    = 'A'
    etiket    = 'COREGRID'
    dateo  =  0
    ig1       =  hco_ens%ig1
    ig2       =  hco_ens%ig2
    ig3       =  hco_ens%ig3
    ig4       =  hco_ens%ig4

    ier = vfstecr(Field2d, work, npak,                                       &
                  iun, dateo, deet, npas, hco_ens%ni, hco_ens%nj, 1, ip1, &
                  ip2, ip3, typvar, 'P0', etiket, grtyp, ig1,                &
                  ig2, ig3, ig4, datyp, .true.)

    deallocate(Field2d)

    !
    !- 3.  Create and Write the extended grid (Analysis) template
    !
    ni_ext = hco_ens%ni + grd_ext_x
    nj_ext = hco_ens%nj + grd_ext_y

    !- 3.1 Tic-Tac
    allocate(lon_ext(ni_ext))
    allocate(lat_ext(nj_ext))

    !- Copy core grid info
    lon_ext(1:hco_ens%ni) = hco_ens%lon(:) 
    lat_ext(1:hco_ens%nj) = hco_ens%lat(:)

    !- Extend the lat lon
    dlon = hco_ens%lon(2) - hco_ens%lon(1) 
    do i = hco_ens%ni + 1, ni_ext
       lon_ext(i) = lon_ext(hco_ens%ni) + (i - hco_ens%ni) * dlon
    end do

    dlat = hco_ens%lat(2) - hco_ens%lat(1) 
    do j = hco_ens%nj + 1, nj_ext
       lat_ext(j) = lat_ext(hco_ens%nj) + (j - hco_ens%nj) * dlat
    end do

    !- Write
    deet     =  0
    ip1      =  hco_ens%ig1 + 100 ! Must be different from the core grid
    ip2      =  hco_ens%ig2 + 100 ! Must be different from the core grid
    ip3      =  0
    npas     =  0
    datyp    =  1
    grtyp    = 'E'
    typvar   = 'X'
    etiket   = 'ANALYSIS'
    dateo =  0
    ig1      =  ig1_tictac
    ig2      =  ig2_tictac
    ig3      =  ig3_tictac
    ig4      =  ig4_tictac

    ier = vfstecr(lon_ext*MPC_DEGREES_PER_RADIAN_R8, work, npak, &
                  iun, dateo, deet, npas, ni_ext, 1, 1, ip1,  &
                  ip2, ip3, typvar, '>>', etiket, grtyp, ig1,    &
                  ig2, ig3, ig4, datyp, .true.)

    ier = vfstecr(lat_ext*MPC_DEGREES_PER_RADIAN_R8, work, npak, &
                  iun, dateo, deet, npas, 1, nj_ext, 1, ip1,  &
                  ip2, ip3, typvar, '^^', etiket, grtyp, ig1,    &
                  ig2, ig3, ig4, datyp, .true.)

    deallocate(lon_ext)
    deallocate(lat_ext)

    !- 3.2 2D Field
    allocate(Field2d(ni_ext,nj_ext))
    Field2d(:,:) = 10.d0

    deet      =  0
    ip1       =  0
    ip2       =  0
    ip3       =  0
    npas      =  0
    datyp     =  1
    grtyp     =  hco_ens%grtyp
    typvar    = 'A'
    etiket    = 'ANALYSIS'
    dateo  =  0
    ig1       =  hco_ens%ig1 + 100 ! Must be different from the core grid
    ig2       =  hco_ens%ig2 + 100 ! Must be different from the core grid
    ig3       =  0
    ig4       =  0

    ier = vfstecr(Field2d, work, npak,                               &
                  iun, dateo, deet, npas, ni_ext, nj_ext, 1, ip1, &
                  ip2, ip3, typvar, 'P0', etiket, grtyp, ig1,        &
                  ig2, ig3, ig4, datyp, .true.)

    deallocate(Field2d)

    !
    !- 4.  Closing the output template file
    !
    ier = fstfrm(iun)
    ier = fclos (iun)

  end subroutine CreateLamTemplateGrids

!--------------------------------------------------------------------------
! VarLevInfo
!--------------------------------------------------------------------------

  subroutine VarLevInfo( nlev, GridType, VarName, infile ) 1,3
    implicit none

    character(len=4), intent(in)  :: VarName
    character(len=*), intent(in)  :: infile

    integer         , intent(out) :: nlev
    character(len=2), intent(out) :: GridType

    integer :: fstouv, fnom, fstfrm, fclos, iunens
    integer :: key, fstinf, fstinl, fstprm, infon
    integer, parameter :: nmax=2000
    integer :: liste(nmax)

    integer           :: ip1, ip2, ip3, ier
    integer           :: ni_t, nj_t, nlev_t, dateo, deet, npas, nbits, datyp
    integer           :: ig1, ig2, ig3, ig4, extra1, extra2, extra3, swa, lng, dltf, ubc
    character(len=4 ) :: nomvar
    character(len=2 ) :: typvar
    character(len=1 ) :: grtyp
    character(len=12) :: etiket

    !
    !- 1.  Open the input file
    !
    iunens = 0
    ier    = fnom(iunens,infile,'RND+OLD+R/O',0)
    ier    = fstouv(iunens,'RND+OLD')

    !
    !- 2.  Find the number of records
    !
    dateo  = -1
    etiket = ' '
    ip1    = -1
    ip2    = -1
    ip3    = -1
    typvar = ' '

    key = fstinl(iunens,                                       & ! IN
                ni_t, nj_t, nlev_t,                            & ! OUT
                dateo, etiket, ip1, ip2, ip3, typvar, VarName, & ! IN
                liste, infon,                                  & ! OUT
                nmax )                                           ! IN

    if (key < 0) then
      write(*,*)
      write(*,*) 'VarLevInfo: Cannot find variable ', VarName 
      call abort3d('VarLevInfo') 
    end if

    !
    !- 3.  Find de the Number of verical levels (ip1)
    !
    
    ! We assume that the number of levels = number of records (this could be improve...)
    nlev = infon 
    if ( nlev /= 1 .and. nlev /= vco_bhi%nlev_T .and. nlev /= vco_bhi%nlev_M ) then
       write(*,*)
       write(*,*) 'The number of levels found does not match with the vco structure !!!'
       write(*,*) 'nlev found = ', nlev
       write(*,*) 'vco nlevs  = ', vco_bhi%nlev_T, vco_bhi%nlev_M
       call abort3d('VarLevInfo')
    end if

    !
    !- 4.  Find the type of vertical grid
    !
    if ( nlev /=1 ) then
      ier = fstprm( liste(1),                                            & ! IN
                    dateo, deet, npas, ni_t, nj_t, nlev_t,               & ! OUT
                    nbits, datyp, ip1, ip2, ip3, typvar, nomvar, etiket, & ! OUT
                    grtyp, ig1, ig2, ig3, ig4, swa, lng, dltf, ubc,      & ! OUT
                    extra1, extra2, extra3 )                               ! OUT

      if      ( ANY(vco_bhi%ip1_M == ip1) ) then
         GridType = 'MM'
      else if ( ANY(vco_bhi%ip1_T == ip1) ) then
         GridType = 'TH'
      else
         write(*,*)
         write(*,*) 'The ip1 found does not match with the vco structure !!!', ip1
         call abort3d('VarLevInfo')
      end if

    else
       GridType = 'NS'
    end if

    !
    !- 5.  Close the input file
    !
    ier =  fstfrm(iunens)
    ier =  fclos (iunens)

  end subroutine VarLevInfo

!--------------------------------------------------------------------------
! ReadEnsemble
!--------------------------------------------------------------------------

  subroutine ReadEnsemble(ensPerturbations) 3,8
    implicit none

    real(4), intent(inout) :: ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens)

    real(4), allocatable :: work2d(:,:)

    real(4)   :: factor

    integer   :: ier, fstouv, fnom, fstfrm, fclos, fstecr, fstlir
    integer   :: iunens, ens, var, k, kgdim
    integer   :: ip1, ip2, ip3
    integer   :: ni_t, nj_t, nlev_t, dateo  

    character(len=4 )           :: nomvar
    character(len=1 )           :: typvar
    character(len=12)           :: etiket

    allocate(work2d(hco_ens%ni, hco_ens%nj))

    !- Loop over the Ensemble members
    do ens = 1, nens

      write(*,*)
      write(*,*) 'Reading ensemble member: ', ens, trim(cflensin(ens))

      iunens = 0
      ier    = fnom(iunens,cflensin(ens),'RND+OLD+R/O',0)
      ier    = fstouv(iunens,'RND+OLD')

      !- Loop over Control Variables
      do var = 1, nControlVariable

        !- Loop over vertical Levels
        do k = 1, ControlVariable(var)%nlev

          dateo  = -1
          ip1    = ControlVariable(var)%ip1(k)
          ip2    = -1
          ip3    = -1
          typvar = ' '
          nomvar = trim(ControlVariable(var)%nomvar(cv_model))
          etiket = ' '
          if ( trim(nomvar) == 'UU' .or. trim(nomvar) == 'VV') then
            factor = 1.0/1.94246 ! knots -> m/s
          else if ( trim(nomvar) == 'P0' ) then
            factor = 100.0 ! hPa -> Pa
          else if ( trim(nomvar) == 'TG' ) then
            nomvar = 'TT'
            ip1    = 93423264
            factor = 0.0  ! On Force a ZERO !!!!!!!! JFC
          else
            factor = 1.0
          end if

          !- Reading 
          ier = fstlir( work2d,                                      & ! OUT 
                        iunens,                                      & ! IN
                        ni_t, nj_t, nlev_t,                          & ! OUT
                        dateo, etiket, ip1, ip2, ip3, typvar, nomvar)  ! IN 

          if (ier < 0) then
            write(*,*)
            write(*,*) 'Read Ensemble: Cannot find field ...'
            write(*,*) 'nomvar =', trim(ControlVariable(var)%nomvar(cv_model))
            write(*,*) 'etiket =', trim(etiket)
            write(*,*) 'ip1    =', ControlVariable(var)%ip1(k)
            stop
          end if

          if (ni_t /= hco_ens%ni .or. nj_t /= hco_ens%nj) then
            write(*,*)
            write(*,*) 'ReadEnsemble: Invalid dimensions for ...'
            write(*,*) 'nomvar      =', trim(ControlVariable(var)%nomvar(cv_model))
            write(*,*) 'etiket      =', trim(etiket)
            write(*,*) 'ip1         =', ControlVariable(var)%ip1(k)
            write(*,*) 'Found ni,nj =', ni_t, nj_t 
            write(*,*) 'Should be   =', hco_ens%ni, hco_ens%nj
            stop
          end if

          !- Insert in EnsPerturbations
          kgdim = ControlVariable(var)%kDimStart + k - 1
          EnsPerturbations(1:hco_ens%ni, 1:hco_ens%nj,kgdim,ens) = factor * work2d(:,:) 

        end do ! Vertical Levels

      end do ! Variables
    
      ier =  fstfrm(iunens)
      ier =  fclos (iunens)

    end do ! Ensemble members

    deallocate(work2d)

  end subroutine ReadEnsemble

!--------------------------------------------------------------------------
! UVToCtrlVar
!--------------------------------------------------------------------------

  subroutine UVToCtrlVar(ensPerturbations) 1,2
    implicit none

    real(4), intent(inout) :: ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens)

    real(8), allocatable :: uwind_3d(:,:,:)
    real(8), allocatable :: vwind_3d(:,:,:)
    real(8), allocatable :: CtrlVar1_3d(:,:,:)
    real(8), allocatable :: CtrlVar2_3d(:,:,:)

    integer :: nlev, ens, kStart_u, kStart_v, kEnd_u, kEnd_v

    !- Error traps
    if ( ControlVariable(1)%nlev /= ControlVariable(2)%nlev ) then
      write(*,*)
      write(*,*) 'UVToCtrlVar: Error in Wind Field vertical grid !!!'
      stop
    end if
    if ( ControlVariable(1)%nomvar(cv_model) /= 'UU' .or. &
         ControlVariable(2)%nomvar(cv_model) /= 'VV' ) then
      write(*,*)
      write(*,*) 'UVToCtrlVar: Wind field(s) missing on input !!!'
      write(*,*) 'nomvar(1) = ', ControlVariable(1)%nomvar(cv_model)
      write(*,*) 'nomvar(2) = ', ControlVariable(2)%nomvar(cv_model)
      stop
    end if

    !- Allocation
    nlev = ControlVariable(1)%nlev
    allocate(uwind_3d   (hco_bhi%ni, hco_bhi%nj, nlev))
    allocate(vwind_3d   (hco_bhi%ni, hco_bhi%nj, nlev))
    allocate(CtrlVar1_3d(hco_bhi%ni, hco_bhi%nj, nlev))
    allocate(CtrlVar2_3d(hco_bhi%ni, hco_bhi%nj, nlev))

    !- Loop over all ensemble members
    do ens   = 1, nens

      !- Wind position in EnsPerturbations
      kStart_u = ControlVariable(1)%kDimStart
      kStart_v = ControlVariable(2)%kDimStart
      kEnd_u   = ControlVariable(1)%kDimEnd
      kEnd_v   = ControlVariable(2)%kDimEnd

      !- Extract from EnsPerturbations
      uwind_3d = real(ensPerturbations(:,:,kStart_u:kEnd_u,ens),8)
      vwind_3d = real(ensPerturbations(:,:,kStart_v:kEnd_v,ens),8)

      !- U-wind,V-wind -> Vorticity,Divergence
      call lag_UVToVortDiv(CtrlVar1_3d, CtrlVar2_3d, & ! OUT
                           uwind_3d   , vwind_3d   , & ! IN
                           nlev                      ) ! IN

      if (trim(WindTransform) == 'PsiChi') then
        !- Vorticity,Divergence -> Stream Function,Velocity Potential
        call VortDivToPsiChi(CtrlVar1_3d, CtrlVar2_3d, & ! INOUT
                             nlev)                       ! IN
      end if

      !- Insert results in EnsPerturbations
      ensPerturbations(:,:,kStart_u:kEnd_u,ens) = real(CtrlVar1_3d(:,:,:),4)
      ensPerturbations(:,:,kStart_v:kEnd_v,ens) = real(CtrlVar2_3d(:,:,:),4)

    end do

    deallocate(uwind_3d   )
    deallocate(vwind_3d   )
    deallocate(CtrlVar1_3d)
    deallocate(CtrlVar2_3d)

  end subroutine UVToCtrlVar

!--------------------------------------------------------------------------
! VortDivToPsiChi
!--------------------------------------------------------------------------

  subroutine VortDivToPsiChi(VortPsi,DivChi,nk) 1,2
    implicit none

    integer, intent(in)    :: nk
    real(8), intent(inout) :: VortPsi(hco_bhi%ni, hco_bhi%nj,nk)
    real(8), intent(inout) :: DivChi (hco_bhi%ni, hco_bhi%nj,nk)

    call lst_Laplacian( lst_bhi%id,   & ! IN
                        VortPsi,        & ! INOUT
                        'Inverse', nk )   ! IN    

    call lst_Laplacian( lst_bhi%id,   & ! IN
                        DivChi,         & ! INOUT
                        'Inverse', nk )   ! IN

  end subroutine VortDivToPsiChi

!--------------------------------------------------------------------------
! RemoveMean
!--------------------------------------------------------------------------

  subroutine RemoveMean(ensPerturbations,ensMean3d) 3
    implicit none

    real(4), intent(inout)  :: ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens)
    real(8), intent(out)    :: ensMean3d(hco_bhi%ni,hco_bhi%nj,nkgdim)

    real(8) :: inens
    integer :: i,j,kgdim,ens

    inens=1.0d0/real(nens,8)

    ensMean3d(:,:,:) = 0.0d0

!$OMP PARALLEL
!$OMP DO PRIVATE (kgdim,ens,j,i)
    do kgdim = 1, nkgdim

      !- Sum
      do ens = 1, nens
        do j = 1 , hco_bhi%nj
          do i = 1, hco_bhi%ni
            ensMean3d(i,j,kgdim) = ensMean3d(i,j,kgdim) + real(ensPerturbations(i,j,kgdim,ens),8)
          end do
        end do
      end do

      !- Mean
      do j = 1, hco_bhi%nj
        do i = 1, hco_bhi%ni
          ensMean3d(i,j,kgdim) = ensMean3d(i,j,kgdim) * inens
        end do
      end do

      !- Remove Mean
      do ens = 1, nens
        do j = 1, hco_bhi%nj
          do i = 1, hco_bhi%ni
            ensPerturbations(i,j,kgdim,ens) = ensPerturbations(i,j,kgdim,ens) - &
                                                real(ensMean3d(i,j,kgdim),4)
          end do
        end do
      end do

    end do
!$OMP END DO
!$OMP END PARALLEL

    write(*,*)
    write(*,*) 'finished removing the ensemble mean...'

  end subroutine removeMean

!--------------------------------------------------------------------------
! CalcStdDev3d
!--------------------------------------------------------------------------

  subroutine CalcStdDev3d(ensPerturbations,StdDev3d,Bin2d,NumBins2d) 6
    implicit none

    real(4), intent(inout)  :: ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens)
    real(8), intent(out)    :: StdDev3d(hco_bhi%ni,hco_bhi%nj,nkgdim)
    integer, intent(in)     :: Bin2d(hco_bhi%ni,hco_bhi%nj)
    integer, intent(in)     :: NumBins2d

    integer :: BinCount (NumBins2d)
    real(8) :: StdDevBin(NumBins2d)

    real(8) :: inens
    integer :: i,j,kgdim,ens,b

!$OMP PARALLEL
!$OMP DO PRIVATE (kgdim,BinCount,StdDevBin,b,ens,j,i)
    do kgdim = 1, nkgdim

      !- Sum of Squares per bin
      BinCount (:) = 0
      StdDevBin(:) = 0.d0
      do ens = 1, nens
        do j = 1, hco_bhi%nj
          do i = 1, hco_bhi%ni
            b = Bin2d(i,j)
            BinCount(b)  = BinCount(b) + 1
            StdDevBin(b) = StdDevBin(b) + real(ensPerturbations(i,j,kgdim,ens),8)**2
          end do
        end do
      end do

      !- Convert to Standard Deviation
      StdDevBin(:) = sqrt( StdDevBin(:) / real((BinCount(:)-1),8) )

      !- Distribute bin values at each grid point
      do j = 1, hco_bhi%nj
        do i = 1, hco_bhi%ni
          b = Bin2d(i,j)
          StdDev3d(i,j,kgdim) = StdDevBin(b)
        end do
      end do

    end do
!$OMP END DO
!$OMP END PARALLEL

    write(*,*)
    write(*,*) 'finished computing Standard Deviations...'

  end subroutine CalcStdDev3d

!--------------------------------------------------------------------------
! Normalize3d
!--------------------------------------------------------------------------

  subroutine normalize3d(ensPerturbations,StdDev3d) 3
    implicit none

    real(4), intent(inout) :: ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens)
    real(8), intent(in)    :: StdDev3d(hco_bhi%ni,hco_bhi%nj,nkgdim)

    integer :: ens, kgdim, j, i

    real(4) :: fact

!$OMP PARALLEL
!$OMP DO PRIVATE (ens, kgdim, j, i)
    do kgdim = 1, nkgdim
      do j = 1, hco_bhi%nj
         do i = 1, hco_bhi%ni
           if (StdDev3d(i,j,kgdim) > 0.0d0 ) then
             fact = real(1.0d0/StdDev3d(i,j,kgdim),4)
           else
             fact = 0.0
           endif
           do ens = 1, nens
              ensPerturbations(i,j,kgdim,ens) = ensPerturbations(i,j,kgdim,ens) &
                                                * fact
           end do
         end do
       end do
     end do
!$OMP END DO
!$OMP END PARALLEL

    write(*,*)
    write(*,*) 'finished normalizing by stddev3D...'
  
  end subroutine normalize3d

!--------------------------------------------------------------------------
! CalcSpectralStats
!--------------------------------------------------------------------------

  subroutine CalcSpectralStats(ensPerturbations,SpVertCorrel,PowerSpectrum, & 1,4
                               NormB)
    implicit none

    real(4), intent(in)     :: ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens)
    real(8), intent(out)    :: SpVertCorrel(nkgdim,nkgdim,0:ntrunc)
    real(8), intent(out)    :: PowerSpectrum(nkgdim,0:ntrunc)
    real(8), intent(out)    :: NormB(nkgdim,nkgdim,0:ntrunc)

    real(8), allocatable    :: NormPowerSpectrum(:,:)
    real(8), allocatable    :: SpectralStateVar(:,:,:)
    real(8), allocatable    :: GridState(:,:,:)
    real(8), allocatable    :: SumWeight(:)

    real(8)           :: fact, sum

    integer           :: i, j, k1, k2, ens, b, e, ila, p, k, totwvnb

    character(len=24) :: kind

    SpVertCorrel(:,:,:) = 0.d0

    !
    !- 1.  Calculate the Vertical Covariances in Spectral Space
    !
    allocate( SpectralStateVar(lst_bhi%nla,lst_bhi%nphase,nkgdim) )
    allocate( GridState(hco_bhi%ni, hco_bhi%nj, nkgdim) )

    allocate(SumWeight(0:ntrunc))
    SumWeight(:) = 0.d0

    do ens = 1, nens

      !- 1.1 Extract fields from ensPerturbations
      GridState(:,:,:) = real(ensPerturbations(:,:,:,ens),8)

      !- 1.2 Grid Point Space -> Spectral Space
      kind = 'GridPointToSpectral'
      call lst_VarTransform( lst_bhi%id,          & ! IN
                             SpectralStateVar,      & ! OUT
                             GridState,             & ! IN
                             kind, nkgdim     )       ! IN

      !- 1.3 Compute the covariances
!$OMP PARALLEL
!$OMP DO PRIVATE (totwvnb,fact,e,ila,p,k2,k1)
      do totwvnb = 0, ntrunc
        do e = 1, lst_bhi%nePerK(totwvnb)
          ila = lst_bhi%ilaFromEK(e,totwvnb)
          do p = 1, lst_bhi%nphase
            SumWeight(totwvnb) = SumWeight(totwvnb) + lst_bhi%Weight(ila)
            do k2 = 1, nkgdim
              do k1 = 1, nkgdim
                SpVertCorrel(k1,k2,totwvnb) = SpVertCorrel(k1,k2,totwvnb) &
                    + lst_bhi%Weight(ila) * (SpectralStateVar(ila,p,k1) * SpectralStateVar(ila,p,k2))
              end do
            end do
          end do
        end do
      end do
!$OMP END DO
!$OMP END PARALLEL

    end do ! Loop in Ensemble

    !- 1.4 Compute the weighted COVARIANCES for each total wavenumber
    do totwvnb = 0, ntrunc
      if ( SumWeight(totwvnb) /= 0.d0 ) then 
        SpVertCorrel(:,:,totwvnb) = SpVertCorrel(:,:,totwvnb) / SumWeight(totwvnb)
      else
        SpVertCorrel(:,:,totwvnb) = 0.d0
      end if
    end do

    deallocate(SumWeight)

    !- 1.5 Extract the power spectrum (the variances on the diagonal elements)
    do k = 1, nkgdim
      PowerSpectrum(k,:) = SpVertCorrel(k,k,:)
    end do

    !
    !- 2.  Calculate the Vertical Correlations in Spectral Space
    !
!$OMP PARALLEL
!$OMP DO PRIVATE (totwvnb,k2,k1)
    do totwvnb = 0, ntrunc
      do k2 = 1, nkgdim
        do k1 = 1, nkgdim 
          if ( PowerSpectrum(k1,totwvnb) /= 0.d0 .and. &
               PowerSpectrum(k2,totwvnb) /= 0.d0 ) then
            SpVertCorrel(k1,k2,totwvnb) = SpVertCorrel(k1,k2,totwvnb) / &
               sqrt( PowerSpectrum(k1,totwvnb) * PowerSpectrum(k2,totwvnb) )
          else
            SpVertCorrel(k1,k2,totwvnb) = 0.d0
          end if
        end do
      end do
    end do
!$OMP END DO
!$OMP END PARALLEL

    !
    !- 3.  Normalize the power spectrum (i.e. build normalised spectral densities of the variance)
    !
    allocate(NormPowerSpectrum(nkgdim,0:ntrunc))

    !- 3.1 Part 1

!$OMP PARALLEL
!$OMP DO PRIVATE (totwvnb,k,sum)
    do k = 1, nkgdim
      sum = 0.0d0
      do totwvnb = 0, ntrunc
        sum = sum + real(totwvnb,8) * PowerSpectrum(k,totwvnb)
      end do
      do totwvnb = 0, ntrunc
        if ( sum /= 0.0d0 ) then
          NormPowerSpectrum(k,totwvnb) = PowerSpectrum(k,totwvnb) / sum
        else
          NormPowerSpectrum(k,totwvnb) = 0.d0
        end if
      end do
    end do
!$OMP END DO
!$OMP END PARALLEL

    !- 3.2 Part 2
    
    !- 3.2.1 Spectral transform of a delta function (at the center of the domain)
    GridState(:,:,:) = 0.d0
    GridState(hco_bhi%ni/2,hco_bhi%nj/2,:) = 1.d0

    kind = 'GridPointToSpectral'
    call lst_VarTransform( lst_bhi%id,          & ! IN
                           SpectralStateVar,      & ! OUT
                           GridState,             & ! IN
                           kind, nkgdim     )       ! IN

    !- 3.2.2 Apply the horizontal correlation function
!$OMP PARALLEL
!$OMP DO PRIVATE (totwvnb,e,ila,p,k)
    do totwvnb = 0, ntrunc
       do e = 1, lst_bhi%nePerK(totwvnb)
          ila = lst_bhi%ilaFromEK(e,totwvnb)
          do p = 1, lst_bhi%nphase
             do k = 1, nkgdim
                SpectralStateVar(ila,p,k) = SpectralStateVar(ila,p,k) * NormPowerSpectrum(k,totwvnb) * &
                              lst_bhi%NormFactor(ila,p) * lst_bhi%NormFactorAd(ila,p)
             end do
          end do
       end do
    end do
!$OMP END DO
!$OMP END PARALLEL

    !- 3.2.3 Move back to physical space
    kind = 'SpectralToGridPoint'
    call lst_VarTransform( lst_bhi%id,      & ! IN
                           SpectralStateVar,  & ! IN
                           GridState,         & ! OUT
                           kind, nkgdim )       ! IN

    !- 3.2.4 Normalize to 1
    do k = 1, nkgdim
       if ( GridState(hco_bhi%ni/2,hco_bhi%nj/2,k) < 0.d0 ) then
          write(*,*) 'CalcSpectralStats: Problem in normalization ', k, GridState(hco_bhi%ni/2,hco_bhi%nj/2,k)
          call abort3d('aborting in CalcSpectralStats')
       end if

       if ( GridState(hco_bhi%ni/2,hco_bhi%nj/2,k) /= 0.d0 ) then
          write(*,*) 'CalcSpectralStats: Normalization factor = ', k, GridState(hco_bhi%ni/2,hco_bhi%nj/2,k), 1.d0 / GridState(hco_bhi%ni/2,hco_bhi%nj/2,k)
          NormPowerSpectrum(k,:) = NormPowerSpectrum(k,:) / GridState(hco_bhi%ni/2,hco_bhi%nj/2,k)
       else
          write(*,*) 'CalcSpectralStats: Setting NormPowerSpectrum to zero = ', k
          NormPowerSpectrum(k,:) = 0.d0
       end if
    end do

    deallocate(SpectralStateVar)
    deallocate(GridState)

    !
    !- 4.  Normalize the spectral vertical correlation matrix to ensure correlations in horizontal
    !

!$OMP PARALLEL
!$OMP DO PRIVATE (totwvnb,k2,k1)
    do totwvnb = 0, ntrunc
      do k2 = 1, nkgdim
        do k1 = 1, nkgdim
          NormB(k1,k2,totwvnb) = SpVertCorrel(k1,k2,totwvnb) * &
                sqrt( NormPowerSpectrum(k1,totwvnb) * NormPowerSpectrum(k2,totwvnb) )
        end do
      end do
    end do
!$OMP END DO
!$OMP END PARALLEL

    deallocate(NormPowerSpectrum)

    write(*,*)
    write(*,*) 'Calculating spectral covariance stats. Done!'

  end subroutine CalcSpectralStats

!--------------------------------------------------------------------------
! CalcHorizScale
!--------------------------------------------------------------------------

  subroutine CalcHorizScale(HorizScale,SpCovariance) 1,2
    use MathPhysConstants_mod, only: MPC_PI_R8
    use EarthConstants_mod, only: RA
    implicit none

    real(8), intent(out) :: HorizScale(nkgdim)
    real(8), intent(in)  :: SpCovariance(nkgdim,nkgdim,0:ntrunc)

    real(8) :: circ_eq, cur_circ_eq, un_deg_lon, dx, dist
    real(8) :: a, b, beta

    integer :: totwvnb, k, var

    write(*,*)
    write(*,*) 'Calculating horizontal correlation length scale'

    !
    !- Computing distance-related variables
    !

    ! Grid spacing in meters
    dx = hco_bhi%dlon * RA
    write(*,*)
    write(*,*) 'grid spacing (m) =', dx

    dist = max(hco_bhi%ni, hco_bhi %nj) * dx
    beta = (dist/(2.d0*MPC_PI_R8))**2

    !
    !- Estimate horizontal correlation scales based on the power spectra
    !
    do k = 1, nkgdim
      a = 0.d0
      b = 0.d0
      do totwvnb = 0, ntrunc
        a = a + SpCovariance(k,k,totwvnb) * totwvnb
        b = b + SpCovariance(k,k,totwvnb) * totwvnb**3
      end do
      if (b <= 0.d0) then 
        HorizScale(k) = 0.d0
      else
        HorizScale(k) = sqrt(2.d0*a*beta/b)
      end if
    end do

    do var = 1, nControlVariable
      write(*,*)
      write(*,*) ControlVariable(var)%nomvar(cv_bhi)
      do k = ControlVariable(var)%kDimStart, ControlVariable(var)%kDimEnd
        write(*,'(i3,2X,f9.2,2X,a2)') k, HorizScale(k)/1000.d0, 'km'
      end do
    end do

    write(*,*)
    write(*,*) 'Calculating horizontal correlation length scale. Done!'

  end subroutine CalcHorizScale

!--------------------------------------------------------------------------
! CalcTotVertCorrel
!--------------------------------------------------------------------------

  subroutine CalcTotVertCorrel(TotVertCorrel, NormB) 1
    implicit none

    real(8), intent(out)    :: TotVertCorrel(nkgdim,nkgdim)
    real(8), intent(in)     :: NormB(nkgdim,nkgdim,0:ntrunc)

    real(8), allocatable    :: TotVertCov(:,:)
    real(8), allocatable    :: SumWeight(:)

    real(8)           :: fact

    integer           :: k1, k2, totwvnb, e, p, ila

    !
    !- 1.  Calculate the total Normalized Covariance Matrix
    !
    allocate(TotVertCov(nkgdim,nkgdim))
    allocate(SumWeight(0:ntrunc))
    TotVertCov(:,:) = 0.d0
    SumWeight(:) = 0.d0

    !- 1.1 Compute Weights
    do totwvnb = 0, ntrunc
      do e = 1, lst_bhi%nePerK(totwvnb)
        ila = lst_bhi%ilaFromEK(e,totwvnb)
        do p = 1, lst_bhi%nphase
          SumWeight(totwvnb) = SumWeight(totwvnb) + lst_bhi%Weight(ila)
        end do
      end do
    end do

    !- 1.2 Compute Weighted Total Covariance matrix 
    do k2 = 1, nkgdim
      do k1 = 1, nkgdim
        do totwvnb = 0, ntrunc
          TotVertCov(k1,k2) = TotVertCov(k1,k2) + &
                              SumWeight(totwvnb) * real(totwvnb,8) * NormB(k1,k2,totwvnb) *    &
                              NormB(k1,k1,totwvnb) * NormB(k2,k2,totwvnb)
        end do
      end do
    end do

    deallocate(SumWeight)

    !
    !- 2.  Transform into correlations
    !
    do k2 = 1, nkgdim
      do k1 = 1, nkgdim
        if ( TotVertCov(k1,k1) /= 0.d0 .and. &
             TotVertCov(k2,k2) /= 0.d0 ) then
          TotVertCorrel(k1,k2) = TotVertCov(k1,k2) / &
               ( sqrt(TotVertCov(k1,k1)) * sqrt(TotVertCov(k2,k2)) )
        else
          TotVertCorrel(k1,k2) = 0.d0
        end if
      end do
    end do

    deallocate(TotVertCov)

  end subroutine CalcTotVertCorrel

!--------------------------------------------------------------------------
! CalcBsqrt
!--------------------------------------------------------------------------

  subroutine CalcBsqrt(Bsqrt,B) 1
    implicit none
    !
    !  - produce matrix B^0.5 = V D^0.5 D^t where V and D are the
    !    eigenvectors and eigenvalues of B
    !
    real(8), intent(out)   :: Bsqrt(nkgdim,nkgdim,0:ntrunc)
    real(8), intent(in)    :: B    (nkgdim,nkgdim,0:ntrunc)

    real(8), allocatable :: EigenValues(:)
    real(8), allocatable :: Work(:)

    real(8), allocatable :: EigenVectors(:,:)

    integer :: sizework, info, totwvnb, k, k1, k2 

    sizework = 64 * nkgdim
    allocate(work(sizework))

    allocate(EigenValues(nkgdim))
    allocate(EigenVectors(nkgdim,nkgdim))

    !
    !-  Calculate B^0.5 for each total wave number
    !
    do totwvnb = 0, ntrunc

       EigenVectors(:,:) = B(:,:,totwvnb)

       !- Calculate EigenVectors (V) and EigenValues (D) of B matrix
       call dsyev('V','U',nkgdim,  & ! IN
                   EigenVectors,   & ! INOUT
                   nkgdim,         & ! IN
                   EigenValues,    & ! OUT
                   work, sizework, & ! IN
                   info )            ! OUT
       
       if ( info /= 0 ) then
         write(*,*)
         write(*,*) 'CalcBsqrt: DSYEV failed !!! ', totwvnb, info
         stop
       end if

       !- Calculate B^0.5 = V D^0.5 V^t
       where(EigenValues < 0.d0)
         EigenValues = 0.d0
       end where

       do k1 = 1, nkgdim
         do k2 = 1, nkgdim
           Bsqrt(k1,k2,totwvnb) = sum ( EigenVectors (k1,1:nkgdim)   &
                                   *    EigenVectors (k2,1:nkgdim)   &
                                   *    sqrt(EigenValues(1:nkgdim)) )
         end do
       end do

    end do  ! total wave number

    deallocate(EigenVectors)
    deallocate(EigenValues)
    deallocate(work)

  end subroutine CalcBsqrt

!--------------------------------------------------------------------------
! SetSpVertCorrel
!--------------------------------------------------------------------------

  subroutine SetSpVertCorrel(SpVertCorrel) 1
    implicit none

    real(8), intent(inout) :: SpVertCorrel(nkgdim,nkgdim,0:ntrunc)

    real(8), allocatable :: KeepOrDiscard(:,:)

    integer :: totwvnb, var1, var2, k1, k2 

    write(*,*)
    write(*,*) 'Setting Vertical Correlations'

    !
    !-  Determine which bloc of the correlation matrix to keep/discard
    !
    allocate(KeepOrDiscard(nControlVariable,nControlVariable))

    !- Calculate upper half
    do var2 = 1, nControlVariable
      do var1 = 1, var2
         if (var1 == var2) then
           KeepOrDiscard(var1,var2) = 1.d0 ! Keep Auto-Correlations
         elseif( (ControlVariable(var2)%nomvar(cv_bhi) == 'CC' .and. &
                  ControlVariable(var1)%nomvar(cv_bhi) == 'PP') .or. &
                 (ControlVariable(var2)%nomvar(cv_bhi) == 'TT' .and. &
                  ControlVariable(var1)%nomvar(cv_bhi) == 'PP') .or. &
                 (ControlVariable(var2)%nomvar(cv_bhi) == 'P0' .and. &
                  ControlVariable(var1)%nomvar(cv_bhi) == 'PP') .or. &
                 (ControlVariable(var2)%nomvar(cv_bhi) == 'P0' .and. &
                  ControlVariable(var1)%nomvar(cv_bhi) == 'TT') ) then
           KeepOrDiscard(var1,var2) = 1.d0 ! Keep these Cross-Correlations
         else
           KeepOrDiscard(var1,var2) = 0.d0 ! Discard these Cross-Correlation
         end if
         write(*,*) var1, var2, ControlVariable(var1)%nomvar(cv_bhi), ControlVariable(var2)%nomvar(cv_bhi), KeepOrDiscard(var1,var2) 
      end do
    end do

    ! Symmetrize
    do var1 = 2, nControlVariable
      do var2 = 1, var1-1
        KeepOrDiscard(var1,var2) = KeepOrDiscard(var2,var1)
      end do
    end do

    !
    !- Modify the Vertical Correlation Matrix
    !
    do totwvnb = 0, ntrunc

      do var2 = 1, nControlVariable
        do var1 = 1, nControlVariable

          do k2 = ControlVariable(var2)%kDimStart, ControlVariable(var2)%kDimEnd
            do k1 = ControlVariable(var1)%kDimStart, ControlVariable(var1)%kDimEnd
              SpVertCorrel(k1,k2,totwvnb) = KeepOrDiscard(var1,var2) * &
                                            SpVertCorrel(k1,k2,totwvnb)
            end do
          end do

        end do
      end do

    end do  ! total wave number

    deallocate(KeepOrDiscard)

    write(*,*)
    write(*,*) 'Setting Vertical Correlations. Done!'

  end subroutine SetSpVertCorrel

!--------------------------------------------------------------------------
! CreateBins
!--------------------------------------------------------------------------

  subroutine CreateBins(Bin2d,NumBins2d,BinningStrategy) 2
    implicit none

    integer, intent(out)     :: Bin2d(hco_bhi%ni,hco_bhi%nj)
    integer, intent(out)     :: NumBins2d
    character(*), intent(in) :: BinningStrategy

    real(8) :: BinCat
    integer :: i,j,kgdim,ens,b

    select case(trim(BinningStrategy))
    case ('GridPoint')
      write(*,*) ' BIN_TYPE : No horizontal averaging '
      write(*,*) '          = One bin per horizontal grid point'

      NumBins2d = hco_bhi%ni * hco_bhi%nj
      BinCat    = 0
      do j = 1, hco_bhi%nj
        do i = 1, hco_bhi%ni
          BinCat = BinCat + 1
          bin2d(i,j) = BinCat
        end do
      end do

    case ('YrowBand')
      write(*,*)
      write(*,*) ' BIN_TYPE : One bin per Y row'

      NumBins2d = hco_bhi%nj
      BinCat    = 0
      do j = 1, hco_bhi%nj
        BinCat = BinCat + 1
        bin2d(:,j) = BinCat
      end do

    case ('HorizontalMean')
      write(*,*)
      write(*,*) ' BIN_TYPE : Average over all horizontal points'

      NumBins2d   = 1
      bin2d(:, :) = 1

    case default
      write(*,*)
      write(*,*) 'Invalid Binning Strategy : ',trim(BinningStrategy)
      stop
    end select

  end subroutine CreateBins

!--------------------------------------------------------------------------
! BiPeriodization
!--------------------------------------------------------------------------

  subroutine BiPeriodization(ensPerturbations) 1,1
    implicit none

    real(4), intent(inout) :: ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens)

    real(8), allocatable :: work3d(:,:,:)

    integer :: kgdim, ens

    allocate(work3d(hco_bhi%ni, hco_bhi%nj, nkgdim))

    !- Loop over all variables / levels / ensemble members
    do ens   = 1, nens

        work3d(:,:,:) = real(ensPerturbations(:,:,:,ens),8)

        call lag_mach(work3d,                             & ! INOUT
                      hco_bhi%ni, hco_bhi%nj, nkgdim)   ! IN

        ensPerturbations(:,:,:,ens) = real(work3d(:,:,:),4)

    end do

    deallocate(work3d)

  end subroutine BiPeriodization

!--------------------------------------------------------------------------
! WriteVarStats
!--------------------------------------------------------------------------

  subroutine WriteVarStats(Bsqrt,StdDev3d) 1,4
    implicit none

    real(8), intent(in) :: Bsqrt(nkgdim,nkgdim,0:ntrunc)
    real(8), intent(in) :: StdDev3d(hco_bhi%ni,hco_bhi%nj,nkgdim)

    integer   :: ier, fstouv, fnom, fstfrm, fclos
    integer   :: iunstats

    write(*,*)
    write(*,*) 'Writing covariance statistics for VAR'

    !
    !- Opening Output file
    !
    iunstats = 0
    ier    = fnom(iunstats,'./bgcov.fst','RND',0)
    ier    = fstouv(iunstats,'RND')

    !
    !- Add Tic-Tac and Toc-Toc
    !
    call WriteTicTacToc(iunstats)

    !
    !- Add Control Variable Info
    !
    call WriteControlVarInfo(iunstats)

    !
    !- Bsqrt
    !
    call WriteSpVertCorrel(Bsqrt,iunstats,'ZN','B_SQUAREROOT') ! IN
 
    !
    !- 3D Standard Deviations
    !
    call Write3d(StdDev3d,iunstats,'STDDEV',cv_bhi) ! IN

    !
    !- Closing output file
    !
    ier =  fstfrm(iunstats)
    ier =  fclos (iunstats)

  end subroutine WriteVarStats

!--------------------------------------------------------------------------
! WriteDiagStats
!--------------------------------------------------------------------------

  subroutine WriteDiagStats(NormB,SpVertCorrel,TotVertCorrel,EnsMean3d, & 1,8
                            StdDev3dGridPoint,PowerSpectrum,HorizScale)
    implicit none

    real(8), intent(in) :: NormB(nkgdim,nkgdim,0:ntrunc)
    real(8), intent(in) :: SpVertCorrel(nkgdim,nkgdim,0:ntrunc)
    real(8), intent(in) :: TotVertCorrel(nkgdim,nkgdim)
    real(8), intent(in) :: EnsMean3d(hco_bhi%ni,hco_bhi%nj,nkgdim)
    real(8), intent(in) :: StdDev3dGridPoint(hco_bhi%ni,hco_bhi%nj,nkgdim)
    real(8), intent(in) :: PowerSpectrum(nkgdim,0:ntrunc)
    real(8), intent(in) :: HorizScale(nkgdim)

    integer   :: ier, fstouv, fnom, fstfrm, fclos
    integer   :: iunstats

    write(*,*)
    write(*,*) 'Writing Diagnostics'

    !
    !- Opening Output file
    !
    iunstats = 0
    ier    = fnom(iunstats,'./bgcov_diag.fst','RND',0)
    ier    = fstouv(iunstats,'RND')

    !
    !- Add Tic-Tac and Toc-Toc
    !
    call WriteTicTacToc(iunstats)

    !
    !- Spectral Vertical correlations
    !

    !- Spectral Vertical Correlations
    call WriteSpVertCorrel(SpVertCorrel,iunstats,'ZZ','SPVERTCORREL') ! IN
 
    !- Total Vertical Correlations
    call WriteTotVertCorrel(TotVertCorrel,iunstats,'ZT','TTVERTCORREL') ! IN

    !- Normalized Vertical Correlations
    call WriteSpVertCorrel(NormB,iunstats,'ZN','NRVERTCORREL') ! IN

    !
    !- 3D Grid Point Ensemble Mean
    !
    call Write3d(EnsMean3d,iunstats,'ENSMEAN',cv_bhi) ! IN

    !
    !- 3D Grid Point Standard Deviation
    !
    call Write3d(StdDev3dGridPoint,iunstats,'STDDEV_GRIDP',cv_bhi) ! IN

    !
    !- Power Spectrum
    !
    call WritePowerSpectrum(PowerSpectrum,iunstats,'POWERSPECT',cv_bhi) ! IN

    !
    !- Horizontal Correlation Length scale
    !
    call WriteHorizScale(HorizScale,iunstats,'HORIZSCALE',cv_bhi) ! IN

    !
    !- Closing output file
    !
    ier =  fstfrm(iunstats)
    ier =  fclos (iunstats)

  end subroutine WriteDiagStats

!--------------------------------------------------------------------------
! WriteSpVertCorrel
!--------------------------------------------------------------------------

  subroutine WriteSpVertCorrel(SpVertCorrel,iun,nomvar_in,etiket_in) 3
    implicit none

    real(8), intent(in) :: SpVertCorrel(nkgdim,nkgdim,0:ntrunc)
    integer, intent(in) :: iun
    character(len=*), intent(in) :: nomvar_in
    character(len=*), intent(in) :: etiket_in

    real(4), allocatable :: work2d(:,:)

    real(4)   :: work

    integer   :: ier, fstecr
    integer   :: k, kgdim, totwvnb

    integer :: dateo, npak, ni, nj, nk
    integer :: ip1, ip2, ip3, deet, npas, datyp
    integer :: ig1 ,ig2 ,ig3 ,ig4

    character(len=1)  :: grtyp
    character(len=4)  :: nomvar
    character(len=2)  :: typvar
    character(len=12) :: etiket

    allocate(work2d(nkgdim, nkgdim))

    !- Loop over Total Wavenumbers
    do totwvnb = 0, ntrunc

      npak   = -32
      dateo  = 0
      deet   = 0
      npas   = 0
      ni     = nkgdim
      nj     = nkgdim
      nk     = 1
      ip1    = 0
      ip2    = totwvnb
      ip3    = nens
      typvar = 'XX'
      nomvar = nomvar_in
      etiket = etiket_in
      grtyp  = 'X'
      ig1    = 0
      ig2    = 0
      ig3    = 0
      ig4    = 0
      datyp  = 5

      !- Extract from full Matrix
      work2d(:,:) = real(SpVertCorrel(:,:,totwvnb),4)

      !- Writing 
      ier = fstecr(work2d, work, npak, iun, dateo, deet, npas, ni, nj, &
                   nk, ip1, ip2, ip3, typvar, nomvar, etiket, grtyp,        &
                   ig1, ig2, ig3, ig4, datyp, .true.)

    end do ! Total Wavenumbers

    deallocate(work2d)

  end subroutine WriteSpVertCorrel

!--------------------------------------------------------------------------
! WriteSpVertCorrel
!--------------------------------------------------------------------------

  subroutine WriteTotVertCorrel(TotVertCorrel,iun,nomvar_in,etiket_in) 1
    implicit none

    real(8), intent(in) :: TotVertCorrel(nkgdim,nkgdim)
    integer, intent(in) :: iun
    character(len=*), intent(in) :: nomvar_in
    character(len=*), intent(in) :: etiket_in

    real(4), allocatable :: workecr(:,:)

    real(4)   :: work

    integer   :: ier, fstecr

    integer :: dateo, npak, ni, nj, nk
    integer :: ip1, ip2, ip3, deet, npas, datyp
    integer :: ig1 ,ig2 ,ig3 ,ig4

    character(len=1)  :: grtyp
    character(len=4)  :: nomvar
    character(len=2)  :: typvar
    character(len=12) :: etiket

    allocate(workecr(nkgdim, nkgdim))

    npak   = -32
    dateo  = 0
    deet   = 0
    npas   = 0
    ni     = nkgdim
    nj     = nkgdim
    nk     = 1
    ip1    = 0
    ip2    = 0
    ip3    = nens
    typvar = 'XX'
    nomvar = nomvar_in
    etiket = etiket_in
    grtyp  = 'X'
    ig1    = 0
    ig2    = 0
    ig3    = 0
    ig4    = 0
    datyp  = 5

    !- Covert to real 4
    workecr(:,:) = real(TotVertCorrel(:,:),4)

    !- Writing 
    ier = fstecr(workecr, work, npak, iun, dateo, deet, npas, ni, nj, &
                 nk, ip1, ip2, ip3, typvar, nomvar, etiket, grtyp,        &
                 ig1, ig2, ig3, ig4, datyp, .true.)

    deallocate(workecr)

  end subroutine WriteTotVertCorrel

!--------------------------------------------------------------------------
! WriteEnsemble
!--------------------------------------------------------------------------

  subroutine WriteEnsemble(ensPerturbations, OutputBaseName, cv_type)
    implicit none

    real(4), intent(in) :: ensPerturbations(hco_bhi%ni,hco_bhi%nj,nkgdim,nens)
    integer, intent(in) :: cv_type
    character(len=*), intent(in) :: OutputBaseName

    real(4), allocatable :: work2d(:,:)

    real(4)   :: factor, work

    integer   :: ier, fstouv, fnom, fstfrm, fclos, fstecr
    integer   :: iunens, ens, var, k, kgdim

    integer :: dateo, npak, ni, nj, nk
    integer :: ip1, ip2, ip3, deet, npas, datyp
    integer :: ig1 ,ig2 ,ig3 ,ig4

    character(len=1)  :: grtyp
    character(len=4)  :: nomvar
    character(len=2)  :: typvar
    character(len=12) :: etiket
    character(len=128):: OutputFileName
    character(len=3)  :: cens

    allocate(work2d(hco_bhi%ni, hco_bhi%nj))

    !- Loop over the Ensemble members
    do ens = 1, nens

      if (ens < 10) then
        write( cens, '(i1)' ) ens
        cens='00'//cens
      else if (ens < 100) then
        write( cens, '(i2)' ) ens
        cens='0'//cens
      else
        write( cens, '(i3)' ) ens
      end if
      OutputFileName= trim(OutputBaseName) // '_' // cens // '.fst'

      write(*,*)
      write(*,*) 'Writing ensemble member: ', ens, trim(OutputFileName)

      iunens = 0
      ier    = fnom(iunens,trim(OutputFileName),'RND',0)
      ier    = fstouv(iunens,'RND')

      !- Loop over Control Variables
      do var = 1, nControlVariable

        !- Loop over vertical Levels
        do k = 1, ControlVariable(var)%nlev

          npak   = -32
          dateo  = 0
          deet   = 0
          npas   = 0
          ni     = hco_bhi%ni
          nj     = hco_bhi%nj
          nk     = 1
          ip1    = ControlVariable(var)%ip1(k)
          ip2    = 0
          ip3    = 0
          typvar = 'E'
          nomvar = trim(ControlVariable(var)%nomvar(cv_type))
          etiket = 'DEBUG'
          grtyp  = hco_bhi%grtyp
          ig1    = hco_bhi%ig1
          ig2    = hco_bhi%ig2
          ig3    = hco_bhi%ig3
          ig4    = hco_bhi%ig4
          datyp  = 1

          if ( trim(nomvar) == 'UU' .or. trim(nomvar) == 'VV') then
            factor = 1.94246 ! m/s -> knots
          else if ( trim(nomvar) == 'P0' ) then
            factor = 0.01 ! Pa -> hPa
          else
            factor = 1.0
          end if

          !- Extract from EnsPerturbations
          kgdim = ControlVariable(var)%kDimStart + k - 1
          work2d(:,:) = factor * EnsPerturbations(:,:,kgdim,ens)

          !- Writing 
          ier = fstecr(work2d, work, npak, iunens, dateo, deet, npas, ni, nj, &
                       nk, ip1, ip2, ip3, typvar, nomvar, etiket, grtyp,      &
                       ig1, ig2, ig3, ig4, datyp, .true.)

        end do ! Vertical Levels

      end do ! Variables
    
      ier =  fstfrm(iunens)
      ier =  fclos (iunens)

    end do ! Ensemble members

    deallocate(work2d)

  end subroutine WriteEnsemble

!--------------------------------------------------------------------------
! Write3D
!--------------------------------------------------------------------------

  subroutine Write3D(Field3d, iun, Etiket_in, cv_type) 3
    implicit none

    real(8), intent(in) :: Field3d(hco_bhi%ni,hco_bhi%nj,nkgdim)
    integer, intent(in) :: iun
    integer, intent(in) :: cv_type
    character(len=*), intent(in) :: Etiket_in

    real(4), allocatable :: work2d(:,:)

    real(4)   :: factor, work

    integer   :: ier, fstecr
    integer   :: var, k, kgdim

    integer :: dateo, npak, ni, nj, nk
    integer :: ip1, ip2, ip3, deet, npas, datyp
    integer :: ig1 ,ig2 ,ig3 ,ig4

    character(len=1)  :: grtyp
    character(len=4)  :: nomvar
    character(len=2)  :: typvar
    character(len=12) :: etiket
    character(len=3)  :: cens

    allocate(work2d(hco_bhi%ni, hco_bhi%nj))
    
    !- Loop over Control Variables
    do var = 1, nControlVariable

      !- Loop over vertical Levels
      do k = 1, ControlVariable(var)%nlev
         
        npak   = -32
        dateo  = 0
        deet   = 0
        npas   = 0
        ni     = hco_bhi%ni
        nj     = hco_bhi%nj
        nk     = 1
        ip1    = ControlVariable(var)%ip1(k)
        ip2    = 0
        ip3    = 0
        typvar = 'E'
        nomvar = trim(ControlVariable(var)%nomvar(cv_type))
        etiket = trim(Etiket_in)
        grtyp  = hco_bhi%grtyp
        ig1    = hco_bhi%ig1
        ig2    = hco_bhi%ig2
        ig3    = hco_bhi%ig3
        ig4    = hco_bhi%ig4
        datyp  = 1

        if ( trim(nomvar) == 'UU' .or. trim(nomvar) == 'VV') then
          factor = 1.94246 ! m/s -> knots
        else if ( trim(nomvar) == 'P0' ) then
          factor = 0.01 ! Pa -> hPa
        else
          factor = 1.0
        end if

        !- Extract from EnsPerturbations
        kgdim = ControlVariable(var)%kDimStart + k - 1
        work2d(:,:) = factor * real(Field3d(:,:,kgdim),4)

        !- Writing 
        ier = fstecr(work2d, work, npak, iun, dateo, deet, npas, ni, nj, &
                     nk, ip1, ip2, ip3, typvar, nomvar, etiket, grtyp,      &
                     ig1, ig2, ig3, ig4, datyp, .true.)

      end do ! Vertical Levels

    end do ! Variables

    deallocate(work2d)

  end subroutine Write3D

!--------------------------------------------------------------------------
! WritePowerSpectrum
!--------------------------------------------------------------------------

  subroutine WritePowerSpectrum(PowerSpectrum,iun,etiket_in,cv_type) 1
    implicit none

    real(8), intent(in) :: PowerSpectrum(nkgdim,0:ntrunc)
    integer, intent(in) :: iun
    integer, intent(in) :: cv_type
    character(len=*), intent(in) :: Etiket_in

    real(4), allocatable :: workecr(:,:)

    real(4)   :: factor, work

    integer   :: ier, fstecr
    integer   :: var, k, kgdim

    integer :: dateo, npak, ni, nj, nk
    integer :: ip1, ip2, ip3, deet, npas, datyp
    integer :: ig1 ,ig2 ,ig3 ,ig4

    character(len=1)  :: grtyp
    character(len=4)  :: nomvar
    character(len=2)  :: typvar
    character(len=12) :: etiket
    character(len=3)  :: cens

    allocate(workecr(ntrunc+1, 1))
    
    !- Loop over Control Variables
    do var = 1, nControlVariable

      !- Loop over vertical Levels
      do k = 1, ControlVariable(var)%nlev

        npak   = -32
        dateo  = 0
        deet   = 0
        npas   = 0
        ni     = ntrunc + 1
        nj     = 1
        nk     = 1
        ip1    = ControlVariable(var)%ip1(k)
        ip2    = 0
        ip3    = 0
        typvar = 'E'
        nomvar = trim(ControlVariable(var)%nomvar(cv_type))
        etiket = trim(Etiket_in)
        grtyp  = 'X'
        ig1    = 0
        ig2    = 0
        ig3    = 0 
        ig4    = 0
        datyp  = 1

        !- Extract
        kgdim = ControlVariable(var)%kDimStart + k - 1
        workecr(:,1) = real(PowerSpectrum(kgdim,:),4)

        !- Writing 
        ier = fstecr(workecr, work, npak, iun, dateo, deet, npas, ni, nj, &
                     nk, ip1, ip2, ip3, typvar, nomvar, etiket, grtyp,   &
                     ig1, ig2, ig3, ig4, datyp, .true.)

      end do ! Vertical Levels

    end do ! Variables

    deallocate(workecr)

  end subroutine WritePowerSpectrum

!--------------------------------------------------------------------------
! WriteHorizScale
!--------------------------------------------------------------------------

  subroutine WriteHorizScale(HorizScale,iun,etiket_in,cv_type) 1
    implicit none

    real(8), intent(in) :: HorizScale(nkgdim)
    integer, intent(in) :: iun
    integer, intent(in) :: cv_type
    character(len=*), intent(in) :: Etiket_in

    real(4), allocatable :: workecr(:,:,:)

    real(4)   :: factor, work

    integer   :: ier, fstecr
    integer   :: var, k, kgdim

    integer :: dateo, npak, ni, nj, nk
    integer :: ip1, ip2, ip3, deet, npas, datyp
    integer :: ig1 ,ig2 ,ig3 ,ig4

    character(len=1)  :: grtyp
    character(len=4)  :: nomvar
    character(len=2)  :: typvar
    character(len=12) :: etiket
    character(len=3)  :: cens
    
    !- Loop over Control Variables
    do var = 1, nControlVariable

      allocate(workecr(1,1,ControlVariable(var)%nlev))

      npak   = -32
      dateo  = 0
      deet   = 0
      npas   = 0
      ni     = 1
      nj     = 1
      nk     = ControlVariable(var)%nlev
      ip1    = 0
      ip2    = 0
      ip3    = 0
      typvar = 'E'
      nomvar = trim(ControlVariable(var)%nomvar(cv_type))
      etiket = trim(Etiket_in)
      grtyp  = 'X'
      ig1    = 0
      ig2    = 0
      ig3    = 0
      ig4    = 0
      datyp  = 1

      !- Extract
      workecr(1,1,:) = real(HorizScale(ControlVariable(var)%kDimStart:ControlVariable(var)%kDimEnd),4)

      !- Writing 
      ier = fstecr(workecr, work, npak, iun, dateo, deet, npas, ni, nj, &
                   nk, ip1, ip2, ip3, typvar, nomvar, etiket, grtyp,   &
                   ig1, ig2, ig3, ig4, datyp, .true.)

      deallocate(workecr)
      
    end do ! Variables

  end subroutine WriteHorizScale

!--------------------------------------------------------------------------
! WriteTicTacToc
!--------------------------------------------------------------------------

  subroutine WriteTicTacToc(iun) 2,3
    use vGrid_Descriptors , only: vgrid_descriptor, vgd_write, VGD_OK
    use MathPhysConstants_mod, only : MPC_DEGREES_PER_RADIAN_R8
    implicit none

    integer, intent(in) :: iun

    integer :: ier, vfstecr

    real(8) :: work

    integer :: dateo,npak, status
    integer :: ip1,ip2,ip3,deet,npas,datyp,ig1,ig2,ig3,ig4
    integer :: ig1_tictac,ig2_tictac,ig3_tictac,ig4_tictac

    character(len=1)  :: grtyp
    character(len=2)  :: typvar
    character(len=12) :: etiket

    !
    !- 1.  Writing Tic-Tac
    !
    npak     = -32
    deet     =  0
    ip1      =  hco_bhi%ig1
    ip2      =  hco_bhi%ig2
    ip3      =  0
    npas     =  0
    datyp    =  1
    grtyp    = 'E'
    typvar   = 'X'
    etiket   = 'EXTENDEDGRID'
    dateo =  0

    call cxgaig ( grtyp,                                          & ! IN
                  ig1_tictac, ig2_tictac, ig3_tictac, ig4_tictac, & ! OUT
                  real(hco_bhi%xlat1), real(hco_bhi%xlon1),   & ! IN
                  real(hco_bhi%xlat2), real(hco_bhi%xlon2)  )   ! IN

    ig1      =  ig1_tictac
    ig2      =  ig2_tictac
    ig3      =  ig3_tictac
    ig4      =  ig4_tictac

    ier = vfstecr(hco_bhi%lon*MPC_DEGREES_PER_RADIAN_R8, work, npak, &
                  iun, dateo, deet, npas, hco_bhi%ni, 1, 1, ip1,    &
                  ip2, ip3, typvar, '>>', etiket, grtyp, ig1,          &
                  ig2, ig3, ig4, datyp, .true.)

    ier = vfstecr(hco_bhi%lat*MPC_DEGREES_PER_RADIAN_R8, work, npak, &
                  iun, dateo, deet, npas, 1, hco_bhi%nj, 1, ip1,    &
                  ip2, ip3, typvar, '^^', etiket, grtyp, ig1,          &
                  ig2, ig3, ig4, datyp, .true.)

    !
    !- Writing Toc-Toc
    !
    status = vgd_write(vco_bhi%vgrid,iun,'fst')

    if ( status /= VGD_OK ) then
      write(*,*)
      write(*,*) 'WriteTicTacToc: ERROR with vgd_write '
      stop
    end if

  end subroutine WriteTicTacToc

!--------------------------------------------------------------------------
! WriteControlVarInfo
!--------------------------------------------------------------------------

  subroutine WriteControlVarInfo(iun) 1
    implicit none

    integer, intent(in) :: iun

    integer :: ier, fstecr, fstecr_s

    real(8) :: work

    integer :: npak, var, dateo, ni, nj
    integer :: ip1,ip2,ip3,deet,npas,datyp,ig1,ig2,ig3,ig4
    integer :: ig1_tictac,ig2_tictac,ig3_tictac,ig4_tictac

    character(len=1)  :: grtyp
    character(len=2)  :: typvar
    character(len=4)  :: nomvar
    character(len=12) :: etiket

    character(len=4)  :: ControlModelVarnameList(nControlVariable)
    character(len=4)  :: ControlBhiVarnameList  (nControlVariable)
    character(len=2)  :: ControlVarGridTypeList (nControlVariable)
    integer           :: ControlVarNlevList     (nControlVariable)

    !
    !- 1. Gathering the info
    !

    !- Loop over Control Variables
    do var = 1, nControlVariable
       ControlModelVarnameList(var) = trim(ControlVariable(var)%nomvar(cv_model))
       ControlBhiVarnameList(var)   = trim(ControlVariable(var)%nomvar(cv_bhi))
       ControlVarNlevList(var)      = ControlVariable(var)%nlev
       ControlVarGridTypeList(var)  = ControlVariable(var)%GridType
    end do

    write(*,*)
    write(*,*) 'ControlModelVarnameList = ',ControlModelVarnameList(:)
    write(*,*) 'ControlBhiVarnameList   = ',ControlBhiVarnameList(:)
    write(*,*) 'ControlVarNlevList      = ',ControlVarNlevList(:)
    write(*,*) 'ControlVarGridTypeList  = ',ControlVarGridTypeList(:)

    !
    !- 2.  Writing the list of control variables and number of vertical levels
    !
    npak     = -32
    dateo    =  0
    deet     =  0
    ip1      =  0
    ip2      =  0
    ip3      =  0
    npas     =  0
    grtyp    = 'X'
    typvar   = 'X'
    ig1      =  0
    ig2      =  0
    ig3      =  0
    ig4      =  0

    nomvar   = 'CVN'
    ni       =  4  ! 4 Characters
    nj       =  nControlVariable
    datyp    =  7 ! Character

    ier = fstecr_s(ControlModelVarnameList, work, npak, &
                 iun, dateo, deet, npas, ni, nj, 1, ip1,    &
                 ip2, ip3, typvar, nomvar, 'MODEL', grtyp, ig1, &
                 ig2, ig3, ig4, datyp, .true.)

    ier = fstecr_s(ControlBhiVarnameList, work, npak, &
                 iun, dateo, deet, npas, ni, nj, 1, ip1,    &
                 ip2, ip3, typvar, nomvar, 'B_HI', grtyp, ig1, &
                 ig2, ig3, ig4, datyp, .true.)

    nomvar   = 'CVL'
    ni       =  2  ! 2 Characters
    ier = fstecr_s(ControlVarGridTypeList, work, npak, &
                 iun, dateo, deet, npas, ni, nj, 1, ip1,    &
                 ip2, ip3, typvar, nomvar, 'LEVTYPE', grtyp, ig1, &
                 ig2, ig3, ig4, datyp, .true.)
 
    datyp    =  2 ! Integer
    ni       =  nControlVariable
    nj       =  1 
    ier = fstecr(ControlVarNlevList, work, npak, &
                 iun, dateo, deet, npas, ni, nj, 1, ip1,    &
                 ip2, ip3, typvar, nomvar, 'NLEV', grtyp, ig1, &
                 ig2, ig3, ig4, datyp, .true.)

  end subroutine WriteControlVarInfo

end module calcbmatrix_lam_mod