OOPS
gletkf_mod.f90
Go to the documentation of this file.
1 module letkf
2 !$$$ module documentation block
3 !
4 ! module: letkf Update model state variables and
5 ! bias coefficients with the LETKF.
6 !
7 ! prgmmr: ota org: np23 date: 2011-06-01
8 ! updates, optimizations by whitaker
9 ! adaptation to oops: frolov
10 !
11 ! abstract: Updates the model state using the LETKF (Hunt et al 2007,
12 ! Physica D, 112-126). Uses 'gain form' of LETKF algorithm described
13 ! Bishop et al 2017 (https://doi.org/10.1175/MWR-D-17-0102.1).
14 !
15 ! 2020-04-01 (frolov) adopted letkf_core routine from GSI to be used
16 ! inside of the JEDI GETKFSolver.h
17 !
18 ! attributes:
19 ! language: f95
20 !
21 !$$$
22 
23 use iso_c_binding
24 
25 implicit none
26 
27 integer, parameter :: r_double=c_double, r_single=c_float, num_bytes_for_r_single=4
28 integer, parameter :: i_kind=c_int, r_kind=r_single
29 
30 contains
31 
32 subroutine letkf_core(nobsl,hxens,hxens_orig,dep,&
33  wts_ensmean,wts_ensperts,&
34  rdiaginv_loc,nanals,neigv,getkf_inflation,denkf,getkf)
35 !$$$ subprogram documentation block
36 ! . . .
37 ! subprogram: letkf_core
38 !
39 ! prgmmr: whitaker
40 !
41 ! abstract: LETKF core subroutine. Returns analysis weights
42 ! for ensemble mean update and ensemble perturbation update (increments
43 ! represented as a linear combination of prior ensemble perturbations).
44 ! Uses 'gain form' LETKF, which works when ensemble used to estimate
45 ! covariances not the same as ensemble being updated. If getkf=false
46 ! (no modulated ensemble model-space localization), then the
47 ! traditional form of the LETKF analysis weights for ensemble
48 ! perturbations are returned (which represents posterior
49 ! ensemble perturbations, not analysis increments, as a linear
50 ! combination of prior ensemble perturbations).
51 !
52 ! program history log:
53 ! 2011-06-03 ota: created from miyoshi's LETKF core subroutine
54 ! 2014-06-20 whitaker: Use LAPACK routine dsyev for eigenanalysis.
55 ! 2016-02-01 whitaker: Use LAPACK dsyevr for eigenanalysis (faster
56 ! than dsyev in most cases).
57 ! 2018-07-01 whitaker: implement gain form of LETKF from Bishop et al 2017
58 ! (https://doi.org/10.1175/MWR-D-17-0102.1), allow for use of modulated
59 ! ensemble vert localization (ensemble used to estimate posterior covariance
60 ! in ensemble space different than ensemble being updated). Add denkf,
61 ! getkf,getkf_inflation options.
62 !
63 ! input argument list:
64 ! nobsl - number of observations in the local patch
65 ! hxens - on input: first-guess modulated ensemble in observation space (Yb)
66 ! on output: overwritten with Yb * R**-1.
67 ! hxens_orig - first-guess original ensembles in observation space.
68 ! not used if getkf=false.
69 ! dep - nobsl observation departures (y-Hxmean)
70 ! rdiaginv_loc - inverse of diagonal element of observation error covariance
71 ! localized (inflated) based on distance to analysis point
72 ! nanals - number of ensemble members (1st dimension of hxens)
73 ! neigv - for modulated ensemble model-space localization, number
74 ! of eigenvectors of vertical localization (1 if not using
75 ! model space localization). 1st dimension of hxens_orig is
76 ! nanals/neigv.
77 ! getkf_inflation - if true (and getkf=T,denkf=F),
78 ! return posterior covariance matrix in
79 ! needed to compute getkf inflation (eqn 30 in Bishop et al
80 ! 2017).
81 ! denkf - if true, use DEnKF approximation (implies getkf=T)
82 ! See Sakov and Oke 2008 https://doi.org/10.1111/j.1600-0870.2007.00299.x
83 ! getkf - if true, use gain formulation
84 !
85 ! output argument list:
86 !
87 ! wts_ensmean - Factor used to compute ens mean analysis increment
88 ! by pre-multiplying with
89 ! model space ensemble perts. In notation from Bishop et al 2017,
90 ! wts_ensmean = C (Gamma + I)**-1 C^T (HZ)^ T R**-1/2 (y - Hxmean)
91 ! where HZ^T = Yb*R**-1/2 (YbRinvsqrt),
92 ! C are eigenvectors of (HZ)^T HZ and Gamma are eigenvalues
93 ! Has dimension (nanals) - increment is weighted average of ens
94 ! perts, wts_ensmean are weights.
95 !
96 ! wts_ensperts - if getkf=T same as above, but for computing increments to
97 ! ensemble perturbations. From Bishop et al 2017 eqn 29
98 ! wts_ensperts = -C [ (I - (Gamma+I)**-1/2)*Gamma**-1 ] C^T (HZ)^T R**-1/2 Hxprime
99 ! Has dimension (nanals,nanals/neigv), analysis weights for each
100 ! member. Hxprime (hxens_orig) is the original, unmodulated
101 ! ensemble in observation space, HZ is the modulated ensemble in
102 ! ob space times R**-1/2. If denkf=T, wts_ensperts is approximated
103 ! as wts_ensperts = -0.5*C (Gamma + I)**-1 C^T (HZ)^ T R**-1/2 Hxprime
104 ! If getkf=F and denkf=F, then the original LETKF formulation is used with
105 ! wts_ensperts =
106 ! C (Gamma + I)**-1/2 C^T (square root of analysis error cov in ensemble space)
107 ! and these weights are applied to transform the background ensemble into an
108 ! analysis ensemble. Note that modulated ensemble vertical localization
109 ! requires the gain form (getkf=T and/or denkf=T) since this form of the weights
110 ! requires that the background ensemble used to compute covariances is
111 ! the same ensemble being updated.
112 !
113 ! paens - only allocated and returned
114 ! if getkf_inflation=T (and denkf=F). In this case
115 ! paens is allocated dimension (nanals,nanals) and contains posterior
116 ! covariance matrix in (modulated) ensemble space.
117 !
118 ! attributes:
119 ! language: f95
120 ! machine:
121 !
122 !$$$ end documentation block
123 
124 implicit none
125 integer(i_kind), intent(in) :: nobsl,nanals,neigv
126 real(r_kind),dimension(nobsl),intent(in ) :: rdiaginv_loc
127 real(r_kind),dimension(nanals,nobsl),intent(inout) :: hxens
128 real(r_single),dimension(nanals/neigv,nobsl),intent(in) :: hxens_orig
129 real(r_single),dimension(nobsl),intent(in) :: dep
130 real(r_single),dimension(nanals),intent(out) :: wts_ensmean
131 real(r_single),dimension(nanals,nanals/neigv),intent(out) :: wts_ensperts
132 !real(r_single),dimension(:,:),allocatable, intent(inout) :: paens
133 ! local variables.
134 real(r_kind),allocatable,dimension(:,:) :: work3,evecs
135 real(r_single),allocatable,dimension(:,:) :: swork2,pa,swork3,shxens
136 real(r_single),allocatable,dimension(:) :: swork1
137 real(r_kind),allocatable,dimension(:) :: rrloc,evals,gammapI,gamma_inv
138 real(r_kind) eps
139 integer(i_kind) :: nanal,ierr,lwork,liwork
140 !for LAPACK dsyevr
141 integer(i_kind) isuppz(2*nanals)
142 real(r_kind) vl,vu,normfact
143 integer(i_kind), allocatable, dimension(:) :: iwork
144 real(r_kind), dimension(:), allocatable :: work1
145 logical, intent(in) :: getkf_inflation,denkf,getkf
146 
147 if (neigv < 1) then
148  !print *,'neigv must be >=1 in letkf_core'
149  call abor1_ftn("neigv must be >=1 in letkf_core")
150 endif
151 
152 allocate(work3(nanals,nanals),evecs(nanals,nanals))
153 allocate(rrloc(nobsl),gammapi(nanals),evals(nanals),gamma_inv(nanals))
154 ! for dsyevr
155 !allocate(iwork(10*nanals),work1(70*nanals))
156 ! for dsyevd
157 allocate(iwork(3+5*nanals),work1(1+6*nanals+2*nanals*nanals))
158 
159 ! HZ^T = hxens sqrt(Rinv)
160 rrloc = rdiaginv_loc
161 eps = epsilon(0.0_r_single)
162 where (rrloc < eps) rrloc = eps
163 rrloc = sqrt(rrloc)
164 normfact = sqrt(real((nanals/neigv)-1,r_kind))
165 ! normalize so dot product is covariance
166 
167 !$OMP PARALLEL DO PRIVATE(nanal) shared(hxens, nobsl, rrloc, normfact)
168 do nanal=1,nanals
169  hxens(nanal,1:nobsl) = hxens(nanal,1:nobsl) * &
170  rrloc(1:nobsl)/normfact
171 end do
172 !$OMP END PARALLEL DO
173 
174 ! compute eigenvectors/eigenvalues of HZ^T HZ (left SV)
175 ! (in Bishop paper HZ is nobsl, nanals, here is it nanals, nobsl)
176 lwork = size(work1); liwork = size(iwork)
177 if(r_kind == kind(1.d0)) then ! double precision
178  !work3 = matmul(hxens,transpose(hxens))
179  call dgemm('n','t',nanals,nanals,nobsl,1.d0,hxens,nanals, &
180  hxens,nanals,0.d0,work3,nanals)
181  ! evecs contains eigenvectors of HZ^T HZ, or left singular vectors of HZ
182  ! evals contains eigenvalues (singular values squared)
183 ! call dsyevr('V','A','L',nanals,work3,nanals,vl,vu,1,nanals,-1.d0,nanals,evals,evecs, &
184 ! nanals,isuppz,work1,lwork,iwork,liwork,ierr)
185 ! use LAPACK dsyevd instead of dsyevr
186  evecs = work3
187  call dsyevd('V','L',nanals,evecs,nanals,evals,work1,lwork,iwork,liwork,ierr)
188 else ! single precision
189  call sgemm('n','t',nanals,nanals,nobsl,1.e0,hxens,nanals, &
190  hxens,nanals,0.e0,work3,nanals)
191 ! call ssyevr('V','A','L',nanals,work3,nanals,vl,vu,1,nanals,-1.e0,nanals,evals,evecs, &
192 ! nanals,isuppz,work1,lwork,iwork,liwork,ierr)
193 
194 ! use LAPACK dsyevd instead of dsyevr
195  evecs = work3
196  call ssyevd('V','L',nanals,evecs,nanals,evals,work1,lwork,iwork,liwork,ierr)
197 end if
198 if (ierr .ne. 0) print *,'warning: dsyev* failed, ierr=',ierr
199 deallocate(work1,iwork,work3) ! no longer needed
200 gamma_inv = 0.0_r_kind
201 
202 !$OMP PARALLEL DO PRIVATE(nanal)
203 do nanal=1,nanals
204  if (evals(nanal) > eps) then
205  gamma_inv(nanal) = 1./evals(nanal)
206  else
207  evals(nanal) = 0.0_r_kind
208  endif
209 enddo
210 !$OMP END PARALLEL DO
211 
212 ! gammapI used in calculation of posterior cov in ensemble space
213 gammapi = evals+1.0
214 deallocate(evals)
215 
216 ! create HZ^T R**-1/2
217 allocate(shxens(nanals,nobsl))
218 !$OMP PARALLEL DO PRIVATE(nanal)
219 do nanal=1,nanals
220  shxens(nanal,1:nobsl) = hxens(nanal,1:nobsl) * rrloc(1:nobsl)
221 end do
222 !$OMP END PARALLEL DO
223 deallocate(rrloc)
224 
225 ! compute factor to multiply with model space ensemble perturbations
226 ! to compute analysis increment (for mean update), save in single precision.
227 ! This is the factor C (Gamma + I)**-1 C^T (HZ)^ T R**-1/2 (y - HXmean)
228 ! in Bishop paper (eqs 10-12).
229 
230 allocate(swork3(nanals,nanals),swork2(nanals,nanals),pa(nanals,nanals))
231 !$OMP PARALLEL DO PRIVATE(nanal)
232 do nanal=1,nanals
233  swork3(nanal,:) = evecs(nanal,:)/gammapi
234  swork2(nanal,:) = evecs(nanal,:)
235 enddo
236 !$OMP END PARALLEL DO
237 
238 ! pa = C (Gamma + I)**-1 C^T (analysis error cov in ensemble space)
239 !pa = matmul(swork3,transpose(swork2))
240 call sgemm('n','t',nanals,nanals,nanals,1.e0,swork3,nanals,swork2,&
241  nanals,0.e0,pa,nanals)
242 ! work1 = (HZ)^ T R**-1/2 (y - HXmean)
243 ! (nanals, nobsl) x (nobsl,) = (nanals,)
244 ! in Bishop paper HZ is nobsl, nanals, here is it nanals, nobsl
245 allocate(swork1(nanals))
246 !$OMP PARALLEL DO PRIVATE(nanal)
247 do nanal=1,nanals
248  swork1(nanal) = sum(shxens(nanal,:)*dep(:))
249 end do
250 !$OMP END PARALLEL DO
251 ! wts_ensmean = C (Gamma + I)**-1 C^T (HZ)^ T R**-1/2 (y - HXmean)
252 ! (nanals, nanals) x (nanals,) = (nanals,)
253 !$OMP PARALLEL DO PRIVATE(nanal)
254 do nanal=1,nanals
255  wts_ensmean(nanal) = sum(pa(nanal,:)*swork1(:))/normfact
256 end do
257 !$OMP END PARALLEL DO
258 
259 !if (.not. denkf .and. getkf_inflation) then
260 ! allocate(paens(nanals,nanals))
261 ! paens = pa/normfact**2
262 !endif
263 deallocate(swork1)
264 
265 ! compute factor to multiply with model space ensemble perturbations
266 ! to compute analysis increment (for perturbation update), save in single precision.
267 ! This is -C [ (I - (Gamma+I)**-1/2)*Gamma**-1 ] C^T (HZ)^T R**-1/2 HXprime
268 ! in Bishop paper (eqn 29).
269 ! For DEnKF factor is -0.5*C (Gamma + I)**-1 C^T (HZ)^ T R**-1/2 HXprime
270 ! = -0.5 Pa (HZ)^ T R**-1/2 HXprime (Pa already computed)
271 
272 if (getkf) then ! use Gain formulation for LETKF weights
273 
274 if (denkf) then
275  ! use Pa = C (Gamma + I)**-1 C^T (already computed)
276  ! wts_ensperts = -0.5 Pa (HZ)^ T R**-1/2 HXprime
277  pa = 0.5*pa
278 else
279  gammapi = sqrt(1.0/gammapi)
280 !$OMP PARALLEL DO PRIVATE(nanal)
281  do nanal=1,nanals
282  swork3(nanal,:) = &
283  evecs(nanal,:)*(1.-gammapi(:))*gamma_inv(:)
284  enddo
285 !$OMP END PARALLEL DO
286  ! swork2 still contains eigenvectors, over-write pa
287  ! pa = C [ (I - (Gamma+I)**-1/2)*Gamma**-1 ] C^T
288  !pa = matmul(swork3,transpose(swork2))
289  call sgemm('n','t',nanals,nanals,nanals,1.e0,swork3,nanals,swork2,&
290  nanals,0.e0,pa,nanals)
291 endif
292 deallocate(swork2,swork3)
293 
294 ! work2 = (HZ)^ T R**-1/2 HXprime
295 ! (nanals, nobsl) x (nobsl, nanals/neigv) = (nanals, nanals/neigv)
296 ! in Bishop paper HZ is nobsl, nanals, here is it nanals, nobsl
297 ! HXprime in paper is nobsl, nanals/neigv here it is nanals/neigv, nobsl
298 allocate(swork2(nanals,nanals/neigv))
299 !swork2 = matmul(shxens,transpose(hxens_orig))
300 call sgemm('n','t',nanals,nanals/neigv,nobsl,1.e0,&
301  shxens,nanals,hxens_orig,nanals/neigv,0.e0,swork2,nanals)
302 ! wts_ensperts = -C [ (I - (Gamma+I)**-1/2)*Gamma**-1 ] C^T (HZ)^T R**-1/2 HXprime
303 ! (nanals, nanals) x (nanals, nanals/eigv) = (nanals, nanals/neigv)
304 ! if denkf, wts_ensperts = -0.5 C (Gamma + I)**-1 C^T (HZ)^T R**-1/2 HXprime
305 !wts_ensperts = -matmul(pa, swork2)/normfact
306 call sgemm('n','n',nanals,nanals/neigv,nanals,-1.e0,&
307  pa,nanals,swork2,nanals,0.e0,wts_ensperts,nanals)
308 wts_ensperts = wts_ensperts/normfact
309 
310 ! clean up
311 deallocate(shxens,swork2,pa)
312 
313 else ! use original LETKF formulation (won't work if neigv != 1)
314 
315 if (neigv > 1) then
316  !print *,'neigv must be 1 in letkf_core if getkf=F'
317  !stop 993
318  call abor1_ftn("neigv must be 1 in letkf_core if getkf=F")
319 endif
320 ! compute sqrt(Pa) - analysis weights
321 ! (apply to prior ensemble to determine posterior ensemble,
322 ! not analysis increments as in Gain formulation)
323 ! hxens_orig not used
324 ! saves two matrix multiplications (nanals, nobsl) x (nobsl, nanals) and
325 ! (nanals, nanals) x (nanals, nanals)
326 deallocate(shxens,pa)
327 gammapi = sqrt(1.0/gammapi)
328 !$OMP PARALLEL DO PRIVATE(nanal)
329 do nanal=1,nanals
330  swork3(nanal,:) = evecs(nanal,:)*gammapi
331 enddo
332 !$OMP END PARALLEL DO
333 ! swork2 already contains evecs
334 ! wts_ensperts =
335 ! C (Gamma + I)**-1/2 C^T (square root of analysis error cov in ensemble space)
336 !wts_ensperts = matmul(swork3,transpose(swork2))
337 call sgemm('n','t',nanals,nanals,nanals,1.0,swork3,nanals,swork2,&
338  nanals,0.e0,wts_ensperts,nanals)
339 deallocate(swork3,swork2)
340 
341 endif
342 
343 deallocate(evecs,gammapi,gamma_inv)
344 
345 
346 return
347 end subroutine letkf_core
348 
349 end module letkf
integer, parameter r_kind
Definition: gletkf_mod.f90:28
subroutine letkf_core(nobsl, hxens, hxens_orig, dep, wts_ensmean, wts_ensperts, rdiaginv_loc, nanals, neigv, getkf_inflation, denkf, getkf)
Definition: gletkf_mod.f90:35
integer, parameter r_double
Definition: gletkf_mod.f90:27
integer, parameter num_bytes_for_r_single
Definition: gletkf_mod.f90:27
integer, parameter i_kind
Definition: gletkf_mod.f90:28
integer, parameter r_single
Definition: gletkf_mod.f90:27