MODFLOW 6  version 6.6.0.dev0
USGS Modular Hydrologic Model
DistributedSim.f90
Go to the documentation of this file.
2  use kindmodule, only: i4b, lgp
5  use arrayhandlersmodule, only: ifind
12 
13  implicit none
14  private
15 
16  public :: distributedsimtype
17  public :: get_dsim
18 
20  character(len=LENMEMPATH) :: memory_path
21  integer(I4B), pointer :: nr_models !< the total (global) number of models, equals the length of the model block in mfsim.nam
22  integer(I4B), dimension(:), pointer, contiguous :: load_mask => null() !< mask for loading models and exchanges, 1 when active on this processor, else 0
23  integer(I4B), dimension(:), pointer, contiguous :: model_ranks => null() !< load balance: model rank (0,...,nr_procs-1) per global model id (array index)
24  contains
25  procedure :: create
26  procedure :: get_load_mask
27  procedure :: get_load_balance
28  procedure :: destroy
29  ! private
30  procedure, private :: create_load_mask
31  procedure, private :: set_load_balance_from_input
32  procedure, private :: set_load_balance_default
33  procedure, private :: validate_load_balance
34  end type
35 
36  ! singleton, private member
37  class(distributedsimtype), private, pointer :: dist_sim => null()
38 
39 contains
40 
41  !> @brief Get pointer to the distributed simulation object
42  !<
43  function get_dsim() result(ds)
44  class(distributedsimtype), pointer :: ds
45 
46  if (.not. associated(dist_sim)) then
47  allocate (dist_sim)
48  call dist_sim%create()
49  end if
50  ds => dist_sim
51 
52  end function get_dsim
53 
54  !> Create the distributed simulation object from the simulation input ctx
55  !<
56  subroutine create(this)
57  class(distributedsimtype) :: this
58  ! local
59  character(len=LENMEMPATH) :: input_mempath
60  integer(I4B), pointer :: nmod
61 
62  this%memory_path = create_mem_path(component='SIM')
63 
64  input_mempath = create_mem_path(component='SIM', context=idm_context)
65  call mem_setptr(nmod, 'NUMMODELS', input_mempath)
66 
67  call mem_allocate(this%nr_models, 'NUMMODELS', this%memory_path)
68  this%nr_models = nmod
69 
70  end subroutine create
71 
72  !> @brief Return pointer to the load mask for models
73  !!
74  !! Get a load mask to determine which models
75  !! should be loaded by idm on this process. This is in
76  !! sync with models create. The mask array is allocated
77  !! with its size equal to the global number of models.
78  !! It is returned as (1, 1, 0, 0, ... 0) with each entry
79  !! being a load mask for the model at the corresponding
80  !< location in the 'MNAME' array of the IDM.
81  function get_load_mask(this) result(load_mask)
82  class(distributedsimtype) :: this
83  integer(I4B), dimension(:), pointer :: load_mask
84 
85  if (.not. associated(this%load_mask)) then
86  call this%create_load_mask()
87  end if
88  load_mask => this%load_mask
89 
90  end function get_load_mask
91 
92  !> @brief Create a load mask for IDM from the load balance array
93  !<
94  subroutine create_load_mask(this)
95  class(distributedsimtype) :: this
96  ! local
97  integer(I4B), dimension(:), pointer :: model_ranks => null() !< the load balance
98  integer(I4B) :: m_id !< model id
99 
100  call mem_allocate(this%load_mask, this%nr_models, 'LOADMASK', &
101  this%memory_path)
102  this%load_mask = 0
103 
104  ! get load balance (probably the first call, so creates it)
105  model_ranks => this%get_load_balance()
106 
107  ! set mask from balance
108  do m_id = 1, this%nr_models
109  if (model_ranks(m_id) == proc_id) then
110  this%load_mask(m_id) = 1
111  else
112  this%load_mask(m_id) = 0
113  end if
114  end do
115 
116  end subroutine create_load_mask
117 
118  !> @brief Get the model load balance for the simulation
119  !<
120  function get_load_balance(this) result(mranks)
121  class(distributedsimtype) :: this !< this distributed sim instance
122  integer(I4B), dimension(:), pointer :: mranks !< the load balance: array of ranks per model id
123  ! local
124  integer(I4B) :: isize
125  character(len=LENMEMPATH) :: hpc_mempath
126 
127  ! if load balance available, return here:
128  if (associated(this%model_ranks)) then
129  mranks => this%model_ranks
130  return
131  end if
132 
133  call mem_allocate(this%model_ranks, this%nr_models, 'MODELRANKS', &
134  this%memory_path)
135 
136  ! check if exists (partitions block is optional in HPC file)
137  hpc_mempath = create_mem_path('UTL', 'HPC', idm_context)
138  call get_isize('MNAME', hpc_mempath, isize)
139 
140  if (isize > 0) then
141  ! HPC file present
142  if (nr_procs == 1) then
143  write (warnmsg, *) "Ignoring PARTITIONS block in HPC file when "// &
144  "running a serial process"
145  call store_warning(warnmsg)
146 
147  ! single process, everything on cpu 0:
148  this%model_ranks = 0
149  else
150  ! set balance from HPC file
151  call this%set_load_balance_from_input()
152  ! check if valid configuration
153  call this%validate_load_balance()
154  end if
155  else
156  ! no HPC file present
157  if (nr_procs == 1) then
158  ! single process, everything on cpu 0:
159  this%model_ranks = 0
160  else
161  ! set balance from default algorithm
162  call this%set_load_balance_default()
163  end if
164  end if
165 
166  mranks => this%model_ranks
167 
168  end function get_load_balance
169 
170  !> @brief Load load balance from the input configuration
171  !<
173  class(distributedsimtype) :: this !< this distributed sim instance
174  ! local
175  character(len=LENMEMPATH) :: simnam_mempath, hpc_mempath
176  character(len=LENMODELNAME) :: model_name
177  type(characterstringtype), dimension(:), contiguous, pointer :: mnames !< model names (all) from the simulation nam file
178  type(characterstringtype), dimension(:), contiguous, pointer :: mnames_hpc !< model names in the hpc file
179  integer(I4B), dimension(:), contiguous, pointer :: mranks_hpc !< rank numbers in the hpc file
180  integer(I4B) :: i, model_idx
181  integer(I4B) :: target_rank
182  integer(I4B), dimension(:), allocatable :: rank_used
183  type(characterstringtype), dimension(:), contiguous, pointer :: hpc_names !< helper array to get the hpc filename
184  character(len=LINELENGTH) :: hpc_filename !< the HPC option file
185 
186  ! set to uninitialized
187  this%model_ranks = -1
188 
189  ! from IDM
190  simnam_mempath = create_mem_path('SIM', 'NAM', idm_context)
191  hpc_mempath = create_mem_path('UTL', 'HPC', idm_context)
192  call mem_setptr(mnames, 'MNAME', simnam_mempath)
193  call mem_setptr(mnames_hpc, 'MNAME', hpc_mempath)
194  call mem_setptr(mranks_hpc, 'MRANK', hpc_mempath)
195  call mem_setptr(hpc_names, 'HPC6_FILENAME', simnam_mempath)
196 
197  ! FILEIN options give an array, so take the first:
198  hpc_filename = hpc_names(1)
199 
200  ! check: valid model names
201  do i = 1, size(mnames_hpc)
202  if (ifind(mnames, mnames_hpc(i)) == -1) then
203  model_name = mnames_hpc(i)
204  write (errmsg, *) "HPC input error: undefined model name (", &
205  trim(model_name), ")"
206  call store_error(errmsg)
207  end if
208  end do
209  ! check: valid ranks
210  do i = 1, size(mranks_hpc)
211  target_rank = mranks_hpc(i)
212  if (target_rank < 0 .or. target_rank > nr_procs - 1) then
213  model_name = mnames_hpc(i)
214  write (errmsg, '(a,i0,2a)') "HPC input error: invalid target rank (", &
215  target_rank, ") for model ", trim(model_name)
216  call store_error(errmsg)
217  end if
218  end do
219  if (count_errors() > 0) then
220  call store_error_filename(hpc_filename)
221  end if
222 
223  ! construct rank array
224  do i = 1, size(mnames_hpc)
225  model_idx = ifind(mnames, mnames_hpc(i))
226  this%model_ranks(model_idx) = mranks_hpc(i)
227  end do
228 
229  ! check: all models acquired rank
230  do i = 1, size(this%model_ranks)
231  if (this%model_ranks(i) == -1) then
232  model_name = mnames(i)
233  write (errmsg, '(2a)') "HPC input error: no target rank for model ", &
234  trim(model_name)
235  call store_error(errmsg)
236  end if
237  end do
238  if (count_errors() > 0) then
239  call store_error_filename(hpc_filename)
240  end if
241 
242  ! check: no idle ranks
243  allocate (rank_used(nr_procs))
244  rank_used = 0
245  do i = 1, size(this%model_ranks)
246  if (this%model_ranks(i) >= 0 .and. this%model_ranks(i) < nr_procs) then
247  rank_used(this%model_ranks(i) + 1) = 1
248  end if
249  end do
250  do i = 1, size(rank_used)
251  if (rank_used(i) == 0) then
252  write (errmsg, '(a,i0,a)') "HPC input error: rank ", i - 1, &
253  " has no models assigned"
254  call store_error(errmsg)
255  end if
256  end do
257  deallocate (rank_used)
258  if (count_errors() > 0) then
259  call store_error_filename(hpc_filename)
260  end if
261 
262  end subroutine set_load_balance_from_input
263 
264  !> @brief Distribute the models over the available
265  !! processes in a parallel run. Expects an array sized
266  !< to the number of models in the global simulation
267  subroutine set_load_balance_default(this)
268  class(distributedsimtype) :: this !< this distributed sim. instance
269  ! local
270  integer(I4B) :: im, imm, ie, ip, cnt
271  integer(I4B) :: nr_models, nr_gwf_models
272  integer(I4B) :: nr_exchanges
273  integer(I4B) :: min_per_proc, nr_left
274  integer(I4B) :: rank
275  integer(I4B), dimension(:), allocatable :: nr_models_proc
276  character(len=LENPACKAGETYPE) :: model_type_str
277  character(len=LENMEMPATH) :: input_mempath
278  type(characterstringtype), dimension(:), contiguous, &
279  pointer :: mtypes !< model types
280  type(characterstringtype), dimension(:), contiguous, &
281  pointer :: mnames !< model names
282  type(characterstringtype), dimension(:), contiguous, &
283  pointer :: etypes !< exg types
284  type(characterstringtype), dimension(:), contiguous, &
285  pointer :: emnames_a !< model a names
286  type(characterstringtype), dimension(:), contiguous, &
287  pointer :: emnames_b !< model b names
288 
289  this%model_ranks = 0
290 
291  ! load IDM data
292  input_mempath = create_mem_path('SIM', 'NAM', idm_context)
293  call mem_setptr(mtypes, 'MTYPE', input_mempath)
294  call mem_setptr(mnames, 'MNAME', input_mempath)
295  call mem_setptr(etypes, 'EXGTYPE', input_mempath)
296  call mem_setptr(emnames_a, 'EXGMNAMEA', input_mempath)
297  call mem_setptr(emnames_b, 'EXGMNAMEB', input_mempath)
298 
299  ! count flow models
300  nr_models = size(mnames)
301  nr_gwf_models = 0
302  do im = 1, nr_models
303  if (mtypes(im) == 'GWF6') then
304  nr_gwf_models = nr_gwf_models + 1
305  end if
306 
307  if (mtypes(im) == 'GWF6' .or. &
308  mtypes(im) == 'GWT6' .or. &
309  mtypes(im) == 'GWE6') then
310  cycle
311  end if
312 
313  model_type_str = mtypes(im)
314  write (errmsg, *) 'Model type ', model_type_str, &
315  ' not supported in parallel mode.'
316  call store_error(errmsg, terminate=.true.)
317  end do
318 
319  ! calculate nr of flow models for each rank
320  allocate (nr_models_proc(nr_procs))
321  min_per_proc = nr_gwf_models / nr_procs
322  nr_left = nr_gwf_models - nr_procs * min_per_proc
323  cnt = 1
324  do ip = 1, nr_procs
325  rank = ip - 1
326  nr_models_proc(ip) = min_per_proc
327  if (rank < nr_left) then
328  nr_models_proc(ip) = nr_models_proc(ip) + 1
329  end if
330  end do
331 
332  ! assign ranks for flow models
333  rank = 0
334  do im = 1, nr_models
335  if (mtypes(im) == 'GWF6') then
336  if (nr_models_proc(rank + 1) == 0) then
337  rank = rank + 1
338  end if
339  this%model_ranks(im) = rank
340  nr_models_proc(rank + 1) = nr_models_proc(rank + 1) - 1
341  end if
342  end do
343 
344  ! match other models to flow
345  nr_exchanges = size(etypes)
346  do im = 1, nr_models
347  if (mtypes(im) == 'GWT6') then
348 
349  ! find match
350  do ie = 1, nr_exchanges
351  if (etypes(ie) == 'GWF6-GWT6' .and. mnames(im) == emnames_b(ie)) then
352  rank = 0
353  do imm = 1, nr_models
354  if (mnames(imm) == emnames_a(ie)) then
355  rank = this%model_ranks(imm)
356  exit
357  end if
358  end do
359  this%model_ranks(im) = rank
360  exit
361  end if
362  end do
363 
364  else if (mtypes(im) == 'GWE6') then
365  do ie = 1, nr_exchanges
366  if (etypes(ie) == 'GWF6-GWE6' .and. mnames(im) == emnames_b(ie)) then
367  rank = 0
368  do imm = 1, nr_models
369  if (mnames(imm) == emnames_a(ie)) then
370  rank = this%model_ranks(imm)
371  exit
372  end if
373  end do
374  this%model_ranks(im) = rank
375  exit
376  end if
377  end do
378 
379  else
380  cycle ! e.g., for a flow model
381  end if
382  end do
383 
384  ! cleanup
385  deallocate (nr_models_proc)
386 
387  end subroutine set_load_balance_default
388 
389  !> @brief Check validity of load balance configuration
390  !<
391  subroutine validate_load_balance(this)
392  class(distributedsimtype) :: this
393  character(len=LENMEMPATH) :: input_mempath
394  type(characterstringtype), dimension(:), contiguous, &
395  pointer :: mtypes !< model types
396  type(characterstringtype), dimension(:), contiguous, &
397  pointer :: mnames !< model names
398  type(characterstringtype), dimension(:), contiguous, &
399  pointer :: etypes !< exg types
400  type(characterstringtype), dimension(:), contiguous, &
401  pointer :: emnames_a !< model a names
402  type(characterstringtype), dimension(:), contiguous, &
403  pointer :: emnames_b !< model b names
404  type(characterstringtype), dimension(:), contiguous, &
405  pointer :: hpc_names !< helper array to get hpc filename
406  integer(I4B) :: ie
407  integer(I4B) :: idx_a, idx_b
408  integer(I4B) :: rank_a, rank_b
409  integer(I4B) :: nr_exchanges
410  character(len=LINELENGTH) :: hpc_filename !< the HPC option file
411  character(len=LENMODELNAME) :: name_a, name_b
412  character(len=LINELENGTH) :: exg_type
413 
414  ! load IDM data
415  input_mempath = create_mem_path('SIM', 'NAM', idm_context)
416  call mem_setptr(mtypes, 'MTYPE', input_mempath)
417  call mem_setptr(mnames, 'MNAME', input_mempath)
418  call mem_setptr(etypes, 'EXGTYPE', input_mempath)
419  call mem_setptr(emnames_a, 'EXGMNAMEA', input_mempath)
420  call mem_setptr(emnames_b, 'EXGMNAMEB', input_mempath)
421  call mem_setptr(hpc_names, 'HPC6_FILENAME', input_mempath)
422 
423  ! FILEIN options give an array, so take the first:
424  hpc_filename = hpc_names(1)
425 
426  nr_exchanges = size(etypes)
427 
428  ! loop over exchanges
429  do ie = 1, nr_exchanges
430  if (etypes(ie) == 'GWF6-GWT6' .or. etypes(ie) == 'GWF6-GWE6') then
431  idx_a = ifind(mnames, emnames_a(ie))
432  idx_b = ifind(mnames, emnames_b(ie))
433  rank_a = this%model_ranks(idx_a)
434  rank_b = this%model_ranks(idx_b)
435  if (rank_a /= rank_b) then
436  name_a = emnames_a(ie)
437  name_b = emnames_b(ie)
438  exg_type = etypes(ie)
439  write (errmsg, '(7a)') "HPC input error: models ", &
440  trim(name_a), " and ", trim(name_b), " with a ", &
441  trim(exg_type), " coupling have to be assigned to the same rank"
442  call store_error(errmsg)
443  end if
444  end if
445  end do
446 
447  if (count_errors() > 0) then
448  call store_error_filename(hpc_filename)
449  end if
450 
451  end subroutine validate_load_balance
452 
453  !> @brief clean up
454  !<
455  subroutine destroy(this)
456  class(distributedsimtype) :: this
457 
458  call mem_deallocate(this%load_mask)
459  call mem_deallocate(this%model_ranks)
460 
461  call mem_deallocate(this%nr_models)
462 
463  ! delete singleton instance
464  if (associated(dist_sim)) deallocate (dist_sim)
465 
466  end subroutine destroy
467 
468 end module distributedsimmodule
This module contains simulation constants.
Definition: Constants.f90:9
integer(i4b), parameter linelength
maximum length of a standard line
Definition: Constants.f90:45
integer(i4b), parameter lenmodelname
maximum length of the model name
Definition: Constants.f90:22
integer(i4b), parameter lenpackagetype
maximum length of a package type (DIS6, SFR6, CSUB6, etc.)
Definition: Constants.f90:38
integer(i4b), parameter lenmempath
maximum length of the memory path
Definition: Constants.f90:27
subroutine set_load_balance_from_input(this)
Load load balance from the input configuration.
subroutine validate_load_balance(this)
Check validity of load balance configuration.
subroutine create_load_mask(this)
Create a load mask for IDM from the load balance array.
class(distributedsimtype) function, pointer, public get_dsim()
Get pointer to the distributed simulation object.
integer(i4b) function, dimension(:), pointer get_load_mask(this)
Return pointer to the load mask for models.
subroutine create(this)
Create the distributed simulation object from the simulation input ctx.
class(distributedsimtype), pointer, private dist_sim
subroutine set_load_balance_default(this)
Distribute the models over the available processes in a parallel run. Expects an array sized.
integer(i4b) function, dimension(:), pointer get_load_balance(this)
Get the model load balance for the simulation.
subroutine destroy(this)
clean up
This module defines variable data types.
Definition: kind.f90:8
character(len=lenmempath) function create_mem_path(component, subcomponent, context)
returns the path to the memory object
subroutine, public mem_print_detailed(iout)
subroutine, public get_isize(name, mem_path, isize)
@ brief Get the number of elements for this variable
This module contains simulation methods.
Definition: Sim.f90:10
subroutine, public store_warning(msg, substring)
Store warning message.
Definition: Sim.f90:236
subroutine, public store_error(msg, terminate)
Store an error message.
Definition: Sim.f90:92
integer(i4b) function, public count_errors()
Return number of errors.
Definition: Sim.f90:59
subroutine, public store_error_filename(filename, terminate)
Store the erroring file name.
Definition: Sim.f90:203
This module contains simulation variables.
Definition: SimVariables.f90:9
character(len=maxcharlen) errmsg
error message string
character(len=linelength) idm_context
integer(i4b) nr_procs
character(len=maxcharlen) warnmsg
warning message string
integer(i4b) proc_id
This class is used to store a single deferred-length character string. It was designed to work in an ...
Definition: CharString.f90:23