diff --git a/datahandling.py b/datahandling.py index b64300ac87cd5bde97f8916711c3b88f52cba67e..1c7c6e20f88edb54e911e75a8f42531b0e8d3c91 100644 --- a/datahandling.py +++ b/datahandling.py @@ -1,5 +1,10 @@ import numpy as np from abc import ABC, abstractmethod, abstractproperty + +from collections import defaultdict +from contextlib import contextmanager + +from lbmpy.boundaries.periodicityhandling import PeriodicityHandling from pystencils import Field, makeSlice from pystencils.parallel.blockiteration import BlockIterationInfo from pystencils.slicing import normalizeSlice, removeGhostLayers @@ -11,6 +16,61 @@ except ImportError: gpuarray = None +class WalberlaFlagInterface: + def __init__(self, flagField): + self.flagField = flagField + + def registerFlag(self, flagName): + return self.flagField.registerFlag(flagName) + + def flag(self, flagName): + return self.flagField.flag(flagName) + + def flagName(self, flag): + return self.flagField.flagName(flag) + + @property + def flags(self): + return self.flagField.flags + + +class PythonFlagInterface: + def __init__(self): + self.nameToFlag = {} + self.flagToName = {} + self.nextFreeBit = 0 + + def registerFlag(self, flagName): + assert flagName not in self.nameToFlag + flag = 1 << self.nextFreeBit + self.nextFreeBit += 1 + self.flagToName[flag] = flagName + self.nameToFlag[flagName] = flag + return flag + + def flag(self, flagName): + return self.nameToFlag[flagName] + + def flagName(self, flag): + return self.flagToName[flag] + + @property + def flags(self): + return tuple(self.nameToFlag.keys()) + + +class FlagArray(np.ndarray): + def __new__(cls, inputArray, flagInterface): + obj = np.asarray(inputArray).view(cls) + obj.flagInterface = flagInterface + assert inputArray.dtype.kind in ('u', 'i'), "FlagArrays can only be created from integer arrays" + return obj + + def __array_finalize__(self, obj): + if obj is None: return + self.flagInterface = getattr(obj, 'flagInterface', None) + + class DataHandling(ABC): """ Manages the storage of arrays and maps them to a symbolic field. @@ -21,13 +81,16 @@ class DataHandling(ABC): 'gather' function that has collects (parts of the) distributed data on a single process. """ + def __init__(self): + self._preAccessFunctions = defaultdict(list) + self._postAccessFunctions = defaultdict(list) + # ---------------------------- Adding and accessing data ----------------------------------------------------------- @property @abstractmethod def dim(self): """Dimension of the domain, either 2 or 3""" - pass @abstractmethod def add(self, name, fSize=1, dtype=np.float64, latexName=None, ghostLayers=None, layout=None, cpu=True, gpu=False): @@ -48,7 +111,6 @@ class DataHandling(ABC): :param cpu: allocate field on the CPU :param gpu: allocate field on the GPU """ - pass @abstractmethod def addLike(self, name, nameOfTemplateField, latexName=None, cpu=True, gpu=False): @@ -60,13 +122,18 @@ class DataHandling(ABC): :param cpu: see 'add' method :param gpu: see 'add' method """ - pass + + def addFlagArray(self, name, dtype=np.int32, latexName=None, ghostLayers=None): + """ + Adds a flag array (of integer type) where each bit is interpreted as a boolean + Flag arrays additionally store a mapping of name to bit nr, which is accessible as arr.flagInterface. + For parameter documentation see 'add()' function. + """ @property @abstractmethod def fields(self): """Dictionary mapping data name to symbolic pystencils field - use this to create pystencils kernels""" - pass @abstractmethod def access(self, name, sliceObj=None, innerGhostLayers=None, outerGhostLayers=0): @@ -79,7 +146,6 @@ class DataHandling(ABC): :param outerGhostLayers: how many ghost layers at the domain border to include Yields a numpy array with local part of data and a BlockIterationInfo object containing geometric information """ - pass @abstractmethod def gather(self, name, sliceObj=None, allGather=False): @@ -92,8 +158,20 @@ class DataHandling(ABC): :param allGather: if False only the root process receives the result, if True all processes :return: generator expression yielding the gathered field, the gathered field does not include any ghost layers """ - pass + def registerPreAccessFunction(self, name, function): + self._preAccessFunctions[name].append(function) + + def registerPostAccessFunction(self, name, function): + self._postAccessFunctions[name].append(function) + + @contextmanager + def accessWrapper(self, name): + for func in self._preAccessFunctions[name]: + func() + yield + for func in self._postAccessFunctions[name]: + func() # ------------------------------- CPU/GPU transfer ----------------------------------------------------------------- @abstractmethod @@ -136,12 +214,19 @@ class SerialDataHandling(DataHandling): :param defaultGhostLayers: nr of ghost layers used if not specified in add() method :param defaultLayout: layout used if no layout is given to add() method """ + super(SerialDataHandling, self).__init__() self._domainSize = tuple(domainSize) self.defaultGhostLayers = defaultGhostLayers self.defaultLayout = defaultLayout self._fields = DotDict() self.cpuArrays = DotDict() self.gpuArrays = DotDict() + #if periodicity is None or periodicity is False: + # periodicity = [False] * self.dim + #if periodicity is True: + # periodicity = [True] * self.dim + # + #self._periodicity = periodicity self._fieldInformation = {} @property @@ -193,27 +278,36 @@ class SerialDataHandling(DataHandling): self.fields[name] = Field.createFixedSize(latexName, shape=kwargs['shape'], indexDimensions=indexDimensions, dtype=kwargs['dtype'], layout=kwargs['order']) + def addFlagArray(self, name, dtype=np.int32, latexName=None, ghostLayers=None): + self.add(name, 1, dtype, latexName, ghostLayers, layout='AoS', cpu=True, gpu=False) + self.cpuArrays[name] = FlagArray(self.cpuArrays[name], PythonFlagInterface()) + def addLike(self, name, nameOfTemplateField, latexName=None, cpu=True, gpu=False): + if hasattr(self.fields[nameOfTemplateField], 'flagInterface'): + raise ValueError("addLike() does not work for flag arrays") self.add(name,latexName=latexName, cpu=cpu, gpu=gpu, **self._fieldInformation[nameOfTemplateField]) def access(self, name, sliceObj=None, outerGhostLayers=0, **kwargs): if sliceObj is None: sliceObj = [slice(None, None)] * self.dim - arr = self.cpuArrays[name] - glToRemove = self._fieldInformation[name]['ghostLayers'] - outerGhostLayers - assert glToRemove >= 0 - arr = removeGhostLayers(arr, indexDimensions=self.fields[name].indexDimensions, ghostLayers=glToRemove) - sliceObj = normalizeSlice(sliceObj, arr.shape[:self.dim]) - yield arr[sliceObj], BlockIterationInfo(None, tuple(s.start for s in sliceObj), sliceObj) - def gather(self, name, sliceObj=None, **kwargs): - gls = self._fieldInformation[name]['ghostLayers'] - arr = self.cpuArrays[name] - arr = removeGhostLayers(arr, indexDimensions=self.fields[name].indexDimensions, ghostLayers=gls) + with self.accessWrapper(name): + arr = self.cpuArrays[name] + glToRemove = self._fieldInformation[name]['ghostLayers'] - outerGhostLayers + assert glToRemove >= 0 + arr = removeGhostLayers(arr, indexDimensions=self.fields[name].indexDimensions, ghostLayers=glToRemove) + sliceObj = normalizeSlice(sliceObj, arr.shape[:self.dim]) + yield arr[sliceObj], BlockIterationInfo(None, tuple(s.start for s in sliceObj), sliceObj) - if sliceObj is not None: - arr = arr[sliceObj] - yield arr + def gather(self, name, sliceObj=None, **kwargs): + with self.accessWrapper(name): + gls = self._fieldInformation[name]['ghostLayers'] + arr = self.cpuArrays[name] + arr = removeGhostLayers(arr, indexDimensions=self.fields[name].indexDimensions, ghostLayers=gls) + + if sliceObj is not None: + arr = arr[sliceObj] + yield arr def swap(self, name1, name2, gpu=False): if not gpu: diff --git a/field.py b/field.py index a3ffc22bf9709a8560e9514af2bc129d4de46c66..a8e0e7274726f100fb74a7d63aa8b19989a106c8 100644 --- a/field.py +++ b/field.py @@ -218,6 +218,10 @@ class Field(object): offsetList[coordId] = offset return Field.Access(self, tuple(offsetList)) + def neighbors(self, stencil): + return [ self.__getitem__(dir) for s in stencil] + + @property def center(self): center = tuple([0] * self.spatialDimensions) return Field.Access(self, center) diff --git a/finitedifferences.py b/finitedifferences.py index aa3e9d344bf189092596f4b369ab20864b06be53..a60bb8b9cb2b81a61023613f9ac2d51c191888e1 100644 --- a/finitedifferences.py +++ b/finitedifferences.py @@ -174,7 +174,7 @@ class Advection(sp.Function): def advection(scalar, vector, idx=None): assert isinstance(scalar, Field), "Advected scalar has to be a pystencils.Field" - args = [scalar.center(), vector if not isinstance(vector, Field) else vector.center()] + args = [scalar.center, vector if not isinstance(vector, Field) else vector.center] if idx is not None: args.append(idx) return Advection(*args) @@ -210,7 +210,7 @@ class Diffusion(sp.Function): def diffusion(scalar, diffusionCoeff, idx=None): assert isinstance(scalar, Field), "Advected scalar has to be a pystencils.Field" - args = [scalar.center(), diffusionCoeff if not isinstance(diffusionCoeff, Field) else diffusionCoeff.center()] + args = [scalar.center, diffusionCoeff if not isinstance(diffusionCoeff, Field) else diffusionCoeff.center] if idx is not None: args.append(idx) return Diffusion(*args) @@ -231,7 +231,7 @@ class Transient(sp.Function): def transient(scalar, idx=None): - args = [scalar.center()] + args = [scalar.center] if idx is not None: args.append(idx) return Transient(*args) @@ -249,12 +249,12 @@ class Discretization2ndOrder: if isinstance(expr.diffusionCoeff, Field): firstDiffs = [offset * (scalar.neighbor(c, offset) * expr.diffusionCoeff.neighbor(c, offset) - - scalar.center() * expr.diffusionCoeff.center()) + scalar.center * expr.diffusionCoeff.center()) for offset in [-1, 1]] else: firstDiffs = [offset * (scalar.neighbor(c, offset) * expr.diffusionCoeff - - scalar.center() * expr.diffusionCoeff) + scalar.center * expr.diffusionCoeff) for offset in [-1, 1]] result += firstDiffs[1] - firstDiffs[0] return result / (self.dx**2) diff --git a/parallel/datahandling.py b/parallel/datahandling.py index 7aa029f16404e9494f6a3d7a5f5eccf9c9968e2a..55cb5fc8a2b8c7e4ed01f8853b09559cf0df8a18 100644 --- a/parallel/datahandling.py +++ b/parallel/datahandling.py @@ -1,6 +1,6 @@ import numpy as np from pystencils import Field, makeSlice -from pystencils.datahandling import DataHandling +from pystencils.datahandling import DataHandling, FlagArray, WalberlaFlagInterface from pystencils.parallel.blockiteration import slicedBlockIteration from pystencils.utils import DotDict import waLBerla as wlb @@ -20,6 +20,7 @@ class ParallelDataHandling(DataHandling): waLBerla always uses three dimensions, so if dim=2 the extend of the z coordinate of blocks has to be 1 """ + super(ParallelDataHandling, self).__init__() assert dim in (2, 3) self.blocks = blocks self.defaultGhostLayers = defaultGhostLayers @@ -41,6 +42,68 @@ class ParallelDataHandling(DataHandling): return self._fields def add(self, name, fSize=1, dtype=np.float64, latexName=None, ghostLayers=None, layout=None, cpu=True, gpu=False): + return self._add(name, fSize, dtype, latexName, ghostLayers, layout, cpu, gpu, flagField=False) + + def addLike(self, name, nameOfTemplateField, latexName=None, cpu=True, gpu=False): + assert not self._fieldInformation[nameOfTemplateField]['flagField'] + self._add(name,latexName=latexName, cpu=cpu, gpu=gpu, **self._fieldInformation[nameOfTemplateField]) + + def addFlagArray(self, name, dtype=np.int32, latexName=None, ghostLayers=None): + return self._add(name, dtype=dtype, latexName=latexName, ghostLayers=ghostLayers, flagField=True) + + def swap(self, name1, name2, gpu=False): + if gpu: + name1 = self.GPU_DATA_PREFIX + name1 + name2 = self.GPU_DATA_PREFIX + name2 + for block in self.blocks: + block[name1].swapDataPointers(block[name2]) + + def access(self, name, sliceObj=None, innerGhostLayers=None, outerGhostLayers=0): + fieldInfo = self._fieldInformation[name] + with self.accessWrapper(name): + if innerGhostLayers is None: + innerGhostLayers = fieldInfo['ghostLayers'] + + if outerGhostLayers is None: + outerGhostLayers = fieldInfo['ghostLayers'] + + for iterInfo in slicedBlockIteration(self.blocks, sliceObj, innerGhostLayers, outerGhostLayers): + arr = wlb.field.toArray(iterInfo.block[name], withGhostLayers=innerGhostLayers)[iterInfo.localSlice] + if fieldInfo['flagField']: + arr = FlagArray(arr, WalberlaFlagInterface(iterInfo.block[name])) + if self.fields[name].indexDimensions == 0: + arr = arr[..., 0] + if self.dim == 2: + arr = arr[:, :, 0] + yield arr, iterInfo + + def gather(self, name, sliceObj=None, allGather=False): + with self.accessWrapper(name): + if sliceObj is None: + sliceObj = makeSlice[:, :, :] + for array in wlb.field.gatherGenerator(self.blocks, name, sliceObj, allGather): + if self.fields[name].indexDimensions == 0: + array = array[..., 0] + if self.dim == 2: + array = array[:, :, 0] + yield array + + def toCpu(self, name): + wlb.cuda.copyFieldToCpu(self.blocks, self.GPU_DATA_PREFIX + name, name) + + def toGpu(self, name): + wlb.cuda.copyFieldToGpu(self.blocks, self.GPU_DATA_PREFIX + name, name) + + def allToCpu(self): + for cpuName, gpuName in self._cpuGpuPairs: + wlb.cuda.copyFieldToCpu(self.blocks, gpuName, cpuName) + + def allToGpu(self): + for cpuName, gpuName in self._cpuGpuPairs: + wlb.cuda.copyFieldToGpu(self.blocks, gpuName, cpuName) + + def _add(self, name, fSize=1, dtype=np.float64, latexName=None, ghostLayers=None, layout=None, + cpu=True, gpu=False, flagField=False): if ghostLayers is None: ghostLayers = self.defaultGhostLayers if layout is None: @@ -57,19 +120,27 @@ class ParallelDataHandling(DataHandling): self._fieldInformation[name] = {'ghostLayers': ghostLayers, 'fSize': fSize, 'layout': layout, - 'dtype': dtype} + 'dtype': dtype, + 'flagField': flagField} layoutMap = {'fzyx': wlb.field.Layout.fzyx, 'zyxf': wlb.field.Layout.zyxf, 'SoA': wlb.field.Layout.fzyx, 'AoS': wlb.field.Layout.zyxf} - if cpu: - wlb.field.addToStorage(self.blocks, name, dtype, fSize=fSize, layout=layoutMap[layout], - ghostLayers=ghostLayers) - if gpu: - wlb.cuda.addGpuFieldToStorage(self.blocks, self.GPU_DATA_PREFIX+name, dtype, fSize=fSize, - usePitchedMem=False, ghostLayers=ghostLayers, layout=layoutMap[layout]) - if cpu and gpu: - self._cpuGpuPairs.append((name, self.GPU_DATA_PREFIX + name)) + if flagField: + assert not gpu + assert np.dtype(dtype).kind in ('u', 'i'), "FlagArrays can only be created from integer arrays" + nrOfBits = np.dtype(dtype).itemsize * 8 + wlb.field.addFlagFieldToStorage(self.blocks, name, nrOfBits, ghostLayers) + else: + if cpu: + wlb.field.addToStorage(self.blocks, name, dtype, fSize=fSize, layout=layoutMap[layout], + ghostLayers=ghostLayers) + if gpu: + wlb.cuda.addGpuFieldToStorage(self.blocks, self.GPU_DATA_PREFIX+name, dtype, fSize=fSize, + usePitchedMem=False, ghostLayers=ghostLayers, layout=layoutMap[layout]) + + if cpu and gpu: + self._cpuGpuPairs.append((name, self.GPU_DATA_PREFIX + name)) blockBB = self.blocks.getBlockCellBB(self.blocks[0]) shape = tuple(s + 2 * ghostLayers for s in blockBB.size) @@ -79,52 +150,3 @@ class ParallelDataHandling(DataHandling): assert all(f.name != latexName for f in self.fields.values()), "Symbolic field with this name already exists" self.fields[name] = Field.createFixedSize(latexName, shape, indexDimensions, dtype, layout) - - def addLike(self, name, nameOfTemplateField, latexName=None, cpu=True, gpu=False): - self.add(name,latexName=latexName, cpu=cpu, gpu=gpu, **self._fieldInformation[nameOfTemplateField]) - - def swap(self, name1, name2, gpu=False): - if gpu: - name1 = self.GPU_DATA_PREFIX + name1 - name2 = self.GPU_DATA_PREFIX + name2 - for block in self.blocks: - block[name1].swapDataPointers(block[name2]) - - def access(self, name, sliceObj=None, innerGhostLayers=None, outerGhostLayers=0): - if innerGhostLayers is None: - innerGhostLayers = self._fieldInformation[name]['ghostLayers'] - - if outerGhostLayers is None: - outerGhostLayers = self._fieldInformation[name]['ghostLayers'] - - for iterInfo in slicedBlockIteration(self.blocks, sliceObj, innerGhostLayers, outerGhostLayers): - arr = wlb.field.toArray(iterInfo.block[name], withGhostLayers=innerGhostLayers)[iterInfo.localSlice] - if self.fields[name].indexDimensions == 0: - arr = arr[..., 0] - if self.dim == 2: - arr = arr[:, :, 0] - yield arr, iterInfo - - def gather(self, name, sliceObj=None, allGather=False): - if sliceObj is None: - sliceObj = makeSlice[:, :, :] - for array in wlb.field.gatherGenerator(self.blocks, name, sliceObj, allGather): - if self.fields[name].indexDimensions == 0: - array = array[..., 0] - if self.dim == 2: - array = array[:, :, 0] - yield array - - def toCpu(self, name): - wlb.cuda.copyFieldToCpu(self.blocks, self.GPU_DATA_PREFIX + name, name) - - def toGpu(self, name): - wlb.cuda.copyFieldToGpu(self.blocks, self.GPU_DATA_PREFIX + name, name) - - def allToCpu(self): - for cpuName, gpuName in self._cpuGpuPairs: - wlb.cuda.copyFieldToCpu(self.blocks, gpuName, cpuName) - - def allToGpu(self): - for cpuName, gpuName in self._cpuGpuPairs: - wlb.cuda.copyFieldToGpu(self.blocks, gpuName, cpuName)