analysisErrorOI_mod sourceΒΆ

   1module analysisErrorOI_mod
   2  ! MODULE analysisErrorOI_mod (prefix='aer' category='1. High-level functionality')
   3  !
   4  !:Purpose:  Calculate the analysis-error standard deviation.
   5  !           The method used is Optimal Interpolation,
   6  !           where it is assumed that only a subset of the
   7  !           total number of observations influence the analysis at a given grid point.
   8  !
   9  use columnData_mod
  10  use gridStateVector_mod
  11  use gridStateVectorFileIO_mod
  12  use kdTree2_mod
  13  use midasMpi_mod
  14  use obsSpaceData_mod
  15  use physicsFunctions_mod
  16  use stateToColumn_mod
  17  use varNamelist_mod
  18  use utilities_mod
  19  use horizontalCoord_mod
  20  use verticalCoord_mod
  21  use obsOperators_mod
  22  use message_mod
  23  use oceanMask_mod
  24  use timeCoord_mod
  25
  26  implicit none
  27
  28  private
  29
  30  ! public subroutines and functions
  31  public :: aer_analysisError
  32
  33  type struct_neighborhood
  34   integer          :: numObs
  35   integer, pointer :: headerIndex(:)
  36   integer, pointer :: bodyIndex(:)
  37   integer, pointer :: procIndex(:)
  38  end type struct_neighborhood
  39
  40  integer, external :: get_max_rss
  41  integer, parameter :: maxNumLocalGridptsSearch = 1500
  42
  43contains
  44
  45  !--------------------------------------------------------------------------
  46  ! aer_analysisError
  47  !--------------------------------------------------------------------------
  48  subroutine aer_analysisError(obsSpaceData, hco_ptr, vco_ptr)
  49    !
  50    !:Purpose: Calculate analysis-error variance.
  51    !
  52    implicit none
  53
  54    ! Arguments:
  55    type(struct_obs),          intent(inout) :: obsSpaceData ! observation data structure
  56    type(struct_hco), pointer, intent(in)    :: hco_ptr      ! horizontal grid definition
  57    type(struct_vco), pointer, intent(in)    :: vco_ptr      ! vertical grid definition
  58
  59    ! Locals:
  60    integer :: fnom, fclos, nulnam, ierr
  61    type(struct_gsv) :: stateVectorAnlErrorStd      ! state vector for analysis error std deviation
  62    type(struct_gsv) :: stateVectorTrlErrorStd      ! state vector for background error std deviation
  63    real(8), pointer :: anlErrorStdDev_ptr(:,:,:,:) ! pointer for analysis error std deviation
  64    real(8), pointer :: trlErrorStdDev_ptr(:,:,:,:) ! pointer for background error std deviation
  65    integer, allocatable :: numObs(:,:)
  66    integer :: lonIndex, latIndex
  67    character(len=4), pointer :: analysisVariable(:)
  68    type(struct_gsv)          :: statevectorLcorr
  69    real(4), pointer          :: field3D_r4_ptr(:,:,:)
  70    type(struct_columnData)   :: column
  71    type(struct_columnData)   :: columng
  72    type(struct_ocm)          :: oceanMask
  73    real(8) :: leadTimeInHours
  74    character(len=20), parameter :: errStddevFileName_in  = 'errorstdev_in'        ! input  filename for anl ot trl error standard deviation
  75    character(len=20), parameter :: anlErrStddevFileName_out = 'anlerrorstdev_out' ! output filename for anl error std deviation
  76    character(len=20), parameter :: trlErrStddevFileName_out = 'trlerrorstdev_out' ! output filename for trl (background) error std deviation
  77    type(struct_neighborhood), pointer :: influentObs(:,:)
  78    real(8), allocatable :: Lcorr(:,:)
  79    
  80    ! namelist variables:
  81    real(8)           :: maxAnalysisErrorStdDev ! maximum limit imposed on analysis error stddev
  82    logical           :: propagateAnalysisError ! choose to propagate analysis error
  83    logical           :: propagateDSLO          ! choose to propagate Days Since Last Obs field
  84    real(4)           :: errorGrowth            ! seaice: fraction of ice per hour, SST: estimated growth
  85    character(len=12) :: analysisEtiket         ! analysis field etiket in a standard file
  86    character(len=12) :: anlErrorStdEtiket      ! analysis error standard deviation field etiket in the input/output standard files
  87    character(len=12) :: trlErrorStdEtiket      ! background error standard deviation field etiket in the input/output standard files
  88    integer           :: hoursSinceLastAnalysis ! number of hours since the last analysis
  89    logical           :: saveTrlStdField        ! choose to save trial standard deviation field
  90    character(len=2)  :: inputTypeVar           ! typvar of the analysis error field in the input file 
  91    character(len=2)  :: outputTypeVar          ! typvar of the analysis error field for the output file 
  92    real(4)           :: multFactorLcorr        ! multiplication scaling factor to increase the correlation length scale field
  93    namelist /namaer/ maxAnalysisErrorStdDev, propagateAnalysisError, propagateDSLO, &
  94                      errorGrowth, analysisEtiket, anlErrorStdEtiket, trlErrorStdEtiket, &
  95                      hoursSinceLastAnalysis, saveTrlStdField, inputTypeVar, outputTypeVar, &
  96                      multFactorLcorr
  97
  98    write(*,*) '**********************************************************'
  99    write(*,*) '** aer_analysisError: Calculate analysis-error variance **'
 100    write(*,*) '**********************************************************'
 101
 102    ! default namelist variable values
 103    maxAnalysisErrorStdDev = 1.0d0
 104    propagateAnalysisError = .false.
 105    propagateDSLO = .false.
 106    errorGrowth = 1.0
 107    analysisEtiket = ''
 108    anlErrorStdEtiket = 'A-ER STD DEV'
 109    trlErrorStdEtiket = 'B-ER STD DEV'
 110    hoursSinceLastAnalysis = 6
 111    saveTrlStdField = .false.
 112    inputTypeVar = 'P@'
 113    outputTypeVar = 'A@'
 114    multFactorLcorr = 1.0
 115    
 116    ! read the namelist
 117    if (.not. utl_isNamelistPresent('namaer','./flnml')) then
 118      if (mmpi_myid == 0) then
 119        call msg('aer_analysisError:', ' namaer is missing in the namelist.')
 120        call msg('aer_analysisError:', ' the default values will be taken.')
 121      end if
 122    else
 123      ! reading namelist variables
 124      nulnam = 0
 125      ierr = fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
 126      read(nulnam, nml = namaer, iostat = ierr)
 127      if (ierr /= 0) call utl_abort('aer_analysisError:: Error reading namelist')
 128      ierr = fclos(nulnam)
 129    end if
 130    write(*, nml = namaer)
 131
 132    nullify(analysisVariable)
 133    call gsv_varNamesList(analysisVariable)
 134    
 135    if (size(analysisVariable) > 1) then
 136      call utl_abort('aer_analysisError: Check namelist NAMSTATE. analysisVariable is greater than 1.')
 137    end if
 138
 139    if (analysisVariable(1) == 'GL') then
 140      call msg('aer_analysisError:', ' computing seaice analysis error...')
 141    else if(analysisVariable(1) == 'TM') then
 142      call msg('aer_analysisError:', ' computing SST analysis error...')
 143    else
 144      call utl_abort('aer_analysisError:: The current code does not work with '&
 145                     //trim(analysisVariable(1))//' analysis variable.')
 146    end if
 147
 148    call gsv_allocate(stateVectorAnlErrorStd, 1, hco_ptr, vco_ptr, dateStamp_opt = -1, &
 149                      mpi_local_opt = .false., mpi_distribution_opt = 'None', &
 150                      varNames_opt = (/analysisVariable(1)/), dataKind_opt = 8)
 151    call gsv_getField(stateVectorAnlErrorStd, anlErrorStdDev_ptr)
 152    call gsv_allocate(stateVectorTrlErrorStd, 1, hco_ptr, vco_ptr, dateStamp_opt = -1, &
 153                      mpi_local_opt = .false., mpi_distribution_opt = 'None', &
 154                      varNames_opt = (/analysisVariable(1)/), dataKind_opt = 8)
 155    call gsv_getField(stateVectorTrlErrorStd, trlErrorStdDev_ptr)
 156
 157    if (propagateAnalysisError) then     
 158      call msg('aer_analysisError:', &
 159               ' analysis error std field is read from: '//trim(errStddevFileName_in))    
 160      call gio_readFromFile(stateVectorAnlErrorStd, errStddevFileName_in, &
 161                            etiket_in = anlErrorStdEtiket, typvar_in = inputTypeVar, &
 162                            containsFullField_opt = .false.)
 163      
 164      ! initialize trl error std deviation field:
 165      trlErrorStdDev_ptr(:,:,:,:) = anlErrorStdDev_ptr(:,:,:,:)
 166
 167      call ocm_readMaskFromFile (oceanMask, hco_ptr, vco_ptr, errStddevFileName_in)
 168      call aer_propagateAnalysisError (stateVectorTrlErrorStd, oceanMask, &
 169                                       analysisVariable(1), &
 170                                       analysisEtiket, errorGrowth, &
 171                                       hco_ptr, vco_ptr)
 172
 173      ! impose maximum value on trial error standard deviation field
 174      trlErrorStdDev_ptr(:,:,:,:) = min(trlErrorStdDev_ptr(:,:,:,:), &
 175                                        maxAnalysisErrorStdDev)
 176
 177      if (saveTrlStdField) then
 178        ! zap analysis error etiket with background error etiket
 179        stateVectorTrlErrorStd%etiket = trlErrorStdEtiket
 180        
 181        ! copy mask from analysis error std deviation field to trl error std field
 182        call gsv_copyMask(stateVectorAnlErrorStd, stateVectorTrlErrorStd)
 183        
 184        ! update dateStamp from env variable
 185        call gsv_modifyDate(stateVectorTrlErrorStd, tim_getDateStamp(), &
 186                            modifyDateOrigin_opt = .true.)
 187
 188        ! save background error (increased analysis error) standard deviation field
 189        call gio_writeToFile(stateVectorTrlErrorStd, trlErrStddevFileName_out, &
 190                             trlErrorStdEtiket, typvar_opt = outputTypeVar, &
 191                             containsFullField_opt = .false.)
 192      end if
 193
 194    else
 195      call msg('aer_analysisError:', &
 196               ' trial error std field is read from: '//trim(errStddevFileName_in))
 197      call gio_readFromFile(stateVectorTrlErrorStd, errStddevFileName_in, &
 198                            etiket_in = trlErrorStdEtiket, typvar_in = inputTypeVar, &
 199                            containsFullField_opt = .false.)
 200      call gsv_copyMask(stateVectorTrlErrorStd, stateVectorAnlErrorStd)
 201    end if
 202
 203    leadTimeInHours = real(stateVectorTrlErrorStd%deet * stateVectorTrlErrorStd%npasList(1), 8) / 3600.0d0
 204    call incdatr(stateVectorAnlErrorStd%dateOriginList(1), &
 205                 stateVectorTrlErrorStd%dateOriginList(1), leadTimeInHours)
 206
 207    stateVectorAnlErrorStd%etiket = anlErrorStdEtiket
 208
 209    call col_setVco(column, vco_ptr)
 210    call col_allocate(column,  obs_numHeader(obsSpaceData), varNames_opt=(/analysisVariable(1)/))
 211    call col_setVco(columng, vco_ptr)
 212    call col_allocate(columng, obs_numHeader(obsSpaceData), varNames_opt=(/analysisVariable(1)/))
 213    call s2c_tl(stateVectorTrlErrorStd, column, columng, obsSpaceData)
 214
 215    allocate(Lcorr(hco_ptr%ni, hco_ptr%nj))
 216
 217    ! get correlation length scale field
 218    write(*,*) 'aer_analysisError: get correlation length scale field...'
 219    call gsv_allocate(statevectorLcorr, 1, hco_ptr, vco_ptr, dateStamp_opt = -1, &
 220                      mpi_local_opt = .false., mpi_distribution_opt = 'None', &
 221                      dataKind_opt = 4, hInterpolateDegree_opt = 'LINEAR', &
 222                      varNames_opt = (/analysisVariable(1)/))
 223    call gsv_zero(statevectorLcorr)
 224    call gio_readFromFile(statevectorLcorr, './bgstddev', 'CORRLEN', ' ', &
 225                          unitConversion_opt = .false.)
 226    call gsv_getField(statevectorLcorr, field3D_r4_ptr, analysisVariable(1))
 227
 228    ! apply multiplication scaling factor
 229    field3D_r4_ptr(:, :, 1) = field3D_r4_ptr(:, :, 1) * multFactorLcorr
 230
 231    ! Convert from km to meters
 232    Lcorr(:,:) = 1000.0d0 * real(field3D_r4_ptr(:, :, 1), 8)
 233
 234    write(*,*) 'aer_analysisError: min/max correlation length scale 2D field: ', &
 235               minval(Lcorr(:,:) ), maxval(Lcorr(:,:))
 236    call gsv_deallocate(statevectorLcorr)
 237
 238    write(*,*) 'aer_analysisError: done creating kdtree for stateVectorTrlErrorStd'
 239    write(*,*) 'Memory Used: ', get_max_rss() / 1024, 'Mb'
 240
 241    ! Go through all observations a first time to get
 242    ! the number of influential observations
 243    ! in order to allocate memory appropriately.
 244
 245    allocate(influentObs(hco_ptr%ni, hco_ptr%nj))
 246    allocate(numObs(hco_ptr%ni, hco_ptr%nj))
 247
 248    call msg('aer_analysisError:', ' looking for observations...')
 249    call findObs(obsSpaceData, stateVectorTrlErrorStd, numObs, &
 250                 analysisVariable(1), Lcorr, influentObs)
 251    write(*,*) 'aer_analysisError: min/max of numObs = ', minval(numObs), maxval(numObs)
 252
 253    ! Memory allocation
 254
 255    do latIndex = 1, hco_ptr%nj
 256      do lonIndex = 1, hco_ptr%ni
 257        influentObs(lonIndex, latIndex)%numObs = numObs(lonIndex,latIndex)
 258        allocate(influentObs(lonIndex, latIndex)%headerIndex(numObs(lonIndex, latIndex)))
 259        allocate(influentObs(lonIndex, latIndex)%bodyIndex(numObs(lonIndex, latIndex)))
 260        allocate(influentObs(lonIndex, latIndex)%procIndex(numObs(lonIndex, latIndex)))
 261      end do
 262    end do
 263
 264    ! Go through all observations a second time to get
 265    ! the indexes of the influential observations.
 266    ! This is not efficient to go through all observations twice
 267    ! but it saves lot of memory space.
 268
 269    call msg('aer_analysisError:', ' go through all observations a second time...')
 270    call findObs(obsSpaceData, stateVectorTrlErrorStd, numObs, &
 271                 analysisVariable(1), Lcorr, influentObs)
 272
 273    deallocate(numObs)
 274
 275    write(*,*) 'aer_analysisError:: obs found'
 276    write(*,*) 'Memory Used: ',get_max_rss() / 1024, 'Mb'
 277
 278    ! compute analysis error standard deviation
 279    call aer_computeAnlErrorStd(obsSpaceData, stateVectorAnlErrorStd, stateVectorTrlErrorStd, &
 280                                analysisVariable(1), maxAnalysisErrorStdDev, influentObs, Lcorr)
 281
 282    deallocate(influentObs)
 283    deallocate(Lcorr)
 284
 285    ! update dateStamp from env variable
 286    call gsv_modifyDate(stateVectorAnlErrorStd, tim_getDateStamp(), &
 287                        modifyDateOrigin_opt = .true.)
 288
 289    ! save analysis error
 290    call msg('aer_analysisError:', ' writing analysis error std field to output file...')
 291    if (mmpi_myid == 0) then
 292      call gio_writeToFile(stateVectorAnlErrorStd, anlErrStddevFileName_out, &
 293                           anlErrorStdEtiket, typvar_opt = outputTypeVar, &
 294                           containsFullField_opt = .false.)
 295    end if
 296
 297    call col_deallocate(columng)
 298    call col_deallocate(column)
 299    call gsv_deallocate(stateVectorAnlErrorStd)
 300    call gsv_deallocate(stateVectorTrlErrorStd)
 301
 302    if (analysisVariable(1) == 'GL') then
 303      ! Update the Days Since Last Obs
 304      call aer_daysSinceLastObs(obsSpaceData, hco_ptr, vco_ptr, &
 305                                errStddevFileName_in, anlErrStddevFileName_out, &
 306                                analysisVariable(1), propagateDSLO, &
 307                                hoursSinceLastAnalysis)
 308    end if
 309    
 310    call msg('aer_analysisError:', ' finished.')
 311
 312  end subroutine aer_analysisError
 313
 314  !---------------------------------------------------------
 315  ! findObs
 316  !---------------------------------------------------------
 317  subroutine findObs(obsSpaceData, stateVectorTrlErrorStd, numObs, variableName, Lcorr, influentObs)
 318    !
 319    !:Purpose: Find all observations used for the analysis.
 320    !
 321    implicit none
 322
 323    ! Arguments:
 324    type(struct_obs),                   intent(in)    :: obsSpaceData     ! observation data structure
 325    type(struct_gsv),                   intent(in)    :: stateVectorTrlErrorStd ! state containing background error stddev
 326    integer,                            intent(out)   :: numObs(:,:)      ! number of observations found
 327    character(len=*),                   intent(in)    :: variableName     ! 'GL' for seaice or 'TM' for SST
 328    real(8)         ,                   intent(in)    :: Lcorr(:,:)       ! horizontal background-error correlation length scale
 329    type(struct_neighborhood), pointer, intent(inout) :: influentObs(:,:) ! details about observations to use in update
 330
 331    ! Locals:
 332    integer :: headerIndex, bodyIndexBeg, bodyIndexEnd, bodyIndex
 333    integer :: procIndex, kIndex, stepIndex
 334    integer :: gridptCount, gridpt, numLocalGridptsFoundSearch
 335    integer :: lonIndex, latIndex, resultsIndex, gridIndex, numStep
 336    type(kdtree2_result) :: searchResults(maxNumLocalGridptsSearch)
 337    real(kdkind)         :: refPosition(3), maxRadiusSquared
 338    real(4) :: footprintRadius_r4 ! (metres) used for seaice observations only
 339    real(4) :: influenceRadius_r4 ! (metres)
 340    real(8) :: obsLonInRad, obsLatInRad, maxLcorr
 341    type(kdtree2), pointer :: tree
 342    real(kdkind), allocatable :: positionArray(:,:)
 343    real(8),      allocatable :: latInRad(:,:), lonInRad(:,:)
 344    real(8) :: interpWeight(maxNumLocalGridptsSearch)
 345    integer :: obsLatIndex(maxNumLocalGridptsSearch), obsLonIndex(maxNumLocalGridptsSearch)
 346    integer, allocatable :: obsAss(:), allObsAss(:,:)
 347    integer, allocatable :: obsRln(:), allObsRln(:,:)
 348    integer, allocatable :: obsNlv(:), allObsNlv(:,:)
 349    integer :: ierr, numHeaderMax, allNumHeader(mmpi_nprocs), numBodyMax, allNumBody(mmpi_nprocs)
 350    real(4), allocatable :: footprintRadiusVec_r4(:), allFootprintRadius_r4(:,:)
 351    real(8), allocatable :: obsLon(:), allObsLon(:,:), obsLat(:), allObsLat(:,:)
 352
 353    numStep = stateVectorTrlErrorStd%numStep
 354    if (numStep > 1) call utl_abort('aer findObs: Code is not adapted for numStep > 1')
 355
 356    ! Communicate some quantities to all MPI tasks
 357
 358    call rpn_comm_allgather(obs_numHeader(obsSpaceData), 1, 'mpi_integer',       &
 359                            allNumHeader, 1, 'mpi_integer', 'GRID', ierr)
 360    numHeaderMax = maxval(allNumHeader)
 361    call rpn_comm_allgather(obs_numBody(obsSpaceData), 1, 'mpi_integer',       &
 362                            allNumBody, 1, 'mpi_integer', 'GRID', ierr)
 363    numBodyMax = maxval(allNumBody)
 364
 365    allocate(obsAss(numBodyMax))
 366    obsAss(:) = 0
 367    allocate(allObsAss(numBodyMax, mmpi_nprocs))
 368    do bodyIndex = 1, obs_numBody(obsSpaceData)
 369      obsAss(bodyIndex) = obs_notassimilated
 370      if (obs_bodyElem_i(obsSpaceData, obs_ass, bodyIndex) == obs_assimilated) then
 371        obsAss(bodyIndex) = obs_assimilated
 372      end if
 373    end do
 374    call rpn_comm_gather(obsAss,    numBodyMax, 'mpi_integer',  &
 375                         allObsAss, numBodyMax, 'mpi_integer', 0, 'grid', ierr)
 376    
 377    allocate(obsRln(numHeaderMax))
 378    obsRln(:) = 0
 379    allocate(obsNlv(numHeaderMax))
 380    obsNlv(:) = 0
 381    allocate(obsLon(numHeaderMax))
 382    obsLon(:) = 0.0d0
 383    allocate(obsLat(numHeaderMax))
 384    obsLat(:) = 0.0d0
 385    do headerIndex = 1, obs_numHeader(obsSpaceData)
 386      obsRln(headerIndex) = obs_headElem_i(obsSpaceData, obs_rln, headerIndex)
 387      obsNlv(headerIndex) = obs_headElem_i(obsSpaceData, obs_nlv, headerIndex)
 388      obsLon(headerIndex) = obs_headElem_r(obsSpaceData, obs_lon, headerIndex)
 389      obsLat(headerIndex) = obs_headElem_r(obsSpaceData, obs_lat, headerIndex)
 390    end do
 391    allocate(allObsRln(numHeaderMax,mmpi_nprocs))
 392    allocate(allObsNlv(numHeaderMax,mmpi_nprocs))
 393    allocate(allObsLon(numHeaderMax,mmpi_nprocs))
 394    allocate(allObsLat(numHeaderMax,mmpi_nprocs))
 395    call rpn_comm_gather(obsRln,    numHeaderMax, 'mpi_integer',  &
 396                         allObsRln, numHeaderMax, 'mpi_integer', 0, 'grid', ierr)
 397    call rpn_comm_gather(obsNlv,    numHeaderMax, 'mpi_integer',  &
 398                         allObsNlv, numHeaderMax, 'mpi_integer', 0, 'grid', ierr)
 399    call rpn_comm_gather(obsLon,    numHeaderMax, 'mpi_real8',  &
 400                         allObsLon, numHeaderMax, 'mpi_real8', 0, 'grid', ierr)
 401    call rpn_comm_gather(obsLat,    numHeaderMax, 'mpi_real8',  &
 402                         allObsLat, numHeaderMax, 'mpi_real8', 0, 'grid', ierr)
 403
 404    allocate(footprintRadiusVec_r4(numHeaderMax))
 405    do headerIndex = 1, obs_numHeader(obsSpaceData)
 406      footprintRadiusVec_r4(headerIndex) = s2c_getFootprintRadius(obsSpaceData, stateVectorTrlErrorStd, headerIndex)
 407    end do
 408    allocate(allFootprintRadius_r4(numHeaderMax,mmpi_nprocs))
 409    call rpn_comm_gather(footprintRadiusVec_r4,      numHeaderMax, 'MPI_REAL4', &
 410                         allFootprintRadius_r4(:,:), numHeaderMax, 'MPI_REAL4', &
 411                         0, 'GRID', ierr)
 412
 413    ! create kdtree
 414    write(*,*) 'findObs: start creating kdtree for stateVectorTrlErrorStd'
 415    write(*,*) 'Memory Used: ', get_max_rss() / 1024, 'Mb'
 416
 417    allocate(positionArray(3, stateVectorTrlErrorStd%hco%ni * stateVectorTrlErrorStd%hco%nj))
 418    allocate(lonInRad(stateVectorTrlErrorStd%hco%ni, stateVectorTrlErrorStd%hco%nj))
 419    allocate(latInRad(stateVectorTrlErrorStd%hco%ni, stateVectorTrlErrorStd%hco%nj))
 420
 421    gridIndex = 0
 422    do latIndex = 1, stateVectorTrlErrorStd%hco%nj
 423      do lonIndex = 1, stateVectorTrlErrorStd%hco%ni
 424        gridIndex = gridIndex + 1
 425        latInRad(lonIndex, latIndex) = real(stateVectorTrlErrorStd%hco%lat2d_4(lonIndex, latIndex), 8)
 426        lonInRad(lonIndex, latIndex) = real(stateVectorTrlErrorStd%hco%lon2d_4(lonIndex, latIndex), 8)
 427        positionArray(:, gridIndex) = kdtree2_3dPosition(lonInRad(lonIndex, latIndex), &
 428                                                         latInRad(lonIndex, latIndex))
 429      end do
 430    end do
 431
 432    write(*,*) 'findObs: latInRad min/max: ', minval(latInRad(:,:)), maxval(latInRad(:,:))
 433    write(*,*) 'findObs: lonInRad min/max: ', minval(lonInRad(:,:)), maxval(lonInRad(:,:))
 434
 435    nullify(tree)
 436    tree => kdtree2_create(positionArray, sort = .false., rearrange = .true.) 
 437
 438    deallocate(positionArray)
 439    deallocate(lonInRad)
 440    deallocate(latInRad)
 441
 442    maxLcorr = maxval(Lcorr(:,:))
 443    numObs(:,:) = 0
 444
 445    if (mmpi_myid == 0) then
 446      PROC_LOOP: do procIndex = 1, mmpi_nprocs
 447        HEADER_LOOP: do headerIndex = 1, allNumHeader(procIndex)
 448
 449          bodyIndexBeg = allObsRln(headerIndex,procIndex)
 450          bodyIndexEnd = allObsNlv(headerIndex,procIndex) + bodyIndexBeg - 1
 451
 452          BODY_LOOP: do bodyIndex = bodyIndexBeg, bodyIndexEnd
 453
 454            if (allObsAss(bodyIndex,procIndex) /= obs_assimilated) then
 455              cycle BODY_LOOP
 456            end if
 457
 458            if (trim(variableName) == 'GL') then
 459              footprintRadius_r4 = allFootPrintRadius_r4(headerIndex, procIndex)
 460              influenceRadius_r4 = max(0.0, footprintRadius_r4) + maxLcorr
 461            else if (trim(variableName) == 'TM') then 
 462              influenceRadius_r4 = maxLcorr
 463            else
 464              call utl_abort('findObs: The current code does not work with '&
 465                             //trim(variableName)//' analysis variable.')
 466            end if
 467
 468            if (maxLcorr == 0.0d0) then
 469
 470              do kIndex = stateVectorTrlErrorStd%mykBeg, stateVectorTrlErrorStd%mykEnd
 471                do stepIndex = 1, stateVectorTrlErrorStd%numStep
 472
 473                  call s2c_getWeightsAndGridPointIndexes(headerIndex, kIndex, stepIndex, &
 474                                                         procIndex, interpWeight, obsLatIndex, &
 475                                                         obsLonIndex, gridptCount)
 476
 477                  GRIDPT_LOOP: do gridpt = 1, gridptCount
 478
 479                    if (interpWeight(gridpt) == 0.0d0) cycle GRIDPT_LOOP
 480
 481                    lonIndex = obsLonIndex(gridpt)
 482                    latIndex = obsLatIndex(gridpt)
 483
 484                    numObs(lonIndex, latIndex) = numObs(lonIndex, latIndex) + 1
 485                    if(associated(influentObs(lonIndex, latIndex)%bodyIndex)) then
 486                      if(numObs(lonIndex, latIndex) > influentObs(lonIndex, latIndex)%numObs) then
 487                        call utl_abort('findObs: Array too small')
 488                      end if
 489                      influentObs(lonIndex, latIndex)%headerIndex(numObs(lonIndex, latIndex)) = headerIndex
 490                      influentObs(lonIndex, latIndex)%bodyIndex(numObs(lonIndex, latIndex)) = bodyIndex
 491                      influentObs(lonIndex, latIndex)%procIndex(numObs(lonIndex, latIndex)) = procIndex
 492                    end if
 493
 494                  end do GRIDPT_LOOP
 495
 496                end do
 497              end do
 498
 499            else
 500
 501              ! Determine the grid point nearest the observation.
 502
 503              obsLonInRad = allObsLon(headerIndex, procIndex)
 504              obsLatInRad = allObsLat(headerIndex, procIndex)
 505
 506              ! do the search
 507              maxRadiusSquared = real(influenceRadius_r4, 8) ** 2
 508              refPosition(:) = kdtree2_3dPosition(obsLonInRad, obsLatInRad)
 509              call kdtree2_r_nearest(tp = tree, qv = refPosition, r2 = maxRadiusSquared, &
 510                                     nfound = numLocalGridptsFoundSearch, &
 511                                     nalloc = maxNumLocalGridptsSearch, &
 512                                     results = searchResults)
 513
 514              if (numLocalGridptsFoundSearch > maxNumLocalGridptsSearch) then
 515                call utl_abort('findObs: parameter maxNumLocalGridptsSearch must be increased')
 516              end if
 517
 518              do resultsIndex = 1, numLocalGridptsFoundSearch
 519
 520                gridIndex = searchResults(resultsIndex)%idx
 521                if (gridIndex < 1 .or. &
 522                     gridIndex > stateVectorTrlErrorStd%hco%ni * stateVectorTrlErrorStd%hco%nj) then
 523                  write(*,*) 'analysisErrorStdDev: gridIndex = ', gridIndex
 524                  call utl_abort('findObs: gridIndex out of bound.')
 525                end if
 526
 527                latIndex = (gridIndex - 1) / stateVectorTrlErrorStd%hco%ni + 1
 528                lonIndex = gridIndex - (latIndex - 1) * stateVectorTrlErrorStd%hco%ni
 529                if (lonIndex < 1 .or. lonIndex > stateVectorTrlErrorStd%hco%ni .or. &
 530                     latIndex < 1 .or. latIndex > stateVectorTrlErrorStd%hco%nj) then
 531                  write(*,*) 'analysisErrorStdDev: lonIndex = ', lonIndex, &
 532                                                ', latIndex = ', latIndex
 533                  call utl_abort('findObs: lonIndex/latIndex out of bound.')
 534                end if
 535
 536                numObs(lonIndex, latIndex) = numObs(lonIndex, latIndex) + 1
 537                if (associated(influentObs(lonIndex, latIndex)%bodyIndex)) then
 538                  if (numObs(lonIndex, latIndex) > influentObs(lonIndex, latIndex)%numObs) then
 539                    call utl_abort('findObs: Array too small in subroutine findObs')
 540                  end if
 541                  influentObs(lonIndex, latIndex)%headerIndex(numObs(lonIndex, latIndex)) = headerIndex
 542                  influentObs(lonIndex, latIndex)%bodyIndex(numObs(lonIndex, latIndex)) = bodyIndex
 543                  influentObs(lonIndex, latIndex)%procIndex(numObs(lonIndex, latIndex)) = procIndex
 544                end if
 545
 546              end do
 547
 548            end if
 549
 550          end do BODY_LOOP
 551
 552        end do HEADER_LOOP
 553
 554      end do PROC_LOOP
 555    end if
 556
 557    ! Communicate values from proc 0 to others
 558    if (mmpi_nprocs > 1) then
 559      call rpn_comm_bcast(numObs, size(numObs), 'MPI_INTEGER', 0, 'GRID', ierr)
 560      if (associated(influentObs(1,1)%bodyIndex)) then
 561        write(*,*) 'findObs: communicate bodyIndex, headerIndex'
 562        do latIndex = 1, stateVectorTrlErrorStd%hco%nj
 563          do lonIndex = 1, stateVectorTrlErrorStd%hco%ni
 564            call rpn_comm_bcast(influentObs(lonIndex, latIndex)%headerIndex,  &
 565                                influentObs(lonIndex, latIndex)%numObs, 'MPI_INTEGER', 0, 'GRID', ierr)
 566            call rpn_comm_bcast(influentObs(lonIndex, latIndex)%bodyIndex,    &
 567                                influentObs(lonIndex, latIndex)%numObs, 'MPI_INTEGER', 0, 'GRID', ierr)
 568            call rpn_comm_bcast(influentObs(lonIndex, latIndex)%procIndex,    &
 569                                influentObs(lonIndex, latIndex)%numObs, 'MPI_INTEGER', 0, 'GRID', ierr)
 570          end do
 571        end do
 572      end if
 573    end if
 574
 575    deallocate(obsAss)
 576    deallocate(allObsAss)
 577    deallocate(obsRln)
 578    deallocate(obsNlv)
 579    deallocate(obsLon)
 580    deallocate(obsLat)
 581    deallocate(allObsRln)
 582    deallocate(allObsNlv)
 583    deallocate(allObsLon)
 584    deallocate(allObsLat)
 585    deallocate(footprintRadiusVec_r4)
 586    deallocate(allFootprintRadius_r4)
 587
 588  end subroutine findObs
 589
 590  !---------------------------------------------------------
 591  ! aer_daysSinceLastObs
 592  !---------------------------------------------------------
 593  subroutine aer_daysSinceLastObs(obsSpaceData, hco_ptr, vco_ptr, &
 594                                  inputFileName, outputFileName, &
 595                                  variableName, propagateDSLO, &
 596                                  hoursSinceLastAnalysis)
 597    !
 598    !:Purpose: Update the field "days since last obs" with the newly assimilated obs.
 599    !
 600    implicit none
 601
 602    ! Arguments:
 603    type(struct_obs),          intent(inout) :: obsSpaceData   ! observation data structure
 604    type(struct_hco), pointer, intent(in)    :: hco_ptr        ! horizontal grid definition
 605    type(struct_vco), pointer, intent(in)    :: vco_ptr        ! vertical grid definition
 606    character(len=*),          intent(in)    :: inputFileName  ! input file name
 607    character(len=*),          intent(in)    :: outputFileName ! output file name
 608    character(len=*),          intent(in)    :: variableName   ! name of variable being treated
 609    logical         ,          intent(in)    :: propagateDSLO  ! propagate (increase) Days Since Last Obs in time
 610    integer         ,          intent(in)    :: hoursSinceLastAnalysis ! number of hours between analysis times
 611
 612    ! Locals:
 613    type(struct_gsv) :: stateVectorTrlDSLO, stateVectorAnlDSLO
 614    real(8), pointer :: trlDSLO_ptr(:,:,:,:), anlDSLO_ptr(:,:,:,:)
 615    integer :: stepIndex, levIndex, lonIndex, latIndex, headerIndex
 616    integer :: bodyIndexBeg, bodyIndexEnd, bodyIndex, kIndex, procIndex
 617    integer :: gridptCount, gridpt
 618    type(struct_columnData) :: column, columng
 619    real(8) :: leadTimeInHours, interpWeight(maxNumLocalGridptsSearch)
 620    integer :: obsLatIndex(maxNumLocalGridptsSearch), obsLonIndex(maxNumLocalGridptsSearch)
 621    integer :: ierr, numHeaderMax, allNumHeader(mmpi_nprocs)
 622    integer, allocatable :: obsAss(:), obsAssMpiGlobal(:,:)
 623
 624    write(*,*) '**********************************************************'
 625    write(*,*) '** aer_daysSinceLastObs: Update the days since last obs **'
 626    write(*,*) '**********************************************************'
 627
 628    write(*,*) 'aer_daysSinceLastObs: input variable: ', trim(variableName)
 629
 630    call gsv_allocate(stateVectorTrlDSLO, 1, hco_ptr, vco_ptr, dateStamp_opt = -1, &
 631                      mpi_local_opt = .false., mpi_distribution_opt = 'None', &
 632                      varNames_opt=(/'DSLO'/), dataKind_opt = 8)
 633    call gsv_allocate(stateVectorAnlDSLO,  1, hco_ptr, vco_ptr, dateStamp_opt = -1, &
 634                      mpi_local_opt = .false., mpi_distribution_opt = 'None', &
 635                      varNames_opt = (/'DSLO'/), dataKind_opt = 8)
 636
 637    call rpn_comm_allgather(obs_numHeader(obsSpaceData), 1, 'mpi_integer',       &
 638                            allNumHeader, 1, 'mpi_integer', 'GRID', ierr)
 639
 640    if (propagateDSLO) then
 641      call aer_propagateDSLO(stateVectorTrlDSLO, inputFileName, outputFileName, &
 642                             hoursSinceLastAnalysis, hco_ptr)
 643    else
 644      write(*,*) 'aer_daysSinceLastObs: DSLO trial field is read from: ', trim(inputFileName)
 645      call gio_readFromFile(stateVectorTrlDSLO, inputFileName, &
 646                            etiket_in = ' ', typvar_in = 'P@')
 647    end if
 648
 649    leadTimeInHours = real(stateVectorTrlDSLO%deet * stateVectorTrlDSLO%npasList(1), 8) / 3600.0d0
 650    call incdatr(stateVectorAnlDSLO%dateOriginList(1), stateVectorTrlDSLO%dateOriginList(1), &
 651                 leadTimeInHours)
 652
 653    call gsv_copyMask(stateVectorTrlDSLO, stateVectorAnlDSLO)
 654    stateVectorAnlDSLO%etiket = stateVectorTrlDSLO%etiket
 655
 656    call col_setVco(column, vco_ptr)
 657    call col_allocate(column,  obs_numHeader(obsSpaceData), varNames_opt=(/'DSLO'/))
 658    call col_setVco(columng, vco_ptr)
 659    call col_allocate(columng, obs_numHeader(obsSpaceData), varNames_opt=(/'DSLO'/))
 660    call s2c_tl(stateVectorTrlDSLO, column, columng, obsSpaceData)
 661
 662    call gsv_getField(stateVectorTrlDSLO, trlDSLO_ptr, 'DSLO')
 663    call gsv_getField(stateVectorAnlDSLO, anlDSLO_ptr, 'DSLO')
 664
 665    ! Initialisation
 666    do stepIndex = 1, stateVectorTrlDSLO%numStep
 667      do levIndex = 1, gsv_getNumLev(stateVectorTrlDSLO, vnl_varLevelFromVarname('DSLO'))
 668        do latIndex = 1, hco_ptr%nj
 669          do lonIndex = 1, hco_ptr%ni
 670            anlDSLO_ptr(lonIndex, latIndex, levIndex, stepIndex) = &
 671               trlDSLO_ptr(lonIndex, latIndex, levIndex, stepIndex)
 672          end do
 673        end do
 674      end do
 675    end do
 676
 677    numHeaderMax = maxval(allNumHeader)
 678    allocate(obsAss(numHeaderMax))
 679    allocate(obsAssMpiGlobal(numHeaderMax, mmpi_nprocs))
 680    do headerIndex = 1, obs_numHeader(obsSpaceData)
 681      obsAss(headerIndex) = obs_notassimilated
 682      bodyIndexBeg = obs_headElem_i(obsSpaceData, obs_rln, headerIndex)
 683      bodyIndexEnd = obs_headElem_i(obsSpaceData, obs_nlv, headerIndex) + bodyIndexBeg - 1
 684
 685      do bodyIndex = bodyIndexBeg, bodyIndexEnd
 686        if (obs_bodyElem_i(obsSpaceData, obs_ass, bodyIndex) == obs_assimilated) then
 687          obsAss(headerIndex) = obs_assimilated
 688        end if
 689      end do
 690    end do
 691    call rpn_comm_gather(obsAss,          numHeaderMax, 'mpi_integer',  &
 692                         obsAssMpiGlobal, numHeaderMax, 'mpi_integer', 0, 'grid', ierr)
 693
 694    if (mmpi_myid == 0) then
 695      do procIndex = 1, mmpi_nprocs
 696        HEADER_LOOP: do headerIndex = 1, allNumHeader(procIndex)
 697
 698          if (obsAssMpiGlobal(headerIndex,procIndex) /= obs_assimilated) then
 699            cycle HEADER_LOOP
 700          end if
 701
 702          do kIndex = stateVectorTrlDSLO%mykBeg, stateVectorTrlDSLO%mykEnd
 703            do stepIndex = 1, stateVectorTrlDSLO%numStep
 704              call s2c_getWeightsAndGridPointIndexes(headerIndex, kIndex, stepIndex, &
 705                                                     procIndex, interpWeight, obsLatIndex, &
 706                                                     obsLonIndex, gridptCount)
 707
 708              GRIDPT_LOOP: do gridpt = 1, gridptCount
 709
 710                if (interpWeight(gridpt) == 0.0d0) cycle GRIDPT_LOOP
 711
 712                lonIndex = obsLonIndex(gridpt)
 713                latIndex = obsLatIndex(gridpt)
 714
 715                do levIndex = 1, gsv_getNumLev(stateVectorTrlDSLO, vnl_varLevelFromVarname('DSLO'))
 716                  anlDSLO_ptr(lonIndex, latIndex, levIndex, stepIndex) = 0.0
 717                end do
 718
 719              end do GRIDPT_LOOP
 720            end do
 721          end do
 722
 723        end do HEADER_LOOP
 724      end do
 725      
 726      call gio_writeToFile(stateVectorAnlDSLO, outputFileName, '', typvar_opt = 'A@', &
 727                           containsFullField_opt = .false. )
 728    end if
 729
 730    deallocate(obsAss)
 731    deallocate(obsAssMpiGlobal)
 732    call col_deallocate(columng)
 733    call col_deallocate(column)
 734    call gsv_deallocate(stateVectorTrlDSLO)
 735    call gsv_deallocate(stateVectorAnlDSLO)
 736
 737  end subroutine aer_daysSinceLastObs
 738
 739  !---------------------------------------------------------
 740  ! aer_propagateAnalysisError
 741  !---------------------------------------------------------
 742  subroutine aer_propagateAnalysisError(stateVectorErrorStd, oceanMask, variableName, &
 743                                        analysisEtiket, errorGrowth, hco_ptr, vco_ptr)
 744    !
 745    !:Purpose: read analysis error standard deviation field and propagate it forward in time
 746    !
 747    implicit none
 748
 749    ! Arguments:
 750    type(struct_gsv),          intent(inout) :: stateVectorErrorStd ! Input: analysis std error; Output: background std error. 
 751    type(struct_ocm),          intent(in)    :: oceanMask           ! ocean-land mask (1=water, 0=land)
 752    character(len=*),          intent(in)    :: variableName        ! variable name
 753    character(len=*),          intent(in)    :: analysisEtiket      ! analysis etiket in the input std file 
 754    real(4)         ,          intent(in)    :: errorGrowth         ! seaice: fraction of ice per hour, SST: estimated growth
 755    type(struct_hco), pointer, intent(in)    :: hco_ptr             ! horizontal coordinates structure, pointer
 756    type(struct_vco), pointer, intent(in)    :: vco_ptr             ! vertical coordinates structure, pointer
 757
 758    ! Locals:
 759    type(struct_gsv) :: stateVectorAnalysis
 760    integer :: latIndex, lonIndex, localLatIndex, localLonIndex, pointCount 
 761    real(8), pointer :: stateVectorStdError_ptr(:,:,:), stateVectorAnalysis_ptr(:,:,:)
 762    real(4) :: totalLocalVariance
 763
 764    write(*,*) ''
 765    write(*,*) 'aer_propagateAnalysisError: propagate analysis error forward in time for: ', &
 766               trim(variableName)
 767    
 768    ! read analysis itself (seaice concentration or SST analysis)
 769    call msg('aer_propagateAnalysisError:', ' reading analysis field...')
 770    call gsv_allocate(stateVectorAnalysis, 1, hco_ptr, vco_ptr, dateStamp_opt = -1, &
 771                      mpi_local_opt = .false., mpi_distribution_opt = 'None', &
 772                      varNames_opt = (/trim(variableName)/), dataKind_opt = 8)
 773    call gio_readFromFile(stateVectorAnalysis, './anlm_000m', analysisEtiket, &
 774                          'A@', containsFullField_opt = .true.)
 775    call gsv_getField(stateVectorAnalysis, stateVectorAnalysis_ptr)
 776
 777    ! initialize pointer for the error standard deviation field    
 778    call gsv_getField(stateVectorErrorStd, stateVectorStdError_ptr)
 779    
 780    ! calculation in variance unit
 781    stateVectorStdError_ptr(:,:,1) = stateVectorStdError_ptr(:,:,1)**2 
 782
 783    pointCount = 0
 784    totalLocalVariance = 0.0d0
 785
 786    do latIndex = 1, hco_ptr%nj
 787      do lonIndex = 1, hco_ptr%ni
 788
 789        OCEANPOINTS: if (oceanMask%mask(lonIndex, latIndex, 1)) then
 790          do localLatIndex = latIndex - 1, latIndex + 1
 791            if (localLatIndex >= 1 .and. localLatIndex <= hco_ptr%nj) then
 792              do localLonIndex = lonIndex - 1,  lonIndex + 1
 793                if (localLonIndex >= 1 .and. localLonIndex <= hco_ptr%ni) then
 794                  if (oceanMask%mask(localLonIndex, localLatIndex, 1) .and. &
 795                      (localLonIndex /= lonIndex .or. localLatIndex /= latIndex)) then
 796
 797                    pointCount = pointCount + 1
 798                    totalLocalVariance = totalLocalVariance + &
 799                                         (stateVectorAnalysis_ptr(localLonIndex, localLatIndex, 1) - &
 800                                          stateVectorAnalysis_ptr(lonIndex, latIndex, 1))**2
 801
 802                  end if
 803                end if
 804              end do
 805            end if
 806          end do
 807        end if OCEANPOINTS
 808
 809        if (pointCount > 0) then
 810          totalLocalVariance = totalLocalVariance / real(pointCount)
 811          if (stateVectorStdError_ptr(lonIndex, latIndex,1) < totalLocalVariance) then
 812            stateVectorStdError_ptr(lonIndex, latIndex, 1) = totalLocalVariance
 813          end if
 814        end if
 815
 816      end do
 817    end do
 818
 819    call gsv_deallocate(stateVectorAnalysis)
 820
 821    ! Back to standard deviation
 822    stateVectorStdError_ptr(:, :, 1) = sqrt(stateVectorStdError_ptr(:, :, 1))
 823
 824    ! Add the standard deviation increment
 825    do latIndex = 1, hco_ptr%nj
 826      do lonIndex = 1, hco_ptr%ni
 827        if (trim(variableName) == 'GL') then
 828          stateVectorStdError_ptr(lonIndex, latIndex, 1) = &
 829                     min(stateVectorStdError_ptr(lonIndex, latIndex, 1) + &
 830                         errorGrowth * tim_dstepobs, 1.0)
 831        else if (trim(variableName) == 'TM') then
 832          stateVectorStdError_ptr(lonIndex, latIndex, 1) = &
 833                         stateVectorStdError_ptr(lonIndex, latIndex, 1) + &
 834                         errorGrowth * tim_dstepobs / 2.0
 835        end if
 836      end do
 837    end do
 838
 839  end subroutine aer_propagateAnalysisError
 840
 841  !---------------------------------------------------------
 842  ! aer_propagateDSLO
 843  !---------------------------------------------------------
 844  subroutine aer_propagateDSLO(stateVectorErrorStd, inputFileName, outputFileName, &
 845                               hoursSinceLastAnalysis, hco_ptr)
 846    !
 847    !:Purpose: propagate the field "days since last obs" in time.
 848    !
 849    implicit none
 850
 851    ! Arguments:
 852    type(struct_gsv),          intent(inout) :: stateVectorErrorStd    ! read "analysis" into it and increase by hoursSinceLastAnalysis
 853    character(len=*),          intent(in)    :: inputFileName          ! input file name
 854    character(len=*),          intent(in)    :: outputFileName         ! output file name
 855    integer         ,          intent(in)    :: hoursSinceLastAnalysis ! hours since last analysis (namelist variable)
 856    type(struct_hco), pointer, intent(in)    :: hco_ptr                ! horizontal grid definition
 857
 858    ! Locals:
 859    real(8), pointer :: analysisDaysSinceLastObs_ptr(:,:,:)
 860    real(8) :: daysSinceLastAnalysis
 861    integer :: latIndex, lonIndex
 862
 863    write(*,*) 'aer_propagateDSLO: propagating in time the Days Since Last Obs field...'
 864    write(*,*) 'aer_propagateDSLO: hours since last analysis: ', hoursSinceLastAnalysis
 865
 866    daysSinceLastAnalysis = real(hoursSinceLastAnalysis, 8) / 24.0d0
 867
 868    ! get DSLO field
 869    call gio_readFromFile(stateVectorErrorStd, inputFileName, '', 'A@')
 870    call gsv_getField(stateVectorErrorStd, analysisDaysSinceLastObs_ptr)
 871
 872    ! Add number of days since last obs
 873    do latIndex = 1, hco_ptr%nj
 874      do lonIndex = 1, hco_ptr%ni
 875        analysisDaysSinceLastObs_ptr(lonIndex, latIndex, 1) = analysisDaysSinceLastObs_ptr(lonIndex, latIndex, 1) + &
 876                                                              daysSinceLastAnalysis
 877      end do
 878    end do
 879
 880    ! update dateStamp from env variable
 881    call gsv_modifyDate(stateVectorErrorStd, tim_getDateStamp(), &
 882                        modifyDateOrigin_opt = .true.)
 883
 884    ! save increased DSLO field into an fst-file
 885    call gio_writeToFile(stateVectorErrorStd, outputFileName, &
 886                         '', typvar_opt = 'P@', &
 887                         containsFullField_opt = .false.)
 888
 889  end subroutine aer_propagateDSLO
 890
 891  !---------------------------------------------------------
 892  ! aer_computeAnlErrorStd
 893  !---------------------------------------------------------
 894  subroutine aer_computeAnlErrorStd(obsSpaceData, stateVectorAnlErrorStd, stateVectorTrlErrorStd, &
 895                                    analysisVariable, maxAnalysisErrorStdDev, influentObs, Lcorr)
 896    !
 897    !:Purpose: compute analysis error standard deviation for
 898    !          one analysis variable (grid point) at a time
 899    !
 900    implicit none
 901
 902    ! Arguments:
 903    type(struct_obs)         ,          intent(in)    :: obsSpaceData           ! obsSpaceData structure
 904    type(struct_gsv)         ,          intent(inout) :: stateVectorAnlErrorStd ! state vector for analysis error std deviation
 905    type(struct_gsv)         ,          intent(in)    :: stateVectorTrlErrorStd ! state vector for background error std deviation
 906    character(len=*)         ,          intent(in)    :: analysisVariable       ! variable name ('GL' or 'TM') 
 907    real(8)                  ,          intent(in)    :: maxAnalysisErrorStdDev ! maximum limit imposed on analysis error stddev
 908    type(struct_neighborhood), pointer, intent(in)    :: influentObs(:,:)       ! details about observations to use in update
 909    real(8)                  ,          intent(in)    :: Lcorr(:,:)             ! horizontal background-error length scale
 910
 911    ! Locals:
 912    integer :: latIndex, lonIndex, stepIndex, levIndex, kIndex, numStep, numLev
 913    integer :: numInfluentObs, bodyIndex, numVariables
 914    integer :: influentObsIndex2, influentObsIndex
 915    integer :: xStateIndex, yStateIndex, procIndex
 916    integer :: xIndex1, yIndex1, xIndex2, yIndex2
 917    integer :: gridptCount, gridpt, headerIndex
 918    integer :: varIndex1, varIndex2, currentAnalVarIndex, ni, nj
 919    real(8), allocatable :: latInRad(:,:), lonInRad(:,:)
 920    real(8), pointer     :: anlErrorStdDev_ptr(:,:,:,:)  ! pointer for analysis error std deviation
 921    real(8), pointer     :: trlErrorStdDev_ptr(:,:,:,:)  ! pointer for background error std deviation
 922    real(8), allocatable :: obsOperator(:,:), Bmatrix(:,:), PHiA(:)
 923    real(8), allocatable :: innovCovariance(:,:), obsErrorVariance(:)
 924    real(8), allocatable :: PH(:,:), KH(:), IKH(:), innovCovarianceInverse(:,:)
 925    integer, parameter :: maxvar = 15000
 926    integer :: statei(maxvar), statej(maxvar) ! Model grid coordinate i,j of neighbors
 927    real(8) :: scaling, distance
 928    logical :: found
 929    real(8) :: interpWeight(maxNumLocalGridptsSearch)
 930    integer :: obsLatIndex(maxNumLocalGridptsSearch), obsLonIndex(maxNumLocalGridptsSearch)
 931    integer :: ierr
 932    integer :: numBodyMax, allNumBody(mmpi_nprocs)
 933    real(8), allocatable :: allObsOer(:,:), obsOer(:), iceScaling(:), allIceScaling(:,:), anlErrorStdDevMpiGlobal(:,:,:,:)
 934
 935    call msg('aer_computeAnlErrorStd:', ' computing analysis error std field initialization...')
 936
 937    call utl_tmg_start(124,'--AnalErrOI_computeAnlErrStd')
 938
 939    ni = stateVectorTrlErrorStd%hco%ni
 940    nj = stateVectorTrlErrorStd%hco%nj
 941
 942    ! compute lon/lat in radians
 943    allocate(lonInRad(ni, nj))
 944    allocate(latInRad(ni, nj))
 945
 946    do latIndex = 1, nj
 947      do lonIndex = 1, ni
 948        latInRad(lonIndex, latIndex) = real(stateVectorTrlErrorStd%hco%lat2d_4(lonIndex, latIndex), 8)
 949        lonInRad(lonIndex, latIndex) = real(stateVectorTrlErrorStd%hco%lon2d_4(lonIndex, latIndex), 8)
 950      end do
 951    end do
 952    
 953    ! Initialisation of pointers
 954    call gsv_getField(stateVectorAnlErrorStd, anlErrorStdDev_ptr)
 955    call gsv_getField(stateVectorTrlErrorStd, trlErrorStdDev_ptr)
 956    anlErrorStdDev_ptr(:,:,:,:) = 0.0d0
 957
 958    ! Communicate some values from proc 0 to all others
 959    call rpn_comm_allgather(obs_numBody(obsSpaceData), 1, 'mpi_integer',       &
 960                            allNumBody, 1, 'mpi_integer', 'GRID', ierr)
 961    numBodyMax = maxval(allNumBody)
 962    write(*,*) 'aer_computeAnlErrorStd: numBodyMax = ', numBodyMax
 963
 964    if (analysisVariable == 'GL') then
 965      allocate(iceScaling(numBodyMax))
 966      allocate(allIceScaling(numBodyMax,mmpi_nprocs))
 967      do bodyIndex = 1, obs_numBody(obsSpaceData)
 968        iceScaling(bodyIndex) = oop_iceScaling(obsSpaceData, bodyIndex)
 969      end do
 970      call rpn_comm_allGather(iceScaling,    numBodyMax, 'MPI_REAL8', &
 971                              allIceScaling, numBodyMax, 'MPI_REAL8', 'GRID', ierr)
 972    end if
 973
 974    allocate(obsOer(numBodyMax))
 975    allocate(allObsOer(numBodyMax,mmpi_nprocs))
 976    do bodyIndex = 1, obs_numBody(obsSpaceData)
 977      obsOer(bodyIndex) = obs_bodyElem_r(obsSpaceData, OBS_OER, &
 978                                         bodyIndex)
 979    end do
 980    call rpn_comm_allGather(obsOer,    numBodyMax, 'MPI_REAL8', &
 981                            allObsOer, numBodyMax, 'MPI_REAL8', 'GRID', ierr)
 982
 983    numStep = stateVectorTrlErrorStd%numStep
 984    numLev = gsv_getNumLev(stateVectorTrlErrorStd, vnl_varLevelFromVarname(analysisVariable))
 985
 986    ! Only variables assigned within or by the loop can be private.
 987    STEP: do stepIndex = 1, numStep
 988      LEVEL: do levIndex = 1, numLev
 989
 990        !$omp parallel do default(shared) schedule(dynamic) private(obsOperator, Bmatrix, &
 991        !$omp        PHiA, innovCovariance, innovCovarianceInverse, obsErrorVariance, &
 992        !$omp        PH, KH, IKH, &
 993        !$omp        statei, statej, &
 994        !$omp        numVariables, varIndex1, varIndex2, currentAnalVarIndex, found, &
 995        !$omp        influentObsIndex2, influentObsIndex, &
 996        !$omp        scaling, xStateIndex, yStateIndex, lonIndex, latIndex, headerIndex, &
 997        !$omp        bodyIndex, kIndex, procIndex, &
 998        !$omp        numInfluentObs, xIndex1, yIndex1, xIndex2, yIndex2, distance, &
 999        !$omp        interpWeight, obsLatIndex, obsLonIndex, gridptCount, gridpt)
1000        YINDEX: do latIndex = mmpi_myidy+1, nj, mmpi_npey
1001          XINDEX: do lonIndex = mmpi_myidx+1, ni, mmpi_npex
1002
1003            ! Initialize to trial value
1004            anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex) =  &
1005                 trlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex)
1006
1007            numInfluentObs = influentObs(lonIndex, latIndex)%numObs
1008
1009            if (numInfluentObs == 0 .or. &
1010                .not. stateVectorTrlErrorStd%oceanMask%mask(lonIndex, latIndex, levIndex)) cycle XINDEX
1011
1012            ! form the observation-error covariance (diagonal) matrix
1013
1014            allocate(obsErrorVariance(numInfluentObs))
1015
1016            do influentObsIndex = 1, numInfluentObs
1017              bodyIndex = influentObs(lonIndex, latIndex)%bodyIndex(influentObsIndex)
1018              procIndex = influentObs(lonIndex, latIndex)%procIndex(influentObsIndex)
1019              obsErrorVariance(influentObsIndex) = allObsOer(bodyIndex,procIndex)**2
1020            end do
1021
1022            ! find all model variables involved here
1023
1024            numVariables = 0
1025            statei(:) = 0
1026            statej(:) = 0
1027
1028            INFLUENTOBSCYCLE: do influentObsIndex = 1, numInfluentObs
1029
1030              headerIndex = influentObs(lonIndex, latIndex)%headerIndex(influentObsIndex)
1031              bodyIndex   = influentObs(lonIndex, latIndex)%bodyIndex(influentObsIndex)
1032              procIndex   = influentObs(lonIndex, latIndex)%procIndex(influentObsIndex)
1033
1034              if (analysisVariable == 'GL') then
1035                scaling = allIceScaling(bodyIndex,procIndex)
1036              else if (analysisVariable == 'TM') then
1037                scaling = 1.0d0
1038              end if	
1039
1040              if (scaling == 0.0d0) cycle INFLUENTOBSCYCLE
1041              KINDEXCYCLE: do kIndex = stateVectorTrlErrorStd%mykBeg, stateVectorTrlErrorStd%mykEnd
1042
1043                call s2c_getWeightsAndGridPointIndexes(headerIndex, kIndex, stepIndex, procIndex, &
1044                                                       interpWeight, obsLatIndex, obsLonIndex, &
1045                                                       gridptCount)
1046
1047                GRIDPTCYCLE: do gridpt = 1, gridptCount
1048
1049                  if (interpWeight(gridpt) == 0.0d0) cycle GRIDPTCYCLE
1050
1051                  xStateIndex = obsLonIndex(gridpt)
1052                  yStateIndex = obsLatIndex(gridpt)
1053
1054                  found = .false.
1055                  do varIndex2=1, numVariables
1056                    if(xStateIndex == statei(varIndex2) .and. &
1057                       yStateIndex == statej(varIndex2)) then
1058                      found = .true.
1059                      exit
1060                    end if
1061                  end do
1062
1063                  if (found) cycle GRIDPTCYCLE
1064
1065                  numVariables = numVariables + 1
1066                  if(numVariables > maxvar) then
1067                    call utl_abort('aer_computeAnlErrorStd: Structure state'// &
1068                                   ' too small in subroutine'// &
1069                                   ' analysis_error_mod. Increase maxvar')
1070                  end if
1071                  statei(numVariables) = xStateIndex
1072                  statej(numVariables) = yStateIndex
1073
1074                end do GRIDPTCYCLE
1075              end do KINDEXCYCLE
1076            end do INFLUENTOBSCYCLE
1077
1078            ! make sure that current analysis variable is part of the state
1079            ! vector even if it does not participate in obs calculation
1080
1081            found = .false.
1082            do varIndex2 = 1, numVariables
1083              if(lonIndex == statei(varIndex2) .and. &
1084                 latIndex == statej(varIndex2)) then
1085                currentAnalVarIndex = varIndex2
1086                found = .true.
1087                exit
1088              end if
1089            end do
1090
1091            if(.not. found) then
1092              numVariables = numVariables + 1
1093              if(numVariables > maxvar) then
1094                call utl_abort('aer_computeAnlErrorStd: Structure state too small in'// &
1095                     ' subroutine analysis_error_mod'// &
1096                     ' increase maxvar')
1097              end if
1098              currentAnalVarIndex = numVariables
1099              statei(numVariables) = lonIndex
1100              statej(numVariables) = latIndex
1101            end if
1102
1103            ! form the observation operator matrix (obsOperator)
1104
1105            allocate(obsOperator(numVariables, numInfluentObs))
1106
1107            obsOperator(:,:) = 0.0d0
1108
1109            varIndex2 = 0
1110
1111            INFLUENTOBSCYCLE2: do influentObsIndex = 1, numInfluentObs
1112
1113              headerIndex = influentObs(lonIndex, latIndex)%headerIndex(influentObsIndex)
1114              bodyIndex   = influentObs(lonIndex, latIndex)%bodyIndex(influentObsIndex)
1115              procIndex   = influentObs(lonIndex, latIndex)%procIndex(influentObsIndex)
1116
1117              if (analysisVariable == 'GL') then
1118                scaling = allIceScaling(bodyIndex,procIndex)
1119              else if (analysisVariable == 'TM') then
1120                scaling = 1.0d0
1121              end if
1122
1123              if (scaling == 0.0d0) cycle INFLUENTOBSCYCLE2
1124
1125              KINDEXCYCLE2: do kIndex = stateVectorTrlErrorStd%mykBeg, stateVectorTrlErrorStd%mykEnd
1126
1127                call s2c_getWeightsAndGridPointIndexes(headerIndex, kIndex, stepIndex, procIndex, &
1128                                                       interpWeight, obsLatIndex, obsLonIndex, &
1129                                                       gridptCount)
1130
1131                GRIDPTCYCLE2: do gridpt = 1, gridptCount
1132
1133                  if (interpWeight(gridpt) == 0.0d0) cycle GRIDPTCYCLE2
1134
1135                  xStateIndex = obsLonIndex(gridpt)
1136                  yStateIndex= obsLatIndex(gridpt)
1137
1138                  found = .false.
1139                  do while (.not. found)
1140                    if(varIndex2 < numVariables) then
1141                      varIndex2 = varIndex2 + 1
1142                    else
1143                      varIndex2 = 1
1144                    end if
1145                    if(xStateIndex == statei(varIndex2) .and. &
1146                       yStateIndex == statej(varIndex2)) then
1147                      found = .true.
1148                      obsOperator(varIndex2, influentObsIndex) = scaling * &
1149                                                                 interpWeight(gridpt)
1150                    end if
1151                  end do
1152
1153                  if(found) cycle GRIDPTCYCLE2
1154
1155                  write(*,*) 'xStateIndex = ', xStateIndex
1156                  write(*,*) 'yStateIndex = ', yStateIndex
1157                  write(*,*) 'lonIndex = ', lonIndex
1158                  write(*,*) 'latIndex = ', latIndex
1159                  write(*,*) 'gridptCount = ', gridptCount
1160                  write(*,*) 'gridpt = ', gridpt
1161                  write(*,*) 'numVariables = ', numVariables
1162                  do varIndex2=1, numVariables
1163                    write(*,*) 'varIndex2 statei statej = ',statei(varIndex2), &
1164                                                            statej(varIndex2)
1165                  end do
1166                  call utl_abort('aer_computeAnlErrorStd: not found in state vect.')
1167
1168                end do GRIDPTCYCLE2
1169              end do KINDEXCYCLE2
1170            end do INFLUENTOBSCYCLE2
1171
1172            ! form the background-error covariance matrix
1173
1174            allocate(Bmatrix(numVariables, numVariables))
1175
1176            Bmatrix(:,:) = 0.0d0
1177
1178            do varIndex2 = 1, numVariables
1179
1180              xIndex2 = statei(varIndex2)
1181              yIndex2 = statej(varIndex2)
1182
1183              do varIndex1 = varIndex2, numVariables
1184
1185                xIndex1 = statei(varIndex1)
1186                yIndex1 = statej(varIndex1)
1187
1188                if(xIndex2 == xIndex1 .and. yIndex2 == yIndex1) then
1189                  Bmatrix(varIndex1,varIndex2) = trlErrorStdDev_ptr(xIndex1, yIndex1, levIndex, stepIndex)**2
1190                else if(Lcorr(xIndex2,yIndex2) > 0.0d0) then
1191                  distance = phf_calcDistance(latInRad(xIndex2, yIndex2), lonInRad(xIndex2, yIndex2), &
1192                                              latInRad(xIndex1, yIndex1), lonInRad(xIndex1, yIndex1))
1193                  Bmatrix(varIndex1,varIndex2) = trlErrorStdDev_ptr(xIndex2, yIndex2, levIndex, stepIndex) * &
1194                                                 trlErrorStdDev_ptr(xIndex1, yIndex1, levIndex, stepIndex) * &
1195                                                 exp(-0.5 * (distance / Lcorr(xIndex2, yIndex2))**2)
1196                end if
1197
1198                ! symmetric matrix !
1199                Bmatrix(varIndex2, varIndex1) = Bmatrix(varIndex1, varIndex2)
1200
1201              end do
1202
1203            end do
1204
1205            ! form the observation background error covariance matrix (PHT)
1206
1207            allocate(PH(numInfluentObs, numVariables))
1208
1209            ! PH = matmul (Bmatrix, transpose(obsOperator))
1210            do varIndex1 = 1, numVariables
1211              do influentObsIndex = 1, numInfluentObs
1212                PH(influentObsIndex,varIndex1) = dot_product(Bmatrix(:, varIndex1), &
1213                                                 obsOperator(:, influentObsIndex))
1214              end do
1215            end do
1216
1217            !  covariance matrix of the innovation (HPHT + R)
1218
1219            allocate(innovCovariance(numInfluentObs, numInfluentObs), &
1220                     innovCovarianceInverse(numInfluentObs, numInfluentObs))
1221
1222            do influentObsIndex = 1, numInfluentObs
1223
1224              do influentObsIndex2 = 1, numInfluentObs
1225                if (influentObsIndex == influentObsIndex2) then
1226                  innovCovariance(influentObsIndex2, influentObsIndex) = &
1227                       dot_product(obsOperator(:, influentObsIndex), &
1228                       PH(influentObsIndex2,:)) + &
1229                       obsErrorVariance(influentObsIndex)
1230                else
1231                  innovCovariance(influentObsIndex2, influentObsIndex) = &
1232                       dot_product(obsOperator(:, influentObsIndex), &
1233                       PH(influentObsIndex2,:))
1234                end if
1235              end do
1236
1237            end do
1238
1239            ! Inverse of the covariance matrix of the innovation
1240
1241            innovCovarianceInverse(:,:) = innovCovariance(:,:)
1242            call utl_matInverse(innovCovarianceInverse, numInfluentObs, &
1243                                printInformation_opt = .false.)
1244
1245            ! Kalman gain; this is the row corresponding to the analysis variable
1246
1247            allocate(PHiA(numInfluentObs))
1248
1249            do influentObsIndex = 1, numInfluentObs
1250              PHiA(influentObsIndex) = dot_product(PH(:, currentAnalVarIndex), &
1251                                       innovCovarianceInverse(influentObsIndex,:))
1252            end do
1253
1254            ! compute the error variance of the analysis
1255
1256            allocate(KH(numVariables))
1257
1258            ! KH = matmul (PHiA, obsOperator)
1259            do varIndex1 = 1, numVariables
1260              KH(varIndex1) = dot_product(PHiA, obsOperator(varIndex1,:))
1261            end do
1262
1263            ! IKH = I - KH
1264
1265            allocate(IKH(numVariables))
1266
1267            IKH = -KH
1268
1269            IKH(currentAnalVarIndex) = 1.0d0 - KH(currentAnalVarIndex)
1270
1271            anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex) = &
1272                 sqrt(dot_product (IKH, Bmatrix(:, currentAnalVarIndex)))
1273
1274            if(anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex) < 0.0) then
1275              write(*,*) 'aer_computeAnlErrorStd: negative analysis-error Std dev. = ', &
1276                         anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex), &
1277                         ' reset to zero at grid point (',lonIndex, latIndex,')'
1278              anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex) = &
1279                   max(anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex), 0.0)
1280            end if
1281
1282            if(anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex) > &
1283               trlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex)) then
1284              write(*,*) 'aer_computeAnlErrorStd: analysis-error Std dev. = ', &
1285                         anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex), &
1286                         ' is larger than background-error ', &
1287                         'Std dev. is kept at = ', &
1288                         trlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex)
1289              anlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex) = &
1290                   min(trlErrorStdDev_ptr(lonIndex, latIndex, levIndex, stepIndex), &
1291                   maxAnalysisErrorStdDev)
1292            end if
1293
1294            deallocate(Bmatrix)
1295            deallocate(obsErrorVariance)
1296            deallocate(innovCovariance)
1297            deallocate(innovCovarianceInverse)
1298            deallocate(obsOperator)
1299            deallocate(PH)
1300            deallocate(PHiA)
1301            deallocate(KH)
1302            deallocate(IKH)
1303            deallocate(influentObs(lonIndex, latIndex)%headerIndex)
1304            deallocate(influentObs(lonIndex, latIndex)%bodyIndex)
1305            deallocate(influentObs(lonIndex, latIndex)%procIndex)
1306
1307          end do XINDEX
1308        end do YINDEX
1309        !$omp end parallel do
1310
1311      end do LEVEL
1312    end do STEP
1313
1314    deallocate(lonInRad)
1315    deallocate(latInRad)
1316
1317    ! Combine results from all MPI tasks onto task 0
1318    if (mmpi_nprocs > 1) then
1319      write(*,*) 'aer_computeAnlErrorStd: do mpi communication of anlErrorStdDev'
1320      allocate(anlErrorStdDevMpiGlobal(ni,nj,numStep,numLev))
1321      call rpn_comm_reduce(anlErrorStdDev_ptr, anlErrorStdDevMpiGlobal,  &
1322                           size(anlErrorStdDev_ptr), "mpi_real8", "MPI_SUM", 0, "GRID", ierr)
1323      anlErrorStdDev_ptr(:,:,:,:) = anlErrorStdDevMpiGlobal(:,:,:,:)
1324      deallocate(anlErrorStdDevMpiGlobal)
1325    end if
1326
1327    call utl_tmg_stop(124)
1328
1329    write(*,*) 'aer_computeAnlErrorStd: finished'
1330
1331  end subroutine aer_computeAnlErrorStd
1332
1333end module analysisErrorOI_mod