"serialization/src/RBTorsionForceProxy.cpp" did not exist on "a416009a9f850944bf8cfb355712b2510182c02c"
CudaNonbondedUtilities.cpp 36 KB
Newer Older
1
2
3
4
5
6
7
8
/* -------------------------------------------------------------------------- *
 *                                   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.               *
 *                                                                            *
Peter Eastman's avatar
Peter Eastman committed
9
 * Portions copyright (c) 2009-2025 Stanford University and the Authors.      *
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 * Authors: Peter Eastman                                                     *
 * Contributors:                                                              *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify       *
 * it under the terms of the GNU Lesser General Public License as published   *
 * by the Free Software Foundation, either version 3 of the License, or       *
 * (at your option) any later version.                                        *
 *                                                                            *
 * This program is distributed in the hope that it will be useful,            *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
 * GNU Lesser General Public License for more details.                        *
 *                                                                            *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.      *
 * -------------------------------------------------------------------------- */

#include "openmm/OpenMMException.h"
#include "CudaNonbondedUtilities.h"
#include "CudaArray.h"
30
#include "CudaContext.h"
31
32
#include "CudaKernelSources.h"
#include "CudaExpressionUtilities.h"
33
#include <algorithm>
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <map>
#include <set>
#include <utility>

using namespace OpenMM;
using namespace std;

#define CHECK_RESULT(result) \
    if (result != CUDA_SUCCESS) { \
        std::stringstream m; \
        m<<errorMessage<<": "<<context.getErrorString(result)<<" ("<<result<<")"<<" at "<<__FILE__<<":"<<__LINE__; \
        throw OpenMMException(m.str());\
    }

48

49
class CudaNonbondedUtilities::BlockSortTrait : public ComputeSortImpl::SortTrait {
50
public:
51
52
53
54
55
56
57
58
59
    BlockSortTrait() {}
    int getDataSize() const {return sizeof(int);}
    int getKeySize() const {return sizeof(int);}
    const char* getDataType() const {return "unsigned int";}
    const char* getKeyType() const {return "unsigned int";}
    const char* getMinKey() const {return "0";}
    const char* getMaxKey() const {return "0xFFFFFFFFu";}
    const char* getMaxValue() const {return "0xFFFFFFFFu";}
    const char* getSortKey() const {return "value";}
60
61
};

62
CudaNonbondedUtilities::CudaNonbondedUtilities(CudaContext& context) : context(context), useCutoff(false), usePeriodic(false), useNeighborList(false), anyExclusions(false), usePadding(true),
63
        pinnedCountBuffer(NULL), forceRebuildNeighborList(true), groupFlags(0), canUsePairList(true), tilesAfterReorder(0) {
64
65
66
67
68
    // Decide how many thread blocks to use.

    string errorMessage = "Error initializing nonbonded utilities";
    int multiprocessors;
    CHECK_RESULT(cuDeviceGetAttribute(&multiprocessors, CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, context.getDevice()));
69
    CHECK_RESULT(cuEventCreate(&downloadCountEvent, context.getEventFlags()));
70
    CHECK_RESULT(cuMemHostAlloc((void**) &pinnedCountBuffer, 2*sizeof(unsigned int), CU_MEMHOSTALLOC_PORTABLE));
71
    numForceThreadBlocks = 4*multiprocessors;
72
    forceThreadBlockSize = (context.getComputeCapability() < 2.0 ? 128 : 256);
73
74
75
76
77
78
79
    
    // When building the neighbor list, we can optionally use large blocks (1024 atoms) to
    // accelerate the process.  This makes building the neighbor list faster, but it prevents
    // us from sorting atom blocks by size, which leads to a slightly less efficient neighbor
    // list.  We guess based on system size which will be faster.

    useLargeBlocks = (context.getNumAtoms() > 90000);
peastman's avatar
peastman committed
80
    setKernelSource(CudaKernelSources::nonbonded);
81
82
83
}

CudaNonbondedUtilities::~CudaNonbondedUtilities() {
84
85
86
    if (pinnedCountBuffer != NULL)
        cuMemFreeHost(pinnedCountBuffer);
    cuEventDestroy(downloadCountEvent);
87
88
}

89
90
void CudaNonbondedUtilities::addInteraction(bool usesCutoff, bool usesPeriodic, bool usesExclusions, double cutoffDistance,
        const vector<vector<int> >& exclusionList, const string& kernel, int forceGroup, bool useNeighborList, bool supportsPairList) {
91
    if (groupCutoff.size() > 0) {
92
93
94
95
        if (usesCutoff != useCutoff)
            throw OpenMMException("All Forces must agree on whether to use a cutoff");
        if (usesPeriodic != usePeriodic)
            throw OpenMMException("All Forces must agree on whether to use periodic boundary conditions");
96
97
        if (usesCutoff && groupCutoff.find(forceGroup) != groupCutoff.end() && groupCutoff[forceGroup] != cutoffDistance)
            throw OpenMMException("All Forces in a single force group must use the same cutoff distance");
98
99
100
101
102
    }
    if (usesExclusions)
        requestExclusions(exclusionList);
    useCutoff = usesCutoff;
    usePeriodic = usesPeriodic;
103
    this->useNeighborList |= (useNeighborList && useCutoff);
104
105
    groupCutoff[forceGroup] = cutoffDistance;
    groupFlags |= 1<<forceGroup;
106
    canUsePairList &= supportsPairList;
107
108
109
110
111
112
113
114
    if (kernel.size() > 0) {
        if (groupKernelSource.find(forceGroup) == groupKernelSource.end())
            groupKernelSource[forceGroup] = "";
        map<string, string> replacements;
        replacements["CUTOFF"] = "CUTOFF_"+context.intToString(forceGroup);
        replacements["CUTOFF_SQUARED"] = "CUTOFF_"+context.intToString(forceGroup)+"_SQUARED";
        groupKernelSource[forceGroup] += context.replaceStrings(kernel, replacements)+"\n";
    }
115
116
}

117
void CudaNonbondedUtilities::addParameter(ComputeParameterInfo parameter) {
118
119
120
    parameters.push_back(parameter);
}

121
void CudaNonbondedUtilities::addArgument(ComputeParameterInfo parameter) {
122
123
124
    arguments.push_back(parameter);
}

125
126
127
128
129
130
131
132
133
134
135
136
137
string CudaNonbondedUtilities::addEnergyParameterDerivative(const string& param) {
    // See if the parameter has already been added.
    
    int index;
    for (index = 0; index < energyParameterDerivatives.size(); index++)
        if (param == energyParameterDerivatives[index])
            break;
    if (index == energyParameterDerivatives.size())
        energyParameterDerivatives.push_back(param);
    context.addEnergyParameterDerivative(param);
    return string("energyParamDeriv")+context.intToString(index);
}

138
139
140
141
void CudaNonbondedUtilities::requestExclusions(const vector<vector<int> >& exclusionList) {
    if (anyExclusions) {
        bool sameExclusions = (exclusionList.size() == atomExclusions.size());
        for (int i = 0; i < (int) exclusionList.size() && sameExclusions; i++) {
142
143
144
145
146
147
148
             if (exclusionList[i].size() != atomExclusions[i].size())
                 sameExclusions = false;
            set<int> expectedExclusions;
            expectedExclusions.insert(atomExclusions[i].begin(), atomExclusions[i].end());
            for (int j = 0; j < (int) exclusionList[i].size(); j++)
                if (expectedExclusions.find(exclusionList[i][j]) == expectedExclusions.end())
                     sameExclusions = false;
149
150
151
152
153
154
155
156
157
158
        }
        if (!sameExclusions)
            throw OpenMMException("All Forces must have identical exceptions");
    }
    else {
        atomExclusions = exclusionList;
        anyExclusions = true;
    }
}

159
static bool compareInt2(int2 a, int2 b) {
160
161
162
    return ((a.y < b.y) || (a.y == b.y && a.x < b.x));
}

163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
void CudaNonbondedUtilities::initialize(const System& system) {
    string errorMessage = "Error initializing nonbonded utilities";    
    if (atomExclusions.size() == 0) {
        // No exclusions were specifically requested, so just mark every atom as not interacting with itself.
        
        atomExclusions.resize(context.getNumAtoms());
        for (int i = 0; i < (int) atomExclusions.size(); i++)
            atomExclusions[i].push_back(i);
    }

    // Create the list of tiles.

    numAtoms = context.getNumAtoms();
    int numAtomBlocks = context.getNumAtomBlocks();
    int numContexts = context.getPlatformData().contexts.size();
178
    setAtomBlockRange(context.getContextIndex()/(double) numContexts, (context.getContextIndex()+1)/(double) numContexts);
179

180
    // Build a list of tiles that contain exclusions.
181
182
183
184
185
186
187
188
189
190

    set<pair<int, int> > tilesWithExclusions;
    for (int atom1 = 0; atom1 < (int) atomExclusions.size(); ++atom1) {
        int x = atom1/CudaContext::TileSize;
        for (int j = 0; j < (int) atomExclusions[atom1].size(); ++j) {
            int atom2 = atomExclusions[atom1][j];
            int y = atom2/CudaContext::TileSize;
            tilesWithExclusions.insert(make_pair(max(x, y), min(x, y)));
        }
    }
191
    vector<int2> exclusionTilesVec;
192
    for (set<pair<int, int> >::const_iterator iter = tilesWithExclusions.begin(); iter != tilesWithExclusions.end(); ++iter)
193
194
195
        exclusionTilesVec.push_back(make_int2(iter->first, iter->second));
    sort(exclusionTilesVec.begin(), exclusionTilesVec.end(), compareInt2);
    exclusionTiles.initialize<int2>(context, exclusionTilesVec.size(), "exclusionTiles");
Peter Eastman's avatar
Peter Eastman committed
196
    exclusionTiles.upload(exclusionTilesVec);
197
198
    map<pair<int, int>, int> exclusionTileMap;
    for (int i = 0; i < (int) exclusionTilesVec.size(); i++) {
199
        int2 tile = exclusionTilesVec[i];
200
201
202
203
204
205
206
        exclusionTileMap[make_pair(tile.x, tile.y)] = i;
    }
    vector<vector<int> > exclusionBlocksForBlock(numAtomBlocks);
    for (set<pair<int, int> >::const_iterator iter = tilesWithExclusions.begin(); iter != tilesWithExclusions.end(); ++iter) {
        exclusionBlocksForBlock[iter->first].push_back(iter->second);
        if (iter->first != iter->second)
            exclusionBlocksForBlock[iter->second].push_back(iter->first);
207
208
209
    }
    vector<unsigned int> exclusionRowIndicesVec(numAtomBlocks+1, 0);
    vector<unsigned int> exclusionIndicesVec;
210
211
212
    for (int i = 0; i < numAtomBlocks; i++) {
        exclusionIndicesVec.insert(exclusionIndicesVec.end(), exclusionBlocksForBlock[i].begin(), exclusionBlocksForBlock[i].end());
        exclusionRowIndicesVec[i+1] = exclusionIndicesVec.size();
213
    }
214
215
216
    maxExclusions = 0;
    for (int i = 0; i < (int) exclusionBlocksForBlock.size(); i++)
        maxExclusions = (maxExclusions > exclusionBlocksForBlock[i].size() ? maxExclusions : exclusionBlocksForBlock[i].size());
Peter Eastman's avatar
Peter Eastman committed
217
218
219
220
    exclusionIndices.initialize<unsigned int>(context, exclusionIndicesVec.size(), "exclusionIndices");
    exclusionRowIndices.initialize<unsigned int>(context, exclusionRowIndicesVec.size(), "exclusionRowIndices");
    exclusionIndices.upload(exclusionIndicesVec);
    exclusionRowIndices.upload(exclusionRowIndicesVec);
221
222
223

    // Record the exclusion data.

Peter Eastman's avatar
Peter Eastman committed
224
    exclusions.initialize<tileflags>(context, tilesWithExclusions.size()*CudaContext::TileSize, "exclusions");
225
    tileflags allFlags = (tileflags) -1;
Peter Eastman's avatar
Peter Eastman committed
226
    vector<tileflags> exclusionVec(exclusions.getSize(), allFlags);
227
228
229
230
231
232
233
234
    for (int atom1 = 0; atom1 < (int) atomExclusions.size(); ++atom1) {
        int x = atom1/CudaContext::TileSize;
        int offset1 = atom1-x*CudaContext::TileSize;
        for (int j = 0; j < (int) atomExclusions[atom1].size(); ++j) {
            int atom2 = atomExclusions[atom1][j];
            int y = atom2/CudaContext::TileSize;
            int offset2 = atom2-y*CudaContext::TileSize;
            if (x > y) {
235
236
                int index = exclusionTileMap[make_pair(x, y)]*CudaContext::TileSize;
                exclusionVec[index+offset1] &= allFlags-(1<<offset2);
237
238
            }
            else {
239
240
                int index = exclusionTileMap[make_pair(y, x)]*CudaContext::TileSize;
                exclusionVec[index+offset2] &= allFlags-(1<<offset1);
241
242
243
244
            }
        }
    }
    atomExclusions.clear(); // We won't use this again, so free the memory it used
Peter Eastman's avatar
Peter Eastman committed
245
    exclusions.upload(exclusionVec);
246
247
248

    // Create data structures for the neighbor list.

Peter Eastman's avatar
Peter Eastman committed
249
    maxCutoff = getMaxCutoffDistance();
250
    if (useCutoff) {
251
252
        // Select a size for the arrays that hold the neighbor list.  We have to make a fairly
        // arbitrary guess, but if this turns out to be too small we'll increase it later.
253

254
        maxTiles = 20*numAtomBlocks;
255
256
257
258
        if (maxTiles > numTiles)
            maxTiles = numTiles;
        if (maxTiles < 1)
            maxTiles = 1;
259
        maxSinglePairs = 5*numAtoms;
Peter Eastman's avatar
Peter Eastman committed
260
261
262
263
        interactingTiles.initialize<int>(context, maxTiles, "interactingTiles");
        interactingAtoms.initialize<int>(context, CudaContext::TileSize*maxTiles, "interactingAtoms");
        interactionCount.initialize<unsigned int>(context, 2, "interactionCount");
        singlePairs.initialize<int2>(context, maxSinglePairs, "singlePairs");
264
        int elementSize = (context.getUseDoublePrecision() ? sizeof(double) : sizeof(float));
Peter Eastman's avatar
Peter Eastman committed
265
266
        blockCenter.initialize(context, numAtomBlocks, 4*elementSize, "blockCenter");
        blockBoundingBox.initialize(context, numAtomBlocks, 4*elementSize, "blockBoundingBox");
267
        sortedBlocks.initialize<unsigned int>(context, numAtomBlocks, "sortedBlocks");
Peter Eastman's avatar
Peter Eastman committed
268
269
        sortedBlockCenter.initialize(context, numAtomBlocks+1, 4*elementSize, "sortedBlockCenter");
        sortedBlockBoundingBox.initialize(context, numAtomBlocks+1, 4*elementSize, "sortedBlockBoundingBox");
270
271
        numBlockSizes = min((context.getNumAtomBlocks()+63)/64, context.getNumThreadBlocks());
        blockSizeRange.initialize(context, numBlockSizes, 2*elementSize, "blockSizeRange");
272
273
        largeBlockCenter.initialize(context, numAtomBlocks, 4*elementSize, "largeBlockCenter");
        largeBlockBoundingBox.initialize(context, numAtomBlocks, 4*elementSize, "largeBlockBoundingBox");
Peter Eastman's avatar
Peter Eastman committed
274
275
        oldPositions.initialize(context, numAtoms, 4*elementSize, "oldPositions");
        rebuildNeighborList.initialize<int>(context, 1, "rebuildNeighborList");
276
        blockSorter = context.createSort(new BlockSortTrait(), numAtomBlocks, false);
277
        vector<unsigned int> count(2, 0);
Peter Eastman's avatar
Peter Eastman committed
278
        interactionCount.upload(count);
Peter Eastman's avatar
Bug fix  
Peter Eastman committed
279
        rebuildNeighborList.upload(&count[0]);
280
281
    }

282
    // Record arguments for kernels.
283

284
285
286
    forceArgs.push_back(&context.getForce().getDevicePointer());
    forceArgs.push_back(&context.getEnergyBuffer().getDevicePointer());
    forceArgs.push_back(&context.getPosq().getDevicePointer());
Peter Eastman's avatar
Peter Eastman committed
287
288
    forceArgs.push_back(&exclusions.getDevicePointer());
    forceArgs.push_back(&exclusionTiles.getDevicePointer());
289
290
291
    forceArgs.push_back(&startTileIndex);
    forceArgs.push_back(&numTiles);
    if (useCutoff) {
Peter Eastman's avatar
Peter Eastman committed
292
293
        forceArgs.push_back(&interactingTiles.getDevicePointer());
        forceArgs.push_back(&interactionCount.getDevicePointer());
294
295
296
297
298
299
        forceArgs.push_back(context.getPeriodicBoxSizePointer());
        forceArgs.push_back(context.getInvPeriodicBoxSizePointer());
        forceArgs.push_back(context.getPeriodicBoxVecXPointer());
        forceArgs.push_back(context.getPeriodicBoxVecYPointer());
        forceArgs.push_back(context.getPeriodicBoxVecZPointer());
        forceArgs.push_back(&maxTiles);
Peter Eastman's avatar
Peter Eastman committed
300
301
302
        forceArgs.push_back(&blockCenter.getDevicePointer());
        forceArgs.push_back(&blockBoundingBox.getDevicePointer());
        forceArgs.push_back(&interactingAtoms.getDevicePointer());
303
        forceArgs.push_back(&maxSinglePairs);
Peter Eastman's avatar
Peter Eastman committed
304
        forceArgs.push_back(&singlePairs.getDevicePointer());
305
    }
306
307
308
309
    hasInitializedParams = false;
    paramStartIndex = forceArgs.size();
    for (int i = 0; i < parameters.size()+arguments.size(); i++)
        forceArgs.push_back(NULL);
310
311
    if (energyParameterDerivatives.size() > 0)
        forceArgs.push_back(&context.getEnergyParamDerivBuffer().getDevicePointer());
312
313
314
315
    if (useCutoff) {
        findBlockBoundsArgs.push_back(&numAtoms);
        findBlockBoundsArgs.push_back(context.getPeriodicBoxSizePointer());
        findBlockBoundsArgs.push_back(context.getInvPeriodicBoxSizePointer());
316
317
318
        findBlockBoundsArgs.push_back(context.getPeriodicBoxVecXPointer());
        findBlockBoundsArgs.push_back(context.getPeriodicBoxVecYPointer());
        findBlockBoundsArgs.push_back(context.getPeriodicBoxVecZPointer());
319
        findBlockBoundsArgs.push_back(&context.getPosq().getDevicePointer());
Peter Eastman's avatar
Peter Eastman committed
320
321
322
        findBlockBoundsArgs.push_back(&blockCenter.getDevicePointer());
        findBlockBoundsArgs.push_back(&blockBoundingBox.getDevicePointer());
        findBlockBoundsArgs.push_back(&rebuildNeighborList.getDevicePointer());
323
324
325
326
327
        findBlockBoundsArgs.push_back(&blockSizeRange.getDevicePointer());
        computeSortKeysArgs.push_back(&blockBoundingBox.getDevicePointer());
        computeSortKeysArgs.push_back(&sortedBlocks.getDevicePointer());
        computeSortKeysArgs.push_back(&blockSizeRange.getDevicePointer());
        computeSortKeysArgs.push_back(&numBlockSizes);
Peter Eastman's avatar
Peter Eastman committed
328
329
330
331
332
        sortBoxDataArgs.push_back(&sortedBlocks.getDevicePointer());
        sortBoxDataArgs.push_back(&blockCenter.getDevicePointer());
        sortBoxDataArgs.push_back(&blockBoundingBox.getDevicePointer());
        sortBoxDataArgs.push_back(&sortedBlockCenter.getDevicePointer());
        sortBoxDataArgs.push_back(&sortedBlockBoundingBox.getDevicePointer());
333
334
335
336
337
338
339
340
341
        if (useLargeBlocks) {
            sortBoxDataArgs.push_back(&largeBlockCenter.getDevicePointer());
            sortBoxDataArgs.push_back(&largeBlockBoundingBox.getDevicePointer());
            sortBoxDataArgs.push_back(context.getPeriodicBoxSizePointer());
            sortBoxDataArgs.push_back(context.getInvPeriodicBoxSizePointer());
            sortBoxDataArgs.push_back(context.getPeriodicBoxVecXPointer());
            sortBoxDataArgs.push_back(context.getPeriodicBoxVecYPointer());
            sortBoxDataArgs.push_back(context.getPeriodicBoxVecZPointer());
        }
342
        sortBoxDataArgs.push_back(&context.getPosq().getDevicePointer());
Peter Eastman's avatar
Peter Eastman committed
343
344
345
        sortBoxDataArgs.push_back(&oldPositions.getDevicePointer());
        sortBoxDataArgs.push_back(&interactionCount.getDevicePointer());
        sortBoxDataArgs.push_back(&rebuildNeighborList.getDevicePointer());
346
        sortBoxDataArgs.push_back(&forceRebuildNeighborList);
347
348
        findInteractingBlocksArgs.push_back(context.getPeriodicBoxSizePointer());
        findInteractingBlocksArgs.push_back(context.getInvPeriodicBoxSizePointer());
349
350
351
        findInteractingBlocksArgs.push_back(context.getPeriodicBoxVecXPointer());
        findInteractingBlocksArgs.push_back(context.getPeriodicBoxVecYPointer());
        findInteractingBlocksArgs.push_back(context.getPeriodicBoxVecZPointer());
Peter Eastman's avatar
Peter Eastman committed
352
353
354
355
        findInteractingBlocksArgs.push_back(&interactionCount.getDevicePointer());
        findInteractingBlocksArgs.push_back(&interactingTiles.getDevicePointer());
        findInteractingBlocksArgs.push_back(&interactingAtoms.getDevicePointer());
        findInteractingBlocksArgs.push_back(&singlePairs.getDevicePointer());
356
357
        findInteractingBlocksArgs.push_back(&context.getPosq().getDevicePointer());
        findInteractingBlocksArgs.push_back(&maxTiles);
358
        findInteractingBlocksArgs.push_back(&maxSinglePairs);
359
360
        findInteractingBlocksArgs.push_back(&startBlockIndex);
        findInteractingBlocksArgs.push_back(&numBlocks);
Peter Eastman's avatar
Peter Eastman committed
361
362
363
        findInteractingBlocksArgs.push_back(&sortedBlocks.getDevicePointer());
        findInteractingBlocksArgs.push_back(&sortedBlockCenter.getDevicePointer());
        findInteractingBlocksArgs.push_back(&sortedBlockBoundingBox.getDevicePointer());
364
365
366
367
        if (useLargeBlocks) {
            findInteractingBlocksArgs.push_back(&largeBlockCenter.getDevicePointer());
            findInteractingBlocksArgs.push_back(&largeBlockBoundingBox.getDevicePointer());
        }
Peter Eastman's avatar
Peter Eastman committed
368
369
370
371
        findInteractingBlocksArgs.push_back(&exclusionIndices.getDevicePointer());
        findInteractingBlocksArgs.push_back(&exclusionRowIndices.getDevicePointer());
        findInteractingBlocksArgs.push_back(&oldPositions.getDevicePointer());
        findInteractingBlocksArgs.push_back(&rebuildNeighborList.getDevicePointer());
372
373
374
    }
}

375
376
377
378
379
380
381
double CudaNonbondedUtilities::getMaxCutoffDistance() {
    double cutoff = 0.0;
    for (map<int, double>::const_iterator iter = groupCutoff.begin(); iter != groupCutoff.end(); ++iter)
        cutoff = max(cutoff, iter->second);
    return cutoff;
}

382
double CudaNonbondedUtilities::padCutoff(double cutoff) {
383
    double padding = (usePadding ? 0.08*cutoff : 0.0);
384
385
386
    return cutoff+padding;
}

387
388
389
390
391
392
void CudaNonbondedUtilities::prepareInteractions(int forceGroups) {
    if ((forceGroups&groupFlags) == 0)
        return;
    if (groupKernels.find(forceGroups) == groupKernels.end())
        createKernelsForGroups(forceGroups);
    KernelSet& kernels = groupKernels[forceGroups];
393
    if (useCutoff && usePeriodic) {
394
        double4 box = context.getPeriodicBoxSize();
Peter Eastman's avatar
Peter Eastman committed
395
        double minAllowedSize = 1.999999*maxCutoff;
396
397
398
        if (box.x < minAllowedSize || box.y < minAllowedSize || box.z < minAllowedSize)
            throw OpenMMException("The periodic box size has decreased to less than twice the nonbonded cutoff.");
    }
399
400
401
402
    if (!useNeighborList)
        return;
    if (numTiles == 0)
        return;
403
404
405

    // Compute the neighbor list.

406
407
408
    context.executeKernel(kernels.findBlockBoundsKernel, &findBlockBoundsArgs[0], context.getNumAtomBlocks());
    context.executeKernel(kernels.computeSortKeysKernel, &computeSortKeysArgs[0], context.getNumAtomBlocks());
    blockSorter->sort(sortedBlocks);
409
410
411
    context.executeKernel(kernels.sortBoxDataKernel, &sortBoxDataArgs[0], context.getNumAtoms());
    context.executeKernel(kernels.findInteractingBlocksKernel, &findInteractingBlocksArgs[0], context.getNumAtoms(), 256);
    forceRebuildNeighborList = false;
Peter Eastman's avatar
Peter Eastman committed
412
    interactionCount.download(pinnedCountBuffer, false);
413
    cuEventRecord(downloadCountEvent, context.getCurrentStream());
414
415
}

416
417
418
419
420
421
422
423
424
void CudaNonbondedUtilities::initParamArgs() {
    int index = paramStartIndex;
    for (ComputeParameterInfo& param : parameters)
        forceArgs[index++] = &context.unwrap(param.getArray()).getDevicePointer();
    for (ComputeParameterInfo& arg : arguments)
        forceArgs[index++] = &context.unwrap(arg.getArray()).getDevicePointer();
    hasInitializedParams = true;
}

425
void CudaNonbondedUtilities::computeInteractions(int forceGroups, bool includeForces, bool includeEnergy) {
426
427
428
429
    if ((forceGroups&groupFlags) == 0)
        return;
    KernelSet& kernels = groupKernels[forceGroups];
    if (kernels.hasForces) {
430
431
432
        CUfunction& kernel = (includeForces ? (includeEnergy ? kernels.forceEnergyKernel : kernels.forceKernel) : kernels.energyKernel);
        if (kernel == NULL)
            kernel = createInteractionKernel(kernels.source, parameters, arguments, true, true, forceGroups, includeForces, includeEnergy);
433
434
        if (!hasInitializedParams)
            initParamArgs();
435
        context.executeKernel(kernel, &forceArgs[0], numForceThreadBlocks*forceThreadBlockSize, forceThreadBlockSize);
436
    }
437
    if (useNeighborList && numTiles > 0) {
438
439
440
        cuEventSynchronize(downloadCountEvent);
        updateNeighborListSize();
    }
441
442
}

443
bool CudaNonbondedUtilities::updateNeighborListSize() {
444
    if (!useCutoff)
445
        return false;
446
    if (context.getStepsSinceReorder() == 0 || tilesAfterReorder == 0)
447
448
449
        tilesAfterReorder = pinnedCountBuffer[0];
    else if (context.getStepsSinceReorder() > 25 && pinnedCountBuffer[0] > 1.1*tilesAfterReorder)
        context.forceReorder();
450
    if (pinnedCountBuffer[0] <= maxTiles && pinnedCountBuffer[1] <= maxSinglePairs)
451
        return false;
452
453
454
455

    // The most recent timestep had too many interactions to fit in the arrays.  Make the arrays bigger to prevent
    // this from happening in the future.

456
    if (pinnedCountBuffer[0] > maxTiles) {
457
458
459
        maxTiles = (unsigned int) (1.2*pinnedCountBuffer[0]);
        unsigned int numBlocks = context.getNumAtomBlocks();
        int totalTiles = numBlocks*(numBlocks+1)/2;
460
461
        if (maxTiles > totalTiles)
            maxTiles = totalTiles;
Peter Eastman's avatar
Peter Eastman committed
462
        interactingTiles.resize(maxTiles);
463
        interactingAtoms.resize(CudaContext::TileSize*(size_t) maxTiles);
464
        if (forceArgs.size() > 0)
Peter Eastman's avatar
Peter Eastman committed
465
466
            forceArgs[7] = &interactingTiles.getDevicePointer();
        findInteractingBlocksArgs[6] = &interactingTiles.getDevicePointer();
467
        if (forceArgs.size() > 0)
Peter Eastman's avatar
Peter Eastman committed
468
469
            forceArgs[17] = &interactingAtoms.getDevicePointer();
        findInteractingBlocksArgs[7] = &interactingAtoms.getDevicePointer();
470
471
    }
    if (pinnedCountBuffer[1] > maxSinglePairs) {
472
        maxSinglePairs = (unsigned int) (1.2*pinnedCountBuffer[1]);
Peter Eastman's avatar
Peter Eastman committed
473
        singlePairs.resize(maxSinglePairs);
474
        if (forceArgs.size() > 0)
Peter Eastman's avatar
Peter Eastman committed
475
476
            forceArgs[19] = &singlePairs.getDevicePointer();
        findInteractingBlocksArgs[8] = &singlePairs.getDevicePointer();
477
    }
peastman's avatar
peastman committed
478
    forceRebuildNeighborList = true;
479
    context.setForcesValid(false);
480
    return true;
481
482
}

483
484
485
486
487
488
489
490
void CudaNonbondedUtilities::setUsePadding(bool padding) {
    usePadding = padding;
}

void CudaNonbondedUtilities::setAtomBlockRange(double startFraction, double endFraction) {
    int numAtomBlocks = context.getNumAtomBlocks();
    startBlockIndex = (int) (startFraction*numAtomBlocks);
    numBlocks = (int) (endFraction*numAtomBlocks)-startBlockIndex;
491
    long long totalTiles = context.getNumAtomBlocks()*((long long)context.getNumAtomBlocks()+1)/2;
492
    startTileIndex = (int) (startFraction*totalTiles);
493
    numTiles = (long long) (endFraction*totalTiles)-startTileIndex;
494
    forceRebuildNeighborList = true;
495
496
}

497
498
499
500
501
502
503
504
505
void CudaNonbondedUtilities::createKernelsForGroups(int groups) {
    KernelSet kernels;
    string source;
    for (int i = 0; i < 32; i++) {
        if ((groups&(1<<i)) != 0) {
            source += groupKernelSource[i];
        }
    }
    kernels.hasForces = (source.size() > 0);
506
507
    kernels.source = source;
    kernels.forceKernel = kernels.energyKernel = kernels.forceEnergyKernel = NULL;
508
    if (useCutoff) {
Peter Eastman's avatar
Peter Eastman committed
509
        double paddedCutoff = padCutoff(maxCutoff);
510
511
512
513
        map<string, string> defines;
        defines["TILE_SIZE"] = context.intToString(CudaContext::TileSize);
        defines["NUM_BLOCKS"] = context.intToString(context.getNumAtomBlocks());
        defines["NUM_ATOMS"] = context.intToString(context.getNumAtoms());
Peter Eastman's avatar
Peter Eastman committed
514
        defines["PADDING"] = context.doubleToString(paddedCutoff-maxCutoff);
515
516
        defines["PADDED_CUTOFF"] = context.doubleToString(paddedCutoff);
        defines["PADDED_CUTOFF_SQUARED"] = context.doubleToString(paddedCutoff*paddedCutoff);
Peter Eastman's avatar
Peter Eastman committed
517
        defines["NUM_TILES_WITH_EXCLUSIONS"] = context.intToString(exclusionTiles.getSize());
518
519
        if (usePeriodic)
            defines["USE_PERIODIC"] = "1";
520
521
        if (context.getBoxIsTriclinic())
            defines["TRICLINIC"] = "1";
522
523
        if (useLargeBlocks)
            defines["USE_LARGE_BLOCKS"] = "1";
524
        defines["MAX_EXCLUSIONS"] = context.intToString(maxExclusions);
525
        defines["MAX_BITS_FOR_PAIRS"] = (canUsePairList ? (context.getComputeCapability() < 8.0 ? "2" : "3") : "0");
526
527
528
529
530
        int binShift = 1;
        while (1<<binShift <= context.getNumAtomBlocks())
            binShift++;
        defines["BIN_SHIFT"] = context.intToString(binShift);
        defines["BLOCK_INDEX_MASK"] = context.intToString((1<<binShift)-1);
531
532
        CUmodule interactingBlocksProgram = context.createModule(CudaKernelSources::vectorOps+CudaKernelSources::findInteractingBlocks, defines);
        kernels.findBlockBoundsKernel = context.getKernel(interactingBlocksProgram, "findBlockBounds");
533
        kernels.computeSortKeysKernel = context.getKernel(interactingBlocksProgram, "computeSortKeys");
534
535
536
537
538
539
        kernels.sortBoxDataKernel = context.getKernel(interactingBlocksProgram, "sortBoxData");
        kernels.findInteractingBlocksKernel = context.getKernel(interactingBlocksProgram, "findBlocksWithInteractions");
    }
    groupKernels[groups] = kernels;
}

540
CUfunction CudaNonbondedUtilities::createInteractionKernel(const string& source, vector<ComputeParameterInfo>& params, vector<ComputeParameterInfo>& arguments, bool useExclusions, bool isSymmetric, int groups, bool includeForces, bool includeEnergy) {
541
542
543
544
545
    map<string, string> replacements;
    replacements["COMPUTE_INTERACTION"] = source;
    const string suffixes[] = {"x", "y", "z", "w"};
    stringstream localData;
    int localDataSize = 0;
546
    for (const ComputeParameterInfo& param : params) {
547
548
        if (param.getNumComponents() == 1)
            localData<<param.getType()<<" "<<param.getName()<<";\n";
549
        else {
550
551
            for (int j = 0; j < param.getNumComponents(); ++j)
                localData<<param.getComponentType()<<" "<<param.getName()<<"_"<<suffixes[j]<<";\n";
552
        }
553
        localDataSize += param.getSize();
554
555
556
    }
    replacements["ATOM_PARAMETER_DATA"] = localData.str();
    stringstream args;
557
    for (const ComputeParameterInfo& param : params) {
peastman's avatar
peastman committed
558
        args << ", ";
559
        if (param.isConstant())
peastman's avatar
peastman committed
560
            args << "const ";
561
        args << param.getType();
562
        args << "* __restrict__ global_";
563
        args << param.getName();
564
    }
565
    for (const ComputeParameterInfo& arg : arguments) {
peastman's avatar
peastman committed
566
        args << ", ";
567
        if (arg.isConstant())
peastman's avatar
peastman committed
568
            args << "const ";
569
        args << arg.getType();
570
        args << "* __restrict__ ";
571
        args << arg.getName();
572
    }
573
574
    if (energyParameterDerivatives.size() > 0)
        args << ", mixed* __restrict__ energyParamDerivs";
575
    replacements["PARAMETER_ARGUMENTS"] = args.str();
576

577
    stringstream load1;
578
    for (const ComputeParameterInfo& param : params) {
579
        load1 << param.getType();
580
        load1 << " ";
581
        load1 << param.getName();
582
        load1 << "1 = global_";
583
        load1 << param.getName();
584
        load1 << "[atom1];\n";
585
    }
586
587
588
    replacements["LOAD_ATOM1_PARAMETERS"] = load1.str();

    // Part 1. Defines for on diagonal exclusion tiles
589

590
    stringstream broadcastWarpData;
591
592
593
594
    broadcastWarpData << "posq2.x = real_shfl(shflPosq.x, j);\n";
    broadcastWarpData << "posq2.y = real_shfl(shflPosq.y, j);\n";
    broadcastWarpData << "posq2.z = real_shfl(shflPosq.z, j);\n";
    broadcastWarpData << "posq2.w = real_shfl(shflPosq.w, j);\n";
595
    for (const ComputeParameterInfo& param : params) {
596
597
598
599
600
601
        broadcastWarpData << param.getType() << " shfl" << param.getName() << ";\n";
        for (int j = 0; j < param.getNumComponents(); j++) {
            if (param.getNumComponents() == 1)
                broadcastWarpData << "shfl" << param.getName() << "=real_shfl(" << param.getName() <<"1,j);\n";
            else
                broadcastWarpData << "shfl" << param.getName()+"."+suffixes[j] << "=real_shfl(" << param.getName()+"1."+suffixes[j] <<",j);\n";
602
603
        }
    }
604
605
606
    replacements["BROADCAST_WARP_DATA"] = broadcastWarpData.str();
    
    // Part 2. Defines for off-diagonal exclusions, and neighborlist tiles. 
607
    stringstream declareLocal2;
608
    for (const ComputeParameterInfo& param : params)
609
        declareLocal2<<param.getType()<<" shfl"<<param.getName()<<";\n";
610
611
612
    replacements["DECLARE_LOCAL_PARAMETERS"] = declareLocal2.str();

    stringstream loadLocal2;
613
    for (const ComputeParameterInfo& param : params)
614
        loadLocal2<<"shfl"<<param.getName()<<" = global_"<<param.getName()<<"[j];\n";
615
    replacements["LOAD_LOCAL_PARAMETERS_FROM_GLOBAL"] = loadLocal2.str();
616
   
617
    stringstream load2j;
618
    for (const ComputeParameterInfo& param : params)
619
        load2j<<param.getType()<<" "<<param.getName()<<"2 = shfl"<<param.getName()<<";\n";
620
    replacements["LOAD_ATOM2_PARAMETERS"] = load2j.str();
621
622

    stringstream load2g;
623
    for (const ComputeParameterInfo& param : params)
624
625
        load2g<<param.getType()<<" "<<param.getName()<<"2 = global_"<<param.getName()<<"[atom2];\n";
    replacements["LOAD_ATOM2_PARAMETERS_FROM_GLOBAL"] = load2g.str();
626
627
    
    stringstream clearLocal;
628
    for (const ComputeParameterInfo& param : params) {
629
        clearLocal<<"shfl"<<param.getName()<<" = ";
630
        if (param.getNumComponents() == 1)
631
632
            clearLocal<<"0;\n";
        else
633
            clearLocal<<"make_"<<param.getType()<<"(0);\n";
634
635
636
    }
    replacements["CLEAR_LOCAL_PARAMETERS"] = clearLocal.str();

637
638
639
640
641
642
643
644
645
646
    stringstream initDerivs;
    for (int i = 0; i < energyParameterDerivatives.size(); i++)
        initDerivs<<"mixed energyParamDeriv"<<i<<" = 0;\n";
    replacements["INIT_DERIVATIVES"] = initDerivs.str();
    stringstream saveDerivs;
    const vector<string>& allParamDerivNames = context.getEnergyParamDerivNames();
    int numDerivs = allParamDerivNames.size();
    for (int i = 0; i < energyParameterDerivatives.size(); i++)
        for (int index = 0; index < numDerivs; index++)
            if (allParamDerivNames[index] == energyParameterDerivatives[i])
647
                saveDerivs<<"energyParamDerivs[GLOBAL_ID*"<<numDerivs<<"+"<<index<<"] += energyParamDeriv"<<i<<";\n";
648
    replacements["SAVE_DERIVATIVES"] = saveDerivs.str();
649

650
    stringstream shuffleWarpData;
651
652
653
654
655
656
657
    shuffleWarpData << "shflPosq.x = real_shfl(shflPosq.x, tgx+1);\n";
    shuffleWarpData << "shflPosq.y = real_shfl(shflPosq.y, tgx+1);\n";
    shuffleWarpData << "shflPosq.z = real_shfl(shflPosq.z, tgx+1);\n";
    shuffleWarpData << "shflPosq.w = real_shfl(shflPosq.w, tgx+1);\n";
    shuffleWarpData << "shflForce.x = real_shfl(shflForce.x, tgx+1);\n";
    shuffleWarpData << "shflForce.y = real_shfl(shflForce.y, tgx+1);\n";
    shuffleWarpData << "shflForce.z = real_shfl(shflForce.z, tgx+1);\n";
658
    for (const ComputeParameterInfo& param : params) {
659
660
661
662
663
664
665
666
667
        if (param.getNumComponents() == 1)
            shuffleWarpData<<"shfl"<<param.getName()<<"=real_shfl(shfl"<<param.getName()<<", tgx+1);\n";
        else {
            for (int j = 0; j < param.getNumComponents(); j++) {
                // looks something like shflsigmaEpsilon.x = real_shfl(shflsigmaEpsilon.x,tgx+1);
                shuffleWarpData<<"shfl"<<param.getName()
                    <<"."<<suffixes[j]<<"=real_shfl(shfl"
                    <<param.getName()<<"."<<suffixes[j]
                    <<", tgx+1);\n";
668
669
670
671
672
            }
        }
    }
    replacements["SHUFFLE_WARP_DATA"] = shuffleWarpData.str();

673
    map<string, string> defines;
674
675
676
677
678
679
680
681
    if (useCutoff)
        defines["USE_CUTOFF"] = "1";
    if (usePeriodic)
        defines["USE_PERIODIC"] = "1";
    if (useExclusions)
        defines["USE_EXCLUSIONS"] = "1";
    if (isSymmetric)
        defines["USE_SYMMETRIC"] = "1";
682
683
    if (useNeighborList)
        defines["USE_NEIGHBOR_LIST"] = "1";
684
    defines["ENABLE_SHUFFLE"] = "1";
685
686
687
688
    if (includeForces)
        defines["INCLUDE_FORCES"] = "1";
    if (includeEnergy)
        defines["INCLUDE_ENERGY"] = "1";
689
    defines["THREAD_BLOCK_SIZE"] = context.intToString(forceThreadBlockSize);
690
691
692
693
694
695
696
697
698
699
    double maxCutoff = 0.0;
    for (int i = 0; i < 32; i++) {
        if ((groups&(1<<i)) != 0) {
            double cutoff = groupCutoff[i];
            maxCutoff = max(maxCutoff, cutoff);
            defines["CUTOFF_"+context.intToString(i)+"_SQUARED"] = context.doubleToString(cutoff*cutoff);
            defines["CUTOFF_"+context.intToString(i)] = context.doubleToString(cutoff);
        }
    }
    defines["MAX_CUTOFF"] = context.doubleToString(maxCutoff);
700
701
702
    defines["NUM_ATOMS"] = context.intToString(context.getNumAtoms());
    defines["PADDED_NUM_ATOMS"] = context.intToString(context.getPaddedNumAtoms());
    defines["NUM_BLOCKS"] = context.intToString(context.getNumAtomBlocks());
703
    defines["TILE_SIZE"] = context.intToString(CudaContext::TileSize);
Peter Eastman's avatar
Peter Eastman committed
704
    int numExclusionTiles = exclusionTiles.getSize();
705
706
707
708
709
710
    defines["NUM_TILES_WITH_EXCLUSIONS"] = context.intToString(numExclusionTiles);
    int numContexts = context.getPlatformData().contexts.size();
    int startExclusionIndex = context.getContextIndex()*numExclusionTiles/numContexts;
    int endExclusionIndex = (context.getContextIndex()+1)*numExclusionTiles/numContexts;
    defines["FIRST_EXCLUSION_TILE"] = context.intToString(startExclusionIndex);
    defines["LAST_EXCLUSION_TILE"] = context.intToString(endExclusionIndex);
711
    if ((localDataSize/4)%2 == 0 && !context.getUseDoublePrecision())
712
        defines["PARAMETER_SIZE_IS_EVEN"] = "1";
peastman's avatar
peastman committed
713
    CUmodule program = context.createModule(CudaKernelSources::vectorOps+context.replaceStrings(kernelSource, replacements), defines);
714
715
716
    CUfunction kernel = context.getKernel(program, "computeNonbonded");
    return kernel;
}
peastman's avatar
peastman committed
717
718
719
720

void CudaNonbondedUtilities::setKernelSource(const string& source) {
    kernelSource = source;
}