! File: m_local_analysis.F90 ! ! Created: L. Bertino, 2002 ! ! Last modified: 13/04/2010 ! ! Purpose: Local analysis: ! -- calculation of X5 ! -- update of the ensemble fields ! ! Description: This module handles local analysis. ! ! Modifications: ! 20/9/2011 PS: ! - modified update_fields() to allow individual inflation ! for each of `nfields' fields - thanks to Ehouarn Simon ! for spotting this inconsistency ! 25/8/2010 PS: ! - "obs" and "nobs" are now global, stored in m_obs. ! Accordingly, the local observations variables are now ! called "lobs" and "nlobs". Renamed "DD" to "D" and "d" ! to "dy". ! 5/8/2010 PS: ! - moved applying inflation from calc_X5() to ! update_fields() ! - introduced "rfactor" argument to calc_X5() - increases ! obs. error variance for the update of anomalies. ! 29/7/2010 PS: ! - calc_X5(): updated the list of things that needed to be ! done for a point with no local obs ! 6/7/2010 PS: ! - moved ij2nc() to p2nc_writeobs() in m_point2nc.F90 ! 19/6/2010 PS: ! - added X5 to the ij2nc() output ! 25/5/2010 PS: ! - modified to accommodate inflation ! - modified to calculate SRF (spread reduction factor) ! 13/4/2010 Alok Gupta: added open/close/barrier to ensure that ! X5tmp.uf exists before any node tries to access it. ! 8/4/2010 PS: replaced "X4" by "X5"; renamed "localanalysis()" ! to "update_fields()", and "pre_local_analysis()" by ! "calc_X5" ! 1/03/2010 PS: ! - Additional checks for file I/O, as the X4 read/write have ! been demonstrated to fail occasionally. A record is now ! written to X4tmp, then read back and compared until the ! two instances coincide (10 attempts max). ! 11/11/2009 PS: ! - Changed numerics. Now it is always assumed that R is ! diagonal ! - Choice of two chemes: EnKF and DEnKF (for now) ! - X4 calculated either in ens or obs space, depending on ! relation between nobs (# of local observations) and nrens ! - dfs and nobs for each (i,j) are written to enkf_diag.nc ! - if TEST = .true. then local stuff for (I,J) around ! (TEST_I, TEST_J) is dumped to enkf_,.nc ! 6/3/2008 PS: ! - in pre_local_analysis(): ! - introduced quick sort (O(n log n)) of pre-selected ! observations ! - reshuffled the interface ! - replaced output array of flags for local obs by an array ! of indices ! - in local_analysis(): ! -- unified arrays subD and subS ! -- got rid of calls to getD() ! -- used matmul() ! -- introduced localisation function ! -- eliminated X2 and V ! 2007 K. A. Liseter and Ragnhild Blikberg: ! -- MPI parallelisation module m_local_analysis implicit none ! ! public stuff ! real(4), allocatable, public :: X5(:,:,:) real(4), allocatable, public :: X5check(:,:,:) public calc_X5 public update_fields integer, parameter, private :: STRLEN = 512 integer, parameter, private :: MAXITER = 10 integer, private :: nX5pad real(4), allocatable, private :: X5pad(:) private get_npad_la private locfun private get_local_obs private diag2nc private traceprod ! ! available localisation functions ! integer, parameter, private :: LOCFUN_NONE = 1 integer, parameter, private :: LOCFUN_STEP = 2 integer, parameter, private :: LOCFUN_GASPARI_COHN = 3 ! ! used localisation function ! integer, private :: LOCFUN_USED = LOCFUN_GASPARI_COHN ! ! available schemes ! integer, parameter, private :: SCHEME_ENKF = 1 integer, parameter, private :: SCHEME_ETKF = 2 ! not implemented integer, parameter, private :: SCHEME_DENKF = 3 ! ! used scheme ! integer, private :: SCHEME_USED = SCHEME_DENKF contains ! This routine is called for each "field" (horizontal slab) after calcX5(). ! It conducts the multiplication ! E^a(i, :) = E^f(i, :) * X5(i), i = 1,...,n, ! where n - state dimension. ! ! In this package the localisation is conducted only horizontally, so that ! the local (nrens x nrens) ensemble transform matrix X5 is stored for each ! node of the horizontal model grid. In TOPAZ4 this requires ! 880 x 800 x 100 x 100 x 4 = 28 GB of storage on disk for "tmpX5.uf". If the ! fileds were updated on one-by-one basis, this file would have to be read ! (in TOPAZ4) 146 times. Therefore, the fields are updated in bunches of ! `nfields' to reduce the load on disk. ! subroutine update_fields(ni, nj, nrens, nfields, nobs_array, depths, fld, infls) #if defined (QMPI) use qmpi #else use qmpi_fake #endif use mod_measurement implicit none integer, intent(in) :: ni, nj ! size of grid integer, intent(in) :: nrens ! size of ensemble integer, intent(in) :: nfields ! number of 2D fields to be updated integer, dimension(ni, nj), intent(in) :: nobs_array! number of local obs real, dimension(ni, nj), intent(in) :: depths real(4), dimension(ni * nj, nrens * nfields), intent(inout) :: fld ! fields real, dimension(nfields), intent(in) :: infls ! inflation factors real(4), dimension(nrens, nrens) :: X5tmp real(4), dimension(nrens, nrens) :: IM ! inflation matrix integer :: m, i, j, f integer :: irecl, iostatus real(4) :: infl !KAL -- all nodes open for read access to temporary "X5" file inquire(iolength = irecl) X5(1 : nrens, 1 : nrens, 1 : ni), X5pad open(17, file = 'tmpX5.uf', form = 'unformatted', access = 'direct',& status = 'old', recl = irecl) do j = 1, nj ! read X5 from disk read(17, rec = j, iostat = iostatus) X5 if (iostatus /= 0) then print *, 'ERROR: local_analysis(): I/O error at reading X5, iostatus = ', iostatus print *, 'ERROR: at j = ', j stop end if do i = 1, ni ! skip this cell if it is on land if (depths(i,j) <= 0.0) then cycle end if if (nobs_array(i, j) == 0 .and. all(infls == 1.0d0)) then cycle end if X5tmp = X5(:, :, i) do m = 1, nrens if (abs(1.0e0 - sum(X5tmp(:, m))) > 1.0e-5) then print *, 'ERROR: detected inconsistency in X5' print *, 'ERROR: at j = ', j, 'i = ', i print *, 'ERROR: sum(X5(:, ', m, ') = ', sum(X5tmp(:, m)) stop end if enddo ! ensemble transformation, in real(4) ! do f = 1, nfields infl = infls(f) ! conversion to real(4) if (infl /= 1.0) then IM = - (infl - 1.0) / real(nrens, 4) do m = 1, nrens IM(m, m) = IM(m, m) + infl end do fld((j - 1) * ni + i, (f - 1) * nrens + 1 : f * nrens) =& matmul(fld((j - 1) * ni + i, (f - 1) * nrens + 1 : f * nrens),& matmul(X5tmp, IM)) else fld((j - 1) * ni + i, (f - 1) * nrens + 1 : f * nrens) =& matmul(fld((j - 1) * ni + i, (f - 1) * nrens + 1 : f * nrens), X5tmp) end if end do enddo enddo close(17) end subroutine update_fields ! This routine calculates X5 matrices involved in the EnKF analysis, ! E^a(i, :) = E^f(i, :) * X5(i), i = 1,...,n, ! where n - state dimension. ! ! X5(i) is calculated locally (for a given state element i) as ! X5 = I + G s 1^T + T, ! where ! G = S^T (I + S S^T)^{-1} = (I + S^T S)^{-1} S^T [ FM ] Very important. This is a reformulation of the EnKF in the ensemble space. ! T = I - 1/2 G S (DEnKF) Details about this can be found in Sakov et al 2010 in which ! I appended the demonstration ! T = I + G(D - S) (EnKF) ! T = (I + S^T S)^{-1/2} (ETKF) ! S = R^{-1/2} HA^f / sqrt(m - 1) ! s = R^{-1/2} (d - Hx^f) / sqrt(m - 1) ! ! see Sakov et al. (2010): Asynchronous data assimilation with the EnKF, ! Tellus 62A, 24-29. ! subroutine calc_X5(nrens, modlon, modlat, depths, mindx, meandx, dy, S,& radius, rfactor, nlobs_array, ni, nj) #if defined (QMPI) use qmpi #else use qmpi_fake #endif use m_parameters use distribute use mod_measurement use m_obs use m_spherdist use m_random use m_point2nc implicit none ! Input/output arguments integer, intent(in) :: nrens real, dimension(ni, nj), intent(in) :: modlon, modlat real, dimension(ni, nj), intent(in) :: depths real, intent(in) :: mindx ! min grid size real, intent(in) :: meandx ! mean grid size real, dimension(nobs), intent(inout) :: dy ! innovations real, dimension(nobs, nrens), intent(inout) :: S ! HA real, intent(in) :: radius ! localisation radius in km real, intent(in) :: rfactor ! obs. variance multiplier for anomalies integer, dimension(ni, nj), intent(out) :: nlobs_array ! # of local obs ! for each grid cell integer, intent(in) :: ni, nj ! horizontal grid size real, dimension(nrens, nrens) :: X5tmp integer, dimension(nobs) :: lobs ! indices of local observations real, allocatable, dimension(:,:) :: D ! observation perturbations real, allocatable, dimension(:) :: subdy real, allocatable, dimension(:) :: lfactors ! loc. coeffs stored for QC real, allocatable, dimension(:,:) :: subD, subS ! nobs x nrens real, allocatable, dimension(:,:) :: X1 ! nobs x nobs real, allocatable, dimension(:,:) :: G real, allocatable, dimension(:) :: x real :: sqrtm real :: tmp(1) integer :: iostatus integer, dimension(nj):: jmap, jmap_check #if defined (QMPI) integer, allocatable, dimension(:, :) :: mpibuffer_int real(4), allocatable, dimension(:, :) :: mpibuffer_float1, mpibuffer_float2 #endif integer :: lapack_info #if defined (QMPI) integer :: p #endif integer :: nlobs ! # of local obs integer :: m, i, j, o, jj, iter logical :: testthiscell ! test analysis at a certain cell integer :: irecl integer :: nlobs_max ! maximal number of local obs real :: dist, lfactor type(measurement) :: obs0 ! dfs calculation real :: dfs real(4) :: dfs_array(ni, nj) ! srf calculation real :: srf real(4) :: srf_array(ni, nj) ! "partial" dfs real :: pdfs(nuobs) real(4) :: pdfs_array(ni, nj, nuobs) ! "partial" srf real :: psrf(nuobs) real(4) :: psrf_array(ni, nj, nuobs) ! auxiliary variables for dfs and srf calculation, such as ! nobs for different obs types integer :: plobs(nobs, nuobs) integer :: pnlobs(nuobs) integer :: uo if (trim(METHODTAG) == "ENKF") then SCHEME_USED = SCHEME_ENKF elseif (trim(METHODTAG) == "DENKF") then SCHEME_USED = SCHEME_DENKF end if if (master) then if (SCHEME_USED == SCHEME_ENKF) then print *, 'using EnKF analysis scheme' elseif (SCHEME_USED == SCHEME_DENKF) then print *, 'using DEnKF analysis scheme' end if end if if (LOCRAD > 0.0d0) then if (trim(LOCFUNTAG) == "GASPARI-COHN"& .or. trim(LOCFUNTAG) == "GASPARI_COHN") then LOCFUN_USED = LOCFUN_GASPARI_COHN elseif (trim(LOCFUNTAG) == "STEP") then LOCFUN_USED = LOCFUN_STEP elseif (trim(LOCFUNTAG) == "NONE") then LOCFUN_USED = LOCFUN_NONE end if else LOCFUN_USED = LOCFUN_NONE end if if (master) then if (LOCFUN_USED == LOCFUN_GASPARI_COHN) then print *, 'using Gaspari-Cohn localisation' elseif (LOCFUN_USED == LOCFUN_STEP) then print *, 'using STEP localisation' elseif (LOCFUN_USED == LOCFUN_NONE) then print *, 'using NO localisation' end if end if sqrtm = sqrt(real(nrens) - 1.0d0) if (SCHEME_USED == SCHEME_ENKF) then allocate(D(nobs, nrens)) do o = 1, nobs call randn(nrens, D(o, :)) D(o, :) = D(o, :) / (rfactor * sqrtm) end do end if do o = 1, nobs S(o, :) = S(o, :) / (sqrt(obs(o) % var) * sqrtm) dy(o) = dy(o) / (sqrt(obs(o) % var) * sqrtm) end do ! Distribute loops across MPI nodes call distribute_iterations(nj) ! The binary file tmpX5.uf holds (ni x nj) local ensemble transform ! matrices X5, (nrens x nrens) each. They are used for creating the ! analysed ensemble in local_analysis(). In TOPAZ3 tmpX5.uf takes about ! 30GB of the disk space. ! nX5pad = get_npad_la(nrens * nrens, ni) allocate(X5pad(nX5pad)) inquire(iolength = irecl) X5, X5pad if (master) then open(17, file = 'tmpX5.uf', form = 'unformatted', access = 'direct', status = 'unknown', recl = irecl) ! get the necessary space on disk, before starting simultaneous writing ! by different nodes write(17, rec = nj) X5 close(17) end if #if defined (QMPI) call barrier() #endif open(17, file = 'tmpX5.uf', form = 'unformatted', access = 'direct',& status = 'old', recl = irecl) open(31, file = trim(JMAPFNAME), status = 'old', iostat = iostatus) if (iostatus /= 0) then if (master) then print *, 'WARNING: could not open jmap.txt for reading' print *, ' no re-mapping of grid rows performed' end if do j = 1, nj jmap(j) = j end do else read(31, *, iostat = iostatus) jmap if (iostatus /= 0) then print *, 'ERROR reading jmap.txt' stop end if close(31) jmap_check = 1 jmap_check(jmap) = 0 if (sum(jmap_check) /= 0) then print *, 'ERROR: non-zero control sum for jmap =', sum(jmap_check) stop end if end if ! main cycle (over horizontal grid cells) ! dfs_array = 0.0 pdfs_array = 0.0 srf_array = 0.0 psrf_array = 0.0 nlobs_array = 0 do jj = my_first_iteration, my_last_iteration j = jmap(jj) print *, 'calc_X5(): jj =', jj, 'j =', j do i = 1, ni ! data dumping flag testthiscell = p2nc_testthiscell(i, j) if (testthiscell) then print *, 'testthiscell: depth(,', i, ',', j, ') =', depths(i, j) end if if (depths(i, j) > 0.0d0) then nlobs = 0 ! no upper limit on the number of local observations call get_local_obs(i, j, radius * 1000.0, modlon, modlat,& mindx, ni, nj, nlobs, lobs) nlobs_array(i, j) = nlobs else nlobs = 0 end if if (testthiscell) then print *, 'testthiscell: nlobs(,', i, ',', j, ') =', nlobs end if if (nlobs == 0) then ! just in case X5(:, :, i) = 0.0 X5tmp = 0.0d0 do m = 1, nrens X5(m, m, i) = 1.0 X5tmp(m, m) = 1.0d0 enddo if (testthiscell) then call p2nc_writeobs(i, j, nlobs, nrens, X5tmp, modlon(i, j),& modlat(i, j), depths(i, j)) end if dfs_array(i, j) = 0.0 pdfs_array(i, j, :) = 0.0 srf_array(i, j) = 0.0 psrf_array(i, j, :) = 0.0 cycle end if if (nlobs < 0) then ! an extra check on the C-Fortran interface print *, 'ERROR: nlobs =', nlobs, ' for i, j =', i, j call stop_mpi() end if ! Allocate local arrays if (SCHEME_USED == SCHEME_ENKF) then allocate(subD(nlobs, nrens)) end if allocate(subdy(nlobs)) allocate(lfactors(nlobs)) allocate(subS(nlobs, nrens)) ! ( BTW subS1 = subS / sqrt(rfactor) ) allocate(G(nrens, nlobs)) if (nlobs < nrens) then allocate(X1(nlobs, nlobs)) else allocate(X1(nrens, nrens)) end if if (SCHEME_USED == SCHEME_ENKF) then subD = D(lobs(1 : nlobs), :) end if subS = S(lobs(1 : nlobs), :) subdy = dy(lobs(1 : nlobs)) ! taper ensemble observation anomalies and innovations ! if (LOCFUN_USED /= LOCFUN_NONE) then do o = 1, nlobs obs0 = obs(lobs(o)) dist = spherdist(modlon(i, j), modlat(i, j),& obs0 % lon, obs0 % lat) lfactor = locfun(dist / radius / 1000.0) subS(o, :) = subS(o, :) * lfactor subdy(o) = subdy(o) * lfactor lfactors(o) = lfactor if (SCHEME_USED == SCHEME_ENKF) then subD(o, :) = subD(o, :) * lfactor end if end do else lfactors = 1 end if ! first iteration - with rfactor = 1, for the update of the mean ! secons iteration - with the specified rfactorm for the update of ! the anomalies ! do iter = 1,2 if (iter == 2) then if (rfactor == 1.0d0) then go to 10 end if subS = subS / sqrt(rfactor) end if if (nlobs < nrens) then ! use observation space ! Construct matrix (S * S' + I) - to be inverted ! X1 = matmul(subS, transpose(subS)) do o = 1, nlobs X1(o, o) = X1(o, o) + 1.0d0 end do ! Inversion via Cholesky decomposition, done in two stages. ! call dpotrf('U', nlobs, X1, nlobs, lapack_info) if (lapack_info /= 0) then print *, ' ERROR: m_local_analysis(): LAPACK error in dpotrf: errno = '& , lapack_info, 'i, j =', i, j call stop_mpi endif call dpotri('U', nlobs, X1, nlobs, lapack_info) if (lapack_info /= 0) then print *, ' ERROR: m_local_analysis(): LAPACK error in dpotri: errno = '& , lapack_info, 'i, j =', i, j call stop_mpi endif ! fill the lower triangular part of (symmetric) X1 ! do o = 2, nlobs X1(o, 1 : o - 1) = X1(1 : o - 1, o) end do G = matmul(transpose(subS), X1) else ! nlobs >= nrens: use ensemble space X1 = matmul(transpose(subS), subS) do m = 1, nrens X1(m, m) = X1(m, m) + 1.0d0 end do ! Inversion ! call dpotrf('U', nrens, X1, nrens, lapack_info) if (lapack_info /= 0) then print *, ' ERROR: m_local_analysis(): LAPACK error in dpotrf: errno = '& , lapack_info, 'i, j =', i, j call stop_mpi endif call dpotri('U', nrens, X1, nrens, lapack_info) if (lapack_info /= 0) then print *, ' ERROR: m_local_analysis(): LAPACK error in dpotri: errno = '& , lapack_info, 'i, j =', i, j call stop_mpi endif do m = 2, nrens X1(m, 1 : m - 1) = X1(1 : m - 1, m) end do G = matmul(X1, transpose(subS)) end if if (iter == 1) then do m = 1, nrens X5tmp(m, :) = sum(G(m, :) * subdy(:)) end do end if 10 continue ! calculate DFS at iteration 1, SRF at iteration 2 ! if (iter == 1) then dfs = traceprod(G, subS, nrens, nlobs) dfs_array(i, j) = real(dfs, 4) pnlobs = 0 do uo = 1, nuobs do o = 1, nlobs if (lobs(o) >= uobs_begin(uo) .and.& lobs(o) <= uobs_end(uo)) then pnlobs(uo) = pnlobs(uo) + 1 plobs(pnlobs(uo), uo) = o end if end do end do pdfs = 0.0d0 psrf = 0.0d0 do uo = 1, nuobs if (pnlobs(uo) > 0) then pdfs(uo) = traceprod(G(:, plobs(1 : pnlobs(uo), uo)),& subS(plobs(1 : pnlobs(uo), uo), :), nrens, pnlobs(uo)) end if pdfs_array(i, j, uo) = real(pdfs(uo), 4) end do else if (dfs /= 0.0d0) then srf = sqrt(traceprod(subS, transpose(subS), nlobs, nrens)& / traceprod(G, subS, nrens, nlobs)) - 1.0d0 else srf = 0.0d0 end if srf_array(i, j) = real(srf, 4) do uo = 1, nuobs if (pnlobs(uo) > 0) then if (pdfs(uo) /= 0.0d0) then psrf(uo) = sqrt(& traceprod(subS(plobs(1 : pnlobs(uo), uo), :),& transpose(subS(plobs(1 : pnlobs(uo), uo), :)),& pnlobs(uo), nrens) /& traceprod(G(:, plobs(1 : pnlobs(uo), uo)),& subS(plobs(1 : pnlobs(uo), uo), :),& nrens, pnlobs(uo))) - 1.0d0 else psrf(uo) = 0.0d0 end if end if psrf_array(i, j, uo) = real(psrf(uo), 4) end do end if end do ! iter if (SCHEME_USED == SCHEME_ENKF) then X5tmp = X5tmp + matmul(G, subD - subS) elseif (SCHEME_USED == SCHEME_DENKF) then X5tmp = X5tmp - 0.5d0 * matmul(G, subS) end if do m = 1, nrens X5tmp(m, m) = X5tmp(m, m) + 1.0d0 enddo if (testthiscell) then ! ensemble mean allocate(x(nlobs)) do o = 1, nlobs x(o) = obs(lobs(o)) % d - dy(lobs(o)) * sqrtm * sqrt(obs(lobs(o)) % var) end do tmp(1) = rfactor call p2nc_writeobs(i, j, nlobs, nrens, X5tmp, modlon(i, j),& modlat(i, j), depths(i, j), tmp(1), lobs(1 : nlobs), & obs(lobs(1 : nlobs)), x, subS, subdy, lfactors) deallocate(x) end if ! Put X5tmp into the final X5 matrix - to be written to a file ! X5(:, :, i) = real(X5tmp, 4) deallocate(subS, subdy, lfactors, X1, G) if (SCHEME_USED == SCHEME_ENKF) then deallocate(subD) end if end do ! i = 1, ni ! Write one "stripe" of the temporary matrix X5 to disk iter = 0 do while (.true.) iter = iter + 1 write(17, rec = j, iostat = iostatus) X5 if (iostatus /= 0) then print *, 'ERROR: calc_X5(): I/O error at writing X5, iostatus = ',& iostatus print *, 'ERROR: at model line j =', j, ' counter jj = ', jj, 'iter =', iter if (iter < MAXITER) then cycle else print *, 'ERROR: max number of iterations reached, STOP' stop end if end if read(17, rec = j, iostat = iostatus) X5check if (iostatus /= 0) then print *, 'ERROR: calc_X5(): I/O error at reading X5, iostatus = ',& iostatus print *, 'ERROR: at j = ', j, ' jj = ', jj, 'iter =', iter if (iter < MAXITER) then cycle else print *, 'ERROR: max number of iterations reached, STOP' stop end if end if if (abs(maxval(X5 - X5check)) > 1.0e-6) then print *, 'ERROR: calc_X5(): inconsistency between written/read X5' print *, 'ERROR: j = ', j, ' jj = ', jj, 'iter =', iter,& ' maxval(X5 - X5check) =', maxval(X5 - X5check) if (iter < MAXITER) then cycle else print *, 'ERROR: max number of iterations reached, STOP' stop end if end if exit ! OK end do print *, 'FINISHED j =', j, ' jj =', jj end do ! j = my_first_iteration, my_last_iteration close(17) ! X5 file if (SCHEME_USED == SCHEME_ENKF) then deallocate(D) end if #if defined(QMPI) if (.not. master) then ! broadcast nlobs and dfs arrays to master call send(nlobs_array(:, jmap(my_first_iteration : my_last_iteration)), 0, 0) call send(dfs_array(:, jmap(my_first_iteration : my_last_iteration)), 0, 1) call send(srf_array(:, jmap(my_first_iteration : my_last_iteration)), 0, 1) allocate(mpibuffer_float1(ni, my_last_iteration - my_first_iteration + 1)) allocate(mpibuffer_float2(ni, my_last_iteration - my_first_iteration + 1)) do uo = 1, nuobs mpibuffer_float1 = pdfs_array(:, jmap(my_first_iteration : my_last_iteration), uo) call send(mpibuffer_float1, 0, uo + 1) mpibuffer_float2 = psrf_array(:, jmap(my_first_iteration : my_last_iteration), uo) call send(mpibuffer_float2, 0, uo + 1) end do deallocate(mpibuffer_float1) deallocate(mpibuffer_float2) else ! receive nlobs and dfs arrays do p = 2, qmpi_num_proc ! ! PS: Ideally, it would be nice to be able to use a simple code like: ! ! call receive(nlobs_array(& ! jmap(first_iteration(p) : last_iteration(p))), p - 1) ! ! but this seems not to work, at least with the PGI compiler. ! Perhaps, this is too much to expect from a call to a C function... ! The good news is that using a temporal array works fine. ! allocate(mpibuffer_int(ni, last_iteration(p) - first_iteration(p) + 1)) call receive(mpibuffer_int, p - 1, 0) nlobs_array(:, jmap(first_iteration(p) : last_iteration(p))) = mpibuffer_int deallocate(mpibuffer_int) allocate(mpibuffer_float1(ni, last_iteration(p) - first_iteration(p) + 1)) call receive(mpibuffer_float1, p - 1, 1) dfs_array(:, jmap(first_iteration(p) : last_iteration(p))) = mpibuffer_float1 allocate(mpibuffer_float2(ni, last_iteration(p) - first_iteration(p) + 1)) call receive(mpibuffer_float2, p - 1, 1) srf_array(:, jmap(first_iteration(p) : last_iteration(p))) = mpibuffer_float2 do uo = 1, nuobs call receive(mpibuffer_float1, p - 1, uo + 1) pdfs_array(:, jmap(first_iteration(p) : last_iteration(p)), uo) = mpibuffer_float1 call receive(mpibuffer_float2, p - 1, uo + 1) psrf_array(:, jmap(first_iteration(p) : last_iteration(p)), uo) = mpibuffer_float2 end do deallocate(mpibuffer_float1) deallocate(mpibuffer_float2) enddo endif ! broadcast nlobs array call broadcast(nlobs_array) #endif if (master) then nlobs_max = maxval(nlobs_array) print *, 'maximal # of local obs =', nlobs_max,& ' reached for', count(nlobs_array == nlobs_max), 'grid cells' print *, 'average #(*) of local obs =', sum(nlobs_array(:, 1 : nj)) / real(count(nlobs_array(:, 1 : nj) > 0)) print *, ' * over cells with non-zero number of local obs only' print *, 'localisation function of type', LOCFUN_USED, 'has been used' print *, 'analysis conducted in obs space in', count(nlobs_array(:, 1 : nj) > 0 .and. nlobs_array(:, 1 : nj) < nrens),& 'cells' print *, 'analysis conducted in ens space in', count(nlobs_array(:, 1 : nj) >= nrens),& 'cells' print *, 'maximal DFS =', maxval(dfs_array) print *, 'average(*) DFS =', sum(dfs_array) / real(count(dfs_array > 0)) print *, ' * over cells with non-zero number of local obs only' print *, '# of cells with DFS > N / 2 =', count(dfs_array > real(nrens / 2, 4)) call diag2nc(ni, nj, modlon, modlat, nlobs_array, dfs_array, pdfs_array,& srf_array, psrf_array) end if end subroutine calc_X5 integer function get_npad_la(ni, nj) integer, intent(in) :: ni, nj get_npad_la = 4096 - mod(ni * nj, 4096) get_npad_la = mod(get_npad_la, 4096) end function get_npad_la real function locfun(x) real, intent(in) :: x real :: xx, xx2, xx3 select case(LOCFUN_USED) case (LOCFUN_NONE) locfun = 1.0 case (LOCFUN_STEP) if (x > 1.0) then locfun = 0.0 else locfun = 1.0 end if case (LOCFUN_GASPARI_COHN) if (x > 1.0) then locfun = 0.0 else xx = x * 2.0 xx2 = xx * xx xx3 = xx2 * xx if (xx < 1.0) then locfun = 1.0 + xx2 * (- xx3 / 4.0 + xx2 / 2.0)& + xx3 * (5.0 / 8.) - xx2 * (5.0 / 3.0) else locfun = xx2 * (xx3 / 12.0 - xx2 / 2.0)& + xx3 * (5.0 / 8.0) + xx2 * (5.0 / 3.0)& - xx * 5.0 + 4.0 - (2.0 / 3.0) / xx end if locfun = max(locfun, 0.0) end if case default print *, 'ERROR: m_local_analysis.F90: locfun(): LOCFUN_USED =', LOCFUN_USED, 'is unknown' stop end select end function locfun ! - Sort observations by their distance to the given grid point (i, j). ! - Identify observations within a given radius `rmax'. ! - Select `nlobs' nearest observations; update `nlobs' if there are not ! enough observations within the radius. ! ! Note that because all observations are parsed for each 2D grid point, this ! subroutine may become a bottleneck if the total number of observations ! grows substantially from the current point... If this happens, we may ! consider putting all observations in a K-D tree like in Szyonykh et. al ! (2008), A local ensemble transform Kalman filter data assimilation system ! for the NCEP global model (2008). Tellus 60A, 113-130. ! subroutine get_local_obs(i, j, rmax, modlon, modlat, mindx,& ni, nj, nlobs, lobs) use mod_measurement use m_obs use m_spherdist implicit none integer, intent(in) :: i, j real, intent(in) :: rmax ! maximal allowed distance real, intent(in) :: modlon(ni, nj) real, intent(in) :: modlat(ni, nj) real, intent(in) :: mindx integer, intent(in) :: ni, nj integer, intent(inout) :: nlobs ! input : max allowed # of local obs ! output: actual # of local obs for this ! point integer, intent(out) :: lobs(nobs) ! indices of local observations integer :: ngood integer :: sorted(nobs) real :: dist(nobs) integer :: o real :: rmax2 lobs = 0 ngood = 0 rmax2 = (rmax / mindx) ** 2 do o = 1, nobs if ((obs(o) % ipiv - i) ** 2 + (obs(o) % jpiv - j) ** 2 > rmax2) then cycle end if dist(o) = spherdist(obs(o) % lon, obs(o) % lat, modlon(i, j), modlat(i, j)) if (dist(o) <= rmax) then ngood = ngood + 1 lobs(ngood) = o end if end do if (nlobs <= 0 .or. nlobs >= ngood) then ! ! use all observations within localisation support radius ! nlobs = ngood else ! ! use `nlobs' closest observations ! call order(dble(nobs), dist, dble(ngood), lobs, sorted) lobs(1 : nlobs) = sorted(1 : nlobs) end if end subroutine get_local_obs ! This subroutine writes (1) the number of local observations, (2) ! the number of degrees of freedom of signal (DFS), and (3) spread reduction ! factor (SRF) to file "enkf_diag.nc" ! subroutine diag2nc(ni, nj, lon, lat, nlobs_array, dfs_array, pdfs_array, & srf_array, psrf_array) use mod_measurement use m_obs use nfw_mod implicit none integer, intent(in) :: ni integer, intent(in) :: nj real, intent(in) :: lon(ni, nj) real, intent(in) :: lat(ni, nj) integer, intent(in) :: nlobs_array(ni, nj) real(4), intent(in) :: dfs_array(ni, nj) real(4), intent(in) :: pdfs_array(ni, nj, nuobs) real(4), intent(in) :: srf_array(ni, nj) real(4), intent(in) :: psrf_array(ni, nj, nuobs) character(STRLEN) :: fname character(STRLEN) :: varname integer :: ncid integer :: dimids(2) integer :: lon_id, lat_id, nlobs_id, dfs_id, pdfs_id(nuobs), srf_id,& psrf_id(nuobs) integer :: uo fname = 'enkf_diag.nc' call nfw_create(fname, nf_clobber, ncid) call nfw_def_dim(fname, ncid, 'i', ni, dimids(1)) call nfw_def_dim(fname, ncid, 'j', nj, dimids(2)) call nfw_def_var(fname, ncid, 'lon', nf_float, 2, dimids, lon_id) call nfw_def_var(fname, ncid, 'lat', nf_float, 2, dimids, lat_id) call nfw_def_var(fname, ncid, 'nobs', nf_int, 2, dimids, nlobs_id) call nfw_def_var(fname, ncid, 'dfs', nf_float, 2, dimids, dfs_id) do uo = 1, nuobs write(varname, '(a, a)') 'dfs_', trim(unique_obs(uo)) call nfw_def_var(fname, ncid, trim(varname), nf_float, 2, dimids, pdfs_id(uo)) end do call nfw_def_var(fname, ncid, 'srf', nf_float, 2, dimids, srf_id) do uo = 1, nuobs write(varname, '(a, a)') 'srf_', trim(unique_obs(uo)) call nfw_def_var(fname, ncid, trim(varname), nf_float, 2, dimids, psrf_id(uo)) end do call nfw_enddef(fname, ncid) call nfw_put_var_double(fname, ncid, lon_id, lon) call nfw_put_var_double(fname, ncid, lat_id, lat) call nfw_put_var_int(fname, ncid, nlobs_id, nlobs_array) call nfw_put_var_real(fname, ncid, dfs_id, dfs_array) call nfw_put_var_real(fname, ncid, srf_id, srf_array) do uo = 1, nuobs call nfw_put_var_real(fname, ncid, pdfs_id(uo), pdfs_array(:, :, uo)) call nfw_put_var_real(fname, ncid, psrf_id(uo), psrf_array(:, :, uo)) end do call nfw_close(fname, ncid) end subroutine diag2nc ! Calculates the trace of a product of two matrices. (Does not calculate ! the off-diagonal elements in the process.) ! real function traceprod(A, B, n, m) real, intent(in) :: A(n, m), B(m, n) integer, intent(in) :: n, m integer :: i traceprod = 0.0d0 do i = 1, n traceprod = traceprod + sum(A(i, :) * B(:, i)) end do end function traceprod end module m_local_analysis