From b85607ba61527a98efb7d4c417c387a138ccd7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franck=20P=C3=A9rignon?= <franck.perignon@imag.fr> Date: Fri, 26 Sep 2014 18:25:55 +0200 Subject: [PATCH] Review mesh mechanism, add/fix subsets. All tests ok except those with obstacles somewhere. Work in progress --- HySoP/hysop/domain/mesh.py | 309 +++++++++++ HySoP/hysop/domain/obstacle/controlBox.py | 6 +- HySoP/hysop/domain/subsets/__init__.py | 3 + HySoP/hysop/domain/subsets/boxes.py | 156 ++++++ HySoP/hysop/domain/subsets/subset.py | 47 ++ HySoP/hysop/domain/tests/test_mesh.py | 141 +++++ HySoP/hysop/domain/tests/test_submesh.py | 53 ++ HySoP/hysop/domain/tests/test_subset.py | 175 ++++++ HySoP/hysop/mpi/bridge_inter.py | 9 +- HySoP/hysop/mpi/bridge_overlap.py | 9 +- HySoP/hysop/mpi/tests/test_mesh.py | 59 -- HySoP/hysop/operator/advold.py | 521 ------------------ HySoP/hysop/operator/computational.py | 2 +- .../hysop/operator/discrete/drag_and_lift.py | 6 +- HySoP/hysop/operator/hdf_io.py | 28 +- HySoP/hysop/tools/misc.py | 44 ++ HySoP/setup.py.in | 1 + 17 files changed, 953 insertions(+), 616 deletions(-) create mode 100644 HySoP/hysop/domain/mesh.py create mode 100644 HySoP/hysop/domain/subsets/__init__.py create mode 100644 HySoP/hysop/domain/subsets/boxes.py create mode 100644 HySoP/hysop/domain/subsets/subset.py create mode 100644 HySoP/hysop/domain/tests/test_mesh.py create mode 100644 HySoP/hysop/domain/tests/test_submesh.py create mode 100644 HySoP/hysop/domain/tests/test_subset.py delete mode 100644 HySoP/hysop/mpi/tests/test_mesh.py delete mode 100644 HySoP/hysop/operator/advold.py create mode 100644 HySoP/hysop/tools/misc.py diff --git a/HySoP/hysop/domain/mesh.py b/HySoP/hysop/domain/mesh.py new file mode 100644 index 000000000..3ca7710fa --- /dev/null +++ b/HySoP/hysop/domain/mesh.py @@ -0,0 +1,309 @@ +""" +@file mesh.py local cartesian grid. +""" +from parmepy.constants import debug +import parmepy.tools.numpywrappers as npw +from parmepy.tools.parameters import Discretization +import numpy as np +from parmepy.tools.misc import utils + + +class Mesh(object): + """ + A cartesian grid, defined on each mpi process, in relation to a + global resolution. + + For example, consider a 1D domain and a global resolution with N+1 points + and a ghost layer with 2 points: + \code + N = 9 + dom = Box(dimension=1) + discr = Discretization([N + 1], [2] + \endcode + + \code + m = Mesh(dom, discr, [start], [resol]) + \endcode + will describe a grid on the current process, starting at point + of index start in the global mesh (from discr), + and of local resolution equal to resol. + + Usually, thanks to a topology, a Mesh will be defined + on each mpi processes. + For example, with 2 procs: + + global grid (node number): 0 1 2 3 4 5 6 7 8 + + proc 0 (global indices): X X 0 1 2 3 X X + (local indices) : 0 1 2 3 4 5 6 7 + proc 1 : X X 4 5 6 7 X X + 0 1 2 3 4 5 6 7 + with 'X' for ghost points. + + - Node '8' of the global grid is not represented on local mesh, + because of periodicity. N8 == N0 + + - on proc 1, we have: + - local resolution = 8 + - global_start = 4 + - 'computation nodes' = 2, 3, 4, 5 + - 'ghost nodes' = 0, 1, 6, 7 + + Remarks: + - all 'global' values refer to the discretization parameter. + For example 'global start' = 2 means a point of index 2 in the + global resolution. + - only periodic grid are considered + """ + + @debug + def __new__(cls, *args, **kw): + return object.__new__(cls, *args, **kw) + + @debug + def __init__(self, parent, discretization, resolution, global_start): + """ + @param parent : the geometry on which this mesh is defined + (it must be a rectangle or a rectangular cuboid) + @param discretization : parmepy.tools.parameters.Discretization which + describes the global discretization of the domain + (resolution and ghost layer) + @param global_start + """ + # geometry of reference for the mesh + self.domain = parent + assert isinstance(discretization, Discretization) + ## The global discretization of reference for this mesh. + ## It defines the resolution of the grid on the whole domain and + ## the number of ghost points on each subdomain. + self.discretization = discretization + + ## Local resolution of this mesh, INCLUDING ghost points + self.resolution = npw.asdimarray(resolution) + self.resolution.flags.writeable = False + + # dimension of the mesh + self._dim = self.resolution.size + + ## Position of this mesh in the global grid + self.position = [] + ## Mesh step size + self.space_step = self.domain.length / (self.discretization.resolution + - 1) + # position (index) of the first computational (i.e. no ghosts) + # point of this mesh in the global grid. + start = npw.asdimarray(global_start) + + # last computational point, global index + ghosts = self.discretization.ghosts + end = start + self.resolution - 2 * ghosts + self.position = [slice(start[d], end[d]) for d in xrange(self._dim)] + ## Coordinates of the "lowest" point of this mesh (including ghost) + self.origin = self.domain.origin.copy() + self.origin += self.space_step * (start - ghosts) + + ## Coordinates of the last point of the mesh + self.end = self.origin + self.space_step * (self.resolution - 1) + + # shift between local and global index. + # Usefull for start() and end() methods. + self._shift = ghosts - start + + ## coordinates of the points of the grid + self.coords = None + + ## Local position of the computational points + self.iCompute = [] + # position (index) of the first computational (i.e. no ghosts), local. + start = self.discretization.ghosts + # last computational point, local index + end = self.resolution - start + self.iCompute = [slice(start[d], end[d]) + for d in xrange(self._dim)] + self.ind4integ = self.iCompute + + self.coords = np.ix_(*(np.linspace(self.origin[d], + self.end[d], self.resolution[d]) + for d in xrange(self._dim))) + + def local_indices(self, point): + """ + returns indices of the point of coordinates (close to) tab = x, y, z + If (x, y, z) is not a grid point, it returns the closest grid point. + """ + point = npw.asrealarray(point) + if self.is_inside(point): + return npw.asdimarray(np.rint((point - self.origin) + / self.space_step)) + else: + return False + + def global_indices(self, point): + point = npw.asrealarray(point) + return npw.asdimarray(np.rint((point - self.domain.origin) + / self.space_step)) + + def is_inside(self, point): + """ + @param point: coordinates of a point + @return : true if the point is inside the local (mpi proc) mesh + """ + point = npw.asrealarray(point) + return ((point - self.origin) >= 0.).all() and ((self.end + - point) >= 0.).all() + + def __str__(self): + """ + Sub mesh display + """ + s = 'Local mesh: \n' + s += ' - resolution : ' + str(self.resolution) + '\n' + s += ' - position in global mesh: ' + str(self.position) + '\n' + s += ' - global discretization : ' + str(self.discretization) + '\n' + s += ' - computation points :' + str(self.iCompute) + '\n' + return s + + def convert2local(self, sl): + """ + Return the local mesh indices from + their global indices. + Ilocal = Iglobal - _global_start + ghost + @param sl: the list of slices (one per dir) for global indices + something like [(start_dir1, end_dir1), ...] + @return the same kind of list but in local coordinates + """ + tmp = utils.intersl(sl, self.position) + if tmp is not None: + return [slice(tmp[i].start + self._shift[i], + tmp[i].stop + self._shift[i]) + for i in xrange(self._dim)] + else: + return None + + def convert2global(self, sl): + """ + Return the global mesh indices from + their local indices. + Iglobal = Ilocal + _global_start - ghost + @param loc_index an array of size domain.dim*2 + with loc_index = [start_dir1, end_dir1, start_dir2, end_dir2, ...] + return an array of indices, same 'setup' as loc_index. + """ + return [slice(sl[i].start - self._shift[i], + sl[i].stop - self._shift[i]) + for i in xrange(self._dim)] + + def __eq__(self, other): + """ + = operator for mesh. + We consider that 2 submeshes are equal if all the conditions + below are fulfilled: + - their global resolution is the same + - the ghost layer is the same + - their local resolution is the same + """ + if self.__class__ != other.__class__: + return False + return self.discretization == other.discretization and\ + npw.equal(self.resolution, other.resolution).all() + + def start(self): + """ + @return indices of the lowest point of the local mesh + in the global grid + """ + return npw.asdimarray([p for p in + (self.position[d].start + for d in xrange(self._dim))]) + + def stop(self): + """ + @return indices + 1 of the 'highest' point of the local mesh + in the global grid. + Warning: made to be used in slices : + slice(mesh.start()[d], mesh.stop()[d]) represents the + points of the current process mesh, indices given + in the global grid. + """ + return npw.asdimarray([p for p in + (self.position[d].stop + for d in xrange(self._dim))]) + + +class SubMesh(object): + """ + A subset of a predefined (distributed) mesh. + """ + + def __init__(self, mesh, gstart, gend): + """ + @param mesh : the parent mesh + @param gstart : indices in the global grid of mesh + of the lowest point of this submesh + @param gend : indices of the 'highest' point of this submesh, + in the global grid. + Warning : gend/gstart are global values, that do not depend on the + mpi distribution of data. + + \todo : a proper scheme to clarify all the notations for meshes + (global/local start end and so on). + """ + ## parent mesh + self.mesh = mesh + ## Index of the lowest point of the global submesh in the global grid + ## of its parent + self.gstart = npw.asdimarray(gstart) + ## Index of the 'highest' point of the global submesh + ## in the global grid of its parent + self.gend = npw.asdimarray(gend) + ## domain dim + self._dim = len(self.gstart) + ## + self.gposition = [slice(gstart[d], gend[d] + 1) + for d in xrange(self._dim)] + ## + self.discretization = Discretization(self.gend - self.gstart + 1, + mesh.discretization.ghosts) + dimension = len(self.gstart) + # Find which part of submesh is on the current process and + # find its computational points. Warning: + # the indices of computational points must be + # relative to the parent mesh local grid! + sl = utils.intersl(self.gposition, self.mesh.position) + # Bool to check if this process holds the end (in any direction) + # of the domain. Useful for proper integration on this subset. + is_last = [False, ] * dimension + self.on_proc = sl is not None + if self.on_proc: + self.position = [s for s in sl] + self.iCompute = self.mesh.convert2local(self.position) + self.resolution = [self.position[d].stop - self.position[d].start + for d in xrange(dimension)] + is_last = np.asarray([self.gend[d] <= self.position[d].stop - 1 + for d in xrange(dimension)]) + stop = npw.asdimarray([self.iCompute[d].stop + for d in xrange(dimension)]) + stop[is_last] -= 1 + self.ind4integ = [slice(self.iCompute[d].start, + max(stop[d], self.iCompute[d].start + 1)) + for d in xrange(dimension)] + else: + self.position = None + self.iCompute = [slice(0, 0), ] * dimension + self.ind4integ = self.iCompute + self.resolution = [0, ] * dimension + + self.coords = [None, ] * dimension + self.coords[0] = self.mesh.coords[0][self.iCompute[0]] + if dimension > 1: + self.coords[1] = self.mesh.coords[1][:, self.iCompute[1], :] + if dimension > 2: + self.coords[2] = self.mesh.coords[2][:, :, self.iCompute[2]] + self.coords = tuple(self.coords) + self.coords4int = [None, ] * dimension + self.coords4int[0] = self.mesh.coords[0][self.ind4integ[0]] + if dimension > 1: + self.coords4int[1] = self.mesh.coords[1][:, self.ind4integ[1], :] + if dimension > 2: + self.coords4int[2] = self.mesh.coords[2][:, :, self.ind4integ[2]] + self.coords4int = tuple(self.coords4int) diff --git a/HySoP/hysop/domain/obstacle/controlBox.py b/HySoP/hysop/domain/obstacle/controlBox.py index e8588fc7f..9c982da56 100644 --- a/HySoP/hysop/domain/obstacle/controlBox.py +++ b/HySoP/hysop/domain/obstacle/controlBox.py @@ -55,7 +55,7 @@ class ControlBox(Obstacle): ## of the obstacle. Only for subspaces. self.gstart = None - def createVolumeAndSides(self, spaceStep): + def _createVolumeAndSides(self, spaceStep): """ @param[in] array of size self._dim, space step size in each direction This value will be used to compute a tolerance and detect @@ -112,7 +112,7 @@ class ControlBox(Obstacle): spaceStep = topo.mesh.space_step # -- Control volume : union of two halfspaces -- if not self._boxCreated: - self.createVolumeAndSides(spaceStep) + self._createVolumeAndSides(spaceStep) # Discretize all volume and surfaces of # the box for topo @@ -157,7 +157,7 @@ class ControlBox(Obstacle): # slice(start,end) --> end not included, so +1 end += 1 resol = end - lstart + 2 * topo.mesh.discretization.ghosts - gstart = lstart + topo.mesh.global_start + gstart = lstart + topo.mesh.start() gstart -= topo.mesh.discretization.ghosts self.mesh[topo] = SubMesh(self.domain, topo.mesh.discretization, diff --git a/HySoP/hysop/domain/subsets/__init__.py b/HySoP/hysop/domain/subsets/__init__.py new file mode 100644 index 000000000..4dce0d91f --- /dev/null +++ b/HySoP/hysop/domain/subsets/__init__.py @@ -0,0 +1,3 @@ +## @package parmepy.domain.subsets +# Description (geometry) and discretisation (grid and data distribution) of some subsets +# of a domain diff --git a/HySoP/hysop/domain/subsets/boxes.py b/HySoP/hysop/domain/subsets/boxes.py new file mode 100644 index 000000000..06195693f --- /dev/null +++ b/HySoP/hysop/domain/subsets/boxes.py @@ -0,0 +1,156 @@ +""" +@file boxes.py +2D rectangle or 3D Rectangular Cuboid subsets +""" +from parmepy.domain.subsets.subset import Subset +from parmepy.domain.mesh import SubMesh +import parmepy.tools.numpywrappers as npw +from parmepy import Field +import numpy as np +from parmepy.mpi.topology import Cartesian + + +class SubBox(Subset): + """ + A rectangle (in 2 or 3D space), defined by the coordinates of + its lowest point, its lenghts and its normal. + """ + def __init__(self, origin, length, **kwds): + """ + """ + super(SubBox, self).__init__(**kwds) + ## Dictionnary of parmepy.domain.mesh.Submesh, keys=topo, values=mesh + self.mesh = {} + ## coordinates of the lowest point of this subset + self.origin = npw.asrealarray(origin) + ## length of this subset + self.length = npw.asrealarray(length) + ## coordinates of the 'highest' point of this subset + self.end = self.origin + self.length + + self._s_dir = np.where(self.length != 0) + msg = 'Subset error, the origin is outside of the domain.' + assert ((self.origin - self._parent.origin) >= 0).all(), msg + msg = 'Subset error, the subset is too large for the domain.' + assert ((self._parent.end - self.end) >= 0).all(), msg + ## dict of coordinates of the lengthes of the subset (values) + ## for a given topo (keys). If the origin/length does not + ## fit exactly with the mesh, there may be a small shift of + ## these values. + self.real_length = {} + self.real_orig = {} + + def discretize(self, topo): + """ + Compute a local submesh on the subset, for a given topology + @param topo: a parmepy topology + """ + assert isinstance(topo, Cartesian) + # Find the indices of starting/ending points in the global_end + # grid of the mesh of topo. + gstart = topo.mesh.global_indices(self.origin) + gend = topo.mesh.global_indices(self.origin + self.length) + # create a submesh + self.mesh[topo] = SubMesh(topo.mesh, gstart, gend) + self.real_length[topo] = (gend - gstart) * topo.mesh.space_step + self.real_orig[topo] = topo.domain.origin + \ + gstart * topo.mesh.space_step + + def apply(self, tab): + pass + + def integrate_field_allc(self, field, topo, root=None): + """ + @param[in] field : a parmepy continuous field + @param[in] topo : a parmepy.mpi.topology.Cartesian topology + @param[in] root : root process used for mpi reduction. If None + reduction is done on all processes from topo. + @return a numpy array, with res[i] = integral of component i + of the input field over the current subset. + """ + res = npw.zeros(field.nbComponents) + gres = npw.zeros(field.nbComponents) + for i in xrange(res.size): + res[i] = self._integrate_field_on_proc(field, topo, component=i) + if root is None: + topo.comm.Allreduce(res, gres) + else: + topo.comm.Reduce(res, gres, root=root) + + return gres + + def integrate_field(self, field, topo, component=0, root=None): + """ + @param[in] field : a parmepy continuous field + @param[in] topo : a parmepy.mpi.topology.Cartesian topology + @param[in] component : number of the component of field + to integrate + @return integral of a component of the input field + over the current subset, for the discretization given by + topo + """ + res = self._integrate_field_on_proc(field, topo, component) + if root is None: + return topo.comm.allreduce(res) + else: + return topo.comm.reduce(res, root=root) + + def _integrate_field_on_proc(self, field, topo, component=0): + """ + @param[in] field : a parmepy continuous field + @param[in] topo : a parmepy.mpi.topology.Cartesian topology + @param[in] component : number of the component of field + integrate the field on the subset on the current process + (i.e. no mpi reduce over all processes) for the discretization + given by topo. + @return integral of a component of the input field + over the current subset. + """ + assert isinstance(field, Field) + assert isinstance(topo, Cartesian) + df = field.discretize(topo) + dvol = npw.prod(topo.mesh.space_step[self._s_dir]) + ic = self.mesh[topo].ind4integ + result = npw.real_sum(df[component][ic]) + result *= dvol + return result + + def integrate_func(self, func, topo, nbc, root=None): + """ + @param[in] func : a python function, depending on the space + coordinates. See below for the required signature. + @param[in] topo : a parmepy.mpi.topology.Cartesian topology + @param[in] nbc : number of components of the return value from func + @return integral of a component of the input field + over the current subset, for the discretization given by + topo + """ + res = npw.zeros(nbc) + gres = npw.zeros(nbc) + for i in xrange(res.size): + res[i] = self._integrate_func_on_proc(func, topo) + if root is None: + topo.comm.Allreduce(res, gres) + else: + topo.comm.Reduce(res, gres, root=root) + return gres + + def _integrate_func_on_proc(self, func, topo): + """ + @param[in] func : a python function, depending on the space + coordinates. See below for the required signature. + @param[in] : a parmepy.mpi.topology.Cartesian topology + integrate a funcion on the subset on the current process + (i.e. no mpi reduce over all processes) + """ + assert hasattr(func, '__call__') + assert isinstance(topo, Cartesian) + coords = self.mesh[topo].coords4int + vfunc = np.vectorize(func) + if self.mesh[topo].on_proc: + result = npw.real_sum(vfunc(*coords)) + else: + result = 0. + dvol = npw.prod(topo.mesh.space_step) + result *= dvol + return result diff --git a/HySoP/hysop/domain/subsets/subset.py b/HySoP/hysop/domain/subsets/subset.py new file mode 100644 index 000000000..d9d749768 --- /dev/null +++ b/HySoP/hysop/domain/subsets/subset.py @@ -0,0 +1,47 @@ +""" +@file subset.py +Abstract interface to subset of a given domain +""" +from abc import ABCMeta, abstractmethod +from parmepy.domain.domain import Domain + + +class Subset(object): + """ + A subset is a geometry defined inside a domain. + Given a topology on the parent domain, the subset must + provide 'slices' or 'tabs' to allow the computation of a discrete + field on the subset, with: + + data[subset.slices] = ... + or + data[subset.tab] = ... + or + subset.apply(data) = ... + + slices : a list of python slices defining the grid points inside the subset + tab : an array of boolean, where tab[i, j, k] = True + if the grid point at (i, j, k) is inside the subset. + + There are two types of subsets: + - 'regular' ones, those on which a regular mesh can be defined + - others, like spheres, cylinders ... + + For regular subsets, one must use 'slices' to compute field inside + the subset whereas 'tab' is required for others. + """ + + __metaclass__ = ABCMeta + + def __init__(self, parent): + """ + @param parent : the domain in which the subset is defined + """ + assert isinstance(parent, Domain) + self._parent = parent + + @abstractmethod + def apply(self, field): + """ + return field on the current subset + """ diff --git a/HySoP/hysop/domain/tests/test_mesh.py b/HySoP/hysop/domain/tests/test_mesh.py new file mode 100644 index 000000000..0999bd537 --- /dev/null +++ b/HySoP/hysop/domain/tests/test_mesh.py @@ -0,0 +1,141 @@ +""" +@file parmepy.domain.tests.test_mesh +Testing regular grids. +""" +from parmepy.domain.box import Box +from parmepy.tools.parameters import Discretization +from parmepy.mpi import main_size, main_rank +import numpy as np + + +Nx = Ny = Nz = 32 + + +def create_mesh(discr): + """ + Periodic mesh + """ + gh = discr.ghosts + dim = gh.size + dom = Box(dimension=dim, origin=[0.1, ] * dim) + cutdir = [False, ] * dim + cutdir[-1] = True + topo = dom.create_topology(discr, cutdir=cutdir) + mesh = topo.mesh + # Test global grid + assert mesh.discretization == discr + assert (mesh.space_step == dom.length / (discr.resolution - 1)).all() + # Position compared with global grid + shift = np.asarray([0, ] * dim) + shift[-1] = Nz / main_size * main_rank + resolution = (discr.resolution - 1) / topo.shape + 2 * gh + end = shift + resolution - 2 * gh + assert mesh.position == [slice(shift[d], end[d]) for d in xrange(dim)] + assert (mesh.start() == shift).all() + assert (mesh.stop() == end).all() + # Test local grid + assert (mesh.resolution == resolution).all() + assert (mesh.origin == dom.origin + (shift - gh) * mesh.space_step).all() + assert mesh.iCompute == [slice(gh[d], resolution[d] - gh[d]) + for d in xrange(dim)] + assert (mesh.global_indices(dom.origin) == [0, ] * dim).all() + assert (mesh.local_indices(mesh.origin) == [0, ] * dim).all() + if main_size == 0: + assert mesh.is_inside([0.3, ] * dim) + pt2 = [0., ] * dim + pt2[-1] = - 0.8 + assert not mesh.is_inside(pt2) + point = 6 * mesh.space_step + dom.origin + point[-1] = (Nz - 1) * mesh.space_step[-1] + dom.origin[-1] + req_point = [6, ] * dim + req_point[-1] = Nz - 1 + assert (mesh.global_indices(point) == req_point).all() + if main_rank == main_size - 1: + assert mesh.is_inside(point) + pos = np.asarray(req_point) + pos[-1] = Nz / main_size - 1 + pos += gh + assert (mesh.local_indices(point) == pos).all() + else: + assert mesh.local_indices(point) is False + + +def test_mesh3D(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1]) + create_mesh(discr) + + +def test_mesh3D_ghost(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [2, 2, 2]) + create_mesh(discr) + + +def test_mesh2D(): + discr = Discretization([Nx + 1, Nz + 1]) + create_mesh(discr) + + +def test_mesh2D_ghost(): + discr = Discretization([Nx + 1, Nz + 1], [2, 2]) + create_mesh(discr) + + +def test_convert_local(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [2, 1, 2]) + gh = discr.ghosts + dim = gh.size + dom = Box(dimension=dim, origin=[0.1, ] * dim) + topo = dom.create_topology(discr, cutdir=[False, False, True]) + mesh = topo.mesh + + # test conversion + start = 2 + end = 10 + source = [slice(start, end), ] * dim + res = mesh.convert2local(source) + if main_size == 1: + assert res == [slice(start + gh[d], end + gh[d]) for d in xrange(dim)] + elif main_size == 8: + newstart = start - topo.rank * Nz / main_size + newend = end - topo.rank * Nz / main_size + slref = slice(max(mesh.iCompute[-1].start, newstart + gh[-1]), + min(max(newend + gh[-1], gh[-1]), + mesh.iCompute[-1].stop)) + sl = [slice(start + gh[d], end + gh[d]) for d in xrange(dim)] + sl[-1] = slref + if res is not None: + assert res == sl + else: + assert slref.stop - slref.start == 0 + + +def test_convert_global(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [2, 1, 2]) + gh = discr.ghosts + dim = gh.size + dom = Box(dimension=dim, origin=[0.1, ] * dim) + topo = dom.create_topology(discr, cutdir=[False, False, True]) + mesh = topo.mesh + + # test conversion + start = 3 + end = 6 + source = [slice(start, end), ] * dim + res = mesh.convert2global(source) + if main_size == 1: + assert res == [slice(start - gh[d], end - gh[d]) for d in xrange(dim)] + elif main_size == 8: + newstart = start + topo.rank * Nz / main_size + newend = end + topo.rank * Nz / main_size + slref = slice(newstart - gh[-1], newend - gh[-1]) + sl = [slice(start - gh[d], end - gh[d]) for d in xrange(dim)] + sl[-1] = slref + assert res == sl + +if __name__ == '__main__': + test_mesh3D() + test_mesh3D_ghost() + test_mesh2D() + test_mesh2D_ghost() + test_convert_local() + test_convert_global() diff --git a/HySoP/hysop/domain/tests/test_submesh.py b/HySoP/hysop/domain/tests/test_submesh.py new file mode 100644 index 000000000..f3135b6eb --- /dev/null +++ b/HySoP/hysop/domain/tests/test_submesh.py @@ -0,0 +1,53 @@ +""" +@file parmepy.domain.tests.test_mesh +Testing regular grids. +""" +from parmepy.domain.box import Box +from parmepy.tools.parameters import Discretization +from parmepy.domain.mesh import SubMesh +from parmepy.tools.misc import utils + +Nx = Ny = Nz = 32 +g = 2 + + +def test_submesh(): + """ + Periodic mesh + """ + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [g, g, g]) + gh = discr.ghosts + dim = gh.size + dom = Box(dimension=dim, origin=[0.1, ] * dim) + topo = dom.create_topology(discr, cutdir=[False, False, True]) + mesh = topo.mesh + # Set a starting point and a length for the submesh + hh = mesh.space_step + xr = 1. * hh + # Find the indices of these points + gstart = mesh.global_indices(xr) + gend = mesh.global_indices(xr + 15 * hh) + # Create the submesh + subm = SubMesh(mesh, gstart, gend) + # check global params of the submesh + assert subm.mesh is mesh + assert (subm.gstart == gstart).all() + assert (subm.gend == gend).all() + # the global position we expect + gpos = [slice(gstart[d], gend[d] + 1) for d in xrange(dim)] + assert subm.gposition == gpos + assert (subm.discretization.resolution == [gend[d] - gstart[d] + 1 + for d in xrange(dim)]).all() + assert (subm.discretization.ghosts == mesh.discretization.ghosts).all() + # check position of the local submesh, relative to the global grid + # It must corresponds to the intersection of the expected position + # of the submesh and the position of the parent mesh. + if subm.on_proc: + assert subm.position == [s for s in utils.intersl(gpos, mesh.position)] + + r2 = mesh.convert2local([s for s + in utils.intersl(gpos, mesh.position)]) + assert subm.iCompute == r2 + +if __name__ == '__main__': + test_submesh() diff --git a/HySoP/hysop/domain/tests/test_subset.py b/HySoP/hysop/domain/tests/test_subset.py new file mode 100644 index 000000000..e0a291679 --- /dev/null +++ b/HySoP/hysop/domain/tests/test_subset.py @@ -0,0 +1,175 @@ +from parmepy.domain.subsets.boxes import SubBox +from parmepy.tools.parameters import Discretization +from parmepy import Field, Box +import numpy as np +import math + + +Nx = Ny = Nz = 128 +g = 2 + + +def v3d(res, x, y, z, t): + res[0][...] = 1. + res[1][...] = 1. + res[2][...] = 1. + return res + + +def f_test(x, y, z): + return 1 + + +def f_test_2(x, y, z): + return math.cos(z) + + +def test_subbox(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [g, g, g]) + dim = 3 + dom = Box(dimension=dim, origin=[0.1, ] * dim) + # Starting point and length of the subdomain + xr = [0.2, 0.4, 0.1] + lr = [0.3, 0.4, 0.8] + cub = SubBox(origin=xr, length=lr, parent=dom) + assert (cub.length == lr).all() + assert (cub.origin == xr).all() + # discretization of the dom and of its subset + topo = dom.create_topology(discr, cutdir=[False, False, True]) + cub.discretize(topo) + assert cub.mesh.values()[0].mesh == topo.mesh + assert cub.mesh.keys()[0] == topo + + +def test_integ(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [g, g, g]) + dim = 3 + dom = Box(dimension=dim, origin=[0.01, ] * dim) + # discretization of the dom and of its subset + topo = dom.create_topology(discr, cutdir=[False, False, True]) + hh = topo.mesh.space_step + # Starting point and length of the subdomain + xr = 3 * hh + lr = 10 * hh + cub = SubBox(origin=xr, length=lr, parent=dom) + cub.discretize(topo) + velo = Field(domain=dom, isVector=True, formula=v3d, name='velo') + velo.discretize(topo) + velo.initialize(topo=topo) + i0 = cub.integrate_field_allc(velo, topo) + vref = np.prod(cub.real_length[topo]) + assert (np.abs(i0 - vref) < 1e-6).all() + + +def test_integ_2(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [g, g, g]) + dim = 3 + dom = Box(dimension=dim, origin=[0.01, ] * dim) + # discretization of the dom and of its subset + topo = dom.create_topology(discr, cutdir=[False, False, True]) + # Starting point and length of the subdomain + xr = [0.1, 0.2, 0.15] + lr = [0.51, 0.53, 0.3] + cub = SubBox(origin=xr, length=lr, parent=dom) + cub.discretize(topo) + velo = Field(domain=dom, isVector=True, formula=v3d, name='velo') + velo.discretize(topo) + velo.initialize(topo=topo) + vref = np.prod(cub.real_length[topo]) + for i in xrange(velo.nbComponents): + i0 = cub.integrate_field(velo, topo, component=i) + assert np.abs(i0 - vref) < 1e-6 + + +def test_integ_3(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [g, g, g]) + dim = 3 + dom = Box(dimension=dim, origin=[0.01, ] * dim) + # discretization of the dom and of its subset + topo = dom.create_topology(discr, cutdir=[False, False, True]) + # Starting point and length of the subdomain + xr = [0.1, 0.2, 0.15] + lr = [0.51, 0.53, 0.3] + cub = SubBox(origin=xr, length=lr, parent=dom) + cub.discretize(topo) + nbc = 1 + vref = np.prod(cub.real_length[topo]) + for i in xrange(1, nbc + 1): + i0 = cub.integrate_func(f_test, topo, nbc=i) + assert np.abs(i0 - vref) < 1e-6 + + +def test_integ_4(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [g, g, g]) + dim = 3 + dom = Box(dimension=dim, origin=[0., ] * dim, + length=[math.pi * 2., ] * dim) + # discretization of the dom and of its subset + topo = dom.create_topology(discr, cutdir=[False, False, True]) + # Starting point and length of the subdomain + xr = [math.pi * 0.5, ] * dim + lr = [math.pi, ] * dim + cub = SubBox(origin=xr, length=lr, parent=dom) + cub.discretize(topo) + nbc = 1 + vref = - 2 * np.prod(cub.real_length[topo][:2]) + for i in xrange(1, nbc + 1): + i0 = cub.integrate_func(f_test_2, topo, nbc=i) + assert np.abs(i0 - vref) / np.abs(vref) < topo.mesh.space_step[2] + + +def test_subbox_2d(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [g, g, g]) + dim = 3 + dom = Box(dimension=dim, origin=[0.1, ] * dim) + # Starting point and length of the subdomain + xr = [0.2, 0.4, 0.1] + lr = [0., 0.2, 0.8] + cub = SubBox(origin=xr, length=lr, parent=dom) + assert (cub.length == lr).all() + assert (cub.origin == xr).all() + # discretization of the dom and of its subset + topo = dom.create_topology(discr, cutdir=[False, False, True]) + cub.discretize(topo) + assert cub.mesh.values()[0].mesh == topo.mesh + assert cub.mesh.keys()[0] == topo + velo = Field(domain=dom, isVector=True, formula=v3d, name='velo') + velo.discretize(topo) + velo.initialize(topo=topo) + i0 = cub.integrate_field_allc(velo, topo) + vref = np.prod(cub.real_length[topo][1:]) + assert (np.abs(i0 - vref) < 1e-6).all() + + +def test_line(): + discr = Discretization([Nx + 1, Ny + 1, Nz + 1], [g, g, g]) + dim = 3 + dom = Box(dimension=dim, origin=[0.1, ] * dim) + # Starting point and length of the subdomain + xr = [0.2, 0.4, 0.1] + lr = [0., 0., 0.8] + cub = SubBox(origin=xr, length=lr, parent=dom) + assert (cub.length == lr).all() + assert (cub.origin == xr).all() + # discretization of the dom and of its subset + topo = dom.create_topology(discr, cutdir=[False, False, True]) + cub.discretize(topo) + assert cub.mesh.values()[0].mesh == topo.mesh + assert cub.mesh.keys()[0] == topo + velo = Field(domain=dom, isVector=True, formula=v3d, name='velo') + velo.discretize(topo) + velo.initialize(topo=topo) + i0 = cub.integrate_field_allc(velo, topo) + vref = cub.real_length[topo][2] + assert (np.abs(i0 - vref) < 1e-6).all() + + + +# This may be useful to run mpi tests +if __name__ == "__main__": + test_subbox() + test_integ() + test_integ_2() + test_integ_3() + test_subbox_2d() + test_line() diff --git a/HySoP/hysop/mpi/bridge_inter.py b/HySoP/hysop/mpi/bridge_inter.py index ae500e18f..55fd153a7 100644 --- a/HySoP/hysop/mpi/bridge_inter.py +++ b/HySoP/hysop/mpi/bridge_inter.py @@ -4,6 +4,7 @@ Tools to compute the intersection between two parmes topologies. """ from parmepy.mpi.topology import Cartesian, topotools +from parmepy.tools.misc import utils from parmepy.mpi import MPI import parmepy.tools.numpywrappers as npw @@ -72,12 +73,12 @@ class BridgeInter(object): self._tranfer_indices = {} current = current_indices[self._topology.rank] for rk in remote_indices: - inter = topotools.intersl(current, remote_indices[rk]) + inter = utils.intersl(current, remote_indices[rk]) if inter is not None: self._tranfer_indices[rk] = inter # Back to local indices - convert = self._topology.mesh.toIndexLocal2 + convert = self._topology.mesh.convert2local self._tranfer_indices = {rk: convert(self._tranfer_indices[rk]) for rk in self._tranfer_indices} @@ -115,8 +116,8 @@ class BridgeInter(object): self.comm.bcast(current_indices, root=MPI.PROC_NULL) # Convert numpy arrays to dict of slices ... - current_indices = topotools.arrayToDict(current_indices) - remote_indices = topotools.arrayToDict(remote_indices) + current_indices = utils.arrayToDict(current_indices) + remote_indices = utils.arrayToDict(remote_indices) return current_indices, remote_indices diff --git a/HySoP/hysop/mpi/bridge_overlap.py b/HySoP/hysop/mpi/bridge_overlap.py index d03f87911..cdc1007a1 100644 --- a/HySoP/hysop/mpi/bridge_overlap.py +++ b/HySoP/hysop/mpi/bridge_overlap.py @@ -5,6 +5,7 @@ two parmes topologies defined on the same comm but for a different number of processes. """ from parmepy.mpi.topology import Cartesian, topotools +from parmepy.tools.misc import utils from parmepy.mpi.bridge import Bridge @@ -89,13 +90,13 @@ class BridgeOverlap(Bridge): current = [slice(None, None, None), ] * dimension for rk in indices_target: - inter = topotools.intersl(current, indices_target[rk]) + inter = utils.intersl(current, indices_target[rk]) if inter is not None: self._send_indices[rk] = inter if self._source is not None: # Back to local indices - convert = self._source.mesh.toIndexLocal2 + convert = self._source.mesh.convert2local self._send_indices = {rk: convert(self._send_indices[rk]) for rk in self._send_indices} @@ -104,11 +105,11 @@ class BridgeOverlap(Bridge): else: current = [slice(None, None, None), ] * dimension for rk in indices_source: - inter = topotools.intersl(current, indices_source[rk]) + inter = utils.intersl(current, indices_source[rk]) if inter is not None: self._recv_indices[rk] = inter if self._target is not None: - convert = self._target.mesh.toIndexLocal2 + convert = self._target.mesh.convert2local self._recv_indices = {rk: convert(self._recv_indices[rk]) for rk in self._recv_indices} diff --git a/HySoP/hysop/mpi/tests/test_mesh.py b/HySoP/hysop/mpi/tests/test_mesh.py deleted file mode 100644 index ac416debf..000000000 --- a/HySoP/hysop/mpi/tests/test_mesh.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -@file parmepy.mpi.tests.test_mesh -Testing mesh. -""" -from parmepy.domain.box import Box -from parmepy.tools.parameters import Discretization - - -def test_mesh3D(): - """Periodic mesh""" - dom = Box() - resolTopo = Discretization([33, 33, 17]) - topo = dom.create_topology(resolTopo, dim=3) - res = resolTopo.resolution - dx = [1. / (n - 1) for n in res] - assert (topo.mesh.origin == [0. for n in res]).all() - assert (topo.mesh.end == [1. - dxx for dxx in dx]).all() - assert (topo.mesh.global_start == [0 for n in res]).all() - assert (topo.mesh.global_end == [n - 2 for n in res]).all() - assert (topo.mesh.local_start == [0 for n in res]).all() - assert (topo.mesh.local_end == [n - 2 for n in res]).all() - assert len(topo.mesh.coords) == 3 - assert topo.mesh.coords[0].shape == (res[0] - 1, 1, 1) - assert topo.mesh.coords[1].shape == (1, res[1] - 1, 1) - assert topo.mesh.coords[2].shape == (1, 1, res[2] - 1) - assert topo.mesh.coords[0][1, 0, 0] == dx[0] - assert topo.mesh.coords[1][0, 1, 0] == dx[1] - assert topo.mesh.coords[2][0, 0, 1] == dx[2] - - -def test_mesh3D_ghost(): - """Periodic mesh""" - dom = Box() - resolTopo = Discretization([33, 33, 17], [2, 2, 2]) - topo = dom.create_topology(resolTopo, dim=3) - ghost = topo.ghosts() - res = resolTopo.resolution - dx = [1. / (n - 1) for n in res] - assert (topo.mesh.origin == - [0. - g * dxx for dxx, g in zip(dx, ghost)]).all() - assert (topo.mesh.end == - [1. - dxx + g * dxx for dxx, g in zip(dx, ghost)]).all() - assert (topo.mesh.global_start == [0 for n in res]).all() - assert (topo.mesh.global_end == [n - 2 for n in res]).all() - assert (topo.mesh.local_start == [g for g in ghost]).all() - assert (topo.mesh.local_end == - [n - 2 + g for n, g in zip(res, ghost)]).all() - assert len(topo.mesh.coords) == 3 - assert topo.mesh.coords[0].shape == (res[0] - 1 + 2 * ghost[0], 1, 1) - assert topo.mesh.coords[1].shape == (1, res[1] - 1 + 2 * ghost[1], 1) - assert topo.mesh.coords[2].shape == (1, 1, res[2] - 1 + 2 * ghost[2]) - assert topo.mesh.coords[0][1, 0, 0] == (-ghost[0] + 1) * dx[0] - assert topo.mesh.coords[1][0, 1, 0] == (-ghost[1] + 1) * dx[1] - assert topo.mesh.coords[2][0, 0, 1] == (-ghost[2] + 1) * dx[2] - -# Todo : update tests for multi-proc mpi runs. -#if __name__ == '__main__': -# test_mesh3D() -# test_mesh3D_ghost() diff --git a/HySoP/hysop/operator/advold.py b/HySoP/hysop/operator/advold.py deleted file mode 100644 index 7980d0e9b..000000000 --- a/HySoP/hysop/operator/advold.py +++ /dev/null @@ -1,521 +0,0 @@ -""" -@file advection.py - -Advection of a field. -""" -from parmepy.constants import debug, np, PARMES_INDEX, S_DIR -from parmepy.operator.computational import Computational -from parmepy.methods_keys import Scales, TimeIntegrator, Interpolation,\ - Remesh, Support, Splitting, MultiScale -from parmepy.numerics.remeshing import L2_1 -from parmepy.fields.continuous import Field -from parmepy.operator.redistribute import Redistribute -from parmepy.operator.advectionDir import AdvectionDir -import parmepy.default_methods as default -import parmepy.tools.numpywrappers as npw -from parmepy.operator.continuous import opsetup - - -class Advection(Computational): - """ - Advection of a field, - \f{eqnarray*} - X = Op(X,velocity) - \f} for : - \f{eqnarray*} - \frac{\partial{X}}{\partial{t}} + velocity.\nabla X = 0 - \f} - Note : we assume incompressible flow. - - Computations are performed within a dimensional splitting as folows: - - 2nd order: - - X-dir, half time step - - Y-dir, half time step - - Z-dir, full time step - - Y-dir, half time step - - X-dir, half time step - - 2nd order full half-steps: - - X-dir, half time step - - Y-dir, half time step - - Z-dir, half time step - - Z-dir, half time step - - Y-dir, half time step - - X-dir, half time step - - 1st order g: - - X-dir, half time step - - Y-dir, half time step - - Z-dir, half time step - - """ - - @debug - def __init__(self, velocity, advectedFields, **kwds): - """ - Create a Transport operator from given variables velocity and scalar. - - @param velocity : velocity variable. - @param advectedFields : Advected fields (may be a list of Fields). - @param resolutions : list of resolutions (one per variable) - @param method : Method used - @param splittingConfig : Dimensional splitting configuration - (default 'o2') - @param topo : a predefined topology to discretize variables - """ - v = [velocity] - if isinstance(advectedFields, list): - self.advectedFields = advectedFields - else: - self.advectedFields = [advectedFields] - v += self.advectedFields - assert 'variables' not in kwds, 'variables parameter is useless.' - super(Advection, self).__init__(variables=v, **kwds) - - vars_str = "_(" - for vv in self.advectedFields: - vars_str += vv.name + "," - self.name += vars_str[0:-1] + ')' - - if self.method is None: - self.method = default.ADVECTION - - ## Transport velocity - self.velocity = velocity - ## Transported fields - self.output = self.advectedFields - self.input = [var for var in self.variables] - - self.config = {} - - # Find which solver is used for advection, - # among Scales, pure-python and GPU-like. - # Check also operator-splitting type. - if Scales in self.method.keys(): - self._isSCALES = True - # Default splitting = Strang - if Splitting not in self.method.keys(): - self.method[Splitting] = 'strang' - else: - self._isSCALES = False - assert TimeIntegrator in self.method.keys() - assert Interpolation in self.method.keys() - assert Remesh in self.method.keys() - assert Support in self.method.keys() - if Splitting not in self.method.keys(): - self.method[Splitting] = 'o2' - self._old_dir = 0 - self.splitting = [] - self._dim = self.velocity.dimension - self.advecDir = None - if not self._isSCALES: - particles_advectedFields = [ - Field(adF.domain, name="Particle_AdvectedFields", - isVector=adF.isVector) - for adF in self.advectedFields] - if self.method[Support].find('gpu_1k') >= 0: - particles_positions = None - else: - particles_positions = \ - Field(self.advectedFields[0].domain, - name="Particle_Position", isVector=False - ) - - # Directional continuous Advection operators - self.advecDir = [None] * self._dim - for i in xrange(self._dim): - self.advecDir[i] = AdvectionDir( - self.velocity, self.advectedFields, i, - particles_advectedFields, particles_positions, - isMultiScale=self._isMultiScale, - name_suffix=vars_str[0:-1] + ')', **kwds) - - # function to switch between CPU or GPU setup. - self._my_setup = None - - @debug - def discretize(self): - """ - Discretisation according to the chosen method. - Available methods : - - 'scales' : SCALES fortran routines (3d only, list of vector - and/or scalar) - - 'p_O2' : order 4 method, corrected to allow large CFL number, - untagged particles - - 'p_O4' : order 4 method, corrected to allow large CFL number, - untagged particles - - 'p_L2' : limited and corrected lambda 2 - - 'p_M4' : Lambda_2,1 (=M'4) 4 point formula - - 'p_M6' (default) : Lambda_4,2 (=M'6) 6 point formula - - 'p_M8' : M8prime formula - - 'p_44' : Lambda_4,4 formula - - 'p_64' : Lambda_6,4 formula - - 'p_66' : Lambda_6,6 formula - - 'p_84' : Lambda_8,4 formula - - 'gpu' : OpenCL kernels (2d and 3d, single field, scalar or vector) - - Kernels versions: - - '1k' : Single OpenCL kernel for advection and remeshing - - '2k' : Separate kernels - - Integration method: - - 'rk2' : Runge Kutta 2nd order advection - - 'rk4' : Runge Kutta 4th order advection - - remeshing formula: - - 'm4prime' : = 'l2_1' - - 'l2_1' : Labmda2,1 : (=M'4) 4 point formula, C1 regularity - - 'l2_2' : Labmda2,2 : 4 point formula, C2 regularity - - 'm6prime' : = 'l4_2' - - 'l4_2' : Labmda4,2 : (=M'6) 6 point formula, C2 regularity - - 'l4_3' : Labmda4,3 : 6 point formula, C3 regularity - - 'l4_4' : Labmda4,4 : 6 point formula, C4 regularity - - 'l6_3' : Labmda6,3 : 8 point formula, C3 regularity - - 'l6_4' : Labmda6,4 : 8 point formula, C4 regularity - - 'l6_5' : Labmda6,5 : 8 point formula, C5 regularity - - 'l6_6' : Labmda6,6 : 8 point formula, C6 regularity - - 'l8_4' : Labmda8,4 : 10 point formula, C4 regularity - - 'm8prime' : M8prime formula - - other : Pure python (2d and 3d, list of vector and/or scalar) - - Integration method: - - 'rk2' : Runge Kutta 2nd order advection - - 'rk4' : Runge Kutta 4th order advection - - remeshing formula: - - 'm4prime' : = 'l2_1' - - 'l2_1' : Labmda2,1 : (=M'4) 4 point formula, C1 regularity - - 'l2_2' : Labmda2,2 : 4 point formula, C2 regularity - - 'm6prime' : = 'l4_2' - - 'l4_2' : Labmda4,2 : (=M'6) 6 point formula, C2 regularity - - 'l4_3' : Labmda4,3 : 6 point formula, C3 regularity - - 'l4_4' : Labmda4,4 : 6 point formula, C4 regularity - - 'l6_3' : Labmda6,3 : 8 point formula, C3 regularity - - 'l6_4' : Labmda6,4 : 8 point formula, C4 regularity - - 'l6_5' : Labmda6,5 : 8 point formula, C5 regularity - - 'l6_6' : Labmda6,6 : 8 point formula, C6 regularity - - 'l8_4' : Labmda8,4 : 10 point formula, C4 regularity - - 'm8prime' : M8prime formula - """ - # --- Advection solver from SCALES --- - if self._isSCALES: - if not self._dim == 3: - raise ValueError("Scales Advection not implemented in 2D.") - # - Scales imports - - from parmepy.f2py import scales2py as scales - - # - Extract order form self.method (default p_M6) - - order = None - for o in ['p_O2', 'p_O4', 'p_L2', - 'p_M4', 'p_M6', 'p_M8', - 'p_44', 'p_64', 'p_66', 'p_84']: - if self.method[Scales].find(o) >= 0: - order = o - if order is None: - print ('Unknown advection method, turn to default (p_M6).') - order = 'p_M6' - # - Extract splitting form self.method (default strang) - - splitting = 'strang' - for s in ['classic', 'strang', 'particle']: - if self.method[Splitting].find(s) >= 0: - splitting = s - - # - Create the topologies (get param from scales) - - # Scales nbcells equals resolutions - 1 - nbcells = npw.asintarray(self.resolutions[self.advectedFields[0]]) - nbcells -= 1 - if self._fromTopo: - main_size = self.topology.size - comm = self.topology.comm - topodims = self.topology.shape - elif self._comm is not None: - main_size = self._comm.Get_size() - comm = self._comm - topodims = [1, 1, main_size] - else: - from parmepy.mpi.main_var import main_size - from parmepy.mpi.main_var import main_comm as comm - topodims = [1, 1, main_size] - scalesres, scalesoffset = \ - scales.init_advection_solver(nbcells, - self.domain.length, - topodims, comm.py2f(), - order=order, - dim_split=splitting) - msg = 'Scales Advection not yet implemented with ghosts points.' - # Use same topodims as scales to create Cartesian topology - # in order to discretize our fields - # Case 1 : topology provided by user at init - # and one topo for all fields - if self._fromTopo: - for v in self.variables: - topo = self.topologies[v] - assert (topo.shape == topodims).all(),\ - 'input topology is not scales compliant.' - assert not (topo.ghosts > 0).any(), msg - self.discreteFields[v] = v.discretize(topo) - - # Case 2 : a dict of resolutions - else: - if self.ghosts is not None: - assert not (self.ghosts > 0).any(), msg - if self._singleTopo: - topo = self.domain.getOrCreateTopology( - self._dim, self.resolutions[self.velocity], - topodims, precomputed=True, offset=scalesoffset, - localres=scalesres, ghosts=self.ghosts, - comm=self._comm) - for v in self.variables: - self.discreteFields[v] = v.discretize(topo) - else: - for v in self.variables: - topo = \ - self.domain.getOrCreateTopology( - self._dim, self.resolutions[v], topodims, - precomputed=True, offset=scalesoffset, - localres=scalesres, ghosts=self.ghosts, - comm=self._comm) - # ... and the corresponding discrete field - self.discreteFields[v] = v.discretize(topo) - - - if self._isMultiScale: - self.config['isMultiscale'] = self._isMultiScale - v_shape = np.asarray(self.resolutions[self.velocity], - dtype=PARMES_INDEX) - 1 - scales.init_multiscale(v_shape[0], v_shape[1], v_shape[2], - self.method[MultiScale]) - self._my_setup = self.setup_Scales - - # --- GPU or pure-python advection --- - else: - for i in xrange(self._dim): - self.advecDir[i].discretize() - self.discreteFields = self.advecDir[0].discreteFields - self._my_setup = self.setup_Python - - @staticmethod - def getWorkLengths(method=None, domain_dim=None): - """ - Return the length of working arrays lists required - for advction discrete operator, depending on : - - the time integrator (RK2, ...) - - the interpolation (which depends on domain dimension) - - the remeshing (which depends on domain dimension) - @param method : the dict of parameters for the operator. - Default = parmepy.default_methods.ADVECTION - """ - if method is None: - method = default.ADVECTION - assert Interpolation in method,\ - 'An interpolation is required for the advection method.' - assert TimeIntegrator in method,\ - 'A time integrator is required for the advection method.' - assert Remesh in method,\ - 'A remesh is required for the advection method.' - tw = method[TimeIntegrator].getWorkLengths(1) - iw, iiw = method[Interpolation].getWorkLengths(domain_dim=domain_dim) - rw, riw = method[Remesh].getWorkLengths(domain_dim=domain_dim) - return max(tw + iw, rw), max(iiw, riw) - - def setWorks(self, rwork=None, iwork=None): - if rwork is None: - rwork = [] - if iwork is None: - iwork = [] - if not self._isSCALES: - for i in xrange(self._dim): - self.advecDir[i].setWorks(rwork, iwork) - - @opsetup - def setup(self): - # Check resolutions to set multiscale case, if required. - self._isMultiScale = False - v_resol = self.variables[self.velocity] - if v_resol != self.variables[self.advectedFields[0]]: - self._isMultiScale = True - if self._isMultiScale and not MultiScale in self.method.keys(): - print ("Using default mutiscale interpolation : L2_1") - self.method[MultiScale] = L2_1 - - if not self._is_uptodate: - self._my_setup() - - def setup_Scales(self): - advectedDiscreteFields = [self.discreteFields[f] - for f in self.advectedFields] - # - Create the discreteOperator from the - # list of discrete fields - - from parmepy.operator.discrete.scales_advection import \ - ScalesAdvection - self.discreteOperator = ScalesAdvection( - self.discreteFields[self.velocity], - advectedDiscreteFields, method=self.method, - **self.config) - - # -- Final set up -- - self.discreteOperator.setup() - self._is_uptodate = True - - def setup_Python(self): - for i in xrange(self._dim): - self.advecDir[i].setup() - # If topologies differs between directions, - # one need Redistribute - # operators - main_size = self.advecDir[0].discreteFields[ - self.velocity].topology.size - if main_size > 1: - # Build bridges - self.bridges = self._dim * [None] - if main_size > 1: - for dfrom in xrange(self.domain.dimension): - self.bridges[dfrom] = self._dim * [None] - for dto in xrange(self._dim): - if dfrom == dto: - self.bridges[dfrom][dto] = None - else: - nsuffix = str(dfrom) + '_' + str(dto) - self.bridges[dfrom][dto] = Redistribute( - variables=self.advectedFields, - opFrom=self.advecDir[dfrom], - opTo=self.advecDir[dto], - name_suffix=nsuffix) - self.bridges[dfrom][dto].setup() - - # Splitting configuration - if self.method[Splitting] == 'o2_FullHalf': - ## Half timestep in all directions - [self.splitting.append((i, 0.5)) - for i in xrange(self._dim)] - [self.splitting.append((self._dim - 1 - i, 0.5)) - for i in xrange(self._dim)] - elif self.method[Splitting] == 'o1': - [self.splitting.append((i, 1.)) for i in xrange(self._dim)] - elif self.method[Splitting] == 'x_only': - self.splitting.append((0, 1.)) - elif self.method[Splitting] == 'y_only': - self.splitting.append((1, 1.)) - elif self.method[Splitting] == 'z_only': - self.splitting.append((2, 1.)) - elif self.method[Splitting] == 'o2': - ## Half timestep in all directions but last - [self.splitting.append((i, 0.5)) - for i in xrange(self._dim - 1)] - self.splitting.append((self._dim - 1, 1.)) - [self.splitting.append((self._dim - 2 - i, 0.5)) - for i in xrange(self._dim - 1)] - else: - raise ValueError('Unknown splitting configuration:' + - self.method[Splitting]) - - if self.method[Support].find('gpu') >= 0: - splitting_nbSteps = len(self.splitting) - for d in xrange(self.domain.dimension): - dOp = self.advecDir[d].discreteOperator - assert len(dOp.exec_list) == splitting_nbSteps, \ - "Discrete operator execution " + \ - "list and splitting steps sizes must be equal " + \ - str(len(dOp.exec_list)) + " != " + \ - str(splitting_nbSteps) - - if main_size > 1: - self.apply = self._apply_Comm - else: - self.apply = self._apply_noComm - self._is_uptodate = True - - if self.method[Support].find('gpu') >= 0: - s = "" - device_id = self.advecDir[d].discreteOperator.cl_env._device_id - gpu_comm = self.advecDir[d].discreteOperator.cl_env.gpu_comm - gpu_rank = gpu_comm.Get_rank() - if gpu_rank == 0: - s += "=== OpenCL buffers allocated" - s += " on Device:{0} ===\n".format(device_id) - s += "Global memory used:\n" - total_gmem = 0 - for d in xrange(self.domain.dimension): - g_mem_d = 0 - for df in self.advecDir[d].discreteOperator.variables: - if not df.gpu_allocated: - df.allocate() - g_mem_df = gpu_comm.allreduce(df.mem_size) - g_mem_d += g_mem_df - if gpu_rank == 0: - s += " Advection" + S_DIR[d] + ": {0:9d}".format(g_mem_d) - s += "Bytes ({0:5d} MB)\n".format(g_mem_d / (1024 ** 2)) - total_gmem += g_mem_d - if gpu_rank == 0: - s += " Total : {0:9d}".format(total_gmem) - s += "Bytes ({0:5d} MB)\n".format(total_gmem / (1024 ** 2)) - s += "Local memory used:\n" - total_lmem = 0 - for d in xrange(self.domain.dimension): - l_mem_d = gpu_comm.allreduce( - self.advecDir[d].discreteOperator.size_local_alloc) - if gpu_rank == 0: - s += " Advection" + S_DIR[d] + ": {0:9d}".format(l_mem_d) - s += "Bytes ({0:5d} MB)\n".format(l_mem_d / (1024 ** 2)) - total_lmem += l_mem_d - if gpu_rank == 0: - s += " Total : {0:9d}".format(total_lmem) + "Bytes" - print (s) - - @debug - def _apply_noComm(self, simulation=None): - """ - Apply this operator to its variables. - @param simulation : object that describes the simulation - parameters (time, time step, iteration number ...), see - parmepy.problem.simulation.Simulation for details. - - Redefinition for advection. Applying a dimensional splitting. - """ - for req in self.requirements: - req.wait() - for split_id, split in enumerate(self.splitting): - self.advecDir[split[0]].apply( - simulation, split[1], split_id, self._old_dir) - self._old_dir = split[0] - - @debug - def _apply_Comm(self, simulation=None): - """ - Apply this operator to its variables. - @param simulation : object that describes the simulation - parameters (time, time step, iteration number ...), see - parmepy.problem.simulation.Simulation for details. - - Redefinition for advection. Applying a dimensional splitting. - """ - for req in self.requirements: - req.wait() - for split_id, split in enumerate(self.splitting): - # Calling the redistribute operators between directions - if not self._old_dir == split[0]: - self.bridges[self._old_dir][split[0]].apply() - self.bridges[self._old_dir][split[0]].wait() - self.advecDir[split[0]].apply( - simulation, split[1], split_id, self._old_dir) - self._old_dir = split[0] - - @debug - def finalize(self): - """ - Memory cleaning. - """ - if self._isSCALES: - Computational.finalize(self) - else: - for dop in self.advecDir: - dop.finalize() - self.timer = self.timer + dop.timer - - def __str__(self): - """ - Common printings for operators - """ - shortName = str(self.__class__).rpartition('.')[-1][0:-2] - if self._isSCALES: - super(Advection, self).__str__() - else: - for i in xrange(self._dim): - if self.advecDir[i].discreteOperator is not None: - s = str(self.advecDir[i].discreteOperator) - else: - s = shortName + " operator. Not discretised." - return s + "\n" diff --git a/HySoP/hysop/operator/computational.py b/HySoP/hysop/operator/computational.py index 4a51b4cc8..80b30340c 100644 --- a/HySoP/hysop/operator/computational.py +++ b/HySoP/hysop/operator/computational.py @@ -264,7 +264,7 @@ class Computational(Operator): resolution, self.domain.length, comm=comm.py2f()) assert (topo.mesh.resolution == localres).all() - assert (topo.mesh.global_start == global_start).all() + assert (topo.mesh.start() == global_start).all() msg = 'Ghosts points not yet implemented for fftw-type operators.' assert (topo.ghosts() == 0).all(), msg diff --git a/HySoP/hysop/operator/discrete/drag_and_lift.py b/HySoP/hysop/operator/discrete/drag_and_lift.py index 0d54f4284..a22a48a75 100644 --- a/HySoP/hysop/operator/discrete/drag_and_lift.py +++ b/HySoP/hysop/operator/discrete/drag_and_lift.py @@ -10,7 +10,7 @@ from parmepy.numerics.finite_differences import FD_C_2 from parmepy.operator.discrete.discrete import DiscreteOperator import parmepy.tools.numpywrappers as npw from parmepy.tools.profiler import profile -from parmepy.numerics.utils.Utils import sum_cross_product +from parmepy.numerics.utils.Utils import Utils class DragAndLift(DiscreteOperator): @@ -242,7 +242,7 @@ class DragAndLift(DiscreteOperator): # slices representing the points inside the subset # (global indices relative to the global mesh of the current topology) sl = self._voc.slices[self._topology] - res = sum_cross_product(coords, self.vorticity.data, - sl, self._rwork, self._obsinds) + res = Utils.sum_cross_product(coords, self.vorticity.data, + sl, self._rwork, self._obsinds) res *= self._dvol return res diff --git a/HySoP/hysop/operator/hdf_io.py b/HySoP/hysop/operator/hdf_io.py index 25715432e..6338a081f 100644 --- a/HySoP/hysop/operator/hdf_io.py +++ b/HySoP/hysop/operator/hdf_io.py @@ -37,7 +37,7 @@ class HDF_IO(Computational): @param var_names : a dictionnary of names to connect fields to the dataset in the hdf file. See example below. @param subset : a subset of the domain, on which data are read. - It must be a parmepy.domain.obstacles.Obstacle. + It must be a parmepy.domain.subset. Names paramater example: if variables=[velo, vorti], and if hdf file contains @@ -108,26 +108,13 @@ class HDF_IO(Computational): # Discretize the subset, if required if self.subset is not None: self.subset.discretize(self._topology) - self._slices = self.subset.slices[self._topology] - # Global resolution for hdf5 output - self._global_resolution = \ - self.subset.global_resolution(self._topology) - g_start = self.subset.gstart - # convert self._slices to global position in topo - sl = self._topology.mesh.toIndexGlobal(self._slices) - # And shift using global position of the surface - sl = [slice(sl[i].start - g_start[i], sl[i].stop - g_start[i]) - for i in xrange(self.domain.dimension)] - + refmesh = self.subset.mesh[self._topology] else: - self._global_resolution = \ - list(self._topology.mesh.discretization.resolution - 1) - self._slices = self._topology.mesh.iCompute - g_start = self._topology.mesh.global_start - g_end = self._topology.mesh.global_end + 1 - sl = [slice(g_start[i], g_end[i]) - for i in xrange(self.domain.dimension)] - + refmesh = self._topology.mesh + # Global resolution for hdf5 output + self._global_resolution = list(refmesh.discretization.resolution - 1) + self._slices = refmesh.iCompute + sl = list(refmesh.position) # Reverse order, to fit with xdmf req. self._global_resolution.reverse() sl.reverse() @@ -136,7 +123,6 @@ class HDF_IO(Computational): @opsetup def setup(self, rwork=None, iwork=None): # No list of hdf dataset names provided by user ... - print 'HDF setup ...', self.__class__ if self.var_names is None: # Get field names and initialize dataset dict. for df in self.discreteFields.values(): diff --git a/HySoP/hysop/tools/misc.py b/HySoP/hysop/tools/misc.py new file mode 100644 index 000000000..dca1db598 --- /dev/null +++ b/HySoP/hysop/tools/misc.py @@ -0,0 +1,44 @@ +""" +@file misc.py +Some utilities to deal with slices and other python objects +""" + + +class utils(object): + @staticmethod + def arrayToDict(inarray): + """ + convert an array into a dictionnary, + keys being the column numbers in array + and values the content of each corresponding column + transformed into a list of slices like this: + column = [1, 4, 2, 6, ...] ---> [slice(1, 4), slice(2, 6), ...] + """ + outslice = {} + size = inarray.shape[1] + dimension = (int)(0.5 * inarray.shape[0]) + for rk in xrange(size): + outslice[rk] = [slice(inarray[2 * d, rk], + inarray[2 * d + 1, rk] + 1) + for d in xrange(dimension)] + return outslice + + @staticmethod + def intersl(sl1, sl2): + """ + @param sl1 : a list of slices + @param sl2 : a list of slices + @return : guess what ... a list of slices such that: + result[i] = intersection between sl1[i] and sl2[i] + """ + assert len(sl1) == len(sl2) + res = [None] * len(sl1) + for d in xrange(len(sl1)): + s1 = sl1[d] + s2 = sl2[d] + start = max(s1.start, s2.start) + stop = min(s1.stop, s2.stop) + if stop <= start: + return None + res[d] = slice(start, stop) + return tuple(res) diff --git a/HySoP/setup.py.in b/HySoP/setup.py.in index 04c5da8e6..86e72c608 100644 --- a/HySoP/setup.py.in +++ b/HySoP/setup.py.in @@ -14,6 +14,7 @@ name = '@PYPACKAGE_NAME@' # List of modules (directories) to be included packages = ['parmepy', 'parmepy.domain', + 'parmepy.domain.subsets', 'parmepy.domain.obstacle', 'parmepy.fields', 'parmepy.operator', -- GitLab