CoMD
A Mini-app for Co-Design of Classical Molecular Dynamics.
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
linkCells.c
Go to the documentation of this file.
1 /// \file
2 /// Functions to maintain link cell structures for fast pair finding.
3 
4 #include "linkCells.h"
5 
6 #include <stdio.h>
7 #include <string.h>
8 #include <assert.h>
9 #include <math.h>
10 
11 #include "parallel.h"
12 #include "memUtils.h"
13 #include "decomposition.h"
14 #include "performanceTimers.h"
15 #include "CoMDTypes.h"
16 
17 #define MIN(A,B) ((A) < (B) ? (A) : (B))
18 #define MAX(A,B) ((A) > (B) ? (A) : (B))
19 
20 static void copyAtom(LinkCell* boxes, Atoms* atoms, int iAtom, int iBox, int jAtom, int jBox);
21 static int getBoxFromCoord(LinkCell* boxes, real_t rr[3]);
22 static void emptyHaloCells(LinkCell* boxes);
23 static void getTuple(LinkCell* boxes, int iBox, int* ixp, int* iyp, int* izp);
24 
25 /// In CoMD 1.1, atoms are stored in link cells. Link cells are widely
26 /// used in classical MD to avoid an O(N^2) search for atoms that
27 /// interact. Link cells are formed by subdividing the local spatial
28 /// domain with a Cartesian grid where the grid spacing in each
29 /// direction is at least as big as he potential's cutoff distance.
30 /// Because atoms don't interact beyond the potential cutoff, for an
31 /// atom iAtom in any given link cell, we can be certain that all atoms
32 /// that interact with iAtom are contained in the same link cell, or one
33 /// of the 26 neighboring link cells.
34 ///
35 /// CoMD chooses the link cell size (boxSize) on each axis to be the
36 /// shortest possible distance, longer than cutoff, such that the local
37 /// domain size divided by boxSize is an integer. I.e., the link cells
38 /// are commensurate with with the local domain size. While this does
39 /// not result in the smallest possible link cells, it does allow us to
40 /// keep a strict separation between the link cells that are entirely
41 /// inside the local domain and those that represent halo regions.
42 ///
43 /// The number of local link cells in each direction is stored in
44 /// gridSize. Local link cells have 3D grid coordinates (ix, iy, iz)
45 /// where ix, iy, and iz can range from 0 to gridSize[iAxis]-1,
46 /// whiere iAxis is 0 for x, 1 for y and 2 for the z direction. The
47 /// number of local link cells is thus nLocalBoxes =
48 /// gridSize[0]*gridSize[1]*gridSize[2].
49 ///
50 /// The local link cells are surrounded by one complete shell of halo
51 /// link cells. The halo cells provide temporary storage for halo or
52 /// "ghost" atoms that belong to other tasks, but whose coordinates are
53 /// needed locally to complete the force calculation. Halo link cells
54 /// have at least one coordinate with a value of either -1 or
55 /// gridSize[iAxis].
56 ///
57 /// Because CoMD stores data in ordinary 1D C arrays, a mapping is
58 /// needed from the 3D grid coords to a 1D array index. For the local
59 /// cells we use the conventional mapping ix + iy*nx + iz*nx*ny. This
60 /// keeps all of the local cells in a contiguous region of memory
61 /// starting from the beginning of any relevant array and makes it easy
62 /// to iterate the local cells in a single loop. Halo cells are mapped
63 /// differently. After the local cells, the two planes of link cells
64 /// that are face neighbors with local cells across the -x or +x axis
65 /// are next. These are followed by face neighbors across the -y and +y
66 /// axis (including cells that are y-face neighbors with an x-plane of
67 /// halo cells), followed by all remaining cells in the -z and +z planes
68 /// of halo cells. The total number of link cells (on each rank) is
69 /// nTotalBoxes.
70 ///
71 /// Data storage arrays that are used in association with link cells
72 /// should be allocated to store nTotalBoxes*MAXATOMS items. Data for
73 /// the first atom in linkCell iBox is stored at index iBox*MAXATOMS.
74 /// Data for subsequent atoms in the same link cell are stored
75 /// sequentially, and the number of atoms in link cell iBox is
76 /// nAtoms[iBox].
77 ///
78 /// \see getBoxFromTuple is the 3D->1D mapping for link cell indices.
79 /// \see getTuple is the 1D->3D mapping
80 ///
81 /// \param [in] cutoff The cutoff distance of the potential.
82 LinkCell* initLinkCells(const Domain* domain, real_t cutoff)
83 {
84  assert(domain);
85  LinkCell* ll = comdMalloc(sizeof(LinkCell));
86 
87  for (int i = 0; i < 3; i++)
88  {
89  ll->localMin[i] = domain->localMin[i];
90  ll->localMax[i] = domain->localMax[i];
91  ll->gridSize[i] = domain->localExtent[i] / cutoff; // local number of boxes
92  ll->boxSize[i] = domain->localExtent[i] / ((real_t) ll->gridSize[i]);
93  ll->invBoxSize[i] = 1.0/ll->boxSize[i];
94  }
95 
96  ll->nLocalBoxes = ll->gridSize[0] * ll->gridSize[1] * ll->gridSize[2];
97 
98  ll->nHaloBoxes = 2 * ((ll->gridSize[0] + 2) *
99  (ll->gridSize[1] + ll->gridSize[2] + 2) +
100  (ll->gridSize[1] * ll->gridSize[2]));
101 
102  ll->nTotalBoxes = ll->nLocalBoxes + ll->nHaloBoxes;
103 
104  ll->nAtoms = comdMalloc(ll->nTotalBoxes*sizeof(int));
105  for (int iBox=0; iBox<ll->nTotalBoxes; ++iBox)
106  ll->nAtoms[iBox] = 0;
107 
108  assert ( (ll->gridSize[0] >= 2) && (ll->gridSize[1] >= 2) && (ll->gridSize[2] >= 2) );
109  return ll;
110 }
111 
113 {
114  if (! boxes) return;
115  if (! *boxes) return;
116 
117  comdFree((*boxes)->nAtoms);
118  comdFree(*boxes);
119  *boxes = NULL;
120 
121  return;
122 }
123 
124 /// \details
125 /// Populates the nbrBoxes array with the 27 boxes that are adjacent to
126 /// iBox. The count is 27 instead of 26 because iBox is included in the
127 /// list (as neighbor 13). Caller is responsible to alloc and free
128 /// nbrBoxes.
129 /// \return The number of nbr boxes (always 27 in this implementation).
130 int getNeighborBoxes(LinkCell* boxes, int iBox, int* nbrBoxes)
131 {
132  int ix, iy, iz;
133  getTuple(boxes, iBox, &ix, &iy, &iz);
134 
135  int count = 0;
136  for (int i=ix-1; i<=ix+1; i++)
137  for (int j=iy-1; j<=iy+1; j++)
138  for (int k=iz-1; k<=iz+1; k++)
139  nbrBoxes[count++] = getBoxFromTuple(boxes,i,j,k);
140 
141  return count;
142 }
143 
144 /// \details
145 /// Finds the appropriate link cell for an atom based on the spatial
146 /// coordinates and stores data in that link cell.
147 /// \param [in] gid The global of the atom.
148 /// \param [in] iType The species index of the atom.
149 /// \param [in] x The x-coordinate of the atom.
150 /// \param [in] y The y-coordinate of the atom.
151 /// \param [in] z The z-coordinate of the atom.
152 /// \param [in] px The x-component of the atom's momentum.
153 /// \param [in] py The y-component of the atom's momentum.
154 /// \param [in] pz The z-component of the atom's momentum.
155 void putAtomInBox(LinkCell* boxes, Atoms* atoms,
156  const int gid, const int iType,
157  const real_t x, const real_t y, const real_t z,
158  const real_t px, const real_t py, const real_t pz)
159 {
160  real_t xyz[3] = {x,y,z};
161 
162  // Find correct box.
163  int iBox = getBoxFromCoord(boxes, xyz);
164  int iOff = iBox*MAXATOMS;
165  iOff += boxes->nAtoms[iBox];
166 
167  // assign values to array elements
168  if (iBox < boxes->nLocalBoxes)
169  atoms->nLocal++;
170  boxes->nAtoms[iBox]++;
171  atoms->gid[iOff] = gid;
172  atoms->iSpecies[iOff] = iType;
173 
174  atoms->r[iOff][0] = x;
175  atoms->r[iOff][1] = y;
176  atoms->r[iOff][2] = z;
177 
178  atoms->p[iOff][0] = px;
179  atoms->p[iOff][1] = py;
180  atoms->p[iOff][2] = pz;
181 }
182 
183 /// Calculates the link cell index from the grid coords. The valid
184 /// coordinate range in direction ii is [-1, gridSize[ii]]. Any
185 /// coordinate that involves a -1 or gridSize[ii] is a halo link cell.
186 /// Because of the order in which the local and halo link cells are
187 /// stored the indices of the halo cells are special cases.
188 /// \see initLinkCells for an explanation of storage order.
189 int getBoxFromTuple(LinkCell* boxes, int ix, int iy, int iz)
190 {
191  int iBox = 0;
192  const int* gridSize = boxes->gridSize; // alias
193 
194  // Halo in Z+
195  if (iz == gridSize[2])
196  {
197  iBox = boxes->nLocalBoxes + 2*gridSize[2]*gridSize[1] + 2*gridSize[2]*(gridSize[0]+2) +
198  (gridSize[0]+2)*(gridSize[1]+2) + (gridSize[0]+2)*(iy+1) + (ix+1);
199  }
200  // Halo in Z-
201  else if (iz == -1)
202  {
203  iBox = boxes->nLocalBoxes + 2*gridSize[2]*gridSize[1] + 2*gridSize[2]*(gridSize[0]+2) +
204  (gridSize[0]+2)*(iy+1) + (ix+1);
205  }
206  // Halo in Y+
207  else if (iy == gridSize[1])
208  {
209  iBox = boxes->nLocalBoxes + 2*gridSize[2]*gridSize[1] + gridSize[2]*(gridSize[0]+2) +
210  (gridSize[0]+2)*iz + (ix+1);
211  }
212  // Halo in Y-
213  else if (iy == -1)
214  {
215  iBox = boxes->nLocalBoxes + 2*gridSize[2]*gridSize[1] + iz*(gridSize[0]+2) + (ix+1);
216  }
217  // Halo in X+
218  else if (ix == gridSize[0])
219  {
220  iBox = boxes->nLocalBoxes + gridSize[1]*gridSize[2] + iz*gridSize[1] + iy;
221  }
222  // Halo in X-
223  else if (ix == -1)
224  {
225  iBox = boxes->nLocalBoxes + iz*gridSize[1] + iy;
226  }
227  // local link celll.
228  else
229  {
230  iBox = ix + gridSize[0]*iy + gridSize[0]*gridSize[1]*iz;
231  }
232  assert(iBox >= 0);
233  assert(iBox < boxes->nTotalBoxes);
234 
235  return iBox;
236 }
237 
238 /// Move an atom from one link cell to another.
239 /// \param iId [in] The index with box iBox of the atom to be moved.
240 /// \param iBox [in] The index of the link cell the particle is moving from.
241 /// \param jBox [in] The index of the link cell the particle is moving to.
242 void moveAtom(LinkCell* boxes, Atoms* atoms, int iId, int iBox, int jBox)
243 {
244  int nj = boxes->nAtoms[jBox];
245  copyAtom(boxes, atoms, iId, iBox, nj, jBox);
246  boxes->nAtoms[jBox]++;
247 
248  assert(boxes->nAtoms[jBox] < MAXATOMS);
249 
250  boxes->nAtoms[iBox]--;
251  int ni = boxes->nAtoms[iBox];
252  if (ni) copyAtom(boxes, atoms, ni, iBox, iId, iBox);
253 
254  if (jBox > boxes->nLocalBoxes)
255  --atoms->nLocal;
256 
257  return;
258 }
259 
260 /// \details
261 /// This is the first step in returning data structures to a consistent
262 /// state after the atoms move each time step. First we discard all
263 /// atoms in the halo link cells. These are all atoms that are
264 /// currently stored on other ranks and so any information we have about
265 /// them is stale. Next, we move any atoms that have crossed link cell
266 /// boundaries into their new link cells. It is likely that some atoms
267 /// will be moved into halo link cells. Since we have deleted halo
268 /// atoms from other tasks, it is clear that any atoms that are in halo
269 /// cells at the end of this routine have just transitioned from local
270 /// to halo atoms. Such atom must be sent to other tasks by a halo
271 /// exchange to avoid being lost.
272 /// \see redistributeAtoms
273 void updateLinkCells(LinkCell* boxes, Atoms* atoms)
274 {
275  emptyHaloCells(boxes);
276 
277  for (int iBox=0; iBox<boxes->nLocalBoxes; ++iBox)
278  {
279  int iOff = iBox*MAXATOMS;
280  int ii=0;
281  while (ii < boxes->nAtoms[iBox])
282  {
283  int jBox = getBoxFromCoord(boxes, atoms->r[iOff+ii]);
284  if (jBox != iBox)
285  moveAtom(boxes, atoms, ii, iBox, jBox);
286  else
287  ++ii;
288  }
289  }
290 }
291 
292 /// \return The largest number of atoms in any link cell.
294 {
295  int localMax = 0;
296  for (int ii=0; ii<boxes->nLocalBoxes; ++ii)
297  localMax = MAX(localMax, boxes->nAtoms[ii]);
298 
299  int globalMax;
300 
302  maxIntParallel(&localMax, &globalMax, 1);
304 
305  return globalMax;
306 }
307 
308 /// Copy atom iAtom in link cell iBox to atom jAtom in link cell jBox.
309 /// Any data at jAtom, jBox is overwritten. This routine can be used to
310 /// re-order atoms within a link cell.
311 void copyAtom(LinkCell* boxes, Atoms* atoms, int iAtom, int iBox, int jAtom, int jBox)
312 {
313  const int iOff = MAXATOMS*iBox+iAtom;
314  const int jOff = MAXATOMS*jBox+jAtom;
315  atoms->gid[jOff] = atoms->gid[iOff];
316  atoms->iSpecies[jOff] = atoms->iSpecies[iOff];
317  memcpy(atoms->r[jOff], atoms->r[iOff], sizeof(real3));
318  memcpy(atoms->p[jOff], atoms->p[iOff], sizeof(real3));
319  memcpy(atoms->f[jOff], atoms->f[iOff], sizeof(real3));
320  memcpy(atoms->U+jOff, atoms->U+iOff, sizeof(real_t));
321 }
322 
323 /// Get the index of the link cell that contains the specified
324 /// coordinate. This can be either a halo or a local link cell.
325 ///
326 /// Because the rank ownership of an atom is strictly determined by the
327 /// atom's position, we need to take care that all ranks will agree which
328 /// rank owns an atom. The conditionals at the end of this function are
329 /// special care to ensure that all ranks make compatible link cell
330 /// assignments for atoms that are near a link cell boundaries. If no
331 /// ranks claim an atom in a local cell it will be lost. If multiple
332 /// ranks claim an atom it will be duplicated.
333 int getBoxFromCoord(LinkCell* boxes, real_t rr[3])
334 {
335  const real_t* localMin = boxes->localMin; // alias
336  const real_t* localMax = boxes->localMax; // alias
337  const int* gridSize = boxes->gridSize; // alias
338  int ix = (int)(floor((rr[0] - localMin[0])*boxes->invBoxSize[0]));
339  int iy = (int)(floor((rr[1] - localMin[1])*boxes->invBoxSize[1]));
340  int iz = (int)(floor((rr[2] - localMin[2])*boxes->invBoxSize[2]));
341 
342 
343  // For each axis, if we are inside the local domain, make sure we get
344  // a local link cell. Otherwise, make sure we get a halo link cell.
345  if (rr[0] < localMax[0])
346  {
347  if (ix == gridSize[0]) ix = gridSize[0] - 1;
348  }
349  else
350  ix = gridSize[0]; // assign to halo cell
351  if (rr[1] < localMax[1])
352  {
353  if (iy == gridSize[1]) iy = gridSize[1] - 1;
354  }
355  else
356  iy = gridSize[1];
357  if (rr[2] < localMax[2])
358  {
359  if (iz == gridSize[2]) iz = gridSize[2] - 1;
360  }
361  else
362  iz = gridSize[2];
363 
364  return getBoxFromTuple(boxes, ix, iy, iz);
365 }
366 
367 /// Set the number of atoms to zero in all halo link cells.
369 {
370  for (int ii=boxes->nLocalBoxes; ii<boxes->nTotalBoxes; ++ii)
371  boxes->nAtoms[ii] = 0;
372 }
373 
374 /// Get the grid coordinates of the link cell with index iBox. Local
375 /// cells are easy as they use a standard 1D->3D mapping. Halo cell are
376 /// special cases.
377 /// \see initLinkCells for information on link cell order.
378 /// \param [in] iBox Index to link cell for which tuple is needed.
379 /// \param [out] ixp x grid coord of link cell.
380 /// \param [out] iyp y grid coord of link cell.
381 /// \param [out] izp z grid coord of link cell.
382 void getTuple(LinkCell* boxes, int iBox, int* ixp, int* iyp, int* izp)
383 {
384  int ix, iy, iz;
385  const int* gridSize = boxes->gridSize; // alias
386 
387  // If a local box
388  if( iBox < boxes->nLocalBoxes)
389  {
390  ix = iBox % gridSize[0];
391  iBox /= gridSize[0];
392  iy = iBox % gridSize[1];
393  iz = iBox / gridSize[1];
394  }
395  // It's a halo box
396  else
397  {
398  int ink;
399  ink = iBox - boxes->nLocalBoxes;
400  if (ink < 2*gridSize[1]*gridSize[2])
401  {
402  if (ink < gridSize[1]*gridSize[2])
403  {
404  ix = 0;
405  }
406  else
407  {
408  ink -= gridSize[1]*gridSize[2];
409  ix = gridSize[0] + 1;
410  }
411  iy = 1 + ink % gridSize[1];
412  iz = 1 + ink / gridSize[1];
413  }
414  else if (ink < (2 * gridSize[2] * (gridSize[1] + gridSize[0] + 2)))
415  {
416  ink -= 2 * gridSize[2] * gridSize[1];
417  if (ink < ((gridSize[0] + 2) *gridSize[2]))
418  {
419  iy = 0;
420  }
421  else
422  {
423  ink -= (gridSize[0] + 2) * gridSize[2];
424  iy = gridSize[1] + 1;
425  }
426  ix = ink % (gridSize[0] + 2);
427  iz = 1 + ink / (gridSize[0] + 2);
428  }
429  else
430  {
431  ink -= 2 * gridSize[2] * (gridSize[1] + gridSize[0] + 2);
432  if (ink < ((gridSize[0] + 2) * (gridSize[1] + 2)))
433  {
434  iz = 0;
435  }
436  else
437  {
438  ink -= (gridSize[0] + 2) * (gridSize[1] + 2);
439  iz = gridSize[2] + 1;
440  }
441  ix = ink % (gridSize[0] + 2);
442  iy = ink / (gridSize[0] + 2);
443  }
444 
445  // Calculated as off by 1
446  ix--;
447  iy--;
448  iz--;
449  }
450 
451  *ixp = ix;
452  *iyp = iy;
453  *izp = iz;
454 }
455 
456