CpuNeighborList.cpp 20 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* -------------------------------------------------------------------------- *
 *                                   OpenMM                                   *
 * -------------------------------------------------------------------------- *
 * This is part of the OpenMM molecular simulation toolkit originating from   *
 * Simbios, the NIH National Center for Physics-Based Simulation of           *
 * Biological Structures at Stanford, funded under the NIH Roadmap for        *
 * Medical Research, grant U54 GM072970. See https://simtk.org.               *
 *                                                                            *
 * Portions copyright (c) 2013 Stanford University and the Authors.           *
 * Authors: Peter Eastman                                                     *
 * Contributors:                                                              *
 *                                                                            *
 * Permission is hereby granted, free of charge, to any person obtaining a    *
 * copy of this software and associated documentation files (the "Software"), *
 * to deal in the Software without restriction, including without limitation  *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
 * and/or sell copies of the Software, and to permit persons to whom the      *
 * Software is furnished to do so, subject to the following conditions:       *
 *                                                                            *
 * The above copyright notice and this permission notice shall be included in *
 * all copies or substantial portions of the Software.                        *
 *                                                                            *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,    *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR      *
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE  *
 * USE OR OTHER DEALINGS IN THE SOFTWARE.                                     *
 * -------------------------------------------------------------------------- */

32
#include "CpuNeighborList.h"
33
#include "openmm/internal/hardware.h"
34
#include "openmm/internal/vectorize.h"
35
#include "hilbert.h"
36
#include <algorithm>
37
38
39
40
41
42
43
44
45
46
47
#include <set>
#include <map>
#include <cmath>

using namespace std;

namespace OpenMM {

class VoxelIndex 
{
public:
48
49
    VoxelIndex() : x(0), y(0) {
    }
50
    VoxelIndex(int x, int y) : x(x), y(y) {
51
    }
52
53
54
55
    int x;
    int y;
};

56
57
58
59
60
/**
 * This data structure organizes the particles spatially.  It divides them into bins along the x and y axes,
 * then sorts each bin along the z axis so ranges can be identified quickly with a binary search.
 */
class CpuNeighborList::Voxels {
61
public:
62
63
    Voxels(int blockSize, float vsx, float vsy, float minx, float maxx, float miny, float maxy, const float* periodicBoxSize, bool usePeriodic) :
            blockSize(blockSize), voxelSizeX(vsx), voxelSizeY(vsy), minx(minx), maxx(maxx), miny(miny), maxy(maxy), periodicBoxSize(periodicBoxSize), usePeriodic(usePeriodic) {
64
65
66
        if (usePeriodic) {
            nx = (int) floorf(periodicBoxSize[0]/voxelSizeX+0.5f);
            ny = (int) floorf(periodicBoxSize[1]/voxelSizeY+0.5f);
67
            voxelSizeX = periodicBoxSize[0]/nx;
peastman's avatar
Bug fix  
peastman committed
68
            voxelSizeY = periodicBoxSize[1]/ny;
69
70
71
72
73
74
75
76
77
78
79
80
81
82
        }
        else {
            nx = max(1, (int) floorf((maxx-minx)/voxelSizeX+0.5f));
            ny = max(1, (int) floorf((maxy-miny)/voxelSizeY+0.5f));
            if (maxx > minx)
                voxelSizeX = (maxx-minx)/nx;
            if (maxy > miny)
                voxelSizeY = (maxy-miny)/ny;
        }
        bins.resize(nx);
        for (int i = 0; i < nx; i++) {
            bins[i].resize(ny);
            for (int j = 0; j < ny; j++)
                bins[i][j].resize(0);
83
84
85
        }
    }

86
87
88
89
    /**
     * Insert a particle into the voxel data structure.
     */
    void insert(const int& atom, const float* location) {
90
        VoxelIndex voxelIndex = getVoxelIndex(location);
peastman's avatar
peastman committed
91
        bins[voxelIndex.x][voxelIndex.y].push_back(make_pair(location[2], atom));
92
93
94
95
96
97
98
99
100
    }
    
    /**
     * Sort the particles in each voxel by z coordinate.
     */
    void sortItems() {
        for (int i = 0; i < nx; i++)
            for (int j = 0; j < ny; j++)
                sort(bins[i][j].begin(), bins[i][j].end());
101
    }
102
    
103
104
105
106
    /**
     * Find the index of the first particle in voxel (x,y) whose z coordinate in >= the specified value.
     */
    int findLowerBound(int x, int y, double z) const {
peastman's avatar
peastman committed
107
        const vector<pair<float, int> >& bin = bins[x][y];
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
        int lower = 0;
        int upper = bin.size();
        while (lower < upper) {
            int middle = (lower+upper)/2;
            if (bin[middle].first < z)
                lower = middle+1;
            else
                upper = middle;
        }
        return lower;
    }
    
    /**
     * Find the index of the first particle in voxel (x,y) whose z coordinate in greater than the specified value.
     */
    int findUpperBound(int x, int y, double z) const {
peastman's avatar
peastman committed
124
        const vector<pair<float, int> >& bin = bins[x][y];
125
126
127
128
129
130
131
132
133
134
135
        int lower = 0;
        int upper = bin.size();
        while (lower < upper) {
            int middle = (lower+upper)/2;
            if (bin[middle].first > z)
                upper = middle;
            else
                lower = middle+1;
        }
        return upper;
    }
136

137
138
139
    /**
     * Get the voxel index containing a particular location.
     */
140
    VoxelIndex getVoxelIndex(const float* location) const {
141
        float xperiodic, yperiodic;
142
        if (!usePeriodic) {
143
144
            xperiodic = location[0]-minx;
            yperiodic = location[1]-miny;
145
146
147
148
149
        }
        else {
            xperiodic = location[0]-periodicBoxSize[0]*floorf(location[0]/periodicBoxSize[0]);
            yperiodic = location[1]-periodicBoxSize[1]*floorf(location[1]/periodicBoxSize[1]);
        }
150
151
        int x = min(nx-1, int(floorf(xperiodic / voxelSizeX)));
        int y = min(ny-1, int(floorf(yperiodic / voxelSizeY)));
152
        
153
        return VoxelIndex(x, y);
154
155
    }

156
    void getNeighbors(vector<int>& neighbors, int blockIndex, const fvec4& blockCenter, const fvec4& blockWidth, const vector<int>& sortedAtoms, vector<char>& exclusions, float maxDistance, const vector<int>& blockAtoms, const float* atomLocations, const vector<VoxelIndex>& atomVoxelIndex) const {
157
158
        neighbors.resize(0);
        exclusions.resize(0);
159
160
        fvec4 boxSize(periodicBoxSize[0], periodicBoxSize[1], periodicBoxSize[2], 0);
        fvec4 invBoxSize(1/periodicBoxSize[0], 1/periodicBoxSize[1], 1/periodicBoxSize[2], 0);
161
162
        
        float maxDistanceSquared = maxDistance * maxDistance;
163
164
        float refineCutoff = maxDistance-max(max(blockWidth[0], blockWidth[1]), blockWidth[2]);
        float refineCutoffSquared = refineCutoff*refineCutoff;
165

peastman's avatar
peastman committed
166
167
168
169
170
171
        int dIndexX = int((maxDistance+blockWidth[0])/voxelSizeX)+1; // How may voxels away do we have to look?
        int dIndexY = int((maxDistance+blockWidth[1])/voxelSizeY)+1;
        if (usePeriodic) {
            dIndexX = min(nx/2, dIndexX);
            dIndexY = min(ny/2, dIndexY);
        }
172
173
174
        float centerPos[4];
        blockCenter.store(centerPos);
        VoxelIndex centerVoxelIndex = getVoxelIndex(centerPos);
175
176
177
178
179
        int startx = centerVoxelIndex.x-dIndexX;
        int starty = centerVoxelIndex.y-dIndexY;
        int endx = centerVoxelIndex.x+dIndexX;
        int endy = centerVoxelIndex.y+dIndexY;
        int numRanges;
180
        if (usePeriodic) {
181
182
183
184
185
186
187
188
            endx = min(endx, centerVoxelIndex.x-dIndexX+nx-1);
            endy = min(endy, centerVoxelIndex.y-dIndexY+ny-1);
        }
        else {
            startx = max(startx, 0);
            starty = max(starty, 0);
            endx = min(endx, nx-1);
            endy = min(endy, ny-1);
189
        }
190
        int lastSortedIndex = blockSize*(blockIndex+1);
191
192
        VoxelIndex voxelIndex(0, 0);
        for (int x = startx; x <= endx; ++x) {
193
194
195
            voxelIndex.x = x;
            if (usePeriodic)
                voxelIndex.x = (x < 0 ? x+nx : (x >= nx ? x-nx : x));
196
            for (int y = starty; y <= endy; ++y) {
197
198
199
                voxelIndex.y = y;
                if (usePeriodic)
                    voxelIndex.y = (y < 0 ? y+ny : (y >= ny ? y-ny : y));
200
201
202
203
                
                // Identify the range of atoms within this bin we need to search.  When using periodic boundary
                // conditions, there may be two separate ranges.
                
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
                float minz = centerPos[2];
                float maxz = centerPos[2];
                fvec4 offset(voxelSizeX*x+(usePeriodic ? 0.0f : minx), voxelSizeY*y+(usePeriodic ? 0.0f : miny), 0, 0);
                for (int k = 0; k < (int) blockAtoms.size(); k++) {
                    const float* atomPos = &atomLocations[4*blockAtoms[k]];
                    fvec4 posVec(atomPos);
                    fvec4 delta1 = offset-posVec;
                    fvec4 delta2 = delta1+fvec4(voxelSizeX, voxelSizeY, 0, 0);
                    if (usePeriodic) {
                        delta1 -= round(delta1*invBoxSize)*boxSize;
                        delta2 -= round(delta2*invBoxSize)*boxSize;
                    }
                    fvec4 delta = min(abs(delta1), abs(delta2));
                    float dx = (x == atomVoxelIndex[k].x ? 0.0f : delta[0]);
                    float dy = (y == atomVoxelIndex[k].y ? 0.0f : delta[1]);
                    float dist2 = maxDistanceSquared-dx*dx-dy*dy;
                    if (dist2 > 0) {
                        float dist = sqrtf(dist2);
                        minz = min(minz, atomPos[2]-dist);
                        maxz = max(maxz, atomPos[2]+dist);
                    }
                }
peastman's avatar
peastman committed
226
227
                if (minz == maxz)
                    continue;
peastman's avatar
peastman committed
228
229
                bool needPeriodic = (centerPos[0]-blockWidth[0] < maxDistance || centerPos[0]+blockWidth[0] > periodicBoxSize[0]-maxDistance ||
                                     centerPos[1]-blockWidth[1] < maxDistance || centerPos[1]+blockWidth[1] > periodicBoxSize[1]-maxDistance ||
230
                                     minz < 0.0f || maxz > periodicBoxSize[2]);
231
232
                int rangeStart[2];
                int rangeEnd[2];
233
                rangeStart[0] = findLowerBound(voxelIndex.x, voxelIndex.y, minz);
234
235
                if (needPeriodic) {
                    numRanges = 2;
236
                    rangeEnd[0] = findUpperBound(voxelIndex.x, voxelIndex.y, maxz);
237
238
                    if (rangeStart[0] > 0) {
                        rangeStart[1] = 0;
239
                        rangeEnd[1] = min(findUpperBound(voxelIndex.x, voxelIndex.y, maxz-periodicBoxSize[2]), rangeStart[0]);
240
241
                    }
                    else {
242
                        rangeStart[1] = max(findLowerBound(voxelIndex.x, voxelIndex.y, minz+periodicBoxSize[2]), rangeEnd[0]);
243
244
245
                        rangeEnd[1] = bins[voxelIndex.x][voxelIndex.y].size();
                    }
                }
246
247
                else {
                    numRanges = 1;
248
                    rangeEnd[0] = findUpperBound(voxelIndex.x, voxelIndex.y, maxz);
249
                }
250
251
252
253
254
                
                // Loop over atoms and check to see if they are neighbors of this block.
                
                for (int range = 0; range < numRanges; range++) {
                    for (int item = rangeStart[range]; item < rangeEnd[range]; item++) {
peastman's avatar
peastman committed
255
                        const int sortedIndex = bins[voxelIndex.x][voxelIndex.y][item].second;
256

peastman's avatar
peastman committed
257
                        // Avoid duplicate entries.
258
                        if (sortedIndex >= lastSortedIndex)
259
                            continue;
260
                        
peastman's avatar
peastman committed
261
                        fvec4 atomPos(atomLocations+4*sortedAtoms[sortedIndex]);
262
                        fvec4 delta = atomPos-centerPos;
263
                        if (needPeriodic) {
264
265
                            fvec4 base = round(delta*invBoxSize)*boxSize;
                            delta = delta-base;
266
                        }
267
                        delta = max(0.0f, abs(delta)-blockWidth);
268
                        float dSquared = dot3(delta, delta);
269
270
                        if (dSquared > maxDistanceSquared)
                            continue;
peastman's avatar
peastman committed
271
                        
272
273
274
275
276
277
278
279
                        if (dSquared > refineCutoffSquared) {
                            // The distance is large enough that there might not be any actual interactions.
                            // Check individual atom pairs to be sure.
                            
                            bool any = false;
                            for (int k = 0; k < (int) blockAtoms.size(); k++) {
                                fvec4 pos1(&atomLocations[4*blockAtoms[k]]);
                                delta = atomPos-pos1;
280
                                if (needPeriodic) {
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
                                    fvec4 base = round(delta*invBoxSize)*boxSize;
                                    delta = delta-base;
                                }
                                float r2 = dot3(delta, delta);
                                if (r2 < maxDistanceSquared) {
                                    any = true;
                                    break;
                                }
                            }
                            if (!any)
                                continue;
                        }
                        
                        // Add this atom to the list of neighbors.
                        
                        neighbors.push_back(sortedAtoms[sortedIndex]);
297
                        if (sortedIndex < blockSize*blockIndex)
298
                            exclusions.push_back(0);
299
300
301
302
                        else {
                            int mask = (1<<blockSize)-1;
                            exclusions.push_back(mask & (mask<<(sortedIndex-blockSize*blockIndex)));
                        }
303
304
305
306
307
308
309
                    }
                }
            }
        }
    }

private:
310
    int blockSize;
311
312
313
    float voxelSizeX, voxelSizeY;
    float minx, maxx, miny, maxy;
    int nx, ny;
314
315
    const float* periodicBoxSize;
    const bool usePeriodic;
peastman's avatar
peastman committed
316
    vector<vector<vector<pair<float, int> > > > bins;
317
318
};

319
class CpuNeighborList::ThreadTask : public ThreadPool::Task {
320
public:
321
322
323
324
    ThreadTask(CpuNeighborList& owner) : owner(owner) {
    }
    void execute(ThreadPool& threads, int threadIndex) {
        owner.threadComputeNeighborList(threads, threadIndex);
325
326
327
    }
    CpuNeighborList& owner;
};
328

329
CpuNeighborList::CpuNeighborList(int blockSize) : blockSize(blockSize) {
330
}
331

332
void CpuNeighborList::computeNeighborList(int numAtoms, const AlignedArray<float>& atomLocations, const vector<set<int> >& exclusions,
333
            const float* periodicBoxSize, bool usePeriodic, float maxDistance, ThreadPool& threads) {
334
    int numBlocks = (numAtoms+blockSize-1)/blockSize;
335
336
337
338
    blockNeighbors.resize(numBlocks);
    blockExclusions.resize(numBlocks);
    sortedAtoms.resize(numAtoms);
    
339
    // Record the parameters for the threads.
340
    
341
342
343
344
345
346
347
348
349
350
351
    this->exclusions = &exclusions;
    this->atomLocations = &atomLocations[0];
    this->periodicBoxSize = periodicBoxSize;
    this->numAtoms = numAtoms;
    this->usePeriodic = usePeriodic;
    this->maxDistance = maxDistance;
    
    // Identify the range of atom positions along each axis.
    
    fvec4 minPos(&atomLocations[0]);
    fvec4 maxPos = minPos;
352
    for (int i = 0; i < numAtoms; i++) {
353
354
355
        fvec4 pos(&atomLocations[4*i]);
        minPos = min(minPos, pos);
        maxPos = max(maxPos, pos);
356
    }
357
358
359
360
361
362
363
364
365
366
    minx = minPos[0];
    maxx = maxPos[0];
    miny = minPos[1];
    maxy = maxPos[1];
    minz = minPos[2];
    maxz = maxPos[2];
    
    // Sort the atoms based on a Hilbert curve.
    
    atomBins.resize(numAtoms);
367
368
369
    ThreadTask task(*this);
    threads.execute(task);
    threads.waitForThreads();
370
371
    sort(atomBins.begin(), atomBins.end());

372
373
    // Build the voxel hash.
    
374
    float edgeSizeX, edgeSizeY;
375
    if (!usePeriodic)
376
        edgeSizeX = edgeSizeY = maxDistance; // TODO - adjust this as needed
377
    else {
378
379
        edgeSizeX = 0.6f*periodicBoxSize[0]/floorf(periodicBoxSize[0]/maxDistance);
        edgeSizeY = 0.6f*periodicBoxSize[1]/floorf(periodicBoxSize[1]/maxDistance);
380
    }
381
    Voxels voxels(blockSize, edgeSizeX, edgeSizeY, minx, maxx, miny, maxy, periodicBoxSize, usePeriodic);
382
383
384
    for (int i = 0; i < numAtoms; i++) {
        int atomIndex = atomBins[i].second;
        sortedAtoms[i] = atomIndex;
385
        voxels.insert(i, &atomLocations[4*atomIndex]);
386
    }
387
388
    voxels.sortItems();
    this->voxels = &voxels;
389
390
391
    
    // Signal the threads to start running and wait for them to finish.
    
392
393
    threads.resumeThreads();
    threads.waitForThreads();
394
    
395
    // Add padding atoms to fill up the last block.
396
    
397
    int numPadding = numBlocks*blockSize-numAtoms;
398
    if (numPadding > 0) {
399
        char mask = ((0xFFFF-(1<<blockSize)+1) >> numPadding);
400
401
402
403
404
405
        for (int i = 0; i < numPadding; i++)
            sortedAtoms.push_back(0);
        vector<char>& exc = blockExclusions[blockExclusions.size()-1];
        for (int i = 0; i < (int) exc.size(); i++)
            exc[i] |= mask;
    }
406
407
}

408
int CpuNeighborList::getNumBlocks() const {
409
    return sortedAtoms.size()/blockSize;
410
411
}

412
413
414
415
416
417
418
419
420
421
422
423
424
const std::vector<int>& CpuNeighborList::getSortedAtoms() const {
    return sortedAtoms;
}

const std::vector<int>& CpuNeighborList::getBlockNeighbors(int blockIndex) const {
    return blockNeighbors[blockIndex];
}

const std::vector<char>& CpuNeighborList::getBlockExclusions(int blockIndex) const {
    return blockExclusions[blockIndex];
    
}

425
426
void CpuNeighborList::threadComputeNeighborList(ThreadPool& threads, int threadIndex) {
    // Compute the positions of atoms along the Hilbert curve.
427

428
429
430
431
432
433
434
435
436
437
438
439
440
    float binWidth = max(max(maxx-minx, maxy-miny), maxz-minz)/255.0f;
    float invBinWidth = 1.0f/binWidth;
    bitmask_t coords[3];
    int numThreads = threads.getNumThreads();
    for (int i = threadIndex; i < numAtoms; i += numThreads) {
        const float* pos = &atomLocations[4*i];
        coords[0] = (bitmask_t) ((pos[0]-minx)*invBinWidth);
        coords[1] = (bitmask_t) ((pos[1]-miny)*invBinWidth);
        coords[2] = (bitmask_t) ((pos[2]-minz)*invBinWidth);
        int bin = (int) hilbert_c2i(3, 8, coords);
        atomBins[i] = pair<int, int>(bin, i);
    }
    threads.syncThreads();
441

442
443
444
445
    // Compute this thread's subset of neighbors.

    int numBlocks = blockNeighbors.size();
    vector<int> blockAtoms;
446
    vector<VoxelIndex> atomVoxelIndex;
447
448
449
    for (int i = threadIndex; i < numBlocks; i += numThreads) {
        // Find the atoms in this block and compute their bounding box.
        
450
451
        int firstIndex = blockSize*i;
        int atomsInBlock = min(blockSize, numAtoms-firstIndex);
452
        blockAtoms.resize(atomsInBlock);
453
454
        atomVoxelIndex.resize(atomsInBlock);
        for (int j = 0; j < atomsInBlock; j++) {
455
            blockAtoms[j] = sortedAtoms[firstIndex+j];
456
457
            atomVoxelIndex[j] = voxels->getVoxelIndex(&atomLocations[4*blockAtoms[j]]);
        }
458
459
460
461
462
463
        fvec4 minPos(&atomLocations[4*sortedAtoms[firstIndex]]);
        fvec4 maxPos = minPos;
        for (int j = 1; j < atomsInBlock; j++) {
            fvec4 pos(&atomLocations[4*sortedAtoms[firstIndex+j]]);
            minPos = min(minPos, pos);
            maxPos = max(maxPos, pos);
464
        }
465
        voxels->getNeighbors(blockNeighbors[i], i, (maxPos+minPos)*0.5f, (maxPos-minPos)*0.5f, sortedAtoms, blockExclusions[i], maxDistance, blockAtoms, atomLocations, atomVoxelIndex);
466

467
        // Record the exclusions for this block.
468

469
470
471
472
473
474
475
476
477
        for (int j = 0; j < atomsInBlock; j++) {
            const set<int>& atomExclusions = (*exclusions)[sortedAtoms[firstIndex+j]];
            char mask = 1<<j;
            for (int k = 0; k < (int) blockNeighbors[i].size(); k++) {
                int atomIndex = blockNeighbors[i][k];
                if (atomExclusions.find(atomIndex) != atomExclusions.end())
                    blockExclusions[i][k] |= mask;
            }
        }
478
479
480
    }
}

481
} // namespace OpenMM