ObsColumnNames_mod sourceΒΆ

   1! obsSpaceData_mod:  the module, obsSpaceData_mod, follows IndexListDepot_mod
   2
   3
   4module IndexListDepot_mod
   5  ! MODULE indexListDepot_mod (prefix='ild' category='7. Low-level data objects and utilities')
   6  !
   7  !:Purpose: The raison d'etre of this module is to support ObsSpaceData_mod in
   8  !           facilitating the traversal of a selection of the rows in its table.
   9  !           The selection of rows could be from either the header table or the
  10  !           body table. ObsSpaceData_mod currently populates the list with one
  11  !           of:
  12  !                * all header members of a given family
  13  !                * all   body members of a given family
  14  !                * all   body members of a given header row index
  15  !
  16  !:Usage:
  17  !    An ObsSpaceData_mod client must first call either
  18  !    obs_set_current_body_list or obs_set_current_header_list, specifying
  19  !    either the family of interest or the header row index of interest. 
  20  !    This does not return the list directly to the caller, but rather writes
  21  !    the list, as a struct_index_list, to the private contents of the obs
  22  !    oject that is returned to the caller but which cannot be examined by the
  23  !    caller.  Two lists can be active simultaneously:  one header list and
  24  !    one body list.
  25  !
  26  !    In order to access the indices that are in the list, the
  27  !    ObsSpaceData_mod client must call either obs_getHeaderIndex or
  28  !    obs_getBodyIndex, giving the ObsSpaceData object as the only argument. 
  29  !    On each call, one index is returned.  On calls after the last index in
  30  !    the list, a value of -1 is returned.
  31  !
  32  !    This is not a fully fledged module.  It is better described as a
  33  !    structure definition with a couple of helpful methods.  It is intended
  34  !    that the client, ObsSpaceData_mod, read/write directly from/to instances
  35  !    of these structures.
  36
  37
  38
  39  ! STRUCT_INDEX_LIST:
  40  !    A struct_index_list contains the identity of the family or header that
  41  !    was used to create the list, the actual list of indices, and the number
  42  !    of the list element that was last returned to the user.
  43  !
  44  ! STRUCT_INDEX_LIST_DEPOT:
  45  !    Because it is typical for a client to traverse a small group of lists
  46  !    several times each, performance is improved by retaining recent lists,
  47  !    thus avoiding having to regenerate them on each request.  Recent lists
  48  !    are stored in a struct_index_list_depot.  ObsSpaceData_mod contains one
  49  !    struct_index_list_depot for header lists and another for body lists.
  50  !    The struct_index_list_depot structure contains current_list, a pointer
  51  !    to the list in the depot that was last requested by the ObsSpaceData_mod
  52  !    client.
  53  !
  54  ! OMP:
  55  !    ObsSpaceData_mod has been designed so that it may be called from within
  56  !    an OMP section of code.  If there are n OMP threads, it is possible that
  57  !    there be as many as n lists in use simultaneously.  The parameter, 
  58  !    NUMBER_OF_LISTS, has been set to n to accommodate that many threads.  In
  59  !    this case, because the current_list of the depot is not OMP-private, it
  60  !    cannot be asked to remember the current list for each of the OMP
  61  !    threads.  Therefore, obs_set_current_header/body_list returns to the
  62  !    client an additional pointer to the list itself.  This pointer must then
  63  !    be passed as an optional parameter to obs_getHeader/BodyIndex.  When a
  64  !    new list is requested by an OMP thread, the same physical memory is
  65  !    re-used for the new list.
  66  !
  67  !    In order to ensure that the same physical memory is not initially
  68  !    distributed to more than one OMP thread, the two small sections of
  69  !    IndexListDepot_mod that accomplish this are marked omp-critical.
  70  !
  71
  72   implicit none
  73   save
  74   public
  75
  76   ! methods
  77   public :: ild_initialize             ! initialize a list depot
  78   public :: ild_finalize               ! finalize a list depot
  79   public :: ild_get_empty_index_list   ! return a pointer to an initialized list
  80   public :: ild_get_next_index         ! return the next element in the list
  81
  82   interface ild_get_next_index
  83      module procedure ild_get_next_index_depot
  84      module procedure ild_get_next_index_private
  85   end interface ild_get_next_index
  86
  87                                        ! This dimension must accommodate the
  88                                        ! maximum number of OMP processors 
  89   integer, parameter :: NUMBER_OF_LISTS = 32
  90
  91   type struct_index_list
  92      ! a list of integers, not to say indices into a struct_obs
  93      character(len=2) :: family        ! current_element's belong to this family
  94                                        ! Used only for a body list:
  95      integer :: header                 ! current_element's belong to this header
  96      integer :: current_element        ! the element that has just been returned
  97
  98                                        ! the actual list of integers
  99                                        ! N.B.:  These are the indices that are
 100                                        !        being held for the client.
 101                                        !        In the context of this module,
 102                                        !        these are elements; i.e. row
 103                                        !        indices.  Current_element is the
 104                                        !        last index that was returned
 105                                        !        to the client.
 106      integer, dimension(:), allocatable :: indices
 107   end type struct_index_list
 108
 109   type struct_index_list_depot
 110      ! A collection of lists, either empty or populated
 111                                        ! the collection of lists
 112      type(struct_index_list), dimension(NUMBER_OF_LISTS) :: index_lists
 113      integer :: list_last_attributed   ! list that was populated most recently
 114                                        ! list that was   used    most recently
 115      type(struct_index_list), pointer :: current_list
 116   end type struct_index_list_depot
 117
 118
 119contains
 120
 121   subroutine ild_finalize( depot )
 122      !
 123      !:Purpose: Finalize the indicated list depot
 124      !
 125      implicit none
 126
 127      ! Arguments:
 128      type(struct_index_list_depot), intent(inout) :: depot ! the depot to be finalized
 129
 130      ! Locals:
 131      integer :: list                   ! an index
 132
 133      ! Deallocate each list
 134      do list = 1,NUMBER_OF_LISTS
 135         depot%index_lists(list)%family = 'xx'
 136         depot%index_lists(list)%header = 0
 137         depot%index_lists(list)%current_element = 0
 138         deallocate(depot%index_lists(list)%indices)
 139      end do
 140      depot%list_last_attributed = 0
 141   end subroutine ild_finalize
 142
 143
 144   function ild_get_empty_index_list( depot, private_list_opt ) &
 145                                                         result(empty_index_list)
 146      !
 147      !:Purpose: From the given depot, return an index-list structure that contains
 148      !           no data, as a pointer.
 149      !           In other words, clear data from the (cyclicly) next (i.e. oldest)
 150      !           list and return a pointer to it.
 151      !
 152      !           If the list is being used within an OMP block, the ObsSpaceData
 153      !           client is responsible for holding a pointer to its own list.  This
 154      !           is supplied as the parameter, private_list_opt.
 155      !
 156      implicit none
 157
 158      ! Arguments:
 159      type(struct_index_list_depot), target,      intent(inout) :: depot            ! the depot containing the list
 160      type(struct_index_list), pointer, optional, intent(inout) :: private_list_opt ! used instead of depot (for OMP blocks)
 161      ! Result:
 162      type(struct_index_list), pointer                     :: empty_index_list      ! the returned list
 163
 164      nullify(empty_index_list)
 165
 166      if(present(private_list_opt)) then
 167         ! This is an OMP thread
 168         if(associated(private_list_opt)) then
 169            ! Memory has already been assigned for that thread.  Re-use it.
 170            empty_index_list => private_list_opt ! Set the return pointer
 171         end if
 172      end if
 173
 174      if(.not. associated(empty_index_list)) then
 175        !$omp critical
 176         ! Increment (cyclicly) the index to the next list
 177         depot%list_last_attributed = depot%list_last_attributed + 1
 178         if (depot%list_last_attributed > NUMBER_OF_LISTS) &
 179            depot%list_last_attributed = 1
 180
 181         ! Set the return pointer
 182         empty_index_list => depot%index_lists(depot%list_last_attributed)
 183         !$omp end critical
 184      end if
 185
 186      ! Initialize some values in the list
 187      ! empty_index_list%indices(:) = -1 --> No, the array is too big.
 188      empty_index_list%family = '  '
 189      empty_index_list%header = -1
 190      empty_index_list%current_element = 0
 191
 192      return
 193   end function ild_get_empty_index_list
 194
 195
 196   function ild_get_next_index_depot(depot, no_advance_opt) result(next_index)
 197      !
 198      !:Purpose: From the given depot, increment the index to the current element,
 199      !           and return the element itself, the new current element.
 200      !
 201      implicit none
 202
 203      ! Arguments:
 204      type(struct_index_list_depot), target, intent(inout) :: depot          ! the depot containing the list
 205      logical, optional,                     intent(in)    :: no_advance_opt ! current_element, just return next one
 206      ! Result:
 207      integer :: next_index                                                  ! the returned index
 208
 209      ! Locals:
 210      type(struct_index_list), pointer                     :: current_list ! current list of the depot
 211      integer                                              :: next_element ! next element of the current list
 212
 213      current_list => depot%current_list
 214      !$omp critical
 215                                        ! Obtain the next element from the list
 216      next_element = current_list%current_element + 1
 217      next_index = current_list%indices(next_element)
 218      if(.not. present(no_advance_opt) .and. next_index /= -1) then
 219                                        ! Increment the current element
 220         current_list%current_element = next_element
 221      end if
 222      !$omp end critical
 223   end function ild_get_next_index_depot
 224
 225
 226   function ild_get_next_index_private(private_list, no_advance_opt) &
 227                                                               result(next_index)
 228      !
 229      !:Purpose:  From the given list, increment the index to the current element, and
 230      !            return the element itself, the new current element.
 231      !
 232      implicit none
 233
 234      ! Arguments:
 235      type(struct_index_list), pointer, intent(inout) :: private_list   ! the list of interest
 236      logical, optional,                intent(in)    :: no_advance_opt ! current_element, just return next one
 237      ! Result:
 238      integer :: next_index                                           ! the returned index
 239
 240      ! Locals:
 241      integer                                         :: next_element ! next element of the list
 242
 243                                        ! Obtain the next element from the list
 244      next_element = private_list%current_element + 1
 245      next_index = private_list%indices(next_element)
 246
 247      if(.not. present(no_advance_opt) .and. next_index /= -1) then
 248                                        ! Increment the current element
 249         private_list%current_element = next_element
 250      end if
 251   end function ild_get_next_index_private
 252
 253
 254   subroutine ild_initialize(depot, numHeaderBody_max)
 255      !
 256      !:Purpose: Initialize the indicated list depot
 257      !   
 258      !:Note: indices is allocated with 2 extra elements to make room for
 259      !        the end-of-list flag that is set in
 260      !        obs_set_current_header/body_list
 261      !
 262      implicit none
 263
 264      ! Arguments:
 265      type(struct_index_list_depot), intent(inout) :: depot ! the depot to be initialized
 266                                                            ! max size of header or body of
 267                                                            ! struct_obs & hence of depot
 268      integer, intent(in) :: numHeaderBody_max
 269
 270      ! Locals:
 271      integer :: list                   ! an index
 272
 273      ! Allocate each list
 274      do list = 1,NUMBER_OF_LISTS
 275         depot%index_lists(list)%family = 'xx'
 276         depot%index_lists(list)%header = 0
 277         depot%index_lists(list)%current_element = 0
 278         allocate(depot%index_lists(list)%indices(numHeaderBody_max+2))
 279         depot%index_lists(list)%indices(:)=0
 280      end do
 281      depot%list_last_attributed = 0
 282      nullify(depot%current_list)
 283   end subroutine ild_initialize
 284
 285end module IndexListDepot_mod
 286
 287
 288
 289module ObsColumnNames_mod
 290  ! MODULE obsColumnNames_mod (prefix='ocn' category='7. Low-level data objects and utilities')
 291  !
 292  !:Purpose:  This module simply groups together many fortran paramters that
 293  !           serve as column names for ObsSpaceData_mod.
 294  !
 295  !:NOTE:  This module is logistically a part of the ObsSpaceData_mod module.
 296  !        In fact, if fortran allowed it, ObsColumnNames_mod would be
 297  !        'contain'ed inside the ObsSpaceData_mod module. For this reason, and
 298  !        more importantly because these parameters constitute a part of the
 299  !        visible (from outside ObsSpaceData_mod) interface to
 300  !        ObsSpaceData_mod, the parameters defined in this module carry the
 301  !        prefix, OBS, and not CN.
 302  !
 303
 304   public
 305
 306
 307   !
 308   ! INTEGER-HEADER COLUMN NUMBERS
 309   !  
 310   ! the first column index for integer header variables defined below
 311   ! (chosen such that every column number is unique, so that a mismatch between
 312   !  a column number and a column type (real, int, head, body) can be detected)
 313   integer, parameter :: NHDR_INT_BEG = 101
 314   integer, parameter, public :: OBS_RLN = NHDR_INT_BEG ! report location
 315                                             ! unique(within obsdat), possibly
 316   integer, parameter, public :: OBS_ONM = OBS_RLN+1  ! ordered, station id number
 317   integer, parameter, public :: OBS_INS = OBS_ONM+1  ! instrument ID  
 318   integer, parameter, public :: OBS_ITY = OBS_INS+1  ! code: instrument & retrieval type
 319   integer, parameter, public :: OBS_SAT = OBS_ITY+1  ! satellite code 
 320   integer, parameter, public :: OBS_TEC = OBS_SAT+1  ! satellite processing technique
 321   integer, parameter, public :: OBS_SEN = OBS_TEC+1  ! satellite sensor code
 322   integer, parameter, public :: OBS_DAT = OBS_SEN+1  ! observation date YYYYMMD
 323   integer, parameter, public :: OBS_ETM = OBS_DAT+1  ! observation time HHMM
 324   integer, parameter, public :: OBS_NLV = OBS_ETM+1  ! number of data at this location
 325   integer, parameter, public :: OBS_PAS = OBS_NLV+1  ! batch no. in sequential analysis
 326   integer, parameter, public :: OBS_REG = OBS_PAS+1  ! region number in the batch
 327   integer, parameter, public :: OBS_IP  = OBS_REG+1  ! number of mpi processors
 328   integer, parameter, public :: OBS_IPF = OBS_IP+1   ! mpi task id for file
 329   integer, parameter, public :: OBS_IPC = OBS_IPF+1  ! mpi task id for column/obsspacedata
 330   integer, parameter, public :: OBS_IPT = OBS_IPC+1  ! mpi task id for latlontile
 331   integer, parameter, public :: OBS_ST1 = OBS_IPT+1  ! header level status/rejection flag
 332   integer, parameter, public :: OBS_IDF = OBS_ST1+1  ! id. no. of observation-source file
 333
 334   integer, parameter, public :: OBS_GQF = OBS_IDF+1  ! iasi GQISFLAGQUAL: general quality flag which indicates,
 335                                                      ! when it is set to TRUE (=1), that some anomaly has been
 336                                                      ! detected at some step in the Level 0 or Level 1 IASI Processing.
 337                                                      ! The products should not be used if this occurs.
 338
 339   integer, parameter, public :: OBS_GQL = OBS_GQF+1  ! iasi GQISQUALINDEXLOC: It is defined as the uncertainty of the
 340                                                      ! coregistration between IIS (IASI internal imager) and AVHRR.
 341
 342   integer, parameter, public :: OBS_NCO2= OBS_GQL+1  ! NCO2: number of valid CO2 slicing estimates (AIRS,IASI,CrIS)
 343   integer, parameter, public :: OBS_STYP= OBS_NCO2+1 ! surface type in obs file (0,1,2)
 344   integer, parameter, public :: OBS_ROQF= OBS_STYP+1 ! QUALITY FLAGS FOR RADIO OCCULTATION DATA
 345   integer, parameter, public :: OBS_SWQ1= OBS_ROQF+1 ! QUALITY VALUES FOR SW (AMV) DATA
 346   integer, parameter, public :: OBS_SWQ2= OBS_SWQ1+1 ! QUALITY VALUES FOR SW (AMV) DATA
 347   integer, parameter, public :: OBS_SWMT= OBS_SWQ2+1 ! QUALITY VALUES FOR SW (AMV) DATA
 348   integer, parameter, public :: OBS_SWLS= OBS_SWMT+1 ! QUALITY VALUES FOR SW (AMV) DATA
 349   integer, parameter, public :: OBS_SWGA= OBS_SWLS+1 ! QUALITY VALUES FOR SW (AMV) DATA
 350   integer, parameter, public :: OBS_SWHA= OBS_SWGA+1 ! QUALITY VALUES FOR SW (AMV) DATA
 351   integer, parameter, public :: OBS_CHM = OBS_SWHA+1 ! BUFR code (table 08046) of constituent type (for the CH family)
 352   integer, parameter, public :: OBS_FOV = OBS_CHM+1  ! field of view 
 353   integer, parameter, public :: OBS_PRFL= OBS_FOV+1  ! profile id. number
 354   integer, parameter, public :: OBS_PHAS= OBS_PRFL+1 ! phase of flight
 355   integer, parameter, public :: OBS_ORI = OBS_PHAS+1 ! originating centre code
 356   integer, parameter, public :: OBS_LCH = OBS_ORI+1  ! launch time (hhmm)
 357   integer, parameter, public :: OBS_RTP = OBS_LCH+1  ! radiosonde type code
 358   integer, parameter, public :: OBS_HDD = OBS_RTP+1  ! date in burp header
 359   integer, parameter, public :: OBS_HDT = OBS_HDD+1  ! time in burp header
 360   integer, parameter, public :: OBS_TFLG= OBS_HDT+1  ! flag for hi-res time element
 361   integer, parameter, public :: OBS_LFLG= OBS_TFLG+1 ! flag for hi-res lat element
 362   integer, parameter, public :: OBS_ORBI= OBS_LFLG+1! satellite orbit index
 363   integer, parameter, public :: OBS_AQF1= OBS_ORBI+1! ATMS Geolocalisation Quality Control Flag 
 364   integer, parameter, public :: OBS_AQF2= OBS_AQF1+1! ATMS Granule Level Quality Control Flag 
 365   integer, parameter, public :: OBS_AQF3= OBS_AQF2+1! ATMS Scan Level Quality Control Flag 
 366   integer, parameter, public :: OBS_TTYP= OBS_AQF3+1! TERRAIN TYP INDICE for TOVS QC
 367   integer, parameter, public :: OBS_INFG= OBS_TTYP+1! SATPLOT INFO FLAG for TOVS
 368   integer, parameter, public :: OBS_RAIN= OBS_INFG+1! UKMO rain flag for ssmis obs
 369   integer, parameter, public :: OBS_CHID= OBS_RAIN+1! id. no. of ice chart
 370
 371
 372   ! the last column index for integer header variables defined just above
 373   integer, parameter :: NHDR_INT_END = OBS_CHID
 374
 375   integer, parameter :: NHDR_INT_SIZE = NHDR_INT_END - NHDR_INT_BEG + 1
 376
 377   !
 378   ! INTEGER-HEADER COLUMN NAMES
 379   !
 380   character(len=4), target :: ocn_ColumnNameList_IH(NHDR_INT_BEG:NHDR_INT_END) = &
 381      (/ 'RLN ','ONM ','INS ','ITY ','SAT ','TEC ','SEN ','DAT ','ETM ', &  
 382         'NLV ','PAS ','REG ','IP  ','IPF ','IPC ','IPT ', &  
 383         'ST1 ','IDF ','GQF ','GQL ','NCO2','STYP','ROQF', &
 384         'SWQ1','SWQ2','SWMT','SWLS','SWGA','SWHA','CHM ','FOV ', &
 385         'PRFL','PHAS','ORI ','LCH ','RTP ','HDD ','HDT ','TFLG',&
 386         'LFLG','ORBI','AQF1','AQF2','AQF3','TTYP','INFG','RAIN','CHID'/)
 387
 388   !
 389   ! REAL-HEADER COLUMN NUMBERS
 390   !
 391   ! the first column index for real header variables defined below
 392   ! (chosen such that every column number is unique, so that a mismatch between
 393   !  a column number and a column type (real, int, head, body) can be detected)
 394   integer, parameter :: NHDR_REAL_BEG = 201
 395   integer, parameter, public :: OBS_LAT = NHDR_REAL_BEG  ! latitude  in radians (N positive)
 396   integer, parameter, public :: OBS_LON = OBS_LAT+1 ! longitude in radians (E positive)
 397   integer, parameter, public :: OBS_ALT = OBS_LON+1 ! station altitude
 398   integer, parameter, public :: OBS_BX  = OBS_ALT+1 ! x-coordinate of block in R3
 399   integer, parameter, public :: OBS_BY  = OBS_BX +1 ! y-coordinate of block in R3
 400   integer, parameter, public :: OBS_BZ  = OBS_BY +1 ! z-coordinate of block in R3
 401
 402   integer, parameter, public :: OBS_M1C1  = OBS_BZ +1   ! mean for class 1 AVHRR channel 1
 403   integer, parameter, public :: OBS_M1C2  = OBS_M1C1 +1 ! mean for class 1 AVHRR channel 2
 404   integer, parameter, public :: OBS_M1C3  = OBS_M1C2 +1 ! mean for class 1 AVHRR channel 3
 405   integer, parameter, public :: OBS_M1C4  = OBS_M1C3 +1 ! mean for class 1 AVHRR channel 4
 406   integer, parameter, public :: OBS_M1C5  = OBS_M1C4 +1 ! mean for class 1 AVHRR channel 5
 407   integer, parameter, public :: OBS_M1C6  = OBS_M1C5 +1 ! mean for class 1 AVHRR channel 6
 408
 409   integer, parameter, public :: OBS_M2C1  = OBS_M1C6 +1 ! mean for class 2 AVHRR channel 1
 410   integer, parameter, public :: OBS_M2C2  = OBS_M2C1 +1 ! mean for class 2 AVHRR channel 2
 411   integer, parameter, public :: OBS_M2C3  = OBS_M2C2 +1 ! mean for class 2 AVHRR channel 3
 412   integer, parameter, public :: OBS_M2C4  = OBS_M2C3 +1 ! mean for class 2 AVHRR channel 4
 413   integer, parameter, public :: OBS_M2C5  = OBS_M2C4 +1 ! mean for class 2 AVHRR channel 5
 414   integer, parameter, public :: OBS_M2C6  = OBS_M2C5 +1 ! mean for class 2 AVHRR channel 6
 415
 416   integer, parameter, public :: OBS_M3C1  = OBS_M2C6 +1 ! mean for class 3 AVHRR channel 1
 417   integer, parameter, public :: OBS_M3C2  = OBS_M3C1 +1 ! mean for class 3 AVHRR channel 2
 418   integer, parameter, public :: OBS_M3C3  = OBS_M3C2 +1 ! mean for class 3 AVHRR channel 3
 419   integer, parameter, public :: OBS_M3C4  = OBS_M3C3 +1 ! mean for class 3 AVHRR channel 4
 420   integer, parameter, public :: OBS_M3C5  = OBS_M3C4 +1 ! mean for class 3 AVHRR channel 5
 421   integer, parameter, public :: OBS_M3C6  = OBS_M3C5 +1 ! mean for class 3 AVHRR channel 6
 422
 423   integer, parameter, public :: OBS_M4C1  = OBS_M3C6 +1 ! mean for class 4 AVHRR channel 1
 424   integer, parameter, public :: OBS_M4C2  = OBS_M4C1 +1 ! mean for class 4 AVHRR channel 2
 425   integer, parameter, public :: OBS_M4C3  = OBS_M4C2 +1 ! mean for class 4 AVHRR channel 3
 426   integer, parameter, public :: OBS_M4C4  = OBS_M4C3 +1 ! mean for class 4 AVHRR channel 4
 427   integer, parameter, public :: OBS_M4C5  = OBS_M4C4 +1 ! mean for class 4 AVHRR channel 5
 428   integer, parameter, public :: OBS_M4C6  = OBS_M4C5 +1 ! mean for class 4 AVHRR channel 6
 429
 430   integer, parameter, public :: OBS_M5C1  = OBS_M4C6 +1 ! mean for class 5 AVHRR channel 1
 431   integer, parameter, public :: OBS_M5C2  = OBS_M5C1 +1 ! mean for class 5 AVHRR channel 2
 432   integer, parameter, public :: OBS_M5C3  = OBS_M5C2 +1 ! mean for class 5 AVHRR channel 3
 433   integer, parameter, public :: OBS_M5C4  = OBS_M5C3 +1 ! mean for class 5 AVHRR channel 4
 434   integer, parameter, public :: OBS_M5C5  = OBS_M5C4 +1 ! mean for class 5 AVHRR channel 5
 435   integer, parameter, public :: OBS_M5C6  = OBS_M5C5 +1 ! mean for class 5 AVHRR channel 6
 436
 437   integer, parameter, public :: OBS_M6C1  = OBS_M5C6 +1 ! mean for class 6 AVHRR channel 1
 438   integer, parameter, public :: OBS_M6C2  = OBS_M6C1 +1 ! mean for class 6 AVHRR channel 2
 439   integer, parameter, public :: OBS_M6C3  = OBS_M6C2 +1 ! mean for class 6 AVHRR channel 3
 440   integer, parameter, public :: OBS_M6C4  = OBS_M6C3 +1 ! mean for class 6 AVHRR channel 4
 441   integer, parameter, public :: OBS_M6C5  = OBS_M6C4 +1 ! mean for class 6 AVHRR channel 5
 442   integer, parameter, public :: OBS_M6C6  = OBS_M6C5 +1 ! mean for class 6 AVHRR channel 6
 443
 444   integer, parameter, public :: OBS_M7C1  = OBS_M6C6 +1 ! mean for class 7 AVHRR channel 1
 445   integer, parameter, public :: OBS_M7C2  = OBS_M7C1 +1 ! mean for class 7 AVHRR channel 2
 446   integer, parameter, public :: OBS_M7C3  = OBS_M7C2 +1 ! mean for class 7 AVHRR channel 3
 447   integer, parameter, public :: OBS_M7C4  = OBS_M7C3 +1 ! mean for class 7 AVHRR channel 4
 448   integer, parameter, public :: OBS_M7C5  = OBS_M7C4 +1 ! mean for class 7 AVHRR channel 5
 449   integer, parameter, public :: OBS_M7C6  = OBS_M7C5 +1 ! mean for class 7 AVHRR channel 6
 450
 451
 452   integer, parameter, public :: OBS_S1C1  = OBS_M7C6 +1 ! stdev for class 1 AVHRR channel 1
 453   integer, parameter, public :: OBS_S1C2  = OBS_S1C1 +1 ! stdev for class 1 AVHRR channel 2
 454   integer, parameter, public :: OBS_S1C3  = OBS_S1C2 +1 ! stdev for class 1 AVHRR channel 3
 455   integer, parameter, public :: OBS_S1C4  = OBS_S1C3 +1 ! stdev for class 1 AVHRR channel 4
 456   integer, parameter, public :: OBS_S1C5  = OBS_S1C4 +1 ! stdev for class 1 AVHRR channel 5
 457   integer, parameter, public :: OBS_S1C6  = OBS_S1C5 +1 ! stdev for class 1 AVHRR channel 6
 458
 459   integer, parameter, public :: OBS_S2C1  = OBS_S1C6 +1 ! stdev for class 2 AVHRR channel 1
 460   integer, parameter, public :: OBS_S2C2  = OBS_S2C1 +1 ! stdev for class 2 AVHRR channel 2
 461   integer, parameter, public :: OBS_S2C3  = OBS_S2C2 +1 ! stdev for class 2 AVHRR channel 3
 462   integer, parameter, public :: OBS_S2C4  = OBS_S2C3 +1 ! stdev for class 2 AVHRR channel 4
 463   integer, parameter, public :: OBS_S2C5  = OBS_S2C4 +1 ! stdev for class 2 AVHRR channel 5
 464   integer, parameter, public :: OBS_S2C6  = OBS_S2C5 +1 ! stdev for class 2 AVHRR channel 6
 465
 466   integer, parameter, public :: OBS_S3C1  = OBS_S2C6 +1 ! stdev for class 3 AVHRR channel 1
 467   integer, parameter, public :: OBS_S3C2  = OBS_S3C1 +1 ! stdev for class 3 AVHRR channel 2
 468   integer, parameter, public :: OBS_S3C3  = OBS_S3C2 +1 ! stdev for class 3 AVHRR channel 3
 469   integer, parameter, public :: OBS_S3C4  = OBS_S3C3 +1 ! stdev for class 3 AVHRR channel 4
 470   integer, parameter, public :: OBS_S3C5  = OBS_S3C4 +1 ! stdev for class 3 AVHRR channel 5
 471   integer, parameter, public :: OBS_S3C6  = OBS_S3C5 +1 ! stdev for class 3 AVHRR channel 6
 472
 473   integer, parameter, public :: OBS_S4C1  = OBS_S3C6 +1 ! stdev for class 4 AVHRR channel 1
 474   integer, parameter, public :: OBS_S4C2  = OBS_S4C1 +1 ! stdev for class 4 AVHRR channel 2
 475   integer, parameter, public :: OBS_S4C3  = OBS_S4C2 +1 ! stdev for class 4 AVHRR channel 3
 476   integer, parameter, public :: OBS_S4C4  = OBS_S4C3 +1 ! stdev for class 4 AVHRR channel 4
 477   integer, parameter, public :: OBS_S4C5  = OBS_S4C4 +1 ! stdev for class 4 AVHRR channel 5
 478   integer, parameter, public :: OBS_S4C6  = OBS_S4C5 +1 ! stdev for class 4 AVHRR channel 6
 479
 480   integer, parameter, public :: OBS_S5C1  = OBS_S4C6 +1 ! stdev for class 5 AVHRR channel 1
 481   integer, parameter, public :: OBS_S5C2  = OBS_S5C1 +1 ! stdev for class 5 AVHRR channel 2
 482   integer, parameter, public :: OBS_S5C3  = OBS_S5C2 +1 ! stdev for class 5 AVHRR channel 3
 483   integer, parameter, public :: OBS_S5C4  = OBS_S5C3 +1 ! stdev for class 5 AVHRR channel 4
 484   integer, parameter, public :: OBS_S5C5  = OBS_S5C4 +1 ! stdev for class 5 AVHRR channel 5
 485   integer, parameter, public :: OBS_S5C6  = OBS_S5C5 +1 ! stdev for class 5 AVHRR channel 6
 486
 487   integer, parameter, public :: OBS_S6C1  = OBS_S5C6 +1 ! stdev for class 6 AVHRR channel 1
 488   integer, parameter, public :: OBS_S6C2  = OBS_S6C1 +1 ! stdev for class 6 AVHRR channel 2
 489   integer, parameter, public :: OBS_S6C3  = OBS_S6C2 +1 ! stdev for class 6 AVHRR channel 3
 490   integer, parameter, public :: OBS_S6C4  = OBS_S6C3 +1 ! stdev for class 6 AVHRR channel 4
 491   integer, parameter, public :: OBS_S6C5  = OBS_S6C4 +1 ! stdev for class 6 AVHRR channel 5
 492   integer, parameter, public :: OBS_S6C6  = OBS_S6C5 +1 ! stdev for class 6 AVHRR channel 6
 493
 494   integer, parameter, public :: OBS_S7C1  = OBS_S6C6 +1 ! stdev for class 7 AVHRR channel 1
 495   integer, parameter, public :: OBS_S7C2  = OBS_S7C1 +1 ! stdev for class 7 AVHRR channel 2
 496   integer, parameter, public :: OBS_S7C3  = OBS_S7C2 +1 ! stdev for class 7 AVHRR channel 3
 497   integer, parameter, public :: OBS_S7C4  = OBS_S7C3 +1 ! stdev for class 7 AVHRR channel 4
 498   integer, parameter, public :: OBS_S7C5  = OBS_S7C4 +1 ! stdev for class 7 AVHRR channel 5
 499   integer, parameter, public :: OBS_S7C6  = OBS_S7C5 +1 ! stdev for class 7 AVHRR channel 6
 500   integer, parameter, public :: OBS_CF1   = OBS_S7C6 +1 ! AVHRR fraction of class 1
 501   integer, parameter, public :: OBS_CF2   = OBS_CF1  +1 ! AVHRR fraction of class 2
 502   integer, parameter, public :: OBS_CF3   = OBS_CF2  +1 ! AVHRR fraction of class 3
 503   integer, parameter, public :: OBS_CF4   = OBS_CF3  +1 ! AVHRR fraction of class 4
 504   integer, parameter, public :: OBS_CF5   = OBS_CF4  +1 ! AVHRR fraction of class 5
 505   integer, parameter, public :: OBS_CF6   = OBS_CF5  +1 ! AVHRR fraction of class 6
 506   integer, parameter, public :: OBS_CF7   = OBS_CF6  +1 ! AVHRR fraction of class 7
 507   integer, parameter, public :: OBS_ETOP  = OBS_CF7  +1 ! CO2 slicing consensus (median) cloud top pressure
 508   integer, parameter, public :: OBS_VTOP  = OBS_ETOP +1 ! estimated error on CO2 slicing cloud top pressure
 509   integer, parameter, public :: OBS_ECF   = OBS_VTOP +1 ! CO2 slicing effective cloud fraction 
 510   integer, parameter, public :: OBS_VCF   = OBS_ECF  +1 ! estimated error on CO2 CO2 slicing cloud  fraction 
 511   integer, parameter, public :: OBS_HE    = OBS_VCF  +1 ! cloud effective height (one channel)
 512   integer, parameter, public :: OBS_ZTSR  = OBS_HE   +1 ! retrieved skin temperature from window channel in K
 513   integer, parameter, public :: OBS_ZTM   = OBS_ZTSR +1 ! model temperature, eta=1, in K (should not be there)
 514   integer, parameter, public :: OBS_ZTGM  = OBS_ZTM  +1 ! surface model temperature (skin) in K
 515   integer, parameter, public :: OBS_ZLQM  = OBS_ZTGM +1 ! specific humidity at surface (2m) in kg/kg
 516   integer, parameter, public :: OBS_ZPS   = OBS_ZLQM +1 ! surface model pressure in Pa
 517   integer, parameter, public :: OBS_TRAD  = OBS_ZPS  +1 ! Local EARTH Radius Metres
 518   integer, parameter, public :: OBS_GEOI  = OBS_TRAD +1 ! Geoid Undulation  Metres
 519   integer, parameter, public :: OBS_CLF   = OBS_GEOI +1 ! cloud fraction
 520   integer, parameter, public :: OBS_SUN   = OBS_CLF  +1 ! sun zenith angle
 521   integer, parameter, public :: OBS_SZA   = OBS_SUN  +1 ! satellite zenith angle
 522   integer, parameter, public :: OBS_AZA   = OBS_SZA  +1 ! satellite azimuthal angle
 523   integer, parameter, public :: OBS_SAZ   = OBS_AZA  +1 ! sun azimuth angle
 524   integer, parameter, public :: OBS_CLWO  = OBS_SAZ  +1 ! cloud liquid water retrieved from observation
 525   integer, parameter, public :: OBS_CLWB  = OBS_CLWO +1 ! cloud liquid water retrieved from background
 526   integer, parameter, public :: OBS_MWS   = OBS_CLWB +1 ! model wind speed (in ASCAT data)
 527   integer, parameter, public :: OBS_SIO   = OBS_MWS  +1 ! scatering index retrieved from observation
 528   integer, parameter, public :: OBS_SIB   = OBS_SIO  +1 ! scatering index retrieved from background
 529   integer, parameter, public :: OBS_IWV   = OBS_SIB  +1 ! atmospheric integrated water vapor for ssmis
 530   integer, parameter, public :: OBS_RZAM  = OBS_IWV  +1 ! Azimuth of the Radar beam   (radians)
 531   integer, parameter, public :: OBS_RELE  = OBS_RZAM +1 ! Elevation of the Radar beam (radians)
 532   integer, parameter, public :: OBS_RANS  = OBS_RELE +1 ! Initial range of the Radar beam 
 533   integer, parameter, public :: OBS_RANE  = OBS_RANS +1 ! Final range of the Radar beam 
 534
 535   ! the last column index for real header variables defined just above
 536   integer, parameter :: NHDR_REAL_END = OBS_RANE 
 537   integer, parameter :: NHDR_REAL_SIZE = NHDR_REAL_END - NHDR_REAL_BEG + 1
 538
 539   !
 540   ! REAL-HEADER COLUMN NAMES
 541   !
 542   character(len=4), target :: ocn_ColumnNameList_RH(NHDR_REAL_BEG:NHDR_REAL_END) =  &
 543      (/'LAT ','LON ','ALT ','BX  ','BY  ','BZ  ', &
 544        'M1C1','M1C2','M1C3','M1C4','M1C5','M1C6', &
 545        'M2C1','M2C2','M2C3','M2C4','M2C5','M2C6', &
 546        'M3C1','M3C2','M3C3','M3C4','M3C5','M3C6', &
 547        'M4C1','M4C2','M4C3','M4C4','M4C5','M4C6', &
 548        'M5C1','M5C2','M5C3','M5C4','M5C5','M5C6', &
 549        'M6C1','M6C2','M6C3','M6C4','M6C5','M6C6', &
 550        'M7C1','M7C2','M7C3','M7C4','M7C5','M7C6', &
 551        'S1C1','S1C2','S1C3','S1C4','S1C5','S1C6', &
 552        'S2C1','S2C2','S2C3','S2C4','S2C5','S2C6', &
 553        'S3C1','S3C2','S3C3','S3C4','S3C5','S3C6', &
 554        'S4C1','S4C2','S4C3','S4C4','S4C5','S4C6', &
 555        'S5C1','S5C2','S5C3','S5C4','S5C5','S5C6', &
 556        'S6C1','S6C2','S6C3','S6C4','S6C5','S6C6', &
 557        'S7C1','S7C2','S7C3','S7C4','S7C5','S7C6', &
 558        'CF1 ','CF2 ','CF3 ','CF4 ','CF5 ','CF6 ', &
 559        'CF7 ','ETOP','VTOP','ECF ','VCF ','HE  ', &
 560        'ZTSR','ZTM ','ZTGM','ZLQM','ZPS ','TRAD', &
 561        'GEOI','CLF ','SUN ','SZA ','AZA ','SAZ ', &
 562        'CLW1','CLW2','MWS ','SIO ','SIB ','IWV ', &
 563        'RZAM','RELE','RANS','RANE'/)
 564   !
 565   ! INTEGER-BODY COLUMN NUMBERS
 566   !
 567   ! the first column index for integer body variables defined below
 568   ! (chosen such that every column number is unique, so that a mismatch between
 569   !  a column number and a column type (real, int, head, body) can be detected)
 570   integer, parameter :: NBDY_INT_BEG = 401
 571   integer, parameter, public :: OBS_VNM = NBDY_INT_BEG ! variable number
 572   integer, parameter, public :: OBS_FLG = OBS_VNM+1  ! flags
 573   integer, parameter, public :: OBS_KFA = OBS_FLG+1  ! marker for forward interp problems
 574   integer, parameter, public :: OBS_ASS = OBS_KFA+1  ! flag to indicate if assimilated
 575   integer, parameter, public :: OBS_HIND= OBS_ASS+1  ! corresponding header row index
 576   integer, parameter, public :: OBS_VCO = OBS_HIND+1 ! type of vertical coordinate
 577   integer, parameter, public :: OBS_LYR = OBS_VCO+1  ! Index of anal level above observ'n
 578                                             ! Flag: extrapolation necessary of
 579   integer, parameter, public :: OBS_XTR = OBS_LYR+1  ! anal variables to obs'n location
 580   integer, parameter, public :: OBS_QCF2= OBS_XTR+1  ! TOVs Data Level Qc Flag
 581                                                      ! flag values for btyp=9248 block ele 033081
 582   integer, parameter, public :: OBS_CLA= OBS_QCF2+1  ! csr cloud amount btyp 9248 ele 020081
 583
 584   ! the last column index for integer body variables defined just above
 585   integer, parameter :: NBDY_INT_END = OBS_CLA
 586   integer, parameter :: NBDY_INT_SIZE = NBDY_INT_END - NBDY_INT_BEG + 1
 587
 588   !
 589   ! INTEGER-BODY COLUMN NAMES
 590   !
 591   character(len=4), target :: ocn_ColumnNameList_IB(NBDY_INT_BEG:NBDY_INT_END) = &
 592      (/ 'VNM ','FLG ','KFA ','ASS ','HIND','VCO ','LYR ','XTR ', 'QCFL ', &
 593          'CLA '/)  
 594
 595   !
 596   ! REAL-BODY COLUMN NUMBERS
 597   !
 598   ! the first column index for real body variables defined below
 599   ! (chosen such that every column number is unique, so that a mismatch between
 600   !  a column number and a column type (real, int, head, body) can be detected)
 601   integer, parameter :: NBDY_REAL_BEG   = 501
 602   integer, parameter, public :: OBS_PPP = NBDY_REAL_BEG ! vertical coordinate
 603   integer, parameter, public :: OBS_SEM = OBS_PPP +1 ! surface emissivity
 604   integer, parameter, public :: OBS_VAR = OBS_SEM +1 ! value of the observation
 605   integer, parameter, public :: OBS_OMP = OBS_VAR +1 ! obs - H (trial field)
 606   integer, parameter, public :: OBS_OMA = OBS_OMP +1 ! obs - H (analysis mean)
 607   integer, parameter, public :: OBS_OMAM= OBS_OMA +1 ! obs - H (analysis member)
 608   integer, parameter, public :: OBS_OER = OBS_OMAM +1 ! sigma(obs)
 609   integer, parameter, public :: OBS_HPHT= OBS_OER +1 ! root of (hpht with hx scalar)
 610   integer, parameter, public :: OBS_HAHT= OBS_HPHT +1 ! root of (hp_{a}ht with hx scalar)
 611   integer, parameter, public :: OBS_ZHA = OBS_HAHT+1 ! vert coordinate for Schur product
 612   integer, parameter, public :: OBS_OMP6= OBS_ZHA +1 ! obs - H (6-h trial field)
 613   integer, parameter, public :: OBS_OMA0= OBS_OMP6+1 ! obs - H (analysis at central time)
 614   integer, parameter, public :: OBS_SIGI= OBS_OMA0+1 ! ensemble-based estimate of the innov std dev
 615   integer, parameter, public :: OBS_SIGO= OBS_SIGI+1 ! ensemble-based estimate of obs std dev
 616   integer, parameter, public :: OBS_POB = OBS_SIGO+1 ! initial value of "gamma" for variational QC 
 617   integer, parameter, public :: OBS_WORK= OBS_POB +1 ! temporary values
 618   integer, parameter, public :: OBS_PRM = OBS_WORK+1 ! (adjusted) observed value for tovs in variational assimilation
 619   integer, parameter, public :: OBS_JOBS= OBS_PRM +1 ! contribution to obs cost function
 620   integer, parameter, public :: OBS_QCV = OBS_JOBS+1 ! weight-reduction factor for var QC
 621   integer, parameter, public :: OBS_FSO = OBS_QCV+1  ! forecast sensitivity to observations
 622   integer, parameter, public :: OBS_CRPS= OBS_FSO+1  ! Continuous Ranked Probability Score
 623   integer, parameter, public :: OBS_BCOR= OBS_CRPS+1 ! observation bias correction
 624   integer, parameter, public :: OBS_OMPE= OBS_BCOR+1 ! error standard deviation of [obs - H (trial field)]
 625   integer, parameter, public :: OBS_LATD= OBS_OMPE+1 ! obs LATitude  in Data table (radians)
 626   integer, parameter, public :: OBS_LOND= OBS_LATD+1 ! obs LONgitude in Data table (radians)
 627   integer, parameter, public :: OBS_BTCL= OBS_LOND+1 ! clear-sky simulated observation
 628   integer, parameter, public :: OBS_LOCI= OBS_BTCL+1 ! LOCation Information for observation (e.g. range along radar beam)
 629   
 630   ! the number of real body variables defined just above
 631   integer, parameter :: NBDY_REAL_END = OBS_LOCI
 632   integer, parameter :: NBDY_REAL_SIZE = NBDY_REAL_END - NBDY_REAL_BEG + 1
 633
 634   !
 635   ! REAL-BODY COLUMN NAMES
 636   !
 637   character(len=4), target :: ocn_ColumnNameList_RB(NBDY_REAL_BEG:NBDY_REAL_END) = &
 638      (/ 'PPP ','SEM ','VAR ','OMP ','OMA ','OMAM','OER ','HPHT','HAHT','ZHA ','OMP6',     &
 639         'OMA0','SIGI','SIGO','POB ','WORK','PRM ','JOBS','QCV ','FSO ','CRPS','BCOR',     &
 640         'OMPE','ROLA','ROLO','VAR2','LOCI' /)
 641end module ObsColumnNames_mod
 642
 643
 644
 645module ObsDataColumn_mod
 646  !
 647  ! MODULE obsDataColumn_mod (prefix='odc' category='7. Low-level data objects and utilities')
 648  !
 649  !:Purpose:
 650  !    This module is used exclusively by the obsSpaceData module which follows
 651  !    in this file. The derived type is used to represent a "column" of
 652  !    observation data in an instance of the struct_obs defined in obsSpaceData.
 653  !    It contains a pointer for each possible type of data stored in a column,
 654  !    but only one should be allocated at any time.
 655  !
 656   use codePrecision_mod
 657   use ObsColumnNames_mod
 658   implicit none
 659   save
 660   private
 661
 662   ! CLASS-CONSTANT:
 663   ! CLASS-CONSTANT:
 664   ! CLASS-CONSTANT:
 665
 666   ! This type gathers together into one structure the various CLASS-CONSTANT
 667   ! characteristics of a data column.  Four instances (flavours) of this derived
 668   ! type are defined below.
 669   type, public :: struct_odc_flavour
 670                                        ! These 2 values are informational only.
 671                                        ! They are used in error messages.
 672      character(len=4) :: dataType      ! REAL or INT
 673      character(len=4) :: headOrBody    ! HEAD or BODY
 674
 675      integer :: ncol_beg, ncol_end
 676      logical         , dimension(:), pointer :: columnActive   ! indexed from 1
 677      character(len=4), dimension(:), pointer :: columnNameList
 678
 679      integer,dimension(:), pointer ::activeIndexFromColumnIndex
 680      logical :: activeIndexFromColumnIndex_defined
 681
 682      integer,dimension(:), pointer ::columnIndexFromActiveIndex
 683      logical :: columnIndexFromActiveIndex_defined
 684   end type struct_odc_flavour
 685
 686   ! The four CLASS-CONSTANT flavours of data columns:
 687   !    Integer / Real  +  Body / Header
 688   type(struct_odc_flavour), public, target :: odc_flavour_IB, &
 689                                               odc_flavour_IH, &
 690                                               odc_flavour_RB, &
 691                                               odc_flavour_RH
 692   ! end of CLASS-CONSTANT objects
 693   ! end of CLASS-CONSTANT objects
 694   ! end of CLASS-CONSTANT objects
 695
 696
 697   ! methods
 698   public :: odc_allocate, odc_deallocate, odc_class_initialize
 699   public :: odc_initAllColumnFlavours
 700   public :: odc_activateColumn, odc_numActiveColumn
 701   public :: odc_columnElem, odc_columnSet
 702   public :: odc_columnIndexFromActiveIndex, odc_activeIndexFromColumnIndex
 703
 704   ! This type allows a single derived type to contain either a real or an int
 705   type, public :: struct_obsDataColumn
 706      logical          :: allocated = .false.
 707                                        ! For these arrays:
 708                                        !   1st dim'n:  row index (element index)
 709      integer,          pointer :: value_i(:)     => NULL()
 710      real(pre_obsReal),pointer :: value_r(:)     => NULL()
 711      character(len=4) :: dataType
 712   end type struct_obsDataColumn
 713
 714
 715   ! This type contains one array of data columns.  Four of these are necessary
 716   ! to constitute a complete set of observation data (struct_obs).
 717   type, public :: struct_obsDataColumn_Array
 718                                        ! CLASS-CONSTANT values (1 of 4 flavours)
 719      type(struct_odc_flavour), pointer :: odc_flavour => NULL()
 720                                        ! object-specific values
 721                                        !   1st dim'n: column index (column name)
 722      type(struct_obsDataColumn), dimension(:), pointer :: columns => NULL()
 723   end type struct_obsDataColumn_Array
 724  
 725
 726   ! These arrays store the status of the columns.  An active column is 
 727   ! allocated (and can therefore be used) in any object that is instantiated.
 728   logical, target :: columnActive_IH(NHDR_INT_BEG:NHDR_INT_END ) = .false.
 729   logical, target :: columnActive_RH(NHDR_REAL_BEG:NHDR_REAL_END) = .false.
 730   logical, target :: columnActive_IB(NBDY_INT_BEG:NBDY_INT_END ) = .false.
 731   logical, target :: columnActive_RB(NBDY_REAL_BEG:NBDY_REAL_END) = .false.
 732
 733   integer, target :: activeIndexFromColumnIndex_IB(NBDY_INT_BEG:NBDY_INT_END)
 734   integer, target :: activeIndexFromColumnIndex_IH(NHDR_INT_BEG:NHDR_INT_END)
 735   integer, target :: activeIndexFromColumnIndex_RB(NBDY_REAL_BEG:NBDY_REAL_END)
 736   integer, target :: activeIndexFromColumnIndex_RH(NHDR_REAL_BEG:NHDR_REAL_END)
 737
 738   integer, target :: columnIndexFromActiveIndex_IB(NBDY_INT_SIZE)
 739   integer, target :: columnIndexFromActiveIndex_IH(NHDR_INT_SIZE)
 740   integer, target :: columnIndexFromActiveIndex_RB(NBDY_REAL_SIZE)
 741   integer, target :: columnIndexFromActiveIndex_RH(NHDR_REAL_SIZE)
 742
 743   integer, public, parameter :: odc_ENKF_bdy_int_column_list(8) = &
 744      (/OBS_VNM, OBS_FLG, OBS_ASS, OBS_HIND, OBS_VCO, OBS_LYR, &
 745        OBS_QCF2, OBS_CLA /)
 746   integer, public, parameter :: odc_ENKF_bdy_real_column_list(15) = &
 747      (/OBS_PPP, OBS_SEM, OBS_VAR,  OBS_OMP,  OBS_OMA,  OBS_OER,  OBS_HPHT,&
 748        OBS_HAHT,OBS_ZHA, OBS_OMP6, OBS_OMA0, OBS_SIGI, OBS_SIGO, OBS_LATD,&
 749        OBS_LOND /)
 750
 751contains
 752
 753   subroutine odc_abort(cdmessage)
 754     !
 755     !:Purpose: Abort a job on error (same as OBS_ABORT)
 756     !
 757     !:Arguments:
 758     !           :cdmessage: message to be printed
 759     !
 760     !:Note: For debugging (i.e. UNIT_TESTING is defined), obs_abort should
 761     !        generally be followed by a 'return' in the calling routine.
 762
 763!#if defined(UNIT_TESTING)
 764!      use pFUnit
 765!#endif
 766      implicit none
 767
 768      ! Arguments:
 769      character(len=*), intent(in) :: cdmessage
 770
 771      write(*,'(//,4X,"ABORTING IN ObsDataColumn_mod:-------",/,8X,A)')cdmessage
 772      call flush(6)
 773
 774!#if defined(UNIT_TESTING)
 775!      call throw(Exception('exiting in odc_abort:' // cdmessage))
 776!#else
 777      call qqexit(1)
 778      stop
 779!#endif
 780   end subroutine odc_abort
 781
 782
 783   function odc_activeIndexFromColumnIndex(odc_flavour,column_index_in, &
 784                                           recompute_opt) result(active_index_out)
 785      !
 786      !:Purpose: The list of active columns is only a subset of all possible
 787      !           columns.  Return the index into the list of active columns, given
 788      !           the index into the list of all columns.
 789      !
 790      implicit none
 791
 792      ! Arguments:
 793      type(struct_odc_flavour), intent(inout) :: odc_flavour
 794      integer                 , intent(in)    :: column_index_in
 795      logical, optional       , intent(in)    :: recompute_opt
 796      ! Result:
 797      integer                                 :: active_index_out
 798
 799      ! Locals:
 800      integer :: active_index, &
 801                 column_index
 802      character(len=100) :: message
 803
 804      if(present(recompute_opt)) then
 805         if(recompute_opt) odc_flavour%activeIndexFromColumnIndex_defined=.false.
 806      endif
 807
 808      if(.not. odc_flavour%activeIndexFromColumnIndex_defined) then
 809         odc_flavour%activeIndexFromColumnIndex_defined=.true.
 810         active_index=0
 811         odc_flavour%activeIndexFromColumnIndex(:)=-1
 812
 813         do column_index = odc_flavour%ncol_beg, odc_flavour%ncol_end
 814            if(odc_flavour%columnActive(column_index)) then
 815               active_index=active_index+1
 816               odc_flavour%activeIndexFromColumnIndex(column_index) =active_index
 817            endif
 818         enddo
 819      endif
 820
 821      active_index_out=odc_flavour%activeIndexFromColumnIndex(column_index_in)
 822
 823      if(active_index_out == -1) then
 824         write(message,*)'ODC_activeIndexFromColumnIndex: requested column is ',&
 825                         'not active!  Column name is ', &
 826                         odc_flavour%columnNameList(column_index_in)
 827         call odc_abort(message)
 828      end if
 829   end function odc_activeIndexFromColumnIndex
 830
 831
 832   subroutine odc_allocate( odc, numRows, name, dataType, scratchReal, scratchInt )
 833     !
 834     !:Purpose: Allocate a single column of obs data according to 
 835     !           specifications in input arguments
 836     !
 837     !:Arguments:
 838     !           :odc:        instance of the obsDataColumn type
 839     !           :numRows:    number of column rows to allocate
 840     !           :name:       character string name of column
 841     !           :dataType:   character string type of column data: REAL or INT
 842     !           :headOrBody: character string indicating HEAD or BODY
 843     !
 844     !
 845      implicit none
 846
 847      ! Arguments:
 848      type(struct_obsDataColumn), intent(inout) :: odc
 849      integer,                    intent(in)    :: numRows
 850      character(len=*),           intent(in)    :: name,dataType
 851      real(pre_obsReal), pointer, intent(in)    :: scratchReal(:)
 852      integer          , pointer, intent(in)    :: scratchInt(:)
 853
 854      if(odc%allocated) then
 855         call odc_abort('ODC_ALLOCATE: column is already allocated. name=' &
 856                        // name)
 857         return
 858      endif
 859
 860      odc%allocated=.true.
 861      odc%dataType = dataType
 862
 863      select case (trim(dataType))
 864      case ('INT')
 865         allocate(odc%value_i(numRows))
 866         odc%value_i(:)=0
 867         odc%value_r   => scratchReal
 868
 869      case ('REAL')
 870         allocate(odc%value_r(numRows))
 871         odc%value_r(:)=real(0.0D0, pre_obsReal)
 872         odc%value_i   => scratchInt
 873
 874      case default
 875         call odc_abort('ODC_ALLOCATE: unknown data type. type=' // dataType)
 876      end select
 877
 878   end subroutine odc_allocate
 879
 880
 881   subroutine odc_activateColumn(odc_flavour, column_index)
 882      !
 883      !:Purpose: Set the 'active' flag for the indicated column.  This enables memory
 884      !           allocation for this column without actually allocating the memory.
 885      !
 886      implicit none
 887
 888      ! Arguments:
 889      type(struct_odc_flavour), intent(inout) :: odc_flavour
 890      integer,                  intent(in)    :: column_index
 891
 892      ! Locals:
 893      integer :: active_index, dummy_index
 894
 895      if(.not.odc_flavour%columnActive(column_index)) then
 896         odc_flavour%columnActive(column_index) = .true.
 897      endif
 898
 899      ! force the recalculation of indices to go between activeColumnIndex and
 900      ! columnIndex
 901      active_index=odc_activeIndexFromColumnIndex(odc_flavour,column_index, &
 902                                                  recompute_opt=.true.)
 903      dummy_index =odc_columnIndexFromActiveIndex(odc_flavour,active_index, &
 904                                                  recompute_opt=.true.)
 905   end subroutine odc_activateColumn
 906
 907
 908   subroutine odc_initColumnFlavour(odc_flavour, dataType_in, headOrBody_in)
 909      !
 910      !:Purpose: Set pointers according to the four column flavours (header /
 911      !           body, integer / real).
 912      !      
 913      implicit none
 914
 915      ! Arguments:
 916      type(struct_odc_flavour), intent(inout) :: odc_flavour
 917      character(len=*)        , intent(in)    :: dataType_in    ! REAL or INT
 918      character(len=*)        , intent(in)    :: headOrBody_in  ! HEAD or BODY
 919
 920      odc_flavour%dataType   = trim(dataType_in)
 921      odc_flavour%headOrBody = trim(headOrBody_in)
 922
 923      select case (trim(dataType_in))
 924      case ('REAL')
 925         select case (trim(headOrBody_in))
 926         case ('HEAD')
 927            odc_flavour%ncol_beg = NHDR_REAL_BEG
 928            odc_flavour%ncol_end = NHDR_REAL_END
 929            odc_flavour%columnActive   => columnActive_RH
 930            odc_flavour%columnNameList => ocn_ColumnNameList_RH
 931            odc_flavour%activeIndexFromColumnIndex=>activeIndexFromColumnIndex_RH
 932            odc_flavour%activeIndexFromColumnIndex_defined = .false.
 933            odc_flavour%columnIndexFromActiveIndex=>columnIndexFromActiveIndex_RH
 934            odc_flavour%columnIndexFromActiveIndex_defined = .false.
 935         case ('BODY')
 936            odc_flavour%ncol_beg = NBDY_REAL_BEG
 937            odc_flavour%ncol_end = NBDY_REAL_END
 938            odc_flavour%columnActive   => columnActive_RB
 939            odc_flavour%columnNameList => ocn_ColumnNameList_RB
 940            odc_flavour%activeIndexFromColumnIndex=>activeIndexFromColumnIndex_RB
 941            odc_flavour%activeIndexFromColumnIndex_defined = .false.
 942            odc_flavour%columnIndexFromActiveIndex=>columnIndexFromActiveIndex_RB
 943            odc_flavour%columnIndexFromActiveIndex_defined = .false.
 944         end select
 945
 946      case ('INT')
 947         select case (trim(headOrBody_in))
 948         case ('HEAD')
 949            odc_flavour%ncol_beg = NHDR_INT_BEG
 950            odc_flavour%ncol_end = NHDR_INT_END
 951            odc_flavour%columnActive   => columnActive_IH
 952            odc_flavour%columnNameList => ocn_ColumnNameList_IH
 953            odc_flavour%activeIndexFromColumnIndex=>activeIndexFromColumnIndex_IH
 954            odc_flavour%activeIndexFromColumnIndex_defined = .false.
 955            odc_flavour%columnIndexFromActiveIndex=>columnIndexFromActiveIndex_IH
 956            odc_flavour%columnIndexFromActiveIndex_defined = .false. 
 957         case ('BODY') 
 958            odc_flavour%ncol_beg = NBDY_INT_BEG
 959            odc_flavour%ncol_end = NBDY_INT_END
 960            odc_flavour%columnActive   => columnActive_IB
 961            odc_flavour%columnNameList => ocn_ColumnNameList_IB
 962            odc_flavour%activeIndexFromColumnIndex=>activeIndexFromColumnIndex_IB
 963            odc_flavour%activeIndexFromColumnIndex_defined = .false.
 964            odc_flavour%columnIndexFromActiveIndex=>columnIndexFromActiveIndex_IB
 965            odc_flavour%columnIndexFromActiveIndex_defined = .false.
 966         end select
 967      end select
 968
 969   end subroutine odc_initColumnFlavour
 970
 971
 972   subroutine odc_initAllColumnFlavours()
 973      !
 974      !:Purpose: Initialize the 4 flavours of odc
 975      !
 976      implicit none
 977
 978      ! Locals:
 979      logical, save :: firstCall = .true.
 980
 981      if (firstCall) then
 982        firstCall = .false.
 983        ! Initialize the four column flavours:
 984        call odc_initColumnFlavour(odc_flavour_IB, 'INT',  'BODY')
 985        call odc_initColumnFlavour(odc_flavour_IH, 'INT',  'HEAD')
 986        call odc_initColumnFlavour(odc_flavour_RB, 'REAL', 'BODY')
 987        call odc_initColumnFlavour(odc_flavour_RH, 'REAL', 'HEAD')
 988      end if
 989
 990   end subroutine odc_initAllColumnFlavours
 991
 992
 993   subroutine odc_class_initialize(obsColumnMode)
 994      !
 995      !:Purpose: Set variables that use the same values for all instances of the class.
 996      !
 997      implicit none
 998      ! mode controlling the subset of columns that are activated in all objects
 999
1000      ! Arguments:
1001      character(len=*), intent(in) :: obsColumnMode
1002
1003      ! Locals:
1004      integer :: column_index, list_index, ii
1005      integer, parameter :: COLUMN_LIST_SIZE = 100
1006      integer, dimension(COLUMN_LIST_SIZE) :: hdr_int_column_list, &
1007                  hdr_real_column_list, bdy_int_column_list, bdy_real_column_list
1008
1009      ! Initialize the four column flavours:
1010      call odc_initAllColumnFlavours()
1011
1012      COLUMN_MODE:if(trim(obsColumnMode) == 'ALL') then
1013         do column_index=NHDR_INT_BEG,NHDR_INT_END
1014            call odc_activateColumn(odc_flavour_IH, column_index)
1015         enddo
1016         do column_index=NHDR_REAL_BEG,NHDR_REAL_END
1017            call odc_activateColumn(odc_flavour_RH, column_index)
1018         enddo
1019         do column_index=NBDY_INT_BEG,NBDY_INT_END
1020            call odc_activateColumn(odc_flavour_IB, column_index)
1021         enddo
1022         do column_index=NBDY_REAL_BEG,NBDY_REAL_END
1023            call odc_activateColumn(odc_flavour_RB, column_index)
1024         enddo
1025
1026      elseif(trim(obsColumnMode) == 'ENKF') then COLUMN_MODE
1027
1028         hdr_int_column_list= &
1029            (/OBS_RLN , OBS_ONM , OBS_INS , OBS_ITY , OBS_SAT , OBS_TEC , OBS_DAT , &
1030              OBS_ETM , OBS_NLV , OBS_STYP, OBS_PAS , OBS_REG , OBS_IP  , OBS_ST1 , &
1031              OBS_IDF , OBS_SEN , OBS_GQF , OBS_GQL , OBS_ROQF,(0,ii=20,100) /)
1032
1033         hdr_real_column_list= &
1034            (/OBS_LAT , OBS_LON , OBS_ALT , OBS_BX  , OBS_BY  , OBS_BZ  , OBS_TRAD, &
1035              OBS_GEOI, OBS_CLF , OBS_SUN , OBS_SZA , OBS_AZA , OBS_SAZ , OBS_RZAM, &
1036              OBS_RELE, OBS_RANS, OBS_RANE, (0,ii=18,100)/)
1037
1038         bdy_int_column_list(:)    = 0
1039         bdy_int_column_list(1:size(odc_ENKF_bdy_int_column_list)) = &
1040            odc_ENKF_bdy_int_column_list(:)
1041
1042         bdy_real_column_list(:)   = 0
1043         bdy_real_column_list(1:size(odc_ENKF_bdy_real_column_list)) = &
1044            odc_ENKF_bdy_real_column_list(:)
1045
1046         do list_index=1,COLUMN_LIST_SIZE
1047            column_index = hdr_int_column_list(list_index)
1048            if(column_index == 0) exit
1049            call odc_activateColumn(odc_flavour_IH, column_index)
1050         end do
1051
1052         do list_index=1,COLUMN_LIST_SIZE
1053            column_index = hdr_real_column_list(list_index)
1054            if(column_index == 0) exit
1055            call odc_activateColumn(odc_flavour_RH, column_index)
1056         end do
1057
1058         do list_index=1,COLUMN_LIST_SIZE
1059            column_index = bdy_int_column_list(list_index)
1060            if(column_index == 0) exit
1061            call odc_activateColumn(odc_flavour_IB, column_index)
1062         end do
1063
1064         do list_index=1,COLUMN_LIST_SIZE
1065            column_index = bdy_real_column_list(list_index)
1066            if(column_index == 0) exit
1067            call odc_activateColumn(odc_flavour_RB, column_index)
1068         end do
1069
1070      elseif(trim(obsColumnMode) == 'ENKFMIDAS') then COLUMN_MODE
1071
1072         hdr_int_column_list= &
1073            (/OBS_RLN , OBS_ONM , OBS_INS , OBS_ITY , OBS_SAT , OBS_TEC , OBS_DAT , &
1074              OBS_ETM , OBS_NLV , OBS_STYP, OBS_PAS , OBS_REG , OBS_IP  , OBS_ST1 , &
1075              OBS_IDF , OBS_SEN , OBS_SWQ1, OBS_SWQ2, OBS_SWMT, OBS_SWLS, OBS_SWGA, &
1076              OBS_SWHA, OBS_GQF , OBS_GQL , OBS_ROQF, OBS_TTYP, (0,ii=27,100) /)
1077
1078         hdr_real_column_list= &
1079            (/OBS_LAT , OBS_LON , OBS_ALT , OBS_BX  , OBS_BY  , OBS_BZ  , OBS_TRAD, &
1080              OBS_GEOI, OBS_CLF , OBS_SUN , OBS_SZA , OBS_AZA , OBS_SAZ , OBS_RZAM, &
1081              OBS_RELE, OBS_RANS, OBS_RANE, (0,ii=18,100)/)
1082
1083         bdy_int_column_list= &
1084            (/OBS_VNM , OBS_FLG , OBS_ASS , OBS_HIND, OBS_VCO , OBS_LYR , OBS_XTR , &
1085              OBS_QCF2, OBS_CLA , (0,ii=10,100) /)
1086
1087         bdy_real_column_list= &
1088            (/OBS_PPP , OBS_SEM , OBS_VAR , OBS_OMP , OBS_OMA , OBS_OMAM, OBS_OER , &
1089              OBS_HPHT, OBS_HAHT, OBS_ZHA , OBS_OMP6, OBS_OMA0, OBS_SIGI, OBS_SIGO, &
1090              OBS_WORK, OBS_PRM , OBS_JOBS, OBS_BCOR, OBS_LOND, OBS_LATD, (0,ii=21,100) /)
1091
1092         do list_index=1,COLUMN_LIST_SIZE
1093            column_index = hdr_int_column_list(list_index)
1094            if(column_index == 0) exit
1095            call odc_activateColumn(odc_flavour_IH, column_index)
1096         end do
1097
1098         do list_index=1,COLUMN_LIST_SIZE
1099            column_index = hdr_real_column_list(list_index)
1100            if(column_index == 0) exit
1101            call odc_activateColumn(odc_flavour_RH, column_index)
1102         end do
1103
1104         do list_index=1,COLUMN_LIST_SIZE
1105            column_index = bdy_int_column_list(list_index)
1106            if(column_index == 0) exit
1107            call odc_activateColumn(odc_flavour_IB, column_index)
1108         end do
1109
1110         do list_index=1,COLUMN_LIST_SIZE
1111            column_index = bdy_real_column_list(list_index)
1112            if(column_index == 0) exit
1113            call odc_activateColumn(odc_flavour_RB, column_index)
1114         end do
1115
1116      elseif(trim(obsColumnMode) == 'VAR') then COLUMN_MODE
1117
1118         do column_index=NHDR_INT_BEG,NHDR_INT_END
1119            if( column_index < OBS_GQF .or. column_index > OBS_NCO2  &
1120              ) call odc_activateColumn(odc_flavour_IH, column_index)
1121         enddo
1122         do column_index=NHDR_REAL_BEG,NHDR_REAL_END
1123            if(     column_index /= OBS_BX &
1124               .and.column_index /= OBS_BY &
1125               .and.column_index /= OBS_BZ &
1126               .and. (column_index < OBS_M1C1 .or. &
1127                      column_index > OBS_ZPS) &
1128              ) call odc_activateColumn(odc_flavour_RH, column_index)
1129         enddo
1130
1131         do column_index=NBDY_INT_BEG,NBDY_INT_END
1132            if( column_index /= OBS_KFA ) call odc_activateColumn(odc_flavour_IB, column_index)
1133         enddo
1134         do column_index = NBDY_REAL_BEG, NBDY_REAL_END
1135           if(       column_index /= OBS_OMAM &
1136               .and. column_index /= OBS_OMP6 & 
1137               .and. column_index /= OBS_OMA0 &
1138               .and. column_index /= OBS_HAHT &
1139               .and. column_index /= OBS_SIGI &
1140               .and. column_index /= OBS_SIGO &
1141              )call odc_activateColumn(odc_flavour_RB, column_index)
1142         enddo
1143
1144      endif COLUMN_MODE
1145   end subroutine odc_class_initialize
1146
1147
1148   subroutine odc_columnElem(odc_array, column_index, row_index, value_i,value_r)
1149     !
1150     !:Purpose: Returns the value of the row_index'th element in the column array
1151     !           with the indicated column_index.
1152     !
1153     !           The column array can be of any one of the four possible column-array
1154     !           flavours.  The flavour is selected by one of four wrappers to this
1155     !           method.
1156     !
1157     implicit none
1158
1159     ! Arguments:
1160     type(struct_obsDataColumn_Array), intent(in)  :: odc_array
1161     integer                         , intent(in)  :: column_index
1162     integer                         , intent(in)  :: row_index
1163     integer                         , intent(out) :: value_i
1164     real(pre_obsReal)               , intent(out) :: value_r
1165
1166     ! Locals:
1167     character(len=100) :: message
1168
1169      if(      column_index >= odc_array%odc_flavour%ncol_beg &
1170         .and. column_index <= odc_array%odc_flavour%ncol_end) then
1171         if(odc_array%odc_flavour%columnActive(column_index)) then
1172            ! Return the value (return int AND real, just to make it simple)
1173            value_i = odc_array%columns(column_index)%value_i(row_index)
1174            value_r = odc_array%columns(column_index)%value_r(row_index)
1175         else
1176            write(message,*)'abort in odc_columnElem [' &
1177                          // odc_array%odc_flavour%dataType //',' &
1178                          // odc_array%odc_flavour%headOrBody // &
1179                          ']: column not active: ', &
1180                          odc_array%odc_flavour%columnNameList(column_index)
1181            call odc_abort(message)
1182         endif
1183      else
1184         write(message,*) 'abort in odc_columnElem [' &
1185                          // odc_array%odc_flavour%dataType //',' &
1186                          // odc_array%odc_flavour%headOrBody // &
1187                          ']: column index out of range: ', column_index
1188         call odc_abort(message); return
1189      endif
1190   end subroutine odc_columnElem
1191
1192
1193   function odc_columnIndexFromActiveIndex(odc_flavour,active_index_in, &
1194                                           recompute_opt) result(column_index_out)
1195      !
1196      !:Purpose: The list of active columns is only a subset of all possible
1197      !           columns.  Return the index into the list of all columns, given
1198      !           the index into the list of active columns, and given the column
1199      !           flavour.
1200      !
1201      implicit none
1202
1203      ! Arguments:
1204      type(struct_odc_flavour), intent(inout) :: odc_flavour
1205      integer                 , intent(in)    :: active_index_in
1206      logical, optional       , intent(in)    :: recompute_opt
1207      ! Result:
1208      integer                                 :: column_index_out
1209
1210      ! Locals:
1211      integer :: active_index, &
1212                 column_index
1213
1214      if(present(recompute_opt)) then
1215         if(recompute_opt) odc_flavour%columnIndexFromActiveIndex_defined=.false.
1216      endif
1217
1218      if(.not. odc_flavour%columnIndexFromActiveIndex_defined) then
1219         odc_flavour%columnIndexFromActiveIndex_defined=.true.
1220         active_index=0
1221
1222         do column_index = odc_flavour%ncol_beg, odc_flavour%ncol_end
1223            if(odc_flavour%columnActive(column_index)) then
1224               active_index=active_index+1
1225               odc_flavour%columnIndexFromActiveIndex(active_index) =column_index
1226            endif
1227         enddo
1228      endif
1229
1230      column_index_out=odc_flavour%columnIndexFromActiveIndex(active_index_in)
1231   end function odc_columnIndexFromActiveIndex
1232
1233
1234   subroutine odc_columnSet(odc_array, column_index, row_index, &
1235                            value_i, value_r, numElements, numElements_max)
1236      !
1237      !:Purpose: Sets the value of the row_index'th element in the column array with
1238      !           the indicated column_index.
1239      !
1240      !           The column array can be of any one of the four possible column-array
1241      !           flavours.  The flavour is selected by one of four wrappers to this
1242      !           method.
1243      !
1244      implicit none
1245
1246      ! Arguments:
1247      type(struct_obsDataColumn_Array), intent(inout) :: odc_array
1248      integer                         , intent(in)    :: column_index
1249      integer                         , intent(in)    :: row_index
1250      integer                         , intent(in)    :: value_i
1251      real(pre_obsReal)               , intent(in)    :: value_r
1252      integer                         , intent(inout) :: numElements
1253      integer                         , intent(in)    :: numElements_max
1254
1255      ! Locals:
1256      character(len=100) :: message
1257
1258      ! Validate the requested row_index, and
1259      ! Increment the number of elements, if necessary
1260      if(row_index > numElements_max) then
1261         write(message,*)'The requested ', &
1262                         trim(odc_array%odc_flavour%headOrBody), ' row_index, ',&
1263                         row_index,', is greater than the maximum, ', &
1264                         numElements_max
1265         call odc_abort(message)
1266
1267      else if(row_index > numElements+1) then
1268         write(message,*)'The requested ', &
1269                         trim(odc_array%odc_flavour%headOrBody), &
1270                         ' row_index, ', row_index, &
1271                         ', is beyond the next available index, ', &
1272                         numElements+1
1273         call odc_abort(message)
1274
1275      else if(row_index == numElements+1) then
1276         numElements = numElements+1
1277      end if
1278
1279
1280      ! Validate the requested column index, and
1281      ! Record the value
1282      if(      column_index >= odc_array%odc_flavour%ncol_beg &
1283         .and. column_index <= odc_array%odc_flavour%ncol_end) then
1284         if(odc_array%odc_flavour%columnActive(column_index)) then
1285            ! Record the value (record int AND real, just to make it simple)
1286            odc_array%columns(column_index)%value_i(row_index) = value_i
1287            odc_array%columns(column_index)%value_r(row_index) = value_r
1288         else
1289            write(message,*) 'abort in odc_columnSet [' &
1290                          // odc_array%odc_flavour%dataType //',' &
1291                          // odc_array%odc_flavour%headOrBody // &
1292                          ']: column not active: ', &
1293                          odc_array%odc_flavour%columnNameList(column_index)
1294            call odc_abort(message)
1295         endif
1296      else
1297         write(message,*) 'abort in odc_columnSet [' &
1298                          // odc_array%odc_flavour%dataType //',' &
1299                          // odc_array%odc_flavour%headOrBody // &
1300                          ']: column index out of range: ', column_index
1301         call odc_abort(message); return
1302      endif
1303
1304   end subroutine odc_columnSet
1305
1306
1307   subroutine odc_deallocate(odc)
1308      !
1309      !:Purpose: Deallocate a single column of obs data
1310      !
1311      !:Arguments:
1312      !           :odc: instance of the obsDataColumn type
1313      !
1314      implicit none
1315
1316      ! Arguments:
1317      type(struct_obsDataColumn), intent(inout) :: odc
1318
1319      if(.not.odc%allocated) then
1320         call odc_abort('ODC_DEALLOCATE: column is not already allocated.')
1321      endif
1322
1323      odc%allocated=.false.
1324      if(trim(odc%dataType) == 'INT' .and. associated(odc%value_i)) then
1325         deallocate(odc%value_i)
1326         nullify(odc%value_i)
1327         nullify(odc%value_r)    ! Dont deallocate:  this is not the only pointer
1328      end if
1329
1330      if(trim(odc%dataType) == 'REAL' .and. associated(odc%value_r)) then
1331         deallocate(odc%value_r)
1332         nullify(odc%value_r)
1333         nullify(odc%value_i)    ! Dont deallocate:  this is not the only pointer
1334      endif
1335
1336   end subroutine odc_deallocate
1337
1338
1339   function odc_numActiveColumn(odc_array) result(numActiveColumn)
1340      !
1341      !:Purpose: Return the number of active columns that are contained in the given
1342      !           column array.
1343      !
1344      !           The column array can be of any one of the four possible column-array
1345      !           flavours.
1346      !
1347      implicit none
1348
1349      ! Arguments:
1350      type(struct_obsDataColumn_Array), intent(in) :: odc_array
1351      ! Result:
1352      integer :: numActiveColumn
1353
1354      ! Locals:
1355      integer :: column_index
1356
1357      numActiveColumn=0
1358      do column_index = odc_array%odc_flavour%ncol_beg, &
1359                        odc_array%odc_flavour%ncol_end
1360         if(odc_array%odc_flavour%columnActive(column_index))  &
1361            numActiveColumn=numActiveColumn+1
1362      enddo
1363   end function odc_numActiveColumn
1364
1365end module ObsDataColumn_mod
1366
1367
1368module ObsSpaceData_mod
1369  ! MODULE obsSpaceData_mod (prefix='obs' category='6. High-level data objects')
1370  !
1371  !:Purpose:  This module contains the definition of the structure named "struct_obs" and
1372  !           methods for accessing its data.  An instance of struct_obs contains
1373  !           all information pertaining to a set of observation-space data.
1374  !
1375  !           This has evolved from the CMA structure, originated in work by
1376  !           D. Vasiljevic at ECMWF.
1377  !
1378  !           |
1379  !
1380  !           Very generally, obsSpaceData can be thought as two tables linked to one another. 
1381  !           
1382  !           A "Header" table:
1383  ! 
1384  !           +-----------------+------+------+------+
1385  !           | headerIndex     | Lat  | Lon  | ...  |
1386  !           +=================+======+======+======+
1387  !           |    1            |      |      |      |
1388  !           +-----------------+------+------+------+
1389  !           |    2            |      |      |      |
1390  !           +-----------------+------+------+------+
1391  !           |    ...          |      |      |      |
1392  !           +-----------------+------+------+------+
1393  !           | n_header        |      |      |      |
1394  !           +-----------------+------+------+------+
1395  !
1396  !           and a "data" table:
1397  ! 
1398  !           +-----------------+-------------------------+-------------+------+
1399  !           | dataIndex       | Associated headerIndex  | Obs Value   | ...  |
1400  !           +=================+=========================+=============+======+
1401  !           |    1            |            1            |             |      |
1402  !           +-----------------+-------------------------+-------------+------+
1403  !           |    2            |            1            |             |      |
1404  !           +-----------------+-------------------------+-------------+------+
1405  !           |    3            |            1            |             |      |
1406  !           +-----------------+-------------------------+-------------+------+
1407  !           |    4            |            1            |             |      |
1408  !           +-----------------+-------------------------+-------------+------+
1409  !           |    5            |            2            |             |      |
1410  !           +-----------------+-------------------------+-------------+------+
1411  !           |    6            |            2            |             |      |
1412  !           +-----------------+-------------------------+-------------+------+
1413  !           |    7            |            3            |             |      |
1414  !           +-----------------+-------------------------+-------------+------+
1415  !           |    8            |            3            |             |      |
1416  !           +-----------------+-------------------------+-------------+------+
1417  !           |    9            |            3            |             |      |
1418  !           +-----------------+-------------------------+-------------+------+
1419  !           | n_data          |                         |             |      |
1420  !           +-----------------+-------------------------+-------------+------+
1421  !
1422  !           |
1423  !
1424  !           * For satellite observations
1425  !               * One header contains information on lat/lon, azimuth, zenith angle and time. 
1426  !               * Data entries contain: channels, measurement value, etc. 
1427  !
1428  !             .. image:: /_static/satellite.png
1429  !                 :align: center
1430  !
1431  !           |
1432  !
1433  !           * For radar observations
1434  !               * One header for each radar "ray". Contains information on lat/lon of radar, elevation, azimuth, etc. 
1435  !               * Data entries contain: range, altitude, Doppler velocity, etc. 
1436  !
1437  !             .. image:: /_static/radar.png
1438  !                 :align: center
1439  !
1440  !           |
1441  !
1442  !           * For gps radio occultation (GPS-RO)
1443  !               * One header for each profile. Contains information on lat/lon time, etc. 
1444  !               * Data entries contain: bending angle, refractivity, etc.
1445  !
1446  !             .. image:: /_static/gps_ro.png
1447  !                 :align: center
1448  !
1449  !           |
1450  !
1451  !           * For other observations types such as radiosondes and aircrafts, there is one data entry per header rentry. 
1452  !               * headers contain information on lat/lon, time, etc. 
1453  !               * Data entries contain measurement values. 
1454  !
1455  !             .. image:: /_static/others.png
1456  !                 :align: center
1457  !
1458  !
1459  !:What:   The module  "ObsSpaceData_mod" relies on three other modules:
1460  !
1461  !          * ``IndexListDepot_mod``
1462  !          * ``ObsColumnNames_mod``
1463  !          * ``ObsDataColumn_mod``
1464  ! 
1465  !:Note:   Throughout this file:
1466  !
1467  !          * Column_index  is not (in general) indexed from one.  
1468  !          * Each column index has an equivalent name, ``OBS_*`` as defined in ``ObsColumnNames_mod``.
1469  !          * Active_index is indexed from one by definition (a column index).
1470  !          * row_index is indexed from one.  It has no equivalent name.
1471  !          * ``bodyIndex``, etc. is necessarily a row index
1472  !          * ``HeaderIndex``, etc. is necessarily a row index
1473  !
1474   use codePrecision_mod
1475   use ObsColumnNames_mod
1476   use ObsDataColumn_mod
1477   use IndexListDepot_mod
1478   use mathPhysConstants_mod
1479   implicit none
1480   save
1481   private
1482
1483
1484
1485   ! CLASS-CONSTANT:
1486   ! CLASS-CONSTANT:
1487   ! CLASS-CONSTANT:
1488
1489   logical, save :: obs_class_initialized = .false.
1490
1491   ! end of CLASS-CONSTANT variables.
1492   ! end of CLASS-CONSTANT variables
1493   ! end of CLASS-CONSTANT variables.
1494
1495
1496   ! PUBLIC METHODS:
1497   public obs_bodyElem_i ! obtain an integer body element from observation object
1498   public obs_bodyElem_r ! obtain a real body element from the observation object
1499   public obs_bodyPrimaryKey ! obtain the body primary key value
1500   public obs_bodyIndex_mpiglobal ! obtain mpiglobal body row index
1501   public obs_bodySet_i  ! set an integer body value in the observation object
1502   public obs_bodySet_r  ! set a real body value in the observation object
1503   public obs_class_initialize ! initialize class variables: column mode
1504   public obs_clean      ! remove from obs data those that not to be assimilated
1505   public obs_clean2     ! modified version of obs_clean used by MIDAS
1506   public obs_columnActive_IB ! return the active status for a column (T/F)
1507   public obs_columnActive_IH !                 "
1508   public obs_columnActive_RB !                 "
1509   public obs_columnActive_RH !                 "
1510   public obs_isColumnNameValid      ! Check if column name is a valid obsSpaceData name
1511   public obs_columnIndexFromName    ! get the index from the name (any flavour)
1512   public obs_columnIndexFromName_IB ! get the index from the name
1513   public obs_columnIndexFromName_IH !         "
1514   public obs_columnIndexFromName_RB !         "
1515   public obs_columnIndexFromName_RH !         "
1516   public obs_columnDataType ! tell user if column index has real or integer data
1517   public obs_copy       ! copy an obsdat object
1518   public obs_elem_c     ! obtain character element from the observation object
1519   public obs_enkf_prntbdy! print all data records associated with an observation
1520   public obs_enkf_prnthdr! print the header of an observation record
1521   public obs_expandToMpiGlobal ! restore data for the mpi-global context
1522   public obs_extractObsRealBodyColumn    ! return entire selected column (real/body)
1523   public obs_extractObsRealBodyColumn_r4 ! return entire selected column (real/body), real4 version
1524   public obs_extractObsIntBodyColumn     ! return entire selected column (int/body)
1525   public obs_extractObsRealHeaderColumn  ! return entire selected column (real/header)
1526   public obs_extractObsIntHeaderColumn   ! return entire selected column (int/header)
1527   public obs_finalize   ! object clean-up
1528   public obs_getBodyIndex ! obtain an element from the current body list
1529   public obs_getFamily  ! return the family of a datum
1530   public obs_getHeaderIndex ! obtain an element from the current header list
1531   public obs_getNchanAvhrr  ! to get the number of AVHRR channels
1532   public obs_getNclassAvhrr ! to get the number of AVHRR radiance classes
1533   public obs_headElem_i ! obtain an integer header element from the obs'n object
1534   public obs_headElem_r ! obtain real header element from the observation object
1535   public obs_headPrimaryKey ! obtain the header primary key value
1536   public obs_headerIndex_mpiglobal ! obtain mpiglobal header row index
1537   public obs_headSet_i  ! set an integer header value in the observation object
1538   public obs_headSet_r  ! set a real header value in the observation object
1539   public obs_initialize ! variable initialization
1540   public obs_mpiLocal   ! obtain the current mpi state of the observation object
1541   public obs_MpiRedistribute ! do a general redistribution of obs over mpi tasks
1542   public obs_numBody    ! returns the number of bodies recorded
1543   public obs_numBody_max! returns the dimensioned number of bodies
1544   public obs_numBody_mpiglobal ! returns mpi-global number of bodies recorded
1545   public obs_numHeader  ! returns the number of headers recorded
1546   public obs_numHeader_max ! returns the dimensioned number of headers
1547   public obs_numHeader_mpiglobal ! returns mpi-global number of headers recorded
1548   public obs_print      ! obs_enkf_prnthdr & obs_enkf_prntbdy for each station
1549   public obs_prntbdy    ! print the body data for one header
1550   public obs_prnthdr    ! print the data contained in one header
1551   public obs_reduceToMpiLocal ! retain only data pertinent to the mpi-local PE
1552   public obs_squeeze    ! reallocate objects arrays to reduce memory use
1553   public obs_setBodyPrimaryKey ! set the value of the body primary key
1554   public obs_setHeadPrimaryKey ! set the value of the body primary key
1555   public obs_set_c      ! set a character value in the observation object
1556   public obs_set_current_body_list   ! set a body list for a family as current
1557   public obs_set_current_header_list ! set a header list for a family as current
1558   public obs_setFamily  ! set the family of a datum
1559   public obs_sethind    ! set the header index in body table
1560   public obs_famExist   ! checks if a family is present in the observation set
1561   public obs_write      ! write the observation data to binary files
1562                         ! (calls obs_write_hdr, obs_write_bdy, obs_write_hx
1563                         !  for each station)
1564   public obs_write_hx   ! write to binary files a station's interpolated values
1565
1566   interface obs_getBodyIndex
1567      module procedure obs_getBodyIndex_depot
1568      module procedure obs_getBodyIndex_private
1569   end interface obs_getBodyIndex
1570
1571   interface obs_set_current_body_list
1572      module procedure obs_set_current_body_list_from_family
1573      module procedure obs_set_current_body_list_from_header
1574      module procedure obs_set_current_body_list_all
1575   end interface obs_set_current_body_list
1576
1577   interface obs_set_current_header_list
1578      module procedure obs_set_current_header_list_from_family
1579      module procedure obs_set_current_header_list_all
1580   end interface
1581
1582   interface obs_bodySet_r
1583      module procedure obs_bodySet_r4
1584      module procedure obs_bodySet_r8
1585   end interface obs_bodySet_r
1586
1587   interface obs_headSet_r
1588      module procedure obs_headSet_r4
1589      module procedure obs_headSet_r8
1590   end interface obs_headSet_r
1591
1592   ! PRIVATE METHODS:
1593   private obs_abort     ! abort a job on error
1594   private obs_allocate  ! array allocation
1595   private obs_columnIndexFromNameForFlavour ! get the index from the name
1596   private obs_deallocate! array de-allocation
1597   private obs_mpiDistributeIndices ! distribute header & body indices for mpi parallelization
1598   private obs_write_bdy ! write the observation data to binary files
1599   private obs_write_hdr ! write the observation header to binary files
1600
1601
1602   ! PARAMETERS INHERITED FROM ObsColumnNames_mod (make them public)
1603   !    integer-header column numbers
1604   public :: OBS_RLN, OBS_ONM, OBS_INS, OBS_IDF, OBS_ITY, OBS_SAT, OBS_TEC
1605   public :: OBS_DAT, OBS_ETM, OBS_NLV, OBS_PAS, OBS_REG, OBS_IP , OBS_SEN
1606   public :: OBS_IPF, OBS_IPC, OBS_IPT
1607   public :: OBS_ST1 
1608   public :: OBS_GQF, OBS_GQL
1609   public :: OBS_NCO2,OBS_STYP,OBS_ROQF
1610   public :: OBS_SWQ1,OBS_SWQ2,OBS_SWMT,OBS_SWLS,OBS_SWGA,OBS_SWHA
1611   public :: OBS_CHM, OBS_FOV, OBS_PRFL, OBS_PHAS, OBS_ORI
1612   public :: OBS_LCH, OBS_RTP, OBS_HDD, OBS_HDT, OBS_TFLG, OBS_LFLG
1613   public :: OBS_ORBI,OBS_AQF1, OBS_AQF2, OBS_AQF3, OBS_TTYP, OBS_INFG
1614   public :: OBS_RAIN, OBS_CHID
1615
1616   !    real-header column numbers
1617   public :: OBS_LAT, OBS_LON, OBS_ALT, OBS_BX,  OBS_BY,  OBS_BZ
1618   public :: OBS_M1C1, OBS_M1C2, OBS_M1C3, OBS_M1C4, OBS_M1C5, OBS_M1C6
1619   public :: OBS_M2C1, OBS_M2C2, OBS_M2C3, OBS_M2C4, OBS_M2C5, OBS_M2C6
1620   public :: OBS_M3C1, OBS_M3C2, OBS_M3C3, OBS_M3C4, OBS_M3C5, OBS_M3C6
1621   public :: OBS_M4C1, OBS_M4C2, OBS_M4C3, OBS_M4C4, OBS_M4C5, OBS_M4C6
1622   public :: OBS_M5C1, OBS_M5C2, OBS_M5C3, OBS_M5C4, OBS_M5C5, OBS_M5C6
1623   public :: OBS_M6C1, OBS_M6C2, OBS_M6C3, OBS_M6C4, OBS_M6C5, OBS_M6C6
1624   public :: OBS_M7C1, OBS_M7C2, OBS_M7C3, OBS_M7C4, OBS_M7C5, OBS_M7C6
1625   public :: OBS_S1C1, OBS_S1C2, OBS_S1C3, OBS_S1C4, OBS_S1C5, OBS_S1C6
1626   public :: OBS_S2C1, OBS_S2C2, OBS_S2C3, OBS_S2C4, OBS_S2C5, OBS_S2C6
1627   public :: OBS_S3C1, OBS_S3C2, OBS_S3C3, OBS_S3C4, OBS_S3C5, OBS_S3C6
1628   public :: OBS_S4C1, OBS_S4C2, OBS_S4C3, OBS_S4C4, OBS_S4C5, OBS_S4C6
1629   public :: OBS_S5C1, OBS_S5C2, OBS_S5C3, OBS_S5C4, OBS_S5C5, OBS_S5C6
1630   public :: OBS_S6C1, OBS_S6C2, OBS_S6C3, OBS_S6C4, OBS_S6C5, OBS_S6C6
1631   public :: OBS_S7C1, OBS_S7C2, OBS_S7C3, OBS_S7C4, OBS_S7C5, OBS_S7C6
1632   public :: OBS_CF1,  OBS_CF2,  OBS_CF3,  OBS_CF4,  OBS_CF5,  OBS_CF6, OBS_CF7
1633   public :: OBS_ETOP, OBS_VTOP, OBS_ECF,  OBS_VCF , OBS_HE  , OBS_ZTSR
1634   public :: OBS_ZTM , OBS_ZTGM, OBS_ZLQM, OBS_ZPS , OBS_TRAD, OBS_GEOI
1635   public :: OBS_CLF , OBS_SUN,  OBS_SZA,  OBS_AZA , OBS_SAZ , OBS_CLWO, OBS_CLWB, OBS_MWS
1636   public :: OBS_SIO , OBS_SIB,  OBS_IWV,  OBS_RZAM, OBS_RELE, OBS_RANS, OBS_RANE
1637   !    integer-body column numbers
1638   public :: OBS_VNM, OBS_FLG, OBS_KFA, OBS_ASS, OBS_HIND,OBS_VCO, OBS_LYR
1639   public :: OBS_XTR, OBS_QCF2, OBS_CLA
1640
1641   !    real-body column numbers
1642   public :: OBS_PPP, OBS_SEM, OBS_VAR, OBS_OMP, OBS_OMA, OBS_OMAM, OBS_OER
1643   public :: OBS_HPHT,OBS_HAHT,OBS_ZHA, OBS_OMP6,OBS_OMA0,OBS_SIGI, OBS_SIGO
1644   public :: OBS_WORK,OBS_PRM, OBS_JOBS,OBS_QCV, OBS_FSO, OBS_CRPS, OBS_BCOR
1645   public :: OBS_POB, OBS_OMPE,OBS_LATD,OBS_LOND,OBS_BTCL,OBS_LOCI
1646
1647   ! OBSERVATION-SPACE FUNDAMENTAL PARAMETERS
1648   integer, public, parameter :: obs_assimilated    = 1 ! OBS_ASS value for assimilated obs
1649   integer, public, parameter :: obs_notAssimilated = 0 ! OBS_ASS value for non assimilated obs
1650
1651   real(pre_obsReal), public, parameter :: obs_missingValue_R = real(MPC_missingValue_R8, pre_obsReal) ! Missing value
1652
1653   ! DERIVED TYPE AND MODULE VARIABLE DECLARATIONS
1654
1655                                        ! It is intended that these null values
1656                                        ! be used with scratchRealHeader, etc.
1657   real(pre_obsReal), parameter :: NULL_COLUMN_VALUE_R = real(-9.99D9, pre_obsReal)
1658   integer          , parameter :: NULL_COLUMN_VALUE_I = -9.99
1659
1660   ! This type is the goal of the ObsSpaceData and supporting modules.  An
1661   ! instance of this derived type contains all information pertaining to a set
1662   ! of observation-space data.
1663   type, public :: struct_obs
1664      private
1665      logical          :: allocated = .false.
1666                                        ! Two internal structures for
1667                                        ! facilitating multiple traversals of
1668                                        ! subsets of the observation-space data.
1669      type(struct_index_list_depot) :: header_index_list_depot
1670      type(struct_index_list_depot) :: body_index_list_depot
1671                                        ! For the cstnid and cfamily arrays:
1672                                        !   1st dim'n:  row index
1673      character(len=12),  pointer :: cstnid(:)
1674      character(len=2),   pointer :: cfamily(:)
1675                                        ! The primary keys for both tables
1676      integer(8), pointer :: headerPrimaryKey(:), bodyPrimaryKey(:)
1677                                        ! The four arrays of data columns
1678      type(struct_obsDataColumn_Array) :: &
1679                             realHeaders, & ! real header columns
1680                             intHeaders,  & ! integer header columns
1681                             realBodies,  & ! real body columns
1682                             intBodies      ! integer body columns
1683
1684      ! These scratch arrays are the basis for allowing the manipulation of both
1685      ! (real and integer) values of any obsDataColumn, without having to decide
1686      ! which one (real or integer) contains the sought value.  This ability
1687      ! simplifies the code.
1688      real(pre_obsReal), pointer :: scratchRealHeader(:), &
1689                                    scratchRealBody  (:)
1690      integer          , pointer :: scratchIntHeader (:), &
1691                                    scratchIntBody   (:)
1692
1693      integer :: numHeader              ! Actual number of headers on record
1694      integer :: numHeader_max          ! maximum number of headers(i.e.stations)
1695      integer :: numBody                ! Actual total number of bodies on record
1696      integer :: numBody_max            ! maximum number of bodies (i.e. data)
1697
1698                                        ! row indices of mpiglobal data, useful
1699                                        ! for transforming from mpiLocal back to
1700                                        ! mpiGlobal
1701                                        !    1st dim'n: row index, only in
1702                                        !               mpiLocal context
1703      integer, pointer, dimension(:) :: headerIndex_mpiglobal  => NULL()
1704      integer, pointer, dimension(:) :: bodyIndex_mpiglobal    => NULL()
1705
1706      logical :: mpi_local       ! T: keep only data needed by this PE (mpilocal)
1707   end type struct_obs
1708
1709
1710contains
1711
1712
1713   subroutine obs_abort(cdmessage)
1714      !
1715      !:Purpose: To stop a job when an error occurred
1716      !
1717      !:Arguments:
1718      !           :cdmessage: message to be printed
1719      !
1720
1721!#if defined(UNIT_TESTING)
1722!      use pFUnit
1723!#endif
1724      implicit none
1725
1726      ! Arguments:
1727      character(len=*), intent(in) :: cdmessage
1728
1729      write(*,'(//,4X,"ABORTING IN ObsSpaceData_mod:-------",/,8X,A)')cdmessage
1730      call flush(6)
1731
1732!#if defined(UNIT_TESTING)
1733!      call throw(Exception('exiting in obs_abort'))
1734!#else
1735      call qqexit(13)
1736      stop
1737!#endif
1738   end subroutine obs_abort
1739
1740
1741   subroutine obs_allocate(obsdat, numHeader_max, numBody_max, silent_opt)
1742      !
1743      !:Purpose: Allocate arrays according to the parameters, numHeader_max and
1744      !           numBody_max.  This is a private method.
1745      !
1746      implicit none
1747
1748      ! Arguments:
1749      type(struct_obs),  intent(inout) :: obsdat
1750      integer,           intent(in)    :: numHeader_max
1751      integer,           intent(in)    :: numBody_max
1752      logical, optional, intent(in)    :: silent_opt
1753
1754      ! Locals:
1755      logical :: silent
1756      integer :: column_index
1757
1758      if(obsdat%allocated) then
1759         call obs_abort('OBS_ALLOCATE:  a second allocation of ObsSpaceData has been attempted.')
1760         return
1761      endif
1762      obsdat%allocated=.true.
1763
1764      if(present(silent_opt)) then
1765         silent  = silent_opt
1766      else
1767         silent  = .false.
1768      end if
1769
1770      if(.not. silent) then
1771         write(*,*) ' DIMENSIONS OF OBSERVATION ARRAYS:'
1772         write(*,*) ' numHeader_max = ',numHeader_max,'  numBody_max = ', &
1773                    numBody_max
1774      end if
1775      obsdat%numHeader_max=numHeader_max
1776      obsdat%numBody_max=numBody_max
1777
1778      !
1779      ! ALLOCATE THE ARRAYS and initialize the contents
1780      !
1781      allocate(obsdat%realHeaders%columns(NHDR_REAL_BEG:NHDR_REAL_END))
1782      obsdat%realHeaders%odc_flavour => odc_flavour_RH
1783
1784      allocate(obsdat%intHeaders%columns(NHDR_INT_BEG:NHDR_INT_END))
1785      obsdat%intHeaders%odc_flavour => odc_flavour_IH
1786
1787      HEADER:if(numHeader_max > 0) then
1788         allocate(obsdat%headerPrimaryKey(numHeader_max))
1789         obsdat%headerPrimaryKey(:) = 0
1790
1791         allocate(obsdat%cfamily(numHeader_max))
1792         obsdat%cfamily(:)='XX'
1793
1794         allocate(obsdat%cstnid(numHeader_max))
1795         obsdat%cstnid(:)='XXXXXXXXXXXX'
1796
1797         allocate(obsdat%scratchRealHeader(numHeader_max))
1798         allocate(obsdat%scratchIntHeader (numHeader_max))
1799         obsdat%scratchRealHeader(:) = NULL_COLUMN_VALUE_R
1800         obsdat%scratchIntHeader (:) = NULL_COLUMN_VALUE_I
1801
1802         do column_index=NHDR_REAL_BEG,NHDR_REAL_END
1803            if(obsdat%realHeaders%odc_flavour%columnActive(column_index))  &
1804               call odc_allocate(obsdat%realHeaders%columns(column_index), &
1805                                 numHeader_max, &
1806                                 ocn_ColumnNameList_RH(column_index),'REAL',&
1807                                 obsdat%scratchRealHeader, &
1808                                 obsdat%scratchIntHeader)
1809         enddo
1810
1811         do column_index=NHDR_INT_BEG,NHDR_INT_END
1812            if(obsdat%intHeaders%odc_flavour%columnActive(column_index))  &
1813               call odc_allocate(obsdat%intHeaders%columns(column_index), &
1814                                 numHeader_max, &
1815                                 ocn_ColumnNameList_IH(column_index),'INT ', &
1816                                 obsdat%scratchRealHeader, &
1817                                 obsdat%scratchIntHeader)
1818         enddo
1819      endif HEADER
1820
1821      allocate(obsdat%realBodies%columns(NBDY_REAL_BEG:NBDY_REAL_END))
1822      obsdat%realBodies%odc_flavour => odc_flavour_RB
1823
1824      allocate(obsdat%intBodies%columns(NBDY_INT_BEG:NBDY_INT_END))
1825      obsdat%intBodies%odc_flavour => odc_flavour_IB
1826
1827      BODY:if(numBody_max > 0) then
1828         allocate(obsdat%bodyPrimaryKey(numBody_max))
1829         obsdat%bodyPrimaryKey(:)   = 0
1830
1831         allocate(obsdat%scratchRealBody(numBody_max))
1832         allocate(obsdat%scratchIntBody (numBody_max))
1833         obsdat%scratchRealBody(:) = NULL_COLUMN_VALUE_R
1834         obsdat%scratchIntBody (:) = NULL_COLUMN_VALUE_I
1835
1836         do column_index=NBDY_REAL_BEG,NBDY_REAL_END
1837            if(obsdat%realBodies%odc_flavour%columnActive(column_index))  &
1838               call odc_allocate(obsdat%realBodies%columns(column_index), &
1839                                 numBody_max, &
1840                                 ocn_ColumnNameList_RB(column_index),'REAL', &
1841                                 obsdat%scratchRealBody, obsdat%scratchIntBody)
1842         enddo
1843
1844         do column_index=NBDY_INT_BEG,NBDY_INT_END
1845            if(obsdat%intBodies%odc_flavour%columnActive(column_index))  &
1846               call odc_allocate(obsdat%intBodies%columns(column_index), &
1847                                 numBody_max, &
1848                                 ocn_ColumnNameList_IB(column_index),'INT ', &
1849                                 obsdat%scratchRealBody, obsdat%scratchIntBody)
1850         enddo
1851      endif BODY
1852
1853      call ild_initialize(obsdat%header_index_list_depot, obsdat%numHeader_max)
1854      call ild_initialize(obsdat%body_index_list_depot,   obsdat%numBody_max)
1855   end subroutine obs_allocate
1856
1857
1858   function obs_bodyElem_i( obsdat, column_index, row_index) result(value_i)
1859      !
1860      !:Purpose: To control access to the observation object.  Returns the (integer)
1861      !           value of the row_index'th ObsData element with the indicated column
1862      !           index from the "body".
1863      !
1864      implicit none
1865
1866      ! Arguments:
1867      type(struct_obs), intent(in)  :: obsdat
1868      integer         , intent(in)  :: column_index
1869      integer         , intent(in)  :: row_index
1870      ! Result:
1871      integer                       :: value_i
1872
1873      ! Locals:
1874      real(pre_obsReal) :: value_r         ! not used
1875
1876      call odc_columnElem(obsdat%intBodies, column_index, row_index, &
1877                          value_i, value_r)
1878   end function obs_bodyElem_i
1879
1880
1881   function obs_bodyElem_r(obsdat,column_index,row_index) result(value_r)
1882      !
1883      !:Purpose: Get a real-valued body observation-data element
1884      !      To control access to the observation object.  Returns the (real)
1885      !      value of the row_index'th ObsData element with the indicated column
1886      !      index from the "body".
1887      !
1888      implicit none
1889
1890      ! Arguments:
1891      type(struct_obs), intent(in)  :: obsdat
1892      integer         , intent(in)  :: column_index
1893      integer         , intent(in)  :: row_index
1894      ! Result:
1895      real(pre_obsReal)             :: value_r
1896
1897      ! Locals:
1898      integer :: value_i                ! not used
1899
1900      call odc_columnElem(obsdat%realBodies, column_index, row_index, &
1901                          value_i, value_r)
1902   end function obs_bodyElem_r
1903
1904
1905   function obs_bodyPrimaryKey(obsdat,row_index) result(primaryKey)
1906      !
1907      !:Purpose: Get the body primary key value.
1908      !
1909      implicit none
1910
1911      ! Arguments:
1912      type(struct_obs), intent(in)  :: obsdat
1913      integer         , intent(in)  :: row_index
1914      ! Result:
1915      integer(8)                    :: primaryKey
1916
1917      primaryKey = obsdat%bodyPrimaryKey(row_index)
1918
1919   end function obs_bodyPrimaryKey
1920
1921
1922   function obs_bodyIndex_mpiglobal(obsdat,row_index) result(value)
1923      !
1924      !:Purpose: Get the mpiglobal body row_index.
1925      !      To control access to the mpiglobal row_index into the "body".
1926      !
1927      implicit none
1928
1929      ! Arguments:
1930      type(struct_obs), intent(in)  :: obsdat
1931      integer         , intent(in)  :: row_index
1932      ! Result:
1933      integer value
1934
1935      value=obsdat%bodyIndex_mpiglobal(row_index)
1936   end function obs_bodyIndex_mpiglobal
1937
1938
1939   subroutine obs_bodySet_i(obsdat, column_index, row_index, value_i)
1940      !
1941      !:Purpose: Set an integer-valued body observation-data element.
1942      !      To control access to the observation object.  Sets the (integer)
1943      !      value of the row_index'th ObsData element with the indicated column
1944      !      index from the "body".
1945      !
1946      implicit none
1947
1948      ! Arguments:
1949      type(struct_obs), intent(inout)  :: obsdat
1950      integer         , intent(in)     :: column_index
1951      integer         , intent(in)     :: row_index
1952      integer         , intent(in)     :: value_i
1953
1954      call odc_columnSet(obsdat%intBodies, column_index, row_index, &
1955                         value_i, NULL_COLUMN_VALUE_R, &
1956                         obsdat%numBody, obsdat%numBody_max)
1957   end subroutine obs_bodySet_i
1958
1959
1960   subroutine obs_bodySet_r4(obsdat, column_index, row_index, value_r4)
1961      !
1962      !:Purpose: Set a real-valued body observation-data element.
1963      !      To control access to the observation object.  Sets the (real)
1964      !      value of the row_index'th ObsData element with the indicated column
1965      !      index from the "body".
1966      !
1967      implicit none
1968
1969      ! Arguments:
1970      type(struct_obs), intent(inout)  :: obsdat
1971      integer         , intent(in)     :: column_index
1972      integer         , intent(in)     :: row_index
1973      real(4)         , intent(in)     :: value_r4
1974
1975      ! Locals:
1976      real(pre_obsReal)                :: value_r
1977
1978      value_r = value_r4
1979
1980      call odc_columnSet(obsdat%realBodies, column_index, row_index, &
1981                         NULL_COLUMN_VALUE_I, value_r, &
1982                         obsdat%numBody, obsdat%numBody_max)
1983   end subroutine obs_bodySet_r4
1984
1985
1986   subroutine obs_bodySet_r8(obsdat, column_index, row_index, value_r8)
1987      !
1988      !:Purpose: Set a real-valued body observation-data element.
1989      !      To control access to the observation object.  Sets the (real)
1990      !      value of the row_index'th ObsData element with the indicated column
1991      !      index from the "body".
1992      !
1993      implicit none
1994
1995      ! Arguments:
1996      type(struct_obs), intent(inout)  :: obsdat
1997      integer         , intent(in)     :: column_index
1998      integer         , intent(in)     :: row_index
1999      real(8)         , intent(in)     :: value_r8
2000
2001      ! Locals:
2002      real(pre_obsReal)                :: value_r
2003
2004      value_r = value_r8
2005
2006      call odc_columnSet(obsdat%realBodies, column_index, row_index, &
2007                         NULL_COLUMN_VALUE_I, value_r, &
2008                         obsdat%numBody, obsdat%numBody_max)
2009   end subroutine obs_bodySet_r8
2010
2011
2012   subroutine obs_class_initialize(obsColumnMode_in)
2013      !
2014      !:Purpose: Set observation-data class variables.
2015      !      Set variables that take the same value for all instances of the
2016      !      class.
2017      !
2018      implicit none
2019
2020      ! Arguments:
2021      character(len=*), intent(in)  :: obsColumnMode_in ! mode controlling subset of columns activated in all objects
2022
2023      ! Locals:
2024      character(len=12), save :: obsColumnMode_class = '            '
2025
2026      INITIALIZED: if(.not. obs_class_initialized) then
2027         obs_class_initialized = .true.
2028
2029         ! Determine which columns will be initially active
2030         obsColumnMode_class=trim(obsColumnMode_in)
2031
2032         write(*,*)'OBS_CLASS_INITIALIZE: obsColumnMode=', &
2033                   trim(obsColumnMode_class)
2034
2035         ! DELEGATE THE REAL WORK TO THE ODC CLASS
2036         ! DELEGATE THE REAL WORK TO THE ODC CLASS
2037         call odc_class_initialize(obsColumnMode_class)
2038
2039      else INITIALIZED
2040         write(*,*) 'obs_class_initialize: !!! WARNING WARNING WARNING!!!'
2041         write(*,*) 'obs_class_initialize: already called before, not ' &
2042                    // 're-activating columns'
2043         write(*,*) 'obs_class_initialize: !!! WARNING WARNING WARNING!!!'
2044         if(trim(obsColumnMode_in) /= trim(obsColumnMode_class)) then
2045            call obs_abort('obs_class_initialize: called with different '&
2046                         //'value of obsColumnMode than during first call: '&
2047                         // trim(obsColumnMode_class) // ' /= ' &
2048                         // trim(obsColumnMode_in))
2049            return
2050         endif
2051      endif INITIALIZED
2052   end subroutine obs_class_initialize
2053
2054
2055   subroutine obs_clean(obsdat,hx,nens,nobsout,qcvar,checkZha_opt)
2056      !
2057      !:Purpose: remove all observations from the obsdat  
2058      !           that will not be assimilated. 
2059      !
2060      !:Arguments:
2061      !           :nobsout: unit number for the ASCII output
2062      !           :qcvar:   input logical indicating if the input obsdat 
2063      !                     data have benefited from a qc-var procedure
2064      !:The logic applied:
2065      !     A body (and its associated header)
2066      !     will be retained if these three conditions are all met:
2067      !
2068      !         - 1) either of:
2069      !
2070      !             - 1a) btest(obsdat%intBodies%columns(OBS_FLG,jdata),12)
2071      !             - 1b) .not. qcvar (the 5th parameter of obs_clean)
2072      !         - 2) obsdat% intBodies%columns(OBS_ASS,jdata) == 1
2073      !         - 3) obsdat%realBodies%columns(OBS_ZHA,jdata) >= 0.0
2074      !
2075      implicit none
2076
2077      ! Arguments:
2078      type (struct_obs), intent(inout) :: obsdat
2079      real(8),           intent(inout) :: hx(:,:)
2080      integer,           intent(in)    :: nens
2081      integer,           intent(in)    :: nobsout
2082      logical,           intent(in)    :: qcvar
2083      logical, optional, intent(in)    :: checkZha_opt
2084
2085      ! Locals:
2086      integer :: iaccept,idata,ipnt,iwrite
2087      integer :: jdata,kobs,var3d,kobsout
2088      integer :: column_index
2089      integer :: active_index
2090      logical :: checkZha
2091
2092      if (nobsout > 0) write(nobsout,'(1x,A,I7)') 'stations prior to cleanup: ', obsdat%numHeader
2093      write(*,*) 'enter obs_clean'
2094
2095      ! User can choose if check on negativity of OBS_ZHA is done (is done by default)
2096      if (present(checkZha_opt)) then
2097        checkZha = checkZha_opt
2098      else
2099        checkZha = .true.
2100      end if
2101
2102      kobsout=0 
2103      iwrite=0
2104      stations: do kobs=1,obsdat%numHeader
2105         ipnt  = obs_headElem_i(obsdat, OBS_RLN, kobs)
2106         idata = obs_headElem_i(obsdat, OBS_NLV, kobs)
2107         iaccept=0
2108         observations: do jdata = ipnt, ipnt + idata - 1
2109            if (     btest(obs_bodyElem_i(obsdat, OBS_FLG, jdata),12) &
2110                .or. .not. qcvar) then 
2111               ! data will be accepted if they went through the variational
2112               ! system  including the qcvar. They will also be accepted if the
2113               ! qcvar procedure was not applied (i.e. when backalt files are
2114               ! used as input).
2115               var3d=1
2116            else
2117               var3d=0
2118            endif
2119
2120            ! To remove observations for which the height in the atmosphere has
2121            ! not been assigned (for instance because they are above the model
2122            ! top for the EnKF system)
2123            if (checkZha .and. obs_bodyElem_r(obsdat, OBS_ZHA, jdata) < 0.) then
2124               call obs_bodySet_i(obsdat, OBS_ASS, jdata, -1)
2125            endif
2126
2127            if (      (obs_bodyElem_i(obsdat, OBS_ASS, jdata) == obs_assimilated) &
2128                 .and.(var3d == 1)) then 
2129               ! the observation will be used in the analysis
2130               iaccept=iaccept+1
2131               iwrite=iwrite+1
2132               do active_index=1,odc_numActiveColumn(obsdat%intBodies)
2133                  column_index=odc_columnIndexFromActiveIndex( &
2134                                       obsdat%intBodies%odc_flavour,active_index)
2135                     call obs_bodySet_i (obsdat, column_index, iwrite, &
2136                          obs_bodyElem_i(obsdat, column_index, jdata))
2137               enddo
2138               do active_index=1,odc_numActiveColumn(obsdat%realBodies)
2139                  column_index=odc_columnIndexFromActiveIndex( &
2140                                      obsdat%realBodies%odc_flavour,active_index)
2141                     call obs_bodySet_r (obsdat, column_index, iwrite, &
2142                          obs_bodyElem_r(obsdat, column_index, jdata))
2143               enddo
2144               hx(1:nens,iwrite)=hx(1:nens,jdata)
2145               ! Revise the header row cross-index to match the revised headers
2146               call obs_bodySet_i (obsdat, OBS_HIND, iwrite, kobsout+1)
2147            endif
2148         enddo observations
2149
2150         ! adjust obsdat%realHeaders%columns 
2151         if (iaccept > 0) then
2152            kobsout=kobsout+1
2153            do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
2154               column_index = odc_columnIndexFromActiveIndex( &
2155                                      obsdat%intHeaders%odc_flavour,active_index)
2156               call obs_headSet_i (obsdat, column_index, kobsout, &
2157                    obs_headElem_i(obsdat, column_index, kobs))
2158            enddo
2159            do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
2160               column_index = odc_columnIndexFromActiveIndex( &
2161                                     obsdat%realHeaders%odc_flavour,active_index)
2162               call obs_headSet_r (obsdat, column_index, kobsout, &
2163                    obs_headElem_r(obsdat, column_index, kobs))
2164            enddo
2165            obsdat%headerPrimaryKey(kobsout) = obsdat%headerPrimaryKey(kobs)
2166            obsdat%cstnid(kobsout)=obsdat%cstnid(kobs)
2167            obsdat%cfamily(kobsout)=obsdat%cfamily(kobs)
2168            ! Revise the body cross-indices to match the revised bodies
2169            call obs_headSet_i(obsdat, OBS_NLV, kobsout, iaccept)
2170            call obs_headSet_i(obsdat, OBS_RLN, kobsout, iwrite-iaccept+1)
2171         endif
2172      enddo stations
2173      obsdat%numHeader=kobsout
2174      obsdat%numBody = iwrite
2175
2176      if (nobsout > 0) then
2177        write(nobsout,'(1x,A)') 'after cleanup of the cma: '
2178        write(nobsout,'(1x,A,I7)') &
2179             'number of stations containing valid data   ',obsdat%numHeader
2180        write(nobsout,'(1x,A,I7)') & 
2181             'number of observations now in the cma file ',obsdat%numBody
2182      end if
2183
2184   end subroutine obs_clean
2185
2186
2187   subroutine obs_clean2(obsdat)
2188     !
2189     !:Purpose: remove all observations from the obsdat  
2190     !           that will not be assimilated. 
2191     !           modified version of obs_clean, used by MIDAS
2192     !
2193     implicit none
2194
2195     ! Arguments:
2196     type (struct_obs), intent(inout) :: obsdat
2197
2198     ! Locals:
2199     integer :: numBodyAccept,bodyIndexEnd,bodyIndexBeg,numBodyCleaned
2200     integer :: bodyIndex,headerIndex,numHeaderCleaned
2201     integer :: column_index
2202     integer :: active_index
2203
2204     write(*,*) 'before cleanup of obsSpaceData:'
2205     write(*,*) 'number of headers ', obsdat%numHeader
2206     write(*,*) 'number of bodies  ', obsdat%numBody
2207
2208     numHeaderCleaned = 0 
2209     numBodyCleaned = 0
2210     stations: do headerIndex = 1, obsdat%numHeader
2211       bodyIndexBeg = obs_headElem_i(obsdat, OBS_RLN, headerIndex)
2212       bodyIndexEnd = obs_headElem_i(obsdat, OBS_NLV, headerIndex) + bodyIndexBeg - 1
2213       numBodyAccept = 0
2214       observations: do bodyIndex = bodyIndexBeg, bodyIndexEnd
2215         if ( obs_bodyElem_i(obsdat,OBS_ASS,bodyIndex) == obs_assimilated ) then
2216           ! the observation will be used in the analysis
2217           numBodyAccept = numBodyAccept + 1
2218           numBodyCleaned = numBodyCleaned + 1
2219           do active_index = 1, odc_numActiveColumn(obsdat%intBodies)
2220             column_index = odc_columnIndexFromActiveIndex( &
2221                              obsdat%intBodies%odc_flavour,active_index)
2222             call obs_bodySet_i (obsdat, column_index, numBodyCleaned, &
2223                  obs_bodyElem_i(obsdat, column_index, bodyIndex))
2224           enddo
2225           do active_index = 1, odc_numActiveColumn(obsdat%realBodies)
2226             column_index = odc_columnIndexFromActiveIndex( &
2227                                 obsdat%realBodies%odc_flavour,active_index)
2228             call obs_bodySet_r (obsdat, column_index, numBodyCleaned, &
2229                                 obs_bodyElem_r(obsdat, column_index, bodyIndex))
2230           enddo
2231           ! Revise the header row cross-index to match the revised headers
2232           call obs_bodySet_i (obsdat, OBS_HIND, numBodyCleaned, numHeaderCleaned+1)
2233         endif
2234       enddo observations
2235
2236       ! adjust obsdat%realHeaders%columns 
2237       if (numBodyAccept > 0) then
2238         numHeaderCleaned = numHeaderCleaned + 1
2239         do active_index = 1, odc_numActiveColumn(obsdat%intHeaders)
2240           column_index = odc_columnIndexFromActiveIndex( &
2241                                  obsdat%intHeaders%odc_flavour,active_index)
2242           call obs_headSet_i (obsdat, column_index, numHeaderCleaned, &
2243                obs_headElem_i(obsdat, column_index, headerIndex))
2244         enddo
2245         do active_index = 1, odc_numActiveColumn(obsdat%realHeaders)
2246           column_index = odc_columnIndexFromActiveIndex( &
2247                                 obsdat%realHeaders%odc_flavour,active_index)
2248           call obs_headSet_r (obsdat, column_index, numHeaderCleaned, &
2249                obs_headElem_r(obsdat, column_index, headerIndex))
2250         enddo
2251         obsdat%headerPrimaryKey(numHeaderCleaned) = obsdat%headerPrimaryKey(headerIndex)
2252         obsdat%cstnid(numHeaderCleaned) = obsdat%cstnid(headerIndex)
2253         obsdat%cfamily(numHeaderCleaned) = obsdat%cfamily(headerIndex)
2254         ! Revise the body cross-indices to match the revised bodies
2255         call obs_headSet_i(obsdat, OBS_NLV, numHeaderCleaned, numBodyAccept)
2256         call obs_headSet_i(obsdat, OBS_RLN, numHeaderCleaned, numBodyCleaned-numBodyAccept+1)
2257       endif
2258     enddo stations
2259     obsdat%numHeader = numHeaderCleaned
2260     obsdat%numBody = numBodyCleaned
2261
2262     ! reallocate obsdat to free up memory
2263     call obs_squeeze(obsdat)
2264
2265     write(*,*) 'after cleanup of obsSpaceData: '
2266     write(*,*) 'number of headers with valid data   ',obsdat%numHeader
2267     write(*,*) 'number of bodies kept               ',obsdat%numBody
2268
2269   end subroutine obs_clean2
2270
2271   function obs_columnActive_IB(obsdat,column_index) result(columnActive)
2272      !
2273      !:Purpose:
2274      !      Return the active status for a column
2275      !
2276      implicit none
2277
2278      ! Arguments:
2279      type (struct_obs), intent(inout) :: obsdat
2280      integer,           intent(in)    :: column_index
2281      ! Result:
2282      logical :: columnActive
2283
2284      columnActive = obsdat%intBodies%odc_flavour%columnActive(column_index)
2285
2286   end function obs_columnActive_IB
2287
2288
2289   function obs_columnActive_IH(obsdat,column_index) result(columnActive)
2290      !
2291      !:Purpose:
2292      !      Return the active status for a column
2293      !
2294      implicit none
2295
2296      ! Arguments:
2297      type (struct_obs), intent(inout) :: obsdat
2298      integer,           intent(in)    :: column_index
2299      ! Result:
2300      logical :: columnActive
2301
2302      columnActive = obsdat%intHeaders%odc_flavour%columnActive(column_index)
2303
2304   end function obs_columnActive_IH
2305
2306
2307   function obs_columnActive_RB(obsdat,column_index) result(columnActive)
2308      !
2309      !:Purpose:
2310      !      Return the active status for a column
2311      !
2312      implicit none
2313
2314      ! Arguments:
2315      type (struct_obs), intent(inout) :: obsdat
2316      integer,           intent(in)    :: column_index
2317      ! Result:
2318      logical :: columnActive
2319
2320      columnActive = obsdat%realBodies%odc_flavour%columnActive(column_index)
2321
2322   end function obs_columnActive_RB
2323
2324
2325   function obs_columnActive_RH(obsdat,column_index) result(columnActive)
2326      !
2327      !:Purpose:
2328      !      Return the active status for a column
2329      !
2330      implicit none
2331
2332      ! Arguments:
2333      type (struct_obs), intent(inout) :: obsdat
2334      integer,           intent(in)    :: column_index
2335      ! Result:
2336      logical :: columnActive
2337
2338      columnActive = obsdat%realHeaders%odc_flavour%columnActive(column_index)
2339
2340   end function obs_columnActive_RH
2341
2342
2343   function obs_columnIndexFromName(column_name) result(column_index_out)
2344      !
2345      !:Purpose:
2346      !      Situations do occur where the client knows only the name of a
2347      !      column, but needs to know its index. This method supplies the index.
2348      !
2349      implicit none
2350
2351      ! Arguments:
2352      character(len=*)        , intent(in) :: column_name
2353      ! Result:
2354      integer                              :: column_index_out
2355
2356      ! Locals:
2357      integer            :: column_index
2358      logical            :: lfound
2359      character(len=100) :: message
2360
2361      lfound=.false.
2362
2363      ! check integer-header
2364      do column_index=odc_flavour_IH%ncol_beg, odc_flavour_IH%ncol_end
2365         if(trim(column_name) == &
2366            trim(odc_flavour_IH%columnNameList(column_index)))then
2367            lfound=.true.
2368            column_index_out = column_index
2369            exit
2370         endif
2371      enddo
2372      if (lfound) return
2373
2374      ! check real-header
2375      do column_index=odc_flavour_RH%ncol_beg, odc_flavour_RH%ncol_end
2376         if(trim(column_name) == &
2377            trim(odc_flavour_RH%columnNameList(column_index)))then
2378            lfound=.true.
2379            column_index_out = column_index
2380            exit
2381         endif
2382      enddo
2383      if (lfound) return
2384
2385      ! check integer-body
2386      do column_index=odc_flavour_IB%ncol_beg, odc_flavour_IB%ncol_end
2387         if(trim(column_name) == &
2388            trim(odc_flavour_IB%columnNameList(column_index)))then
2389            lfound=.true.
2390            column_index_out = column_index
2391            exit
2392         endif
2393      enddo
2394      if (lfound) return
2395
2396      ! check real-body
2397      do column_index=odc_flavour_RB%ncol_beg, odc_flavour_RB%ncol_end
2398         if(trim(column_name) == &
2399            trim(odc_flavour_RB%columnNameList(column_index)))then
2400            lfound=.true.
2401            column_index_out = column_index
2402            exit
2403         endif
2404      enddo
2405      if (lfound) return
2406
2407      write(message,*)'abort in obs_columnIndexFromName: ' // &
2408                      'name not found='// trim(column_name)
2409      call obs_abort(message); return
2410
2411   end function obs_columnIndexFromName
2412
2413
2414  function obs_isColumnNameValid(column_name) result(isValid)  
2415    !
2416    !:Purpose: Check if the obsSpaceData column name is valid.
2417    !      
2418    implicit none
2419
2420    ! Arguments:
2421    character(len=*), intent(in)  :: column_name
2422    ! Result:
2423    logical                       :: isValid
2424
2425    ! Locals: 
2426    integer                       :: column_index
2427
2428    call odc_initAllColumnFlavours()
2429
2430    isValid = .false.
2431
2432    ! check "STID" 
2433    if (trim(column_name) == "STID") isValid = .true.
2434 
2435    if (isValid) return
2436
2437    ! check integer-header
2438    do column_index = odc_flavour_IH%ncol_beg, odc_flavour_IH%ncol_end
2439      if (trim(column_name) == &
2440           trim(odc_flavour_IH%columnNameList(column_index))) then
2441        isValid = .true.
2442        exit
2443      end if
2444    end do
2445    if (isValid) return
2446
2447    ! check real-header
2448    do column_index = odc_flavour_RH%ncol_beg, odc_flavour_RH%ncol_end
2449      if (trim(column_name) == &
2450           trim(odc_flavour_RH%columnNameList(column_index))) then
2451        isValid = .true.
2452        exit
2453      end if
2454    end do
2455    if (isValid) return
2456  
2457    ! check integer-body
2458    do column_index = odc_flavour_IB%ncol_beg, odc_flavour_IB%ncol_end
2459      if (trim(column_name) == &
2460           trim(odc_flavour_IB%columnNameList(column_index))) then
2461        isValid = .true.
2462        exit
2463      end if
2464    end do
2465    if (isValid) return
2466
2467    ! check real-body
2468    do column_index = odc_flavour_RB%ncol_beg, odc_flavour_RB%ncol_end
2469      if (trim(column_name) == &
2470           trim(odc_flavour_RB%columnNameList(column_index))) then
2471        isValid = .true.
2472        exit
2473      end if
2474    end do
2475    if (isValid) return
2476   
2477  end function obs_isColumnNameValid
2478
2479
2480   function obs_columnIndexFromNameForFlavour(odc_flavour, column_name) &
2481                                                         result(column_index_out)
2482      !
2483      !:Purpose:
2484      !      Situations do occur where the client knows only the name of a
2485      !      column, but needs to know its index. This method supplies the index.
2486      !
2487      implicit none
2488
2489      ! Arguments:
2490      type(struct_odc_flavour), intent(in) :: odc_flavour
2491      character(len=*)        , intent(in) :: column_name
2492      ! Result:
2493      integer                              :: column_index_out
2494
2495      ! Locals:
2496      integer            :: column_index
2497      logical            :: lfound
2498      character(len=100) :: message
2499
2500      lfound=.false.
2501      do column_index=odc_flavour%ncol_beg, odc_flavour%ncol_end
2502         if(trim(column_name) == &
2503            trim(odc_flavour%columnNameList(column_index)))then
2504            lfound=.true.
2505            column_index_out = column_index
2506            exit
2507         endif
2508      enddo
2509
2510      if(.not.lfound) then
2511         write(message,*)'abort in obs_columnIndexFromNameForFlavour [' &
2512                       // odc_flavour%dataType //','// odc_flavour%headOrBody //&
2513                       ']: name not found='// column_name
2514         call obs_abort(message); return
2515      end if
2516   end function obs_columnIndexFromNameForFlavour
2517
2518
2519   function obs_columnIndexFromName_IB(column_name) result(column_index)
2520      !
2521      !:Purpose:
2522      !      This wrapper around obs_columnIndexFromName selects the data-column
2523      !      flavour.
2524      !
2525      implicit none
2526
2527      ! Arguments:
2528      character(len=*), intent(in) :: column_name
2529      ! Result:
2530      integer                      :: column_index
2531
2532      column_index = obs_columnIndexFromNameForFlavour(odc_flavour_IB, column_name)
2533   end function obs_columnIndexFromName_IB
2534
2535
2536   function obs_columnIndexFromName_IH(column_name) result(column_index)
2537      !
2538      !:Purpose:
2539      !      This wrapper around obs_columnIndexFromName selects the data-column
2540      !      flavour.
2541      !
2542      implicit none
2543
2544      ! Arguments:
2545      character(len=*), intent(in) :: column_name
2546      ! Result:
2547      integer                      :: column_index
2548
2549      column_index = obs_columnIndexFromNameForFlavour(odc_flavour_IH, column_name)
2550   end function obs_columnIndexFromName_IH
2551
2552
2553   function obs_columnIndexFromName_RB(column_name) result(column_index)
2554      !
2555      !:Purpose:
2556      !      This wrapper around obs_columnIndexFromName selects the data-column
2557      !      flavour.
2558      !
2559      implicit none
2560
2561      ! Arguments:
2562      character(len=*), intent(in) :: column_name
2563      ! Result:
2564      integer                      :: column_index
2565
2566      column_index = obs_columnIndexFromNameForFlavour(odc_flavour_RB, column_name)
2567   end function obs_columnIndexFromName_RB
2568
2569
2570   function obs_columnIndexFromName_RH(column_name) result(column_index)
2571      !
2572      !:Purpose:
2573      !      This wrapper around obs_columnIndexFromName selects the data-column
2574      !      flavour.
2575      !
2576      implicit none
2577
2578      ! Arguments:
2579      character(len=*), intent(in) :: column_name
2580      ! Result:
2581      integer                      :: column_index
2582
2583      column_index = obs_columnIndexFromNameForFlavour(odc_flavour_RH, column_name)
2584   end function obs_columnIndexFromName_RH
2585
2586
2587   function obs_columnDataType(columnIndex) result(dataType)
2588     !
2589     !:Purpose: return the data type of column, either 'real' or 'integer'
2590     !
2591     implicit none
2592
2593     ! Arguments:
2594     integer, intent(in) :: columnIndex
2595     ! Result:
2596     character(len=7)    :: dataType
2597
2598     if (columnIndex >= NHDR_INT_BEG .and. columnIndex <= NHDR_INT_END) then
2599       dataType = 'integer'
2600     else if (columnIndex >= NHDR_REAL_BEG .and. columnIndex <= NHDR_REAL_END) then
2601       dataType = 'real'
2602     else if (columnIndex >= NBDY_INT_BEG .and. columnIndex <= NBDY_INT_END) then
2603       dataType = 'integer'
2604     else if (columnIndex >= NBDY_REAL_BEG .and. columnIndex <= NBDY_REAL_END) then
2605       dataType = 'real'
2606     else
2607       call obs_abort('obs_columnDataType: invalid value of column index')
2608     end if
2609
2610   end function obs_columnDataType
2611
2612
2613   subroutine obs_copy( obs_a, obs_b )
2614      !
2615      !:Purpose: copy an obsdat object
2616      !
2617      !:Arguments:
2618      !           :obs_a: input object
2619      !           :obs_b: a copy of obs_a
2620      !
2621      !:Note: this method assumes that obs_b has already been initialized
2622      !
2623      implicit none
2624
2625      ! Arguments:
2626      type(struct_obs), intent(in)    :: obs_a
2627      type(struct_obs), intent(inout) :: obs_b
2628
2629      ! Locals:
2630      integer :: column_index
2631
2632      ! check if object to be copied is empty and react appropriately
2633      if (obs_a%numHeader_max == 0 .or. obs_a%numBody_max == 0) then
2634         obs_b%numHeader     = obs_a%numHeader
2635         obs_b%numHeader_max = obs_a%numHeader_max
2636         obs_b%numBody       = obs_a%numBody
2637         obs_b%numBody_max   = obs_a%numBody_max
2638         return
2639      endif
2640
2641      !** Commented out by M. Buehner to allow use in EnVar (also added copy of 
2642      !** headerIndex_mpiglobal and bodyIndex_mpiglobal, if they exist)
2643      !if(obs_a%mpi_local)then
2644      !   call obs_abort( &
2645      !         'obs_copy() is not equipped to handle the case, mpi_local=.true.')
2646      !   return
2647      !end if
2648
2649      do column_index=NHDR_REAL_BEG,NHDR_REAL_END
2650         if(obs_a%realHeaders%odc_flavour%columnActive(column_index))  &
2651              obs_b%realHeaders%columns(column_index)%value_r(:) &
2652            = obs_a%realHeaders%columns(column_index)%value_r(:)
2653      enddo
2654      do column_index=NHDR_INT_BEG,NHDR_INT_END
2655         if(obs_a%intHeaders%odc_flavour%columnActive(column_index))  &
2656              obs_b%intHeaders%columns(column_index)%value_i(:) &
2657            = obs_a%intHeaders%columns(column_index)%value_i(:)
2658      enddo
2659      do column_index=NBDY_REAL_BEG,NBDY_REAL_END
2660         if(obs_a%realBodies%odc_flavour%columnActive(column_index))  &
2661              obs_b%realBodies%columns(column_index)%value_r(:) &
2662            = obs_a%realBodies%columns(column_index)%value_r(:)
2663      enddo
2664      do column_index=NBDY_INT_BEG,NBDY_INT_END
2665         if(obs_a%intBodies%odc_flavour%columnActive(column_index))  &
2666              obs_b%intBodies%columns(column_index)%value_i(:) &
2667            = obs_a%intBodies%columns(column_index)%value_i(:)
2668      enddo
2669      obs_b%headerPrimaryKey(:) = obs_a%headerPrimaryKey(:)
2670      obs_b%cstnid(:)     = obs_a%cstnid(:)
2671      obs_b%cfamily(:)    = obs_a%cfamily(:)
2672
2673      obs_b%numHeader     = obs_a%numHeader
2674      obs_b%numHeader_max = obs_a%numHeader_max
2675      obs_b%numBody       = obs_a%numBody
2676      obs_b%numBody_max   = obs_a%numBody_max
2677
2678      if(associated(obs_a%headerIndex_mpiglobal)) then
2679        write(*,*) 'obs_copy: copying headerIndex_mpiglobal'
2680        if(associated(obs_b%headerIndex_mpiglobal)) then
2681          deallocate(obs_b%headerIndex_mpiglobal)
2682        endif
2683        allocate(obs_b%headerIndex_mpiglobal(size(obs_a%headerIndex_mpiglobal)))
2684        obs_b%headerIndex_mpiglobal(:) = obs_a%headerIndex_mpiglobal(:)
2685      endif
2686
2687      if(associated(obs_a%bodyIndex_mpiglobal)) then
2688        write(*,*) 'obs_copy: copying bodyIndex_mpiglobal'
2689        if(associated(obs_b%bodyIndex_mpiglobal)) then
2690          deallocate(obs_b%bodyIndex_mpiglobal)
2691        endif
2692        allocate(obs_b%bodyIndex_mpiglobal(size(obs_a%bodyIndex_mpiglobal)))
2693        obs_b%bodyIndex_mpiglobal(:) = obs_a%bodyIndex_mpiglobal(:)
2694      endif
2695
2696      obs_b%mpi_local     = obs_a%mpi_local
2697   end subroutine obs_copy
2698
2699
2700   subroutine obs_deallocate(obsdat)
2701      !
2702      !:Purpose:
2703      !      De-allocate arrays.  This is a private method.
2704      !
2705      implicit none
2706
2707      ! Arguments:
2708      type(struct_obs), intent(inout) :: obsdat
2709
2710      ! Locals:
2711      integer :: ierr
2712      integer :: column_index
2713
2714      if(.not.obsdat%allocated) then
2715         ! The object content has already been de-allocated.  Don't repeat it.
2716         return
2717      endif
2718
2719      obsdat%allocated=.false.
2720      HEADER:if(obsdat%numHeader_max > 0) then
2721         if (associated(obsdat%headerPrimaryKey)) then
2722            deallocate(obsdat%headerPrimaryKey,STAT=ierr)
2723            nullify(obsdat%headerPrimaryKey)
2724            if(ierr /= 0)write(*,*) 'Problem detected with headerPrimaryKey. IERR =',ierr
2725         end if
2726          
2727         if (associated(obsdat%cfamily)) then
2728            deallocate(obsdat%cfamily,STAT=ierr)
2729            nullify(obsdat%cfamily)
2730            if(ierr /= 0)write(*,*) 'Problem detected with CFAMILY. IERR =',ierr
2731         end if
2732
2733         if (associated(obsdat%cstnid))then
2734            deallocate(obsdat%cstnid,STAT=ierr)
2735            nullify(obsdat%cstnid)
2736            if(ierr /= 0)write(*,*) 'Problem detected with CSTNID. IERR =',ierr
2737         end if
2738
2739         if (associated(obsdat%realHeaders%columns))then
2740            do column_index=NHDR_REAL_BEG,NHDR_REAL_END
2741               if(obsdat%realHeaders%odc_flavour%columnActive(column_index)) &
2742                  call odc_deallocate(obsdat%realHeaders%columns(column_index))
2743            enddo
2744         end if
2745
2746         if (associated(obsdat%intHeaders%columns))then
2747            do column_index=NHDR_INT_BEG,NHDR_INT_END
2748               if(obsdat%intHeaders%odc_flavour%columnActive(column_index)) &
2749                  call odc_deallocate(obsdat%intHeaders%columns(column_index))
2750            enddo
2751         end if
2752
2753         deallocate(obsdat%scratchRealHeader)
2754         nullify   (obsdat%scratchRealHeader)
2755
2756         deallocate(obsdat%scratchIntHeader)
2757         nullify   (obsdat%scratchIntHeader)
2758      endif HEADER
2759
2760      BODY:if(obsdat%numBody_max > 0) then
2761         if (associated(obsdat%bodyPrimaryKey)) then
2762            deallocate(obsdat%bodyPrimaryKey,STAT=ierr)
2763            nullify(obsdat%bodyPrimaryKey)
2764            if(ierr /= 0)write(*,*) 'Problem detected with bodyPrimaryKey. IERR =',ierr
2765         end if
2766
2767         if (associated(obsdat%realBodies%columns))then
2768            do column_index=NBDY_REAL_BEG,NBDY_REAL_END
2769               if(obsdat%realBodies%odc_flavour%columnActive(column_index)) &
2770                  call odc_deallocate(obsdat%realBodies%columns(column_index))
2771            enddo
2772         end if
2773
2774         if (associated(obsdat%intBodies%columns))then
2775            do column_index=NBDY_INT_BEG,NBDY_INT_END
2776               if(obsdat%intBodies%odc_flavour%columnActive(column_index)) &
2777                  call odc_deallocate(obsdat%intBodies%columns(column_index))
2778            enddo
2779         end if
2780
2781         deallocate(obsdat%scratchRealBody)
2782         nullify   (obsdat%scratchRealBody)
2783
2784         deallocate(obsdat%scratchIntBody)
2785         nullify   (obsdat%scratchIntBody)
2786      endif BODY
2787
2788      obsdat%numHeader_max=0
2789      obsdat%numBody_max=0
2790
2791      call ild_finalize(obsdat%header_index_list_depot)
2792      call ild_finalize(obsdat%body_index_list_depot)
2793
2794   end subroutine obs_deallocate
2795
2796
2797   function obs_elem_c(obsdat,name,row_index) result(value)
2798      !
2799      !:Purpose: Get a character-string-valued observation-data element.
2800      !      To control access to the observation object.  Returns the
2801      !      (character) value of the ObsData element with the indicated name
2802      !      and row_index.
2803      !
2804      implicit none
2805
2806      ! Arguments:
2807      type(struct_obs), intent(in)  :: obsdat
2808      character(len=*), intent(in)  :: name
2809      integer         , intent(in)  :: row_index
2810      ! Result:
2811      character(len=12) :: value
2812
2813      select case (trim(name))
2814      case ('STID'); value=obsdat%cstnid(row_index)
2815
2816      case default
2817         call obs_abort('ERROR:  ' // name(1:4) // &
2818                        ' is not a character(len=*) observation.');return
2819      end select
2820   end function obs_elem_c
2821
2822
2823   subroutine obs_enkf_prntbdy(obsdat,kstn,kulout)
2824      !
2825      !:Purpose:  print all data records associated with an observation
2826      !
2827      !:Arguments:
2828      !     :kstn: no. of station 
2829      !     :kulout: unit used for printing
2830      !
2831      implicit none
2832
2833      ! Arguments:
2834      type(struct_obs), intent(in) :: obsdat
2835      integer,          intent(in) :: kstn
2836      integer,          intent(in) :: kulout
2837
2838      ! Locals:
2839      integer :: ipnt, idata, idata2, jdata, var3d
2840
2841      ! general information
2842
2843      ipnt  = obs_headElem_i(obsdat, OBS_RLN, kstn)
2844      idata = obs_headElem_i(obsdat, OBS_NLV, kstn)
2845
2846      if(idata == 1) then
2847         write(kulout,fmt=9101)idata,kstn
2848      else
2849         write(kulout,fmt=9100)idata,kstn
2850      end if
28519100  format(2x,'there are ',i6,1x,'data in record no.',1x,i6)
28529101  format(2x,'there is ', i6,1x,'data in record no.',1x,i6)
2853
2854      ! print all data records
2855
2856      write(kulout,'(a,a,a,a)') '   no.   var.  press. ass observ. ', &
2857         '  o minus p  o minus p6  o minus a   o min a0     obserr. root(hpht) ', &
2858         'root(haht) innov std dev obs std dev   acc ', &
2859         'zhad  vco'
2860      do jdata = ipnt, ipnt + idata - 1
2861         idata2 = jdata -ipnt + 1
2862         if (btest(obsdat%intBodies%columns(OBS_FLG)%value_i(jdata),12)) then 
2863            var3d=1
2864         else
2865            var3d=0
2866         endif
2867
2868         write(kulout,fmt=9201) idata2,obsdat%intBodies%columns(OBS_VNM)%value_i(jdata), &
2869            obsdat%realBodies%columns(OBS_PPP )%value_r(jdata), &
2870            obsdat%intBodies%columns(OBS_ASS )%value_i(jdata), &
2871            obsdat%realBodies%columns(OBS_VAR )%value_r(jdata), &
2872            obsdat%realBodies%columns(OBS_OMP )%value_r(jdata), &
2873            obsdat%realBodies%columns(OBS_OMP6)%value_r(jdata), &
2874            obsdat%realBodies%columns(OBS_OMA )%value_r(jdata), &
2875            obsdat%realBodies%columns(OBS_OMA0)%value_r(jdata), &
2876            obsdat%realBodies%columns(OBS_OER )%value_r(jdata), &
2877            obsdat%realBodies%columns(OBS_HPHT)%value_r(jdata), &
2878            obsdat%realBodies%columns(OBS_HAHT)%value_r(jdata), &
2879            obsdat%realBodies%columns(OBS_SIGI)%value_r(jdata), &
2880            obsdat%realBodies%columns(OBS_SIGO)%value_r(jdata), &
2881            var3d, &
2882            obsdat%realBodies%columns(OBS_ZHA )%value_r(jdata), &
2883            obsdat%intBodies%columns(OBS_VCO )%value_i(jdata)
2884      enddo
2885
28869201  format(1x,i6,1x,i6,1x,f7.0,1x,i3,10(1x,f10.3),1x,i2, &
2887           1x,f10.3,1x,i2)
2888
2889      return
2890
2891   end subroutine obs_enkf_prntbdy
2892
2893
2894   subroutine obs_enkf_prnthdr(obsdata,kobs,kulout)
2895      !
2896      !:Purpose: printing of the header of an observation record
2897      !
2898      !:Arguments:
2899      !           :kobs: no. of observation
2900      !           :kulout: unit used for optional printing
2901      !
2902      implicit none
2903
2904      ! Arguments:
2905      type(struct_obs), intent(in) :: obsdata
2906      integer,          intent(in) :: kobs
2907      integer,          intent(in) :: kulout
2908
2909      ! Locals:
2910      integer :: obsRLN, obsONM, obsDAT, obsETM, obsINS, obsIDF, obsITY
2911      integer :: obsNLV, obsPAS, obsREG, obsIP
2912      real(pre_obsReal) ::obsLAT, obsLON, obsALT, obsBX, obsBY, obsBZ, obsAZA
2913
2914      ! general information
2915
2916      write(kulout,fmt=9100)kobs,obsdata%cstnid(KOBS)
29179100  format(//,2x,'-- observation record no.' &
2918         ,1x,i6,3x,' station id:',A12)
2919
2920      ! print header's content
2921
29229202  format(2x,'position within realBodies:',i6)
2923      obsRLN = obs_headElem_i(obsdata, OBS_RLN, kobs)
2924      obsONM = obs_headElem_i(obsdata, OBS_ONM, kobs)
2925      obsDAT = obs_headElem_i(obsdata, OBS_DAT, kobs)
2926      obsETM = obs_headElem_i(obsdata, OBS_ETM, kobs)
2927      obsINS = obs_headElem_i(obsdata, OBS_INS, kobs)
2928      obsIDF = obs_headElem_i(obsdata, OBS_IDF, kobs)
2929      obsITY = obs_headElem_i(obsdata, OBS_ITY, kobs)
2930      obsLAT = obs_headElem_r(obsdata, OBS_LAT, kobs)
2931      obsLON = obs_headElem_r(obsdata, OBS_LON, kobs)
2932      obsALT = obs_headElem_r(obsdata, OBS_ALT, kobs)
2933      obsBX  = obs_headElem_r(obsdata, OBS_BX , kobs)
2934      obsBY  = obs_headElem_r(obsdata, OBS_BY , kobs)
2935      obsBZ  = obs_headElem_r(obsdata, OBS_BZ , kobs)
2936      obsAZA = obs_headElem_r(obsdata, OBS_AZA, kobs)
2937      write(kulout,fmt=9200) obsRLN, obsONM, obsDAT, obsETM, &
2938           obsINS, obsIDF, obsITY, obsLAT, obsLON, obsALT, &
2939           obsBX , obsBY , obsBZ , obsAZA
2940
2941      obsNLV = obs_headElem_i(obsdata, OBS_NLV, kobs)
2942      obsPAS = obs_headElem_i(obsdata, OBS_PAS, kobs)
2943      obsREG = obs_headElem_i(obsdata, OBS_REG, kobs)
2944      obsIP  = obs_headElem_i(obsdata, OBS_IP , kobs)
2945      write(kulout,fmt=9201) obsNLV, obsPAS, obsREG, obsIP
2946         
2947
29489200  format(2x,'position within realBodies:',i8,1x,'stn. number:',i6,1x,/, &
2949         '  date: ',i10,1x,' time: ',i8,/, &
2950         '  model box:',i12,1x,'instrument: ',i6,1x, &
2951         'obs. type:',i8,1x,/, &
2952         '  (lat,lon):',f12.6,1x,f12.6,1x, &
2953         'stations altitude:',f12.6,1x,/,2x, &
2954         'block location: ',3(f12.6,1x),/,2x, &
2955         'azimuth angle:',f12.6)
29569201  format('  number of data:',i6,1x, &
2957         ' pass: ',i6,' region: ',i6,/,2x,'processor: ',i6)
2958
2959      return
2960   end subroutine obs_enkf_prnthdr
2961
2962
2963   subroutine obs_expandToMpiGlobal(obsdat)
2964      !
2965      !:Purpose: restore Global array realBodies and intBodies.
2966      !      To reconstitute the mpi-global observation object by gathering the
2967      !      necessary data from all processors (to all processors).
2968      !
2969      !:Note: for the character data cstnid(:), this is converted to integers
2970      !       with IACHAR and back to characters with ACHAR, to facilitate this
2971      !       gather through rpn_comm_allreduce
2972      !
2973      implicit none
2974
2975      ! Arguments:
2976      type(struct_obs), intent(inout) :: obsdat
2977
2978      ! Locals:
2979      integer, allocatable :: headerIndex_mpiglobal(:),all_headerIndex_mpiglobal(:,:)
2980      integer, allocatable :: bodyIndex_mpiglobal(:),all_bodyIndex_mpiglobal(:,:)
2981      integer(8), allocatable :: headerPrimaryKey_mpilocal(:), all_headerPrimaryKey_mpilocal(:,:)
2982      integer(8), allocatable :: bodyPrimaryKey_mpilocal(:), all_bodyPrimaryKey_mpilocal(:,:)
2983      integer, allocatable :: intHeaders_mpilocal(:,:),all_intHeaders_mpilocal(:,:,:)
2984      real(pre_obsReal), allocatable :: realHeaders_mpilocal(:,:),all_realHeaders_mpilocal(:,:,:)
2985      integer, allocatable :: intStnid_mpilocal(:,:),all_intStnid_mpilocal(:,:,:)
2986      integer, allocatable :: intFamily_mpilocal(:,:),all_intFamily_mpilocal(:,:,:)
2987      integer, allocatable :: intBodies_mpilocal(:,:),all_intBodies_mpilocal(:,:,:)
2988      integer, allocatable :: all_numHeader_mpilocal(:), all_numBody_mpilocal(:)
2989      real(pre_obsReal), allocatable :: realBodies_mpilocal(:,:),all_realBodies_mpilocal(:,:,:)
2990      integer :: ierr
2991      integer :: get_max_rss
2992      integer :: numHeader_mpilocalmax,numBody_mpilocalmax
2993      integer :: numHeader_mpiGlobal,numBody_mpiGlobal
2994      integer :: numHeader_mpilocal, numBody_mpilocal
2995      integer :: bodyIndex_mpilocal,bodyIndex
2996      integer :: headerIndex_mpilocal,headerIndex
2997      integer :: headerIndexOffset,bodyIndexOffset
2998      integer :: nsize,sourcePE,nprocs_mpi,myid_mpi,procIndex
2999      integer :: charIndex,activeIndex,columnIndex
3000
3001      write(*,*) 'Entering obs_expandToMpiGlobal'
3002      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3003
3004      if(.not. obsdat%mpi_local)then
3005         call obs_abort('OBS_EXPANDTOMPIGLOBAL has been called, but the ' &
3006                        // 'obsSpaceData object is already in mpi-global state')
3007         return
3008      endif
3009
3010      ! determine rank and number of mpi tasks
3011      call rpn_comm_size("GRID",nprocs_mpi,ierr)
3012      call rpn_comm_rank("GRID",myid_mpi,ierr)
3013
3014      ! determine number of rows in mpiglobal arrays
3015      numHeader_mpiGlobal = obs_numHeader_mpiglobal(obsdat)
3016      numBody_mpiGlobal   = obs_numBody_mpiglobal  (obsdat)
3017
3018      ! determine number of rows in mpilocal arrays
3019      numHeader_mpilocal = obs_numHeader(obsdat)
3020      numBody_mpilocal   = obs_numBody(obsdat)
3021
3022      ! construct the header/body mpiglobal indices if they do not exist
3023      if ( .not. associated(obsdat%headerIndex_mpiglobal) ) then
3024         write(*,*) 'obs_expandToMpiGlobal: This object was never mpiglobal. ' // &
3025                    'Just gather the body and header rows in a simple way.'
3026
3027         if(numHeader_mpilocal > 0) then
3028            ! Allocate the list of global header indices
3029            allocate(obsdat%headerIndex_mpiglobal(numHeader_mpilocal))
3030            obsdat%headerIndex_mpiglobal(:)=0
3031         else
3032            nullify(obsdat%headerIndex_mpiglobal)
3033            write(*,*) 'obs_expandToMpiGlobal: This mpi processor has zero headers.'
3034         end if
3035
3036         if(numBody_mpilocal > 0) then
3037            ! Allocate the list of global body indices
3038            allocate(obsdat%bodyIndex_mpiglobal(numBody_mpilocal))
3039            obsdat%bodyIndex_mpiglobal(:)=0
3040         else
3041            nullify(obsdat%bodyIndex_mpiglobal)
3042            write(*,*) 'obs_expandToMpiGlobal: This mpi processor has zero bodies.'
3043         endif
3044
3045         allocate( all_numHeader_mpilocal(nprocs_mpi) )
3046         call rpn_comm_allgather(numHeader_mpilocal,     1, "mpi_integer", &
3047                                 all_numHeader_mpilocal, 1, "mpi_integer", &
3048                                 "GRID",ierr)
3049         allocate( all_numBody_mpilocal(nprocs_mpi) )
3050         call rpn_comm_allgather(numBody_mpilocal,     1, "mpi_integer", &
3051                                 all_numBody_mpilocal, 1, "mpi_integer", &
3052                                 "GRID",ierr)
3053
3054         headerIndexOffset = 0
3055         bodyIndexOffset = 0
3056         do procIndex = 1, myid_mpi
3057            headerIndexOffset = headerIndexOffset + all_numHeader_mpilocal(procIndex)
3058            bodyIndexOffset   = bodyIndexOffset   + all_numBody_mpilocal(procIndex)
3059         end do
3060
3061         do headerIndex = 1, numHeader_mpilocal
3062            obsdat%headerIndex_mpiglobal(headerIndex) = headerIndex + headerIndexOffset
3063         end do
3064
3065         do bodyIndex = 1, numBody_mpilocal
3066            obsdat%bodyIndex_mpiglobal(bodyIndex) = bodyIndex + bodyIndexOffset
3067         end do
3068
3069      end if ! construct mpiglobal indices
3070
3071      ! first set the mpiglobal header index value stored in the body table
3072      do bodyIndex_mpilocal=1,obsdat%numBody
3073         headerIndex_mpilocal=obs_bodyElem_i(obsdat,OBS_HIND,bodyIndex_mpilocal)
3074         call obs_bodySet_i(obsdat,OBS_HIND,bodyIndex_mpilocal, &
3075            obsdat%headerIndex_mpiglobal(headerIndex_mpilocal))
3076      enddo
3077
3078      ! gather the lists of mpiglobal header indices on proc 0 to know where everything goes
3079      call rpn_comm_allreduce(obsdat%numHeader,numHeader_mpilocalmax,1,"mpi_integer","mpi_max","GRID",ierr)
3080      allocate(headerIndex_mpiglobal(numHeader_mpilocalmax))
3081      headerIndex_mpiglobal(:)=0
3082      do headerIndex_mpilocal=1,obsdat%numHeader
3083         headerIndex_mpiglobal(headerIndex_mpilocal)=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
3084      enddo
3085
3086      if(myid_mpi == 0) then
3087         allocate(all_headerIndex_mpiglobal(numHeader_mpilocalmax,0:nprocs_mpi-1))
3088      else
3089         allocate(all_headerIndex_mpiglobal(1,1))
3090      end if
3091
3092      call rpn_comm_gather(headerIndex_mpiglobal    ,numHeader_mpilocalmax,"mpi_integer", &
3093                           all_headerIndex_mpiglobal,numHeader_mpilocalmax,"mpi_integer", &
3094                           0,"GRID",ierr)
3095      deallocate(headerIndex_mpiglobal)
3096      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3097
3098      ! make the header primary key mpiglobal
3099      allocate(headerPrimaryKey_mpilocal(numHeader_mpilocalmax))
3100      headerPrimaryKey_mpilocal(:) = 0
3101      do headerIndex_mpilocal=1,obsdat%numHeader
3102         headerPrimaryKey_mpilocal(headerIndex_mpilocal)=  &
3103            obsdat%headerPrimaryKey(headerIndex_mpilocal)
3104      enddo
3105      if(myid_mpi == 0) then
3106         allocate(all_headerPrimaryKey_mpilocal(numHeader_mpilocalmax,0:nprocs_mpi-1))
3107      else
3108         allocate(all_headerPrimaryKey_mpilocal(1,1))
3109      end if
3110      nsize=size(headerPrimaryKey_mpilocal)
3111      call rpn_comm_gather(headerPrimaryKey_mpilocal    ,nsize,"mpi_integer8", &
3112                           all_headerPrimaryKey_mpilocal,nsize,"mpi_integer8", &
3113                           0,"GRID",ierr)
3114      deallocate(headerPrimaryKey_mpilocal)
3115      
3116      ! make header-level integer data mpiglobal
3117      allocate(intHeaders_mpilocal(odc_numActiveColumn(obsdat%intHeaders),numHeader_mpilocalmax))
3118      intHeaders_mpilocal(:,:)=0
3119      do headerIndex_mpilocal=1,obsdat%numHeader
3120         do activeIndex=1,odc_numActiveColumn(obsdat%intHeaders)
3121            columnIndex=odc_columnIndexFromActiveIndex( &
3122                                     obsdat%intHeaders%odc_flavour, activeIndex)
3123            intHeaders_mpilocal(activeIndex,headerIndex_mpilocal)=  &
3124               obs_headElem_i(obsdat, columnIndex, headerIndex_mpilocal)
3125         enddo
3126      enddo
3127
3128      if(myid_mpi == 0) then
3129         allocate(all_intHeaders_mpilocal(odc_numActiveColumn(obsdat%intHeaders),numHeader_mpilocalmax,0:nprocs_mpi-1))
3130      else
3131         allocate(all_intHeaders_mpilocal(1,1,1))
3132      end if
3133
3134      nsize=size(intHeaders_mpilocal)
3135      call rpn_comm_gather(intHeaders_mpilocal    ,nsize,"mpi_integer", &
3136                           all_intHeaders_mpilocal,nsize,"mpi_integer", &
3137                           0,"GRID",ierr)
3138      deallocate(intHeaders_mpilocal)
3139      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3140
3141      ! make header-level real data mpiglobal
3142      allocate(realHeaders_mpilocal(odc_numActiveColumn(obsdat%realHeaders),numHeader_mpilocalmax))
3143      realHeaders_mpilocal(:,:)=real(0.0d0,pre_obsReal)
3144      do headerIndex_mpilocal=1,obsdat%numHeader
3145         do activeIndex=1,odc_numActiveColumn(obsdat%realHeaders)
3146            columnIndex=odc_columnIndexFromActiveIndex( &
3147                                     obsdat%realHeaders%odc_flavour, activeIndex)
3148            realHeaders_mpilocal(activeIndex,headerIndex_mpilocal)=  &
3149               obs_headElem_r(obsdat, columnIndex, headerIndex_mpilocal)
3150         enddo
3151      enddo
3152      
3153      if(myid_mpi == 0) then
3154         allocate(all_realHeaders_mpilocal(odc_numActiveColumn(obsdat%realHeaders),numHeader_mpilocalmax,0:nprocs_mpi-1))
3155      else
3156         allocate(all_realHeaders_mpilocal(1,1,1))
3157      end if
3158
3159      nsize=size(realHeaders_mpilocal)
3160      call rpn_comm_gather(realHeaders_mpilocal    ,nsize,pre_obsMpiReal, &
3161                           all_realHeaders_mpilocal,nsize,pre_obsMpiReal, &
3162                           0,"GRID",ierr)
3163      deallocate(realHeaders_mpilocal)
3164      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3165
3166      ! make station-id data mpiglobal
3167      allocate(intStnid_mpilocal(len(obsdat%cstnid(1)),numHeader_mpilocalmax))
3168      intStnid_mpilocal(:,:)=0
3169      do headerIndex_mpilocal=1,obsdat%numHeader
3170         do charIndex=1,len(obsdat%cstnid(1))
3171            intStnid_mpilocal(charIndex,headerIndex_mpilocal)=  &
3172               iachar(obsdat%cstnid(headerIndex_mpilocal)(charIndex:charIndex))
3173         enddo
3174      enddo
3175
3176      if(myid_mpi == 0) then
3177         allocate(all_intStnid_mpilocal(len(obsdat%cstnid(1)),numHeader_mpilocalmax,0:nprocs_mpi-1))
3178      else
3179         allocate(all_intStnid_mpilocal(1,1,1))
3180      end if
3181
3182      nsize=size(intStnid_mpilocal)
3183      call rpn_comm_gather(intStnid_mpilocal    ,nsize,"mpi_integer", &
3184                           all_intStnid_mpilocal,nsize,"mpi_integer", &
3185                           0,"GRID",ierr)
3186      deallocate(intStnid_mpilocal)
3187      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3188
3189      ! make obs family data mpiglobal
3190      allocate(intFamily_mpilocal(len(obsdat%cfamily(1)),numHeader_mpilocalmax))
3191      intFamily_mpilocal(:,:)=0
3192      do headerIndex_mpilocal=1,obsdat%numHeader
3193         do charIndex=1,len(obsdat%cfamily(1))
3194            intFamily_mpilocal(charIndex,headerIndex_mpilocal)=  &
3195               iachar(obsdat%cfamily(headerIndex_mpilocal)(charIndex:charIndex))
3196         enddo
3197      enddo
3198
3199      if(myid_mpi == 0) then
3200         allocate(all_intFamily_mpilocal(len(obsdat%cfamily(1)),numHeader_mpilocalmax,0:nprocs_mpi-1))
3201      else
3202         allocate(all_intFamily_mpilocal(1,1,1))
3203      end if
3204
3205      nsize=size(intFamily_mpilocal)
3206      call rpn_comm_gather(intFamily_mpilocal    ,nsize,"mpi_integer", &
3207                           all_intFamily_mpilocal,nsize,"mpi_integer", &
3208                           0,"GRID",ierr)
3209      deallocate(intFamily_mpilocal)
3210      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3211
3212      ! gather the lists of mpiglobal body indices on proc 0 to know where everything goes
3213      call rpn_comm_allreduce(obsdat%numBody,numBody_mpilocalmax,1,"mpi_integer","mpi_max","GRID",ierr)
3214      allocate(bodyIndex_mpiglobal(numBody_mpilocalmax))
3215      bodyIndex_mpiglobal(:)=0
3216      do bodyIndex_mpilocal=1,obsdat%numBody
3217         bodyIndex_mpiglobal(bodyIndex_mpilocal)=obsdat%bodyIndex_mpiglobal(bodyIndex_mpilocal)
3218      enddo
3219
3220      if(myid_mpi == 0) then
3221         allocate(all_bodyIndex_mpiglobal(numBody_mpilocalmax,0:nprocs_mpi-1))
3222      else
3223         allocate(all_bodyIndex_mpiglobal(1,1))
3224      end if
3225
3226      call rpn_comm_gather(bodyIndex_mpiglobal    ,numBody_mpilocalmax,"mpi_integer", &
3227                           all_BodyIndex_mpiglobal,numBody_mpilocalmax,"mpi_integer", &
3228                           0,"GRID",ierr)
3229      deallocate(bodyIndex_mpiglobal)
3230      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3231
3232      ! make body primary key mpiglobal
3233      allocate(bodyPrimaryKey_mpilocal(numBody_mpilocalmax))
3234      bodyPrimaryKey_mpilocal(:)=0
3235      do bodyIndex_mpilocal=1,obsdat%numBody
3236         bodyPrimaryKey_mpilocal(bodyIndex_mpilocal)=  &
3237            obsdat%bodyPrimaryKey(bodyIndex_mpilocal)
3238      enddo
3239      if(myid_mpi == 0) then
3240         allocate(all_bodyPrimaryKey_mpilocal(numBody_mpilocalmax,0:nprocs_mpi-1))
3241      else
3242         allocate(all_bodyPrimaryKey_mpilocal(1,1))
3243      end if
3244      nsize=size(bodyPrimaryKey_mpilocal)
3245      call rpn_comm_gather(bodyPrimaryKey_mpilocal    ,nsize,"mpi_integer8", &
3246                           all_bodyPrimaryKey_mpilocal,nsize,"mpi_integer8", &
3247                           0,"GRID",ierr)
3248      deallocate(bodyPrimaryKey_mpilocal)
3249
3250      ! make body-level integer data mpiglobal
3251      allocate(intBodies_mpilocal(odc_numActiveColumn(obsdat%intBodies),numBody_mpilocalmax))
3252      intBodies_mpilocal(:,:)=0
3253      do bodyIndex_mpilocal=1,obsdat%numBody
3254         do activeIndex=1,odc_numActiveColumn(obsdat%intBodies)
3255            columnIndex=odc_columnIndexFromActiveIndex( &
3256                                     obsdat%intBodies%odc_flavour, activeIndex)
3257            intBodies_mpilocal(activeIndex,bodyIndex_mpilocal)=  &
3258               obs_bodyElem_i(obsdat, columnIndex, bodyIndex_mpilocal)
3259         enddo
3260      enddo
3261
3262      if(myid_mpi == 0) then
3263         allocate(all_intBodies_mpilocal(odc_numActiveColumn(obsdat%intBodies),numBody_mpilocalmax,0:nprocs_mpi-1))
3264      else
3265         allocate(all_intBodies_mpilocal(1,1,1))
3266      end if
3267
3268      nsize=size(intBodies_mpilocal)
3269      call rpn_comm_gather(intBodies_mpilocal    ,nsize,"mpi_integer", &
3270                           all_intBodies_mpilocal,nsize,"mpi_integer", &
3271                           0,"GRID",ierr)
3272      deallocate(intBodies_mpilocal)
3273      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3274
3275      ! make body-level real data mpiglobal
3276      allocate(realBodies_mpilocal(odc_numActiveColumn(obsdat%realBodies),numBody_mpilocalmax))
3277      realBodies_mpilocal(:,:)=real(0.0d0,pre_obsReal)
3278      do bodyIndex_mpilocal=1,obsdat%numBody
3279         do activeIndex=1,odc_numActiveColumn(obsdat%realBodies)
3280            columnIndex=odc_columnIndexFromActiveIndex( &
3281                                     obsdat%realBodies%odc_flavour, activeIndex)
3282            realBodies_mpilocal(activeIndex,bodyIndex_mpilocal)=  &
3283               obs_bodyElem_r(obsdat, columnIndex, bodyIndex_mpilocal)
3284         enddo
3285      enddo
3286
3287      if(myid_mpi == 0) then
3288         allocate(all_realBodies_mpilocal(odc_numActiveColumn(obsdat%realBodies),numBody_mpilocalmax,0:nprocs_mpi-1))
3289      else
3290         allocate(all_realBodies_mpilocal(1,1,1))
3291      end if
3292
3293      nsize=size(realBodies_mpilocal)
3294      call rpn_comm_gather(realBodies_mpilocal    ,nsize,pre_obsMpiReal, &
3295                           all_realBodies_mpilocal,nsize,pre_obsMpiReal, &
3296                           0,"GRID",ierr)
3297      deallocate(realBodies_mpilocal)
3298      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3299
3300      ! destroy object's mpilocal data and allocate mpiglobal data
3301      call obs_deallocate(obsdat)
3302
3303      ! Only processor 0 does any work hereafter
3304      if(myid_mpi == 0) then
3305          call obs_allocate(obsdat,numHeader_mpiGlobal,numBody_mpiGlobal)
3306      else
3307          call obs_allocate(obsdat,0,0)
3308      endif
3309
3310      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3311
3312      if(myid_mpi == 0) then
3313
3314         do sourcePE=0,nprocs_mpi-1
3315            do headerIndex_mpilocal=1,numHeader_mpilocalmax
3316               ! grab the mpiglobal header index
3317               headerIndex=all_headerIndex_mpiglobal(headerIndex_mpilocal,sourcePE)
3318               if(headerIndex > 0) then
3319
3320                  obsdat%headerPrimaryKey(headerIndex)= &
3321                         all_headerPrimaryKey_mpilocal(headerIndex_mpilocal,sourcePE)
3322
3323                  do activeIndex=1,odc_numActiveColumn(obsdat%realHeaders)
3324                     columnIndex=odc_columnIndexFromActiveIndex(obsdat%realHeaders%odc_flavour,activeIndex)
3325                     obsdat%realHeaders%columns(columnIndex)%value_r(headerIndex)= &
3326                            all_realHeaders_mpilocal(activeIndex,headerIndex_mpilocal,sourcePE)
3327                  enddo
3328
3329                  do activeIndex=1,odc_numActiveColumn(obsdat%intHeaders)
3330                     columnIndex=odc_columnIndexFromActiveIndex(obsdat%intHeaders%odc_flavour,activeIndex)
3331                     obsdat%intHeaders%columns(columnIndex)%value_i(headerIndex)= &
3332                            all_intHeaders_mpilocal(activeIndex,headerIndex_mpilocal,sourcePE)
3333                  enddo
3334
3335                  do charIndex=1,len(obsdat%cstnid(1))
3336                     obsdat%cstnid(headerIndex)(charIndex:charIndex) = &
3337                       achar(all_intStnid_mpilocal(charIndex,headerIndex_mpilocal,sourcePE))
3338                  enddo
3339
3340                  do charIndex=1,len(obsdat%cfamily(1))
3341                     obsdat%cfamily(headerIndex)(charIndex:charIndex) = &
3342                        achar(all_intFamily_mpilocal(charIndex,headerIndex_mpilocal,sourcePE))
3343                  enddo
3344
3345               endif
3346            enddo
3347         enddo
3348
3349         do sourcePE=0,nprocs_mpi-1
3350            do bodyIndex_mpilocal=1,numBody_mpilocalmax
3351               bodyIndex=all_bodyIndex_mpiglobal(bodyIndex_mpilocal,sourcePE)
3352               if(bodyIndex > 0) then
3353
3354                  obsdat%bodyPrimaryKey(bodyIndex)=  &
3355                     all_bodyPrimaryKey_mpilocal(bodyIndex_mpilocal,sourcePE)
3356
3357                  do activeIndex=1,odc_numActiveColumn(obsdat%realBodies)
3358                     columnIndex=odc_columnIndexFromActiveIndex(obsdat%realBodies%odc_flavour,activeIndex)
3359                     obsdat%realBodies%columns(columnIndex)%value_r(bodyIndex)= &
3360                        all_realBodies_mpilocal(activeIndex,bodyIndex_mpilocal,sourcePE)
3361                  enddo
3362
3363                  do activeIndex=1,odc_numActiveColumn(obsdat%intBodies)
3364                     columnIndex=odc_columnIndexFromActiveIndex(obsdat%intBodies%odc_flavour,activeIndex)
3365                     obsdat%intBodies%columns(columnIndex)%value_i(bodyIndex)=  &
3366                        all_intBodies_mpilocal(activeIndex,bodyIndex_mpilocal,sourcePE)
3367                  enddo
3368
3369               endif
3370            enddo
3371         enddo
3372 
3373
3374         ! Make RLN point to global data
3375         do headerIndex=1,numHeader_mpiGlobal
3376            if(headerIndex == 1) then
3377               obsdat%intHeaders%columns(OBS_RLN)%value_i(headerIndex) = 1
3378            else
3379               obsdat%intHeaders%columns(OBS_RLN)%value_i(headerIndex) = &
3380                   obsdat%intHeaders%columns(OBS_RLN)%value_i(headerIndex-1) + &
3381                   obsdat%intHeaders%columns(OBS_NLV)%value_i(headerIndex-1)
3382            endif
3383         enddo
3384
3385         obsdat%numBody     =  numBody_mpiGlobal
3386         obsdat%numHeader   =numHeader_mpiGlobal
3387
3388      else
3389
3390         obsdat%numBody     = 0
3391         obsdat%numHeader   = 0
3392
3393      endif ! myid_mpi == 0
3394
3395      ! deallocate the complete temporary arrays
3396      deallocate(all_headerIndex_mpiglobal)
3397      deallocate(all_bodyIndex_mpiglobal)
3398      deallocate(all_headerPrimaryKey_mpilocal)
3399      deallocate(all_bodyPrimaryKey_mpilocal)
3400      deallocate(all_intStnid_mpilocal)
3401      deallocate(all_intFamily_mpilocal)
3402      deallocate(all_intHeaders_mpilocal)
3403      deallocate(all_realHeaders_mpilocal)
3404      deallocate(all_intBodies_mpilocal)
3405      deallocate(all_realBodies_mpilocal)
3406
3407      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
3408      obsdat%mpi_local = .false.
3409      write(*,*) 'Leaving obs_expandToMpiGlobal'
3410      return
3411   end subroutine obs_expandToMpiGlobal
3412
3413
3414   subroutine obs_extractObsRealBodyColumn(realBodyColumn, obsSpaceData, obsColumnIndex)
3415     !:Purpose: Extract contents of a real body column into a vector.
3416     !
3417     implicit none
3418
3419     ! Arguments:
3420     real(pre_obsReal), intent(out) :: realBodyColumn(:)
3421     type(struct_obs),  intent(in)  :: obsSpaceData
3422     integer,           intent(in)  :: obsColumnIndex
3423
3424     ! Locals:
3425     integer :: bodyIndex
3426
3427     do bodyIndex = 1, obs_numBody(obsSpaceData)
3428       realBodyColumn(bodyIndex) = obs_bodyElem_r(obsSpaceData,obsColumnIndex,bodyIndex)
3429     end do
3430
3431   end subroutine obs_extractObsRealBodyColumn
3432
3433
3434   subroutine obs_extractObsRealBodyColumn_r4(realBodyColumn, obsSpaceData, obsColumnIndex)
3435     !:Purpose: Extract contents of a real body column into a vector.
3436     !
3437     implicit none
3438
3439     ! Arguments:
3440     real(4),          intent(out) :: realBodyColumn(:)
3441     type(struct_obs), intent(in)  :: obsSpaceData
3442     integer,          intent(in)  :: obsColumnIndex
3443
3444     ! Locals:
3445     integer :: bodyIndex
3446
3447     do bodyIndex = 1, obs_numBody(obsSpaceData)
3448       realBodyColumn(bodyIndex) = obs_bodyElem_r(obsSpaceData,obsColumnIndex,bodyIndex)
3449     end do
3450
3451   end subroutine obs_extractObsRealBodyColumn_r4
3452
3453
3454   subroutine obs_extractObsIntBodyColumn(intBodyColumn, obsSpaceData, obsColumnIndex)
3455     !:Purpose: Extract contents of an integer body column into a vector.
3456     !
3457     implicit none
3458
3459     ! Arguments:
3460     integer,          intent(out) :: intBodyColumn(:)
3461     type(struct_obs), intent(in)  :: obsSpaceData
3462     integer,          intent(in)  :: obsColumnIndex
3463
3464     ! Locals:
3465     integer :: bodyIndex
3466
3467     do bodyIndex = 1, obs_numBody(obsSpaceData)
3468       intBodyColumn(bodyIndex) = obs_bodyElem_i(obsSpaceData,obsColumnIndex,bodyIndex)
3469     end do
3470
3471   end subroutine obs_extractObsIntBodyColumn
3472
3473
3474   subroutine obs_extractObsRealHeaderColumn(realHeaderColumn, obsSpaceData, obsColumnIndex)
3475     !:Purpose: Extract contents of a real header column into a vector. Note
3476     !           that the output can be either in the form of a vector with
3477     !           length equal to the number of rows in the body OR header table.
3478     !
3479     implicit none
3480
3481     ! Arguments:
3482     real(pre_obsReal), intent(out) :: realHeaderColumn(:)
3483     type(struct_obs),  intent(in)  :: obsSpaceData
3484     integer,           intent(in)  :: obsColumnIndex
3485
3486     ! Locals:
3487     integer :: bodyIndex, headerIndex
3488
3489     if (size(realHeaderColumn) == obs_numBody(obsSpaceData)) then
3490       do bodyIndex = 1, obs_numBody(obsSpaceData)
3491         headerIndex = obs_bodyElem_i(obsSpaceData,OBS_HIND,bodyIndex)
3492         realHeaderColumn(bodyIndex) = obs_headElem_r(obsSpaceData,obsColumnIndex,headerIndex)
3493       end do
3494     else if(size(realHeaderColumn) == obs_numHeader(obsSpaceData)) then
3495       do headerIndex = 1, obs_numHeader(obsSpaceData)
3496         realHeaderColumn(headerIndex) = obs_headElem_r(obsSpaceData,obsColumnIndex,headerIndex)
3497       end do
3498     else
3499       call obs_abort('extractObsRealHeaderColumn: size of output vector invalid')
3500     end if
3501
3502   end subroutine obs_extractObsRealHeaderColumn
3503
3504
3505   subroutine obs_extractObsIntHeaderColumn(intHeaderColumn, obsSpaceData, obsColumnIndex)
3506     !:Purpose: Extract contents of an integer header column into a vector. Note
3507     !           that the output can be either in the form of a vector with
3508     !           length equal to the number of rows in the body OR header table.
3509     !
3510     implicit none
3511
3512     ! Arguments:
3513     integer,          intent(out) :: intHeaderColumn(:)
3514     type(struct_obs), intent(in)  :: obsSpaceData
3515     integer,          intent(in)  :: obsColumnIndex
3516
3517     ! Locals:
3518     integer :: bodyIndex, headerIndex
3519
3520     if (size(intHeaderColumn) == obs_numBody(obsSpaceData)) then
3521       do bodyIndex = 1, obs_numBody(obsSpaceData)
3522         headerIndex = obs_bodyElem_i(obsSpaceData,OBS_HIND,bodyIndex)
3523         intHeaderColumn(bodyIndex) = obs_headElem_i(obsSpaceData,obsColumnIndex,headerIndex)
3524       end do
3525     else if(size(intHeaderColumn) == obs_numHeader(obsSpaceData)) then
3526       do headerIndex = 1, obs_numHeader(obsSpaceData)
3527         intHeaderColumn(headerIndex) = obs_headElem_i(obsSpaceData,obsColumnIndex,headerIndex)
3528       end do
3529     else
3530       call obs_abort('extractObsIntHeaderColumn: size of output vector invalid')
3531     end if
3532
3533   end subroutine obs_extractObsIntHeaderColumn
3534
3535
3536   subroutine obs_finalize(obsdat)
3537      !
3538      !:Purpose: De-allocate memory and clean up the object.
3539      !      De-allocate object arrays, and perform any other clean-up that is
3540      !      necessary before object deletion.
3541      !
3542      implicit none
3543
3544      ! Arguments:
3545      type(struct_obs), intent(inout) :: obsdat
3546
3547      call obs_deallocate(obsdat)
3548   end subroutine obs_finalize
3549
3550
3551   function obs_getBodyIndex_depot(obsdat) result(row_index)
3552      !
3553      !:Purpose:
3554      !      Return the next element from the current body list
3555      !
3556      implicit none
3557
3558      ! Arguments:
3559      type(struct_obs), intent(inout) :: obsdat
3560      ! Result:
3561      integer :: row_index
3562
3563      row_index = ild_get_next_index(obsdat%body_index_list_depot)
3564   end function obs_getBodyIndex_depot
3565
3566
3567   function obs_getBodyIndex_private(private_list) result(row_index)
3568      !
3569      !:Purpose:
3570      !      Return the next element from the supplied private body list
3571      !
3572      implicit none
3573
3574      ! Arguments:
3575      type(struct_index_list), pointer, intent(inout) :: private_list
3576      ! Result:
3577      integer :: row_index
3578
3579      row_index = ild_get_next_index(private_list)
3580   end function obs_getBodyIndex_private
3581
3582
3583   function obs_getFamily(obsdat,headerIndex_opt,bodyIndex_opt)
3584      !
3585      !:Purpose:
3586      !      Return the family for the indicated header, or else for the
3587      !      indicated body.
3588      !
3589      implicit none
3590
3591      ! Arguments:
3592      type(struct_obs),  intent(in) :: obsdat
3593      integer, optional, intent(in) :: headerIndex_opt
3594      integer, optional, intent(in) :: bodyIndex_opt
3595      ! Result:
3596      character(len=2)              :: obs_getFamily
3597
3598      ! Locals:
3599      integer          :: headerIndex
3600
3601      if(present(headerIndex_opt)) then
3602         headerIndex=headerIndex_opt
3603      elseif(present(bodyIndex_opt)) then
3604         headerIndex=obs_bodyElem_i(obsdat,OBS_HIND,bodyIndex_opt)
3605      else
3606         call obs_abort('OBS_GETFAMILY: Header or Body index must be specified!')
3607         return
3608      endif
3609
3610      obs_getFamily=obsdat%cfamily(headerIndex)
3611
3612   end function obs_getFamily
3613
3614
3615   function obs_getNclassAvhrr() 
3616     !
3617     !:Purpose:
3618     !      to get the number of  AVHRR radiance classes
3619     !
3620     implicit none
3621
3622     ! Result:
3623     integer :: obs_getNclassAvhrr
3624
3625     obs_getNclassAvhrr = ( OBS_CF7 - OBS_CF1 + 1 )
3626
3627   end function obs_getNclassAvhrr
3628
3629   function obs_getNchanAvhrr() 
3630     !
3631     !:Purpose:
3632     !      to get the number of AVHRR channels
3633     !
3634     implicit none
3635
3636     ! Result:
3637     integer :: obs_getNchanAvhrr
3638
3639     obs_getNchanAvhrr = ( OBS_M1C6 - OBS_M1C1 + 1 )
3640
3641   end function obs_getNchanAvhrr
3642
3643
3644   function obs_getHeaderIndex(obsdat) result(row_index)
3645      !
3646      !:Purpose:
3647      !      Return the next element from the current header list.
3648      !
3649      implicit none
3650
3651      ! Arguments:
3652      type(struct_obs), intent(inout) :: obsdat
3653      ! Result:
3654      integer :: row_index
3655
3656      row_index = ild_get_next_index(obsdat%header_index_list_depot)
3657   end function obs_getHeaderIndex
3658
3659
3660   function obs_headElem_i(obsdat,column_index,row_index) result(value_i)
3661      !
3662      !:Purpose: Get an integer-valued header observation-data element.
3663      !      To control access to the observation object.  Returns the (integer)
3664      !      value of the row_index'th ObsData element with the indicated column
3665      !      index from the "header".
3666      !
3667      implicit none
3668
3669      ! Arguments:
3670      type(struct_obs), intent(in)  :: obsdat
3671      integer         , intent(in)  :: column_index
3672      integer         , intent(in)  :: row_index
3673      ! Result:
3674      integer                       :: value_i
3675
3676      ! Locals:
3677      real(pre_obsReal) :: value_r         ! not used
3678
3679      call odc_columnElem(obsdat%intHeaders, column_index, row_index, &
3680                          value_i, value_r)
3681   end function obs_headElem_i
3682
3683
3684   function obs_headElem_r(obsdat,column_index,row_index) result(value_r)
3685      !
3686      !:Purpose: Get a real-valued header observation-data element.
3687      !      To control access to the observation object.  Returns the (real)
3688      !      value of the row_index'th ObsData element with the indicated column
3689      !      index from the "header".
3690      !
3691      implicit none
3692
3693      ! Arguments:
3694      type(struct_obs), intent(in)  :: obsdat
3695      integer         , intent(in)  :: column_index
3696      integer         , intent(in)  :: row_index
3697      ! Result:
3698      real(pre_obsReal)             :: value_r
3699
3700      ! Locals:
3701      integer :: value_i                ! unused
3702
3703      call odc_columnElem(obsdat%realHeaders, column_index, row_index, &
3704                          value_i, value_r)
3705   end function obs_headElem_r
3706
3707
3708   function obs_headPrimaryKey(obsdat,row_index) result(primaryKey)
3709      !
3710      !:Purpose: Get the header primary key value.
3711      !
3712      implicit none
3713
3714      ! Arguments:
3715      type(struct_obs), intent(in)  :: obsdat
3716      integer         , intent(in)  :: row_index
3717      ! Result:
3718      integer(8)                    :: primaryKey
3719
3720      primaryKey = obsdat%headerPrimaryKey(row_index)
3721
3722   end function obs_headPrimaryKey
3723
3724
3725   function obs_headerIndex_mpiglobal(obsdat,row_index) result(value)
3726      !
3727      !:Purpose: Get the mpiglobal header row index.
3728      !      To control access to the mpiglobal row_index into the "header".
3729      !
3730      implicit none
3731
3732      ! Arguments:
3733      type(struct_obs), intent(in)  :: obsdat
3734      integer         , intent(in)  :: row_index
3735      ! Result:
3736      integer value
3737
3738      value=obsdat%headerIndex_mpiglobal(row_index)
3739   end function obs_headerIndex_mpiglobal
3740
3741
3742   subroutine obs_headSet_i(obsdat, column_index, row_index, value_i)
3743      !
3744      !:Purpose: set an integer-valued header observation-data element.
3745      !      To control access to the observation object.  Sets the (integer)
3746      !      value of the row_index'th ObsData element with the indicated column
3747      !      index from the "header".
3748      !
3749      implicit none
3750
3751      ! Arguments:
3752      type(struct_obs), intent(inout)  :: obsdat
3753      integer         , intent(in)     :: column_index
3754      integer         , intent(in)     :: row_index
3755      integer         , intent(in)     :: value_i
3756
3757      call odc_columnSet(obsdat%intHeaders, column_index, row_index, &
3758                         value_i, NULL_COLUMN_VALUE_R, &
3759                         obsdat%numHeader, obsdat%numHeader_max)
3760   end subroutine obs_headSet_i
3761
3762
3763   subroutine obs_headSet_r4(obsdat, column_index, row_index, value_r4)
3764      !
3765      !:Purpose: set a real header value in the observation object.
3766      !      To control access to the observation object.  Sets the (real)
3767      !      value of the row_index'th ObsData element with the indicated column
3768      !      index from the "header".
3769      !
3770      implicit none
3771
3772      ! Arguments:
3773      type(struct_obs), intent(inout)  :: obsdat
3774      integer         , intent(in)     :: column_index
3775      integer         , intent(in)     :: row_index
3776      real(4)         , intent(in)     :: value_r4
3777
3778      ! Locals:
3779      real(pre_obsReal)                :: value_r
3780
3781      value_r = value_r4
3782
3783      call odc_columnSet(obsdat%realHeaders, column_index, row_index, &
3784                         NULL_COLUMN_VALUE_I, value_r, &
3785                         obsdat%numHeader, obsdat%numHeader_max)
3786   end subroutine obs_headSet_r4
3787
3788
3789   subroutine obs_headSet_r8(obsdat, column_index, row_index, value_r8)
3790      !
3791      !:Purpose: set a real header value in the observation object.
3792      !      To control access to the observation object.  Sets the (real)
3793      !      value of the row_index'th ObsData element with the indicated column
3794      !      index from the "header".
3795      !
3796      implicit none
3797
3798      ! Arguments:
3799      type(struct_obs), intent(inout)  :: obsdat
3800      integer         , intent(in)     :: column_index
3801      integer         , intent(in)     :: row_index
3802      real(8)         , intent(in)     :: value_r8
3803
3804      ! Locals:
3805      real(pre_obsReal)                :: value_r
3806
3807      value_r = value_r8
3808
3809      call odc_columnSet(obsdat%realHeaders, column_index, row_index, &
3810                         NULL_COLUMN_VALUE_I, value_r, &
3811                         obsdat%numHeader, obsdat%numHeader_max)
3812   end subroutine obs_headSet_r8
3813
3814
3815   subroutine obs_initialize(obsdat, numHeader_max_opt, numBody_max_opt, mpi_local_opt, &
3816                             silent_opt)
3817      !
3818      !:Purpose: Set an observation-data module to a known state.
3819      !      Initialize object variables, and allocate arrays according to the
3820      !      parameters, header_max and body_max.
3821      !
3822      implicit none
3823
3824      ! Arguments:
3825      type(struct_obs),  intent(inout) :: obsdat            ! inout allows detection of 2nd call
3826      integer, optional, intent(in)    :: numHeader_max_opt ! number of header elements allocated
3827      integer, optional, intent(in)    :: numBody_max_opt   ! total no. of body elements allocated
3828      logical, optional, intent(in)    :: mpi_local_opt
3829      logical, optional, intent(in)    :: silent_opt
3830
3831      ! Locals:
3832      logical :: silent
3833      integer :: nulnam,fnom,fclos,ierr
3834      character(len=120) :: message
3835
3836      ! Namelist variables:
3837      integer :: nmxobs  ! maximum number of rows in 'header' table (used for initial allocation)
3838      integer :: ndatamx ! maximum number of rows in 'body' table (used for initial allocation)
3839      namelist /namdimo/nmxobs,ndatamx
3840
3841      if(.not. obs_class_initialized) then
3842         call obs_abort('obs_class_initialize must be called before ' // &
3843                        'obs_initialize')
3844         return
3845      end if
3846
3847      !
3848      ! INITIALIZE ALL OBJECT VARIABLES
3849      !
3850      nullify(obsdat%headerPrimaryKey)
3851      nullify(obsdat%bodyPrimaryKey)
3852      nullify(obsdat%cstnid)
3853      nullify(obsdat%cfamily)
3854
3855      obsdat%numHeader     = 0
3856      obsdat%numHeader_max = 0
3857      obsdat%numBody       = 0
3858      obsdat%numBody_max   = 0
3859
3860      if(present(mpi_local_opt)) then
3861         obsdat%mpi_local  = mpi_local_opt
3862      else
3863         obsdat%mpi_local  = .false.
3864      end if
3865
3866      if(present(silent_opt)) then
3867         silent  = silent_opt
3868      else
3869         silent  = .false.
3870      end if
3871
3872      nullify(obsdat%headerIndex_mpiglobal)
3873      nullify(obsdat%bodyIndex_mpiglobal)
3874
3875      !
3876      ! DETERMINE THE ARRAY DIMENSIONS
3877      !
3878      if(present(numHeader_max_opt)) then
3879         ! numBody_max is necessarily also present
3880         nmxobs = numHeader_max_opt
3881         ndatamx = numBody_max_opt
3882
3883      else
3884         ! Initialize with bad values
3885         nmxobs=0
3886         ndatamx=0
3887
3888         ! Open the file, flnml
3889         nulnam=0
3890         ierr=fnom(nulnam,'./flnml','FTN+SEQ+R/O',0)
3891         if(ierr < 0) then
3892            write(message,*)'Failed to open flnml to obtain nmxobs and ndatamx:'&
3893                            // '  ierr=', ierr
3894            call obs_abort(message); return
3895         end if
3896
3897         ! Read the dimensions from a namelist
3898         read(nulnam,nml=namdimo,iostat=ierr)
3899         if(ierr /= 0) call obs_abort('obs_initialize: Error reading namelist')
3900         write(*,nml=namdimo)
3901         ierr=fclos(nulnam)
3902         ! Verify that the namelist contained values
3903         if(nmxobs <= 0 .or. ndatamx <= 0) then
3904            write(message,*)'From file, flnml, positive values were not ' &
3905                            // 'obtained for nmxobs or ndatamx:  ',nmxobs,ndatamx
3906            call obs_abort(message); return
3907         end if
3908      end if ! present(numHeader_max)
3909
3910      !
3911      ! ALLOCATE
3912      !
3913      call obs_allocate(obsdat, nmxobs, ndatamx, silent)
3914
3915   end subroutine obs_initialize
3916
3917
3918   subroutine obs_mpiDistributeIndices(obsdat)
3919      !
3920      !:Purpose:
3921      !  Compute headerIndex_mpiglobal and bodyIndex_mpiglobal: 
3922      !  this determines how obs are distributed over MPI processes
3923      !  and is needed for converting from mpiglobal to mpilocal and vice versa.
3924      !  The header indices are distributed following the chosen strategy,
3925      !  currently either "round robin" or by latitude bands.
3926      !
3927      !:Note: this subroutine is called before converting from mpiglobal to 
3928      !        mpilocal
3929      !
3930      !:Comments:  In principle this method could have obtained
3931      !           my_mpi_id by use'ing the module, mpi.  However, it queries
3932      !           rpn_comm for itself because the mpi module belongs to the
3933      !           3dvar code, whereas the present module is shared code.
3934      !
3935      implicit none
3936
3937      ! Arguments:
3938      type(struct_obs), intent(inout) :: obsdat
3939
3940      ! Locals:
3941      integer :: headerIndex_mpiglobal,headerIndex_mpilocal
3942      integer ::   bodyIndex_mpiglobal,  bodyIndex_mpilocal
3943      integer :: numHeader_mpiLocal,numBody_mpiLocal,idata,idataend
3944      integer :: my_mpi_id, my_mpi_idx_dummy, my_mpi_idy_dummy
3945
3946      write(*,*) '-------- Start obs_mpiDistributeIndices ---------'
3947
3948      if(obsdat%mpi_local) then
3949         call obs_abort( &
3950                      'obs_mpiDistributeIndices: data already mpi-local, Abort!')
3951         return
3952      end if
3953
3954      call rpn_comm_mype(my_mpi_id, my_mpi_idx_dummy, my_mpi_idy_dummy)
3955
3956      ! Count number of headers and bodies for each processor
3957      numHeader_mpiLocal=0
3958      numBody_mpiLocal=0
3959      do headerIndex_mpiglobal=1,obsdat%numHeader
3960         if ( my_mpi_id == obs_headElem_i( obsdat, OBS_IP, headerIndex_mpiglobal )) then
3961            numHeader_mpiLocal = numHeader_mpiLocal + 1
3962            numBody_mpilocal = numBody_mpilocal &
3963                            +obs_headElem_i( obsdat, OBS_NLV, headerIndex_mpiglobal )
3964         end if
3965      enddo
3966      write(*,*) 'obs_mpidistributeindices: numHeader_mpiLocal,global=',  &
3967                 numHeader_mpiLocal,obsdat%numHeader
3968      write(*,*) 'obs_mpidistributeindices: numBody_mpiLocal,global=',  &
3969                 numBody_mpiLocal,obsdat%numBody
3970
3971      if ( numHeader_mpilocal > 0 ) then
3972         ! Allocate the list of global header indices
3973         allocate(obsdat%headerIndex_mpiglobal(numHeader_mpilocal))
3974         obsdat%headerIndex_mpiglobal(:)=0
3975      else
3976         nullify(obsdat%headerIndex_mpiglobal)
3977         write(*,*) 'This mpi processor has zero headers.'
3978      end if
3979
3980      if ( numBody_mpilocal > 0 ) then
3981         ! Allocate the list of global body indices
3982         allocate(obsdat%bodyIndex_mpiglobal(numBody_mpilocal))
3983         obsdat%bodyIndex_mpiglobal(:)=0
3984      else
3985         nullify(obsdat%bodyIndex_mpiglobal)
3986         write(*,*) 'This mpi processor has zero bodies to treat'
3987      endif
3988
3989      ! determine the list of header indices
3990      headerIndex_mpilocal = 0
3991      do headerIndex_mpiglobal=1,obsdat%numHeader
3992         if ( my_mpi_id == obs_headElem_i( obsdat, OBS_IP, headerIndex_mpiglobal )) then
3993            headerIndex_mpilocal=headerIndex_mpilocal+1
3994            obsdat%headerIndex_mpiglobal(headerIndex_mpilocal) &
3995                                                           =headerIndex_mpiglobal
3996         endif
3997      enddo
3998
3999      ! determine the corresponding list of body indices
4000      bodyIndex_mpilocal=0
4001      do headerIndex_mpilocal=1,numHeader_mpilocal
4002         headerIndex_mpiglobal=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
4003         idata= obs_headElem_i(obsdat, OBS_RLN, headerIndex_mpiglobal)
4004         idataend = obs_headElem_i(obsdat, OBS_NLV, headerIndex_mpiglobal) &
4005                  + idata -1 
4006         do bodyIndex_mpiglobal=idata,idataend 
4007            bodyIndex_mpilocal=bodyIndex_mpilocal+1 
4008            obsdat%bodyIndex_mpiglobal(bodyIndex_mpilocal) = bodyIndex_mpiglobal
4009         enddo
4010      enddo
4011
4012      write(*,*) '-------- END OF obs_mpiDistributeIndices ---------'
4013      write(*,*) ' '
4014   end subroutine obs_mpiDistributeIndices
4015
4016
4017   logical function obs_mpiLocal(obsdat)
4018      !
4019      !:Purpose: returns true if the object contains only data that are
4020      !          needed by the current mpi PE; false if it contains all
4021      !          data.
4022      !      To provide the state of the internal variable, mpiLocal.  This
4023      !      method exists primarily to facilitate unit tests on this module.
4024      !
4025      implicit none
4026
4027      ! Arguments:
4028      type(struct_obs) , intent(in)  :: obsdat
4029
4030      obs_mpiLocal=obsdat%mpi_local
4031   end function obs_mpiLocal
4032
4033
4034   integer function obs_numBody(obsdat)
4035      !
4036      !:Purpose: returns the number of mpi-local bodies recorded.
4037      !      To provide the number of bodies that are currently recorded in the
4038      !      mpi-local observation-data object.
4039      !
4040      implicit none
4041
4042      ! Arguments:
4043      type(struct_obs) , intent(in)  :: obsdat
4044
4045      obs_numBody=obsdat%numBody
4046
4047   end function obs_numBody
4048
4049
4050   integer function obs_numBody_max(obsdat)
4051      !
4052      !:Purpose: returns the dimensioned mpi-local number of bodies.
4053      !      To provide the dimension for the number of bodies in the mpi-local
4054      !      observation-data object.
4055      !
4056      implicit none
4057
4058      ! Arguments:
4059      type(struct_obs) , intent(in)  :: obsdat
4060
4061      obs_numBody_max=obsdat%numBody_max
4062
4063   end function obs_numBody_max
4064
4065
4066   integer function obs_numBody_mpiglobal(obsdat)
4067      !
4068      !:Purpose: returns the number of bodies recorded in the
4069      !           entire mpi-global obs object.
4070      !      To provide the number of bodies that are currently recorded in the
4071      !      entire mpi-global observation-data object.
4072      !
4073      implicit none
4074
4075      ! Arguments:
4076      type(struct_obs), intent(in)  :: obsdat
4077
4078      ! Locals:
4079      integer :: numBody_mpiGlobal, sizedata, ierr
4080
4081      if(obsdat%mpi_local)then
4082         sizedata=1
4083         call rpn_comm_allreduce(obsdat%numBody,numBody_mpiGlobal,sizedata, &
4084                                 "mpi_integer","mpi_sum","GRID",ierr)
4085         obs_numBody_mpiglobal = numBody_mpiGlobal
4086      else
4087         obs_numBody_mpiglobal = obsdat%numBody
4088      end if
4089
4090   end function obs_numBody_mpiglobal
4091
4092
4093   integer function obs_numHeader(obsdat)
4094      !
4095      !:Purpose: returns the number of mpi-local headers recorded.
4096      !      To provide the number of headers that are currently recorded in the
4097      !      observation-data object.
4098      !
4099      implicit none
4100
4101      ! Arguments:
4102      type(struct_obs) , intent(in)  :: obsdat
4103
4104      obs_numHeader=obsdat%numHeader
4105   end function obs_numHeader
4106
4107
4108   integer function obs_numHeader_max(obsdat)
4109      !
4110      !:Purpose: returns the dimensioned mpi-local number of headers.
4111      !      To provide the dimension for the number of headers in the mpi-local
4112      !      observation-data object.
4113      !
4114      implicit none
4115
4116      ! Arguments:
4117      type(struct_obs) , intent(in)  :: obsdat
4118
4119      obs_numHeader_max=obsdat%numHeader_max
4120   end function obs_numHeader_max
4121
4122
4123   integer function obs_numHeader_mpiglobal(obsdat)
4124      !
4125      !:Purpose: returns the number of headers recorded in
4126      !           the entire mpi-global obs object.
4127      !      To provide the number of headers that are currently recorded in the
4128      !      entire mpi-global observation-data object.
4129      !
4130      implicit none
4131
4132      ! Arguments:
4133      type(struct_obs) , intent(in)  :: obsdat
4134
4135      ! Locals:
4136      integer :: numHeader_mpiGlobal, sizedata, ierr
4137
4138      if(obsdat%mpi_local)then
4139         sizedata=1
4140         call rpn_comm_allreduce(obsdat%numHeader,numHeader_mpiGlobal,sizedata, &
4141                                 "mpi_integer","mpi_sum","GRID",ierr)
4142         obs_numHeader_mpiglobal = numHeader_mpiGlobal
4143      else
4144         obs_numHeader_mpiglobal = obsdat%numHeader
4145      end if
4146   end function obs_numHeader_mpiglobal
4147
4148
4149   subroutine obs_print( obsdat, nobsout )
4150      !
4151      !:Purpose: print the contents of the obsdat to an ASCII file
4152      !
4153      !:Arguments:
4154      !           :obsdat:  obsSpaceData object
4155      !           :nobsout: unit used for printing
4156      !
4157      implicit none
4158
4159      ! Arguments:
4160      type(struct_obs), intent(in) :: obsdat
4161      integer,          intent(in) :: nobsout
4162
4163      ! Locals:
4164      integer :: jo
4165
4166      do jo=1,obsdat%numHeader
4167         call obs_enkf_prnthdr(obsdat,jo,nobsout)
4168         call obs_enkf_prntbdy(obsdat,jo,nobsout)
4169      enddo
4170
4171      return
4172   end subroutine obs_print
4173
4174
4175   subroutine obs_prntbdy( obsdat , index_header, unitout_opt )
4176      !
4177      !:Purpose: Print all data records associated with an observation
4178      !
4179      !:Arguments:
4180      !           :obsdat:  obsSpaceData object
4181      !           :index_header: index of the group of observations to be printed
4182      !           :unitout: unit number on which to print
4183      !
4184      implicit none
4185
4186      ! Arguments:
4187      type(struct_obs),  intent(in) :: obsdat
4188      integer         ,  intent(in) :: index_header
4189      integer, optional, intent(in) :: unitout_opt ! variable output unit facilitates unit testing
4190
4191      ! Locals:
4192      integer :: unitout_
4193      integer :: ipnt, idata, idata2, jdata, ivco
4194      character(len=13) :: ccordtyp(4)
4195      integer :: obsVNM, obsFLG, obsASS
4196      real(pre_obsReal) :: obsPPP, obsVAR, obsOMP, obsOMA, obsOER, obsHPHT, obsOMPE
4197
4198      if ( present( unitout_opt ) ) then
4199        unitout_ = unitout_opt
4200      else
4201        unitout_ = 6
4202      end if
4203
4204      ccordtyp(1) = 'HEIGHT      :'
4205      ccordtyp(2) = 'PRESSURE    :'
4206      ccordtyp(3) = 'CHANNEL NUM :'
4207      ccordtyp(4) = 'VCO UNDEFINED'
4208      !
4209      ! 1. General information
4210      !
4211      ipnt  = obs_headElem_i(obsdat,OBS_RLN,index_header)
4212      idata = obs_headElem_i(obsdat,OBS_NLV,index_header)
4213
4214      if ( idata == 1) then
4215        write ( unitout_, fmt=9101 ) idata, index_header, NBDY_INT_SIZE+NBDY_REAL_SIZE
4216      else
4217        write ( unitout_, fmt=9100 ) idata, index_header, NBDY_INT_SIZE+NBDY_REAL_SIZE
4218      end if
4219
42209100  format(4x,'THERE ARE ', &
4221         i3,1x,'DATA IN OBSERVATION RECORD NO.' &
4222         ,1x,i6,4x,'DATA RECORD''S LENGTH:',i6)
42239101  format(4x,'THERE IS ', &
4224         i3,1x,'DATUM IN OBSERVATION RECORD NO.' &
4225         ,1x,i6,4x,'DATA RECORD''S LENGTH:',i6)
4226      !
4227      ! 2. Print all data records
4228      !
4229      do jdata = ipnt, ipnt + idata - 1
4230        idata2 = jdata -ipnt + 1
4231        if ( obs_bodyElem_i( obsdat, OBS_ASS, jdata ) >= 0) then
4232          ivco = obs_bodyElem_i( obsdat, OBS_VCO, jdata )
4233          if ( ivco < 1 .or. ivco > 3 ) ivco = 4
4234          obsVNM = obs_bodyElem_i( obsdat, OBS_VNM , jdata )
4235          obsPPP = obs_bodyElem_r( obsdat, OBS_PPP , jdata )
4236          obsVAR = obs_bodyElem_r( obsdat, OBS_VAR , jdata )
4237          obsOMP = obs_bodyElem_r( obsdat, OBS_OMP , jdata )
4238          obsOMA = obs_bodyElem_r( obsdat, OBS_OMA , jdata )
4239          obsOER = obs_bodyElem_r( obsdat, OBS_OER , jdata )
4240          obsHPHT= obs_bodyElem_r( obsdat, OBS_HPHT, jdata )
4241          obsOMPE= obs_bodyElem_r( obsdat, OBS_OMPE, jdata )
4242          obsFLG = obs_bodyElem_i( obsdat, OBS_FLG , jdata )
4243          obsASS = obs_bodyElem_i( obsdat, OBS_ASS , jdata )
4244          write ( unitout_, fmt=9201 ) idata2, &
4245               obsVNM, ccordtyp(ivco), &
4246               obsPPP,obsVAR,obsOMP,obsOMA, &
4247               obsOER,obsHPHT,obsOMPE,obsFLG,obsASS
4248        end if
4249      end do
4250
42519201  format(4x,'DATA NO.',i6,/,10x &
4252         ,'VARIABLE NO.:',i6,4x,a13,g13.6,4x &
4253         ,/,10x &
4254         ,'OBSERVED VALUE:',g23.16,5x,'OBSERVED - BACKGROUND VALUES:' &
4255         ,g23.16,4x &
4256         ,/,10x &
4257         ,'OBSERVED - ANALYZED VALUES:',g13.6,4x &
4258         ,/,10x &
4259         ,'ERROR STANDARD DEVIATIONS FOR' &
4260         ,/,20x &
4261         ,'OBSERVATION:',g13.6,4x &
4262         ,/,20x &
4263         ,'FIRST-GUESS:',g13.6,4x &
4264         ,/,20x &
4265         ,'OBS minus F-G:',g13.6,4x &
4266         ,/,10x &
4267         ,'BURP FLAGS:',i6,4x,'OBS. ASSIMILATED (1-->YES;0-->NO):',i3)
4268
4269      return
4270
4271   end subroutine obs_prntbdy
4272
4273
4274   subroutine obs_prnthdr( obsdata, index_hd, unitout_opt )
4275      !
4276      !:Purpose: Printing of the header of an observation record
4277      !:Arguments:
4278      !           :obsdata:  obsSpaceData object
4279      !           :index_hd: index of the header to be printed
4280      !           :unitout_opt: unit number on which to print
4281      !
4282      implicit none
4283
4284      ! Arguments:
4285      type(struct_obs),  intent(in) :: obsdata
4286      integer         ,  intent(in) :: index_hd
4287      integer, optional, intent(in) :: unitout_opt ! variable output unit facilitates unit testing
4288
4289      ! Locals:
4290      integer :: unitout_
4291      integer :: obsRLN, obsONM, obsINS, obsIDF, obsITY
4292      integer :: obsDAT, obsETM, obsNLV, obsST1, obsSTYP, obsTTYP
4293      real(pre_obsReal) :: obsLON, obsLAT, obsALT
4294      character(len=12) :: stnid
4295
4296      if ( present( unitout_opt ) ) then
4297         unitout_ = unitout_opt
4298      else
4299         unitout_ = 6
4300      end if
4301
4302      !
4303      ! 1. General information
4304      !
4305      write(unitout_,fmt=9100)index_hd, NHDR_INT_SIZE + NHDR_REAL_SIZE
43069100  format(//,10x,'-- OBSERVATION RECORD NO.' &
4307         ,1x,i6,3x,'HEADER''S LENGTH:',i6)
4308      !
4309      ! 2. PRINT HEADER'S CONTENT
4310      !
4311      obsRLN = obs_headElem_i(obsdata,OBS_RLN,index_hd)
4312      obsONM = obs_headElem_i(obsdata,OBS_ONM,index_hd)
4313      obsINS = obs_headElem_i(obsdata,OBS_INS,index_hd)
4314      obsIDF = obs_headElem_i(obsdata,OBS_IDF,index_hd)
4315      obsITY = obs_headElem_i(obsdata,OBS_ITY,index_hd)
4316      obsLAT = obs_headElem_r(obsdata,OBS_LAT,index_hd)
4317      obsLON = obs_headElem_r(obsdata,OBS_LON,index_hd)
4318      obsDAT = obs_headElem_i(obsdata,OBS_DAT,index_hd)
4319      obsETM = obs_headElem_i(obsdata,OBS_ETM,index_hd)
4320      obsALT = obs_headElem_r(obsdata,OBS_ALT,index_hd)
4321      obsNLV = obs_headElem_i(obsdata,OBS_NLV,index_hd)
4322      obsSTYP= obs_headElem_i(obsdata,OBS_STYP,index_hd)
4323      obsTTYP= obs_headElem_i(obsdata,OBS_TTYP,index_hd)
4324      obsST1 = obs_headElem_i(obsdata,OBS_ST1,index_hd)
4325      stnid  = obs_elem_c(obsdata,'STID',index_hd)
4326      write(unitout_,fmt=9200) obsRLN, obsONM, obsINS, obsIDF, obsITY, &
4327           obsLAT, obsLON, obsDAT, obsETM, stnid, obsALT, obsNLV, &
4328           obsSTYP, obsTTYP, obsST1
4329
43309200  format(6x,'Position within realBodies:',i6,1x,'OBS. NUMBER:',i6,1x &
4331         ,'INSTR. ID:',i6,1x,'OBS. TYPE:',i6,1x &
4332         ,'INSTR./RETR. TYPE:',i6,1x &
4333         ,/,6x &
4334         ,'OBSERVATION LOCATION. (LAT,LON):',2(f10.4,1x) &
4335         ,'DATE:',i12,1x,'EXACT TIME: ',i6,1x &
4336         ,/,6x &
4337         ,'STATION ID:',a9,1x &
4338         ,'STATION''S ALTITUDE:',g13.6,1x &
4339         ,'NUMBER OF DATA:',i6,1x &
4340         ,/,6x &
4341         ,'SURFACE TYPE:',i4,1x &
4342         ,'TERRAIN TYPE:',i4,1x &
4343         ,'REPORT STATUS 2:',i10,1x &
4344         ,/,6x &
4345         )
4346
4347      return
4348   end subroutine obs_prnthdr
4349
4350
4351   subroutine obs_reduceToMpiLocal(obsdat)
4352      !
4353      !:Purpose: re-construct observation data object by
4354      !          giving local Obs TAG. 
4355      !      To retain in the observation object only those data that are
4356      !      pertinent to the present mpi processor, i.e. convert from mpiglobal
4357      !      to mpilocal.
4358      !
4359      implicit none
4360
4361      ! Arguments:
4362      type(struct_obs), intent(inout) :: obsdat
4363
4364      ! Locals:
4365      integer(8),        allocatable,dimension(:)   :: headerPrimaryKey_tmp
4366      integer(8),        allocatable,dimension(:)   :: bodyPrimaryKey_tmp
4367      character(len=12), allocatable,dimension(:)   :: cstnid_tmp
4368      character(len=2),  allocatable,dimension(:)   :: cfamily_tmp
4369      real(pre_obsReal), allocatable,dimension(:,:) :: realHeaders_tmp
4370      real(pre_obsReal), allocatable,dimension(:,:) :: realBodies_tmp
4371      integer,allocatable,dimension(:,:) :: intHeaders_tmp,intBodies_tmp
4372      integer :: numHeader_mpilocal,numHeader_mpiglobal
4373      integer ::   numBody_mpilocal,  numBody_mpiglobal
4374      integer :: bodyIndex_mpilocal,bodyIndex_mpiglobal
4375      integer :: headerIndex_mpilocal,headerIndex_mpiglobal
4376      integer :: idataend,idata,active_index
4377      integer :: column_index
4378
4379      !!---------------------------------------------------------------
4380      WRITE(*,*) '============= Enter obs_reduceToMpiLocal =============='
4381
4382      if(obsdat%mpi_local)then
4383         call obs_abort('OBS_REDUCETOMPILOCAL() has been called, but the ' &
4384                        // 'obsSpaceData object is already in mpi-local state')
4385         return
4386      end if
4387
4388      if(obsdat%numHeader_max == 0)then
4389         call obs_abort('OBS_REDUCETOMPILOCAL() has been called when there are '&
4390                        // 'no data.  Obs_reduceToMpiLocal cannot be called ' &
4391                        // 'after a call to obs_expandToMpiGlobal.')
4392         return
4393      end if
4394
4395      ! compute the mpilocal lists of indices into the mpiglobal data
4396      call obs_mpiDistributeIndices(obsdat)
4397
4398      ! calculate the size of the local obs data  
4399      if(associated(obsdat%headerIndex_mpiglobal)) then
4400         numHeader_mpilocal=size(obsdat%headerIndex_mpiglobal) 
4401      else
4402         numHeader_mpilocal=0
4403      endif
4404      numBody_mpiLocal=0
4405      do headerIndex_mpilocal=1,numHeader_mpilocal
4406         headerIndex_mpiglobal=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
4407         idata=obs_headElem_i(obsdat, OBS_NLV, headerIndex_mpiglobal)
4408         numBody_mpiLocal = numBody_mpiLocal + idata
4409      enddo
4410
4411      numHeader_mpiGlobal = obs_numHeader(obsdat)
4412      numBody_mpiGlobal   = obs_numBody(obsdat)
4413
4414      ! allocate temporary arrays to hold mpilocal data
4415      if(numHeader_mpiLocal > 0) then
4416         allocate(headerPrimaryKey_tmp(numHeader_mpiLocal)) 
4417         allocate(cfamily_tmp(    numHeader_mpiLocal)) 
4418         allocate( cstnid_tmp(    numHeader_mpiLocal)) 
4419         allocate(realHeaders_tmp(odc_numActiveColumn(obsdat%realHeaders), &
4420                                  numHeader_mpilocal))
4421         allocate( intHeaders_tmp(odc_numActiveColumn(obsdat%intHeaders), &
4422                                  numHeader_mpilocal))
4423      endif
4424
4425      if(numBody_mpiLocal > 0) then
4426         allocate(bodyPrimaryKey_tmp(numBody_mpiLocal)) 
4427         allocate( realBodies_tmp(odc_numActiveColumn(obsdat%realBodies), &
4428                                  numBody_mpilocal))
4429         allocate(  intBodies_tmp(odc_numActiveColumn(obsdat%intBodies), &
4430                                  numBody_mpilocal))
4431      endif
4432
4433      ! copy the mpilocal data to temporary arrays: header-level data
4434      do headerIndex_mpilocal=1,numHeader_mpilocal 
4435         headerIndex_mpiglobal=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
4436
4437         do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
4438            column_index=odc_columnIndexFromActiveIndex( &
4439                                    obsdat%realHeaders%odc_flavour, active_index)
4440            realHeaders_tmp(active_index,headerIndex_mpilocal)= &
4441                      obs_headElem_r(obsdat, column_index, headerIndex_mpiglobal)
4442         enddo
4443
4444         do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
4445            column_index=odc_columnIndexFromActiveIndex( &
4446                                     obsdat%intHeaders%odc_flavour, active_index)
4447            intHeaders_tmp(active_index,headerIndex_mpilocal)= &
4448                      obs_headElem_i(obsdat, column_index, headerIndex_mpiglobal)
4449         enddo
4450
4451         headerPrimaryKey_tmp(headerIndex_mpilocal) = &
4452                obsdat%headerPrimaryKey(headerIndex_mpiglobal)
4453
4454         cstnid_tmp (headerIndex_mpilocal) =obsdat%cstnid (headerIndex_mpiglobal)
4455         cfamily_tmp(headerIndex_mpilocal) =obsdat%cfamily(headerIndex_mpiglobal)
4456
4457                                        ! Make RLN point to local data
4458         if(headerIndex_mpilocal == 1) then
4459            intHeaders_tmp &
4460                (odc_activeIndexFromColumnIndex( &
4461                                        obsdat%intHeaders%odc_flavour,OBS_RLN), &
4462                 1 &
4463                ) = 1
4464         else
4465            intHeaders_tmp &
4466                (odc_activeIndexFromColumnIndex( &
4467                                        obsdat%intHeaders%odc_flavour,OBS_RLN), &
4468                 headerIndex_mpilocal &
4469                ) = intHeaders_tmp(odc_activeIndexFromColumnIndex( &
4470                                        obsdat%intHeaders%odc_flavour,OBS_RLN), &
4471                                   headerIndex_mpilocal-1 &
4472                                  ) &
4473                  + intHeaders_tmp(odc_activeIndexFromColumnIndex( &
4474                                        obsdat%intHeaders%odc_flavour,OBS_NLV), &
4475                                   headerIndex_mpilocal-1 &
4476                                  ) 
4477         endif
4478      enddo
4479
4480      ! copy the mpilocal data to temporary arrays: body-level data
4481      bodyIndex_mpilocal=0 
4482      do headerIndex_mpilocal=1,numHeader_mpilocal
4483         headerIndex_mpiglobal=obsdat%headerIndex_mpiglobal(headerIndex_mpilocal)
4484 
4485                                        ! Make HIND point to local header
4486         idata    = obs_headElem_i(obsdat, OBS_RLN,headerIndex_mpiglobal)
4487         idataend = obs_headElem_i(obsdat, OBS_NLV,headerIndex_mpiglobal)+idata-1
4488         do bodyIndex_mpiglobal=idata,idataend 
4489            bodyIndex_mpilocal=bodyIndex_mpilocal+1 
4490
4491            bodyPrimaryKey_tmp(bodyIndex_mpilocal)= &
4492                        obsdat%bodyPrimaryKey(bodyIndex_mpiglobal)
4493
4494            do active_index=1,odc_numActiveColumn(obsdat%realBodies)
4495               column_index=odc_columnIndexFromActiveIndex( &
4496                                      obsdat%realBodies%odc_flavour,active_index)
4497               realBodies_tmp(active_index,bodyIndex_mpilocal)= &
4498                        obs_bodyElem_r(obsdat, column_index, bodyIndex_mpiglobal)
4499            enddo
4500
4501            do active_index=1,odc_numActiveColumn(obsdat%intBodies)
4502               column_index=odc_columnIndexFromActiveIndex( &
4503                                       obsdat%intBodies%odc_flavour,active_index)
4504               intBodies_tmp(active_index,bodyIndex_mpilocal)= &
4505                        obs_bodyElem_i(obsdat, column_index, bodyIndex_mpiglobal)
4506            enddo
4507
4508            intBodies_tmp(odc_activeIndexFromColumnIndex( &
4509                                       obsdat%intBodies%odc_flavour, OBS_HIND), &
4510                          bodyIndex_mpilocal &
4511                         ) = headerIndex_mpilocal
4512         enddo
4513      enddo
4514
4515      ! destroy object's mpiglobal data and allocate mpilocal data
4516      obsdat%numHeader=numHeader_mpiLocal
4517      obsdat%numBody=numBody_mpiLocal
4518      call obs_deallocate(obsdat)
4519      call obs_allocate(obsdat,obsdat%numHeader,obsdat%numBody)
4520
4521      ! copy all data from temporary arrays to object's arrays
4522      HEADER:if(numHeader_mpiLocal > 0) then
4523         obsdat%headerPrimaryKey(:)=headerPrimaryKey_tmp(:  ) 
4524         obsdat%cfamily(:  )=cfamily_tmp(:  ) 
4525         obsdat%cstnid (:  )= cstnid_tmp(:  )
4526         do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
4527            column_index=odc_columnIndexFromActiveIndex( &
4528                                    obsdat%realHeaders%odc_flavour, active_index)
4529            obsdat%realHeaders%columns(column_index)%value_r(:) &
4530                                                 =realHeaders_tmp(active_index,:)
4531         enddo
4532         do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
4533            column_index=odc_columnIndexFromActiveIndex( &
4534                                     obsdat%intHeaders%odc_flavour, active_index)
4535            obsdat%intHeaders%columns(column_index)%value_i(:) &
4536                                                  =intHeaders_tmp(active_index,:)
4537         enddo
4538
4539         ! deallocate temporary arrays
4540         deallocate(headerPrimaryKey_tmp)
4541         deallocate(cfamily_tmp)
4542         deallocate(cstnid_tmp)
4543         deallocate(realHeaders_tmp)
4544         deallocate(intHeaders_tmp)
4545      endif HEADER
4546
4547      BODY:if(numBody_mpiLocal > 0) then
4548         obsdat%bodyPrimaryKey(:) = bodyPrimaryKey_tmp(:)
4549         do active_index=1,odc_numActiveColumn(obsdat%realBodies)
4550            column_index=odc_columnIndexFromActiveIndex( &
4551                                     obsdat%realBodies%odc_flavour, active_index)
4552            obsdat%realBodies%columns(column_index)%value_r(:) &
4553                                                  =realBodies_tmp(active_index,:)
4554         enddo
4555         do active_index=1,odc_numActiveColumn(obsdat%intBodies)
4556            column_index=odc_columnIndexFromActiveIndex( &
4557                                      obsdat%intBodies%odc_flavour, active_index)
4558            obsdat%intBodies%columns(column_index)%value_i(:) &
4559                                                  =intBodies_tmp(active_index,:) 
4560         enddo
4561
4562         ! deallocate temporary arrays
4563         deallocate(bodyPrimaryKey_tmp)
4564         deallocate(realBodies_tmp)
4565         deallocate(intBodies_tmp)
4566      endif BODY
4567
4568
4569      obsdat%mpi_local = .true.
4570
4571      write(*,*) '============= Leave obs_reduceToMpiLocal =============='
4572
4573      return
4574   end subroutine obs_reduceToMpiLocal
4575
4576
4577   subroutine obs_squeeze( obsdat )
4578      !
4579      !:Purpose: re-construct observation data object to save memory
4580      !
4581      implicit none
4582
4583      ! Arguments:
4584      type(struct_obs), intent(inout) :: obsdat
4585
4586      ! Locals:
4587      integer(8),        allocatable :: headerPrimaryKey_tmp(:)
4588      integer(8),        allocatable :: bodyPrimaryKey_tmp(:)
4589      character(len=12), allocatable :: cstnid_tmp(:)
4590      character(len=2),  allocatable :: cfamily_tmp(:)
4591      real(pre_obsReal), allocatable :: realHeaders_tmp(:,:), realBodies_tmp(:,:)
4592      integer,           allocatable :: intHeaders_tmp(:,:),intBodies_tmp(:,:)
4593      integer :: bodyIndex, headerIndex, active_index, column_index
4594      integer :: idataend, idata
4595
4596      write(*,*) '============= Enter obs_squeeze =============='
4597
4598      if(obsdat%numHeader_max == obsdat%numHeader .and. &
4599         obsdat%numBody_max == obsdat%numBody)then
4600         write(*,*) 'obs_squeeze: obsdata instance is already sized correctly, do nothing'
4601         return
4602      end if
4603
4604      ! allocate temporary arrays to hold data
4605      if(obsdat%numHeader > 0) then
4606         allocate(headerPrimaryKey_tmp(obsdat%numHeader))
4607         allocate(cfamily_tmp(    obsdat%numHeader)) 
4608         allocate( cstnid_tmp(    obsdat%numHeader)) 
4609         allocate(realHeaders_tmp(odc_numActiveColumn(obsdat%realHeaders), &
4610                                  obsdat%numHeader))
4611         allocate( intHeaders_tmp(odc_numActiveColumn(obsdat%intHeaders), &
4612                                  obsdat%numHeader))
4613      endif
4614
4615      if(obsdat%numBody > 0) then
4616         allocate(bodyPrimaryKey_tmp(obsdat%numBody))
4617         allocate( realBodies_tmp(odc_numActiveColumn(obsdat%realBodies), &
4618                                  obsdat%numBody))
4619         allocate(  intBodies_tmp(odc_numActiveColumn(obsdat%intBodies), &
4620                                  obsdat%numBody))
4621      endif
4622
4623      ! copy the data to temporary arrays: header-level data
4624      do headerIndex=1,obsdat%numHeader 
4625         headerPrimaryKey_tmp(headerIndex)= &
4626                      obsdat%headerPrimaryKey(headerIndex)
4627        
4628         do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
4629            column_index=odc_columnIndexFromActiveIndex( &
4630                                    obsdat%realHeaders%odc_flavour, active_index)
4631            realHeaders_tmp(active_index,headerIndex)= &
4632                      obs_headElem_r(obsdat, column_index, headerIndex)
4633         enddo
4634
4635         do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
4636            column_index=odc_columnIndexFromActiveIndex( &
4637                                     obsdat%intHeaders%odc_flavour, active_index)
4638            intHeaders_tmp(active_index,headerIndex)= &
4639                      obs_headElem_i(obsdat, column_index, headerIndex)
4640         enddo
4641
4642         cstnid_tmp (headerIndex) =obsdat%cstnid (headerIndex)
4643         cfamily_tmp(headerIndex) =obsdat%cfamily(headerIndex)
4644
4645      enddo
4646
4647      ! copy the data to temporary arrays: body-level data
4648      do headerIndex=1,obsdat%numHeader
4649                                        ! Make HIND point to local header
4650         idata    = obs_headElem_i(obsdat, OBS_RLN,headerIndex)
4651         idataend = obs_headElem_i(obsdat, OBS_NLV,headerIndex)+idata-1
4652         do bodyIndex=idata,idataend 
4653            bodyPrimaryKey_tmp(bodyIndex)= &
4654                        obsdat%bodyPrimaryKey(bodyIndex)
4655           
4656            do active_index=1,odc_numActiveColumn(obsdat%realBodies)
4657               column_index=odc_columnIndexFromActiveIndex( &
4658                                      obsdat%realBodies%odc_flavour,active_index)
4659               realBodies_tmp(active_index,bodyIndex)= &
4660                        obs_bodyElem_r(obsdat, column_index, bodyIndex)
4661            enddo
4662
4663            do active_index=1,odc_numActiveColumn(obsdat%intBodies)
4664               column_index=odc_columnIndexFromActiveIndex( &
4665                                       obsdat%intBodies%odc_flavour,active_index)
4666               intBodies_tmp(active_index,bodyIndex)= &
4667                        obs_bodyElem_i(obsdat, column_index, bodyIndex)
4668            enddo
4669
4670            intBodies_tmp(odc_activeIndexFromColumnIndex(obsdat%intBodies%odc_flavour, OBS_HIND), &
4671                          bodyIndex) = headerIndex
4672         enddo
4673      enddo
4674
4675      ! destroy object's data and allocate at correct size data
4676      call obs_deallocate(obsdat)
4677      call obs_allocate(obsdat,obsdat%numHeader,obsdat%numBody)
4678
4679      ! copy all data from temporary arrays to object's arrays
4680      HEADER:if(obsdat%numHeader > 0) then
4681         obsdat%headerPrimaryKey(:)=headerPrimaryKey_tmp(:)
4682         obsdat%cfamily(:)=cfamily_tmp(:) 
4683         obsdat%cstnid (:)= cstnid_tmp(:)
4684         do active_index=1,odc_numActiveColumn(obsdat%realHeaders)
4685            column_index=odc_columnIndexFromActiveIndex( &
4686                                    obsdat%realHeaders%odc_flavour, active_index)
4687            obsdat%realHeaders%columns(column_index)%value_r(:) &
4688                                                 =realHeaders_tmp(active_index,:)
4689         enddo
4690         do active_index=1,odc_numActiveColumn(obsdat%intHeaders)
4691            column_index=odc_columnIndexFromActiveIndex( &
4692                                     obsdat%intHeaders%odc_flavour, active_index)
4693            obsdat%intHeaders%columns(column_index)%value_i(:) &
4694                                                  =intHeaders_tmp(active_index,:)
4695         enddo
4696
4697         ! deallocate temporary arrays
4698         deallocate(headerPrimaryKey_tmp)
4699         deallocate(cfamily_tmp)
4700         deallocate(cstnid_tmp)
4701         deallocate(realHeaders_tmp)
4702         deallocate(intHeaders_tmp)
4703      endif HEADER
4704
4705      BODY:if(obsdat%numBody > 0) then
4706         obsdat%bodyPrimaryKey(:) = bodyPrimaryKey_tmp(:)
4707         do active_index=1,odc_numActiveColumn(obsdat%realBodies)
4708            column_index=odc_columnIndexFromActiveIndex( &
4709                                     obsdat%realBodies%odc_flavour, active_index)
4710            obsdat%realBodies%columns(column_index)%value_r(:) &
4711                                                  =realBodies_tmp(active_index,:)
4712         enddo
4713         do active_index=1,odc_numActiveColumn(obsdat%intBodies)
4714            column_index=odc_columnIndexFromActiveIndex( &
4715                                      obsdat%intBodies%odc_flavour, active_index)
4716            obsdat%intBodies%columns(column_index)%value_i(:) &
4717                                                  =intBodies_tmp(active_index,:) 
4718         enddo
4719
4720         ! deallocate temporary arrays
4721         deallocate(bodyPrimaryKey_tmp)
4722         deallocate(realBodies_tmp)
4723         deallocate(intBodies_tmp)
4724      endif BODY
4725
4726      write(*,*) '============= Leave obs_squeeze =============='
4727
4728   end subroutine obs_squeeze
4729
4730
4731   subroutine obs_MpiRedistribute( obsdat_inout, target_ip_index )
4732      !
4733      !:Purpose: Redistribute obs over mpi tasks according to mpi task id stored 
4734      !                            in the integer header column "target_ip_index"
4735      !
4736      implicit none
4737
4738      ! Arguments:
4739      type(struct_obs), intent(inout) :: obsdat_inout
4740      integer,          intent(in)    :: target_ip_index
4741
4742      ! Locals:
4743      type(struct_obs) :: obsdat_tmp
4744      integer, allocatable :: numHeaderPE_mpilocal(:), numHeaderPE_mpiglobal(:)
4745      integer, allocatable :: numBodyPE_mpilocal(:), numBodyPE_mpiglobal(:)
4746      integer,           allocatable :: intcstnid_send(:,:,:), intcstnid_recv(:,:,:)
4747      integer,           allocatable :: intcfamily_send(:,:,:), intcfamily_recv(:,:,:)
4748      integer(8),        allocatable :: primaryKey_send(:,:), primaryKey_recv(:,:)
4749      real(pre_obsReal), allocatable :: real_send(:,:,:), real_recv(:,:,:)
4750      integer,           allocatable :: int_send(:,:,:), int_recv(:,:,:), message_onm(:,:)
4751      real(pre_obsReal), allocatable :: real_send_2d(:,:), real_recv_2d(:,:)
4752      integer,           allocatable :: int_send_2d(:,:), int_recv_2d(:,:)
4753      integer :: numHeader_in, numBody_in
4754      integer :: numHeader_out, numBody_out
4755      integer :: numHeader_mpimessage, numBody_mpimessage
4756      integer :: bodyIndex, headerIndex, headerIndex_out, bodyIndex_out, columnIndex, activeIndex, procIndex, charIndex
4757      integer :: bodyIndexBeg, bodyIndexEnd
4758      integer :: nprocs_mpi, myid_mpi, ierr, nsize, target_ip
4759      logical :: needToRedistribute, needToRedistribute_mpiglobal
4760      integer :: get_max_rss
4761
4762      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
4763      write(*,*) '============= Enter obs_MpiRedistribute =============='
4764      write(*,*) 'redistribute data according to mpi task ID stored in column :', &
4765                 ocn_ColumnNameList_IH(target_ip_index)
4766
4767      ! determine rank and number of mpi tasks
4768      call rpn_comm_size("GRID",nprocs_mpi,ierr)
4769      call rpn_comm_rank("GRID",myid_mpi,ierr)
4770
4771
4772      ! Number of headers and bodies per task before redistribution
4773      numHeader_in = obs_numHeader(obsdat_inout)
4774      numBody_in   = obs_numBody(obsdat_inout)
4775
4776      ! check if redistribution is really needed
4777      needToRedistribute = .false.
4778      do headerIndex = 1, numHeader_in
4779         target_ip = obs_headElem_i(obsdat_inout,target_ip_index,headerIndex)
4780         if (target_ip /= myid_mpi) needToRedistribute = .true.
4781      enddo
4782      call rpn_comm_allreduce(needToRedistribute,needToRedistribute_mpiglobal,1,  &
4783                              "MPI_LOGICAL","MPI_LOR","world",ierr)
4784      if(.not.needToRedistribute_mpiglobal) then
4785         write(*,*) 'obs_MpiRedistribute: do not need to redistribute, returning'
4786         return
4787      endif
4788
4789      ! allocate arrays used for counting on each mpi task
4790      allocate(numHeaderPE_mpilocal(nprocs_mpi))
4791      allocate(numHeaderPE_mpiglobal(nprocs_mpi))
4792      allocate(numBodyPE_mpilocal(nprocs_mpi))
4793      allocate(numBodyPE_mpiglobal(nprocs_mpi))
4794
4795      ! Compute number of headers and bodies per task after redistribution
4796      numHeaderPE_mpilocal(:) = 0
4797      numBodyPE_mpilocal(:) = 0
4798      do headerIndex = 1, numHeader_in
4799         target_ip = obs_headElem_i(obsdat_inout,target_ip_index,headerIndex)
4800         numHeaderPE_mpilocal(1+target_ip) = numHeaderPE_mpilocal(1+target_ip) + 1
4801         numBodyPE_mpilocal(1+target_ip)   = numBodyPE_mpilocal(1+target_ip)   + obs_headElem_i(obsdat_inout,OBS_NLV,headerIndex)
4802      enddo
4803      call rpn_comm_allreduce(numHeaderPE_mpilocal,numHeaderPE_mpiglobal,nprocs_mpi,  &
4804                              "MPI_INTEGER","MPI_SUM","world",ierr)
4805      call rpn_comm_allreduce(numBodyPE_mpilocal,numBodyPE_mpiglobal,nprocs_mpi,  &
4806                              "MPI_INTEGER","MPI_SUM","world",ierr)
4807      numHeader_out = numHeaderPE_mpiglobal(myid_mpi+1)
4808      numBody_out   = numBodyPE_mpiglobal(myid_mpi+1)
4809      write(*,*) 'obs_MpiRedistribute: num mpi header and body before redistribution =', numHeader_in, numBody_in
4810      write(*,*) 'obs_MpiRedistribute: num mpi header and body after redistribution  =', numHeader_out, numBody_out
4811
4812      ! Compute the max number of headers and bodies in each mpi message sent/received in the transpose
4813      call rpn_comm_allreduce(numHeaderPE_mpilocal,numHeaderPE_mpiglobal,nprocs_mpi,  &
4814                              "MPI_INTEGER","MPI_MAX","GRID",ierr)
4815      call rpn_comm_allreduce(numBodyPE_mpilocal,numBodyPE_mpiglobal,nprocs_mpi,  &
4816                              "MPI_INTEGER","MPI_MAX","GRID",ierr)
4817      if(myid_mpi == 0) write(*,*) 'obs_MpiRedistribute: num mpi header messages =', numHeaderPE_mpilocal
4818      if(myid_mpi == 0) write(*,*) 'obs_MpiRedistribute: num mpi body messages =', numBodyPE_mpilocal
4819      if(myid_mpi == 0) write(*,*) 'obs_MpiRedistribute: num mpi header messages (max) =', numHeaderPE_mpiglobal
4820      if(myid_mpi == 0) write(*,*) 'obs_MpiRedistribute: num mpi body messages (max) =', numBodyPE_mpiglobal
4821      numHeader_mpimessage = maxval(numHeaderPE_mpiglobal(:))
4822      numBody_mpimessage   = maxval(numBodyPE_mpiglobal(:))
4823
4824      call obs_initialize(obsdat_tmp,numHeader_max_opt=numHeader_out,  &
4825                          numBody_max_opt=numBody_out,mpi_local_opt=.true.)
4826      obsdat_tmp%numHeader = numHeader_out
4827      obsdat_tmp%numBody   = numBody_out
4828
4829      ! allocate temporary arrays to hold header-level data for mpi communication
4830      allocate(intcfamily_send(len(obsdat_inout%cfamily(1)),numHeader_mpimessage,nprocs_mpi)) 
4831      allocate(intcfamily_recv(len(obsdat_inout%cfamily(1)),numHeader_mpimessage,nprocs_mpi)) 
4832
4833      allocate(intcstnid_send(len(obsdat_inout%cstnid(1)),numHeader_mpimessage,nprocs_mpi)) 
4834      allocate(intcstnid_recv(len(obsdat_inout%cstnid(1)),numHeader_mpimessage,nprocs_mpi)) 
4835
4836      allocate(real_send(odc_numActiveColumn(obsdat_inout%realHeaders), &
4837                         numHeader_mpimessage,nprocs_mpi))
4838      allocate(real_recv(odc_numActiveColumn(obsdat_inout%realHeaders), &
4839                         numHeader_mpimessage,nprocs_mpi))
4840      allocate(int_send(odc_numActiveColumn(obsdat_inout%intHeaders), &
4841                        numHeader_mpimessage,nprocs_mpi))
4842      allocate(int_recv(odc_numActiveColumn(obsdat_inout%intHeaders), &
4843                        numHeader_mpimessage,nprocs_mpi))
4844
4845      allocate(primaryKey_send(numHeader_mpimessage,nprocs_mpi))
4846      allocate(primaryKey_recv(numHeader_mpimessage,nprocs_mpi))
4847
4848      ! copy the data to temporary arrays: header-level data
4849      numHeaderPE_mpilocal(:) = 0
4850      int_send(:,:,:) = -99999
4851      do headerIndex=1,numHeader_in
4852         target_ip = obs_headElem_i(obsdat_inout,target_ip_index,headerIndex)
4853         numHeaderPE_mpilocal(1+target_ip) = numHeaderPE_mpilocal(1+target_ip) + 1
4854
4855         primaryKey_send(numHeaderPE_mpilocal(1+target_ip),1+target_ip)= &
4856               obsdat_inout%headerPrimaryKey(headerIndex)
4857
4858         do activeIndex=1,odc_numActiveColumn(obsdat_inout%realHeaders)
4859            columnIndex=odc_columnIndexFromActiveIndex(obsdat_inout%realHeaders%odc_flavour, &
4860                                                       activeIndex)
4861            real_send(activeIndex,numHeaderPE_mpilocal(1+target_ip),1+target_ip)= &
4862               obs_headElem_r(obsdat_inout, columnIndex, headerIndex)
4863         enddo
4864
4865         do activeIndex=1,odc_numActiveColumn(obsdat_inout%intHeaders)
4866            columnIndex=odc_columnIndexFromActiveIndex(obsdat_inout%intHeaders%odc_flavour, &
4867                                                       activeIndex)
4868            int_send(activeIndex,numHeaderPE_mpilocal(1+target_ip),1+target_ip)= &
4869               obs_headElem_i(obsdat_inout, columnIndex, headerIndex)
4870         enddo
4871
4872         do charIndex=1, len(obsdat_inout%cstnid(1))
4873            intcstnid_send(charIndex,numHeaderPE_mpilocal(1+target_ip),1+target_ip)= &
4874               iachar(obsdat_inout%cstnid(headerIndex)(charIndex:charIndex))
4875         enddo
4876
4877         do charIndex=1,len(obsdat_inout%cfamily(1))
4878            intcfamily_send(charIndex,numHeaderPE_mpilocal(1+target_ip),1+target_ip)=  &
4879               iachar(obsdat_inout%cfamily(headerIndex)(charIndex:charIndex))
4880         enddo
4881
4882      enddo
4883
4884      ! do mpi communication: header-level data
4885      if(nprocs_mpi > 1) then
4886        nsize = numHeader_mpimessage
4887        call rpn_comm_alltoall(primaryKey_send,nsize,"mpi_integer8",  &
4888                               primaryKey_recv,nsize,"mpi_integer8","GRID",ierr)
4889
4890        nsize = numHeader_mpimessage*odc_numActiveColumn(obsdat_inout%realHeaders)
4891        call rpn_comm_alltoall(real_send,nsize,"mpi_double_precision",  &
4892                               real_recv,nsize,"mpi_double_precision","GRID",ierr)
4893
4894        nsize = numHeader_mpimessage*odc_numActiveColumn(obsdat_inout%intHeaders)
4895        call rpn_comm_alltoall(int_send,nsize,"mpi_integer",  &
4896                               int_recv,nsize,"mpi_integer","GRID",ierr)
4897
4898        nsize = numHeader_mpimessage*len(obsdat_inout%cstnid(1))
4899        call rpn_comm_alltoall(intcstnid_send,nsize,"mpi_integer",  &
4900                               intcstnid_recv,nsize,"mpi_integer","GRID",ierr)
4901
4902        nsize = numHeader_mpimessage*len(obsdat_inout%cfamily(1))
4903        call rpn_comm_alltoall(intcfamily_send,nsize,"mpi_integer",  &
4904                               intcfamily_recv,nsize,"mpi_integer","GRID",ierr)
4905      else
4906        primaryKey_recv(:,1)   = primaryKey_send(:,1)
4907        real_recv(:,:,1)       = real_send(:,:,1)
4908        int_recv(:,:,1)        = int_send(:,:,1)
4909        intcstnid_recv(:,:,1)  = intcstnid_send(:,:,1)
4910        intcfamily_recv(:,:,1) = intcfamily_send(:,:,1)
4911      endif
4912
4913      allocate(message_onm(numHeader_mpimessage,nprocs_mpi))
4914      activeIndex = odc_activeIndexFromColumnIndex(obsdat_inout%intHeaders%odc_flavour,OBS_ONM)
4915      message_onm(:,:) = int_recv(activeIndex,:,:)
4916
4917      ! copy the data from temporary arrays: header-level data
4918      headerIndex_out = 0
4919      do procIndex = 1, nprocs_mpi
4920         do headerIndex=1,numHeader_mpimessage
4921            if(int_recv(1,headerIndex,procIndex) /= -99999) then
4922               if(target_ip_index == OBS_IPF) then
4923                 ! put headers back in original order as in the files
4924                 headerIndex_out = message_onm(headerIndex,procIndex)
4925               else
4926                 headerIndex_out = headerIndex_out + 1
4927               endif
4928
4929               obsdat_tmp%headerPrimaryKey(headerIndex_out)= &
4930                     primaryKey_recv(headerIndex,procIndex)
4931
4932               do activeIndex=1,odc_numActiveColumn(obsdat_inout%realHeaders)
4933                  columnIndex=odc_columnIndexFromActiveIndex(obsdat_inout%realHeaders%odc_flavour, &
4934                                                             activeIndex)
4935                  obsdat_tmp%realHeaders%columns(columnIndex)%value_r(headerIndex_out)= &
4936                     real_recv(activeIndex,headerIndex,procIndex)
4937               enddo
4938
4939               do activeIndex=1,odc_numActiveColumn(obsdat_inout%intHeaders)
4940                  columnIndex=odc_columnIndexFromActiveIndex(obsdat_inout%intHeaders%odc_flavour, &
4941                                                             activeIndex)
4942                  obsdat_tmp%intHeaders%columns(columnIndex)%value_i(headerIndex_out)= &
4943                     int_recv(activeIndex,headerIndex,procIndex)
4944               enddo
4945
4946               do charIndex=1, len(obsdat_inout%cstnid(1))
4947                  obsdat_tmp%cstnid(headerIndex_out)(charIndex:charIndex) = &
4948                     achar(intcstnid_recv(charIndex,headerIndex,procIndex))
4949               enddo
4950
4951               do charIndex=1, len(obsdat_inout%cfamily(1))
4952                  obsdat_tmp%cfamily(headerIndex_out)(charIndex:charIndex) = &
4953                     achar(intcfamily_recv(charIndex,headerIndex,procIndex))
4954               enddo
4955
4956            endif
4957         enddo
4958      enddo
4959
4960      ! recompute RLN to point to correct body data
4961      do headerIndex=1,numHeader_out
4962         if(headerIndex == 1) then
4963            obsdat_tmp%intHeaders%columns(OBS_RLN)%value_i(headerIndex) = 1
4964         else
4965            obsdat_tmp%intHeaders%columns(OBS_RLN)%value_i(headerIndex) = &
4966                obsdat_tmp%intHeaders%columns(OBS_RLN)%value_i(headerIndex-1) + &
4967                obsdat_tmp%intHeaders%columns(OBS_NLV)%value_i(headerIndex-1)
4968         endif
4969      enddo
4970
4971      deallocate(primaryKey_send)
4972      deallocate(primaryKey_recv)
4973      deallocate(intcfamily_send)
4974      deallocate(intcfamily_recv)
4975      deallocate(intcstnid_send)
4976      deallocate(intcstnid_recv)
4977      deallocate(real_send)
4978      deallocate(real_recv)
4979      deallocate(int_send)
4980      deallocate(int_recv)
4981
4982      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
4983
4984      ! Do communication for bodyPrimaryKey
4985
4986      allocate(primaryKey_send(numBody_mpimessage,nprocs_mpi))
4987      allocate(primaryKey_recv(numBody_mpimessage,nprocs_mpi))
4988
4989      numBodyPE_mpilocal(:) = 0
4990      primaryKey_send(:,:) = -99999
4991      do bodyIndex=1,numBody_in
4992         headerIndex = obs_bodyElem_i(obsdat_inout,OBS_HIND,bodyIndex)
4993         target_ip = obs_headElem_i(obsdat_inout,target_ip_index,headerIndex)
4994         numBodyPE_mpilocal(1+target_ip) = numBodyPE_mpilocal(1+target_ip) + 1
4995         primaryKey_send(numBodyPE_mpilocal(1+target_ip),1+target_ip)= &
4996              obsdat_inout%bodyPrimaryKey(bodyIndex)
4997      enddo
4998      if(nprocs_mpi > 1) then
4999         nsize = numBody_mpimessage
5000         call rpn_comm_alltoall(primaryKey_send,nsize,"mpi_integer8",  &
5001                                primaryKey_recv,nsize,"mpi_integer8","GRID",ierr)
5002      else
5003         primaryKey_recv(:,1) = primaryKey_send(:,1)
5004      endif
5005      if(target_ip_index == OBS_IPF) then
5006         ! copy the data in the same order as in the original files
5007         do procIndex = 1, nprocs_mpi
5008            bodyIndex = 0
5009            do headerIndex=1,numHeader_mpimessage
5010               headerIndex_out = message_onm(headerIndex,procIndex)
5011               if(headerIndex_out /= -99999) then
5012                  bodyIndexBeg = obs_headElem_i(obsdat_tmp,OBS_RLN,headerIndex_out)
5013                  bodyIndexEnd = obs_headElem_i(obsdat_tmp,OBS_NLV,headerIndex_out) + bodyIndexBeg - 1
5014                  do bodyIndex_out = bodyIndexBeg, bodyIndexEnd
5015                     bodyIndex = bodyIndex + 1
5016                     obsdat_tmp%bodyPrimaryKey(bodyIndex_out)= &
5017                          primaryKey_recv(bodyIndex,procIndex)
5018                  enddo
5019               endif
5020            enddo
5021         enddo
5022         ! copy the data in sequential order
5023         bodyIndex_out = 0
5024         do procIndex = 1, nprocs_mpi
5025            do bodyIndex=1,numBody_mpimessage
5026               if(primaryKey_recv(bodyIndex,procIndex) /= -99999) then
5027                  bodyIndex_out = bodyIndex_out + 1
5028                  obsdat_tmp%bodyPrimaryKey(bodyIndex_out)= &
5029                       primaryKey_recv(bodyIndex,procIndex)
5030               endif
5031            enddo
5032         enddo
5033      endif
5034
5035      deallocate(primaryKey_send)
5036      deallocate(primaryKey_recv)
5037
5038      ! First do REAL body columns
5039
5040      allocate(real_send_2d(numBody_mpimessage,nprocs_mpi))
5041      allocate(real_recv_2d(numBody_mpimessage,nprocs_mpi))
5042
5043      do activeIndex=1,odc_numActiveColumn(obsdat_inout%realBodies)
5044         columnIndex=odc_columnIndexFromActiveIndex(obsdat_inout%realBodies%odc_flavour, &
5045                                                    activeIndex)
5046
5047         ! copy the data to temporary arrays: body-level data
5048         numBodyPE_mpilocal(:) = 0
5049         real_send_2d(:,:) = -99999.0d0
5050         do bodyIndex=1,numBody_in
5051            headerIndex = obs_bodyElem_i(obsdat_inout,OBS_HIND,bodyIndex)
5052            target_ip = obs_headElem_i(obsdat_inout,target_ip_index,headerIndex)
5053            numBodyPE_mpilocal(1+target_ip) = numBodyPE_mpilocal(1+target_ip) + 1
5054            real_send_2d(numBodyPE_mpilocal(1+target_ip),1+target_ip)= &
5055               obs_bodyElem_r(obsdat_inout, columnIndex, bodyIndex)
5056         enddo
5057
5058         ! do mpi communication: body-level data
5059         if(nprocs_mpi > 1) then
5060           nsize = numBody_mpimessage
5061           call rpn_comm_alltoall(real_send_2d,nsize,"mpi_double_precision",  &
5062                                  real_recv_2d,nsize,"mpi_double_precision","GRID",ierr)
5063         else
5064           real_recv_2d(:,1)       = real_send_2d(:,1)
5065         endif
5066
5067         ! copy the data from temporary arrays: body-level data
5068         if(target_ip_index == OBS_IPF) then
5069
5070            ! copy the data in the same order as in the original files
5071            do procIndex = 1, nprocs_mpi
5072               bodyIndex = 0
5073               do headerIndex=1,numHeader_mpimessage
5074                  headerIndex_out = message_onm(headerIndex,procIndex)
5075                  if(headerIndex_out /= -99999) then
5076                     bodyIndexBeg = obs_headElem_i(obsdat_tmp,OBS_RLN,headerIndex_out)
5077                     bodyIndexEnd = obs_headElem_i(obsdat_tmp,OBS_NLV,headerIndex_out) + bodyIndexBeg - 1
5078                     do bodyIndex_out = bodyIndexBeg, bodyIndexEnd
5079                        bodyIndex = bodyIndex + 1
5080                        obsdat_tmp%realBodies%columns(columnIndex)%value_r(bodyIndex_out)= &
5081                           real_recv_2d(bodyIndex,procIndex)
5082                     enddo
5083                  endif
5084               enddo
5085            enddo
5086
5087         else
5088
5089            ! copy the data in sequential order
5090            bodyIndex_out = 0
5091            do procIndex = 1, nprocs_mpi
5092               do bodyIndex=1,numBody_mpimessage
5093                  if(real_recv_2d(bodyIndex,procIndex) /= -99999.0d0) then
5094                     bodyIndex_out = bodyIndex_out + 1
5095                     obsdat_tmp%realBodies%columns(columnIndex)%value_r(bodyIndex_out)= &
5096                        real_recv_2d(bodyIndex,procIndex)
5097                  endif
5098               enddo
5099            enddo
5100
5101         endif
5102
5103      enddo ! activeIndex
5104
5105      deallocate(real_send_2d)
5106      deallocate(real_recv_2d)
5107
5108      ! Now do INTEGER body columns
5109
5110      allocate(int_send_2d(numBody_mpimessage,nprocs_mpi))
5111      allocate(int_recv_2d(numBody_mpimessage,nprocs_mpi))
5112
5113      do activeIndex=1,odc_numActiveColumn(obsdat_inout%intBodies)
5114         columnIndex=odc_columnIndexFromActiveIndex(obsdat_inout%intBodies%odc_flavour, &
5115                                                    activeIndex)
5116
5117         ! copy the data to temporary arrays: body-level data
5118         numBodyPE_mpilocal(:) = 0
5119         int_send_2d(:,:) = -99999
5120         do bodyIndex=1,numBody_in
5121            headerIndex = obs_bodyElem_i(obsdat_inout,OBS_HIND,bodyIndex)
5122            target_ip = obs_headElem_i(obsdat_inout,target_ip_index,headerIndex)
5123            numBodyPE_mpilocal(1+target_ip) = numBodyPE_mpilocal(1+target_ip) + 1
5124            int_send_2d(numBodyPE_mpilocal(1+target_ip),1+target_ip)= &
5125               obs_bodyElem_i(obsdat_inout, columnIndex, bodyIndex)
5126         enddo
5127
5128         ! do mpi communication: body-level data
5129         if(nprocs_mpi > 1) then
5130           nsize = numBody_mpimessage
5131           call rpn_comm_alltoall(int_send_2d,nsize,"mpi_integer",  &
5132                                  int_recv_2d,nsize,"mpi_integer","GRID",ierr)
5133         else
5134           int_recv_2d(:,1)        = int_send_2d(:,1)
5135         endif
5136
5137         ! copy the data from temporary arrays: body-level data
5138         if(target_ip_index == OBS_IPF) then
5139
5140            ! copy the data in the same order as in the original files
5141            do procIndex = 1, nprocs_mpi
5142               bodyIndex = 0
5143               do headerIndex=1,numHeader_mpimessage
5144                  headerIndex_out = message_onm(headerIndex,procIndex)
5145                  if(headerIndex_out /= -99999) then
5146                     bodyIndexBeg = obs_headElem_i(obsdat_tmp,OBS_RLN,headerIndex_out)
5147                     bodyIndexEnd = obs_headElem_i(obsdat_tmp,OBS_NLV,headerIndex_out) + bodyIndexBeg - 1
5148                     do bodyIndex_out = bodyIndexBeg, bodyIndexEnd
5149                        bodyIndex = bodyIndex + 1
5150                        obsdat_tmp%intBodies%columns(columnIndex)%value_i(bodyIndex_out)= &
5151                           int_recv_2d(bodyIndex,procIndex)
5152                     enddo
5153                  endif
5154               enddo
5155            enddo
5156
5157         else
5158
5159            ! copy the data in sequential order
5160            bodyIndex_out = 0
5161            do procIndex = 1, nprocs_mpi
5162               do bodyIndex=1,numBody_mpimessage
5163                  if(int_recv_2d(bodyIndex,procIndex) /= -99999) then
5164                     bodyIndex_out = bodyIndex_out + 1
5165                     obsdat_tmp%intBodies%columns(columnIndex)%value_i(bodyIndex_out)= &
5166                        int_recv_2d(bodyIndex,procIndex)
5167                  endif
5168               enddo
5169            enddo
5170
5171         endif
5172
5173      enddo ! activeIndex
5174
5175      deallocate(int_send_2d)
5176      deallocate(int_recv_2d)
5177
5178      ! reset the headerIndex within the body
5179      do headerIndex = 1, obs_numheader(obsdat_tmp)
5180        bodyIndexBeg = obs_headElem_i(obsdat_tmp,OBS_RLN,headerIndex)
5181        bodyIndexEnd = obs_headElem_i(obsdat_tmp,OBS_NLV,headerIndex) + bodyIndexBeg - 1
5182        do bodyIndex = bodyIndexBeg, bodyIndexEnd
5183          call obs_bodySet_i(obsdat_tmp,OBS_HIND,bodyIndex, headerIndex)
5184        enddo
5185      enddo
5186
5187      deallocate(message_onm)
5188
5189      deallocate(numHeaderPE_mpilocal)
5190      deallocate(numHeaderPE_mpiglobal)
5191      deallocate(numBodyPE_mpilocal)
5192      deallocate(numBodyPE_mpiglobal)
5193
5194      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
5195
5196      ! reallocate obsdat_inout to the new size and copy over redistributed data
5197      call obs_deallocate(obsdat_inout)
5198      call obs_allocate(obsdat_inout,obsdat_tmp%numHeader,obsdat_tmp%numBody)
5199      call obs_copy(obsdat_tmp,obsdat_inout)
5200      call obs_finalize(obsdat_tmp)
5201
5202      write(*,*) '============= Leave obs_MpiRedistribute =============='
5203      write(*,*) 'Memory Used: ',get_max_rss()/1024,'Mb'
5204
5205      return
5206   end subroutine obs_MpiRedistribute
5207
5208
5209   subroutine obs_set_c(obsdat, name, row_index, value)
5210      !
5211      !:Purpose: set a character(len=9) in the observation object
5212      !      To control access to the observation object.
5213      !
5214      implicit none
5215
5216      ! Arguments:
5217      type(struct_obs), intent(inout)  :: obsdat
5218      character(len=*), intent(in)     :: name
5219      integer         , intent(in)     :: row_index
5220      character(len=*), intent(in)     :: value
5221
5222      select case (trim(name))
5223      case ('STID'); obsdat%cstnid (row_index) = value
5224         if(row_index == (obsdat%numHeader+1)) obsdat%numHeader = obsdat%numHeader+1
5225
5226      case default
5227         call obs_abort('ERROR writing:  ' // trim(name) // &
5228                        ' is not a character(len=9) observation.')
5229         return
5230      end select
5231   end subroutine obs_set_c
5232
5233
5234   subroutine obs_set_current_body_list_from_family(obsdat, family, &
5235      list_is_empty_opt, current_list_opt)
5236      !
5237      !:Purpose:
5238      !      Create a row_index list from the indicated family and place it in
5239      !      the body depot.
5240      !
5241      implicit none
5242
5243      ! Arguments:
5244      type(struct_obs), target,                   intent(inout) :: obsdat
5245      character(len=*),                           intent(in)    :: family
5246      logical,                          optional, intent(out)   :: list_is_empty_opt
5247      type(struct_index_list), pointer, optional, intent(out)   :: current_list_opt
5248
5249      ! Locals:
5250      type(struct_index_list_depot), pointer :: depot
5251      type(struct_index_list), pointer :: index_list
5252      integer :: index_header, list, list_index, row_index
5253      integer :: first, last
5254
5255      nullify(index_list)
5256      depot => obsdat%body_index_list_depot
5257
5258      ! Search for an existing list
5259      if(present(current_list_opt)) then
5260         if(associated(current_list_opt)) then
5261            if (current_list_opt%family == family) then
5262               index_list => current_list_opt
5263            end if ! family matches
5264         end if ! associated
5265
5266      else ! not present(current_list)
5267         do list = 1, NUMBER_OF_LISTS
5268            if (depot%index_lists(list)%family == family) then
5269               index_list => depot%index_lists(list)
5270               exit                     ! Don't look any further
5271            end if
5272         end do
5273      end if
5274
5275      ! If the list does not already exist
5276      if (.not. associated(index_list)) then
5277
5278         ! Acquire memory for the list
5279         if(present(current_list_opt)) then
5280            ! This is an OMP thread. Re-use the same physical memory for the list
5281            index_list => ild_get_empty_index_list(depot, current_list_opt)
5282         else
5283            index_list => ild_get_empty_index_list(depot)
5284         end if
5285
5286         ! Initialize the new list
5287         index_list%family = family
5288         index_list%header = -99        ! not used
5289
5290         !
5291         ! Populate the list
5292         !
5293
5294         ! Loop over all header indices of the family
5295         list_index = 0
5296         call obs_set_current_header_list(obsdat, family)
5297         HEADER: do
5298            index_header = obs_getHeaderIndex(obsdat)
5299            if (index_header < 0) exit HEADER
5300            first= obs_headElem_i(obsdat,OBS_RLN,index_header)
5301            last = obs_headElem_i(obsdat,OBS_NLV,index_header) + first - 1
5302            do row_index=first,last     ! For each item indicated in the header
5303               ! Add the row_index to the list
5304               list_index = list_index + 1
5305               index_list%indices(list_index) = row_index
5306            end do
5307         end do HEADER
5308         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
5309         index_list%indices(list_index+2)= -1 ! ... clearly
5310      end if ! list does not already exist
5311
5312      index_list%current_element = 0    ! Set pointer to the start of the list
5313      depot%current_list => index_list  ! Note the current list
5314
5315      if(present(list_is_empty_opt)) then
5316         ! Return whether the list is empty
5317         list_is_empty_opt = (ild_get_next_index(depot, no_advance_opt=.true.) < 0)
5318      end if
5319
5320      if(present(current_list_opt)) then
5321         ! Return a pointer to the current list
5322         current_list_opt => index_list
5323      end if
5324   end subroutine obs_set_current_body_list_from_family
5325
5326
5327   subroutine obs_set_current_body_list_from_header(obsdat, header, &
5328      list_is_empty_opt, current_list_opt)
5329      !
5330      !:Purpose:
5331      !      Create a row_index list from the indicated header and place it in
5332      !      the body depot.
5333      !
5334      implicit none
5335
5336      ! Arguments:
5337      type(struct_obs), target,                   intent(inout) :: obsdat
5338      integer,                                    intent(in)    :: header
5339      logical,                          optional, intent(out)   :: list_is_empty_opt
5340      type(struct_index_list), pointer, optional, intent(out)   :: current_list_opt
5341
5342      ! Locals:
5343      type(struct_index_list_depot), pointer :: depot
5344      type(struct_index_list), pointer :: index_list
5345      integer :: list, list_index, row_index
5346      integer :: first, last
5347
5348      nullify(index_list)
5349      depot => obsdat%body_index_list_depot
5350
5351      ! Search for an existing list
5352      if(present(current_list_opt)) then
5353         if(associated(current_list_opt)) then
5354            if (current_list_opt%header == header) then
5355               index_list => current_list_opt
5356            end if ! header matches
5357         end if ! associated
5358
5359      else ! not present(current_list)
5360         do list = 1, NUMBER_OF_LISTS
5361            if (depot%index_lists(list)%header == header) then
5362               index_list => depot%index_lists(list)
5363               exit                     ! Don't look any further
5364            end if
5365         end do
5366      end if
5367
5368      ! If the list does not already exist
5369      if (.not. associated(index_list)) then
5370
5371         ! Acquire memory for the list
5372         if(present(current_list_opt)) then
5373            ! This is an OMP thread. Re-use the same physical memory for the list
5374            index_list => ild_get_empty_index_list(depot, current_list_opt)
5375         else
5376            index_list => ild_get_empty_index_list(depot)
5377         end if
5378
5379         ! Initialize the new list
5380         index_list%family = 'xx'       ! not used
5381         index_list%header = header
5382
5383         ! Populate the list
5384         first= obs_headElem_i(obsdat,OBS_RLN,header)
5385         last = obs_headElem_i(obsdat,OBS_NLV,header) + first - 1
5386         list_index = 0
5387         do row_index=first,last        ! For each item indicated in the header
5388            ! Add the row_index to the list
5389            list_index = list_index + 1
5390            index_list%indices(list_index) = row_index
5391         end do
5392         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
5393         index_list%indices(list_index+2)= -1 ! ... clearly
5394      end if ! list does not already exist
5395
5396      index_list%current_element = 0    ! Set pointer to the start of the list
5397      depot%current_list => index_list  ! Note the current list
5398
5399      if(present(list_is_empty_opt)) then
5400         ! Return whether the list is empty
5401         list_is_empty_opt = (ild_get_next_index(depot, no_advance_opt=.true.) < 0)
5402      end if
5403
5404      if(present(current_list_opt)) then
5405         ! Return a pointer to the current list
5406         current_list_opt => index_list
5407      end if
5408   end subroutine obs_set_current_body_list_from_header
5409
5410
5411   subroutine obs_set_current_body_list_all(obsdat, list_is_empty_opt, current_list_opt)
5412      !
5413      !:Purpose:
5414      !      Create a row_index list containing all bodies and place it in the
5415      !      body depot.
5416      !
5417      implicit none
5418
5419      ! Arguments:
5420      type(struct_obs), target,                   intent(inout) :: obsdat
5421      logical,                          optional, intent(out)   :: list_is_empty_opt
5422      type(struct_index_list), pointer, optional, intent(out)   :: current_list_opt
5423
5424      ! Locals:
5425      type(struct_index_list_depot), pointer :: depot
5426      type(struct_index_list), pointer :: index_list
5427      integer :: list, list_index, row_index, index_header
5428      integer :: first, last
5429
5430      nullify(index_list)
5431      depot => obsdat%body_index_list_depot
5432
5433      ! Search for an existing list
5434      if(present(current_list_opt)) then
5435         if(associated(current_list_opt)) then
5436            if (      current_list_opt%header == -1 &
5437                .and. current_list_opt%family == '  ') then
5438               index_list => current_list_opt
5439            end if ! null header and family
5440         end if ! associated
5441
5442      else ! not present(current_list)
5443         do list = 1, NUMBER_OF_LISTS
5444            if (      depot%index_lists(list)%header == -1 &
5445                .and. depot%index_lists(list)%family == '  ') then
5446               index_list => depot%index_lists(list)
5447               exit                     ! Don't look any further
5448            end if
5449         end do
5450      end if
5451
5452      ! If the list does not already exist
5453      if (.not. associated(index_list)) then
5454
5455         ! Acquire memory for the list
5456         if(present(current_list_opt)) then
5457            ! This is an OMP thread. Re-use the same physical memory for the list
5458            index_list => ild_get_empty_index_list(depot, current_list_opt)
5459         else
5460            index_list => ild_get_empty_index_list(depot)
5461         end if
5462
5463         ! Initialize the new list
5464         index_list%family = '  '       ! null
5465         index_list%header = -1         ! null
5466
5467         !
5468         ! Populate the list
5469         !
5470
5471         ! Loop over all header indices
5472         list_index = 0
5473         call obs_set_current_header_list(obsdat)
5474         HEADER: do
5475            index_header = obs_getHeaderIndex(obsdat)
5476            if (index_header < 0) exit HEADER
5477            first= obs_headElem_i(obsdat,OBS_RLN,index_header)
5478            last = obs_headElem_i(obsdat,OBS_NLV,index_header) + first - 1
5479            do row_index=first,last     ! For each item indicated in the header
5480               ! Add the row_index to the list
5481               list_index = list_index + 1
5482               index_list%indices(list_index) = row_index
5483            end do
5484         end do HEADER
5485         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
5486         index_list%indices(list_index+2)= -1 ! ... clearly
5487      end if ! list does not already exist
5488
5489      index_list%current_element = 0    ! Set pointer to the start of the list
5490      depot%current_list => index_list  ! Note the current list
5491
5492      if(present(list_is_empty_opt)) then
5493         ! Return whether the list is empty
5494         list_is_empty_opt = (ild_get_next_index(depot, no_advance_opt=.true.) < 0)
5495      end if
5496
5497      if(present(current_list_opt)) then
5498         ! Return a pointer to the current list
5499         current_list_opt => index_list
5500      end if
5501   end subroutine obs_set_current_body_list_all
5502
5503
5504   subroutine obs_set_current_header_list_from_family(obsdat, family)
5505      !
5506      !:Purpose:
5507      !      Find or create a row_index list for the indicated family and place
5508      !      it in the header depot.
5509      !
5510      implicit none
5511
5512      ! Arguments:
5513      type(struct_obs), target, intent(inout) :: obsdat
5514      character(len=*),         intent(in)    :: family
5515
5516      ! Locals:
5517      type(struct_index_list_depot), pointer :: depot
5518      type(struct_index_list), pointer :: index_list
5519      integer :: list, list_index, row_index
5520
5521      nullify(index_list)
5522      depot => obsdat%header_index_list_depot
5523
5524      ! Search for an existing list
5525      do list = 1, NUMBER_OF_LISTS
5526         if (depot%index_lists(list)%family == family) then
5527            index_list => depot%index_lists(list)
5528            index_list%current_element=0! Start at the beginning of the list
5529            exit                        ! Don't look any further
5530         end if
5531      end do
5532
5533      ! If the list does not already exist
5534      if (.not. associated(index_list)) then
5535         ! Create a new list
5536         index_list => ild_get_empty_index_list(depot)
5537         index_list%family = family
5538         index_list%header = -1
5539
5540         ! Populate the list
5541         list_index = 0
5542         do row_index = 1, obsdat%numHeader
5543            ! If the station is of the right family
5544            if(obsdat%cfamily(row_index) == family) then
5545               ! Add the row_index to the list
5546               list_index = list_index + 1
5547               index_list%indices(list_index) = row_index
5548            end if
5549         end do
5550         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
5551         index_list%indices(list_index+2)= -1 ! ... clearly
5552      end if ! list does not already exist
5553
5554      index_list%current_element = 0    ! Set pointer to the start of the list
5555      depot%current_list => index_list  ! Note the current list
5556   end subroutine obs_set_current_header_list_from_family
5557
5558
5559   subroutine obs_set_current_header_list_all(obsdat)
5560      !
5561      !:Purpose:
5562      !      Find or create a row_index list for all headers and place it in the
5563      !      header depot.
5564      !
5565      implicit none
5566
5567      ! Arguments:
5568      type(struct_obs), target, intent(inout) :: obsdat
5569
5570      ! Locals:
5571      type(struct_index_list_depot), pointer :: depot
5572      type(struct_index_list), pointer :: index_list
5573      integer :: list, list_index, row_index
5574
5575      nullify(index_list)
5576      depot => obsdat%header_index_list_depot
5577
5578      ! Search for an existing list
5579      do list = 1, NUMBER_OF_LISTS
5580         if (depot%index_lists(list)%family == '  ') then
5581            index_list => depot%index_lists(list)
5582            index_list%current_element=0! Start at the beginning of the list
5583            exit                        ! Don't look any further
5584         end if
5585      end do
5586
5587      ! If the list does not already exist
5588      if (.not. associated(index_list)) then
5589         ! Create a new list
5590         index_list => ild_get_empty_index_list(depot)
5591         index_list%family = '  '
5592         index_list%header = -1
5593
5594         ! Populate the list
5595         list_index = 0
5596         do row_index = 1, obsdat%numHeader
5597            ! Add the row_index to the list
5598            list_index = list_index + 1
5599            index_list%indices(list_index) = row_index
5600         end do
5601         index_list%indices(list_index+1)= -1 ! Flag the end of the list ...
5602         index_list%indices(list_index+2)= -1 ! ... clearly
5603      end if ! list does not already exist
5604
5605      index_list%current_element = 0    ! Set pointer to the start of the list
5606      depot%current_list => index_list  ! Note the current list
5607   end subroutine obs_set_current_header_list_all
5608
5609
5610   subroutine obs_setBodyPrimaryKey(obsdat,bodyIndex,primaryKey)
5611      !
5612      !:Purpose:
5613      !      Set to the indicated value the body primary key for the indicated body.
5614      !
5615      implicit none
5616
5617      ! Arguments:
5618      type(struct_obs), intent(inout) :: obsdat
5619      integer(8),       intent(in)    :: primaryKey
5620      integer,          intent(in)    :: bodyIndex
5621
5622      obsdat%bodyPrimaryKey(bodyIndex) = primaryKey
5623
5624      if(bodyIndex == (obsdat%numBody+1)) then
5625         obsdat%numBody=obsdat%numBody+1
5626      endif
5627
5628   end subroutine obs_setBodyPrimaryKey
5629
5630
5631   subroutine obs_setHeadPrimaryKey(obsdat,headerIndex,primaryKey)
5632      !
5633      !:Purpose:
5634      !      Set to the indicated value the header primary key for the indicated header.
5635      !
5636      implicit none
5637
5638      ! Arguments:
5639      type(struct_obs), intent(inout) :: obsdat
5640      integer(8),       intent(in)    :: primaryKey
5641      integer,          intent(in)    :: headerIndex
5642
5643      obsdat%headerPrimaryKey(headerIndex) = primaryKey
5644      if(headerIndex == (obsdat%numHeader+1)) then
5645         obsdat%numHeader=obsdat%numHeader+1
5646      endif
5647
5648   end subroutine obs_setHeadPrimaryKey
5649
5650
5651   subroutine obs_setFamily(obsdat,Family_in,headerIndex_opt,bodyIndex_opt)
5652      !
5653      !:Purpose:
5654      !      Set to the indicated value the family for the indicated header, or
5655      !      else for the indicated body.
5656      !
5657      implicit none
5658
5659      ! Arguments:
5660      type(struct_obs),  intent(inout) :: obsdat
5661      character(len=*),  intent(in)    :: Family_in
5662      integer, optional, intent(in)    :: headerIndex_opt
5663      integer, optional, intent(in)    :: bodyIndex_opt
5664
5665      ! Locals:
5666      integer          :: headerIndex
5667
5668      if(present(headerIndex_opt)) then
5669         headerIndex = headerIndex_opt
5670      elseif(present(bodyIndex_opt)) then
5671         headerIndex = obs_bodyElem_i(obsdat,OBS_HIND,bodyIndex_opt)
5672      else
5673         call obs_abort('OBS_SETFAMILY: Header or Body index must be specified!')
5674         return
5675      endif
5676
5677      obsdat%cfamily(headerIndex)=Family_in
5678      if(headerIndex == (obsdat%numHeader+1)) then
5679         obsdat%numHeader=obsdat%numHeader+1
5680      endif
5681
5682   end subroutine obs_setFamily
5683
5684
5685   subroutine obs_sethind(obsSpaceData)
5686     !
5687     !:Purpose: Set the header index in the body table
5688     !
5689     implicit none
5690
5691     ! Arguments:
5692     type(struct_obs), intent(inout) :: obsSpaceData
5693
5694     ! Locals:
5695     integer :: idata,idatend,bodyIndex,headerIndex
5696
5697     ! Set the header index in the body of obsSpaceData
5698     do headerIndex = 1, obs_numheader(obsSpaceData)
5699        idata   = obs_headElem_i(obsSpaceData,OBS_RLN,headerIndex)
5700        idatend = obs_headElem_i(obsSpaceData,OBS_NLV,headerIndex) + idata - 1
5701        do bodyIndex= idata, idatend
5702           call obs_bodySet_i(obsSpaceData, OBS_HIND, bodyIndex, headerIndex)
5703        end do
5704     end do
5705   end subroutine obs_sethind
5706
5707
5708   subroutine obs_write(obsdat,hx, &
5709      nens,nobshdrout,nobsbdyout,nobshxout,nobsdimout)
5710      ! 
5711      !:Purpose: 
5712      !      Write the obsdat info to unformatted files.
5713      !
5714      !:Note: the body information is written in the order that it will
5715      !      be used by sekfeta.f
5716      !
5717      implicit none
5718
5719      ! Arguments:
5720      type(struct_obs), intent(in) :: obsdat
5721      real(8),          intent(in) :: hx(:,:)
5722      integer,          intent(in) :: nens
5723      integer,          intent(in) :: nobshdrout
5724      integer,          intent(in) :: nobsbdyout
5725      integer,          intent(in) :: nobshxout
5726      integer,          intent(in) :: nobsdimout
5727
5728      ! Locals:
5729      integer :: irealBodies,jo,nrealBodies
5730
5731      irealBodies=1
5732      do jo=1,obsdat%numHeader
5733         call obs_write_hdr(obsdat,jo,nobshdrout,irealBodies,nrealBodies)
5734         call obs_write_bdy(obsdat,jo,nobsbdyout)
5735         if (nens > 0) then
5736            call obs_write_hx(obsdat,hx,jo,nobshxout)
5737         endif
5738         irealBodies=irealBodies+nrealBodies
5739      enddo
5740      write(nobsdimout,*) obsdat%numHeader
5741      write(nobsdimout,*) irealBodies-1
5742      write(nobsdimout,*) nens
5743
5744      return
5745
5746   end subroutine obs_write
5747
5748
5749   subroutine obs_write_bdy(obsdat,kobs,kulout)
5750      !
5751      !:Purpose: write the data records associated with a
5752      !                 station in unformatted form.
5753      !
5754      !:Arguments:
5755      !           :obsdat: obsSpaceData object
5756      !           :kobs: no. of observation
5757      !           :kulout: unit used for writing 
5758      !
5759      implicit none 
5760
5761      ! Arguments:
5762      type(struct_obs), intent(in) :: obsdat
5763      integer,          intent(in) :: kobs
5764      integer,          intent(in) :: kulout
5765
5766      ! Locals:
5767      integer :: ipnt,idata,j,jdata,k
5768
5769      ipnt  = obs_headElem_i(obsdat, OBS_RLN, kobs) 
5770      idata = obs_headElem_i(obsdat, OBS_NLV, kobs)
5771
5772      ! write the data records
5773      do jdata=ipnt,ipnt+idata-1
5774         write(kulout) &
5775           (obsdat%intBodies%columns(odc_ENKF_bdy_int_column_list(k) &
5776                                    )%value_i(jdata), &
5777                 k=1,size(odc_ENKF_bdy_int_column_list(:))),&
5778           (obsdat%realBodies%columns(odc_ENKF_bdy_real_column_list(j) &
5779                                     )%value_r(jdata), &
5780                 j=1,size(odc_ENKF_bdy_real_column_list(:)))
5781      enddo
5782
5783      return
5784
5785   end subroutine obs_write_bdy
5786
5787
5788   subroutine obs_write_hdr( obsdat, kobs, kulout, irealBodies, nrealBodies )
5789      !
5790      !:Purpose: writing of the header of a station record
5791      !
5792      !:Arguments:
5793      !           :obsdat: obsSpaceData object
5794      !           :kobs: no. of observation
5795      !           :kulout: unit used for output 
5796      !           :irealBodies: location in the sorted realBodies
5797      !           :nrealBodies: number of observations for this header
5798      !
5799      implicit none
5800
5801      ! Arguments:
5802      type(struct_obs), intent(in)  :: obsdat
5803      integer         , intent(in)  :: kobs
5804      integer         , intent(in)  :: kulout
5805      integer         , intent(in)  :: irealBodies
5806      integer         , intent(out) :: nrealBodies
5807
5808      ! Locals:
5809      integer :: i,j,nprocs_mpi,ierr
5810
5811      ! (note that as a part of the writing, the body is being sorted
5812      !  so that the order of the observations in the body array 
5813      !  corresponds with the order of the headers in the header array).
5814      call rpn_comm_size("GRID",nprocs_mpi,ierr)
5815
5816      if(obsdat%mpi_local .and. nprocs_mpi>1) then
5817         call obs_abort('obs_write_hdr() is not equipped to handle the ' // &
5818                        'case, mpi_local=.true.')
5819         return
5820      end if
5821
5822      nrealBodies=obs_headElem_i(obsdat, OBS_NLV, kobs)
5823      ! write the header's content 
5824      write(kulout) irealBodies, &
5825            (obs_headElem_i &
5826                  (obsdat, &
5827                   odc_columnIndexFromActiveIndex(obsdat%intHeaders%odc_flavour,&
5828                                                  i), &
5829                   kobs &
5830                  ), &
5831             i=2,odc_numActiveColumn(obsdat%intHeaders) &
5832            ), &
5833
5834            (obs_headElem_r &
5835                 (obsdat, &
5836                  odc_columnIndexFromActiveIndex(obsdat%realHeaders%odc_flavour,&
5837                                                 j), &
5838                  kobs &
5839                 ), &
5840             j=1,odc_numActiveColumn(obsdat%realHeaders) &
5841            ), &
5842
5843            obsdat%cstnid(kobs), &
5844            obsdat%cfamily(kobs)
5845
5846      return
5847
5848   end subroutine obs_write_hdr
5849
5850
5851   subroutine obs_write_hx(obsdat,hx,kobs,kulout)
5852      !
5853      !:Purpose: write the interpolated values associated with a
5854      !                 station in unformatted form.
5855      !
5856      !:Arguments:
5857      !           :obsdat: obsSpaceData object
5858      !           :hx: interpolated values    
5859      !           :kobs: no. of station 
5860      !           :kulout: unit used for writing 
5861      !
5862      implicit none 
5863
5864      ! Arguments:
5865      type(struct_obs), intent(in) :: obsdat
5866      real(8),          intent(in) :: hx(:,:)
5867      integer,          intent(in) :: kobs
5868      integer,          intent(in) :: kulout
5869
5870      ! Locals:
5871      integer :: ipnt,idata,iens,jdata,nens
5872
5873      nens = size(hx,1)
5874
5875      ipnt  = obs_headElem_i(obsdat, OBS_RLN, kobs) 
5876      idata = obs_headElem_i(obsdat, OBS_NLV, kobs)
5877
5878      ! write the data records
5879      do jdata=ipnt,ipnt+idata-1
5880         write(kulout) (hx(iens,jdata),iens=1,nens)
5881      enddo
5882
5883      return
5884
5885   end subroutine obs_write_hx
5886
5887
5888   function obs_famExist( obsdat, family, localMPI_opt )
5889     !
5890     !:Purpose: Check if any observations of a particular family are present
5891     !          in obsdat. Returned result will be the MPI local value if
5892     !          the optional argument local_mpi is set to .true., will be
5893     !          the MPI global value otherwise.
5894     !
5895     !:Arguments:
5896     !           :obsdat: ObsSpaceData structure
5897     !           :family: Obs family
5898     !           :localMPI_opt: return MPI local result; optional, default is .false.
5899     !
5900     implicit none 
5901
5902     ! Arguments:
5903     type(struct_obs),  intent(in) :: obsdat
5904     character(len=2),  intent(in) :: family
5905     logical, optional, intent(in) :: localMPI_opt
5906     ! Result:
5907     logical                                :: obs_famExist  ! Logical indicating if 'family' is part of the list
5908
5909     ! Locals:
5910     integer :: index_header,ierr
5911     logical :: famExist,local
5912
5913     if (present(localMPI_opt)) then
5914        local = localMPI_opt
5915     else
5916        local = .false.
5917     end if
5918     
5919     famExist = .false.
5920
5921     ! check if family exists in local MPI process
5922     do index_header = 1,obs_numheader(obsdat)
5923        if (obs_getFamily(obsdat,headerIndex_opt=index_header) == family) then
5924           famExist = .true.
5925           exit
5926        end if
5927     end do
5928
5929     if (local) then
5930        ! return MPI local value
5931        obs_famExist = famExist
5932     else
5933        ! return MPI global value
5934        call rpn_comm_allreduce(famExist,obs_famExist,1,"MPI_LOGICAL","MPI_LOR","GRID",ierr)
5935     end if
5936
5937   end function obs_famExist
5938
5939end module ObsSpaceData_mod