SOCA
soca_covariance_mod.F90
Go to the documentation of this file.
1 ! (C) Copyright 2017-2021 UCAR.
2 !
3 ! This software is licensed under the terms of the Apache Licence Version 2.0
4 ! which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5 
6 !> Structure holding configuration variables for the 3d error
7 !! covariance matrices of the SOCA analysis.
9 
10 use atlas_module, only: atlas_fieldset, atlas_field, atlas_real, atlas_integer, atlas_functionspace
11 use fckit_configuration_module, only: fckit_configuration
12 use fckit_log_module, only: fckit_log
13 use kinds, only: kind_real
14 use oops_variables_mod, only: oops_variables
15 use random_mod, only: normal_distribution
16 use tools_func, only: gau2gc
17 use type_bump, only: bump_type
18 
19 ! soca modules
20 use soca_fields_mod, only: soca_field
21 use soca_geom_mod, only : soca_geom
23 use soca_state_mod, only: soca_state
24 
25 implicit none
26 private
27 
28 !> SOCA background/model covariance
29 type, public :: soca_cov
30  type(bump_type), pointer :: conv(:) !< convolution op from bump
31  type(soca_state), pointer :: bkg !< Background field (or first guess)
32  type(oops_variables) :: vars !< Apply B to vars
33 
34  real(kind=kind_real), allocatable :: pert_scale(:) !< index matches "vars"
35  type(oops_variables), allocatable :: conv_vars(:) !< index mathces "conv"
36 
37 contains
38  !> \copybrief soca_cov_setup \see soca_cov_setup
39  procedure :: setup => soca_cov_setup
40 
41  !> \copybrief soca_cov_delete \see soca_cov_delete
42  procedure :: delete => soca_cov_delete
43 
44  !> \copybrief soca_cov_c_mult \see soca_cov_c_mult
45  procedure :: mult => soca_cov_c_mult
46 
47  !> \copybrief soca_cov_sqrt_c_mult \see soca_cov_sqrt_c_mult
48  procedure :: sqrt_c_mult => soca_cov_sqrt_c_mult
49 
50  !> \copybrief soca_cov_get_conv \see soca_cov_get_conv
51  procedure :: getconv => soca_cov_get_conv
52 end type soca_cov
53 
54 
55 ! ------------------------------------------------------------------------------
56 contains
57 ! ------------------------------------------------------------------------------
58 
59 
60 ! ------------------------------------------------------------------------------
61 !> Setup for the SOCA model's 3d error covariance matrices (B and Q_i)
62 !!
63 !! This routine queries the configuration for the parameters that define the
64 !! covariance matrix, and stores the relevant values in the
65 !! error covariance structure.
66 !! \relates soca_covariance_mod::soca_cov
67 subroutine soca_cov_setup(self, f_conf, geom, bkg, vars)
68  class(soca_cov), intent(inout) :: self !< The covariance structure
69  type(fckit_configuration), intent(in) :: f_conf !< The configuration
70  type(soca_geom), intent(in) :: geom !< Geometry
71  type(soca_state), target, intent(in) :: bkg !< Background
72  type(oops_variables), intent(in) :: vars !< List of variables
73 
74  type(fckit_configuration) :: f_conf2
75  type(fckit_configuration), allocatable :: f_conf_list(:)
76  character(len=:), allocatable :: domain_vars(:)
77  character(len=:), allocatable :: domain
78  integer :: i, isc, iec, jsc, jec, ivar
79 
80  ! Setup list of variables to apply B on
81  self%vars = vars
82 
83  ! get perturbation scales (or set to 1.0)
84  allocate(self%pert_scale(self%vars%nvars()))
85  self%pert_scale = 1.0
86  if (f_conf%get("perturbation scales", f_conf2)) then
87  do ivar=1,self%vars%nvars()
88  if ( .not. f_conf2%get(self%vars%variable(ivar), self%pert_scale(ivar))) then
89  if (geom%f_comm%rank() == 0) call fckit_log%warning( &
90  "WARNING: no pertubation scale given for '" //trim(self%vars%variable(ivar)) &
91  // "' using default of 1.0")
92  end if
93  end do
94  end if
95 
96  ! Associate background
97  self%bkg => bkg
98 
99  ! Indices for compute domain (no halo)
100  isc = bkg%geom%isc ; iec = bkg%geom%iec
101  jsc = bkg%geom%jsc ; jec = bkg%geom%jec
102 
103  ! Initialize bump
104  call f_conf%get_or_die("bump", f_conf2)
105  call f_conf%get_or_die("correlation", f_conf_list)
106  allocate(self%conv(size(f_conf_list)))
107  allocate(self%conv_vars(size(self%conv)))
108  do i=1,size(f_conf_list)
109  call f_conf_list(i)%get_or_die("name", domain)
110  call f_conf_list(i)%get_or_die("variables", domain_vars)
111  self%conv_vars(i) = oops_variables()
112  call self%conv_vars(i)%push_back(domain_vars)
113 
114  call soca_bump_correlation(self, self%conv(i), geom, f_conf2, f_conf_list(i), domain)
115  end do
116 
117 end subroutine soca_cov_setup
118 
119 
120 ! ------------------------------------------------------------------------------
121 !> Delete for the SOCA model's 3d error covariance matrices
122 !!
123 !! \relates soca_covariance_mod::soca_cov
124 subroutine soca_cov_delete(self)
125  class(soca_cov), intent(inout) :: self !< The covariance structure
126 
127  deallocate(self%conv)
128  deallocate(self%conv_vars)
129  deallocate(self%pert_scale)
130  nullify(self%bkg)
131 
132 end subroutine soca_cov_delete
133 
134 
135 ! ------------------------------------------------------------------------------
136 !> Get the convolution operator needed for a specific field
137 !!
138 !! \throws abor1_ftn aborts if trying to use a field not on the tracer grid
139 !! \relates soca_covariance_mod::soca_cov
140 subroutine soca_cov_get_conv(self, field, conv)
141  class(soca_cov), intent(inout) :: self
142  type(soca_field), pointer, intent(in) :: field !< The field that will be convolved
143  type(bump_type), pointer, intent(out) :: conv !< pointer to resulting convolution
144 
145  integer :: j,k
146 
147  ! safety check to make sure field is on h grid
148  if (field%metadata%grid /= "h" ) then
149  call abor1_ftn("ERROR: cannot use fields on u/v grids" )
150  end if
151 
152  ! determine which horizontal convolution to use
153  nullify(conv)
154  outer: do j=1,size(self%conv_vars)
155  do k=1,self%conv_vars(j)%nvars()
156  if (self%conv_vars(j)%variable(k) == field%name) then
157  conv => self%conv(j)
158  exit outer
159  end if
160  end do
161  end do outer
162  if ( .not. associated(conv)) then
163  call abor1_ftn("ERROR: No valid bump operator found for field '"//field%name//"'")
164  end if
165 end subroutine
166 
167 
168 ! ------------------------------------------------------------------------------
169 !> Apply convolution to an increment
170 !!
171 !! \relates soca_covariance_mod::soca_cov
172 subroutine soca_cov_c_mult(self, dx)
173  class(soca_cov), intent(inout) :: self !< The covariance structure
174  type(soca_increment), intent(inout) :: dx !< Input: Increment, Output: C dx
175  integer :: i, z
176  type(soca_field), pointer :: field
177  type(bump_type), pointer :: conv
178 
179  do i = 1, self%vars%nvars()
180  ! why is this sometimes getting an "empty" list with "none" in it?
181  if (.not. dx%has(self%vars%variable(i))) cycle
182 
183  call dx%get(trim(self%vars%variable(i)), field)
184 
185  ! a **TEMPORARY** special exception for hocn
186  if ( field%name == "hocn" ) cycle
187 
188  ! determine which horizontal convolution to use
189  call self%getConv(field, conv)
190 
191  ! apply convolution on each level
192  do z = 1, field%nz
193  call soca_2d_convol(field%val(:,:,z), conv, dx%geom)
194  end do
195  end do
196 end subroutine soca_cov_c_mult
197 
198 
199 ! ------------------------------------------------------------------------------
200 !> Apply the square root of C to an increment
201 !!
202 !! \throws abor1_ftn aborts if no pertubation scales are given
203 !! \relates soca_covariance_mod::soca_cov
204 subroutine soca_cov_sqrt_c_mult(self, dx)
205  class(soca_cov), intent(inout) :: self !< The covariance structure
206  type(soca_increment), intent(inout) :: dx !< Input: Increment, Output: C^1/2 dx
207  integer :: i, z, j
208  type(soca_field), pointer :: field
209  real(kind=kind_real) :: scale
210  type(bump_type), pointer :: conv
211 
212  do i = 1, self%vars%nvars()
213  conv => null()
214  call dx%get(trim(self%vars%variable(i)), field)
215 
216  ! a **TEMPORARY** special exception for hocn
217  if ( field%name == "hocn" ) cycle
218 
219  ! find matching index in self%vars and get the perturbation scale
220  if (.not. allocated(self%pert_scale)) then
221  call abor1_ftn("ERROR: cannot use sqrt_C_mult if no perturbation scales given")
222  endif
223  do j=1,self%vars%nvars()
224  if (self%vars%variable(j) == field%name) exit
225  end do
226  scale = self%pert_scale(j)
227 
228  ! determine which horizontal convolution to use
229  call self%getConv(field, conv)
230 
231  ! apply convolution
232  do z = 1,field%nz
233  call soca_2d_sqrt_convol(field%val(:,:,z), conv, dx%geom, scale)
234  end do
235 
236  end do
237 end subroutine soca_cov_sqrt_c_mult
238 
239 
240 ! ------------------------------------------------------------------------------
241 !> Setup bump for horizontal convolution, Using rossby radiusbased correlation lengths
242 !!
243 !! Used by soca_cov::setup()
244 !!
245 !! Correlation lengths are calculated as follows :
246 !! 1) rh = "base value" + rossby_radius * "rossby mult"
247 !! 2) minimum value of "min grid mult" * grid_size is imposed
248 !! 3) min/max are imposed based on "min value" and "max value"
249 !! 4) converted from a gaussian sigma to Gaspari-Cohn cutoff distance
250 !! \relates soca_covariance_mod::soca_cov
251 subroutine soca_bump_correlation(self, horiz_convol, geom, f_conf_bump, f_conf_domain, domain)
252  class(soca_cov), intent(inout) :: self !< The covariance structure
253  type(bump_type), intent(inout) :: horiz_convol
254  type(soca_geom), intent(in) :: geom
255  type(fckit_configuration), intent(in) :: f_conf_bump, f_conf_domain
256  character(len=3), intent(in) :: domain
257 
258  integer :: i
259  integer, pointer :: int_ptr_2(:,:)
260  real(kind=kind_real), pointer :: real_ptr_1(:), real_ptr_2(:,:)
261  real(kind=kind_real), allocatable :: lats(:), area(:)
262  type(atlas_functionspace) :: afunctionspace
263  type(atlas_fieldset) :: afieldset, rh, rv
264  type(atlas_field) :: afield
265  type(fckit_configuration) :: f_grid
266  real(kind=kind_real) :: r_base, r_mult, r_min, r_max, r_min_grid
267 
268 
269  ! Grid setup
270  f_grid = fckit_configuration()
271  call f_grid%set('prefix', domain)
272  call f_grid%set('nl', 1)
273  call f_grid%set('nv', 1)
274  call f_grid%set('variables', ['var'])
275 
276  ! Wrap functionspace
277  afunctionspace = atlas_functionspace(geom%afunctionspace%c_ptr())
278 
279  ! Geometry fieldset setup
280  afieldset = atlas_fieldset()
281 
282  lats = pack(geom%lat(geom%isc:geom%iec,geom%jsc:geom%jec),.true.)
283 
284  ! Add area
285  afield = geom%afunctionspace%create_field(name='area', kind=atlas_real(kind_real), levels=0)
286  call afield%data(real_ptr_1)
287  area = pack(geom%cell_area(geom%isc:geom%iec,geom%jsc:geom%jec),.true.)
288  real_ptr_1 = area
289  call afieldset%add(afield)
290  call afield%final()
291 
292  ! Add vertical unit
293  afield = geom%afunctionspace%create_field(name='vunit', kind=atlas_real(kind_real), levels=1)
294  call afield%data(real_ptr_2)
295  real_ptr_2(1,:) = 1.0
296  call afieldset%add(afield)
297  call afield%final()
298 
299  ! Add geographical mask
300  afield = geom%afunctionspace%create_field(name='gmask', kind=atlas_integer(kind(0)), levels=1)
301  call afield%data(int_ptr_2)
302  int_ptr_2(1,:) = int(pack(geom%mask2d(geom%isc:geom%iec,geom%jsc:geom%jec),.true.))
303  call afieldset%add(afield)
304  call afield%final()
305 
306  ! Create BUMP object
307  call horiz_convol%create(geom%f_comm,afunctionspace,afieldset,f_conf_bump,f_grid)
308 
309  if (horiz_convol%nam%new_nicas) then
310  ! get parameters for correlation lengths
311  if (.not. f_conf_domain%get('base value', r_base)) r_base = 0.0
312  if (.not. f_conf_domain%get('rossby mult', r_mult)) r_mult = 0.0
313  if (.not. f_conf_domain%get('min grid mult', r_min_grid)) r_min_grid = 1.0
314  if (.not. f_conf_domain%get('min value', r_min)) r_min = 0.0
315  if (.not. f_conf_domain%get('max value', r_max)) r_max = huge(r_max)
316 
317  ! rh is calculated as follows :
318  ! 1) rh = "base value" + rossby_radius * "rossby mult"
319  ! 2) minimum value of "min grid mult" * grid_size is imposed
320  ! 3) min/max are imposed based on "min value" and "max value"
321  ! 4) converted from a gaussian sigma to Gaspari-Cohn cutoff distance
322  rh = atlas_fieldset()
323  afield = geom%afunctionspace%create_field('var',kind=atlas_real(kind_real),levels=0)
324  call rh%add(afield)
325  call afield%data(real_ptr_1)
326  real_ptr_1 = r_base + r_mult*pack(geom%rossby_radius(geom%isc:geom%iec,geom%jsc:geom%jec), .true.)
327  ! min based on grid size
328  if (r_min_grid .gt. 0.0) then
329  real_ptr_1 = max(real_ptr_1, sqrt(area)*r_min_grid )
330  end if
331  real_ptr_1 = min(r_max, real_ptr_1)
332  real_ptr_1 = max(r_min, real_ptr_1)
333  real_ptr_1 = real_ptr_1 * gau2gc ! convert from gaussian sigma to
334  ! Gaspari-Cohn half width
335  call afield%final()
336 
337  ! rv
338  rv = atlas_fieldset()
339  afield = geom%afunctionspace%create_field('var',kind=atlas_real(kind_real),levels=0)
340  call rv%add(afield)
341  call afield%data(real_ptr_1)
342  real_ptr_1 = 1.0
343  call afield%final()
344 
345  ! Copy length-scales into BUMP
346  call horiz_convol%set_parameter('cor_rh', rh)
347  call horiz_convol%set_parameter('cor_rv', rv)
348 
349  ! Clean up
350  call rh%final()
351  call rv%final()
352  end if
353 
354  ! Run BUMP drivers
355  call horiz_convol%run_drivers()
356 
357 end subroutine soca_bump_correlation
358 
359 
360 ! ------------------------------------------------------------------------------
361 !> Apply bump 2D convolution
362 !!
363 !! Used by soca_cov::mult()
364 !! \relates soca_covariance_mod::soca_cov
365 subroutine soca_2d_convol(dx, horiz_convol, geom)
366  real(kind=kind_real), intent(inout) :: dx(:,:)
367  type(bump_type), intent(inout) :: horiz_convol
368  type(soca_geom), intent(in) :: geom
369 
370  type(atlas_fieldset) :: tmp_incr
371 
372  ! Allocate ATLAS tmp_increment and make copy of dx
373  call geom%struct2atlas(dx(:,:), tmp_incr)
374 
375  ! Apply 2D convolution
376  call horiz_convol%apply_nicas(tmp_incr)
377 
378  ! Copy ATLAS tmp_incr to structured dx
379  call geom%atlas2struct(dx(:,:), tmp_incr)
380 
381  ! Clean up
382  call tmp_incr%final()
383 
384 end subroutine soca_2d_convol
385 
386 
387 ! ------------------------------------------------------------------------------
388 !> Apply bump square root of C
389 !!
390 !! used by soca_cov::sqrt_C_mult()
391 !! \relates soca_covariance_mod::soca_cov
392 subroutine soca_2d_sqrt_convol(dx, horiz_convol, geom, pert_scale)
393  real(kind=kind_real), intent(inout) :: dx(:,:)
394  type(bump_type), intent(inout) :: horiz_convol
395  type(soca_geom), intent(in) :: geom
396  real(kind=kind_real), intent(in) :: pert_scale
397 
398  type(atlas_fieldset) :: tmp_incr
399  real(kind=kind_real), allocatable :: pcv(:)
400  integer, parameter :: rseed = 1 ! constant for reproducability of tests
401  ! TODO: pass seed through config
402  integer :: nn
403 
404  ! Allocate ATLAS tmp_increment and make copy of dx
405  call geom%struct2atlas(dx(:,:), tmp_incr)
406 
407  ! Get control variable size
408  call horiz_convol%get_cv_size(nn)
409  allocate(pcv(nn))
410  pcv = 0.0_kind_real
411  call normal_distribution(pcv, 0.0_kind_real, 1.0_kind_real, rseed)
412  pcv = pert_scale * pcv
413 
414  ! Apply C^1/2
415  call horiz_convol%apply_nicas_sqrt(pcv, tmp_incr)
416 
417  ! Back to structured grid
418  call geom%atlas2struct(dx(:,:), tmp_incr)
419 
420  ! Clean up
421  deallocate(pcv)
422  call tmp_incr%final()
423 
424 end subroutine soca_2d_sqrt_convol
425 
426 end module soca_covariance_mod
Structure holding configuration variables for the 3d error covariance matrices of the SOCA analysis.
Handle fields for the model.
Geometry module.
Increment fields.
State fields.
SOCA background/model covariance.
Holds all data and metadata related to a single field variable.
Geometry data structure.