FMS  2024.03
Flexible Modeling System
drifters.F90
1 !***********************************************************************
2 !* GNU Lesser General Public License
3 !*
4 !* This file is part of the GFDL Flexible Modeling System (FMS).
5 !*
6 !* FMS is free software: you can redistribute it and/or modify it under
7 !* the terms of the GNU Lesser General Public License as published by
8 !* the Free Software Foundation, either version 3 of the License, or (at
9 !* your option) any later version.
10 !*
11 !* FMS is distributed in the hope that it will be useful, but WITHOUT
12 !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 !* for more details.
15 !*
16 !* You should have received a copy of the GNU Lesser General Public
17 !* License along with FMS. If not, see <http://www.gnu.org/licenses/>.
18 !***********************************************************************
19 
20 #include "fms_switches.h"
21 #define _FLATTEN(A) reshape((A), (/size((A))/) )
22 
23 !> @defgroup drifters_mod drifters_mod
24 !> @ingroup drifters
25 !! @brief <TT>Drifters_mod</TT>is a module designed to advect a set of particles, in parallel or
26 !! sequentially, given an prescribed velocity field.
27 !! @author Alexander Pletzer
28 !!
29 !> Drifters are idealized point particles with positions that evolve in time according
30 !! to a prescribed velocity field, starting from some initial conditions. Drifters have
31 !! no mass, no energy, no size, and no friction and therefore have no impact on the
32 !! dynamics of the underlying system. The only feature that distinguishes a drifter
33 !! from another is its trajectory. This makes drifters ideal for tracking pollution
34 !! clouds and probing fields (e.g. temperature, salinity) along ocean currents, to name
35 !! a few applications.
36 !! Drifters can mimic real experiments such as the Argo floats
37 !! http://www.metoffice.com/research/ocean/argo/ukfloats.html.
38 !!
39 !! When run in parallel, on a 2d decomposed domain, <TT>drifters_mod</TT> will handle all the
40 !! bookkeeping and communication transparently for the user. This involves adding/removing
41 !! drifters as they enter/leave a processor element (PE) domain. Note that the number of drifters
42 !! can vary greatly both between PE domains and within a PE domain in the course of a simulation; the drifters'
43 !! module will also manage dynamically the memory for the user.
44 !!
45 !! There are a number of basic assumptions which could make the drifters' module
46 !! ill-suited for some tasks. First and foremost, it is assumed that the motion of
47 !! drifters is not erratic but follows deterministic trajectories. Furthermore,
48 !! drifters should not cross both compute and data domain boundaries within less
49 !! than a time step. This limitation is imposed by the Runge-Kutta integration
50 !! scheme, which must be able to complete, within a time step, a trajectory
51 !! calculation that starts inside the compute domain and ends inside the data domain. Therefore, the drifters,
52 !! as they are presently modelled, are unlikely to work for very fast objects.
53 !! This constraint also puts a upper limit to the domain decomposition, although
54 !! it can often be remedied by increasing the number of ghost nodes.
55 !!
56 !! Another fundamental assumption is that the (e.g. velocity) fields are structured,
57 !! on a per PE domain basis. There is no support for locally nested or unstrucured
58 !! meshes. Meshes need not be smooth and continuous across PE domains, however.
59 
60 !> @addtogroup drifters_mod
61 !> @{
62 module drifters_mod
63 #ifdef use_drifters
64 
65 #ifdef _SERIAL
66 
67 ! serial code
68 #define _MPP_PE 0
69 #define _MPP_ROOT 0
70 #define _MPP_NPES 1
71 #define _TYPE_DOMAIN2D integer
72 
73 #else
74 
75 ! parallel code
76  use mpp_mod , only : mpp_pe, mpp_npes
77  use mpp_domains_mod, only : domain2d
78 #define _MPP_PE mpp_pe()
79 #define _MPP_ROOT mpp_root_pe()
80 #define _MPP_NPES mpp_npes()
81 #define _TYPE_DOMAIN2D type(domain2d)
82 
83 #endif
84 
85  use drifters_core_mod, only: drifters_core_type, drifters_core_new, drifters_core_del, assignment(=)
86 
87  use drifters_input_mod, only: drifters_input_type, drifters_input_new, drifters_input_del, assignment(=)
88 
89  use drifters_io_mod, only: drifters_io_type, drifters_io_new, drifters_io_del, drifters_io_set_time_units, &
90  drifters_io_set_position_names, drifters_io_set_position_units, &
91  drifters_io_set_field_names, drifters_io_set_field_units, drifters_io_write
92 
93  use drifters_comm_mod, only: drifters_comm_type,drifters_comm_new,drifters_comm_del, &
94  drifters_comm_set_pe_neighbors, drifters_comm_set_domain, &
95  drifters_comm_gather, drifters_comm_update
96 
97  use cloud_interpolator_mod, only: cld_ntrp_linear_cell_interp, cld_ntrp_locate_cell, cld_ntrp_get_cell_values
98  use platform_mod, only: fms_path_len
99  implicit none
100  private
101 
102  public :: drifters_type, assignment(=), drifters_push, drifters_compute_k, drifters_set_field
103  public :: drifters_new, drifters_del, drifters_set_domain, drifters_set_pe_neighbors
104  public :: drifters_set_v_axes, drifters_set_domain_bounds, drifters_positions2lonlat
105  public :: drifters_print_checksums, drifters_save, drifters_write_restart, drifters_distribute
106 
107  integer, parameter, private :: MAX_STR_LEN = 128
108 ! Include variable "version" to be written to log file.
109 #include<file_version.h>
110  !> @}
111 
112  !> @brief Holds all data needed for drifters communication, io, and input.
113  !> @ingroup drifters_mod
114  type drifters_type
115  ! Be sure to update drifters_new, drifters_del and drifters_copy_new
116  ! when adding members
117  type(drifters_core_type) :: core
118  type(drifters_input_type) :: input
119  type(drifters_io_type) :: io
120  type(drifters_comm_type) :: comm
121  real :: dt !< total dt, over a complete step
122  real :: time
123  ! fields
124  real, allocatable :: fields(:,:)
125  ! velocity field axes
126  real, allocatable :: xu(:) !< velocity field axes
127  real, allocatable :: yu(:) !< velocity field axes
128  real, allocatable :: zu(:) !< velocity field axes
129  real, allocatable :: xv(:) !< velocity field axes
130  real, allocatable :: yv(:) !< velocity field axes
131  real, allocatable :: zv(:) !< velocity field axes
132  real, allocatable :: xw(:) !< velocity field axes
133  real, allocatable :: yw(:) !< velocity field axes
134  real, allocatable :: zw(:) !< velocity field axes
135  ! Runge Kutta coefficients holding intermediate results (positions)
136  real, allocatable :: temp_pos(:,:) !< Runge Kutta coefficients holding
137  !! intermediate results (positions)
138  real, allocatable :: rk4_k1(:,:) !< Runge Kutta coefficients holding
139  !! intermediate results (positions)
140  real, allocatable :: rk4_k2(:,:) !< Runge Kutta coefficients holding
141  !! intermediate results (positions)
142  real, allocatable :: rk4_k3(:,:) !< Runge Kutta coefficients holding
143  !! intermediate results (positions)
144  real, allocatable :: rk4_k4(:,:) !< Runge Kutta coefficients holding
145  !! intermediate results (positions)
146  ! store filenames for convenience
147  character(len=FMS_PATH_LEN) :: input_file !< store filenames for convenience
148  character(len=FMS_PATH_LEN) :: output_file !< store filenames for convenience
149  ! Runge Kutta stuff
150  integer :: rk4_step !< Runge Kutta stuff
151  logical :: rk4_completed !< Runge Kutta stuff
152  integer :: nx, ny
153  logical, allocatable :: remove(:)
154  end type drifters_type
155 
156  !> @brief Assignment override for @ref drifters_type
157  !> @ingroup drifters_mod
158  interface assignment(=)
159  module procedure drifters_copy_new
160  end interface
161 
162  !> @brief "Push" a given drifter at a given velocity for either 2D or 3D data
163  !> @ingroup drifters_mod
164  interface drifters_push
165  module procedure drifters_push_2
166  module procedure drifters_push_3
167  end interface
168 
169  !> @ingroup drifters_mod
170  interface drifters_compute_k
171  module procedure drifters_computek2d
172  module procedure drifters_computek3d
173  end interface
174 
175  !> @brief Set the value of a given drifter field
176  !> @ingroup drifters_mod
177  interface drifters_set_field
178  module procedure drifters_set_field_2d
179  module procedure drifters_set_field_3d
180  end interface
181 
182 !> @addtogroup drifters_mod
183 !> @{
184 
185 contains
186 
187  !> @brief Will read positions stored in the netCDF file <TT>input_file</TT>.
188  !!
189  !> The trajectories will be saved in files <TT>output_file.PE</TT>,
190  !! one file per PE domain.
191  subroutine drifters_new(self, input_file, output_file, ermesg)
192 
193  type(drifters_type) :: self !< Opaque data structure.
194  character(len=*), intent(in) :: input_file !< NetCDF input file name containing initial positions.
195  character(len=*), intent(in) :: output_file !< NetCDF output file. Will contain trajectory
196  !! positions and interpolated fields.
197  character(len=*), intent(out) :: ermesg !< Error message (if any).
198 
199  integer nd, nf, npdim, i
200  character(len=6) :: pe_str
201 
202  ermesg = ''
203 
204  self%input_file = input_file
205  self%output_file = output_file
206 
207  call drifters_input_new(self%input, input_file, ermesg)
208  if(ermesg/='') return
209 
210  ! number of dimensions
211  nd = size(self%input%velocity_names)
212  ! estimate for the max number of particles (will resize if exceeded)
213  npdim = int(1.3*size(self%input%positions, 2))
214  call drifters_core_new(self%core, nd=nd, npdim=npdim, ermesg=ermesg)
215  if(ermesg/='') return
216 
217  ! number of fields
218  nf = size(self%input%field_names)
219 
220  ! one output file per PE
221  pe_str = ' '
222  write(pe_str, '(i6)') _mpp_pe
223  pe_str = adjustr(pe_str)
224  do i = 1, 5
225  if(pe_str(i:i)==' ') pe_str(i:i)='0'
226  enddo
227  call drifters_io_new(self%io, output_file//'.'//pe_str, nd, nf, ermesg)
228  if(ermesg/='') return
229 
230  call drifters_comm_new(self%comm)
231  if(ermesg/='') return
232 
233  ! Set meta data
234  call drifters_io_set_time_units(self%io, name=self%input%time_units, &
235  & ermesg=ermesg)
236 
237  call drifters_io_set_position_names(self%io, names=self%input%position_names, &
238  & ermesg=ermesg)
239  if(ermesg/='') return
240  call drifters_io_set_position_units(self%io, names=self%input%position_units, &
241  & ermesg=ermesg)
242  if(ermesg/='') return
243 
244  call drifters_io_set_field_names(self%io, names=self%input%field_names, &
245  & ermesg=ermesg)
246  if(ermesg/='') return
247  call drifters_io_set_field_units(self%io, names=self%input%field_units, &
248  & ermesg=ermesg)
249  if(ermesg/='') return
250 
251  self%dt = -1
252  self%time = -1
253  self%rk4_step = 0
254  self%nx = 0
255  self%ny = 0
256  self%rk4_completed = .false.
257 
258  allocate(self%rk4_k1(self%core%nd, self%core%npdim))
259  self%rk4_k1 = -huge(1.)
260  allocate(self%rk4_k2(self%core%nd, self%core%npdim))
261  self%rk4_k2 = -huge(1.)
262  allocate(self%rk4_k3(self%core%nd, self%core%npdim))
263  self%rk4_k3 = -huge(1.)
264  allocate(self%rk4_k4(self%core%nd, self%core%npdim))
265  self%rk4_k4 = -huge(1.)
266  allocate(self%remove(self%core%npdim))
267  self%remove = .false.
268  allocate(self%temp_pos(nd, self%core%npdim))
269  self%temp_pos = -huge(1.)
270 
271  allocate(self%fields(nf, self%core%npdim))
272  self%fields = -huge(1.)
273 
274  end subroutine drifters_new
275 
276  !============================================================================
277 
278  !> @brief Destructor, call this to reclaim memory from data used for drifters.
279  subroutine drifters_del(self, ermesg)
280  type(drifters_type) :: self !< Opaque data structure.
281  character(len=*), intent(out) :: ermesg !< Error message (if any).
282 
283  integer flag
284  ermesg = ''
285  deallocate(self%fields, stat=flag)
286  deallocate(self%xu, stat=flag)
287  deallocate(self%yu, stat=flag)
288  deallocate(self%zu, stat=flag)
289  deallocate(self%xv, stat=flag)
290  deallocate(self%yv, stat=flag)
291  deallocate(self%zv, stat=flag)
292  deallocate(self%xw, stat=flag)
293  deallocate(self%yw, stat=flag)
294  deallocate(self%zw, stat=flag)
295  deallocate(self%temp_pos, stat=flag)
296  deallocate(self%rk4_k1, stat=flag)
297  deallocate(self%rk4_k2, stat=flag)
298  deallocate(self%rk4_k3, stat=flag)
299  deallocate(self%rk4_k4, stat=flag)
300  deallocate(self%remove, stat=flag)
301 
302  call drifters_core_del(self%core, ermesg)
303  if(ermesg/='') return
304  call drifters_input_del(self%input, ermesg)
305  if(ermesg/='') return
306  call drifters_io_del(self%io, ermesg)
307  if(ermesg/='') return
308  call drifters_comm_del(self%comm)
309  if(ermesg/='') return
310 
311  end subroutine drifters_del
312 
313  !============================================================================
314  !> @brief Copy a drifter state into a new state. Note: this will not open new files; this will
315  !! copy all members into a new container.
316  subroutine drifters_copy_new(new_instance, old_instance)
317 
318  type(drifters_type), intent(in) :: old_instance !< Old data structure.
319  type(drifters_type), intent(inout) :: new_instance !< New data structure.
320 
321  character(len=MAX_STR_LEN) :: ermesg
322 
323  ermesg = ''
324 
325  ! make sure new_instance is empty
326  call drifters_del(new_instance, ermesg)
327  if(ermesg/='') return
328 
329  new_instance%core = old_instance%core
330  new_instance%input = old_instance%input
331  new_instance%io = old_instance%io
332  new_instance%comm = old_instance%comm
333 
334  new_instance%dt = old_instance%dt
335  new_instance%time = old_instance%time
336 
337  allocate(new_instance%fields( size(old_instance%fields, 1), &
338  & size(old_instance%fields, 2) ))
339  new_instance%fields = old_instance%fields
340 
341  allocate(new_instance%xu( size(old_instance%xu) ))
342  allocate(new_instance%yu( size(old_instance%yu) ))
343  allocate(new_instance%zu( size(old_instance%zu) ))
344  new_instance%xu = old_instance%xu
345  new_instance%yu = old_instance%yu
346  new_instance%zu = old_instance%zu
347  allocate(new_instance%xv( size(old_instance%xv) ))
348  allocate(new_instance%yv( size(old_instance%yv) ))
349  allocate(new_instance%zv( size(old_instance%zv) ))
350  new_instance%xv = old_instance%xv
351  new_instance%yv = old_instance%yv
352  new_instance%zv = old_instance%zv
353  allocate(new_instance%xw( size(old_instance%xw) ))
354  allocate(new_instance%yw( size(old_instance%yw) ))
355  allocate(new_instance%zw( size(old_instance%zw) ))
356  new_instance%xw = old_instance%xw
357  new_instance%yw = old_instance%yw
358  new_instance%zw = old_instance%zw
359 
360  allocate(new_instance%temp_pos( size(old_instance%temp_pos,1), &
361  & size(old_instance%temp_pos,2) ))
362  new_instance%temp_pos = old_instance%temp_pos
363  allocate(new_instance%rk4_k1( size(old_instance%rk4_k1,1), &
364  & size(old_instance%rk4_k1,2) ))
365  allocate(new_instance%rk4_k2( size(old_instance%rk4_k2,1), &
366  & size(old_instance%rk4_k2,2) ))
367  allocate(new_instance%rk4_k3( size(old_instance%rk4_k3,1), &
368  & size(old_instance%rk4_k3,2) ))
369  allocate(new_instance%rk4_k4( size(old_instance%rk4_k4,1), &
370  & size(old_instance%rk4_k4,2) ))
371  new_instance%rk4_k1 = old_instance%rk4_k1
372  new_instance%rk4_k2 = old_instance%rk4_k2
373  new_instance%rk4_k3 = old_instance%rk4_k3
374  new_instance%rk4_k4 = old_instance%rk4_k4
375 
376  new_instance%rk4_step = old_instance%rk4_step
377  new_instance%rk4_completed = old_instance%rk4_completed
378  new_instance%nx = old_instance%nx
379  new_instance%ny = old_instance%ny
380 
381  allocate(new_instance%remove(size(old_instance%remove)))
382  new_instance%remove = old_instance%remove
383 
384 
385  end subroutine drifters_copy_new
386 
387  !============================================================================
388  !> @brief Set the compute, data, and global domain boundaries.
389  !! @details The data domain extends beyond the compute domain and is shared between
390  !! two or more PE domains. A particle crossing the compute domain boundary
391  !! will trigger a communication with one or more neighboring domains. A particle
392  !! leaving the data domain will be removed from the list of particles.
393  !!
394  !! <br>Example usage:
395  !! @code{.F90}
396  !! call drifters_set_domain(self, &
397  !! & xmin_comp, xmax_comp, ymin_comp, ymax_comp, &
398  !! & xmin_data, xmax_data, ymin_data, ymax_data, &
399  !! & xmin_glob, xmax_glob, ymin_glob, ymax_glob, &
400  !! & ermesg)
401  !! @endcode
402  subroutine drifters_set_domain(self, &
403  & xmin_comp, xmax_comp, ymin_comp, ymax_comp, &
404  & xmin_data, xmax_data, ymin_data, ymax_data, &
405  & xmin_glob, xmax_glob, ymin_glob, ymax_glob, &
406  & ermesg)
407  type(drifters_type) :: self !< Opaque data structure.
408  ! compute domain boundaries
409  real, optional, intent(in) :: xmin_comp !< Min of longitude-like axis on compute domain.
410  real, optional, intent(in) :: xmax_comp !< Max of longitude-like axis on compute domain.
411  real, optional, intent(in) :: ymin_comp !< Min of latitude-like axis on compute domain.
412  real, optional, intent(in) :: ymax_comp !< Max of latitude-like axis on compute domain.
413  ! data domain boundaries
414  real, optional, intent(in) :: xmin_data !< Min of longitude-like axis on data domain.
415  real, optional, intent(in) :: xmax_data !< Max of longitude-like axis on data domain.
416  real, optional, intent(in) :: ymin_data !< Min of latitude-like axis on data domain.
417  real, optional, intent(in) :: ymax_data !< Max of latitude-like axis on data domain.
418  ! global boundaries (only specify those if domain is periodic)
419  real, optional, intent(in) :: xmin_glob !< Min of longitude-like axis on global domain.
420  real, optional, intent(in) :: xmax_glob !< Max of longitude-like axis on global domain.
421  real, optional, intent(in) :: ymin_glob !< Min of latitude-like axis on global domain.
422  real, optional, intent(in) :: ymax_glob !< Max of latitude-like axis on global domain.
423  character(len=*), intent(out) :: ermesg !< Error message (if any).
424 
425  ermesg = ''
426  if(present(xmin_comp)) self%comm%xcmin = xmin_comp
427  if(present(xmax_comp)) self%comm%xcmax = xmax_comp
428  if(present(ymin_comp)) self%comm%ycmin = ymin_comp
429  if(present(ymax_comp)) self%comm%ycmax = ymax_comp
430 
431  if(present(xmin_data)) self%comm%xdmin = xmin_data
432  if(present(xmax_data)) self%comm%xdmax = xmax_data
433  if(present(ymin_data)) self%comm%ydmin = ymin_data
434  if(present(ymax_data)) self%comm%ydmax = ymax_data
435 
436  if(present(xmin_glob)) self%comm%xgmin = xmin_glob
437  if(present(xmax_glob)) self%comm%xgmax = xmax_glob
438  if(present(ymin_glob)) self%comm%ygmin = ymin_glob
439  if(present(ymax_glob)) self%comm%ygmax = ymax_glob
440 
441  ! Note: the presence of both xgmin/xgmax will automatically set the
442  ! periodicity flag
443  if(present(xmin_glob) .and. present(xmax_glob)) self%comm%xperiodic = .true.
444  if(present(ymin_glob) .and. present(ymax_glob)) self%comm%yperiodic = .true.
445 
446  end subroutine drifters_set_domain
447 
448  !============================================================================
449  !> @brief Given an MPP based deomposition, set the PE numbers that are adjacent to this
450  !! processor.
451  !!
452  !> This will allow several PEs to track the trajectories of particles in the buffer regions.
453  subroutine drifters_set_pe_neighbors(self, domain, ermesg)
454 
455  type(drifters_type) :: self !< Opaque data structure.
456  _type_domain2d :: domain !< MPP domain.
457  character(len=*), intent(out) :: ermesg !< Error message (if any).
458 
459  ermesg = ''
460 
461  call drifters_comm_set_pe_neighbors(self%comm, domain)
462 
463  end subroutine drifters_set_pe_neighbors
464 
465  !============================================================================
466 #define _DIMS 2
467 #define drifters_push_XXX drifters_push_2
468 #include "drifters_push.fh"
469 #undef _DIMS
470 #undef drifters_push_XXX
471 
472  !============================================================================
473 #define _DIMS 3
474 #define drifters_push_XXX drifters_push_3
475 #include "drifters_push.fh"
476 #undef _DIMS
477 #undef drifters_push_XXX
478 
479  !============================================================================
480  subroutine drifters_modulo(self, positions, ermesg)
481  type(drifters_type) :: self
482  real, intent(inout) :: positions(:,:)
483  character(len=*), intent(out) :: ermesg
484 
485  integer ip, np
486  real x, y
487 
488  ermesg = ''
489  np = self%core%np
490 
491  if(self%comm%xperiodic) then
492  do ip = 1, np
493  x = positions(1, ip)
494  positions(1, ip) = self%comm%xgmin + &
495  & modulo(x - self%comm%xgmin, self%comm%xgmax-self%comm%xgmin)
496  enddo
497  endif
498 
499  if(self%comm%yperiodic) then
500  do ip = 1, np
501  y = positions(2, ip)
502  positions(2, ip) = self%comm%ygmin + &
503  & modulo(y - self%comm%ygmin, self%comm%ygmax-self%comm%ygmin)
504  enddo
505  endif
506 
507  end subroutine drifters_modulo
508 
509  !============================================================================
510 #define _DIMS 2
511 #define drifters_set_field_XXX drifters_set_field_2d
512 #include "drifters_set_field.fh"
513 #undef _DIMS
514 #undef drifters_set_field_XXX
515 
516  !============================================================================
517 #define _DIMS 3
518 #define drifters_set_field_XXX drifters_set_field_3d
519 #include "drifters_set_field.fh"
520 #undef _DIMS
521 #undef drifters_set_field_XXX
522  !============================================================================
523  !> @brief Append new positions to NetCDF file.
524  !!
525  !> Use this method to append the new trajectory positions and the interpolated
526  !! probe fields to a netCDF file.
527  subroutine drifters_save(self, ermesg)
528  type(drifters_type) :: self !< Opaque daata structure.
529  character(len=*), intent(out) :: ermesg !< Error message (if any).
530 
531  integer nf, np
532 
533  ermesg = ''
534  nf = size(self%input%field_names)
535  np = self%core%np
536 
537  ! save to disk
538  call drifters_io_write(self%io, self%time, np, self%core%nd, nf, &
539  & self%core%ids, self%core%positions, &
540  & fields=self%fields(:,1:np), ermesg=ermesg)
541 
542  end subroutine drifters_save
543  !============================================================================
544 
545  !> @brief Distribute particles across PEs.
546  !!
547  !> Use this method after setting the domain boundaries
548  !! (<TT>drifters_set_domain</TT>) to spread the particles across PE domains.
549  subroutine drifters_distribute(self, ermesg)
550  type(drifters_type) :: self !< Opaque handle.
551  character(len=*), intent(out) :: ermesg !< Error message (if any).
552 
553  real x, y
554  integer i, nptot, nd
555 
556  ermesg = ''
557  nd = self%core%nd
558  if(nd < 2) then
559  ermesg = 'drifters_distribute: dimension must be >=2'
560  return
561  endif
562 
563  nptot = size(self%input%positions, 2)
564  do i = 1, nptot
565  x = self%input%positions(1,i)
566  y = self%input%positions(2,i)
567  if(x >= self%comm%xdmin .and. x <= self%comm%xdmax .and. &
568  & y >= self%comm%ydmin .and. y <= self%comm%ydmax) then
569 
570  self%core%np = self%core%np + 1
571  self%core%positions(1:nd, self%core%np) = self%input%positions(1:nd, i)
572  self%core%ids(self%core%np) = i
573 
574  endif
575  enddo
576 
577  end subroutine drifters_distribute
578 
579  !============================================================================
580  !> @brief Write restart file for drifters.
581  !!
582  !> Gather all the particle positions distributed
583  !! across PE domains on root PE and save the data in netCDF file.
584  subroutine drifters_write_restart(self, filename, &
585  & x1, y1, geolon1, &
586  & x2, y2, geolat2, &
587  & root, mycomm, ermesg)
588  ! gather all positions and ids and save the result in
589  ! self%input data structure on PE "root", then write restart file
590 
591  type(drifters_type) :: self !< Opaque data structure.
592  character(len=*), intent(in) :: filename !< Restart file name.
593 
594  ! if these optional arguments are passed, the positions will
595  ! mapped to lon/lat degrees and saved in the file.
596  real, intent(in), optional :: x1(:) !< Pseudo-longitude axis supporting longitudes.
597  real, intent(in), optional :: y1(:) !< Pseudo-latitude axis supporting longitudes.
598  real, intent(in), optional :: geolon1(:,:) !< Longitude array (x1, y1).
599  real, intent(in), optional :: x2(:) !< Pseudo-longitude axis supporting latitudes.
600  real, intent(in), optional :: y2(:) !< Pseudo-latitude axis supporting latitudes.
601  real, intent(in), optional :: geolat2(:,:) !< Latitudes array (x2, y2)
602 
603 
604  integer, intent(in), optional :: root !< root pe
605  integer, intent(in), optional :: mycomm !< MPI communicator
606  character(len=*), intent(out) :: ermesg !< Error message (if any).
607 
608  integer :: np
609  logical :: do_save_lonlat
610  real, allocatable :: lons(:), lats(:)
611 
612  ermesg = ''
613 
614  np = self%core%np
615 
616  allocate(lons(np), lats(np))
617  lons = -huge(1.)
618  lats = -huge(1.)
619 
620  ! get lon/lat if asking for
621  if(present(x1) .and. present(y1) .and. present(geolon1) .and. &
622  & present(x2) .and. present(y2) .and. present(geolat2)) then
623  do_save_lonlat = .true.
624  else
625  do_save_lonlat = .false.
626  endif
627 
628  if(do_save_lonlat) then
629 
630  ! Interpolate positions onto geo longitudes/latitudes
631  call drifters_positions2lonlat(self, &
632  & positions=self%core%positions(:,1:np), &
633  & x1=x1, y1=y1, geolon1=geolon1, &
634  & x2=x2, y2=y2, geolat2=geolat2, &
635  & lons=lons, lats=lats, ermesg=ermesg)
636  if(ermesg/='') return ! problems, bail off
637 
638  endif
639 
640  call drifters_comm_gather(self%comm, self%core, self%input, &
641  & lons, lats, do_save_lonlat, &
642  & filename, &
643  & root, mycomm)
644 
645  end subroutine drifters_write_restart
646 
647  !============================================================================
648 #define _DIMS 2
649 #define drifters_compute_k_XXX drifters_computek2d
650 #include "drifters_compute_k.fh"
651 #undef _DIMS
652 #undef drifters_compute_k_XXX
653 
654  !============================================================================
655 #define _DIMS 3
656 #define drifters_compute_k_XXX drifters_computek3d
657 #include "drifters_compute_k.fh"
658 #undef _DIMS
659 #undef drifters_compute_k_XXX
660 
661 
662  !============================================================================
663  !> @brief Set velocity field axes.
664  !! @details Velocity axis components may be located on different grids or cell faces. For instance, zonal (u)
665  !! and meridional (v) velcity components are staggered by half a cell size in Arakawa's C and D grids.
666  !! This call will set individual axes for each components do as to allow interpolation of the velocity
667  !! field on arbitrary positions.
668  subroutine drifters_set_v_axes(self, component, x, y, z, ermesg)
669  type(drifters_type) :: self !< Opaque data structure.
670  character(len=*), intent(in) :: component !< Velocity component: either 'u', 'v', or 'w'.
671  real, intent(in) :: x(:) !< X-axis.
672  real, intent(in) :: y(:) !< Y-axis.
673  real, intent(in) :: z(:) !< Z-axis.
674  character(len=*), intent(out) :: ermesg !< Error message (if any).
675 
676  integer ier, nx, ny, nz
677 
678  ermesg = ''
679  nx = size(x)
680  ny = size(y)
681  nz = size(z)
682  select case (component(1:1))
683  case ('u', 'U')
684  if(nx > 0) then
685  deallocate(self%xu, stat=ier)
686  allocate(self%xu(nx))
687  self%xu = x
688  self%nx = max(self%nx, size(x))
689  endif
690  if(ny > 0) then
691  deallocate(self%yu, stat=ier)
692  allocate(self%yu(ny))
693  self%yu = y
694  self%ny = max(self%ny, size(y))
695  endif
696  if(nz > 0) then
697  deallocate(self%zu, stat=ier)
698  allocate(self%zu(nz))
699  self%zu = z
700  endif
701  case ('v', 'V')
702  if(nx > 0) then
703  deallocate(self%xv, stat=ier)
704  allocate(self%xv(nx))
705  self%xv = x
706  self%nx = max(self%nx, size(x))
707  endif
708  if(ny > 0) then
709  deallocate(self%yv, stat=ier)
710  allocate(self%yv(ny))
711  self%yv = y
712  self%ny = max(self%ny, size(y))
713  endif
714  if(nz > 0) then
715  deallocate(self%zv, stat=ier)
716  allocate(self%zv(nz))
717  self%zv = z
718  endif
719  case ('w', 'W')
720  if(nx > 0) then
721  deallocate(self%xw, stat=ier)
722  allocate(self%xw(nx))
723  self%xw = x
724  self%nx = max(self%nx, size(x))
725  endif
726  if(ny > 0) then
727  deallocate(self%yw, stat=ier)
728  allocate(self%yw(ny))
729  self%yw = y
730  self%ny = max(self%ny, size(y))
731  endif
732  if(nz > 0) then
733  deallocate(self%zw, stat=ier)
734  allocate(self%zw(nz))
735  self%zw = z
736  endif
737  case default
738  ermesg = 'drifters_set_v_axes: ERROR component must be "u", "v" or "w"'
739  end select
740  end subroutine drifters_set_v_axes
741 
742  !============================================================================
743  !> @brief Set boundaries of "data" and "compute" domains
744  !! @details Each particle will be tracked sol long is it is located in the data domain.
745  subroutine drifters_set_domain_bounds(self, domain, backoff_x, backoff_y, ermesg)
746  type(drifters_type) :: self !< Opaque data structure.
747  _type_domain2d :: domain !< Instance of Domain2D (see mpp_domain)
748  integer, intent(in) :: backoff_x !< particles leaves domain when crossing ied-backoff_x
749  integer, intent(in) :: backoff_y !< particles leaves domain when crossing jed-backoff_y
750  character(len=*), intent(out) :: ermesg !< Error message (if any).
751 
752  ermesg = ''
753 
754  if(.not.allocated(self%xu) .or. .not.allocated(self%yu)) then
755  ermesg = 'drifters_set_domain_bounds: ERROR "u"-component axes not set'
756  return
757  endif
758  call drifters_comm_set_domain(self%comm, domain, self%xu, self%yu, backoff_x, backoff_y)
759  if(.not.allocated(self%xv) .or. .not.allocated(self%yv)) then
760  ermesg = 'drifters_set_domain_bounds: ERROR "v"-component axes not set'
761  return
762  endif
763  if(allocated(self%xw) .and. allocated(self%yw)) then
764  call drifters_comm_set_domain(self%comm, domain, self%xv, self%yv, backoff_x, backoff_y)
765  endif
766 
767 
768  end subroutine drifters_set_domain_bounds
769 
770  !============================================================================
771  !> @brief Interpolates positions onto longitude/latitude grid.
772  !! @details In many cases, the integrated positions will not be longitudes or latitudes. This call
773  !! can be ionvoked to recover the longitude/latitude positions from the "logical" positions.
774  subroutine drifters_positions2lonlat(self, positions, &
775  & x1, y1, geolon1, &
776  & x2, y2, geolat2, &
777  & lons, lats, &
778  & ermesg)
779 
780  type(drifters_type) :: self !< Opaque data structure.
781  ! Input positions
782  real, intent(in) :: positions(:,:) !< Logical positions.
783  ! Input mesh
784  real, intent(in) :: x1(:) !< X-axis of "geolon1" field.
785  real, intent(in) :: y1(:) !< Y-axis of "geolon1" field.
786  real, intent(in) :: geolon1(:,:) !< Y-axis of "geolon1" field.
787  real, intent(in) :: x2(:) !< X-axis of "geolat2" field.
788  real, intent(in) :: y2(:) !< Y-axis of "geolat2" field.
789  real, intent(in) :: geolat2(:,:) !< Latitude field as an array of (x2, y2)
790  ! Output lon/lat
791  real, intent(out) :: lons(:) !< Returned longitudes.
792  real, intent(out) :: lats(:) !< Returned latitudes.
793  character(len=*), intent(out) :: ermesg !< Error message (if any).
794 
795  real fvals(2**self%core%nd), ts(self%core%nd)
796  integer np, ij(2), ip, ier, n1s(2), n2s(2), i, j, iertot
797  character(len=10) :: n1_str, n2_str, np_str, iertot_str
798 
799  ermesg = ''
800  lons = -huge(1.)
801  lats = -huge(1.)
802 
803  ! check dimensions
804  n1s = (/size(x1), size(y1)/)
805  n2s = (/size(x2), size(y2)/)
806  if(n1s(1) /= size(geolon1, 1) .or. n1s(2) /= size(geolon1, 2)) then
807  ermesg = 'drifters_positions2geolonlat: ERROR incompatibles dims between (x1, y1, geolon1)'
808  return
809  endif
810  if(n2s(1) /= size(geolat2, 1) .or. n2s(2) /= size(geolat2, 2)) then
811  ermesg = 'drifters_positions2geolonlat: ERROR incompatibles dims between (x2, y2, geolat2)'
812  return
813  endif
814 
815  np = size(positions, 2)
816  if(size(lons) < np .or. size(lats) < np) then
817  write(np_str, '(i10)') np
818  write(n1_str, '(i10)') size(lons)
819  write(n2_str, '(i10)') size(lats)
820  ermesg = 'drifters_positions2geolonlat: ERROR size of "lons" ('//trim(n1_str)// &
821  & ') or "lats" ('//trim(n2_str)//') < '//trim(np_str)
822  return
823  endif
824 
825  ! Interpolate
826  iertot = 0
827  do ip = 1, np
828 
829  ! get longitude
830  call cld_ntrp_locate_cell(x1, positions(1,ip), i, ier)
831  iertot = iertot + ier
832  call cld_ntrp_locate_cell(y1, positions(2,ip), j, ier)
833  iertot = iertot + ier
834  ij(1) = i; ij(2) = j;
835  call cld_ntrp_get_cell_values(n1s, _flatten(geolon1), ij, fvals, ier)
836  iertot = iertot + ier
837  ts(1) = (positions(1,ip) - x1(i))/(x1(i+1) - x1(i))
838  ts(2) = (positions(2,ip) - y1(j))/(y1(j+1) - y1(j))
839  call cld_ntrp_linear_cell_interp(fvals, ts, lons(ip), ier)
840  iertot = iertot + ier
841 
842  ! get latitude
843  call cld_ntrp_locate_cell(x2, positions(1,ip), i, ier)
844  iertot = iertot + ier
845  call cld_ntrp_locate_cell(y2, positions(2,ip), j, ier)
846  iertot = iertot + ier
847  ij(1) = i; ij(2) = j;
848  call cld_ntrp_get_cell_values(n2s, _flatten(geolat2), ij, fvals, ier)
849  iertot = iertot + ier
850  ts(1) = (positions(1,ip) - x2(i))/(x2(i+1) - x2(i))
851  ts(2) = (positions(2,ip) - y2(j))/(y2(j+1) - y2(j))
852  call cld_ntrp_linear_cell_interp(fvals, ts, lats(ip), ier)
853  iertot = iertot + ier
854 
855  enddo
856 
857  if(iertot /= 0) then
858  write(iertot_str, '(i10)') iertot
859  ermesg = 'drifters_positions2geolonlat: ERROR '//trim(iertot_str)// &
860  & ' interpolation errors (domain out of bounds?)'
861  endif
862 
863  end subroutine drifters_positions2lonlat
864 
865  !============================================================================
866  !> @brief Print Runge-Kutta check sums. Useful for debugging only.
867  subroutine drifters_print_checksums(self, pe, ermesg)
868 
869  type(drifters_type) :: self !< Opaque handle.
870  integer, intent(in), optional :: pe !< Processor element.
871  character(len=*), intent(out) :: ermesg !< Error message (if any).
872 
873  integer, parameter :: i8 = selected_int_kind(13)
874  integer(i8) :: mold, chksum_pos, chksum_k1, chksum_k2, chksum_k3, chksum_k4
875  integer(i8) :: chksum_tot
876  integer nd, np, me
877 
878  ermesg = ''
879 
880  if(.not. present(pe)) then
881  me = _mpp_pe
882  else
883  me = pe
884  endif
885 
886  if(me == _mpp_pe) then
887 
888  nd = self%core%nd
889  np = self%core%np
890  chksum_pos = transfer(sum(sum(self%core%positions(1:nd,1:np),1)), mold)
891  chksum_k1 = transfer(sum(sum(self%rk4_k1(1:nd,1:np),1)), mold)
892  chksum_k2 = transfer(sum(sum(self%rk4_k2(1:nd,1:np),1)), mold)
893  chksum_k3 = transfer(sum(sum(self%rk4_k3(1:nd,1:np),1)), mold)
894  chksum_k4 = transfer(sum(sum(self%rk4_k4(1:nd,1:np),1)), mold)
895  chksum_tot = chksum_pos + chksum_k1 + chksum_k2 + chksum_k3 +chksum_k4
896 
897  print *,'==============drifters checksums=========================='
898  print '(a,i25,a,i6,a,e15.7)','==positions: ', chksum_pos, ' PE=', me, ' time = ', self%time
899  print '(a,i25,a,i6,a,e15.7)','==k1 : ', chksum_k1, ' PE=', me, ' time = ', self%time
900  print '(a,i25,a,i6,a,e15.7)','==k2 : ', chksum_k2, ' PE=', me, ' time = ', self%time
901  print '(a,i25,a,i6,a,e15.7)','==k3 : ', chksum_k3, ' PE=', me, ' time = ', self%time
902  print '(a,i25,a,i6,a,e15.7)','==k4 : ', chksum_k4, ' PE=', me, ' time = ', self%time
903  print '(a,i25,a,i6,a,e15.7)','==total : ', chksum_tot, ' PE=', me, ' time = ', self%time
904 
905  endif
906 
907  end subroutine drifters_print_checksums
908 
909  subroutine drifters_reset_rk4(self, ermesg)
910  type(drifters_type) :: self
911  character(len=*), intent(out) :: ermesg
912 
913  integer ier, nd
914 
915  ermesg = ''
916 
917  if(size(self%rk4_k1, 2) < self%core%np) then
918  deallocate(self%rk4_k1, stat=ier)
919  allocate(self%rk4_k1(self%core%nd, self%core%npdim))
920  self%rk4_k1 = 0
921  endif
922  if(size(self%rk4_k2, 2) < self%core%np) then
923  deallocate(self%rk4_k2, stat=ier)
924  allocate(self%rk4_k2(self%core%nd, self%core%npdim))
925  self%rk4_k2 = 0
926  endif
927  if(size(self%rk4_k3, 2) < self%core%np) then
928  deallocate(self%rk4_k3, stat=ier)
929  allocate(self%rk4_k3(self%core%nd, self%core%npdim))
930  self%rk4_k3 = 0
931  endif
932  if(size(self%rk4_k4, 2) < self%core%np) then
933  deallocate(self%rk4_k4, stat=ier)
934  allocate(self%rk4_k4(self%core%nd, self%core%npdim))
935  self%rk4_k4 = 0
936  endif
937 
938  if(size(self%remove) < self%core%np) then
939  deallocate(self%remove, stat=ier)
940  allocate(self%remove(self%core%npdim))
941  self%remove = .false.
942  endif
943 
944  if(size(self%temp_pos, 2) < self%core%np) then
945  deallocate(self%temp_pos, stat=ier)
946  nd = size(self%input%velocity_names)
947  allocate(self%temp_pos(nd, self%core%npdim))
948  self%temp_pos = -huge(1.)
949  endif
950 
951  end subroutine drifters_reset_rk4
952 #endif
953 end module drifters_mod
954 !> @}
955 ! close documentation grouping
The domain2D type contains all the necessary information to define the global, compute and data domai...
integer function mpp_npes()
Returns processor count for current pelist.
Definition: mpp_util.inc:421
integer function mpp_pe()
Returns processor ID.
Definition: mpp_util.inc:407