← back to index

src/framework/MOM_domains.F90

portedportable, not yet portedexecuted, not portableexecutable, not hit by this run

1! This file is part of MOM6, the Modular Ocean Model version 6.
2! See the LICENSE file for licensing information.
3! SPDX-License-Identifier: Apache-2.0
4
5!> Describes the decomposed MOM domain and has routines for communications across PEs
6module MOM_domains
7
8use MOM_coms_infra, only : MOM_infra_init, MOM_infra_end
9use MOM_coms_infra, only : PE_here, root_PE, num_PEs, broadcast
10use MOM_coms_infra, only : sum_across_PEs, min_across_PEs, max_across_PEs
11use MOM_domain_infra, only : MOM_domain_type, domain2D, domain1D, group_pass_type
12use MOM_domain_infra, only : create_MOM_domain, clone_MOM_domain, deallocate_MOM_domain
13use MOM_domain_infra, only : get_domain_extent, get_domain_components, same_domain
14use MOM_domain_infra, only : compute_block_extent, get_global_shape
15use MOM_domain_infra, only : pass_var, pass_vector, fill_symmetric_edges
16use MOM_domain_infra, only : pass_var_start, pass_var_complete
17use MOM_domain_infra, only : pass_vector_start, pass_vector_complete
18use MOM_domain_infra, only : create_group_pass, do_group_pass
19use MOM_domain_infra, only : start_group_pass, complete_group_pass
20use MOM_domain_infra, only : rescale_comp_data, global_field, redistribute_array, broadcast_domain
21use MOM_domain_infra, only : MOM_thread_affinity_set, set_MOM_thread_affinity
22use MOM_domain_infra, only : AGRID, BGRID_NE, CGRID_NE, SCALAR_PAIR
23use MOM_domain_infra, only : CORNER, CENTER, NORTH_FACE, EAST_FACE
24use MOM_domain_infra, only : To_East, To_West, To_North, To_South, To_All, Omit_Corners
25use MOM_domain_infra, only : compute_extent
26use MOM_error_handler, only : MOM_error, MOM_mesg, NOTE, WARNING, FATAL, is_root_pe
27use MOM_file_parser, only : get_param, log_param, log_version, param_file_type
28use MOM_io_infra, only : file_exists, read_field, open_ASCII_file, close_file, WRITEONLY_FILE
29use MOM_string_functions, only : slasher
30use MOM_cpu_clock, only : cpu_clock_id, cpu_clock_begin, cpu_clock_end, CLOCK_ROUTINE
31use MOM_unit_scaling, only : unit_scale_type
32
33implicit none ; private
34
35public :: MOM_infra_init, MOM_infra_end
36! Domain types and creation and destruction routines
37public :: MOM_domain_type, domain2D, domain1D
38public :: MOM_domains_init, create_MOM_domain, clone_MOM_domain, deallocate_MOM_domain
39public :: MOM_thread_affinity_set, set_MOM_thread_affinity
40public :: MOM_define_layout
41! Domain query routines
42public :: get_domain_extent, get_domain_components, get_global_shape, same_domain
43public :: PE_here, root_PE, num_PEs
44! Blocks are not actively used in MOM6, so this routine could be deprecated.
45public :: compute_block_extent
46! Single call communication routines
47public :: pass_var, pass_vector, fill_symmetric_edges, broadcast
48! Non-blocking communication routines
49public :: pass_var_start, pass_var_complete, pass_vector_start, pass_vector_complete
50! Multi-variable group communication routines and type
51public :: create_group_pass, do_group_pass, group_pass_type, start_group_pass, complete_group_pass
52! Global reduction routines
53public :: sum_across_PEs, min_across_PEs, max_across_PEs
54public :: global_field, redistribute_array, broadcast_domain
55! Simple index-convention-invariant array manipulation routine
56public :: rescale_comp_data
57!> These encoding constants are used to indicate the staggering of scalars and vectors
58public :: AGRID, BGRID_NE, CGRID_NE, SCALAR_PAIR
59!> These encoding constants are used to indicate the discretization position of a variable
60public :: CORNER, CENTER, NORTH_FACE, EAST_FACE
61!> These encoding constants indicate communication patterns. In practice they can be added.
62public :: To_East, To_West, To_North, To_South, To_All, Omit_Corners
63
64contains
65
66!> MOM_domains_init initializes a MOM_domain_type variable, based on the information
67!! read in from a param_file_type, and optionally returns data describing various
68!! properties of the domain type.
691subroutine MOM_domains_init(MOM_dom, param_file, symmetric, static_memory, &
70 NIHALO, NJHALO, NIGLOBAL, NJGLOBAL, NIPROC, NJPROC, &
71 min_halo, domain_name, include_name, param_suffix, US, MOM_dom_unmasked)
72 type(MOM_domain_type), pointer :: MOM_dom !< A pointer to the MOM_domain_type
73 !! being defined here.
74 type(param_file_type), intent(in) :: param_file !< A structure to parse for
75 !! run-time parameters
76 logical, optional, intent(in) :: symmetric !< If present, this specifies
77 !! whether this domain is symmetric, regardless of
78 !! whether the macro SYMMETRIC_MEMORY_ is defined.
79 logical, optional, intent(in) :: static_memory !< If present and true, this
80 !! domain type is set up for static memory and
81 !! error checking of various input values is
82 !! performed against those in the input file.
83 integer, optional, intent(in) :: NIHALO !< Default halo sizes, required
84 !! with static memory.
85 integer, optional, intent(in) :: NJHALO !< Default halo sizes, required
86 !! with static memory.
87 integer, optional, intent(in) :: NIGLOBAL !< Total domain sizes, required
88 !! with static memory.
89 integer, optional, intent(in) :: NJGLOBAL !< Total domain sizes, required
90 !! with static memory.
91 integer, optional, intent(in) :: NIPROC !< Processor counts, required with
92 !! static memory.
93 integer, optional, intent(in) :: NJPROC !< Processor counts, required with
94 !! static memory.
95 integer, dimension(2), optional, intent(inout) :: min_halo !< If present, this sets the
96 !! minimum halo size for this domain in the i- and j-
97 !! directions, and returns the actual halo size used.
98 character(len=*), optional, intent(in) :: domain_name !< A name for this domain, "MOM"
99 !! if missing.
100 character(len=*), optional, intent(in) :: include_name !< A name for model's include file,
101 !! "MOM_memory.h" if missing.
102 character(len=*), optional, intent(in) :: param_suffix !< A suffix to apply to
103 !! layout-specific parameters.
104 type(unit_scale_type), optional, pointer :: US !< A dimensional unit scaling type
105 type(MOM_domain_type), optional, pointer :: MOM_dom_unmasked !< Unmasked MOM domain instance.
106 !! Set to null if masking is not enabled.
107
108 ! Local variables
109 integer, dimension(2) :: layout ! The number of logical processors in the i- and j- directions
110 integer, dimension(2) :: auto_layout ! The layout determined by the auto masking routine
111 integer, dimension(2) :: layout_unmasked ! A temporary layout for unmasked domain
112 integer, dimension(2) :: io_layout ! The layout of logical processors for input and output
113 !$ integer :: ocean_nthreads ! Number of openMP threads
114 !$ logical :: ocean_omp_hyper_thread ! If true use openMP hyper-threads
115 integer, dimension(2) :: n_global ! The number of i- and j- points in the global computational domain
116 integer, dimension(2) :: n_halo ! The number of i- and j- points in the halos
117 integer :: nihalo_dflt, njhalo_dflt ! The default halo sizes
118 integer :: PEs_used ! The number of processors used
119 logical, dimension(2) :: reentrant ! True if the x- and y- directions are periodic.
120 logical :: tripolar_N ! A flag indicating whether there is northern tripolar connectivity
121 logical :: is_static ! If true, static memory is being used for this domain.
122 logical :: is_symmetric ! True if the domain being set up will use symmetric memory.
123 logical :: nonblocking ! If true, nonblocking halo updates will be used.
124 logical :: thin_halos ! If true, If true, optional arguments may be used to specify the
125 ! width of the halos that are updated with each call.
126 logical :: auto_mask_table ! Runtime flag that turns on automatic mask table generator
127 integer :: auto_io_layout_fac ! Used to compute IO layout when auto_mask_table is True.
128 logical :: mask_table_exists ! True if there is a mask table file
129 logical :: is_MOM_domain ! True if this domain is being set for MOM, and not another component like SIS2.
130 character(len=128) :: inputdir ! The directory in which to find the diag table
131 character(len=200) :: mask_table ! The file name and later the full path to the diag table
132 character(len=64) :: inc_nm ! The name of the memory include file
133 character(len=200) :: mesg ! A string to use for error messages
134
135 integer :: nip_parsed, njp_parsed
136 character(len=8) :: char_xsiz, char_ysiz, char_niglobal, char_njglobal
137 character(len=40) :: nihalo_nm, njhalo_nm, layout_nm, io_layout_nm, masktable_nm
138 character(len=40) :: niproc_nm, njproc_nm
139 character(len=200) :: topo_config
140 integer :: id_clock_auto_mask
1411 character(len=:), allocatable :: masktable_desc
1421 character(len=:), allocatable :: auto_mask_table_fname ! Auto-generated mask table file name
143 ! This include declares and sets the variable "version".
144# include "version_variable.h"
145 character(len=40) :: mdl ! This module's name.
146
1472 PEs_used = num_PEs()
148
1491 mdl = "MOM_domains"
150
1511 is_symmetric = .true. ; if (present(symmetric)) is_symmetric = symmetric
1521 if (present(min_halo)) mdl = trim(mdl)//" min_halo"
153
1541 inc_nm = "MOM_memory.h" ; if (present(include_name)) inc_nm = trim(include_name)
155
1561 nihalo_nm = "NIHALO" ; njhalo_nm = "NJHALO"
1571 layout_nm = "LAYOUT" ; io_layout_nm = "IO_LAYOUT" ; masktable_nm = "MASKTABLE"
1581 niproc_nm = "NIPROC" ; njproc_nm = "NJPROC"
1591 if (present(param_suffix)) then ; if (len(trim(adjustl(param_suffix))) > 0) then
1600 nihalo_nm = "NIHALO"//(trim(adjustl(param_suffix)))
1610 njhalo_nm = "NJHALO"//(trim(adjustl(param_suffix)))
1620 layout_nm = "LAYOUT"//(trim(adjustl(param_suffix)))
1630 io_layout_nm = "IO_LAYOUT"//(trim(adjustl(param_suffix)))
1640 masktable_nm = "MASKTABLE"//(trim(adjustl(param_suffix)))
1650 niproc_nm = "NIPROC"//(trim(adjustl(param_suffix)))
1660 njproc_nm = "NJPROC"//(trim(adjustl(param_suffix)))
167 endif ; endif
168
1691 is_static = .false. ; if (present(static_memory)) is_static = static_memory
1701 if (is_static) then
1710 if (.not.present(NIHALO)) call MOM_error(FATAL, "NIHALO must be "// &
1720 "present in the call to MOM_domains_init with static memory.")
1730 if (.not.present(NJHALO)) call MOM_error(FATAL, "NJHALO must be "// &
1740 "present in the call to MOM_domains_init with static memory.")
1750 if (.not.present(NIGLOBAL)) call MOM_error(FATAL, "NIGLOBAL must be "// &
1760 "present in the call to MOM_domains_init with static memory.")
1770 if (.not.present(NJGLOBAL)) call MOM_error(FATAL, "NJGLOBAL must be "// &
1780 "present in the call to MOM_domains_init with static memory.")
1790 if (.not.present(NIPROC)) call MOM_error(FATAL, "NIPROC must be "// &
1800 "present in the call to MOM_domains_init with static memory.")
1810 if (.not.present(NJPROC)) call MOM_error(FATAL, "NJPROC must be "// &
1820 "present in the call to MOM_domains_init with static memory.")
183 endif
184
185 ! Read all relevant parameters and write them to the model log.
1861 call log_version(param_file, mdl, version, "", log_to_all=.true., layout=.true.)
187 call get_param(param_file, mdl, "REENTRANT_X", reentrant(1), &
1881 "If true, the domain is zonally reentrant.", default=.true.)
189 call get_param(param_file, mdl, "REENTRANT_Y", reentrant(2), &
190 "If true, the domain is meridionally reentrant.", &
1911 default=.false.)
192 call get_param(param_file, mdl, "TRIPOLAR_N", tripolar_N, &
193 "Use tripolar connectivity at the northern edge of the "//&
194 "domain. With TRIPOLAR_N, NIGLOBAL must be even.", &
1951 default=.false.)
196
197# ifndef NOT_SET_AFFINITY
198 !$ if (.not.MOM_thread_affinity_set()) then
199 !$ call get_param(param_file, mdl, "OCEAN_OMP_THREADS", ocean_nthreads, &
200 !$ "The number of OpenMP threads that MOM6 will use.", &
201 !$ default=1, layoutParam=.true.)
202 !$ call get_param(param_file, mdl, "OCEAN_OMP_HYPER_THREAD", ocean_omp_hyper_thread, &
203 !$ "If True, use hyper-threading.", default=.false., layoutParam=.true.)
204 !$ call set_MOM_thread_affinity(ocean_nthreads, ocean_omp_hyper_thread)
205 !$ endif
206# endif
207
208 call log_param(param_file, mdl, "!SYMMETRIC_MEMORY_", is_symmetric, &
209 "If defined, the velocity point data domain includes every face of the "//&
210 "thickness points. In other words, some arrays are larger than others, "//&
211 "depending on where they are on the staggered grid. Also, the starting "//&
212 "index of the velocity-point arrays is usually 0, not 1. "//&
213 "This can only be set at compile time.",&
2141 layoutParam=.true.)
215 call get_param(param_file, mdl, "NONBLOCKING_UPDATES", nonblocking, &
216 "If true, non-blocking halo updates may be used.", &
2171 default=.false., layoutParam=.true.)
218 call get_param(param_file, mdl, "THIN_HALO_UPDATES", thin_halos, &
219 "If true, optional arguments may be used to specify the width of the "//&
220 "halos that are updated with each call.", &
2211 default=.true., layoutParam=.true.)
222
2231 nihalo_dflt = 4 ; njhalo_dflt = 4
2241 if (present(NIHALO)) nihalo_dflt = NIHALO
2251 if (present(NJHALO)) njhalo_dflt = NJHALO
226
227 call log_param(param_file, mdl, "!STATIC_MEMORY_", is_static, &
228 "If STATIC_MEMORY_ is defined, the principle variables will have sizes that "//&
229 "are statically determined at compile time. Otherwise the sizes are not "//&
230 "determined until run time. The STATIC option is substantially faster, but "//&
231 "does not allow the PE count to be changed at run time. This can only be "//&
2321 "set at compile time.", layoutParam=.true.)
233
2341 if (is_static) then
235 call get_param(param_file, mdl, "NIGLOBAL", n_global(1), &
236 "The total number of thickness grid points in the x-direction in the physical "//&
237 "domain. With STATIC_MEMORY_ this is set in "//trim(inc_nm)//" at compile time.", &
2380 default=NIGLOBAL)
239 call get_param(param_file, mdl, "NJGLOBAL", n_global(2), &
240 "The total number of thickness grid points in the y-direction in the physical "//&
241 "domain. With STATIC_MEMORY_ this is set in "//trim(inc_nm)//" at compile time.", &
2420 default=NJGLOBAL)
2430 if (n_global(1) /= NIGLOBAL) call MOM_error(FATAL,"MOM_domains_init: " // &
2440 "static mismatch for NIGLOBAL_ domain size. Header file does not match input namelist")
2450 if (n_global(2) /= NJGLOBAL) call MOM_error(FATAL,"MOM_domains_init: " // &
2460 "static mismatch for NJGLOBAL_ domain size. Header file does not match input namelist")
247
248 ! Check the requirement of equal sized compute domains when STATIC_MEMORY_ is used.
2490 if ((MOD(NIGLOBAL, NIPROC) /= 0) .OR. (MOD(NJGLOBAL, NJPROC) /= 0)) then
2500 write( char_xsiz, '(I0)' ) NIPROC
2510 write( char_ysiz, '(I0)' ) NJPROC
2520 write( char_niglobal, '(I0)' ) NIGLOBAL
2530 write( char_njglobal, '(I0)' ) NJGLOBAL
254 call MOM_error(WARNING, 'MOM_domains: Processor decomposition (NIPROC_,NJPROC_) = ('//&
255 trim(char_xsiz)//','//trim(char_ysiz)//') does not evenly divide size '//&
2560 'set by preprocessor macro ('//trim(char_niglobal)//','//trim(char_njglobal)//').')
257 call MOM_error(FATAL,'MOM_domains: #undef STATIC_MEMORY_ in '//trim(inc_nm)//' to use '//&
2580 'dynamic allocation, or change processor decomposition to evenly divide the domain.')
259 endif
260 else
261 call get_param(param_file, mdl, "NIGLOBAL", n_global(1), &
262 "The total number of thickness grid points in the x-direction in the physical "//&
263 "domain. With STATIC_MEMORY_ this is set in "//trim(inc_nm)//" at compile time.", &
2641 fail_if_missing=.true.)
265 call get_param(param_file, mdl, "NJGLOBAL", n_global(2), &
266 "The total number of thickness grid points in the y-direction in the physical "//&
267 "domain. With STATIC_MEMORY_ this is set in "//trim(inc_nm)//" at compile time.", &
2681 fail_if_missing=.true.)
269 endif
270
271 call get_param(param_file, mdl, trim(nihalo_nm), n_halo(1), &
272 "The number of halo points on each side in the x-direction. How this is set "//&
273 "varies with the calling component and static or dynamic memory configuration.", &
2741 default=nihalo_dflt)
275 call get_param(param_file, mdl, trim(njhalo_nm), n_halo(2), &
276 "The number of halo points on each side in the y-direction. How this is set "//&
277 "varies with the calling component and static or dynamic memory configuration.", &
2781 default=njhalo_dflt)
2791 if (present(min_halo)) then
2800 n_halo(1) = max(n_halo(1), min_halo(1))
2810 min_halo(1) = n_halo(1)
2820 n_halo(2) = max(n_halo(2), min_halo(2))
2830 min_halo(2) = n_halo(2)
284 ! These are generally used only with static memory, so they are considered layout params.
2850 call log_param(param_file, mdl, "!NIHALO min_halo", n_halo(1), layoutParam=.true.)
2860 call log_param(param_file, mdl, "!NJHALO min_halo", n_halo(2), layoutParam=.true.)
287 endif
2881 if (is_static .and. .not.present(min_halo)) then
2890 if (n_halo(1) /= NIHALO) call MOM_error(FATAL,"MOM_domains_init: " // &
2900 "static mismatch for "//trim(nihalo_nm)//" domain size")
2910 if (n_halo(2) /= NJHALO) call MOM_error(FATAL,"MOM_domains_init: " // &
2920 "static mismatch for "//trim(njhalo_nm)//" domain size")
293 endif
294
2951 call get_param(param_file, mdl, "INPUTDIR", inputdir, do_not_log=.true., default=".")
2961 inputdir = slasher(inputdir)
297
2981 is_MOM_domain = .true.
2991 if (present(domain_name)) then
3001 is_MOM_domain = (index(domain_name, "MOM") > 1)
301 endif
302
3031 if (is_MOM_domain) then
3040 call get_param(param_file, mdl, "TOPO_CONFIG", topo_config, do_not_log=.true., fail_if_missing=.true.)
305 else ! SIS2 has a default value for TOPO_CONFIG.
3061 call get_param(param_file, mdl, "TOPO_CONFIG", topo_config, default="file", do_not_log=.true.)
307 endif
308
3091 auto_mask_table = .false.
3101 if (.not. present(param_suffix) .and. .not. is_static .and. trim(topo_config) == 'file') then
311 call get_param(param_file, mdl, 'AUTO_MASKTABLE', auto_mask_table, &
312 "Turn on automatic mask table generation to eliminate land blocks.", &
3130 default=.false., layoutParam=.true.)
314 endif
315
316 masktable_desc = "A text file to specify n_mask, layout and mask_list. This feature masks out "//&
317 "processors that contain only land points. The first line of mask_table is the "//&
318 "number of regions to be masked out. The second line is the layout of the "//&
319 "model and must be consistent with the actual model layout. The following "//&
320 "(n_mask) lines give the logical positions of the processors that are masked "//&
321 "out. The mask_table can be created by tools like check_mask. The following "//&
322 "example of mask_table masks out 2 processors, (1,2) and (3,6), out of the 24 "//&
3231 "in a 4x6 layout: \n 2\n 4,6\n 1,2\n 3,6\n"
324
3251 if (auto_mask_table) then
3260 id_clock_auto_mask = cpu_clock_id('(Ocean gen_auto_mask_table)', grain=CLOCK_ROUTINE)
3270 auto_mask_table_fname = "MOM_auto_mask_table"
328
329 ! Auto-generate a mask file and determine the layout
3300 call cpu_clock_begin(id_clock_auto_mask)
3310 if (is_root_PE()) then
332 call gen_auto_mask_table(n_global, reentrant, tripolar_N, PEs_used, param_file, inputdir, &
3330 auto_mask_table_fname, auto_layout, US)
334 endif
3350 call broadcast(auto_layout, length=2)
3360 call cpu_clock_end(id_clock_auto_mask)
337
3380 mask_table = auto_mask_table_fname
339 call log_param(param_file, mdl, trim(masktable_nm), mask_table, masktable_desc, &
3400 default="MOM_mask_table", layoutParam=.true.)
341 else
342 call get_param(param_file, mdl, trim(masktable_nm), mask_table, masktable_desc, &
3431 default="MOM_mask_table", layoutParam=.true.)
344 endif
345
346 ! First, check the run directory for the mask_table input file.
3471 mask_table_exists = file_exists(trim(mask_table))
348 ! If not found, check the input directory
3491 if (.not. mask_table_exists) then
3501 mask_table = trim(inputdir)//trim(mask_table)
3511 mask_table_exists = file_exists(mask_table)
352 endif
353
3541 if (is_static) then
3550 layout(1) = NIPROC ; layout(2) = NJPROC
356 else
357 call get_param(param_file, mdl, trim(layout_nm), layout, &
358 "The processor layout to be used, or 0, 0 to automatically set the layout "//&
3591 "based on the number of processors.", defaults=(/0, 0/), do_not_log=.true.)
360 call get_param(param_file, mdl, trim(niproc_nm), nip_parsed, &
3611 "The number of processors in the x-direction.", default=-1, do_not_log=.true.)
362 call get_param(param_file, mdl, trim(njproc_nm), njp_parsed, &
3631 "The number of processors in the y-direction.", default=-1, do_not_log=.true.)
3641 if (nip_parsed > -1) then
3650 if ((layout(1) > 0) .and. (layout(1) /= nip_parsed)) &
366 call MOM_error(FATAL, trim(layout_nm)//" and "//trim(niproc_nm)//" set inconsistently. "//&
3670 "Only LAYOUT should be used.")
3680 layout(1) = nip_parsed
369 call MOM_mesg(trim(niproc_nm)//" used to set "//trim(layout_nm)//" in dynamic mode. "//&
3700 "Shift to using "//trim(layout_nm)//" instead.")
371 endif
3721 if (njp_parsed > -1) then
3730 if ((layout(2) > 0) .and. (layout(2) /= njp_parsed)) &
374 call MOM_error(FATAL, trim(layout_nm)//" and "//trim(njproc_nm)//" set inconsistently. "//&
3750 "Only "//trim(layout_nm)//" should be used.")
3760 layout(2) = njp_parsed
377 call MOM_mesg(trim(njproc_nm)//" used to set "//trim(layout_nm)//" in dynamic mode. "//&
3780 "Shift to using "//trim(layout_nm)//" instead.")
379 endif
380
3811 if (auto_mask_table) then
3820 if (layout(1) /= 0 .and. layout(1) /= auto_layout(1)) then
3830 call MOM_error(FATAL, "Cannot set LAYOUT or NIPROC when AUTO_MASKTABLE is enabled.")
384 endif
3850 if (layout(2) /= 0 .and. layout(2) /= auto_layout(2)) then
3860 call MOM_error(FATAL, "Cannot set LAYOUT or NJPROC when AUTO_MASKTABLE is enabled.")
387 endif
3880 layout(:) = auto_layout(:)
389 endif
390
3911 if ( (layout(1) == 0) .and. (layout(2) == 0) ) &
3920 call MOM_define_layout(n_global, PEs_used, layout)
3931 if ( (layout(1) /= 0) .and. (layout(2) == 0) ) layout(2) = PEs_used / layout(1)
3941 if ( (layout(1) == 0) .and. (layout(2) /= 0) ) layout(1) = PEs_used / layout(2)
395
3961 if (layout(1)*layout(2) /= PEs_used .and. (.not. mask_table_exists) ) then
397 write(mesg,'("MOM_domains_init: The product of the two components of layout, ", &
398 & I0,", ",I0,", is not the number of PEs used, ",I0,".")') &
3990 layout(1), layout(2), PEs_used
4000 call MOM_error(FATAL, mesg)
401 endif
402 endif
403 call log_param(param_file, mdl, trim(niproc_nm), layout(1), &
404 "The number of processors in the x-direction. With STATIC_MEMORY_ this "//&
4051 "is set in "//trim(inc_nm)//" at compile time.", layoutParam=.true.)
406 call log_param(param_file, mdl, trim(njproc_nm), layout(2), &
407 "The number of processors in the y-direction. With STATIC_MEMORY_ this "//&
4081 "is set in "//trim(inc_nm)//" at compile time.", layoutParam=.true.)
409 call log_param(param_file, mdl, trim(layout_nm), layout, &
4101 "The processor layout that was actually used.", layoutParam=.true.)
411
412 ! Idiot check that fewer PEs than columns have been requested
4131 if (layout(1)*layout(2) > n_global(1)*n_global(2)) then
4140 write(mesg,'(a,I0,a,I0,a)') 'You requested to use ', layout(1)*layout(2), &
4150 ' PEs but there are only ', n_global(1)*n_global(2), ' columns in the model'
4160 call MOM_error(FATAL, mesg)
417 endif
418
4191 if (mask_table_exists) &
4200 call MOM_error(NOTE, 'MOM_domains_init: reading maskmap information from '//trim(mask_table))
421
422 ! Set up the I/O layout, it will be checked later that it uses an even multiple of the number of
423 ! PEs in each direction.
4241 io_layout(:) = (/ 1, 1 /)
425
426 ! Compute a valid IO layout if auto_mask_table is on. Otherwise, read in IO_LAYOUT parameter,
4271 if (auto_mask_table) then
428 call get_param(param_file, mdl, "AUTO_IO_LAYOUT_FAC", auto_io_layout_fac, &
429 "When AUTO_MASKTABLE is enabled, io layout is calculated by performing integer "//&
430 "division of the runtime-determined domain layout with this factor. If the factor "//&
431 "is set to 0 (default), the io layout is set to 1,1.", &
4320 default=0, layoutParam=.true.)
4330 if (auto_io_layout_fac>0) then
4340 io_layout(1) = max(layout(1)/auto_io_layout_fac, 1)
4350 io_layout(2) = max(layout(2)/auto_io_layout_fac, 1)
4360 elseif (auto_io_layout_fac<0) then
4370 call MOM_error(FATAL, 'AUTO_IO_LAYOUT_FAC must be a nonnegative integer.')
438 endif
439 call log_param(param_file, mdl, trim(io_layout_nm), io_layout, &
440 "The processor layout to be used, or 0,0 to automatically set the io_layout "//&
4410 "to be the same as the layout.", layoutParam=.true.)
442 else
443 call get_param(param_file, mdl, trim(io_layout_nm), io_layout, &
444 "The processor layout to be used, or 0,0 to automatically set the io_layout "//&
4451 "to be the same as the layout.", defaults=(/1, 1/), layoutParam=.true.)
446 endif
447
448 ! Create an unmasked domain if requested. This is used for writing out unmasked ocean geometry.
4491 if (present(MOM_dom_unmasked) .and. mask_table_exists) then
4500 call MOM_define_layout(n_global, PEs_used, layout_unmasked)
451 call create_MOM_domain(MOM_dom_unmasked, n_global, n_halo, reentrant, tripolar_N, layout_unmasked, &
452 domain_name=domain_name, symmetric=symmetric, thin_halos=thin_halos, &
4530 nonblocking=nonblocking)
454 endif
455
456 call create_MOM_domain(MOM_dom, n_global, n_halo, reentrant, tripolar_N, layout, &
457 io_layout=io_layout, domain_name=domain_name, mask_table=mask_table, &
4581 symmetric=symmetric, thin_halos=thin_halos, nonblocking=nonblocking)
459
4602end subroutine MOM_domains_init
461
462!> Given a global array size and a number of (logical) processors, provide a layout of the
463!! processors in the two directions where the total number of processors is the product of
464!! the two layouts and number of points in the partitioned arrays are as close as possible
465!! to an aspect ratio of 1.
4660subroutine MOM_define_layout(n_global, ndivs, layout)
467 integer, dimension(2), intent(in) :: n_global !< The total number of gridpoints in 2 directions
468 integer, intent(in) :: ndivs !< The total number of (logical) PEs
469 integer, dimension(2), intent(out) :: layout !< The generated layout of PEs
470
471 ! Local variables
472 integer :: isz, jsz, idiv, jdiv
473
474 ! At present, this algorithm is a copy of mpp_define_layout, but it could perhaps be improved?
475
4760 isz = n_global(1) ; jsz = n_global(2)
477 ! First try to divide ndivs to match the domain aspect ratio. If this is not an even
478 ! divisor of ndivs, reduce idiv until a factor is found.
4790 idiv = max(nint( sqrt(float(ndivs*isz)/jsz) ), 1)
4800 do while( mod(ndivs,idiv) /= 0 )
4810 idiv = idiv - 1
482 enddo ! This will terminate at idiv=1 if not before
4830 jdiv = ndivs / idiv
484
4850 layout = (/ idiv, jdiv /)
4860end subroutine MOM_define_layout
487
488!> Given a desired number of active npes, generate a layout and mask_table
4890subroutine gen_auto_mask_table(n_global, reentrant, tripolar_N, npes, param_file, inputdir, filename, layout, US)
490 integer, dimension(2), intent(in) :: n_global !< The total number of gridpoints in 2 directions
491 logical, dimension(2), intent(in) :: reentrant !< True if the x- and y- directions are periodic.
492 logical, intent(in) :: tripolar_N !< A flag indicating whether there is n. tripolar connectivity
493 integer, intent(in) :: npes !< The desired number of active PEs.
494 type(param_file_type), intent(in) :: param_file !< A structure to parse for run-time parameters
495 character(len=128), intent(in) :: inputdir !< INPUTDIR parameter
496 character(len=:), allocatable, intent(in) :: filename !< Mask table file path (to be auto-generated.)
497 integer, dimension(2), intent(out) :: layout !< The generated layout of PEs (incl. masked blocks)
498 type(unit_scale_type), optional, pointer :: US !< A dimensional unit scaling type
499
500 ! Local variables
5010 real, dimension(n_global(1), n_global(2)) :: D ! Bathymetric depth (to be read in from TOPO_FILE) [Z ~> m]
5020 integer, dimension(:,:), allocatable :: mask ! Cell masks (based on D and MINIMUM_DEPTH)
503 character(len=200) :: topo_filepath, topo_file ! Strings for file/path
504 character(len=200) :: topo_varname ! Variable name in file
505 character(len=200) :: topo_config
506 character(len=40) :: mdl = "gen_auto_mask_table" ! This subroutine's name.
507 integer :: i, j, p
508 real :: Dmask ! The depth for masking in the same units as D [Z ~> m]
509 real :: min_depth ! The minimum ocean depth in the same units as D [Z ~> m]
510 real :: mask_depth ! The depth shallower than which to mask a point as land. [Z ~> m]
511 real :: glob_ocn_frac ! ratio of ocean points to total number of points [nondim]
512 real :: r_p ! aspect ratio for division count p. [nondim]
513 real :: m_to_Z ! A conversion factor from m to height units [Z m-1 ~> 1]
514 integer :: nx, ny ! global domain sizes
515 integer, parameter :: ibuf=2, jbuf=2
516 real, parameter :: r_extreme = 4.0 ! aspect ratio limit (>1) for a layout to be considered [nondim]
517 integer :: num_masked_blocks
5180 integer, allocatable :: mask_table(:,:)
519
5200 m_to_Z = 1.0 ; if (present(US)) m_to_Z = US%m_to_Z
521
522 ! Read in params necessary for auto-masking
523 call get_param(param_file, mdl, "MINIMUM_DEPTH", min_depth, &
5240 units="m", default=0.0, scale=m_to_Z, do_not_log=.true.)
525 call get_param(param_file, mdl, "MASKING_DEPTH", mask_depth, &
5260 units="m", default=-9999.0, scale=m_to_Z, do_not_log=.true.)
5270 call get_param(param_file, mdl, "TOPO_CONFIG", topo_config, default="file", do_not_log=.true.)
5280 call get_param(param_file, mdl, "TOPO_FILE", topo_file, do_not_log=.true., default="topog.nc")
5290 call get_param(param_file, mdl, "TOPO_VARNAME", topo_varname, do_not_log=.true., default="depth")
5300 topo_filepath = trim(inputdir)//trim(topo_file)
531
532 ! Sanity checks
5330 if (.not. is_root_pe()) then
5340 call MOM_error(FATAL, 'gen_auto_mask_table should only be called by the root PE.')
535 endif
5360 if (trim(topo_config) /= "file") then
5370 call MOM_error(FATAL, 'Auto mask table only works with TOPO_CONFIG="file"')
538 endif
5390 if (.not.file_exists(topo_filepath)) then
5400 call MOM_error(FATAL, " gen_auto_mask_table: Unable to open "//trim(topo_filepath))
541 endif
542
5430 nx = n_global(1)
5440 ny = n_global(2)
545
546 ! Read in bathymetric depth.
5470 D(:,:) = -9.0e30 * m_to_Z ! Initializing to a very large negative depth (tall mountains) everywhere.
548 call read_field(topo_filepath, trim(topo_varname), D, start=(/1, 1/), nread=n_global, no_domain=.true., &
5490 scale=m_to_Z)
550
5510 allocate(mask(nx+2*ibuf, ny+2*jbuf), source=0)
552
553 ! Determine cell masks
5540 Dmask = mask_depth
5550 if (mask_depth == -9999.0*m_to_Z) Dmask = min_depth
5560 do i=1,nx ; do j=1,ny
5570 if (D(i,j) <= Dmask) then
5580 mask(i+ibuf,j+jbuf) = 0
559 else
5600 mask(i+ibuf,j+jbuf) = 1
561 endif
562 enddo ; enddo
563
564 ! fill in buffer cells
565
5660 if (reentrant(1)) then ! REENTRANT_X
5670 mask(1:ibuf, :) = mask(nx+1:nx+ibuf, :)
5680 mask(ibuf+nx+1:nx+2*ibuf, :) = mask(ibuf+1:2*ibuf, :)
569 endif
570
5710 if (reentrant(2)) then ! REENTRANT_Y
5720 mask(:, 1:jbuf) = mask(:, ny+1:ny+jbuf)
5730 mask(:, jbuf+ny+1:ny+2*jbuf) = mask(:, jbuf+1:2*jbuf)
574 endif
575
5760 if (tripolar_N) then ! TRIPOLAR_N
5770 do i=1,nx+2*ibuf
5780 do j=1,jbuf
5790 mask(i, jbuf+ny+j) = mask(nx+2*ibuf+1-i, jbuf+ny+1-j)
580 enddo
581 enddo
582 endif
583
584 ! Tripolar Stitch Fix: In cases where masking is asymmetrical across the tripolar stitch, there's a possibility
585 ! that certain unmasked blocks won't be able to obtain grid metrics from the halo points. This occurs when the
586 ! neighboring block on the opposite side of the tripolar stitch is masked. As a consequence, certain metrics like
587 ! dxT and dyT may be calculated through extrapolation (refer to extrapolate_metric), potentially leading to the
588 ! generation of non-positive values. This can result in divide-by-zero errors elsewhere, e.g., in MOM_hor_visc.F90.
589 ! Currently, the safest and most general solution is to prohibit masking along the tripolar stitch:
5900 if (tripolar_N) then
5910 mask(:, jbuf+ny) = 1
592 endif
593
5940 glob_ocn_frac = real(sum(mask(1+ibuf:nx+ibuf, 1+jbuf:ny+jbuf))) / (nx * ny)
595
596 ! Iteratively check for all possible division counts starting from the upper bound of npes/glob_ocn_frac,
597 ! which is over-optimistic for realistic domains, but may be satisfied with idealized domains.
5980 do p = ceiling(npes/glob_ocn_frac), npes, -1
599
600 ! compute the layout for the current division count, p
6010 call MOM_define_layout(n_global, p, layout)
602
603 ! don't bother checking this p if the aspect ratio is extreme
6040 r_p = (real(nx)/layout(1)) / (real(ny)/layout(2))
6050 if ( r_p * r_extreme < 1 .or. r_extreme < r_p ) cycle
606
607 ! Get the number of masked_blocks for this particular division count
6080 call determine_land_blocks(mask, nx, ny, layout(1), layout(2), ibuf, jbuf, num_masked_blocks)
609
610 ! If we can eliminate enough blocks to reach the target npes, adopt
611 ! this p (and the associated layout) and terminate the iteration.
6120 if (p-num_masked_blocks <= npes) then
6130 call MOM_error(NOTE, "Found the optimum layout for auto-masking. Terminating iteration...")
6140 exit
615 endif
616 enddo
617
6180 if (num_masked_blocks == 0) then
619 call MOM_error(FATAL, "Couldn't auto-eliminate any land blocks. Try to increase the number "//&
6200 "of MOM6 PEs or set AUTO_MASKTABLE to False.")
621 endif
622
623 ! Call determine_land_blocks once again, this time to retrieve and write out the mask_table.
6240 allocate(mask_table(num_masked_blocks,2))
6250 call determine_land_blocks(mask, nx, ny, layout(1), layout(2), ibuf, jbuf, num_masked_blocks, mask_table)
6260 call write_auto_mask_file(mask_table, layout, npes, filename)
6270 deallocate(mask_table)
6280 deallocate(mask)
629
6300end subroutine gen_auto_mask_table
631
632!> Given a number of domain divisions, compute the max number of land blocks that can be eliminated,
633!! and return the resulting mask table if requested.
6340subroutine determine_land_blocks(mask, nx, ny, idiv, jdiv, ibuf, jbuf, num_masked_blocks, mask_table)
635 integer, dimension(:,:), intent(in) :: mask !< cell masks based on depth and MINIMUM_DEPTH
636 integer, intent(in) :: nx !< Total number of gridpoints in x-dir (global)
637 integer, intent(in) :: ny !< Total number of gridpoints in y-dir (global)
638 integer, intent(in) :: idiv !< number of divisions along x-dir
639 integer, intent(in) :: jdiv !< number of divisions along y-dir
640 integer, intent(in) :: ibuf !< number of buffer cells in x-dir.
641 !! (not necessarily the same as NIHALO)
642 integer, intent(in) :: jbuf !< number of buffer cells in y-dir.
643 !! (not necessarily the same as NJHALO)
644 integer, intent(out) :: num_masked_blocks !< the final number of masked blocks
645 integer, intent(out), optional :: mask_table(:,:) !< the resulting array of mask_table
646 ! integer
6470 integer, dimension(idiv) :: ibegin !< The starting index of each division along x axis
6480 integer, dimension(idiv) :: iend !< The ending index of each division along x axis
6490 integer, dimension(jdiv) :: jbegin !< The starting index of each division along y axis
6500 integer, dimension(jdiv) :: jend !< The ending index of each division along y axis
651 integer :: i, j, ib, ie, jb,je
652
6530 call compute_extent(1, nx, idiv, ibegin, iend)
6540 call compute_extent(1, ny, jdiv, jbegin, jend)
655
6560 num_masked_blocks = 0
657
6580 do i=1,idiv
6590 ib = ibegin(i)
6600 ie = iend(i) + 2 * ibuf
6610 do j=1,jdiv
6620 jb = jbegin(j)
6630 je = jend(j) + 2 * jbuf
664
6650 if (any(mask(ib:ie,jb:je)==1)) cycle
666
6670 num_masked_blocks = num_masked_blocks + 1
668
6690 if (present(mask_table)) then
6700 if ( num_masked_blocks > size(mask_table, dim=1)) then
6710 call MOM_error(FATAL, "The mask_table argument passed to determine_land_blocks() has insufficient size.")
672 endif
673
6740 mask_table(num_masked_blocks,1) = i
6750 mask_table(num_masked_blocks,2) = j
676 endif
677 enddo
678 enddo
679
6800end subroutine determine_land_blocks
681
682!> Write out the auto-generated mask information to a file in the run directory.
6830subroutine write_auto_mask_file(mask_table, layout, npes, filename)
684 integer, intent(in) :: mask_table(:,:) !> mask table array to be written out.
685 integer, dimension(2), intent(in) :: layout !> PE layout
686 integer, intent(in) :: npes !> Number of divisions (incl. eliminated ones)
687 character(len=:), allocatable, intent(in) :: filename !> file name for the mask_table to be written
688 ! local
689 integer :: file_ascii= -1 !< The unit number of the auto-generated mask_file file.
690 integer :: true_num_masked_blocks
691 integer :: p
692
693 ! Eliminate only enough blocks to ensure that the number of active blocks precisely matches the target npes.
6940 true_num_masked_blocks = layout(1) * layout(2) - npes
695
6960 call open_ASCII_file(file_ascii, trim(filename), action=WRITEONLY_FILE)
6970 write(file_ascii, '(I0)') true_num_masked_blocks
6980 write(file_ascii, '(I0,",",I0)') layout(1), layout(2)
6990 do p = 1, true_num_masked_blocks
7000 write(file_ascii, '(I0,",",I0)') mask_table(p,1), mask_table(p,2)
701 enddo
7020 call close_file(file_ascii)
7030end subroutine write_auto_mask_file
704
705end module MOM_domains