123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991 |
- ! 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_<I>,<J>.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
|