sstBias_mod sourceΒΆ

  1module sstBias_mod
  2  ! MODULE sstBias_mod (prefix='sstb' category='1. High-level functionality')
  3  !
  4  !:Purpose: Compute SST satellite data bias estimation and correction.
  5  !
  6  use obsSpaceData_mod  
  7  use horizontalCoord_mod
  8  use verticalCoord_mod
  9  use timeCoord_mod
 10  use kdTree2_mod
 11  use codePrecision_mod
 12  use mathPhysConstants_mod
 13  use utilities_mod
 14  use midasMpi_mod
 15  use codtyp_mod
 16  use gridStateVector_mod
 17  use gridStateVectorFileIO_mod
 18  use oceanMask_mod
 19  use localizationFunction_mod
 20  use columnData_mod
 21  use statetocolumn_mod 
 22   
 23  implicit none
 24  save
 25  private
 26
 27  ! public subroutines
 28  public :: sstb_computeBias, sstb_applySatelliteSSTBiasCorrection
 29  
 30  ! external 
 31  integer, external  :: fnom, fclos
 32  
 33  ! mpi topology
 34  integer            :: myLatBeg, myLatEnd
 35  integer            :: myLonBeg, myLonEnd
 36  integer            :: latPerPE, latPerPEmax, lonPerPE, lonPerPEmax
 37
 38  integer, parameter :: maxNumberSensors = 10
 39
 40  ! namelist variables
 41  real(8)            :: searchRadius                 ! horizontal search radius, in km, for obs gridding
 42  real(4)            :: maxBias                      ! max acceptable difference (insitu - satellite)
 43  real(4)            :: iceFractionThreshold         ! consider no ice condition below this threshold
 44  integer            :: numberPointsBG               ! parameter, number of matchups of the background bias estimation
 45  character(len=20)  :: timeInterpType_nl            ! 'NEAREST' or 'LINEAR'
 46  integer            :: numObsBatches                ! number of batches for calling interp setup
 47  integer            :: numberSensors                ! MUST NOT BE INCLUDED IN NAMELIST!
 48  character(len=10)  :: sensorList(maxNumberSensors) ! list of sensors
 49  logical            :: saveAuxFields                ! to store or not auxiliary fields: nobs and weight        
 50  real(4)            :: weightMin                    ! minimum value of weight for the current day bias
 51  real(4)            :: weightMax                    ! maximum value of weight for the current day bias
 52  real(4)            :: bgTermZeroBias               ! background term of zero bias estimate
 53
 54  contains
 55
 56  !--------------------------------------------------------------------------
 57  ! sstb_computeBias
 58  !--------------------------------------------------------------------------
 59  subroutine sstb_computeBias(obsData, hco, vco)
 60    !
 61    !:Purpose: compute bias for SST satellite data with respect to insitu data 
 62    !  
 63    implicit none
 64    
 65    ! Arguments: 
 66    type(struct_obs), intent(inout)          :: obsData              ! obsSpaceData
 67    type(struct_hco), intent(inout), pointer :: hco                  ! horizontal grid structure
 68    type(struct_vco), intent(in)   , pointer :: vco                  ! vertical grid structure
 69      
 70    ! Locals:
 71    integer                     :: sensorIndex, productIndex
 72    real(8)                     :: insituGrid   (hco%ni, hco%nj)
 73    real(8)                     :: satelliteGrid(hco%ni, hco%nj)
 74    logical                     :: mask(hco%ni, hco%nj), openWater(hco%ni, hco%nj) 
 75    type(struct_ocm)            :: oceanMask
 76    integer                     :: numberOpenWaterPoints, lonIndex, latIndex
 77    integer                     :: nobsFoundInsituGlob, nobsFoundInsituLoc
 78    integer                     :: nobsFoundSatGlob, nobsFoundSatLoc
 79    type(struct_gsv)            :: stateVector_ice
 80    real(4), pointer            :: seaice_ptr(:,:,:)
 81    integer         , parameter :: numberProducts = 2  ! day and night
 82    character(len=*), parameter :: listProducts(numberProducts)= (/'day', 'night'/)
 83
 84    write(*,*) 'sstb_computeBias: Starting...'
 85    write(*,*) 'sstb_computeBias: Sea-ice Fraction threshold: ', iceFractionThreshold
 86
 87    ! read the namelist
 88    call readNml()
 89
 90    ! get mpi topology
 91    call mmpi_setup_lonbands(hco%ni, lonPerPE, lonPerPEmax, myLonBeg, myLonEnd)
 92    call mmpi_setup_latbands(hco%nj, latPerPE, latPerPEmax, myLatBeg, myLatEnd)
 93
 94    ! get latest sea-ice analysis
 95    call gsv_allocate(stateVector_ice, 1, hco, vco, dataKind_opt = 4, &
 96                      datestamp_opt = -1, mpi_local_opt = .false.,    &
 97                      varNames_opt = (/'LG'/), hInterpolateDegree_opt ='LINEAR')
 98    call gio_readFromFile(stateVector_ice, './seaice_analysis', ' ','A', &
 99                           unitConversion_opt=.false., containsFullField_opt=.true.)
100    call gsv_getField(stateVector_ice, seaice_ptr)
101    
102    ! Get land mask from analysisgrid file (1=water, 0=land) 
103    ! and the number of open water points
104    call ocm_readMaskFromFile(oceanMask, hco, vco, './analysisgrid')
105
106    numberOpenWaterPoints = 0
107    mask(:, :) = .false.
108    openWater(:, :) = .false.
109    do latIndex = 1, hco%nj
110      do lonIndex = 1, hco%ni
111        if (oceanMask%mask(lonIndex, latIndex, 1)) then
112          mask(lonIndex, latIndex) = .true.
113          if (seaice_ptr(lonIndex, latIndex, 1) <= iceFractionThreshold) then
114            openWater(lonIndex, latIndex) = .true.
115            numberOpenWaterPoints = numberOpenWaterPoints + 1
116          end if
117        end if
118      end do
119    end do
120    call ocm_deallocate(oceanMask)
121    call gsv_deallocate(stateVector_ice)
122    write(*,*) 'sstb_computeBias: computing bias for ', numberOpenWaterPoints, ' open water points'
123    
124    insituGrid(:, :) = MPC_missingValue_R8
125
126    call sstb_getGriddedObs(obsData, insituGrid, nobsFoundInsituGlob, &
127                            nobsFoundInsituLoc, hco, openWater, 'insitu')
128
129    do sensorIndex = 1, numberSensors 
130      do productIndex = 1, numberProducts
131        if (nobsFoundInsituGlob > 0) then
132          satelliteGrid(:, :) = MPC_missingValue_R8
133          call sstb_getGriddedObs(obsData, satelliteGrid(:, :), nobsFoundSatGlob, &
134                                  nobsFoundSatLoc, hco, openWater, sensorList(sensorIndex), &
135                                  dayOrNight_opt = listProducts(productIndex))
136          if (nobsFoundSatGlob > 0) then
137            call sstb_getGriddedBias(satelliteGrid(:, :), insituGrid, nobsFoundSatGlob, &
138                                     nobsFoundSatLoc, hco, vco, mask, openWater, &
139                                     sensorList(sensorIndex), numberOpenWaterPoints, &
140                                     listProducts(productIndex))
141          else
142            write(*,*) 'sstb_computeBias: WARNING: missing ', trim(sensorList(sensorIndex)), ' ', &
143                       trim(listProducts(productIndex)),' data.' 
144            write(*,*) 'Bias estimate will be read from the previous state...'
145            call sstb_getBiasFromPreviousState(hco, vco, &
146                                               sensorList(sensorIndex), &
147                                               listProducts(productIndex)) 
148          end if
149        else
150          write(*,*) 'sstb_computeBias: WARNING: missing insitu data.'
151          write(*,*) 'sstb_computeBias: bias estimates for all sensors will be read from the previous state...'
152          call sstb_getBiasFromPreviousState(hco, vco, &
153                                             sensorList(sensorIndex), &
154                                             listProducts(productIndex)) 
155        end if
156      end do
157    end do
158      
159    write(*,*) 'sstb_computeBias: done.'
160  
161  end subroutine sstb_computeBias
162  
163  !--------------------------------------------------------------------------
164  ! sstb_getGriddedObs
165  !--------------------------------------------------------------------------
166  subroutine sstb_getGriddedObs(obsData, obsGrid, countObsGlob, countObsLoc, &
167                                hco, openWater, instrument, dayOrNight_opt)
168    !
169    !:Purpose: put observations of a given family on the grid
170    !           
171    implicit none
172    
173    ! Arguments: 
174    type(struct_obs), intent(inout)          :: obsData        ! obsSpaceData
175    real(8)         , intent(inout)          :: obsGrid(:,:)   ! observations on the grid
176    integer         , intent(out)            :: countObsGlob   ! global number of data found (all procs)
177    integer         , intent(out)            :: countObsLoc    ! number of data found (current MPI proc)
178    type(struct_hco), intent(in)   , pointer :: hco            ! horizontal grid structure
179    logical         , intent(in)             :: openWater(:,:) ! open water points (.true.)
180    character(len=*), intent(in)             :: instrument     ! name of instrument
181    character(len=*), intent(in), optional   :: dayOrNight_opt ! look for daytime or nighttime obs
182    
183    ! Locals:
184    integer, parameter        :: maxPointsSearch = 200000
185    real(4), parameter        :: solarZenithThreshold = 90.0 ! to distinguish day and night
186    type(kdtree2), pointer    :: tree => null() 
187    real(kdkind), allocatable :: positionArray(:,:)
188    type(kdtree2_result)      :: searchResults(maxPointsSearch)
189    real(kdkind)              :: refPosition(3)
190    real(kdkind)              :: lon_grd, lat_grd
191    real(pre_obsReal)         :: lon_obs, lat_obs
192    integer                   :: lonIndex, latIndex
193    integer                   :: bodyIndex, headerIndex, ierr, headerCounter, codtyp
194    integer                   :: localObsIndex
195    integer                   :: ndataFoundGridLoc(hco%ni, hco%nj)  ! kd-tree output: number of data found within the search radius for every grid point
196    integer                   :: ndataFoundGridGlob(hco%ni, hco%nj) ! to compute mpi_allreduce of ndataFoundGridLoc 
197    real(kdkind)              :: searchRadiusSquared
198    integer, allocatable      :: headerIndexes(:)
199    real(8)                   :: currentObs
200    character(len=50)         :: instrumentString 
201
202    countObsLoc = 0
203    
204    ! count local observations for the given instrument
205    if (trim(instrument) == 'insitu') then
206      instrumentString = trim(instrument)
207      do headerIndex = 1, obs_numheader(obsData)
208        codtyp = obs_headElem_i(obsData, obs_ity, headerIndex)
209        if (codtyp == codtyp_get_codtyp('shipnonauto') .or. &
210             codtyp == codtyp_get_codtyp('drifter')     .or. &
211             codtyp == codtyp_get_codtyp('ashipauto')) then
212          countObsLoc = countObsLoc + 1
213        end if
214      end do
215    else
216      instrumentString = trim(instrument)//' '//dayOrNight_opt
217      do headerIndex = 1, obs_numheader(obsData)
218        if (obs_elem_c(obsData, 'STID' , headerIndex) == trim(instrument)) then
219          if (trim(dayOrNight_opt) == 'day') then
220            if (obs_headElem_r(obsData, obs_sun, headerIndex) <  solarZenithThreshold) &
221              countObsLoc = countObsLoc + 1
222          else if (trim(dayOrNight_opt) == 'night') then
223            if (obs_headElem_r(obsData, obs_sun, headerIndex) >= solarZenithThreshold) &
224              countObsLoc = countObsLoc + 1
225          end if
226        end if
227      end do 
228    end if  
229    write(*,*) ''
230    write(*,"(a, i10, a)") 'sstb_getGriddedObs: found ', countObsLoc, ' '//trim(instrumentString)//' data'
231
232    call rpn_comm_allreduce(countObsLoc, countObsGlob, 1, "mpi_integer", "mpi_sum", "grid", ierr)
233
234    obsGrid(:, :) = 0.0d0
235    ndataFoundGridLoc(:,:) = 0
236    ndataFoundGridGlob(:,:) = 0
237    
238    POSITIVECOUNTOBSLOC: if (countObsLoc > 0) then
239
240      write(*,*)'sstb_getGriddedObs: define kd-tree using data positions...'    
241      allocate(positionArray(3, countObsLoc))
242      allocate(headerIndexes(countObsLoc))
243
244      headerCounter = 0
245      do headerIndex = 1, obs_numheader(obsData)
246        if (trim(instrument) == 'insitu') then
247          codtyp = obs_headElem_i(obsData, obs_ity, headerIndex)
248	  if (codtyp == codtyp_get_codtyp('shipnonauto') .or. &
249              codtyp == codtyp_get_codtyp('drifter')     .or. &
250              codtyp == codtyp_get_codtyp('ashipauto')) then
251            lon_obs = obs_headElem_r(obsData, obs_lon, headerIndex)
252            lat_obs = obs_headElem_r(obsData, obs_lat, headerIndex)
253            headerCounter = headerCounter + 1
254            positionArray(:, headerCounter) = kdtree2_3dPosition(lon_obs, lat_obs)
255            headerIndexes(headerCounter) = headerIndex
256          end if
257        else
258          if (obs_elem_c(obsData, 'STID' , headerIndex) == trim(instrument)) then
259            if (trim(dayOrNight_opt) == 'day') then
260              if (obs_headElem_r(obsData, obs_sun, headerIndex) <  solarZenithThreshold) then
261                lon_obs = obs_headElem_r(obsData, obs_lon, headerIndex)
262                lat_obs = obs_headElem_r(obsData, obs_lat, headerIndex)
263                headerCounter = headerCounter + 1
264                positionArray(:, headerCounter) = kdtree2_3dPosition(lon_obs, lat_obs)
265                headerIndexes(headerCounter) = headerIndex
266              end if
267            else if (trim(dayOrNight_opt) == 'night') then 
268              if (obs_headElem_r(obsData, obs_sun, headerIndex) >= solarZenithThreshold) then
269                lon_obs = obs_headElem_r(obsData, obs_lon, headerIndex)
270                lat_obs = obs_headElem_r(obsData, obs_lat, headerIndex)
271                headerCounter = headerCounter + 1
272                positionArray(:, headerCounter) = kdtree2_3dPosition(lon_obs, lat_obs)
273                headerIndexes(headerCounter) = headerIndex
274              end if
275            end if  
276          end if
277        end if 
278      end do
279    
280      nullify(tree)
281      tree => kdtree2_create(positionArray, sort=.true., rearrange=.true.)
282      
283      ! do the search
284      write(*, '(a,f5.1,a)') 'sstb_getGriddedObs: Collocation radius: ', searchRadius, ' km'
285      searchRadiusSquared = (1.1d0 * searchRadius * 1000.d0)**2 ! convert from km to m2
286
287      write(*,*) 'sstb_getGriddedObs: computing the sum of data values and '//&
288                 'their number within the collocation radius for every grid point...'
289      do latIndex = 1, hco%nj
290        do lonIndex = 1, hco%ni 
291
292          ! compute gridded obs for every open water point
293          OPENWATERPTS: if (openWater(lonIndex, latIndex)) then 
294        
295            lon_grd = real(hco % lon2d_4 (lonIndex, latIndex), 8)
296            lat_grd = real(hco % lat2d_4 (lonIndex, latIndex), 8)
297            refPosition(:) = kdtree2_3dPosition(lon_grd, lat_grd)
298            call kdtree2_r_nearest(tp = tree, qv = refPosition, r2 = searchRadiusSquared, &
299                                   nfound = ndataFoundGridLoc(lonIndex, latIndex), & 
300                                   nalloc = maxPointsSearch, results = searchResults)
301            if (ndataFoundGridLoc(lonIndex, latIndex) > maxPointsSearch) &
302            call utl_abort('sstb_getGriddedObs: the parameter maxPointsSearch must be increased')
303
304            if (ndataFoundGridLoc(lonIndex, latIndex) > 0) then
305              do localObsIndex = 1, ndataFoundGridLoc(lonIndex, latIndex)
306                bodyIndex  = obs_headElem_i(obsData, obs_rln, headerIndexes(searchResults(localObsIndex)%idx))
307                currentObs = obs_bodyElem_r(obsData, obs_var, bodyIndex)
308                obsGrid(lonIndex, latIndex) = obsGrid(lonIndex, latIndex) + currentObs 
309              end do
310	    end if
311	    
312	  end if OPENWATERPTS
313        end do
314      end do
315
316      deallocate(headerIndexes)
317      deallocate(positionArray)
318      
319    end if POSITIVECOUNTOBSLOC
320
321    POSITIVECOUNTOBSGLOB: if (countObsGlob > 0) then
322      write(*,*) 'sstb_getGriddedObs: computing average values for every grid point...'
323      do latIndex = 1, hco%nj
324        do lonIndex = 1, hco%ni 
325          ! summing the values over all mpi tasks and sending them back to all tasks preserving the order of summation
326          call mmpi_allreduce_sumreal8scalar(obsGrid(lonIndex, latIndex), "grid")
327          call rpn_comm_allreduce(ndataFoundGridLoc(lonIndex, latIndex), &
328                                  ndataFoundGridGlob(lonIndex, latIndex), 1, &
329                                  'mpi_integer', 'mpi_sum', 'grid', ierr)
330	  if (ndataFoundGridGlob(lonIndex, latIndex) > 0) then
331	    obsGrid(lonIndex, latIndex) = obsGrid(lonIndex, latIndex) / &
332                                          real(ndataFoundGridGlob(lonIndex, latIndex))
333	  else
334	    obsGrid(lonIndex, latIndex) = MPC_missingValue_R8
335	  end if
336          if (.not.openWater(lonIndex, latIndex)) obsGrid(lonIndex, latIndex) = MPC_missingValue_R8
337        end do
338      end do
339      call rpn_comm_barrier('GRID', ierr)
340      write(*,*) 'sstb_getGriddedObs: gridding for '//trim(instrumentString)//' data completed'
341    end if POSITIVECOUNTOBSGLOB
342
343  end subroutine sstb_getGriddedObs
344
345  !--------------------------------------------------------------------------
346  ! sstb_getGriddedBias
347  !--------------------------------------------------------------------------
348  subroutine sstb_getGriddedBias(satelliteGrid, insituGrid, nobsGlob, nobsLoc, &
349                                 hco, vco,  mask, openWater, sensor, &
350                                 numberOpenWaterPoints, dayOrNight)
351    !
352    !:Purpose: compute the satellite SST data bias estimation field on a grid
353    !           
354    implicit none
355    
356    ! Arguments: 
357    real(8)         , intent(inout)          :: satelliteGrid(:,:)   ! gridded satellite data
358    real(8)         , intent(inout)          :: insituGrid(:,:)      ! gridded insitu data
359    integer         , intent(in)             :: nobsGlob             ! number of data on all procs 
360    integer         , intent(in)             :: nobsLoc              ! number of data on the current MPI proc 
361    type(struct_hco), intent(in)   , pointer :: hco                  ! horizontal grid structure
362    type(struct_vco), intent(in)   , pointer :: vco                  ! vertical grid structure
363    logical         , intent(in)             :: mask(:,:)            ! land-ocean mask
364    logical         , intent(in)             :: openWater(:,:)       ! open water points (.true.)
365    character(len=*), intent(in)             :: sensor               ! current sensor name
366    integer         , intent(in)             :: numberOpenWaterPoints! number of open water points to allocate kd-tree work arrays 
367    character(len=*), intent(in)             :: dayOrNight           ! look for daytime or nighttime obs
368
369    ! Locals:
370    real(4), parameter          :: solarZenithThreshold = 90.0       ! to distinguish day and night
371    type(kdtree2), pointer      :: tree => null() 
372    real(kdkind), allocatable   :: positionArray(:,:)
373    integer, parameter          :: maxPointsSearch = 200000    
374    type(kdtree2_result)        :: searchResults(maxPointsSearch)
375    real(kdkind)                :: refPosition(3)
376    real(kdkind)                :: lon_grd, lat_grd
377    integer                     :: lonIndex, latIndex
378    integer                     :: ierr
379    integer                     :: localIndex, indexCounter, localLonIndex, localLatIndex
380    integer                     :: numPointsFound
381    real(kdkind)                :: searchRadiusSquared
382    type(struct_gsv)            :: stateVector                      ! state vector containing bias estimation field
383    type(struct_gsv)            :: stateVector_searchRadius, stateVector_previous
384    type(struct_gsv)            :: stateVectorNobs, stateVectorWeight    
385    real(4), pointer            :: griddedBias_r4_ptr(:,:,:), searchRadius_ptr(:,:,:)
386    real(4), pointer            :: nobsField_r4_ptr(:,:,:), weightField_r4_ptr(:,:,:)
387    real(4), pointer            :: griddedBias_r4_previous_ptr(:,:,:)
388    integer, allocatable        :: gridPointIndexes(:,:)
389    real(8)                     :: weight, distance, correlation, lengthscale, difference, numberPoints
390    character(len=1)            :: extension
391    character(len=*), parameter :: outputFileName = './satellite_bias.fst'
392    character(len=*), parameter :: outputAuxFileName = './auxOutput.fst' ! auxiliary file name to store nobs^a and weight
393    
394    write(*,*) ''
395    write(*,*) 'sstb_getGriddedBias: computing bias for: '//trim(sensor)//' '//trim(dayOrNight)
396    write(*,*) 'sstb_getGriddedBias: the current processor contains ', nobsLoc, ' data out of ', nobsGlob  
397    
398    if (dayOrNight == 'day') then
399      extension = 'D'
400    else if (dayOrNight == 'night') then
401      extension = 'N'	  
402    else  
403      call utl_abort('sstb_getGriddedBias: wrong extension: '//trim(extension)) 
404    end if  
405    
406    POSITIVEOBSNUMBER: if (nobsLoc > 0) then
407
408      write(*,*) 'sstb_getGriddedBias: compute kd-tree with all the grid points...'
409      allocate(positionArray(3, numberOpenWaterPoints))
410      allocate(gridPointIndexes(2, numberOpenWaterPoints))
411    
412      call lfn_setup('FifthOrder')
413
414      indexCounter = 0
415      do latIndex = 1, hco%nj
416        do lonIndex = 1, hco%ni 
417          if (openWater(lonIndex, latIndex)) then
418            indexCounter = indexCounter + 1
419            lon_grd = real(hco%lon2d_4(lonIndex, latIndex), 8)
420            lat_grd = real(hco%lat2d_4(lonIndex, latIndex), 8)
421            positionArray(:, indexCounter) = kdtree2_3dPosition(lon_grd, lat_grd)
422            gridPointIndexes(1, indexCounter) = lonIndex
423            gridPointIndexes(2, indexCounter) = latIndex
424          end if  
425        end do
426      end do
427    
428      nullify(tree)
429      tree => kdtree2_create(positionArray, sort=.true., rearrange=.true.)
430 
431    end if POSITIVEOBSNUMBER 
432
433    ! get search radius field
434    call gsv_allocate(stateVector_searchRadius, 1, hco, vco, dataKind_opt = 4, &
435                      datestamp_opt = -1, mpi_local_opt = .true., varNames_opt = (/'TM'/))
436    call gio_readFromFile(stateVector_searchRadius, './searchRadius', 'RADIUS','A', &
437                          unitConversion_opt=.false., containsFullField_opt=.true.)
438    call gsv_getField(stateVector_searchRadius, searchRadius_ptr)
439
440    if (saveAuxFields) then
441      ! output nobs state vector
442      call gsv_allocate(stateVectorNobs, 1, hco, vco, dataKind_opt = 4, &
443                        datestamp_opt = tim_getDateStamp(), mpi_local_opt = .true., &
444                        varNames_opt = (/'TM'/))
445      ! pointer for nobs stateVector
446      call gsv_getField(stateVectorNobs, nobsField_r4_ptr)
447      ! output weight state vector
448      call gsv_allocate(stateVectorWeight, 1, hco, vco, dataKind_opt = 4, &
449                        datestamp_opt = tim_getDateStamp(), mpi_local_opt = .true., &
450                        varNames_opt = (/'TM'/))
451      ! pointer for weight stateVector
452      call gsv_getField(stateVectorWeight, weightField_r4_ptr)
453      nobsField_r4_ptr(:,:,1) = 0.0d0
454      weightField_r4_ptr(:,:,1) = 0.0d0
455    end if
456    
457    ! previous bias estimation
458    call gsv_allocate(stateVector_previous, 1, hco, vco, dataKind_opt = 4, &
459                      datestamp_opt = -1, mpi_local_opt = .true., varNames_opt = (/'TM'/))
460    call gio_readFromFile(stateVector_previous, './trlm_01', 'B_'//trim(sensor)//'_'//trim(extension), &
461                          ' ', unitConversion_opt=.false., containsFullField_opt=.true.)
462    call gsv_getField(stateVector_previous, griddedBias_r4_previous_ptr) 
463       
464    ! resulting bias estimation state vector
465    call gsv_allocate(stateVector, 1, hco, vco, dataKind_opt = 4, &
466                      datestamp_opt = tim_getDateStamp(), mpi_local_opt = .true., varNames_opt = (/'TM'/))
467    ! pointer for bias estimation stateVector
468    call gsv_getField(stateVector, griddedBias_r4_ptr)
469    
470    griddedBias_r4_ptr(:,:,1) = 0.0d0
471
472    if (nobsLoc > 0) then
473      write(*,*) 'sstb_getGriddedBias: do the search for '//trim(sensor)//' '//trim(dayOrNight)//'...'
474    else
475      write(*,*) 'sstb_getGriddedBias: no '//trim(sensor)//' '&
476                 //trim(dayOrNight)//' data on the current processor' 
477      write(*,*) 'sstb_getGriddedBias: previous estimation state will be used.'
478    end if
479
480    do latIndex = myLatBeg, myLatEnd
481      do lonIndex = myLonBeg, myLonEnd 
482
483        POSITIVEOBSNUMBER2: if (nobsLoc > 0) then
484    
485	  numberPoints = 0.0d0
486          LANDSEAMASK: if (mask(lonIndex, latIndex)) then
487            lon_grd = real(hco%lon2d_4(lonIndex, latIndex), 8)
488            lat_grd = real(hco%lat2d_4(lonIndex, latIndex), 8)
489            refPosition(:) = kdtree2_3dPosition(lon_grd, lat_grd)
490            ! convert from km to m2
491            searchRadiusSquared = (1.1d0 * searchRadius_ptr(lonIndex, latIndex, 1) * 1000.d0)**2
492            call kdtree2_r_nearest(tp = tree, qv = refPosition, &
493                                   r2 = searchRadiusSquared, &
494                                   nfound = numPointsFound, &
495                                   nalloc = maxPointsSearch, &
496                                   results = searchResults)
497            POSITIVENPTSFOUND: if (numPointsFound > 0) then
498              do localIndex = 1, numPointsFound
499                localLonIndex = gridPointIndexes(1, searchResults(localIndex)%idx)
500                localLatIndex = gridPointIndexes(2, searchResults(localIndex)%idx)
501                difference = satelliteGrid(localLonIndex, localLatIndex) - &
502                             insituGrid(localLonIndex, localLatIndex)
503                if (insituGrid   (localLonIndex, localLatIndex) /= MPC_missingValue_R8 .and. &
504                    satelliteGrid(localLonIndex, localLatIndex) /= MPC_missingValue_R8 .and. &
505                    abs(difference) < maxBias) then
506                  distance = sqrt(searchResults(localIndex)%dis)
507                  lengthscale = 1000.d0 * searchRadius_ptr(lonIndex, latIndex, 1)
508                  correlation = lfn_response(distance, lengthscale)
509                  griddedBias_r4_ptr(lonIndex, latIndex, 1) = griddedBias_r4_ptr(lonIndex, latIndex, 1) + &
510                                                              correlation * difference 
511                  numberPoints = numberPoints + correlation
512                end if
513              end do
514    
515              if (numberPoints > 0.0d0) &
516              griddedBias_r4_ptr(lonIndex, latIndex, 1) = griddedBias_r4_ptr(lonIndex, latIndex, 1) / numberPoints
517            end if POSITIVENPTSFOUND   
518          end if LANDSEAMASK
519
520          weight = numberPoints / (numberPoints + numberPointsBG)
521          if (weight < weightMin) weight = weightMin
522          if (weight > weightMax) weight = weightMax
523       
524	  if (saveAuxFields) then
525            weightField_r4_ptr(lonIndex, latIndex, 1) = weight
526	    nobsField_r4_ptr(lonIndex, latIndex, 1) = numberPoints
527	  end if
528	  
529	  ! computation of the bias:
530          griddedBias_r4_ptr(lonIndex, latIndex, 1) = (1.0d0 - weight) * bgTermZeroBias * &
531                                                      griddedBias_r4_previous_ptr(lonIndex, latIndex, 1) + &
532                                                      weight * griddedBias_r4_ptr(lonIndex, latIndex, 1)
533        else ! no data on the current processor   
534       
535	  ! the bias estimation on the current processor is the estimation from previous state:
536          griddedBias_r4_ptr(lonIndex, latIndex, 1) = griddedBias_r4_previous_ptr(lonIndex, latIndex, 1)
537
538        end if POSITIVEOBSNUMBER2
539      end do
540    end do
541    
542    call rpn_comm_barrier('GRID', ierr)
543    call gio_writeToFile(stateVector, outputFileName, 'B_'//trim(sensor)//'_'//trim(extension))
544
545    if (nobsLoc > 0) then
546      deallocate(gridPointIndexes)
547      deallocate(positionArray)
548    end if
549
550    call gsv_deallocate(stateVector_searchRadius)
551    call gsv_deallocate(stateVector_previous)
552    call gsv_deallocate(stateVector)
553    if (saveAuxFields) then
554      call gio_writeToFile(stateVectorNobs, outputAuxFileName, 'N_'//trim(sensor)//'_'//trim(extension))
555      call gio_writeToFile(stateVectorWeight, outputAuxFileName, 'W_'//trim(sensor)//'_'//trim(extension))
556      call gsv_deallocate(stateVectorNobs)
557      call gsv_deallocate(stateVectorWeight)
558    end if
559    
560    write(*,*) 'sstb_getGriddedBias: completed for: '//trim(sensor)//' '//trim(dayOrNight)
561
562  end subroutine sstb_getGriddedBias
563
564  !--------------------------------------------------------------------------
565  ! sstb_getBiasCorrection
566  !--------------------------------------------------------------------------
567  subroutine sstb_getBiasCorrection(stateVector, column, obsData, hco, sensor, dayOrNight)
568    !
569    !:Purpose: To compute bias correction and put it into obsSpace data. 
570    !          Columns from input field are interpolated to obs location
571    !     
572    implicit none
573    
574    ! Arguments:
575    type(struct_gsv)       , intent(inout)       :: stateVector       ! state vector containing bias estimation field    
576    type(struct_columnData), intent(inout)       :: column            ! column data
577    type(struct_obs)       , intent(inout)       :: obsData           ! obsSpaceData
578    type(struct_hco)       , intent(in), pointer :: hco               ! horizontal grid
579    character(len=*)       , intent(in)          :: sensor            ! current sensor name
580    character(len=*)       , intent(in)          :: dayOrNight        ! look for daytime or nighttime obs
581
582    ! Locals:
583    real(4), parameter :: solarZenithThreshold = 90.0 ! to distinguish day and night
584    integer            :: bodyIndex, headerIndex
585    real(8)            :: currentObs
586
587    write(*,*)
588    write(*,*) 'sstb_getBiasCorrection: computing bias correction for ', trim(sensor), ' ', trim(dayOrNight), 'time ************'
589
590    call s2c_nl(stateVector, obsData, column, hco, timeInterpType = timeInterpType_nl, &
591                moveObsAtPole_opt = .true., numObsBatches_opt = numObsBatches, dealloc_opt = .false.)
592
593    do headerIndex = 1, obs_numheader(obsData)
594      
595      if (obs_elem_c(obsData, 'STID' , headerIndex) == trim(sensor)) then
596        
597        bodyIndex  = obs_headElem_i(obsData, obs_rln, headerIndex)
598	currentObs = obs_bodyElem_r(obsData, obs_var, bodyIndex)
599	
600	if (trim(dayOrNight) == 'day') then
601          if (obs_headElem_r(obsData, obs_sun, headerIndex) <  solarZenithThreshold) then
602            call obs_bodySet_r(obsData, obs_bcor, bodyIndex, col_getElem(column, 1, headerIndex, 'TM'))
603            call obs_bodySet_r(obsData, obs_var , bodyIndex, currentObs - col_getElem(column, 1, headerIndex, 'TM'))
604	  end if  
605	else if (trim(dayOrNight) == 'night') then
606          if (obs_headElem_r(obsData, obs_sun, headerIndex) >= solarZenithThreshold) then
607            call obs_bodySet_r(obsData, obs_bcor, bodyIndex, col_getElem(column, 1, headerIndex, 'TM'))
608            call obs_bodySet_r(obsData, obs_var , bodyIndex, currentObs - col_getElem(column, 1, headerIndex, 'TM'))
609          end if
610	end if
611      end if
612      
613    end do 
614 
615    write(*,*) 'sstb_getBiasCorrection: END'
616
617  end subroutine sstb_getBiasCorrection
618
619  !--------------------------------------------------------------------------
620  ! readNml (private subroutine)
621  !--------------------------------------------------------------------------
622  subroutine readNml()
623    !
624    !:Purpose: Read the namelist `namSSTbiasEstimate`
625    !
626    implicit none
627
628    ! Locals:
629    integer :: ierr, nulnam, sensorIndex
630    
631    namelist /namSSTbiasEstimate/ searchRadius, maxBias, iceFractionThreshold, numberPointsBG, &
632                                  timeInterpType_nl, numObsBatches, numberSensors, sensorList, &
633                                  weightMin, weightMax, saveAuxFields, bgTermZeroBias
634
635    ! Setting default namelist variable values
636    searchRadius = 10.            
637    maxBias = 1.                  
638    iceFractionThreshold   = 0.6 
639    numberSensors = MPC_missingValue_INT
640    numberPointsBG = 0            
641    timeInterpType_nl = 'NEAREST'
642    numObsBatches = 20
643    sensorList(:) = ''
644    weightMin = 0.0
645    weightMax = 1.0
646    saveAuxFields = .False.
647    bgTermZeroBias = 1.0
648    
649    ! Read the namelist
650    nulnam = 0
651    ierr = fnom( nulnam, './flnml', 'FTN+SEQ+R/O', 0 )
652    read(nulnam, nml = namSSTbiasEstimate, iostat = ierr )
653    if (ierr /= 0) call utl_abort('readNml (sstb): Error reading namelist')
654    if (mmpi_myid == 0) write(*, nml = namSSTbiasEstimate )
655    ierr = fclos( nulnam )
656
657    if (numberSensors /= MPC_missingValue_INT) then
658      call utl_abort('readNml (sstb): check namSSTbiasEstimate namelist section: numberSensors should be removed')
659    end if
660
661    numberSensors = 0
662    do sensorIndex = 1, maxNumberSensors
663      if (trim(sensorList(sensorIndex))=='') exit
664      numberSensors = numberSensors + 1
665    end do
666    if (numberSensors == 0) call utl_abort('readNml (sstb): check namSSTbiasEstimate namelist section: empty sensorList')
667
668    if (mmpi_myid == 0) then
669      write(*,*)
670      write(*,*) 'readNml (sstb): sensors to treat: '
671      do sensorIndex = 1, numberSensors
672        write(*,*) 'readNml (sstb): sensor index: ', sensorIndex, ', sensor: ', sensorList( sensorIndex )
673      end do
674      write(*,*) 'readNml (sstb): interpolation type: ', timeInterpType_nl
675      write(*,*) 'readNml (sstb): number obs batches: ', numObsBatches
676      write(*,*) 'readNml (sstb): weight limits for current bias estimate: ', weightMin, weightMax
677    end if
678
679  end subroutine readNml
680
681  !--------------------------------------------------------------------------
682  ! sstb_applySatelliteSSTBiasCorrection
683  !--------------------------------------------------------------------------
684  subroutine sstb_applySatelliteSSTBiasCorrection(obsData, hco, vco, column)
685    !
686    !:Purpose: To apply bias satellite SST data bias correction and put it into obsSpace data. 
687    !          Columns from input field are interpolated to obs location
688    !     
689    implicit none
690    
691    ! Arguments:
692    type(struct_obs)       , intent(inout)          :: obsData ! obsSpaceData
693    type(struct_hco)       , intent(inout), pointer :: hco     ! horizontal grid structure
694    type(struct_vco)       , intent(in)   , pointer :: vco     ! vertical grid structure
695    type(struct_columnData), intent(inout)          :: column  ! column data 
696
697    ! Locals:
698    type(struct_gsv)            :: stateVector
699    integer         , parameter :: numberProducts = 2            ! day and night
700    character(len=*), parameter :: listProducts(numberProducts)= (/'day', 'night'/) ! day and night biases
701    integer                     :: sensorIndex, productIndex
702    character(len=1)            :: extension
703    character(len=*), parameter :: biasFileName = './satellite_bias.fst'
704
705    ! read the namelist
706    call readNml()
707
708    ! allocate state vector for bias estimation field
709    call gsv_allocate(stateVector, 1, hco, vco, dataKind_opt = 4, hInterpolateDegree_opt = 'LINEAR', &
710                      datestamp_opt = -1, mpi_local_opt = .true., varNames_opt = (/'TM'/))
711
712    do sensorIndex = 1, numberSensors 
713      do productIndex = 1, numberProducts
714      
715        if (trim(listProducts(productIndex)) == 'day') then
716          extension = 'D'
717        else if (trim(listProducts(productIndex)) == 'night') then
718          extension = 'N'
719        else
720          call utl_abort('sstb_applySatelliteSSTBiasCorrection: wrong extension: '//trim(extension)) 
721        end if
722	
723        call gio_readFromFile(stateVector, biasFileName, 'B_'//trim(sensorList(sensorIndex))//'_'//trim(extension), &
724                              ' ', unitConversion_opt = .false., containsFullField_opt = .true.)
725        call sstb_getBiasCorrection(stateVector, column, obsData, hco, trim(sensorList(sensorIndex)), &
726                                    trim(listProducts(productIndex)))
727      end do
728    end do
729    				    
730    call gsv_deallocate(stateVector)
731			    
732  end subroutine sstb_applySatelliteSSTBiasCorrection
733
734  !--------------------------------------------------------------------------
735  ! sstb_getBiasFromPreviousState
736  !--------------------------------------------------------------------------
737  subroutine sstb_getBiasFromPreviousState(hco, vco, sensor, dayOrNight)
738    !
739    !:Purpose: to get a satellite SST data bias estimate from the previous state if data is missing.
740    !          or there are no insitu data for the current dateStamp,
741    !          hence, unable to compute bias estimates for the satellite data.
742    !
743    implicit none
744    
745    ! Arguments:
746    type(struct_hco), intent(inout), pointer :: hco        ! horizontal grid structure
747    type(struct_vco), intent(in)   , pointer :: vco        ! vertical grid structure
748    character(len=*), intent(in)             :: sensor     ! sensor name
749    character(len=*), intent(in)             :: dayOrNight ! look for daytime or nighttime bias estimation
750
751    ! Locals:
752    type(struct_gsv)            :: stateVector                  ! state vector containing current  bias estimation field
753    type(struct_gsv)            :: stateVector_previous         ! state vector containing previous bias estimation field
754    real(4), pointer            :: griddedBias_r4_ptr(:, :, :)
755    real(4), pointer            :: griddedBias_r4_previous_ptr(:, :, :)
756    character(len=1)            :: extension
757    character(len=*), parameter :: outputFileName = './satellite_bias.fst'
758
759    write(*,*) ''
760    write(*,*) 'sstb_getBiasFromPreviousState: for ', trim(sensor), ' ', trim(dayOrNight), ' data...'
761
762    if (trim(dayOrNight) == 'day') then
763      extension = 'D'
764    else if (trim(dayOrNight) == 'night') then
765      extension = 'N'	  
766    else  
767      call utl_abort('sstb_getBiasFromPreviousState: wrong extension: '//trim(extension))
768    end if
769
770    ! read previous bias estimation
771    call gsv_allocate(stateVector_previous, 1, hco, vco, dataKind_opt = 4, &
772                       datestamp_opt = -1, mpi_local_opt = .true., varNames_opt = (/'TM'/))
773    call gio_readFromFile(stateVector_previous, './trlm_01', 'B_'//trim(sensor)//'_'//trim(extension), &
774                          ' ', unitConversion_opt=.false., containsFullField_opt=.true.)
775    call gsv_getField(stateVector_previous, griddedBias_r4_previous_ptr) 
776
777    ! resulting bias estimation state vector
778    call gsv_allocate(stateVector, 1, hco, vco, dataKind_opt = 4, &
779                      datestamp_opt = tim_getDateStamp(), mpi_local_opt = .true., varNames_opt = (/'TM'/))
780    ! pointer for bias estimation stateVector
781    call gsv_getField(stateVector, griddedBias_r4_ptr)
782        
783    griddedBias_r4_ptr(:, :, :) = griddedBias_r4_previous_ptr(:, :, :)
784    call gio_writeToFile(stateVector, outputFileName, 'B_'//trim(sensor)//'_'//trim(extension))
785
786    call gsv_deallocate(stateVector)
787    call gsv_deallocate(stateVector_previous)
788
789    write(*,*) 'sstb_getBiasFromPreviousState: done ', trim(sensor), ' ', trim(dayOrNight), ' data.'
790
791  end subroutine sstb_getBiasFromPreviousState
792  
793end module SSTbias_mod