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 do nanal=1,nanals
167  hxens(nanal,1:nobsl) = hxens(nanal,1:nobsl) * &
168  rrloc(1:nobsl)/normfact
169 end do
170 
171 ! compute eigenvectors/eigenvalues of HZ^T HZ (left SV)
172 ! (in Bishop paper HZ is nobsl, nanals, here is it nanals, nobsl)
173 lwork = size(work1); liwork = size(iwork)
174 if(r_kind == kind(1.d0)) then ! double precision
175  !work3 = matmul(hxens,transpose(hxens))
176  call dgemm('n','t',nanals,nanals,nobsl,1.d0,hxens,nanals, &
177  hxens,nanals,0.d0,work3,nanals)
178  ! evecs contains eigenvectors of HZ^T HZ, or left singular vectors of HZ
179  ! evals contains eigenvalues (singular values squared)
180 ! call dsyevr('V','A','L',nanals,work3,nanals,vl,vu,1,nanals,-1.d0,nanals,evals,evecs, &
181 ! nanals,isuppz,work1,lwork,iwork,liwork,ierr)
182 ! use LAPACK dsyevd instead of dsyevr
183  evecs = work3
184  call dsyevd('V','L',nanals,evecs,nanals,evals,work1,lwork,iwork,liwork,ierr)
185 else ! single precision
186  call sgemm('n','t',nanals,nanals,nobsl,1.e0,hxens,nanals, &
187  hxens,nanals,0.e0,work3,nanals)
188 ! call ssyevr('V','A','L',nanals,work3,nanals,vl,vu,1,nanals,-1.e0,nanals,evals,evecs, &
189 ! nanals,isuppz,work1,lwork,iwork,liwork,ierr)
190 
191 ! use LAPACK dsyevd instead of dsyevr
192  evecs = work3
193  call ssyevd('V','L',nanals,evecs,nanals,evals,work1,lwork,iwork,liwork,ierr)
194 end if
195 if (ierr .ne. 0) print *,'warning: dsyev* failed, ierr=',ierr
196 deallocate(work1,iwork,work3) ! no longer needed
197 gamma_inv = 0.0_r_kind
198 do nanal=1,nanals
199  if (evals(nanal) > eps) then
200  gamma_inv(nanal) = 1./evals(nanal)
201  else
202  evals(nanal) = 0.0_r_kind
203  endif
204 enddo
205 ! gammapI used in calculation of posterior cov in ensemble space
206 gammapi = evals+1.0
207 deallocate(evals)
208 
209 ! create HZ^T R**-1/2
210 allocate(shxens(nanals,nobsl))
211 do nanal=1,nanals
212  shxens(nanal,1:nobsl) = hxens(nanal,1:nobsl) * rrloc(1:nobsl)
213 end do
214 deallocate(rrloc)
215 
216 ! compute factor to multiply with model space ensemble perturbations
217 ! to compute analysis increment (for mean update), save in single precision.
218 ! This is the factor C (Gamma + I)**-1 C^T (HZ)^ T R**-1/2 (y - HXmean)
219 ! in Bishop paper (eqs 10-12).
220 
221 allocate(swork3(nanals,nanals),swork2(nanals,nanals),pa(nanals,nanals))
222 do nanal=1,nanals
223  swork3(nanal,:) = evecs(nanal,:)/gammapi
224  swork2(nanal,:) = evecs(nanal,:)
225 enddo
226 
227 ! pa = C (Gamma + I)**-1 C^T (analysis error cov in ensemble space)
228 !pa = matmul(swork3,transpose(swork2))
229 call sgemm('n','t',nanals,nanals,nanals,1.e0,swork3,nanals,swork2,&
230  nanals,0.e0,pa,nanals)
231 ! work1 = (HZ)^ T R**-1/2 (y - HXmean)
232 ! (nanals, nobsl) x (nobsl,) = (nanals,)
233 ! in Bishop paper HZ is nobsl, nanals, here is it nanals, nobsl
234 allocate(swork1(nanals))
235 do nanal=1,nanals
236  swork1(nanal) = sum(shxens(nanal,:)*dep(:))
237 end do
238 ! wts_ensmean = C (Gamma + I)**-1 C^T (HZ)^ T R**-1/2 (y - HXmean)
239 ! (nanals, nanals) x (nanals,) = (nanals,)
240 do nanal=1,nanals
241  wts_ensmean(nanal) = sum(pa(nanal,:)*swork1(:))/normfact
242 end do
243 
244 !if (.not. denkf .and. getkf_inflation) then
245 ! allocate(paens(nanals,nanals))
246 ! paens = pa/normfact**2
247 !endif
248 deallocate(swork1)
249 
250 ! compute factor to multiply with model space ensemble perturbations
251 ! to compute analysis increment (for perturbation update), save in single precision.
252 ! This is -C [ (I - (Gamma+I)**-1/2)*Gamma**-1 ] C^T (HZ)^T R**-1/2 HXprime
253 ! in Bishop paper (eqn 29).
254 ! For DEnKF factor is -0.5*C (Gamma + I)**-1 C^T (HZ)^ T R**-1/2 HXprime
255 ! = -0.5 Pa (HZ)^ T R**-1/2 HXprime (Pa already computed)
256 
257 if (getkf) then ! use Gain formulation for LETKF weights
258 
259 if (denkf) then
260  ! use Pa = C (Gamma + I)**-1 C^T (already computed)
261  ! wts_ensperts = -0.5 Pa (HZ)^ T R**-1/2 HXprime
262  pa = 0.5*pa
263 else
264  gammapi = sqrt(1.0/gammapi)
265  do nanal=1,nanals
266  swork3(nanal,:) = &
267  evecs(nanal,:)*(1.-gammapi(:))*gamma_inv(:)
268  enddo
269  ! swork2 still contains eigenvectors, over-write pa
270  ! pa = C [ (I - (Gamma+I)**-1/2)*Gamma**-1 ] C^T
271  !pa = matmul(swork3,transpose(swork2))
272  call sgemm('n','t',nanals,nanals,nanals,1.e0,swork3,nanals,swork2,&
273  nanals,0.e0,pa,nanals)
274 endif
275 deallocate(swork2,swork3)
276 
277 ! work2 = (HZ)^ T R**-1/2 HXprime
278 ! (nanals, nobsl) x (nobsl, nanals/neigv) = (nanals, nanals/neigv)
279 ! in Bishop paper HZ is nobsl, nanals, here is it nanals, nobsl
280 ! HXprime in paper is nobsl, nanals/neigv here it is nanals/neigv, nobsl
281 allocate(swork2(nanals,nanals/neigv))
282 !swork2 = matmul(shxens,transpose(hxens_orig))
283 call sgemm('n','t',nanals,nanals/neigv,nobsl,1.e0,&
284  shxens,nanals,hxens_orig,nanals/neigv,0.e0,swork2,nanals)
285 ! wts_ensperts = -C [ (I - (Gamma+I)**-1/2)*Gamma**-1 ] C^T (HZ)^T R**-1/2 HXprime
286 ! (nanals, nanals) x (nanals, nanals/eigv) = (nanals, nanals/neigv)
287 ! if denkf, wts_ensperts = -0.5 C (Gamma + I)**-1 C^T (HZ)^T R**-1/2 HXprime
288 !wts_ensperts = -matmul(pa, swork2)/normfact
289 call sgemm('n','n',nanals,nanals/neigv,nanals,-1.e0,&
290  pa,nanals,swork2,nanals,0.e0,wts_ensperts,nanals)
291 wts_ensperts = wts_ensperts/normfact
292 
293 ! clean up
294 deallocate(shxens,swork2,pa)
295 
296 else ! use original LETKF formulation (won't work if neigv != 1)
297 
298 if (neigv > 1) then
299  !print *,'neigv must be 1 in letkf_core if getkf=F'
300  !stop 993
301  call abor1_ftn("neigv must be 1 in letkf_core if getkf=F")
302 endif
303 ! compute sqrt(Pa) - analysis weights
304 ! (apply to prior ensemble to determine posterior ensemble,
305 ! not analysis increments as in Gain formulation)
306 ! hxens_orig not used
307 ! saves two matrix multiplications (nanals, nobsl) x (nobsl, nanals) and
308 ! (nanals, nanals) x (nanals, nanals)
309 deallocate(shxens,pa)
310 gammapi = sqrt(1.0/gammapi)
311 do nanal=1,nanals
312  swork3(nanal,:) = evecs(nanal,:)*gammapi
313 enddo
314 ! swork2 already contains evecs
315 ! wts_ensperts =
316 ! C (Gamma + I)**-1/2 C^T (square root of analysis error cov in ensemble space)
317 !wts_ensperts = matmul(swork3,transpose(swork2))
318 call sgemm('n','t',nanals,nanals,nanals,1.0,swork3,nanals,swork2,&
319  nanals,0.e0,wts_ensperts,nanals)
320 deallocate(swork3,swork2)
321 
322 endif
323 
324 deallocate(evecs,gammapi,gamma_inv)
325 
326 
327 return
328 end subroutine letkf_core
329 
330 end module letkf
letkf
Definition: gletkf_mod.f90:1
letkf::letkf_core
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
letkf::r_kind
integer, parameter r_kind
Definition: gletkf_mod.f90:28
letkf::i_kind
integer, parameter i_kind
Definition: gletkf_mod.f90:28
letkf::r_double
integer, parameter r_double
Definition: gletkf_mod.f90:27
letkf::r_single
integer, parameter r_single
Definition: gletkf_mod.f90:27
letkf::num_bytes_for_r_single
integer, parameter num_bytes_for_r_single
Definition: gletkf_mod.f90:27