FMS  2024.03
Flexible Modeling System
ensemble_manager.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 !> @defgroup ensemble_manager_mod ensemble_manager_mod
20 !> @ingroup coupler
21 !> @brief Routines for setting up and managing ensembles and ensemble pe lists.
22 
23 !> @addtogroup ensemble_manager_mod
24 !> @{
25 module ensemble_manager_mod
26 
27 
28  use fms_mod, only : check_nml_error
29  use mpp_mod, only : mpp_npes, stdout, stdlog, mpp_error, fatal
30  use mpp_mod, only : mpp_pe, mpp_declare_pelist
31  use mpp_mod, only : input_nml_file
32  use fms2_io_mod, only : fms2_io_set_filename_appendix=>set_filename_appendix
33 #ifdef use_deprecated_io
34  use fms_io_mod, only : fms_io_set_filename_appendix=>set_filename_appendix
35 #endif
36 
37  IMPLICIT NONE
38 
39  private
40 
41  integer, parameter :: MAX_ENSEMBLE_SIZE = 100
42 
43 
44  integer, allocatable, dimension(:,:) :: ensemble_pelist
45  integer, allocatable, dimension(:,:) :: ensemble_pelist_ocean
46  integer, allocatable, dimension(:,:) :: ensemble_pelist_atmos
47  integer, allocatable, dimension(:,:) :: ensemble_pelist_land
48  integer, allocatable, dimension(:,:) :: ensemble_pelist_ice
49  integer, allocatable, dimension(:) :: ensemble_pelist_ocean_filter
50  integer, allocatable, dimension(:) :: ensemble_pelist_atmos_filter
51  integer, allocatable, dimension(:) :: ensemble_pelist_land_filter
52  integer, allocatable, dimension(:) :: ensemble_pelist_ice_filter
53 
54  integer :: ensemble_size = 1
55  integer :: ensemble_id = 1
56  integer :: pe, total_npes_pm=0,ocean_npes_pm=0,atmos_npes_pm=0
57  integer :: land_npes_pm=0,ice_npes_pm=0
58 
60  public :: ensemble_pelist_setup
62 contains
63 
64 !> @brief Initializes @ref ensemble_manager_mod
65 !!
66 !> @throw FATAL, "ensemble_manager_mod: ensemble_nml variable ensemble_size must be a positive integer"
67 !! @throw FATAL, "ensemble_manager_mod: ensemble_nml variable ensemble_size should be no larger
68 !! than MAX_ENSEMBLE_SIZE, change ensemble_size or increase MAX_ENSEMBLE_SIZE"
69 !! @throw FATAL, "ensemble_size must be divis by npes"
70 !! @throw FATAL, "get_ensemble_pelist: size of pelist 1st index < ensemble_size"
71 !! @throw FATAL, "get_ensemble_pelist: size of pelist 2nd index < ocean_npes_pm"
72 !! @throw FATAL, "get_ensemble_pelist: size of pelist 2nd index < atmos_npes_pm"
73 !! @throw FATAL, "get_ensemble_pelist: size of pelist 2nd index < land_npes_pm"
74 !! @throw FATAL, "get_ensemble_pelist: size of pelist 2nd index < ice_npes_pm"
75 !! @throw FATAL, "get_ensemble_pelist: unknown argument name=[name]"
76 !! @throw FATAL, "get_ensemble_pelist: size of pelist 2nd index < total_npes_pm"
77  subroutine ensemble_manager_init()
78 
79 
80  integer :: i, io_status, npes, ierr
81 
82  namelist /ensemble_nml/ ensemble_size
83 
84  read (input_nml_file, ensemble_nml, iostat=io_status)
85  ierr = check_nml_error(io_status, 'ensemble_nml')
86 
87  if(ensemble_size < 1) call mpp_error(fatal, &
88  'ensemble_manager_mod: ensemble_nml variable ensemble_size must be a positive integer')
89  if(ensemble_size > max_ensemble_size) call mpp_error(fatal, &
90  'ensemble_manager_mod: ensemble_nml variable ensemble_size should be no larger than MAX_ENSEMBLE_SIZE, '// &
91  'change ensemble_size or increase MAX_ENSEMBLE_SIZE')
92 
93  pe = mpp_pe()
94  npes = mpp_npes()
95  if (npes < ensemble_size) then
96  call mpp_error(fatal,'npes must be >= ensemble_size')
97  endif
98  total_npes_pm = npes/ensemble_size
99  if (mod(npes, total_npes_pm) /= 0) call mpp_error(fatal,'ensemble_size must be divis by npes')
100 
101  call mpp_declare_pelist((/(i,i=0,npes-1)/),'_ens0') ! for ensemble driver
102 
103  end subroutine ensemble_manager_init
104 
105  !> @brief Getter function for ensemble_id
106  !! @return integer of ensemble id
107  function get_ensemble_id()
108  integer :: get_ensemble_id
109  get_ensemble_id = ensemble_id
110  end function get_ensemble_id
111 
112  !> @brief Returns ensemble size integer array
113  !! @return integer array of sizes
114  function get_ensemble_size()
115 
116  integer, dimension(6) :: get_ensemble_size
117 
118  get_ensemble_size(1) = ensemble_size
119  get_ensemble_size(2) = total_npes_pm
120  get_ensemble_size(3) = ocean_npes_pm
121  get_ensemble_size(4) = atmos_npes_pm
122  get_ensemble_size(5) = land_npes_pm
123  get_ensemble_size(6) = ice_npes_pm
124 
125  end function get_ensemble_size
126 
127  !> @brief Gets pe list for current ensemble or a given ensemble component.
128  subroutine get_ensemble_pelist(pelist, name)
129 
130  integer, intent(inout) :: pelist(:,:) !< Ensemble pelist
131  character(len=*), intent(in), optional :: name !< Component name.
132 
133  if (size(pelist,1) < ensemble_size) &
134  call mpp_error(fatal,'get_ensemble_pelist: size of pelist 1st index < ensemble_size')
135 
136  if(present(name)) then
137  select case(name)
138  case('ocean')
139  if (size(pelist,2) < ocean_npes_pm)&
140  call mpp_error(fatal,'get_ensemble_pelist: size of pelist 2nd index < ocean_npes_pm')
141  pelist = 0
142  pelist(1:ensemble_size,1:ocean_npes_pm) = &
143  ensemble_pelist_ocean(1:ensemble_size,1:ocean_npes_pm)
144 
145  case('atmos')
146  if (size(pelist,2) < atmos_npes_pm)&
147  call mpp_error(fatal,'get_ensemble_pelist: size of pelist 2nd index < atmos_npes_pm')
148  pelist = 0
149  pelist(1:ensemble_size,1:atmos_npes_pm) = &
150  ensemble_pelist_atmos(1:ensemble_size,1:atmos_npes_pm)
151 
152  case('land')
153  if (size(pelist,2) < land_npes_pm)&
154  call mpp_error(fatal,'get_ensemble_pelist: size of pelist 2nd index < land_npes_pm')
155  pelist = 0
156  pelist(1:ensemble_size,1:land_npes_pm) = &
157  ensemble_pelist_land(1:ensemble_size,1:land_npes_pm)
158 
159  case('ice')
160  if (size(pelist,2) < ice_npes_pm)&
161  call mpp_error(fatal,'get_ensemble_pelist: size of pelist 2nd index < ice_npes_pm')
162  pelist = 0
163  pelist(1:ensemble_size,1:ice_npes_pm) = &
164  ensemble_pelist_ice(1:ensemble_size,1:ice_npes_pm)
165 
166  case default
167  call mpp_error(fatal,'get_ensemble_pelist: unknown argument name='//name)
168  end select
169  else
170  if (size(pelist,2) < total_npes_pm)&
171  call mpp_error(fatal,'get_ensemble_pelist: size of pelist 2nd index < total_npes_pm')
172  pelist = 0
173  pelist(1:ensemble_size,1:total_npes_pm) = &
174  ensemble_pelist(1:ensemble_size,1:total_npes_pm)
175  endif
176 
177  return
178  end subroutine get_ensemble_pelist
179 
180 !> @brief Gets filter pelist for a given ensemble component.
181 !!
182 !! @throw FATAL, "get_ensemble_filter_pelist: size of pelist argument < ensemble_size * ocean_npes_pm"
183 !! @throw FATAL, "get_ensemble_filter_pelist: size of pelist argument < ensemble_size * atmos_npes_pm"
184 !! @throw FATAL, "get_ensemble_filter_pelist: size of pelist argument < ensemble_size * land_npes_pm"
185 !! @throw FATAL, "get_ensemble_filter_pelist: size of pelist argument < ensemble_size * ice_npes_pm"
186 !! @throw FATAL, "get_ensemble_filter_pelist: unknown argument name=[name]"
187  subroutine get_ensemble_filter_pelist(pelist, name)
188 
189  integer, intent(inout) :: pelist(:) !< Returned filter pe list
190  character(len=*), intent(in) :: name !< Ensemble component name
191 
192  select case(name)
193  case('ocean')
194  if (size(pelist) < ensemble_size * ocean_npes_pm)&
195  call mpp_error(fatal,'get_ensemble_filter_pelist: size of pelist argument < ensemble_size * ocean_npes_pm')
196  pelist = 0
197  pelist(1:ensemble_size*ocean_npes_pm) = &
198  ensemble_pelist_ocean_filter(1:ensemble_size*ocean_npes_pm)
199 
200  case('atmos')
201  if (size(pelist) < ensemble_size * atmos_npes_pm)&
202  call mpp_error(fatal,'get_ensemble_filter_pelist: size of pelist argument < ensemble_size * atmos_npes_pm')
203  pelist = 0
204  pelist(1:ensemble_size*atmos_npes_pm) = &
205  ensemble_pelist_atmos_filter(1:ensemble_size*atmos_npes_pm)
206 
207  case('land')
208  if (size(pelist) < ensemble_size * land_npes_pm)&
209  call mpp_error(fatal,'get_ensemble_filter_pelist: size of pelist argument < ensemble_size * land_npes_pm')
210  pelist = 0
211  pelist(1:ensemble_size*land_npes_pm) = &
212  ensemble_pelist_land_filter(1:ensemble_size*land_npes_pm)
213 
214  case('ice')
215  if (size(pelist) < ensemble_size * ice_npes_pm)&
216  call mpp_error(fatal,'get_ensemble_filter_pelist: size of pelist argument < ensemble_size * ice_npes_pm')
217  pelist = 0
218  pelist(1:ensemble_size*ice_npes_pm) = &
219  ensemble_pelist_ice_filter(1:ensemble_size*ice_npes_pm)
220 
221  case default
222  call mpp_error(fatal,'get_ensemble_filter_pelist: unknown argument name='//name)
223  end select
224 
225 
226  return
227  end subroutine get_ensemble_filter_pelist
228 
229 !nnz: I think the following block of code should be contained in a subroutine
230 ! to consolidate and ensure the consistency of declaring the various pelists.
231 !> @brief Sets up pe list for an ensemble.
232 !!
233 !! @throw FATAL, "ensemble_manager_mod: land_npes > atmos_npes"
234 !! @throw FATAL, "ensemble_manager_mod: ice_npes > atmos_npes"
235  subroutine ensemble_pelist_setup(concurrent, atmos_npes, ocean_npes, land_npes, ice_npes, &
236  Atm_pelist, Ocean_pelist, Land_pelist, Ice_pelist)
237  logical, intent(in) :: concurrent
238  integer, intent(in) :: atmos_npes, ocean_npes
239  integer, intent(in) :: land_npes, ice_npes
240  integer, dimension(:), intent(inout) :: atm_pelist, ocean_pelist
241  integer, dimension(:), intent(inout) :: land_pelist, ice_pelist
242  integer :: atmos_pe_start, atmos_pe_end, ocean_pe_start, ocean_pe_end
243  integer :: land_pe_start, land_pe_end, ice_pe_start, ice_pe_end
244  character(len=10) :: pelist_name, text
245  integer :: npes, n, m ,i
246 
247  npes = total_npes_pm
248 
249  ! make sure land_npes and ice_npes are not greater than atmos_npes
250  if(land_npes > atmos_npes) call mpp_error(fatal, 'ensemble_manager_mod: land_npes > atmos_npes')
251  if(ice_npes > atmos_npes) call mpp_error(fatal, 'ensemble_manager_mod: ice_npes > atmos_npes')
252 
253  allocate(ensemble_pelist(ensemble_size,total_npes_pm))
254  allocate(ensemble_pelist_ocean(1:ensemble_size, 1:ocean_npes) )
255  allocate(ensemble_pelist_atmos(1:ensemble_size, 1:atmos_npes) )
256  allocate(ensemble_pelist_land(1:ensemble_size, 1:land_npes) )
257  allocate(ensemble_pelist_ice(1:ensemble_size, 1:ice_npes) )
258 
259  atmos_pe_start = 0
260  ocean_pe_start = 0
261  land_pe_start = 0
262  ice_pe_start = 0
263  if( concurrent .OR. atmos_npes+ocean_npes == npes )then
264  ocean_pe_start = ensemble_size*atmos_npes
265  endif
266  do n=1,ensemble_size
267  atmos_pe_end = atmos_pe_start + atmos_npes - 1
268  ocean_pe_end = ocean_pe_start + ocean_npes - 1
269  land_pe_end = land_pe_start + land_npes - 1
270  ice_pe_end = ice_pe_start + ice_npes - 1
271  ensemble_pelist_atmos(n, 1:atmos_npes) = (/(i,i=atmos_pe_start,atmos_pe_end)/)
272  ensemble_pelist_ocean(n, 1:ocean_npes) = (/(i,i=ocean_pe_start,ocean_pe_end)/)
273  ensemble_pelist_land(n, 1:land_npes) = (/(i,i=land_pe_start, land_pe_end)/)
274  ensemble_pelist_ice(n, 1:ice_npes) = (/(i,i=ice_pe_start, ice_pe_end)/)
275  ensemble_pelist(n, 1:atmos_npes) = ensemble_pelist_atmos(n, 1:atmos_npes)
276  if( concurrent .OR. atmos_npes+ocean_npes == npes ) &
277  ensemble_pelist(n, atmos_npes+1:npes) = ensemble_pelist_ocean(n, 1:ocean_npes)
278  if(any(ensemble_pelist(n,:) == pe)) ensemble_id = n
279  write(pelist_name,'(a,i2.2)') '_ens',n
280  call mpp_declare_pelist(ensemble_pelist(n,:), trim(pelist_name))
281  atmos_pe_start = atmos_pe_end + 1
282  ocean_pe_start = ocean_pe_end + 1
283  land_pe_start = atmos_pe_start
284  ice_pe_start = atmos_pe_start
285  enddo
286 
287  atm_pelist(:) = ensemble_pelist_atmos(ensemble_id,:)
288  ocean_pelist(:) = ensemble_pelist_ocean(ensemble_id,:)
289  land_pelist(:) = ensemble_pelist_land(ensemble_id,:)
290  ice_pelist(:) = ensemble_pelist_ice(ensemble_id,:)
291 
292  ! write(pelist_name,'(a,i2.2)') '_ocn_ens',ensemble_id
293  ! call mpp_declare_pelist(Ocean%pelist , trim(pelist_name) )
294 
295  ! write(pelist_name,'(a,i2.2)') '_atm_ens',ensemble_id
296  ! call mpp_declare_pelist(Atm%pelist , trim(pelist_name) )
297  !
298  !nnz: The above is sufficient for non-concurrent mode.
299  ! BUT
300  ! For atmosphere_init to work in ensemble, concurrent mode
301  ! the following Atm_pelist should be declared (per ensemble member)
302  ! instead of the above Atm%pelist!
303  !
304  ! allocate( Atm_pelist(1:ensemble_size, 1:atmos_npes) )
305  ! do n=1,ensemble_size
306  ! do i=1, atmos_npes
307  ! Atm_pelist(n, i) = ensemble_pelist(n, i)
308  ! enddo
309  ! write(pelist_name,'(a,i2.2)') '_atm_ens',n
310  ! call mpp_declare_pelist(Atm_pelist(n,:) , trim(pelist_name) )
311  ! enddo
312  !
313  ! The way I understand this with the help of Totalview is:
314  ! With mpp_declare_pelist(Atm%pelist)
315  ! When we are in fv_arrays_init when mp_init(comID) is called
316  ! comID is the same for the atmospheric PE's for both ensemble members
317  ! since peset(5)%id is the same (7) for those PE's, so the PE count is double what it should be inside
318  ! mp_init().
319  ! It is also true that for Ocean PE's, peset(4)%id is the same (6) for Ocean PE's in both ensemble members
320  ! but for Ocean it is not a problem because Ocean is not trying to create new communicators
321  ! from this peset whereas ATM does (vis mp_init).
322  !
323  ! Who sets peset(i)%id ? Can it be modified to assign different %id for the two subsets.
324  ! peset(i)%id = 0 for Ocean PE's on ATM pesets and for ATM PE's on Ocean pesets.
325  !
326  ! With mpp_declare_pelist(Atm_pelist(n,:)) n=1,...,ensemble_size
327  ! we get separate pesets for each ATM ensemble member and each with a different %id and mp_init is cured.
328  !
329  ! There is also a matter of precedence. If we have both calls
330  ! call mpp_declare_pelist(Atm%pelist , trim(pelist_name) )
331  ! and
332  ! call mpp_declare_pelist(Atm_pelist(n,:) , trim(pelist_name) )
333  ! then concurrent run fails because with call mpp_set_current_pelist( Atm%pelist )
334  ! peset(i) is searched for i=1,2,... and the first pelist that matches argument, its peset is set as current.
335  !
336  ! To be consistent with ATM and OCEAN we can do the following
337  ! (eventhough mpp_declare_pelist(Ocean%pelist) is adequate right now.)
338 
339  if( concurrent )then
340  do n=1,ensemble_size
341  write(pelist_name,'(a,i2.2)') 'atm_ens',n
342  call mpp_declare_pelist(ensemble_pelist_atmos(n,:) , trim(pelist_name) )
343  write(pelist_name,'(a,i2.2)') 'ocn_ens',n
344  call mpp_declare_pelist(ensemble_pelist_ocean(n,:) , trim(pelist_name) )
345  write(pelist_name,'(a,i2.2)') 'lnd_ens',n
346  call mpp_declare_pelist(ensemble_pelist_land(n,:) , trim(pelist_name) )
347  write(pelist_name,'(a,i2.2)') 'ice_ens',n
348  call mpp_declare_pelist(ensemble_pelist_ice(n,:) , trim(pelist_name) )
349  enddo
350  else
351  write(pelist_name,'(a,i2.2)') 'atm_ens',ensemble_id
352  call mpp_declare_pelist(atm_pelist , trim(pelist_name) )
353  write(pelist_name,'(a,i2.2)') 'ocn_ens',ensemble_id
354  call mpp_declare_pelist(ocean_pelist , trim(pelist_name) )
355  write(pelist_name,'(a,i2.2)') 'lnd_ens',ensemble_id
356  call mpp_declare_pelist(land_pelist , trim(pelist_name) )
357  write(pelist_name,'(a,i2.2)') 'ice_ens',ensemble_id
358  call mpp_declare_pelist(ice_pelist , trim(pelist_name) )
359  endif
360 
361  ocean_npes_pm = ocean_npes
362  atmos_npes_pm = atmos_npes
363  land_npes_pm = land_npes
364  ice_npes_pm = ice_npes
365 
366  !Declare pelist of all Ocean, Atmos, Land and Ice pes across all ensembles ( filters )
367  allocate(ensemble_pelist_ocean_filter(ensemble_size * ocean_npes_pm))
368  allocate(ensemble_pelist_atmos_filter(ensemble_size * atmos_npes_pm))
369  allocate(ensemble_pelist_land_filter(ensemble_size * land_npes_pm))
370  allocate(ensemble_pelist_ice_filter(ensemble_size * ice_npes_pm))
371  do n=1,ensemble_size
372  do m=1,ocean_npes_pm
373  i=(n-1)*ocean_npes_pm + m
374  ensemble_pelist_ocean_filter(i) = ensemble_pelist_ocean(n,m)
375  enddo
376  do m=1,atmos_npes_pm
377  i=(n-1)*atmos_npes_pm + m
378  ensemble_pelist_atmos_filter(i) = ensemble_pelist_atmos(n,m)
379  enddo
380  do m=1,land_npes_pm
381  i=(n-1)*land_npes_pm + m
382  ensemble_pelist_land_filter(i) = ensemble_pelist_land(n,m)
383  enddo
384  do m=1,ice_npes_pm
385  i=(n-1)*ice_npes_pm + m
386  ensemble_pelist_ice_filter(i) = ensemble_pelist_ice(n,m)
387  enddo
388  enddo
389 
390  write(pelist_name,'(a)') 'ocn_filter'
391  call mpp_declare_pelist(ensemble_pelist_ocean_filter, trim(pelist_name) )
392 
393  write(pelist_name,'(a)') 'atm_filter'
394  call mpp_declare_pelist(ensemble_pelist_atmos_filter, trim(pelist_name) )
395 
396  write(pelist_name,'(a)') 'lnd_filter'
397  call mpp_declare_pelist(ensemble_pelist_land_filter, trim(pelist_name) )
398 
399  write(pelist_name,'(a)') 'ice_filter'
400  call mpp_declare_pelist(ensemble_pelist_ice_filter, trim(pelist_name) )
401 
402  !
403  !Rename output files to identify the ensemble
404  !If ensemble_size=1 do not rename files so that the same coupler
405  !can be used for non-ensemble experiments
406  !
407  if (ensemble_size > 1) then
408  write( text,'(a,i2.2)' ) 'ens_', ensemble_id
409  !Append ensemble_id to the restart filenames
410 
411  !< Both calls are needed for cases where both fms2io/fmsio are used
412  call fms2_io_set_filename_appendix(trim(text))
413 #ifdef use_deprecated_io
414  call fms_io_set_filename_appendix(trim(text))
415 #endif
416  endif
417 
418  end subroutine ensemble_pelist_setup
419 
420 
421 end module ensemble_manager_mod
422 !> @}
423 ! close documentation grouping
integer function, public get_ensemble_id()
Getter function for ensemble_id.
integer function, dimension(6), public get_ensemble_size()
Returns ensemble size integer array.
subroutine, public ensemble_pelist_setup(concurrent, atmos_npes, ocean_npes, land_npes, ice_npes, Atm_pelist, Ocean_pelist, Land_pelist, Ice_pelist)
Sets up pe list for an ensemble.
subroutine, public ensemble_manager_init()
Initializes ensemble_manager_mod.
subroutine, public get_ensemble_pelist(pelist, name)
Gets pe list for current ensemble or a given ensemble component.
subroutine, public get_ensemble_filter_pelist(pelist, name)
Gets filter pelist for a given ensemble component.
integer function, public check_nml_error(IOSTAT, NML_NAME)
Checks the iostat argument that is returned after reading a namelist and determines if the error code...
Definition: fms.F90:580
integer function stdout()
This function returns the current standard fortran unit numbers for output.
Definition: mpp_util.inc:43
subroutine mpp_declare_pelist(pelist, name, commID)
Declare a pelist.
Definition: mpp_util.inc:461
integer function stdlog()
This function returns the current standard fortran unit numbers for log messages. Log messages,...
Definition: mpp_util.inc:59
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
Error handler.
Definition: mpp.F90:382