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