From f599aed0746f9f8203d9b5e7e4a7b11901233389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franck=20P=C3=A9rignon?= <franck.perignon@imag.fr> Date: Thu, 18 Sep 2014 16:01:13 +0200 Subject: [PATCH] bye bye monitors (2) --- HySoP/hysop/numerics/utils.py | 64 +++++ .../hysop/operator/discrete/drag_and_lift.py | 248 ++++++++++++++++++ HySoP/hysop/operator/drag_and_lift.py | 79 ++++++ 3 files changed, 391 insertions(+) create mode 100644 HySoP/hysop/numerics/utils.py create mode 100644 HySoP/hysop/operator/discrete/drag_and_lift.py create mode 100644 HySoP/hysop/operator/drag_and_lift.py diff --git a/HySoP/hysop/numerics/utils.py b/HySoP/hysop/numerics/utils.py new file mode 100644 index 000000000..b3d4bf44b --- /dev/null +++ b/HySoP/hysop/numerics/utils.py @@ -0,0 +1,64 @@ +from parmepy.constants import XDIR, YDIR, ZDIR +import parmepy.tools.numpywrappers as npw +import numpy as np + + +class Utils(object): + + i1 = [YDIR, ZDIR, XDIR] + i2 = [ZDIR, XDIR, YDIR] + gen_indices = zip(i1, i2) + + @staticmethod + def sum_cross_product(x, y, sl, work, subsets=None): + """ + @param x : a tuple representing grid coordinates (like coords in mesh) + @param y : a discrete field (i.e. list of numpy arrays) + @param work : numpy array + Sum on a volume (defined by sl) of cross products of + x with y at each grid point + . Result in work. + """ + current_dir = 0 + res = npw.zeros(3) + for (i, j) in Utils.gen_indices: + work[sl] = x[i] * y[j][sl] + work[sl] -= x[j] * y[i][sl] + for sub in subsets: + work[sub] = 0.0 + + res[current_dir] = npw.real_sum(work[sl]) + current_dir += 1 + return res + + @staticmethod + def sum_cross_product_2(x, y, ind, work): + current_dir = 0 + res = npw.zeros(3) + ilist = np.where(ind) + nb = len(ilist[0]) + for (i, j) in Utils.gen_indices: + work.flat[:nb] = x[i].flat[ilist[i]] * y[j][ind]\ + - x[j].flat[ilist[j]] * y[i][ind] + res[current_dir] = npw.real_sum(work.flat[:nb]) + current_dir += 1 + return res + + @staticmethod + def sum_cross_product_3(x, y, ind): + """ + Integrate over the control box using python loops. + ---> wrong way, seems to be really slower although + it costs less in memory. + Used only for tests (timing). + """ + ilist = np.where(ind) + res = npw.zeros(3) + for(ix, iy, iz) in zip(ilist[0], ilist[YDIR], ilist[ZDIR]): + res[XDIR] += x[YDIR][0, iy, 0] * y[ZDIR][ix, iy, iz]\ + - x[ZDIR][0, 0, iz] * y[YDIR][ix, iy, iz] + res[YDIR] += x[ZDIR][0, 0, iz] * y[XDIR][ix, iy, iz]\ + - x[XDIR][ix, 0, 0] * y[ZDIR][ix, iy, iz] + res[ZDIR] += x[XDIR][ix, 0, 0] * y[YDIR][ix, iy, iz]\ + - x[YDIR][0, iy, 0] * y[XDIR][ix, iy, iz] + return res diff --git a/HySoP/hysop/operator/discrete/drag_and_lift.py b/HySoP/hysop/operator/discrete/drag_and_lift.py new file mode 100644 index 000000000..0d54f4284 --- /dev/null +++ b/HySoP/hysop/operator/discrete/drag_and_lift.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +""" +@file compute_forces.py +Compute forces +""" +from parmepy.constants import np, XDIR, YDIR, ZDIR, debug +from parmepy.numerics.update_ghosts import UpdateGhosts +from parmepy.numerics.differential_operations import Laplacian +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 + + +class DragAndLift(DiscreteOperator): + """ + Compute drag and lift using Noca's formula. + See Noca99 or Plouhmans, 2002, Journal of Computational Physics + The present class implements formula (52) of Plouhmans2002. + Integral inside the obstacle is not taken into account. + """ + def __init__(self, velocity, vorticity, nu, normalization, + volumeOfControl, obstacles=None, **kwds): + """ + @param velocity field + @param vorticity field + @param nu viscosity + @param the topology on which forces will be computed + @param a volume of control + (parmepy.domain.obstacle.controlBox.ControlBox object) + @param obstacles a list of parmepy.domain.obstacles inside + the box + @param io_params : parameters (dict) to set file output. + If None, no output. Set io_params = {} if you want output, + with default parameters values. Default file name = 'drag_and_lift' + See parmepy.tools.io_utils.Writer for details + """ + assert 'variables' not in kwds, 'variables parameter is useless.' + super(DragAndLift, self).__init__(variables=[velocity, vorticity], + **kwds) + + ## discrete velocity field + self.velocity = velocity + ## discrete vorticity field + self.vorticity = vorticity + # volume on which forces are computed + self._voc = volumeOfControl + self._dim = self.domain.dimension + msg = 'Force computation undefined for domain of dimension 1.' + assert self._dim > 1, msg + # Local buffers, used for time-derivative computation + self._previous = npw.zeros(self._dim) + self._buffer = npw.zeros(self._dim) + ## The computed forces + self.force = npw.zeros(self._dim) + # Coef in the Noca formula + self._coeff = 1. / (self._dim - 1) + # Set how reduction will be performed + # Default = reduction over all process. + # \todo : add param to choose this option + self.mpi_sum = self._mpi_allsum + # Ghost points synchronizer + self._synchronize = None + ## viscosity + self.nu = nu + # Normalizing coefficient for forces + # (based on the physics of the flow) + self._normalization = normalization + self._topology = self.velocity.topology + self._dvol = npw.prod(self._topology.mesh.space_step) + assert (self._topology.ghosts() >= 1).all() + # function to compute the laplacian of a + # scalar field. Default fd scheme. (See Laplacian) + self._laplacian = Laplacian(self._topology) + # function used to compute first derivative of + # a scalar field in a given direction. + # Default = FD_C_2. Todo : set this as an input method value. + self._fd_scheme = FD_C_2(self._topology.mesh.space_step) + self._voc.discretize(self._topology) + if obstacles is None: + self._obsinds = [] + else: + for obs in obstacles: + obs.discretize(self._topology) + self._obsinds = [ind for ind in obs.ind[self._topology] + for obs in obstacles] + + # prepare ghost points synchro for velocity and vorticity used + # in fd schemes + nbc = self.velocity.nbComponents + self.vorticity.nbComponents + self._synchronize = UpdateGhosts(self._topology, nbc) + + def _set_work_arrays(self, rwork=None, iwork=None): + + v_ind = self.velocity.topology.mesh.iCompute + shape_v = self.velocity.data[0][v_ind].shape + # setup for rwork, iwork is useless. + if rwork is None: + # --- Local allocation --- + self._rwork = [npw.zeros(shape_v)] + else: + assert isinstance(rwork, list), 'rwork must be a list.' + # --- External rwork --- + self._rwork = rwork + assert len(self._rwork) == 1 + assert self._rwork[0].shape == shape_v + + def _mpi_allsum(self): + """ + Performs MPI reduction (sum result value over all process) + All process get the result of the sum. + """ + self.force = self._topology.comm.allreduce(self.force) + + def _mpi_sum(self, root=0): + """ + Performs MPI reduction (sum result value over all process) + Result send only to 'root' process. + @param root : number of the process which get the result. + """ + self.force = self._topology.comm.reduce(self.force, root=root) + + @debug + @profile + def apply(self, simulation=None): + """ + Perform integrals on volume and surfaces of the control box + @param parmepy.problem.simulation : object describing + simulation parameters + """ + assert simulation is not None,\ + "Simulation parameter is required for DragAndLift apply." + dt = simulation.timeStep + ite = simulation.currentIteration + # Synchro of ghost points is required for fd schemes + self._synchronize(self.velocity.data + self.vorticity.data) + + # -- Integration over the volume of control -- + # -1/(N-1) . d/dt int(x ^ w) + if self._voc.isEmpty[self._topology]: + self._buffer[...] = 0.0 + self.force[...] = 0.0 + else: + self._buffer = self._integrateOnBox(self._buffer) + self.force[...] = -1. / dt * self._coeff * (self._buffer + - self._previous) + + # Update previous for next time step ... + self._previous[...] = self._buffer[...] + # -- Integrals on surfaces -- + # Only on surf. normal to XDIR + self._buffer = self._integrateOnSurface(self._voc.upperS[XDIR], + self._buffer) + self.force += self._buffer + self._buffer = self._integrateOnSurface(self._voc.lowerS[XDIR], + self._buffer) + self.force += self._buffer + + # Reduce results over all MPI process in topo + self.mpi_sum() + self.force *= self._normalization + + # Print results, if required + if self._writer is not None and self._writer.do_write(ite): + self._writer.buffer[0, 0] = simulation.time + self._writer.buffer[0, 1:] = self.force + self._writer.write() + + return self.force + + def _integrateOnSurface(self, surf, res): + + res[...] = 0.0 + if surf.isEmpty[self._topology]: + return res + + # Get normal of the surface + normal = surf.normal + # Get indices of the surface + sl = surf.slices[self._topology] + coords = surf.coords[self._topology] + vdata = self.velocity.data + wdata = self.vorticity.data + # i1 : normal dir + # i2 : other dirs + i1 = np.where(normal)[0][0] + i2 = np.where(normal == 0)[0] + dsurf = npw.prod(self._topology.mesh.space_step[i2]) + # Indices used for cross-product + j1 = [YDIR, ZDIR, XDIR] + j2 = [ZDIR, XDIR, YDIR] + + # i1 component + res[i1] = normal[i1] * 0.5 * npw.real_sum((-vdata[i1][sl] ** 2 + + sum([vdata[j][sl] ** 2 + for j in i2]))) + # other components + for j in i2: + res[j] = -normal[i1] * npw.real_sum(vdata[i1][sl] * vdata[j][sl]) + + # Second part of integral on surface ... + buff = npw.zeros(vdata[0][sl].shape) + for j in i2: + buff[...] = vdata[j1[j]][sl] * wdata[j2[j]][sl]\ + - vdata[j2[j]][sl] * wdata[j1[j]][sl] + res[i1] -= self._coeff * normal[i1] * npw.real_sum(coords[j] + * buff) + res[j] -= self._coeff * normal[i1] * coords[i1] *\ + npw.real_sum(buff) + + # Last part + # Update fd schemes in order to compute laplacian and other derivatives + # only on the surface (i.e. for list of indices in sl) + self._laplacian.fd_scheme.computeIndices(sl) + for j in i2: + self._rwork[...] = self._laplacian(vdata[j], self._rwork) + res[i1] += self._coeff * self.nu * normal[i1]\ + * npw.real_sum(coords[j] * self._rwork[sl]) + res[j] -= self._coeff * self.nu * normal[i1] * coords[i1] * \ + npw.real_sum(self._rwork[sl]) + self._fd_scheme.computeIndices(sl) + self._fd_scheme.compute(vdata[i1], i1, self._rwork) + res[i1] += 2.0 * normal[i1] * self.nu * npw.real_sum(self._rwork[sl]) + for j in i2: + self._fd_scheme.compute(vdata[i1], j, self._rwork) + res[j] += normal[i1] * self.nu * npw.real_sum(self._rwork[sl]) + self._fd_scheme.compute(vdata[j], i1, self._rwork) + res[j] += normal[i1] * self.nu * npw.real_sum(self._rwork[sl]) + + res *= dsurf + return res + + def _integrateOnBox(self, res): + """ + Compute the integral over the volume of control + of \f$ x ^ \omega \f$ + """ + assert self._dim == 3, 'Not defined for dim < 3' + # coords of the points in the volume of control + coords = self._voc.coords[self._topology] + # 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 *= self._dvol + return res diff --git a/HySoP/hysop/operator/drag_and_lift.py b/HySoP/hysop/operator/drag_and_lift.py new file mode 100644 index 000000000..c92be7f60 --- /dev/null +++ b/HySoP/hysop/operator/drag_and_lift.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +""" +@file compute_forces.py +Compute forces +""" +from parmepy.operator.computational import Computational +from parmepy.operator.continuous import opsetup + + +class DragAndLift(Computational): + """ + Compute drag and lift using Noca's formula. + See Noca99 or Plouhmans, 2002, Journal of Computational Physics + The present class implements formula (52) of Plouhmans2002. + Integral inside the obstacle is not taken into account. + """ + def __init__(self, velocity, vorticity, nu, normalization, + volumeOfControl, obstacles=None, **kwds): + """ + @param velocity field + @param vorticity field + @param nu viscosity + @param the topology on which forces will be computed + @param a volume of control + (parmepy.domain.obstacle.controlBox.ControlBox object) + @param obstacles a list of parmepy.domain.obstacles inside + the box + @param io_params : parameters (dict) to set file output. + If None, no output. Set io_params = {} if you want output, + with default parameters values. Default file name = 'drag_and_lift' + See parmepy.tools.io_utils.Writer for details + """ + assert 'variables' not in kwds, 'variables parameter is useless.' + super(DragAndLift, self).__init__(variables=[velocity, vorticity], + **kwds) + if obstacles is None: + obstacles = [] + self.input = [velocity, vorticity] + self._discr_args = {velocity: velocity, + vorticity: vorticity, nu: nu, + normalization: normalization, + volumeOfControl: volumeOfControl, + obstacles: obstacles} + + def get_work_properties(self): + if not self._is_discretized: + msg = 'The operator must be discretized ' + msg += 'before any call to this function.' + raise RuntimeError(msg) + vd = self.discreteFields[self.input[0]] + v_ind = vd.topology.mesh.iCompute + shape_v = vd[0][v_ind].shape + return {'rwork': [shape_v], 'iwork': None} + + @opsetup + def setup(self, rwork=None, iwork=None): + msg = 'Multi-resolution is not allowed.' + assert self._single_topo, msg + if not self._is_uptodate: + from parmepy.operator.discrete.drag_and_lift \ + import DragAndLift as DAL + self.discreteOperator = DAL(**self._discr_args) + + # output setup + self._set_io('drag_and_lift', (1, 4)) + self.discreteOperator.setWriter(self._writer) + self._is_uptodate = True + + def drag(self): + """ + @return the last computed value for drag force + """ + return self.discreteOperator.force[2] + + def lift(self): + """ + @return the last computed value for lift + """ + return self.discreteOperator.force[:1] -- GitLab