|
1 | 1 | import numpy as np |
2 | 2 |
|
3 | 3 | import devito as dv |
4 | | -from devito.builtins.utils import check_builtins_args, nbl_to_padsize, pad_outhalo |
| 4 | +from devito.builtins.utils import ( |
| 5 | + axis_slice, check_builtins_args, nbl_to_padsize, pad_outhalo |
| 6 | +) |
5 | 7 | from devito.tools import as_list, as_tuple |
6 | 8 |
|
7 | 9 | __all__ = ['assign', 'gaussian_smooth', 'initialize_function', 'smooth'] |
@@ -217,15 +219,106 @@ def fset(f, g): |
217 | 219 | return f |
218 | 220 |
|
219 | 221 |
|
220 | | -def _initialize_function(function, data, nbl, mapper=None, mode='constant'): |
| 222 | +def _rank_without_interior(function, nbl): |
221 | 223 | """ |
222 | | - Construct the symbolic objects for `initialize_function`. |
| 224 | + Whether some rank owns no part of the interior along a padded Dimension. |
| 225 | +
|
| 226 | + The padding is extended by reading the boundary plane at a fixed global |
| 227 | + index, which only works while every rank owns interior to read from. |
| 228 | +
|
| 229 | + Collective: the answer is reduced over all ranks, since they must then all |
| 230 | + extend the padding the same way. |
223 | 231 | """ |
224 | | - nbl, slices = nbl_to_padsize(nbl, function.ndim) |
| 232 | + distributor = function.grid.distributor |
| 233 | + if not distributor.is_parallel: |
| 234 | + return False |
| 235 | + |
| 236 | + owns = True |
| 237 | + for i, (d, (nl, nr)) in enumerate( |
| 238 | + zip(function.space_dimensions, as_tuple(nbl), strict=True) |
| 239 | + ): |
| 240 | + if function.grid.is_distributed(d): |
| 241 | + # The interior spans `[nl, glb_max - nr]` |
| 242 | + span = distributor.glb_slices[d] |
| 243 | + owns &= not (span.stop <= nl or |
| 244 | + span.start > distributor.glb_shape[i] - 1 - nr) |
| 245 | + |
| 246 | + return not distributor.comm.allreduce(owns, op=dv.mpi.MPI.LAND) |
| 247 | + |
| 248 | + |
| 249 | +def _global_plane(function, axis, index): |
| 250 | + """ |
| 251 | + The plane of `function` at global position `index` along `axis`, assembled |
| 252 | + identically on every rank and keeping `axis` as a length-1 dimension. |
| 253 | +
|
| 254 | + E.g for a 2D function f, the plane at `index` along `y` is f[:, index:index+1], |
| 255 | + which is a 2D array of shape (f.shape[0], 1). |
| 256 | + """ |
| 257 | + distributor = function.grid.distributor |
| 258 | + |
| 259 | + local = np.asarray(function.data[axis_slice(function.ndim, axis, index, |
| 260 | + index + 1)]) |
| 261 | + plane = np.zeros(tuple(distributor.glb_shape[i] if i != axis else 1 |
| 262 | + for i in range(function.ndim)), |
| 263 | + dtype=function.dtype) |
| 264 | + |
| 265 | + # `glb_slices` tile the domain, so each slot gets a single contribution |
| 266 | + if local.size: |
| 267 | + slot = tuple(distributor.glb_slices[dim] if i != axis else slice(None) |
| 268 | + for i, dim in enumerate(function.dimensions)) |
| 269 | + plane[slot] = local.reshape(plane[slot].shape) |
| 270 | + distributor.comm.Allreduce(dv.mpi.MPI.IN_PLACE, plane, op=dv.mpi.MPI.SUM) |
| 271 | + |
| 272 | + return plane |
| 273 | + |
| 274 | + |
| 275 | +def _extend_padding_mpi(function, nbl): |
| 276 | + """ |
| 277 | + Fill the padding of `function` by repeating its nearest boundary plane. |
| 278 | +
|
| 279 | + With MPI, an edge rank may own only padding, while the first interior |
| 280 | + plane belongs to another rank |
| 281 | +
|
| 282 | + edge rank | neighbouring rank |
| 283 | + padding | padding interior |
| 284 | + . . . . | . . . : a b c ... |
| 285 | + a a a a | a a a : a b c ... |
| 286 | + <-------------------------- a |
| 287 | +
|
| 288 | + The edge rank cannot read the interior boundary plane locally, so the |
| 289 | + symbolic extension cannot perform this copy. Global indexing through |
| 290 | + the distributed `Data` accesses that plane on the rank that owns it. |
| 291 | + """ |
| 292 | + distributor = function.grid.distributor |
| 293 | + |
| 294 | + # `TimeFunction`s are rejected upstream, so `axis` indexes `glb_shape` too |
| 295 | + for axis, (nl, nr) in enumerate(as_tuple(nbl)): |
| 296 | + glb_max = distributor.glb_shape[axis] - 1 |
| 297 | + |
| 298 | + # One Dimension at a time, each reading the planes left by the previous |
| 299 | + # ones, so that corners come out filled |
| 300 | + for nb, src, lo in ((nl, nl, 0), (nr, glb_max - nr, glb_max - nr + 1)): |
| 301 | + if nb <= 0: |
| 302 | + continue |
| 303 | + # `plane` is size 1 along `axis`, so it broadcasts over the slot |
| 304 | + plane = _global_plane(function, axis, src) |
| 305 | + function.data[axis_slice(function.ndim, axis, lo, lo + nb)] = plane |
| 306 | + |
| 307 | + |
| 308 | +def _write_interior(function, data, slices): |
| 309 | + """Fill the interior of `function`, that is everything but the padding.""" |
225 | 310 | if isinstance(data, dv.Function): |
226 | 311 | function.data[slices] = data.data[:] |
227 | 312 | else: |
228 | 313 | function.data[slices] = data |
| 314 | + |
| 315 | + |
| 316 | +def _initialize_function(function, data, nbl, mapper=None, mode='constant'): |
| 317 | + """ |
| 318 | + Construct the symbolic objects for `initialize_function`. |
| 319 | + """ |
| 320 | + nbl, slices = nbl_to_padsize(nbl, function.ndim) |
| 321 | + _write_interior(function, data, slices) |
229 | 322 | lhs = [] |
230 | 323 | rhs = [] |
231 | 324 | options = [] |
@@ -385,17 +478,24 @@ def initialize_function(function, data, nbl, mapper=None, mode='constant', |
385 | 478 | else: |
386 | 479 | lhss, rhss, optionss = [], [], [] |
387 | 480 | for f, data in zip(functions, datas, strict=True): |
| 481 | + padsizes, slices = nbl_to_padsize(nbl, f.ndim) |
388 | 482 |
|
389 | | - lhs, rhs, options = _initialize_function(f, data, nbl, mapper, mode) |
| 483 | + # `reflect` has its own, stricter check on the halo size |
| 484 | + if mode == 'constant' and _rank_without_interior(f, padsizes): |
| 485 | + _write_interior(f, data, slices) |
| 486 | + _extend_padding_mpi(f, padsizes) |
| 487 | + continue |
390 | 488 |
|
| 489 | + lhs, rhs, options = _initialize_function(f, data, nbl, mapper, mode) |
391 | 490 | lhss.extend(lhs) |
392 | 491 | rhss.extend(rhs) |
393 | 492 | optionss.extend(options) |
394 | 493 |
|
395 | 494 | assert len(lhss) == len(rhss) == len(optionss) |
396 | 495 |
|
397 | | - name = name or f'initialize_{"_".join(f.name for f in functions)}' |
398 | | - assign(lhss, rhss, options=optionss, name=name, **kwargs) |
| 496 | + if lhss: |
| 497 | + name = name or f'initialize_{"_".join(f.name for f in functions)}' |
| 498 | + assign(lhss, rhss, options=optionss, name=name, **kwargs) |
399 | 499 |
|
400 | 500 | if pad_halo: |
401 | 501 | for f in functions: |
|
0 commit comments