!--------------------------------------- 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 LamBMatrixHI (prefix="lbhi")
!
! Purpose: Performs transformation from control vector to analysis increment 
!          using the homogeneous and isotropic background error covariance 
!          matrix
!
! - Subroutines
!    lbhi_Setup        (public)
!    lbhi_Bsqrt        (public)
!    lbhi_BsqrtAdj     (public)
!    lbhi_Finalize     (public)
!    lbhi_expandToMPIglobal  (public)
!    lbhi_reduceToMPIlocal   (public)
!
! - Public variables
!    NONE
!
! - Dependencies
!    lamspectraltransform
!--------------------------------------------------------------------------

module LamBMatrixHI_mod 1,6
  use mpivar_mod
  use HorizontalCoord_mod
  use VerticalCoord_mod
  use LamSpectralTransform_mod
  use GridStateVector_mod
  use LamAnalysisGrid_mod
  implicit none
  save
  private

  ! public procedures
  public :: lbhi_Setup, lbhi_bSqrt, lbhi_bSqrtAdj, lbhi_Finalize
  public :: lbhi_expandToMPIglobal, lbhi_reduceToMPIlocal

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

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

  integer :: UWindID = -1
  integer :: VWindID = -1

  type(struct_hco), pointer :: hco_bhi    ! Analysis horizontal grid parameters
  type(struct_lst)     :: lst_bhi    ! Spectral transform Parameters
  type(struct_hco)     :: hco_bstats ! Grid-point std dev horizontal grid parameters

  real(8), allocatable :: bsqrt  (:,:,:)  ! B^1/2

  integer              :: nControlVariable
  integer              :: trunc
  integer              :: nksdim
  integer              :: nkgdim
  integer              :: cvDim
  integer              :: cvDim_mpiglobal
  
  integer              :: nlev_M
  integer              :: nlev_T

  logical              :: regrid
  logical              :: initialized = .false.

  integer              :: LatPerPE, myLatBeg, myLatEnd
  integer              :: LonPerPE, myLonBeg, myLonEnd

  integer,parameter    :: maxNumLevels=200
  real(8)              :: scaleFactor(maxNumLevels)

contains

!--------------------------------------------------------------------------
! LBHI_SETUP
!--------------------------------------------------------------------------

  subroutine lbhi_Setup( hco_anl_in, vco_anl_in, cvDim_out ) 1,8
    implicit none

    type(struct_hco), pointer, intent(in)    :: hco_anl_in
    type(struct_vco), pointer, intent(in)    :: vco_anl_in
    integer,          intent(out)   :: cvDim_out

    character(len=8), parameter     :: BStatsFilename = './bgcov'

    integer  :: numvar3d
    integer  :: numvar2d
    integer  :: var
    integer  :: ntrunc

    integer  :: iu_bstats = 0
    integer  :: iu_flnml = 0

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

    logical  :: FileExist

    !namelist
    NAMELIST /NAMBHI/ntrunc,scaleFactor

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

    !
    !- 0.  Read namelist options
    !
    ntrunc         = 75     ! default values
    scaleFactor(:) =  1.0d0 ! default values

    ier = fnom(iu_flnml,'./flnml','FTN+SEQ+R/O',0)
    write(*,*)
    write(*,*) 'lbhi_setup: Reading namelist, ier = ',ier
    read(iu_flnml,nml=nambhi)
    write(*,nml=nambhi)
    ier = fclos(iu_flnml)

    do k = 1, max(vco_anl_in%nlev_M,vco_anl_in%nlev_T)
      if ( scaleFactor(k) > 0.0d0 ) then 
        scaleFactor(k) = sqrt(scaleFactor(k))
      else
        scaleFactor(k) = 0.0d0
      end if
    end do

    write(*,*) ' sum(scaleFactor) : ',sum(scaleFactor(1:max(vco_anl_in%nlev_M,vco_anl_in%nlev_T)))
    if ( sum(scaleFactor(1:max(vco_anl_in%nlev_M,vco_anl_in%nlev_T))) == 0.0d0 ) then
      write(*,*) 'lambmatrixHI: scaleFactor=0, skipping rest of setup'
      cvDim_out   = 0
      initialized = .true.
      return
    end if

    trunc = ntrunc
    write(*,*)
    write(*,*) 'Spectral TRUNCATION = ', trunc

    !
    !- 1.  Open the background stats file
    !
    inquire(file=trim(BStatsFilename), exist=FileExist)

    if ( FileExist ) then
      ier = fnom(iu_bstats,trim(BStatsFilename),'RND+OLD+R/O',0)
      if ( ier == 0 ) then
        write(*,*)
        write(*,*) 'Background Stats File :', trim(BStatsFilename)
        write(*,*) 'opened as unit file ',iu_bstats
        ier = fstouv(iu_bstats,'RND+OLD')
      else
        write(*,*)
        write(*,*) 'lbhi_Setup: Error in opening the background stats file'
        write(*,*) trim(BStatsFilename)
        call abort3d('lbhi_Setup')
      end if
    else
      write(*,*)
      write(*,*) 'lbhi_Setup: The background stats file DOES NOT EXIST'
      write(*,*) trim(BStatsFilename)
      call abort3d('lbhi_Setup')
    end if

    !
    !- 2.  Set some variables
    !
    hco_bhi => hco_anl_in
    nlev_M  = vco_anl_in%nlev_M
    nlev_T  = vco_anl_in%nlev_T

    !- Read variables and vertical grid info from the background stats file
    call lbhi_GetControlVariableInfo( iu_bstats ) ! IN
    call lbhi_GetHorizGridInfo      ( iu_bstats ) ! IN

    nkgdim = 0
    do var = 1, nControlVariable
      allocate( ControlVariable(var)%GpStdDev (1:hco_bhi%ni, 1:hco_bhi%nj, 1:ControlVariable(var)%nlev) )
      allocate( ControlVariable(var)%ip1 (1:ControlVariable(var)%nlev) )
      if (ControlVariable(var)%nlev /= 1) then
         if (ControlVariable(var)%GridType == 'TH') then
            ControlVariable(var)%ip1(:) = vco_anl_in%ip1_T(:)
         else
            ControlVariable(var)%ip1(:) = vco_anl_in%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

    nksdim = nkgdim ! + nlev

    allocate( bsqrt  (1:nksdim, 1:nksdim ,0:trunc) )

    !- 2.2 Initialized the LAM spectral transform
    call mpivar_setup_lonbands(hco_bhi%ni,                  & ! IN
                               lonPerPE, myLonBeg, myLonEnd ) ! OUT

    call mpivar_setup_latbands(hco_bhi%nj,                  & ! IN
                               latPerPE, myLatBeg, myLatEnd ) ! OUT

    call lst_Setup( lst_bhi,                 & ! OUT
                    hco_bhi%ni, hco_bhi%nj,  & ! IN
                    hco_bhi%dlon, trunc,     & ! IN
                    'LatLonMN', nksdim )       ! IN

    cvDim     = nkgdim * lst_bhi%nla * lst_bhi%nphase
    cvDim_out = cvDim

    ! also compute mpiglobal control vector dimension
    call rpn_comm_allreduce(cvDim,cvDim_mpiglobal,1,"mpi_integer","mpi_sum","GRID",ier)

    !
    !- 3.  Read info from the background error statistics file
    !
    call lbhi_ReadStats( iu_bstats )  ! IN

    !
    !- 4.  Close the background stats files
    !
    ier = fstfrm(iu_bstats)
    ier = fclos (iu_bstats)

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

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

  end subroutine lbhi_Setup

!--------------------------------------------------------------------------
! LBHI_GetControlVariableInfo
!--------------------------------------------------------------------------

  subroutine lbhi_GetControlVariableInfo( iu_bstats ) 1,9
    implicit none

    integer, intent(in) :: iu_bstats

    integer :: key, fstinf, ier, fstlir, fstlir_s
    integer :: ni, nj, nlev
    integer :: dateo, deet, npas, nk, nbits, datyp
    integer :: ip1, ip2, ip3
    integer :: var, varvnl

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

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

    logical :: found

    !
    !- 1.  How Many Control Variables do we have?
    !
    dateo  = -1
    etiket = 'NLEV'
    ip1    = -1
    ip2    = -1
    ip3    = -1
    typvar = ' '
    nomvar = 'CVL'

    key = fstinf( iu_bstats,                                  & ! IN
                  ni, nj, nk,                                 & ! OUT
                  dateo, etiket, ip1, ip2, ip3, typvar, nomvar )! IN

    if (key < 0) then
      write(*,*)
      write(*,*) 'lbhi_GetControlVariableInfo: Unable to find variable =',nomvar
      call abort3d('lbhi_GetControlVariableInfo')
    end if

    nControlVariable = ni
    write(*,*)
    write(*,*) 'Number of Control Variables found = ', nControlVariable

    allocate(ControlModelVarnameList(nControlVariable))
    allocate(ControlBhiVarnameList  (nControlVariable))
    allocate(ControlVarGridTypeList (nControlVariable))
    allocate(ControlVarNlevList     (nControlVariable))

    !
    !- 2. Read the info from the input file
    !
    nomvar = 'CVN'

    etiket = 'MODEL'
    key = fstlir_s(ControlModelVarnameList,                    & ! OUT 
                   iu_bstats,                                  & ! IN
                   ni, nj, nlev,                               & ! OUT
                   dateo, etiket, ip1, ip2, ip3, typvar,nomvar)  ! IN
    if (key < 0) then
      write(*,*)
      write(*,*) 'lbhi_GetControlVariableInfo: Cannot find variable ', nomvar 
      call abort3d('lbhi_GetControlVariableInfo') 
    end if

    etiket = 'B_HI'
    key = fstlir_s(ControlBhiVarnameList,                      & ! OUT 
                   iu_bstats,                                  & ! IN
                   ni, nj, nlev,                               & ! OUT
                   dateo, etiket, ip1, ip2, ip3, typvar,nomvar)  ! IN
    if (key < 0) then
      write(*,*)
      write(*,*) 'lbhi_GetControlVariableInfo: Cannot find variable ', nomvar
      call abort3d('lbhi_GetControlVariableInfo') 
    end if
    

    nomvar = 'CVL'

    etiket = 'NLEV'
    key = fstlir  (ControlVarNlevList,                         & ! OUT 
                   iu_bstats,                                  & ! IN
                   ni, nj, nlev,                               & ! OUT
                   dateo, etiket, ip1, ip2, ip3, typvar,nomvar)  ! IN
    if (key < 0) then
      write(*,*)
      write(*,*) 'lbhi_GetControlVariableInfo: Cannot find variable ', nomvar
      call abort3d('lbhi_GetControlVariableInfo') 
    end if

    etiket = 'LEVTYPE'
    key = fstlir_s(ControlVarGridTypeList,                     & ! OUT 
                   iu_bstats,                                  & ! IN
                   ni, nj, nlev,                               & ! OUT
                   dateo, etiket, ip1, ip2, ip3, typvar,nomvar)  ! IN
    if (key < 0) then
      write(*,*)
      write(*,*) 'lbhi_GetControlVariableInfo: Cannot find variable ', nomvar
      call abort3d('lbhi_GetControlVariableInfo') 
    end if

    !
    !- 3. Introduce the info in the ControlVariable structure
    !
    do var = 1, nControlVariable
       ControlVariable(var)%nomvar(cv_model)= trim(ControlModelVarnameList(var))
       ControlVariable(var)%nomvar(cv_bhi)  = trim(ControlBhiVarnameList(var))
       ControlVariable(var)%nlev            = ControlVarNlevList(var)
       ControlVariable(var)%GridType        = trim(ControlVarGridTypeList(var))

       if      (trim(ControlVariable(var)%nomvar(cv_model)) == 'UU' ) then 
          UWindID = var
       else if (trim(ControlVariable(var)%nomvar(cv_model)) == 'VV' ) then
          VWindID = var
       end if

       if ( trim(ControlVariable(var)%nomvar(cv_model)) == 'LQ' ) then
          ControlVariable(var)%nomvar(cv_model) = 'HU' ! PATCH because gridStateVector uses HU
                                                       ! despite that LQ is outputed from bmatrix_mod
       end if

       write(*,*)
       write(*,*) 'nomvar(cv_model) = ', ControlVariable(var)%nomvar(cv_model)
       write(*,*) 'nomvar(cv_bhi)   = ', ControlVariable(var)%nomvar(cv_bhi)
       write(*,*) 'nlev             = ', ControlVariable(var)%nlev
       write(*,*) 'gridtype         = ', ControlVariable(var)%GridType
    end do

    deallocate(ControlModelVarnameList)
    deallocate(ControlBhiVarnameList)
    deallocate(ControlVarGridTypeList)
    deallocate(ControlVarNlevList)

    !
    !- 4. Error traps
    !
    if ( UWindID == -1 .or. VWindID == -1) then
      write(*,*)
      write(*,*) 'lbhi_GetControlVariableInfo: UU and/or VV not in the Control Variable list'
      call abort3d('lbhi_GetControlVariableInfo')
    end if

    !- Make sure that all the control variables are present in GridStateVector
    do var = 1, nControlVariable
       if ( .not. gsv_varExist(ControlVariable(var)%nomvar(cv_model)) ) then
          write(*,*)
          write(*,*) 'lbhi_GetControlVariableInfo: The following variable is MISSING in GridStateVector'
          write(*,*) trim(ControlVariable(var)%nomvar(cv_model))
          call abort3d('lbhi_GetControlVariableInfo')
       end if
    end do

    !- Make sure that all the variables in GridStateVector are present in the control variables
    do varvnl = 1, vnl_numvarmax

       if ( gsv_varExist(vnl_varNameList(varvnl)) ) then

          found = .false.
          do var = 1, nControlVariable
             if ( vnl_varNameList(varvnl) == ControlVariable(var)%nomvar(cv_model) ) then
                found = .true.
                exit
             end if
          end do

          if (.not. found) then
             write(*,*)
             write(*,*) 'lbhi_GetControlVariableInfo: The following variable is MISSING in the Control Variables'
             write(*,*) trim(vnl_varNameList(varvnl))
             call abort3d('lbhi_GetControlVariableInfo')
          end if

       end if

    end do

  end subroutine lbhi_GetControlVariableInfo

!--------------------------------------------------------------------------
! LBHI_GetHorizGridInfo
!--------------------------------------------------------------------------

  subroutine lbhi_GetHorizGridInfo( iu_bstats ) 1,1
    implicit none

    integer, intent(in) :: iu_bstats

    integer, allocatable :: key_list(:)

    integer :: key, fstinf, fstprm, ier, fstinl
    integer :: ni, nj, NivMax, k
    integer :: dateo, deet, npas, nk, nbits, datyp
    integer :: ip1, ip2, ip3, swa, lng, dltf, ubc
    integer :: extra1, extra2, extra3
    integer :: ezdefset, ezqkdef

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

    !
    !- 1.  Get horizontal grid parameters
    !
    dateo  = -1
    etiket = 'STDDEV'
    ip1    = -1
    ip2    = -1
    ip3    = -1
    typvar = ' '
    nomvar = ControlVariable(1)%nomvar(cv_bhi)

    key = fstinf( iu_bstats,                                  & ! IN
                  ni, nj, nk,                                 & ! OUT
                  dateo, etiket, ip1, ip2, ip3, typvar, nomvar )! IN

    if (key < 0) then
      write(*,*)
      write(*,*) 'lbhi_GetHorizGridInfo: Unable to find input horiz grid info using =',nomvar
      call abort3d('lbhi_GetHorizGridInfo')
    end if

    ier = fstprm( key,                                                 & ! IN
                  dateo, deet, npas, hco_bstats%ni, hco_bstats%nj, nk, & ! OUT
                  nbits, datyp, ip1, ip2, ip3, typvar, nomvar, etiket, & ! OUT
                  hco_bstats%grtyp, hco_bstats%ig1, hco_bstats%ig2,    & ! OUT
                  hco_bstats%ig3, hco_bstats%ig4, swa, lng, dltf, ubc, & ! OUT
                  extra1, extra2, extra3 )                               ! OUT

    !- 1.3 Regridding needed ?
    if ( hco_bstats%ni    == hco_bhi%ni    .and. &
         hco_bstats%nj    == hco_bhi%nj    .and. &
         hco_bstats%grtyp == hco_bhi%grtyp .and. &
         hco_bstats%ig1   == hco_bhi%ig1   .and. &
         hco_bstats%ig2   == hco_bhi%ig2   .and. &
         hco_bstats%ig3   == hco_bhi%ig3   .and. &
         hco_bstats%ig4   == hco_bhi%ig4 ) then
      Regrid = .false.
      write(*,*)
      write(*,*) 'lbhi_GetHorizGridInfo: No Horizontal regridding needed'
    else
      Regrid = .true.
      hco_bstats%EZscintID = ezqkdef( hco_bstats%ni, hco_bstats%nj,       & ! IN
                                        hco_bstats%grtyp, hco_bstats%ig1, & ! IN
                                        hco_bstats%ig2, hco_bstats%ig3,   & ! IN
                                        hco_bstats%ig4, iu_bstats )         ! IN
      ier = ezdefset(hco_bhi%EZscintID, hco_bstats%EZscintID)               ! IN
      write(*,*)
      write(*,*) 'lbhi_GetHorizGridInfo: Horizontal regridding is needed'
    end if

  end subroutine lbhi_GetHorizGridInfo

!--------------------------------------------------------------------------
! LBHI_READSTATS
!--------------------------------------------------------------------------

  subroutine lbhi_ReadStats( iu_bstats ) 1,2
    implicit none

    integer, intent(in) :: iu_bstats

    !
    !- 1.  Read the background error statistics
    !

    !- 1.1 Verical correlations of control variables in spectral space
    call lbhi_ReadBSqrt( iu_bstats )        ! IN

    !- 1.2 Mass - Rotational wind statistical linear balance operator

    ! JFC: Pas encore code
    !if ( usePtoT ) then
    !  call lbhi_ReadPtoT( iu_bstats, PtoT_Type )
    !end if

    !- 1.3 Read grid-point standard deviations of control variables
    call lbhi_ReadGridPointStdDev(iu_bstats ) ! IN

  end subroutine lbhi_ReadStats

!--------------------------------------------------------------------------
! LBHI_ReadBSqrt
!--------------------------------------------------------------------------

  subroutine lbhi_ReadBSqrt( iu_bstats ) 1,5
    implicit none

    integer, intent(in) :: iu_bstats

    real(8), allocatable :: bsqrt2d  (:,:)

    integer :: key, fstinf, fstinl, vfstlir, totwvnb, infon
    integer, parameter :: nmax=2000
    integer :: liste(nmax)

    integer                     :: ip1, ip2, ip3
    integer                     :: ni_t, nj_t, nlev_t, dateo  
    character(len=4 )           :: nomvar
    character(len=2 )           :: typvar
    character(len=12)           :: etiket

    dateo  = -1
    etiket = 'B_SQUAREROOT'
    ip1    = -1
    ip3    = -1
    typvar = ' '
    nomvar = 'ZN'

    !
    !- 1.  Find the truncation in the stats file
    !
    ip2    = -1

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

    if (key >= 0) then
      !- 1.2 Ensure spectral trunctation are the same
      if ( infon - 1  /= trunc ) then
        write(*,*)
        write(*,*) 'lbhi_ReadBSqrt: Truncation here and on stats file different'
        write(*,*) 'VAR truncation        = ', trunc
        write(*,*) 'Stats file truncation = ', infon-1
        call abort3d('lbhi_ReadBSqrt')
      end if
    else
      write(*,*)
      write(*,*) 'lbhi_ReadBSqrt: Cannot find B square-root ', nomvar 
      call abort3d('lbhi_ReadBSqrt')
    end if

    !
    !- 2.   Read B^0.5
    !
    allocate( bsqrt2d  (1:nksdim, 1:nksdim) )

    do totwvnb = 0, trunc

      ip2    = totwvnb

      !- 2.1 Check if field exists and its dimensions
      key = fstinf( iu_bstats,                                  & ! IN
                    ni_t, nj_t, nlev_t,                         & ! OUT
                    dateo, etiket, ip1, ip2, ip3, typvar,nomvar)  ! IN

      if (key >= 0) then
        !- 2.2 Ensure that the number of vertical levels are compatible
        if ( ni_t /= nksdim .or. nj_t /= nksdim  ) then
          write(*,*)
          write(*,*) 'lbhi_ReadBSqrt: BG stat levels inconsitencies'
          write(*,*) 'for BSQRT: ni_t, nj_t, nksdim =', ni_t, nj_t, nksdim
          call abort3d('lbhi_ReadBSqrt')
        endif

        !- 2.3 Reading
        key = vfstlir( bsqrt2d,                                    & ! OUT 
                       iu_bstats,                                  & ! IN
                       ni_t, nj_t, nlev_t,                         & ! OUT
                       dateo, etiket, ip1, ip2, ip3, typvar,nomvar)  ! IN
      else
        write(*,*)
        write(*,*) 'lbhi_ReadBSqrt: Cannot find BSQRT for totwvnb = ', totwvnb
        call abort3d('lbhi_ReadBSqrt')
      end if

      !- 2.4 Transfer to a 3D array
      bsqrt(:,:,totwvnb) = bsqrt2d(:,:)

    end do

    deallocate( bsqrt2d )

  end subroutine lbhi_ReadBSqrt

!--------------------------------------------------------------------------
! LBHI_ReadGridPointStdDev
!--------------------------------------------------------------------------

  subroutine lbhi_ReadGridPointStdDev(iu_bstats) 1,3
    implicit none

    integer, intent(in) :: iu_bstats

    real(8), allocatable :: StdDev2D(:,:)
    real(8), allocatable :: StdDev2D_Regrid(:,:)

    integer :: vfstlir, ezsint, ier
    integer :: ni_t, nj_t, nlev_t, var, k
    integer :: dateo, ip1,ip2,ip3

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

    real(8) :: UnitConv

    !
    !- 1.  Read grid point standard deviations
    !
    allocate( StdDev2D(1:hco_bstats%ni,1:hco_bstats%nj) )
    if (Regrid) then
      allocate( StdDev2D_Regrid(1:hco_bhi%ni, 1:hco_bhi%nj) )
    end if

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

      !- 1.2 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_bhi))
        etiket = 'STDDEV'
        if ( trim(nomvar) == 'P0') then
          UnitConv = 100.0d0 ! hPa -> Pa
        else
          UnitConv = 1.0d0
        end if

        !- 1.2.1 Reading
        ier = vfstlir( StdDev2D,                                   & ! OUT 
                       iu_bstats,                                  & ! IN
                       ni_t, nj_t, nlev_t,                         & ! OUT
                       dateo, etiket, ip1, ip2, ip3, typvar,nomvar)  ! IN 

        if (ier < 0) then
          write(*,*)
          write(*,*) 'lbhi_ReadGridPointStdDev: Cannot find Std Deviations'
          write(*,*) 'nomvar =', trim(ControlVariable(var)%nomvar(cv_bhi))
          write(*,*) 'etiket =', trim(etiket)
          write(*,*) 'ip1    =', ControlVariable(var)%ip1(k)
          call abort3d('lbhi_ReadGridPointStdDev')
        end if

        if (ni_t /= hco_bstats%ni .or. nj_t /= hco_bstats%nj) then
          write(*,*)
          write(*,*) 'lbhi_ReadGridPointStdDev: Invalid dimensions for ...'
          write(*,*) 'nomvar      =', trim(ControlVariable(var)%nomvar(cv_bhi))
          write(*,*) 'etiket      =', trim(etiket)
          write(*,*) 'ip1         =', ControlVariable(var)%ip1(k)
          write(*,*) 'Found ni,nj =', ni_t, nj_t 
          write(*,*) 'Should be   =', hco_bstats%ni, hco_bstats%nj
          call abort3d('lbhi_ReadGridPointStdDev')
        end if

        !- 1.2.2 Regrid (if necessary) and transfer to 3D array
        if ( .not. Regrid) then
           ControlVariable(var)%GpStdDev(:,:,k) = StdDev2D(:,:)
        else
           ! Note: EZSCINT setup was done in GetHorizGridInfo
           ier = ezsint(StdDev2D_Regrid, StdDev2D)
           ControlVariable(var)%GpStdDev(:,:,k) = StdDev2D_Regrid(:,:)
        end if

        !- 1.3 Scaling
        ControlVariable(var)%GpStdDev(:,:,k) = ControlVariable(var)%GpStdDev(:,:,k) * &
                                                 UnitConv * scaleFactor(k)

      end do

    end do

    deallocate( StdDev2D )
    if (Regrid) then
      deallocate( StdDev2D_Regrid )
    end if

  end subroutine lbhi_ReadGridPointStdDev

!--------------------------------------------------------------------------
! LBHI_bSqrt
!--------------------------------------------------------------------------

  subroutine lbhi_bSqrt(controlVector_in, statevector) 1,4
    implicit none

    real(8),          intent(in)    :: controlVector_in(cvDim)
    type(struct_gsv), intent(inout) :: statevector

    real(8), allocatable :: gd_out(:,:,:)
    real(8), allocatable :: hiControlVector(:,:,:)

    integer :: ier, k, fstouv, fnom, fstfrm, fclos, fstecr, ila
    integer :: iu_out = 90

    if ( .not. initialized ) then
      call abort3d('lbhi_bSqrt: LAM_bMatrixHI not initialized')
    endif

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

    !
    !-  1.  Extract data from the 1D controlVector array
    !
    allocate( hiControlVector(lst_bhi%nla, lst_bhi%nphase, nksdim) )

    call lbhi_cain( controlVector_in,  & ! IN
                    hiControlVector )    ! OUT

    !
    !-  2.  Move from control variables space to model variables space
    !
    allocate( gd_out  (myLonBeg:myLonEnd, myLatBeg:myLatEnd, nksdim) )

    call lbhi_cv2gd( hiControlVector,   & ! IN
                     gd_out           )   ! OUT
    
    deallocate(hiControlVector)

    !
    !-  3.  Transfer results to statevector structure
    !
    call StatevectorInterface( statevector,   & ! INOUT
                               gd_out,        & ! IN
                              'ToStateVector' ) ! IN

    deallocate(gd_out)

    write(*,*)
    write(*,*) 'lbhi_bSqrt: Done'

  end subroutine lbhi_bSqrt

!--------------------------------------------------------------------------
! LBHI_bSqrtAdj
!--------------------------------------------------------------------------

  subroutine lbhi_bSqrtAdj(statevector, controlVector_out) 1,4
    implicit none

    real(8),          intent(out)   :: controlVector_out(cvDim)
    type(struct_gsv), intent(inout) :: statevector

    real(8), allocatable :: gd_in(:,:,:)
    real(8), allocatable :: hiControlVector(:,:,:)

    if ( .not. initialized ) then
      call abort3d('lbhi_bSqrtAdj: LAM_bMatrixHI not initialized')
    endif

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

    !
    !-  3.  Extract data from the StateVector
    !
    allocate( gd_in(myLonBeg:myLonEnd, myLatBeg:myLatEnd, nksdim) )

    call StatevectorInterface ( statevector,      & ! IN
                                gd_in,            & ! OUT
                               'FromStateVector' )  ! IN

    !
    !-  2.  Move from model variables space to control variables space
    !
    allocate( hiControlVector(lst_bhi%nla, lst_bhi%nphase, nksdim) )
    hiControlVector(:,:,:) = 0.d0

    call lbhi_cv2gdAdj( hiControlVector, & ! OUT
                        gd_in          )   ! IN

    !
    !-  1.  Put data into the 1D controlVector array
    !
    controlVector_out(:) = 0.d0
    call lbhi_cainAdj(controlVector_out, hiControlVector)

    deallocate(gd_in)
    deallocate(hiControlVector)

    write(*,*)
    write(*,*) 'lbhi_bSqrtAdj: Done'

  end subroutine lbhi_bSqrtAdj

!--------------------------------------------------------------------------
! LBHI_cv2gd
!--------------------------------------------------------------------------

  subroutine lbhi_cv2gd(hiControlVector_in, gd_out) 1,4
    implicit none

    real(8), intent(inout) :: hiControlVector_in(lst_bhi%nla, lst_bhi%nphase, nksdim)
    real(8), intent(out)   :: gd_out(myLonBeg:myLonEnd  ,myLatBeg:myLatEnd  ,1:nksdim)

    real(8), allocatable :: uphy(:,:,:)
    real(8), allocatable :: vphy(:,:,:)
    real(8), allocatable :: psi(:,:,:)
    real(8), allocatable :: chi(:,:,:)

    integer :: kstart, kend, var

    character(len=19)   :: kind

    !
    !- 1. B^1/2 * xi (in spectral space)
    !
    call lbhi_bSqrtXi(hiControlVector_in)    ! INOUT

    !
    !- 2. Spectral Space -> Grid Point Space
    !
    kind = 'SpectralToGridPoint'
    call lst_VarTransform( lst_bhi%id,            & ! IN
                           hiControlVector_in,    & ! IN
                           gd_out,                & ! OUT
                           kind, nksdim )           ! IN

    !
    !- 3.  Multiply by the grid point standard deviations
    !
    do var = 1, nControlVariable
      kstart = ControlVariable(var)%kDimStart
      kend   = ControlVariable(var)%kDimEnd
      gd_out(:,:,kstart:kend) = gd_out(:,:,kstart:kend) * ControlVariable(var)%GpStdDev(myLonBeg:myLonEnd,myLatBeg:myLatEnd,:)
    end do

    !
    !- 4.  Psi / Chi -> U-wind / V-wind
    !

    !- 4.1 Memory allocation and Extraction of Psi and Chi from GD
    if ( trim(ControlVariable(UWindID)%nomvar(cv_model)) /= 'UU' .or. &
         trim(ControlVariable(UWindID)%nomvar(cv_bhi))   /= 'PP' .or. &
         trim(ControlVariable(VWindID)%nomvar(cv_model)) /= 'VV' .or. &
         trim(ControlVariable(VWindID)%nomvar(cv_bhi))   /= 'CC' .or. &
         ControlVariable(UWindID)%nlev /= nlev_M .or. &
         ControlVariable(VWindID)%nlev /= nlev_M  ) then
      call abort3d('lbhi_cv2gd: Error in Wind related parameters')
    end if

    allocate(uphy(myLonBeg:myLonEnd,myLatBeg:myLatEnd,1:nlev_M))
    allocate(vphy(myLonBeg:myLonEnd,myLatBeg:myLatEnd,1:nlev_M))
    allocate(psi (myLonBeg:myLonEnd,myLatBeg:myLatEnd,1:nlev_M))
    allocate(chi (myLonBeg:myLonEnd,myLatBeg:myLatEnd,1:nlev_M))

    psi(:,:,:) = gd_out(:,:,ControlVariable(UWindID)%kDimStart:ControlVariable(UWindID)%kDimEnd)
    chi(:,:,:) = gd_out(:,:,ControlVariable(VWindID)%kDimStart:ControlVariable(VWindID)%kDimEnd)

    !- 4.2 Do Transform
    call lag_PsiChiToUV( psi, chi,           & ! IN
                         uphy, vphy,         & ! OUT
                         nlev_M)               ! IN

    !- 4.3 Insert results in gd_out and deallocate memories
    gd_out(:,:,1       :  nlev_M) = uphy(:,:,:)
    gd_out(:,:,nlev_M+1:2*nlev_M) = vphy(:,:,:)

    deallocate(chi)
    deallocate(psi)
    deallocate(vphy)
    deallocate(uphy)

  end subroutine lbhi_cv2gd

!--------------------------------------------------------------------------
! LBHI_cv2gdAdj
!--------------------------------------------------------------------------

  subroutine lbhi_cv2gdAdj(hiControlVector_out, gd_in) 1,4
    implicit none

    real(8), intent(out)   :: hiControlVector_out(lst_bhi%nla, lst_bhi%nphase, nksdim)
    real(8), intent(inout) :: gd_in(myLonBeg:myLonEnd, myLatBeg:myLatEnd ,1:nksdim)

    real(8), allocatable :: uphy(:,:,:)
    real(8), allocatable :: vphy(:,:,:)
    real(8), allocatable :: psi(:,:,:)
    real(8), allocatable :: chi(:,:,:)

    integer :: kstart, kend, var

    character(len=19)   :: kind

    !
    !- 4.  U-wind / V-wind -> Psi / Chi
    !

    !- 4.3 Memory allocation and Extraction of Uphy and Vphy from GDUV
    if ( trim(ControlVariable(UWindID)%nomvar(cv_model)) /= 'UU' .or. &
         trim(ControlVariable(UWindID)%nomvar(cv_bhi))   /= 'PP' .or. &
         trim(ControlVariable(VWindID)%nomvar(cv_model)) /= 'VV' .or. &
         trim(ControlVariable(VWindID)%nomvar(cv_bhi))   /= 'CC' .or. &
         ControlVariable(UWindID)%nlev /= nlev_M .or. &
         ControlVariable(VWindID)%nlev /= nlev_M  ) then
      call abort3d('lbhi_cv2gdadj: Error in Wind related parameters')
    end if

    allocate(uphy(myLonBeg:myLonEnd,myLatBeg:myLatEnd,1:nlev_M))
    allocate(vphy(myLonBeg:myLonEnd,myLatBeg:myLatEnd,1:nlev_M))
    allocate(psi (myLonBeg:myLonEnd,myLatBeg:myLatEnd,1:nlev_M))
    allocate(chi (myLonBeg:myLonEnd,myLatBeg:myLatEnd,1:nlev_M))

    uphy(:,:,:) = gd_in(:,:,1       :  nlev_M)
    vphy(:,:,:) = gd_in(:,:,nlev_M+1:2*nlev_M)

    !- 4.2 Do Transform
    call lag_PsiChiToUVAdj( psi, chi,           & ! OUT
                            uphy, vphy,         & ! IN
                            nlev_M)               ! IN

    !- 4.1 Insert results in gd and deallocate moemories
    gd_in(:,:,ControlVariable(UWindID)%kDimStart:ControlVariable(UWindID)%kDimEnd) = psi(:,:,:)
    gd_in(:,:,ControlVariable(VWindID)%kDimStart:ControlVariable(VWindID)%kDimEnd) = chi(:,:,:)

    deallocate(chi)
    deallocate(psi)
    deallocate(vphy)
    deallocate(uphy)

    !
    !- 3.  Multiply by the grid point standard deviations
    !
    do var = 1, nControlVariable
      kstart = ControlVariable(var)%kDimStart
      kend   = ControlVariable(var)%kDimEnd
      gd_in(:,:,kstart:kend) = gd_in(:,:,kstart:kend) * ControlVariable(var)%GpStdDev(myLonBeg:myLonEnd,myLatBeg:myLatEnd,:)
    end do

    !
    !- 2. Grid Point Space -> Spectral Space
    !
    kind = 'GridPointToSpectral'
    call lst_VarTransform( lst_bhi%id,             & ! IN
                           hiControlVector_out,    & ! OUT
                           gd_in,                  & ! IN
                           kind, nksdim )            ! IN

    !
    !- 1. B^1/2 * xi (in spectral space)
    !
    call lbhi_bSqrtXi( hiControlVector_out )    ! INOUT

 end subroutine lbhi_cv2gdAdj

!--------------------------------------------------------------------------
! LBHI_bSqrtXi
!--------------------------------------------------------------------------

  subroutine lbhi_bSqrtXi(hiControlVector_in) 2
    implicit none

    real(8), intent(inout) :: hiControlVector_in(lst_bhi%nla, lst_bhi%nphase, nksdim)

    real(8), allocatable :: sp_in (:,:,:)
    real(8), allocatable :: sp_out(:,:,:)

    integer :: totwvnb, e, k, ila
    integer :: m, n, lda, ldb, ldc

    !
    !- 1. B^1/2 * xi (in spectral space)
    !
    do totwvnb = 0, trunc

      if ( lst_bhi%nePerK(totwvnb) == 0 ) then
      !   print*
      !   print*,'JFC: No spectral elements for this CPU for totwvnb = ', totwvnb
         cycle
      end if

      allocate( sp_in (nksdim,lst_bhi%nphase,lst_bhi%nePerK(totwvnb)) )
      allocate( sp_out(nksdim,lst_bhi%nphase,lst_bhi%nePerK(totwvnb)) )

      !- 1.1 Select spectral elements associated with the total wavenumber
      do e = 1, lst_bhi%nePerK(totwvnb)
        ila = lst_bhi%ilaFromEK(e,totwvnb)
        do k = 1, nksdim
          sp_in(k,1:lst_bhi%nphase,e) = hiControlVector_in(ila,1:lst_bhi%nphase,k)
        end do
      end do

      !- 1.2 Compute bsqrt * sp_in using DGEMM 

      ! For documentation on dgemm, see: http://www.netlib.org/blas/dgemm.f
      ! Matrix A = BSQRT(:,:,totwvnb)
      ! Matrix B = SP_IN
      ! Matrix C = SP_OUT
      m   = nksdim
      n   = lst_bhi%nphase * lst_bhi%nePerK(totwvnb)
      k   = nksdim
      lda = nksdim
      ldb = nksdim
      ldc = nksdim

      call dgemm( 'N', 'N', m, n, k, 1.d0,                   &  ! IN
                  bsqrt(:,:,totwvnb), lda, sp_in, ldb, 0.d0, &  ! IN
                  sp_out,                                    &  ! OUT
                  ldc )                                         ! IN

      !- 1.3 Replace sp values with output matrix
      do e = 1, lst_bhi%nePerK(totwvnb)
        ila = lst_bhi%ilaFromEK(e,totwvnb)
        do k = 1, nksdim
          hiControlVector_in(ila,1:lst_bhi%nphase,k) = sp_out(k,1:lst_bhi%nphase,e)
        end do
      end do

      deallocate(sp_in)
      deallocate(sp_out)

    end do ! Total Wavenumber

  end subroutine lbhi_bSqrtXi

!--------------------------------------------------------------------------
! LBHI_cain
!--------------------------------------------------------------------------

   SUBROUTINE LBHI_cain(controlVector_in, hiControlVector_out) 1
    implicit none

    real(8), intent(in)    :: controlVector_in(cvDim)
    real(8), intent(out)   :: hiControlVector_out(lst_bhi%nla,lst_bhi%nphase,nksdim)

    integer :: dim, k, ila, p

    dim = 0
    hiControlVector_out(:,:,:) = 0.0d0
    do k = 1, nksdim
      do ila = 1, lst_bhi%nla
        do p = 1, lst_bhi%nphase
          dim = dim + 1
          hiControlVector_out(ila,p,k) = controlVector_in(dim) * lst_bhi%NormFactor(ila,p)
        end do
      end do
    end do

  end SUBROUTINE LBHI_cain

!--------------------------------------------------------------------------
! LBHI_cainAdj
!--------------------------------------------------------------------------

  SUBROUTINE LBHI_cainAdj(controlVector_out, hiControlVector_in) 1
    implicit none

    real(8), intent(out)   :: controlVector_out(cvDim)
    real(8), intent(in )   :: hiControlVector_in(lst_bhi%nla,lst_bhi%nphase,nksdim)

    integer :: dim, k, ila, p

    dim = 0
    do k = 1, nksdim
      do ila = 1, lst_bhi%nla
        do p = 1, lst_bhi%nphase
          dim = dim + 1
          controlVector_out(dim) = controlVector_out(dim) + &
                                   hiControlVector_in(ila,p,k) * lst_bhi%NormFactorAd(ila,p)
        end do
      end do
    end do

  end SUBROUTINE LBHI_cainAdj

!--------------------------------------------------------------------------
! StatevectorInterface
!--------------------------------------------------------------------------

  subroutine StatevectorInterface(statevector, gd, Direction) 2,6
    implicit none

    type(struct_gsv), intent(inout) :: statevector
    real(8),          intent(inout) :: gd(myLonBeg:myLonEnd,myLatBeg:myLatEnd,nksdim)
    character(len=*), intent(in)    :: Direction

    integer :: var,varID
    integer :: kgdStart, kgdEnd, i, j, k, kgd, nlev

    real(8), pointer :: field(:,:,:)

    character(len=4 )      :: varname

    logical :: ToStateVector

    select case ( trim(Direction) )
    case ('ToStateVector')
      ToStateVector = .true.
    case ('FromStateVector')
      ToStateVector = .false.
    case default
      write(*,*)
      write(*,*) 'StatevectorInterface: Unknown Direction ', trim(Direction)
      call abort3d('StatevectorInterface')
    end select

    do var = 1, nControlVariable

      varname = ControlVariable(var)%nomvar(cv_model)

      if (.not. gsv_varExist(varname) ) then
         write(*,*)
         write(*,*) 'StatevectorInterface: The following variable is MISSING in GridStateVector'
         write(*,*) varname
         call abort3d('StatevectorInterface')
      end if

      field => gsv_getField3D(statevector,varname)

      kgdStart = ControlVariable(var)%kDimStart
      kgdEnd   = ControlVariable(var)%kDimEnd
   
      nlev = gsv_getNumLev(statevector,vnl_vartypeFromVarname(varname))
      if ( kgdEnd - kgdStart + 1  /= nlev ) then
         write(*,*)
         write(*,*) 'StatevectorInterface: Number of vertical level mismatch'
         write(*,*) kgdEnd - kgdStart + 1, nlev
         call abort3d('StatevectorInterface')
      end if

!$OMP PARALLEL DO PRIVATE(j,kgd,k,i)
      do j = myLatBeg, myLatEnd
         do kgd = kgdStart, kgdEnd
            k = kgd - kgdStart + 1
            do i = myLonBeg, myLonEnd
               if ( ToStateVector ) then
                  field(i,k,j) = gd(i,j,kgd)
               else
                  gd(i,j,kgd)  = field(i,k,j)
               end if
            end do
         end do
      end do
!$OMP END PARALLEL DO

    end do

  end subroutine StatevectorInterface

!--------------------------------------------------------------------------
! LBHI_reduceToMPILocal
!--------------------------------------------------------------------------

  SUBROUTINE LBHI_reduceToMPILocal(cv_mpilocal,cv_mpiglobal,cvDim_mpilocal_out) 1,2
    implicit none
    real(8), intent(out) :: cv_mpilocal(cvDim)
    real(8), intent(in)  :: cv_mpiglobal(cvDim_mpiglobal)
    integer, intent(out) :: cvDim_mpilocal_out

    integer :: k, ila, p, ilaGlb, jdim_mpilocal, jdim_mpiglobal 

    cvDim_mpilocal_out = cvDim

    do k = 1, nksdim
      do ila = 1, lst_bhi%nla
        do p = 1, lst_bhi%nphase

          jdim_mpilocal = ( (k-1) * lst_bhi%nla * lst_bhi%nphase ) + &
                                        ( (ila-1) * lst_bhi%nphase ) + p

          ilaGlb = lst_bhi%ilaGlobal(ila)
          jdim_mpiglobal = ( (k-1) * lst_bhi%nlaGlobal * lst_bhi%nphase ) + &
                                            ( (ilaGlb-1) * lst_bhi%nphase ) + p
  
          if ( jdim_mpilocal  > cvDim          ) then 
             write(*,*) 'LBHI_reduceToMPILocal: jdim_mpilocal > cvDim ',k,ila,p,ilaGlb
             call abort3d('LBHI_reduceToMPILocal')
          end if
          if ( jdim_mpiglobal > cvDim_mpiglobal) then
             write(*,*) 'LBHI_reduceToMPILocal: jdim_mpiglobal > cvDim_mpiglobal ',k,ila,p,ilaGlb
            call abort3d('LBHI_reduceToMPILocal')
          end if

          cv_mpilocal(jdim_mpilocal) = cv_mpiglobal(jdim_mpiglobal)

        end do
      end do
    end do

  END SUBROUTINE LBHI_reduceToMPILocal

!--------------------------------------------------------------------------
! LBHI_expandToMPIGlobal
!--------------------------------------------------------------------------

  SUBROUTINE LBHI_expandToMPIGlobal(cv_mpilocal,cv_mpiglobal,cvDim_mpiglobal_out) 1,2
    implicit none
    real(8), intent(in)  :: cv_mpilocal(cvDim)
    real(8), intent(out) :: cv_mpiglobal(cvDim_mpiglobal)
    integer, intent(out) :: cvDim_mpiglobal_out

    real(8),allocatable :: my_cv_mpiglobal(:)
    integer :: k, ila, p, ilaGlb, jdim_mpilocal, jdim_mpiglobal, ier

    cvDim_mpiglobal_out = cvDim_mpiglobal

    allocate(my_cv_mpiglobal(cvDim_mpiglobal)) 
    my_cv_mpiglobal(:) = 0.0d0

    do k = 1, nksdim
      do ila = 1, lst_bhi%nla
        do p = 1, lst_bhi%nphase

          jdim_mpilocal = ( (k-1) * lst_bhi%nla * lst_bhi%nphase ) + &
                                        ( (ila-1) * lst_bhi%nphase ) + p

          ilaGlb = lst_bhi%ilaGlobal(ila)
          jdim_mpiglobal = ( (k-1) * lst_bhi%nlaGlobal * lst_bhi%nphase ) + &
                                            ( (ilaGlb-1) * lst_bhi%nphase ) + p
  
          if ( jdim_mpilocal  > cvDim          ) then 
            write(*,*) 'LBHI_expandToMPIGlobal: jdim_mpilocal > cvDim ',k,ila,p,ilaGlb
            call abort3d('LBHI_expandToMPIGlobal')
          end if
          if ( jdim_mpiglobal > cvDim_mpiglobal) then
            write(*,*) 'LBHI_expandToMPIGlobal: jdim_mpiglobal > cvDim_mpiglobal ',k,ila,p,ilaGlb
            call abort3d('LBHI_expandToMPIGlobal')
          end if

          my_cv_mpiglobal(jdim_mpiglobal) = cv_mpilocal(jdim_mpilocal)

        end do
      end do
    end do

    call rpn_comm_allreduce(my_cv_mpiglobal,cv_mpiglobal,cvDim_mpiglobal,"mpi_double_precision","mpi_sum","GRID",ier)
    deallocate(my_cv_mpiglobal) 

  end SUBROUTINE LBHI_expandToMPIGlobal

!--------------------------------------------------------------------------
! LBHI_Finalize
!--------------------------------------------------------------------------

  subroutine LBHI_Finalize 1
    implicit none

    integer :: var

    deallocate(bsqrt)
    do var = 1, nControlVariable
      deallocate(ControlVariable(var)%GpStdDev)
      deallocate(ControlVariable(var)%ip1     )
    end do

  end subroutine LBHI_Finalize

end module LamBMatrixHI_mod