FMS 2025.01-dev
Flexible Modeling System
Loading...
Searching...
No Matches
mpp_pset.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#ifdef test_mpp_pset
20!PSET_DEBUG is always turned on in the test program
21#define PSET_DEBUG
22#endif
23
24!> @defgroup mpp_pset_mod mpp_pset_mod
25!> @ingroup mpp
26!> @brief Handles PSETs(Persistent Shared-memory Execution Threads) for mpp modules
27!!
28!! @author V. Balaji (v.balaji@noaa.gov)
29!! @date 2006-01-15
30
31!> @addtogroup mpp_pset_mod mpp_pset_mod
32!> @{
33module mpp_pset_mod
34#include <fms_platform.h>
35 use mpp_mod, only: mpp_pe, mpp_npes, mpp_root_pe, mpp_send, mpp_recv, &
36 mpp_sync, mpp_error, fatal, warning, stdout, stderr, mpp_chksum, &
37 mpp_declare_pelist, mpp_get_current_pelist, mpp_set_current_pelist, &
38 mpp_init, comm_tag_1, comm_tag_2, comm_tag_3, mpp_sync_self
39 implicit none
40 private
41
42!private variables
43 integer :: pe
44 integer :: commID !MPI communicator, copy here from pset
45 logical :: verbose=.false.
46 logical :: module_is_initialized=.false.
47 character(len=256) :: text
48!generic interfaces
50 module procedure mpp_pset_broadcast_ptr_scalar
51 module procedure mpp_pset_broadcast_ptr_array
52 end interface
53 interface mpp_send_ptr
54 module procedure mpp_send_ptr_scalar
55 module procedure mpp_send_ptr_array
56 end interface
57 interface mpp_recv_ptr
58 module procedure mpp_recv_ptr_scalar
59 module procedure mpp_recv_ptr_array
60 end interface
62 module procedure mpp_pset_print_chksum_1d
63 module procedure mpp_pset_print_chksum_2d
64 module procedure mpp_pset_print_chksum_3d
65 module procedure mpp_pset_print_chksum_4d
66 end interface
67!public type
69 private
70 integer :: npset !number of PSETs
71 integer :: next_in_pset, prev_in_pset !next and prev PE in PSET (cyclic)
72 integer :: root_in_pset !PE designated to be the root within PSET
73 logical :: root !true if you are the root PSET
74 integer :: pos !position of current PE within pset
75!stack is allocated by root
76!it is then mapped to mpp_pset_stack by mpp_pset_broadcast_ptr
77 real, allocatable :: stack(:)
78 integer, allocatable :: pelist(:) !base PElist
79 integer, allocatable :: root_pelist(:) !a PElist of all the roots
80 integer, allocatable :: pset(:) !PSET IDs
81 integer(POINTER_KIND) :: p_stack
82 integer :: lstack, maxstack, hiWM !current stack length, max, hiWM
83 integer :: commID
84 character(len=32) :: name
85 logical :: initialized=.false.
86 end type mpp_pset_type
87!public types
88 public :: mpp_pset_type
89!public variables
90!public member functions
91 public :: mpp_pset_create, mpp_pset_sync, mpp_pset_broadcast, &
92 mpp_pset_broadcast_ptr, mpp_pset_check_ptr, mpp_pset_segment_array, &
93 mpp_pset_stack_push, mpp_pset_stack_reset, mpp_pset_print_chksum, &
94 mpp_pset_delete, mpp_pset_root, mpp_pset_numroots, mpp_pset_init, &
95 mpp_pset_get_root_pelist, mpp_pset_print_stack_chksum
96
97
98contains
99 subroutine mpp_pset_init
100 module_is_initialized = .true.
101 end subroutine mpp_pset_init
102
103 subroutine mpp_pset_create(npset,pset,stacksize,pelist, commID)
104!create PSETs
105! called by all PEs in parent pelist
106! mpset must be exact divisor of npes
107 integer, intent(in) :: npset !number of PSETs per set
108 type(mpp_pset_type), intent(inout) :: pset
109 integer, intent(in), optional :: stacksize
110 integer, intent(in), optional :: pelist(:)
111 integer, intent(in), optional :: commID
112
113 integer :: npes, my_commID
114 integer :: i, j, k, out_unit, errunit
115 integer, allocatable :: my_pelist(:), root_pelist(:)
116
117 call mpp_init()
118 call mpp_pset_init()
119
120#ifdef PSET_DEBUG
121 verbose=.true.
122#endif
123 out_unit = stdout()
124 errunit = stderr()
125 pe = mpp_pe()
126 if(present(pelist)) then
127 npes = size(pelist(:))
128 else
129 npes = mpp_npes()
130 endif
131 if( mod(npes,npset).NE.0 )then
132 write( text,'(a,2i6)' ) &
133 'MPP_PSET_CREATE: PSET size (npset) must divide npes exactly:'// &
134 ' npset, npes=', npset, npes
135 call mpp_error( fatal, text )
136 end if
137
138 !configure out root_pelist
139 allocate(my_pelist(0:npes-1) )
140 allocate(root_pelist(0:npes/npset-1) )
141 if(present(pelist)) then
142 if(.not. present(commid)) call mpp_error(fatal, &
143 'MPP_PSET_CREATE: when pelist is present, commID should also be present')
144 my_pelist = pelist
145 my_commid = commid
146 else
147 call mpp_get_current_pelist(my_pelist, commid = my_commid)
148 endif
149 do i = 0,npes/npset-1
150 root_pelist(i) = my_pelist(npset*i)
151 enddo
152 write( out_unit,'(a,i6)' )'MPP_PSET_CREATE creating PSETs... npset=', npset
153 if(any(my_pelist == pe) ) then
154 if( pset%initialized )call mpp_error( fatal, &
155 'MPP_PSET_CREATE: PSET already initialized!' )
156 pset%npset = npset
157 allocate( pset%pelist(0:npes-1) )
158 allocate( pset%root_pelist(0:npes/npset-1) )
159 pset%commID = my_commid
160 pset%pelist = my_pelist
161!create the root PElist
162 pset%root_pelist = root_pelist
163 allocate( pset%pset(0:npset-1) )
164 do i = 0,npes/npset-1
165 k = npset*i
166!designate the root PE, next PE, prev PE
167 do j = 0,npset-1
168 if( pe.EQ.pset%pelist(k+j) )then
169 pset%pset(:) = pset%pelist(k:k+npset-1)
170 pset%pos = j
171 pset%root_in_pset = pset%root_pelist(i)
172 if( j.EQ.0 )then
173 pset%prev_in_pset = pset%pelist(k+npset-1)
174 else
175 pset%prev_in_pset = pset%pelist(k+j-1)
176 end if
177 if( j.EQ.npset-1 )then
178 pset%next_in_pset = pset%pelist(k)
179 else
180 pset%next_in_pset = pset%pelist(k+j+1)
181 end if
182 end if
183 end do
184 end do
185
186 pset%root = pe.EQ.pset%root_in_pset
187
188!stack
189 pset%hiWM = 0 !initialize hi-water-mark
190 pset%maxstack = 1000000 !default
191 if( PRESENT(stacksize) )pset%maxstack = stacksize
192 write( out_unit,'(a,i8)' ) &
193 'MPP_PSET_CREATE: setting stacksize=', pset%maxstack
194 if( pset%root )then
195 allocate( pset%stack(pset%maxstack) )
196#ifdef use_CRI_pointers
197 pset%p_stack = loc(pset%stack)
198#endif
199 end if
200 pset%initialized = .true. !must be called before using pset
201 call mpp_pset_broadcast_ptr(pset,pset%p_stack)
202 endif
203
204 call mpp_declare_pelist(root_pelist)
205
206 if( verbose )then
207 write( errunit,'(a,4i6)' )'MPP_PSET_CREATE: pe, root, next, prev=', &
208 pe, pset%root_in_pset, pset%next_in_pset, pset%prev_in_pset
209 write( errunit,* )'PE ', pe, ' pset=', pset%pset(:)
210 write( out_unit,* )'root pelist=', pset%root_pelist(:)
211 end if
212 end subroutine mpp_pset_create
213
214 subroutine mpp_pset_delete(pset)
215 type(mpp_pset_type), intent(inout) :: pset
216 integer :: out_unit
217
218 out_unit = stdout()
219 if( .NOT.pset%initialized )call mpp_error( fatal, &
220 'MPP_PSET_DELETE: called with uninitialized PSET.' )
221!deallocate arrays...
222 deallocate( pset%pelist )
223 deallocate( pset%root_pelist )
224 deallocate( pset%pset )
225 if( pset%root )deallocate( pset%stack )
226 write( out_unit, '(a,i10)' ) &
227 'Deleting PSETs... stack high-water-mark=', pset%hiWM
228!... and set status flag
229 pset%initialized = .false.
230 end subroutine mpp_pset_delete
231
232 subroutine mpp_send_ptr_scalar( ptr, pe )
233 integer(POINTER_KIND), intent(in) :: ptr
234 integer, intent(in) :: pe
235
236!currently only wraps mpp_send
237!on some architectures, mangling might occur
238 call mpp_send( ptr, pe, tag=comm_tag_1 )
239 end subroutine mpp_send_ptr_scalar
240
241 subroutine mpp_send_ptr_array( ptr, pe )
242 integer(POINTER_KIND), intent(in) :: ptr(:)
243 integer, intent(in) :: pe
244
245!currently only wraps mpp_send
246!on some architectures, mangling might occur
247 call mpp_send( ptr, size(ptr), pe, tag=comm_tag_2 )
248 end subroutine mpp_send_ptr_array
249
250 subroutine mpp_recv_ptr_scalar( ptr, pe )
251 integer(POINTER_KIND), intent(inout) :: ptr
252 integer, intent(in) :: pe
253
254 call mpp_recv( ptr, pe, tag=comm_tag_1 )
255 call mpp_translate_remote_ptr( ptr, pe )
256 return
257 end subroutine mpp_recv_ptr_scalar
258
259 subroutine mpp_recv_ptr_array( ptr, pe )
260 integer(POINTER_KIND), intent(inout) :: ptr(:)
261 integer, intent(in) :: pe
262 integer :: i
263
264 call mpp_recv( ptr, size(ptr), pe, tag=comm_tag_2 )
265 do i = 1, size(ptr)
266 call mpp_translate_remote_ptr( ptr(i), pe )
267 end do
268 return
269 end subroutine mpp_recv_ptr_array
270
271 subroutine mpp_translate_remote_ptr( ptr, pe )
272!modifies the received pointer to correct numerical address
273 integer(POINTER_KIND), intent(inout) :: ptr
274 integer, intent(in) :: pe
275 return
276 end subroutine mpp_translate_remote_ptr
277
278 subroutine mpp_pset_sync(pset)
279!this is a replacement for mpp_sync, doing syncs across
280!shared arrays without calling mpp_sync
281 type(mpp_pset_type), intent(in) :: pset
282
283 if( .NOT.pset%initialized )call mpp_error( fatal, &
284 'MPP_PSET_SYNC: called with uninitialized PSET.' )
285!currently does mpp_sync!!! slow!!!
286!try and make a lightweight pset sync
287 call mpp_sync
288 end subroutine mpp_pset_sync
289
290 subroutine mpp_pset_broadcast(pset,a)
291!broadcast value on the root to its sub-threads
292 type(mpp_pset_type), intent(in) :: pset
293 real, intent(inout) :: a
294 integer :: i
295
296 if( .NOT.pset%initialized )call mpp_error( fatal, &
297 'MPP_PSET_BROADCAST: called with uninitialized PSET.' )
298 if( pset%root )then
299 do i = 1,pset%npset-1
300 call mpp_send( a, pset%pset(i), tag=comm_tag_3 )
301 end do
302 else
303 call mpp_recv( a, pset%root_in_pset, tag=comm_tag_3 )
304 end if
305 call mpp_pset_sync(pset)
306 end subroutine mpp_pset_broadcast
307
308 subroutine mpp_pset_broadcast_ptr_scalar(pset,ptr)
309!create a shared array by broadcasting pointer
310!root allocates memory and passes pointer in
311!on return all other PSETs will have the pointer to a shared object
312 type(mpp_pset_type), intent(in) :: pset
313 integer(POINTER_KIND), intent(inout) :: ptr
314 integer :: i
315
316 if( .NOT.pset%initialized )call mpp_error( fatal, &
317 'MPP_PSET_BROADCAST_PTR: called with uninitialized PSET.' )
318 commid = pset%commID !pass to mpp_translate_remote_ptr
319 if( pset%root )then
320 do i = 1,pset%npset-1
321 call mpp_send_ptr( ptr, pset%pset(i) )
322 end do
323 else
324 call mpp_recv_ptr( ptr, pset%root_in_pset )
325 end if
326 call mpp_sync_self()
327 end subroutine mpp_pset_broadcast_ptr_scalar
328
329 subroutine mpp_pset_broadcast_ptr_array(pset,ptr)
330!create a shared array by broadcasting pointer
331!root allocates memory and passes pointer in
332!on return all other PSETs will have the pointer to a shared object
333 type(mpp_pset_type), intent(in) :: pset
334 integer(POINTER_KIND), intent(inout) :: ptr(:)
335 integer :: i
336
337 if( .NOT.pset%initialized )call mpp_error( fatal, &
338 'MPP_PSET_BROADCAST_PTR: called with uninitialized PSET.' )
339 commid = pset%commID !pass to mpp_translate_remote_ptr
340 if( pset%root )then
341 do i = 1,pset%npset-1
342 call mpp_send_ptr( ptr, pset%pset(i) )
343 end do
344 else
345 call mpp_recv_ptr( ptr, pset%root_in_pset )
346 end if
347 call mpp_sync_self()
348
349 end subroutine mpp_pset_broadcast_ptr_array
350
351 subroutine mpp_pset_check_ptr(pset,ptr)
352!checks if the supplied pointer is indeed shared
353 type(mpp_pset_type), intent(in) :: pset
354#ifdef use_CRI_pointers
355 real :: dummy
356 pointer( ptr, dummy )
357#else
358 integer(POINTER_KIND), intent(in) :: ptr
359#endif
360#ifdef PSET_DEBUG
361 integer(POINTER_KIND) :: p
362 integer :: i
363 if( .NOT.pset%initialized )call mpp_error( fatal, &
364 'MPP_PSET_CHECK_PTR: called with uninitialized PSET.' )
365 commid = pset%commID !pass to mpp_translate_remote_ptr
366!check if this is a shared pointer
367 p = ptr
368 if( pset%root )then
369 do i = 1,pset%npset-1
370 call mpp_send_ptr( p, pset%pset(i) )
371 end do
372 else
373 call mpp_recv_ptr( p, pset%root_in_pset )
374 end if
375 call mpp_pset_sync(pset)
376 if( p.NE.ptr )call mpp_error( fatal, &
377 'MPP_PSET_CHECK_PTR: pointers do not match!' )
378#else
379!do nothing if the debug CPP flag isn't on
380#endif
381 end subroutine mpp_pset_check_ptr
382
383 subroutine mpp_pset_segment_array( pset, ls, le, lsp, lep )
384!given input indices ls, le, returns indices lsp, lep
385!so that segments span the range ls:le with no overlaps.
386!attempts load balance: also some PSETs might get lsp>lep
387!so that do-loops will be null
388 type(mpp_pset_type), intent(in) :: pset
389 integer, intent(in) :: ls, le
390 integer, intent(out) :: lsp, lep
391 integer :: i
392
393 if( .NOT.pset%initialized )call mpp_error( fatal, &
394 'MPP_PSET_SEGMENT_ARRAY: called with uninitialized PSET.' )
395#ifdef PSET_DEBUG
396 if( le-ls+1.LT.pset%npset )then
397 write( text,'(3(a,i6))' ) &
398 'MPP_PSET_ARRAY_SEGMENT: parallel range (', ls, ',', le, &
399 ') is smaller than the number of threads:', pset%npset
400 call mpp_error( warning, text )
401 end if
402#endif
403 lep = ls-1 !initialize so that lsp is correct on first pass
404 do i = 0,pset%pos
405 lsp = lep + 1
406 lep = lsp + ceiling( real(le-lsp+1)/(pset%npset-i) ) - 1
407 end do
408 end subroutine mpp_pset_segment_array
409
410 subroutine mpp_pset_stack_push( pset, ptr, len )
411!mpp_malloc specialized for shared arrays
412!len is the length of the required array
413!lstack is the stack already in play
414!user should zero lstack (call mpp_pset_stack_reset) when the stack is to be cleared
415 type(mpp_pset_type), intent(inout) :: pset
416 integer, intent(in) :: len
417#ifdef use_CRI_pointers
418 real :: dummy
419 pointer( ptr, dummy )
420 real :: stack(pset%maxstack)
421 pointer( p, stack )
422
423 if( .NOT.pset%initialized )call mpp_error( fatal, &
424 'MPP_PSET_STACK_PUSH: called with uninitialized PSET.' )
425 if( pset%lstack+len.GT.pset%maxstack )then
426 write( text, '(a,3i12)' ) &
427 'MPP_PSET_STACK_PUSH: mpp_pset_stack overflow: '// &
428 .GT.'len+lstackmaxstack. len, lstack, maxstack=', &
429 len, pset%lstack, pset%maxstack
430 call mpp_error( fatal, text )
431 end if
432 p = pset%p_stack !point stack to shared stack pointer
433 ptr = loc( stack(pset%lstack+1) )
434 call mpp_pset_check_ptr(pset,ptr) !make sure ptr is the same across PSETs
435 pset%lstack = pset%lstack + len
436 pset%hiWM = max( pset%hiWM, pset%lstack )
437#else
438 integer(POINTER_KIND), intent(out) :: ptr
439 call mpp_error( fatal, &
440 'MPP_PSET_STACK_PUSH only works with Cray pointers.' )
441#endif
442 end subroutine mpp_pset_stack_push
443
444 subroutine mpp_pset_stack_reset(pset)
445 type(mpp_pset_type), intent(inout) :: pset
446!reset stack... will reuse any temporary arrays! USE WITH CARE
447!next few lines are to zero stack contents...
448!but it's better noone tries to use uninitialized stack variables!
449! integer :: l1, l2
450! real :: mpp_pset_stack(maxstack)
451! pointer( p_mpp_pset_stack, mpp_pset_stack )
452! p_mpp_pset_stack = ptr_mpp_pset_stack
453! call mpp_pset_array_segment( 1, lstack, l1, l2 )
454! mpp_pset_stack(l1:l2) = 0.
455 if( .NOT.pset%initialized )call mpp_error( fatal, &
456 'MPP_PSET_STACK_RESET: called with uninitialized PSET.' )
457 pset%lstack = 0
458 end subroutine mpp_pset_stack_reset
459
460 subroutine mpp_pset_print_chksum_1d(pset, caller, array)
461!print a checksum of an array
462!pass the whole domain seen by root PSET
463!add lines to check on shared array?
464 type(mpp_pset_type), intent(in) :: pset
465 character(len=*), intent(in) :: caller
466 real, intent(in) :: array(:)
467
468#ifdef PSET_DEBUG
469 integer :: errunit
470 logical :: do_print
471 integer(LONG_KIND) :: chksum
472
473 if( .NOT.pset%initialized )call mpp_error( fatal, &
474 'MPP_PSET_PRINT_CHKSUM: called with uninitialized PSET.' )
475 errunit = stderr()
476
477 if( pset%root )then
478 do_print = pe.EQ.mpp_root_pe() !set to T to print from all PEs
479 call mpp_set_current_pelist(pset%root_pelist)
480 chksum = mpp_chksum( array )
481 if( do_print ) &
482 write( errunit, '(a,z18)' )trim(caller)//' chksum=', chksum
483 end if
484 call mpp_set_current_pelist(pset%pelist)
485#endif
486 return
487 end subroutine mpp_pset_print_chksum_1d
488
489 subroutine mpp_pset_print_chksum_2d(pset, caller, array)
490 type(mpp_pset_type), intent(in) :: pset
491 character(len=*), intent(in) :: caller
492 real, intent(in) :: array(:,:)
493 real :: array1D( size(array) )
494#ifdef use_CRI_pointers
495 pointer( p, array1d )
496 p = loc(array)
497#else
498 array1d = transfer( array, array1d )
499#endif
500 call mpp_pset_print_chksum(pset, caller, array1d)
501 end subroutine mpp_pset_print_chksum_2d
502
503 subroutine mpp_pset_print_chksum_3d(pset, caller, array)
504 type(mpp_pset_type), intent(in) :: pset
505 character(len=*), intent(in) :: caller
506 real, intent(in) :: array(:,:,:) !overload for other ranks
507 real :: array1D( size(array) )
508#ifdef use_CRI_pointers
509 pointer( p, array1d )
510 p = loc(array)
511#else
512 array1d = transfer( array, array1d )
513#endif
514 call mpp_pset_print_chksum(pset, caller, array1d)
515 end subroutine mpp_pset_print_chksum_3d
516
517 subroutine mpp_pset_print_chksum_4d(pset, caller, array)
518 type(mpp_pset_type), intent(in) :: pset
519 character(len=*), intent(in) :: caller
520 real, intent(in) :: array(:,:,:,:)
521 real :: array1D( size(array) )
522#ifdef use_CRI_pointers
523 pointer( p, array1d )
524 p = loc(array)
525#else
526 array1d = transfer( array, array1d )
527#endif
528 call mpp_pset_print_chksum(pset, caller, array1d)
529 end subroutine mpp_pset_print_chksum_4d
530
531 subroutine mpp_pset_print_stack_chksum( pset, caller )
532 type(mpp_pset_type), intent(in) :: pset
533 character(len=*), intent(in) :: caller
534
535 if( .NOT.pset%initialized )call mpp_error( fatal, &
536 'MPP_PSET_PRINT_STACK_CHKSUM: called with uninitialized PSET.' )
537 call mpp_pset_print_chksum( pset, trim(caller)//' stack', &
538 pset%stack(1:pset%lstack) )
539 end subroutine mpp_pset_print_stack_chksum
540
541!accessor functions
542 function mpp_pset_root(pset)
543 logical :: mpp_pset_root
544 type(mpp_pset_type), intent(in) :: pset
545
546 if( .NOT.pset%initialized )call mpp_error( fatal, &
547 'MPP_PSET_ROOT: called with uninitialized PSET.' )
548 mpp_pset_root = pset%root
549 end function mpp_pset_root
550
551 function mpp_pset_numroots(pset)
552!necessary to export root_pelist: caller needs to pre-allocate
553 integer :: mpp_pset_numroots
554 type(mpp_pset_type), intent(in) :: pset
555
556 if( .NOT.pset%initialized )call mpp_error( fatal, &
557 'MPP_PSET_NUMROOTS: called with uninitialized PSET.' )
558 mpp_pset_numroots = size(pset%root_pelist)
559 end function mpp_pset_numroots
560
561 subroutine mpp_pset_get_root_pelist(pset,pelist,commID)
562 type(mpp_pset_type), intent(in) :: pset
563 integer, intent(out) :: pelist(:)
564 integer, intent(out), optional :: commID
565
566 if( .NOT.pset%initialized )call mpp_error( fatal, &
567 'MPP_PSET_GET_ROOT_PELIST: called with uninitialized PSET.' )
568 if( size(pelist).NE.size(pset%root_pelist) )then
569 write( text,'(a,2i6)' ) &
570 'pelist argument has wrong size: requested, actual=', &
571 size(pelist), size(pset%root_pelist)
572 call mpp_error( fatal, 'MPP_PSET_GET_ROOT_PELIST: '//text )
573 end if
574 pelist(:) = pset%root_pelist(:)
575 if( PRESENT(commid) )then
576#ifdef use_libMPI
577 commid = pset%commID
578#else
579 call mpp_error( warning, &
580 'MPP_PSET_GET_ROOT_PELIST: commID is only defined under -Duse_libMPI.' )
581#endif
582 end if
583 end subroutine mpp_pset_get_root_pelist
584
585end module mpp_pset_mod
586!> @}
subroutine mpp_sync_self(pelist, check, request, msg_size, msg_type)
This is to check if current PE's outstanding puts are complete but we can't use shmem_fence because w...
subroutine mpp_sync(pelist, do_self)
Synchronize PEs in list.
Calculate parallel checksums.
Definition mpp.F90:1200
Error handler.
Definition mpp.F90:382
Recieve data from another PE.
Definition mpp.F90:937
Send data to a receiving PE.
Definition mpp.F90:1004