From 1d1a192ae08bc5de03e900ceee09295acf86b1c0 Mon Sep 17 00:00:00 2001 From: Martin Bauer <martin.bauer@fau.de> Date: Fri, 26 Jan 2018 12:16:49 +0100 Subject: [PATCH] New boundary handling - plotting - calculation of force on boundary - vtk outpu --- datahandling/__init__.py | 2 +- datahandling/datahandling_interface.py | 41 +++++++++++- datahandling/parallel_datahandling.py | 63 +++++++++++++++++-- datahandling/serial_datahandling.py | 86 +++++++++++++++++++++++--- parallel/blockiteration.py | 6 +- vtk.py | 10 +-- 6 files changed, 185 insertions(+), 23 deletions(-) diff --git a/datahandling/__init__.py b/datahandling/__init__.py index e30d0a5a3..719573055 100644 --- a/datahandling/__init__.py +++ b/datahandling/__init__.py @@ -21,7 +21,7 @@ def createDataHandling(parallel, domainSize, periodicity, defaultLayout='SoA', d elif periodicity is True: periodicity = (1, 1, 1) else: - periodicity = (int(bool(x)) for x in periodicity) + periodicity = tuple(int(bool(x)) for x in periodicity) if len(periodicity) == 2: periodicity += (1,) diff --git a/datahandling/datahandling_interface.py b/datahandling/datahandling_interface.py index ced806dd8..af69c67c8 100644 --- a/datahandling/datahandling_interface.py +++ b/datahandling/datahandling_interface.py @@ -19,6 +19,16 @@ class DataHandling(ABC): def dim(self): """Dimension of the domain, either 2 or 3""" + @property + @abstractmethod + def shape(self): + """Shape of outer bounding box""" + + @property + @abstractmethod + def periodicity(self): + """Returns tuple of booleans for x,y,(z) directions with True if domain is periodic in that direction""" + @abstractmethod def addArray(self, name, fSize=1, dtype=np.float64, latexName=None, ghostLayers=None, layout=None, cpu=True, gpu=False): """ @@ -86,7 +96,7 @@ class DataHandling(ABC): """Returns the number of ghost layers for a specific field/array""" @abstractmethod - def iterate(self, sliceObj=None, gpu=False, ghostLayers=None): + def iterate(self, sliceObj=None, gpu=False, ghostLayers=None, innerGhostLayers=True): """ Iterate over local part of potentially distributed data structure. """ @@ -135,6 +145,26 @@ class DataHandling(ABC): """Copies data from CPU to GPU for all arrays that have a CPU and a GPU representation""" pass + + @abstractmethod + def vtkWriter(self, fileName, dataNames, ghostLayers=False): + """VTK output for one or multiple arrays + :param fileName: base file name without extension for the VTK output + :param dataNames: list of array names that should be included in the vtk output + :param ghostLayers: true if ghost layer information should be written out as well + :return: a function that can be called with an integer time step to write the current state + i.e vtkWriter('someFile', ['velocity', 'density']) (1) + """ + @abstractmethod + def vtkWriterFlags(self, fileName, dataName, masksToName, ghostLayers=False): + """VTK output for an unsigned integer field, where bits are intepreted as flags + :param fileName: see vtkWriter + :param dataName: name of an array with uint type + :param masksToName: dictionary mapping integer masks to a name in the output + :param ghostLayers: see vtkWriter + :returns: functor that can be called with time step + """ + # ------------------------------- Communication -------------------------------------------------------------------- def synchronizationFunctionCPU(self, names, stencil=None, **kwargs): @@ -152,3 +182,12 @@ class DataHandling(ABC): """ Synchronization of GPU fields, for documentation see CPU version above """ + + def reduceFloatSequence(self, sequence, operation, allReduce=False): + """Takes a sequence of floating point values on each process and reduces it element wise to all + processes (allReduce=True) or only to the root process (allReduce=False). + Possible operations are 'sum', 'min', 'max' + """ + + def reduceIntSequence(self, sequence, operation, allReduce=False): + """See function reduceFloatSequence - this is the same for integers""" diff --git a/datahandling/parallel_datahandling.py b/datahandling/parallel_datahandling.py index b92e7963b..20fd0a99f 100644 --- a/datahandling/parallel_datahandling.py +++ b/datahandling/parallel_datahandling.py @@ -1,5 +1,5 @@ import numpy as np -from pystencils import Field, makeSlice +from pystencils import Field from pystencils.datahandling.datahandling_interface import DataHandling from pystencils.parallel.blockiteration import slicedBlockIteration, blockIteration from pystencils.utils import DotDict @@ -33,6 +33,13 @@ class ParallelDataHandling(DataHandling): self._fieldInformation = {} self._cpuGpuPairs = [] self._customDataTransferFunctions = {} + + self._reduceMap = { + 'sum': wlb.mpi.SUM, + 'min': wlb.mpi.MIN, + 'max': wlb.mpi.MAX, + } + if self._dim == 2: assert self.blocks.getDomainCellBB().size[2] == 1 @@ -40,6 +47,14 @@ class ParallelDataHandling(DataHandling): def dim(self): return self._dim + @property + def shape(self): + return self.blocks.getDomainCellBB().size[:self.dim] + + @property + def periodicity(self): + return self.blocks.periodic[:self._dim] + @property def fields(self): return self._fields @@ -120,22 +135,26 @@ class ParallelDataHandling(DataHandling): for block in self.blocks: block[name1].swapDataPointers(block[name2]) - def iterate(self, sliceObj=None, gpu=False, ghostLayers=True): + def iterate(self, sliceObj=None, gpu=False, ghostLayers=True, innerGhostLayers=True): if ghostLayers is True: ghostLayers = self.defaultGhostLayers elif ghostLayers is False: ghostLayers = 0 + if innerGhostLayers is True: + innerGhostLayers = self.defaultGhostLayers + elif innerGhostLayers is False: + innerGhostLayers = 0 prefix = self.GPU_DATA_PREFIX if gpu else "" if sliceObj is not None: - yield from slicedBlockIteration(self.blocks, sliceObj, ghostLayers, ghostLayers, + yield from slicedBlockIteration(self.blocks, sliceObj, innerGhostLayers, ghostLayers, self.dim, prefix) else: yield from blockIteration(self.blocks, ghostLayers, self.dim, prefix) def gatherArray(self, name, sliceObj=None, allGather=False): if sliceObj is None: - sliceObj = makeSlice[:, :, :] + sliceObj = tuple([slice(None, None, None)] * self.dim) if self.dim == 2: sliceObj += (0.5,) for array in wlb.field.gatherGenerator(self.blocks, name, sliceObj, allGather): @@ -223,3 +242,39 @@ class ParallelDataHandling(DataHandling): syncFunction.addDataToCommunicate(createPacking(self.blocks, name)) return syncFunction + + def reduceFloatSequence(self, sequence, operation, allReduce=False): + if allReduce: + return np.array(wlb.mpi.allreduceReal(sequence, self._reduceMap[operation.lower()])) + else: + return np.array(wlb.mpi.reduceReal(sequence, self._reduceMap[operation.lower()])) + + def reduceIntSequence(self, sequence, operation, allReduce=False): + if allReduce: + return np.array(wlb.mpi.allreduceInt(sequence, self._reduceMap[operation.lower()])) + else: + return np.array(wlb.mpi.reduceInt(sequence, self._reduceMap[operation.lower()])) + + def vtkWriter(self, fileName, dataNames, ghostLayers=False): + if ghostLayers is False: + ghostLayers = 0 + if ghostLayers is True: + ghostLayers = min(self.ghostLayersOfField(n) for n in dataNames) + + output = wlb.vtk.makeOutput(self.blocks, fileName, ghostLayers=ghostLayers) + for n in dataNames: + output.addCellDataWriter(wlb.field.createVTKWriter(self.blocks, n)) + return output + + def vtkWriterFlags(self, fileName, dataName, masksToName, ghostLayers=False): + if ghostLayers is False: + ghostLayers = 0 + if ghostLayers is True: + ghostLayers = self.ghostLayersOfField(dataName) + + output = wlb.vtk.makeOutput(self.blocks, fileName, ghostLayers=ghostLayers) + for mask, name in masksToName.items(): + w = wlb.field.createBinarizationVTKWriter(self.blocks, dataName, mask, name) + output.addCellDataWriter(w) + return output + diff --git a/datahandling/serial_datahandling.py b/datahandling/serial_datahandling.py index 3fd69756a..ed149b9b3 100644 --- a/datahandling/serial_datahandling.py +++ b/datahandling/serial_datahandling.py @@ -1,8 +1,8 @@ import numpy as np -from lbmpy.stencils import getStencil +import itertools from pystencils import Field from pystencils.field import layoutStringToTuple, spatialLayoutStringToTuple, createNumpyArrayWithLayout -from pystencils.parallel.blockiteration import Block, SerialBlock +from pystencils.parallel.blockiteration import SerialBlock from pystencils.slicing import normalizeSlice, removeGhostLayers from pystencils.utils import DotDict from pystencils.datahandling.datahandling_interface import DataHandling @@ -55,6 +55,14 @@ class SerialDataHandling(DataHandling): def dim(self): return len(self._domainSize) + @property + def shape(self): + return self._domainSize + + @property + def periodicity(self): + return self._periodicity + @property def fields(self): return self._fields @@ -129,7 +137,7 @@ class SerialDataHandling(DataHandling): def addArrayLike(self, name, nameOfTemplateField, latexName=None, cpu=True, gpu=False): self.addArray(name, latexName=latexName, cpu=cpu, gpu=gpu, **self._fieldInformation[nameOfTemplateField]) - def iterate(self, sliceObj=None, gpu=False, ghostLayers=True): + def iterate(self, sliceObj=None, gpu=False, ghostLayers=True, innerGhostLayers=True): if ghostLayers is True: ghostLayers = self.defaultGhostLayers elif ghostLayers is False: @@ -208,19 +216,22 @@ class SerialDataHandling(DataHandling): return self._synchronizationFunctor(names, stencilName, 'gpu') def _synchronizationFunctor(self, names, stencil, target): - if stencil is None: - stencil = 'D3Q27' if self.dim == 3 else 'D2Q9' - if stencil == 'D3Q15' or stencil == 'D3Q19': - stencil = 'D3Q27' - - assert stencil in ("D2Q9", 'D3Q27'), "Serial scenario support only D2Q9 or D3Q27 for periodicity sync" assert target in ('cpu', 'gpu') if not hasattr(names, '__len__') or type(names) is str: names = [names] filteredStencil = [] - for direction in getStencil(stencil): + neighbors = [-1, 0, 1] + + if stencil.startswith('D2'): + directions = itertools.product(*[neighbors] * 2) + elif stencil.startswith('D3'): + directions = itertools.product(*[neighbors] * 3) + else: + raise ValueError("Invalid stencil") + + for direction in directions: useDirection = True if direction == (0, 0) or direction == (0, 0, 0): useDirection = False @@ -255,3 +266,58 @@ class SerialDataHandling(DataHandling): func(pdfs=self.gpuArrays[name]) return resultFunctor + + @staticmethod + def reduceFloatSequence(sequence, operation, allReduce=False): + return np.array(sequence) + + @staticmethod + def reduceIntSequence(sequence): + return np.array(sequence) + + def vtkWriter(self, fileName, dataNames, ghostLayers=False): + from pystencils.vtk import imageToVTK + + def writer(step): + fullFileName = "%s_%08d" % (fileName, step,) + cellData = {} + for name in dataNames: + field = self._getFieldWithGivenNumberOfGhostLayers(name, ghostLayers) + if self.dim == 2: + field = field[:, :, np.newaxis] + if len(field.shape) == 3: + field = np.ascontiguousarray(field) + elif len(field.shape) == 4: + field = [np.ascontiguousarray(field[..., i]) for i in range(field.shape[-1])] + if len(field) == 2: + field.append(np.zeros_like(field[0])) + field = tuple(field) + else: + assert False + cellData[name] = field + imageToVTK(fullFileName, cellData=cellData) + return writer + + def vtkWriterFlags(self, fileName, dataName, masksToName, ghostLayers=False): + from pystencils.vtk import imageToVTK + + def writer(step): + fullFileName = "%s_%08d" % (fileName, step,) + field = self._getFieldWithGivenNumberOfGhostLayers(dataName, ghostLayers) + if self.dim == 2: + field = field[:, :, np.newaxis] + cellData = {name: np.ascontiguousarray(np.bitwise_and(field, mask) > 0, dtype=np.uint8) + for mask, name in masksToName.items()} + imageToVTK(fullFileName, cellData=cellData) + + return writer + + def _getFieldWithGivenNumberOfGhostLayers(self, name, ghostLayers): + actualGhostLayers = self.ghostLayersOfField(name) + if ghostLayers is True: + ghostLayers = actualGhostLayers + + glToRemove = actualGhostLayers - ghostLayers + indDims = 1 if self._fieldInformation[name]['fSize'] > 1 else 0 + return removeGhostLayers(self.cpuArrays[name], indDims, glToRemove) + diff --git a/parallel/blockiteration.py b/parallel/blockiteration.py index 6e12791a8..e6280d216 100644 --- a/parallel/blockiteration.py +++ b/parallel/blockiteration.py @@ -24,7 +24,7 @@ def blockIteration(blocks, ghostLayers, dim=3, accessPrefix=''): localSlice = [slice(0, w, None) for w in cellInterval.size] if dim == 2: localSlice[2] = ghostLayers - yield ParallelBlock(block, cellInterval.min, tuple(localSlice), ghostLayers, accessPrefix) + yield ParallelBlock(block, cellInterval.min[:dim], tuple(localSlice), ghostLayers, accessPrefix) def slicedBlockIteration(blocks, sliceObj=None, innerGhostLayers=1, outerGhostLayers=1, dim=3, accessPrefix=''): @@ -69,7 +69,7 @@ def slicedBlockIteration(blocks, sliceObj=None, innerGhostLayers=1, outerGhostLa localSlice = localTargetBB.toSlice(False) if dim == 2: localSlice = (localSlice[0], localSlice[1], innerGhostLayers) - yield ParallelBlock(block, intersection.min, localSlice, innerGhostLayers, accessPrefix) + yield ParallelBlock(block, intersection.min[:dim], localSlice, innerGhostLayers, accessPrefix) class Block: @@ -100,7 +100,7 @@ class Block: @property def shape(self): """Shape of the fields (potentially including ghost layers)""" - return tuple(s.stop - s.start for s in self._localSlice) + return tuple(s.stop - s.start for s in self._localSlice[:len(self._offset)]) @property def globalSlice(self): diff --git a/vtk.py b/vtk.py index e47f22eda..2b5408180 100644 --- a/vtk.py +++ b/vtk.py @@ -5,6 +5,7 @@ from pyevtk.hl import _addDataToFile, _appendDataToFile def imageToVTK(path, origin=(0.0, 0.0, 0.0), spacing=(1.0, 1.0, 1.0), cellData=None, pointData=None): """Patched version of same pyevtk function that also support vector data""" assert cellData != None or pointData != None + # Extract dimensions start = (0, 0, 0) end = None @@ -13,10 +14,11 @@ def imageToVTK(path, origin=(0.0, 0.0, 0.0), spacing=(1.0, 1.0, 1.0), cellData=N data = cellData[keys[0]] if hasattr(data, 'shape'): end = data.shape - elif data[0].ndim == 3 and data[1].ndim == 3 and data[0].ndim == 3: - keys = list(cellData.keys()) - data = cellData[keys[0]] - end = data[0].shape + elif isinstance(data, tuple): + shapes = set(d.shape for d in data) + if len(shapes) > 1: + raise ValueError("All components have to have the same shape") + end = shapes.pop() elif pointData: keys = list(pointData.keys()) data = pointData[keys[0]] -- GitLab