Source code for helpers.blocks

[docs] class BlockDecomposition(object): """ Class decomposing a cartesian space domain (1D to 3D) into a given number of processors. Parameters ---------- nProcs : int Total number of processors for space block decomposition. gridSizes : list[int] Number of grid points in each dimension algo : str, optional Algorithm used for the block decomposition : - Hybrid : approach minimizing interface communication, inspired from the `[Hybrid CFD solver] <https://web.stanford.edu/group/ctr/ResBriefs07/5_larsson1_pp47_58.pdf>`_. - ChatGPT : quickly generated using `[ChatGPT] <https://chatgpt.com>`_. The default is "Hybrid". gRank : int, optional If provided, the global rank that will determine the local block distribution. Default is None. order : str, optional The order used when computing the rank block distribution. Default is `C`. """ def __init__(self, nProcs, gridSizes, algo="Hybrid", gRank=None, order="C"): dim = len(gridSizes) assert dim in [1, 2, 3], "block decomposition only works for 1D, 2D or 3D domains" if algo == "ChatGPT": nBlocks = [1] * dim for i in range(2, int(nProcs**0.5) + 1): while nProcs % i == 0: nBlocks[0] *= i nProcs //= i nBlocks.sort() if nProcs > 1: nBlocks[0] *= nProcs nBlocks.sort() while len(nBlocks) < dim: smallest = nBlocks.pop(0) nBlocks += [1, smallest] nBlocks.sort() while len(nBlocks) > dim: smallest = nBlocks.pop(0) next_smallest = nBlocks.pop(0) nBlocks.append(smallest * next_smallest) nBlocks.sort() elif algo == "Hybrid": rest = nProcs facs = { 1: [1], 2: [2, 1], 3: [2, 3, 1], }[dim] exps = [0] * dim for n in range(dim - 1): while (rest % facs[n]) == 0: exps[n] = exps[n] + 1 rest = rest // facs[n] if rest > 1: facs[dim - 1] = rest exps[dim - 1] = 1 nBlocks = [1] * dim for n in range(dim - 1, -1, -1): while exps[n] > 0: dummymax = -1 dmax = 0 for d, nPts in enumerate(gridSizes): dummy = (nPts + nBlocks[d] - 1) // nBlocks[d] if dummy >= dummymax: dummymax = dummy dmax = d nBlocks[dmax] = nBlocks[dmax] * facs[n] exps[n] = exps[n] - 1 else: raise NotImplementedError(f"algo={algo}") # Store attributes self.dim = dim self.nBlocks = nBlocks self.gridSizes = gridSizes # Used for rank block distribution self.gRank = gRank self.order = order @property def ranks(self): gRank, order = self.gRank, self.order assert gRank is not None, "gRank attribute need to be set" dim, nBlocks = self.dim, self.nBlocks if dim == 1: return (gRank,) elif dim == 2: div = nBlocks[-1] if order == "C" else nBlocks[0] return (gRank // div, gRank % div) else: raise NotImplementedError(f"dim={dim}") @property def localBounds(self): iLocList, nLocList = [], [] for rank, nPoints, nBlocks in zip(self.ranks, self.gridSizes, self.nBlocks): n0 = nPoints // nBlocks nRest = nPoints - nBlocks * n0 nLoc = n0 + 1 * (rank < nRest) iLoc = rank * n0 + nRest * (rank >= nRest) + rank * (rank < nRest) iLocList.append(iLoc) nLocList.append(nLoc) return iLocList, nLocList
if __name__ == "__main__": # Base usage of this module for a 2D decomposition from mpi4py import MPI from time import sleep comm: MPI.Intracomm = MPI.COMM_WORLD MPI_SIZE = comm.Get_size() MPI_RANK = comm.Get_rank() blocks = BlockDecomposition(MPI_SIZE, [256, 64], gRank=MPI_RANK) if MPI_RANK == 0: print(f"nBlocks : {blocks.nBlocks}") ranks = blocks.ranks bounds = blocks.localBounds comm.Barrier() sleep(0.01 * MPI_RANK) print(f"[Rank {MPI_RANK}] pRankX={ranks}, bounds={bounds}")