EMPIRE DA  v1.9.1
Data assimilation codes using EMPIRE communication
 All Classes Files Functions Variables Pages
letkf_analysis.f90
Go to the documentation of this file.
1 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2 !!! Time-stamp: <2016-12-08 11:28:35 pbrowne>
3 !!!
4 !!! Ensemble transform Kalman filter
5 !!! Copyright (C) 2014 Philip A. Browne
6 !!!
7 !!! This program is free software: you can redistribute it and/or modify
8 !!! it under the terms of the GNU General Public License as published by
9 !!! the Free Software Foundation, either version 3 of the License, or
10 !!! (at your option) any later version.
11 !!!
12 !!! This program is distributed in the hope that it will be useful,
13 !!! but WITHOUT ANY WARRANTY; without even the implied warranty of
14 !!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 !!! GNU General Public License for more details.
16 !!!
17 !!! You should have received a copy of the GNU General Public License
18 !!! along with this program. If not, see <http://www.gnu.org/licenses/>.
19 !!!
20 !!! Email: p.browne @ reading.ac.uk
21 !!! Mail: School of Mathematical and Physical Sciences,
22 !!! University of Reading,
23 !!! Reading, UK
24 !!! RG6 6BB
25 !!!
26 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
27 
32 subroutine letkf_analysis
33  use output_empire, only : emp_e
34  use timestep_data
35  use comms
36  use pf_control
37  use sizes
38  implicit none
39  integer, parameter :: rk = kind(1.0d0)
40 
41  !!> Forecast ensemble on entry, analysis ensemble on exit
42  !real(kind=rk), dimension(stateDimension,N), intent(inout) :: x
43  real(kind=rk), dimension(state_dim,pf%count) :: x_p
44 
46  real(kind=rk), dimension(obs_dim) :: y
47 
48  ! Local variables for the SVD
49  integer :: r
50  real(kind=rk), dimension(:,:), allocatable :: V
51  real(kind=rk), dimension(:), allocatable :: S
52  real(kind=rk), dimension(pf%nens,pf%nens) :: UT
53  integer :: LWORK,INFO
54  real(kind=rk), dimension(:), allocatable :: WORK
55 
56  ! Miscellaneous local variables
57  real(kind=rk), dimension(state_dim) :: mean_x
58  real(kind=rk), dimension(:), allocatable :: mean_xa
59  real(kind=rk), dimension(state_dim,pf%count) :: Xp_p
60  !real(kind=rk), dimension(stateDimension,N) :: Xp
61  real(kind=rk), dimension(:,:), allocatable :: Xp
62  real(kind=rk), dimension(:,:), allocatable :: Xa
63  real(kind=rk), dimension(obs_dim) :: mean_yf,d,dd
64  real(kind=rk), dimension(obs_dim_g) :: dd_g
65  real(kind=rk), dimension(obs_dim,pf%nens) :: yf,Ysf
66  real(kind=rk), dimension(obs_dim_g,pf%nens) :: Ysf_g
67  real(kind=rk), dimension(obs_dim,pf%count) :: yf_p
68  integer :: i,j,number_gridpoints
69 
70  !variables for localisation
71  real(kind=rk) :: dist
72  real(kind=rk), parameter :: maxscal=exp(8.0d0)
73  real(kind=rk),dimension(obs_dim_g) :: scal
74  logical, dimension(obs_dim_g) :: yes
75  integer :: red_obsdim
76  real(kind=rk), allocatable, dimension(:,:) :: Ysf_red
77  real(kind=rk), allocatable, dimension(:) :: dd_red
78  integer :: stateDim !generally 1 for the letkf
79 
80  !variables for mpi
81  integer :: mpi_err
82  integer, dimension(npfs) :: start_var,stop_var
83  integer :: ensemble_comm
84  include 'mpif.h'
85 
86  if(comm_version .eq. 1 .or. comm_version .eq. 2) then
87  ensemble_comm = pf_mpi_comm
88  elseif(comm_version .eq. 3) then
89  ensemble_comm = pf_ens_comm
90  else
91  write(emp_e,*) 'EMPIRE VERSION ',comm_version,' NOT SUPPORTED IN letkf_analysis'
92  write(emp_e,*) 'THIS IS AN ERROR. STOPPING'
93  stop '-24'
94  end if
95 
96 
97  call get_observation_data(y,pf%timestep)
98 
99  ! Split forecast ensemble into mean and perturbation matrix, inflating
100  ! if necessary
101  ! mean_x will only be the sum of state vectors on this mpi process
102  mean_x = sum(pf%psi,dim=2)
103 
104  !send mean_x to all processes and add up to get global sum
105  call mpi_allreduce(mpi_in_place,mean_x,state_dim,mpi_double_precision&
106  &,mpi_sum,ensemble_comm,mpi_err)
107 
108  !now divide by the total number of ensemble members to make it a true
109  !mean
110  mean_x = mean_x/real(pf%nens,rk)
111 
112  ! compute the ensemble perturbation matrix for those ensemble members
113  ! stored on this local mpi process
114  do i = 1,pf%count
115  xp_p(:,i) = pf%psi(:,i) - mean_x
116  end do
117 
118  ! inflate the ensemble perturbation matrix
119  xp_p = (1.0_rk + pf%rho) * xp_p
120  ! store the local state vectors back in x_p
121  do i = 1,pf%count
122  x_p(:,i) = mean_x + xp_p(:,i)
123  end do
124 
125  ! make the local ensemble perturbation matrix the correct scale
126  xp_p = xp_p/sqrt(real(pf%nens-1,rk))
127 
128  ! Calculate forecast observations, split into mean and ensemble
129  ! perturbation matrix, scale perturbations by inverse square root of
130  ! observation covariance
131 
132  ! first apply observation operator only to local state vectors
133  ! on this mpi process
134  call h(obs_dim,pf%count,x_p,yf_p,pf%timestep)
135 
136 
137  ! as yf_p should be much smaller than x_p, send this to mpi processes
138  ! need to send round all yf_p and store in yf on all processes
139  call mpi_allgatherv(yf_p,pf%count*obs_dim,mpi_double_precision,yf&
140  &,gblcount*obs_dim,gbldisp*obs_dim,mpi_double_precision&
141  &,ensemble_comm,mpi_err)
142 
143  ! compute the mean of yf
144  mean_yf = sum(yf,dim=2)/real(pf%nens,rk)
145  do i = 1,pf%nens
146  yf(:,i) = yf(:,i) - mean_yf
147  end do
148 
149  ! now make yf the forecast perturbation matrix
150  yf = yf/sqrt(real(pf%nens-1,rk))
151  ! scale yf to become ysf
152  call solve_rhalf(obs_dim,pf%nens,yf,ysf,pf%timestep)
153 
154 
155  ! now let us compute which state variables will be analysed on each
156  ! MPI process:
157  do i = 1,npfs
158  start_var(i) = ((i-1)*state_dim)/npfs + 1
159  stop_var(i) = (i*state_dim)/npfs
160  end do
161 
162  !allocate space for Xp and Xa now that we know how many grid points we consider
163  allocate(xp(stop_var(pfrank+1)-start_var(pfrank+1)+1,pf%nens))
164  allocate(xa(stop_var(pfrank+1)-start_var(pfrank+1)+1,pf%nens))
165  allocate(mean_xa(stop_var(pfrank+1)-start_var(pfrank+1)+1))
166  mean_xa = mean_x(start_var(pfrank+1):stop_var(pfrank+1))
167 
168  !now we have to get Xp filled with all the values from each process
169  do i = 1,npfs
170  number_gridpoints = stop_var(i)-start_var(i)+1
171 
172  call mpi_gatherv(xp_p(start_var(i):stop_var(i),:),& !sendbuf
173  pf%count*number_gridpoints,& !sendcount
174  mpi_double_precision,& !sendtype
175  xp,& !recvbuf
176  gblcount*number_gridpoints,& !recvcounts
177  gbldisp*number_gridpoints,& !displs
178  mpi_double_precision,& !recvtype
179  i-1,& !root
180  ensemble_comm,& !comm
181  mpi_err) !ierror
182  end do
183 
184 
185  d = y - mean_yf
186  call solve_rhalf(obs_dim,1,d,dd,pf%timestep)
187 
188  if(comm_version .eq. 1 .or. comm_version .eq. 2) then
189  dd_g = dd
190  ysf_g = ysf
191  elseif(comm_version .eq. 3) then
192  call mpi_allgatherv(dd,obs_dim,mpi_double_precision,dd_g,obs_dims&
193  &,obs_displacements,mpi_double_precision,pf_member_comm&
194  &,mpi_err)
195  call mpi_allgatherv(ysf,obs_dim*pf%nens,mpi_double_precision&
196  &,ysf_g,obs_dims*pf%nens,obs_displacements*pf%nens&
197  &,mpi_double_precision,pf_member_comm,mpi_err)
198  else
199  print*,'should not enter here. error. stopping :('
200  end if
201 
202  !this is for serial processing
203  number_gridpoints = stop_var(pfrank+1)-start_var(pfrank+1)+1
204  !$OMP PARALLEL DO &
205  !$OMP& PRIVATE(stateDim,i,dist,scal,yes), &
206  !$OMP& PRIVATE(Ysf_red,red_obsDim,r,V,S,LWORK,WORK,INFO), &
207  !$OMP& PRIVATE(dd_red,UT,d)
208  do j = 1,number_gridpoints
209  statedim = 1
210  yes = .false.
211  !let us process the observations here:
212  do i = 1,obs_dim_g
213  ! get the distance between the current state variable
214  ! j+start_var(pfrank+1)-1
215  ! and the observation i
216  ! store it as distance
217  call dist_st_ob(j+start_var(pfrank+1)-1,i,dist,pf%timestep)
218  ! compute the scaling factor based in this distance and the
219  ! length scale
220  call loc_function(1,dist,scal(i),yes(i))
221  end do
222 
223  ! count the total number of observations we shall consider for this
224  ! state variable
225  red_obsdim = count(yes)
226 
227 
228  ! if there are no observations in range, treat this as a special case
229  if(red_obsdim .gt. 0) then
230 
231  allocate(ysf_red(red_obsdim,pf%nens))
232  !multiply by the distance matrix
233  !this line only works for diagonal R matrix...
234  ! reduce the forecast ensemble to only the observations in range
235  ! and scale by the distance function
236  do i = 1,pf%nens
237  ysf_red(:,i) = pack(ysf_g(:,i),yes)*sqrt(pack(scal,yes))
238  end do
239 
240  ! Compute the SVD
241  r = min(red_obsdim,pf%nens)
242  allocate(v(red_obsdim,r))
243  allocate(s(r))
244  lwork = 2*max( 3*r+max(red_obsdim,pf%nens), 5*r )
245 
246  allocate(work(lwork))
247 
248  call dgesvd('S','A',red_obsdim,pf%nens,ysf_red,red_obsdim,s,v,red_obsdim,ut,pf%nens,work,lwork,info)
249  if(info .ne. 0) then
250  write(emp_e,*) 'EMPIRE ERROR IN LETKF WITH THE SVD'
251  write(emp_e,*) 'SVD failed with INFO = ',info
252  write(emp_e,*) 'FYI WORK(1) = ',work(1)
253  write(emp_e,*) 'STOPPING'
254  stop
255  end if
256  deallocate(work)
257 
258  ! Compute product of forecast ensemble perturbation matrix and U. We
259  ! store the result in xa, which we here call Xa to distinguish this
260  ! secondary use of the variable.
261  ! pick out the correct row of Xa and Xp here:
262  call dgemm('N','T',statedim,pf%nens,pf%nens,1.0d0,xp(j,:),statedim,ut,pf%nens,0.0d0,xa(j,:),statedim)
263 
264 
265  ! Build up analysis ensemble mean (to be stored in mean_x); done now
266  ! because we shall be reusing X shortly
267 
268  allocate(dd_red(red_obsdim))
269  dd_red = pack(dd_g,yes)*sqrt(pack(scal,yes))
270 
271  ! Only the first r elements of d are in use from here on
272  call dgemv('T',red_obsdim,r,1.0d0,v,red_obsdim,dd_red,1,0.0d0,d,1)
273 
274  d(1:r) = s * d(1:r) / (1.0_rk + s**2)
275 
276  ! Only the first r columns of Xa are used in the following
277  call dgemv('N',statedim,r,1.0d0,xa(j,:),statedim,d,1,1.0d0,mean_xa(j),1)
278 
279  ! Build up analysis ensemble perturbation matrix
280  do i = 1,r
281  xa(j,i) = xa(j,i) / sqrt(1.0_rk + s(i)**2)
282  end do
283  call dgemm('N','N',statedim,pf%nens,pf%nens,1.0d0,xa(j,:),statedim,ut,pf%nens,0.0d0,xp(j,:),statedim)
284 
285  ! Put ensemble mean and perturbation matrix back together
286  xa(j,:) = sqrt(real(pf%nens-1,rk))*xp(j,:)
287  do i = 1,pf%nens
288  xa(j,i) = mean_xa(j) + xa(j,i)
289  end do
290 
291  !put this back into the full state vector
292  !wait it is already
293  deallocate(ysf_red)
294  deallocate(v)
295  deallocate(s)
296  deallocate(dd_red)
297 
298  else !if there are no observations near, the analysis is
299  ! just the forecast
300  do i = 1,pf%nens
301  xa(j,i) = mean_xa(j) + xp(j,i)*sqrt(real(pf%nens-1,rk))
302  end do
303 
304  end if
305  end do
306  !$OMP END PARALLEL DO
307 
308 
309 
310  ! now we must scatter xa back into pf%psi
311  do i = 1,npfs
312 
313  number_gridpoints = stop_var(i)-start_var(i)+1
314 
315  call mpi_scatterv(xa,& !sendbuf
316  gblcount*number_gridpoints,& !sendcounts
317  gbldisp*number_gridpoints,& !displs
318  mpi_double_precision,& !sendtype
319  pf%psi(start_var(i):stop_var(i),:),& !recvbuf
320  pf%count*number_gridpoints,& !recvcount
321  mpi_double_precision,& !recvtype
322  i-1,& !root
323  ensemble_comm,& !comm
324  mpi_err) !ierror
325  end do
326 
327 
328  deallocate(mean_xa)
329  deallocate(xp)
330  deallocate(xa)
331 
333 end subroutine letkf_analysis
Module containing EMPIRE coupling data.
Definition: comms.f90:57
Module that stores the information about the outputting from empire.
Module that stores the information about the timestepping process.
subroutine letkf_analysis
subroutine to perform the ensemble transform Kalman filter as part of L-ETKF
Module that stores the dimension of observation and state spaces.
Definition: sizes.f90:29
subroutine h(obsDim, nrhs, x, hx, t)
subroutine to take a full state vector x and return H(x) in observation space.
subroutine loc_function(loctype, dis, scal, inc)
subroutine to compute a localisation weighting based on a distance
subroutine solve_rhalf(obsdim, nrhs, y, v, t)
subroutine to take an observation vector y and return v in observation space.
module pf_control holds all the information to control the the main program
Definition: pf_control.f90:29
subroutine timestep_data_set_is_analysis
subroutine to define if the current ensemble is an analysis
subroutine get_observation_data(y, t)
Subroutine to read observation from a file .
subroutine dist_st_ob(xp, yp, dis, t)
subroutine to compute the distance between the variable in the state vector and the variable in the o...