ReferenceCCMAAlgorithm.cpp 13.2 KB
Newer Older
1

peastman's avatar
peastman committed
2
/* Portions copyright (c) 2006-2017 Stanford University and Simbios.
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
 * Contributors: Peter Eastman, Pande Group
 *
 * 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.
 */

#include <string.h>
#include <sstream>

28
#include "SimTKOpenMMUtilities.h"
29
#include "ReferenceCCMAAlgorithm.h"
30
#include "ReferenceDynamics.h"
31
#include "quern.h"
32
#include "openmm/OpenMMException.h"
33
#include "openmm/Vec3.h"
34
#include "openmm/internal/ThreadPool.h"
35
#include <map>
36
#include <utility>
37

38
using namespace OpenMM;
39
using namespace std;
40

41
ReferenceCCMAAlgorithm::ReferenceCCMAAlgorithm(int numberOfAtoms,
42
43
                                               int numberOfConstraints,
                                               const vector<pair<int, int> >& atomIndices,
peastman's avatar
peastman committed
44
45
                                               const vector<double>& distance,
                                               vector<double>& masses,
46
                                               vector<AngleInfo>& angles,
peastman's avatar
peastman committed
47
                                               double elementCutoff) {
48
    _numberOfConstraints = numberOfConstraints;
49
    _elementCutoff = elementCutoff;
50
51
    _atomIndices = atomIndices;
    _distance = distance;
52

53
54
    _maximumNumberOfIterations = 150;
    _hasInitializedMasses = false;
55

56
    // work arrays
57
58

    if (_numberOfConstraints > 0) {
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
        _r_ij.resize(numberOfConstraints);
        _d_ij2 = SimTKOpenMMUtilities::allocateOneDRealOpenMMArray(numberOfConstraints, NULL, 1, 0.0, "dij_2");
        _distanceTolerance = SimTKOpenMMUtilities::allocateOneDRealOpenMMArray(numberOfConstraints, NULL, 1, 0.0, "distanceTolerance");
        _reducedMasses = SimTKOpenMMUtilities::allocateOneDRealOpenMMArray(numberOfConstraints, NULL, 1, 0.0, "reducedMasses");
    }
    if (numberOfConstraints > 0)
    {
        // Compute the constraint coupling matrix

        vector<vector<int> > atomAngles(numberOfAtoms);
        for (int i = 0; i < (int) angles.size(); i++)
            atomAngles[angles[i].atom2].push_back(i);
        vector<vector<pair<int, double> > > matrix(numberOfConstraints);
        for (int j = 0; j < numberOfConstraints; j++) {
            for (int k = 0; k < numberOfConstraints; k++) {
                if (j == k) {
                    matrix[j].push_back(pair<int, double>(j, 1.0));
                    continue;
                }
                double scale;
                int atomj0 = _atomIndices[j].first;
                int atomj1 = _atomIndices[j].second;
                int atomk0 = _atomIndices[k].first;
                int atomk1 = _atomIndices[k].second;
peastman's avatar
peastman committed
83
84
                double invMass0 = 1/masses[atomj0];
                double invMass1 = 1/masses[atomj1];
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
                int atoma, atomb, atomc;
                if (atomj0 == atomk0) {
                    atoma = atomj1;
                    atomb = atomj0;
                    atomc = atomk1;
                    scale = invMass0/(invMass0+invMass1);
                }
                else if (atomj1 == atomk1) {
                    atoma = atomj0;
                    atomb = atomj1;
                    atomc = atomk0;
                    scale = invMass1/(invMass0+invMass1);
                }
                else if (atomj0 == atomk1) {
                    atoma = atomj1;
                    atomb = atomj0;
                    atomc = atomk0;
                    scale = invMass0/(invMass0+invMass1);
                }
                else if (atomj1 == atomk0) {
                    atoma = atomj0;
                    atomb = atomj1;
                    atomc = atomk1;
                    scale = invMass1/(invMass0+invMass1);
                }
                else
                    continue; // These constraints are not connected.

                // Look for a third constraint forming a triangle with these two.

                bool foundConstraint = false;
                for (int other = 0; other < numberOfConstraints; other++) {
                    if ((_atomIndices[other].first == atoma && _atomIndices[other].second == atomc) || (_atomIndices[other].first == atomc && _atomIndices[other].second == atoma)) {
                        double d1 = _distance[j];
                        double d2 = _distance[k];
                        double d3 = _distance[other];
                        matrix[j].push_back(pair<int, double>(k, scale*(d1*d1+d2*d2-d3*d3)/(2.0*d1*d2)));
                        foundConstraint = true;
                        break;
                    }
                }
                if (!foundConstraint) {
                    // We didn't find one, so look for an angle force field term.

                    const vector<int>& angleCandidates = atomAngles[atomb];
                    for (vector<int>::const_iterator iter = angleCandidates.begin(); iter != angleCandidates.end(); iter++) {
                        const AngleInfo& angle = angles[*iter];
                        if ((angle.atom1 == atoma && angle.atom3 == atomc) || (angle.atom3 == atoma && angle.atom1 == atomc)) {
                            matrix[j].push_back(pair<int, double>(k, scale*cos(angle.angle)));
                            break;
                        }
                    }
                }
            }
        }

        // Invert it using QR.

        vector<int> matrixRowStart;
        vector<int> matrixColIndex;
        vector<double> matrixValue;
        for (int i = 0; i < numberOfConstraints; i++) {
            matrixRowStart.push_back(matrixValue.size());
peastman's avatar
peastman committed
148
            for (auto& element : matrix[i]) {
149
150
151
152
153
154
155
156
157
                matrixColIndex.push_back(element.first);
                matrixValue.push_back(element.second);
            }
        }
        matrixRowStart.push_back(matrixValue.size());
        int *qRowStart, *qColIndex, *rRowStart, *rColIndex;
        double *qValue, *rValue;
        QUERN_compute_qr(numberOfConstraints, numberOfConstraints, &matrixRowStart[0], &matrixColIndex[0], &matrixValue[0], NULL,
                &qRowStart, &qColIndex, &qValue, &rRowStart, &rColIndex, &rValue);
peastman's avatar
peastman committed
158
        vector<vector<pair<int, double> > > transposedMatrix(numberOfConstraints);
159
        _matrix.resize(numberOfConstraints);
peastman's avatar
peastman committed
160
161
162
163

        // Extract columns from the inverse matrix one at a time.  It is done in parallel,
        // since this can be very slow.
        
164
        ThreadPool threads;
peastman's avatar
peastman committed
165
166
167
168
169
170
171
172
173
174
175
        threads.execute([&] (ThreadPool& pool, int threadIndex) {
            vector<double> rhs(numberOfConstraints);
            for (int i = threadIndex; i < numberOfConstraints; i += pool.getNumThreads()) {
                // Extract column i of the inverse matrix.

                for (int j = 0; j < numberOfConstraints; j++)
                    rhs[j] = (i == j ? 1.0 : 0.0);
                QUERN_multiply_with_q_transpose(numberOfConstraints, qRowStart, qColIndex, qValue, &rhs[0]);
                QUERN_solve_with_r(numberOfConstraints, rRowStart, rColIndex, rValue, &rhs[0], &rhs[0]);
                for (int j = 0; j < numberOfConstraints; j++) {
                    double value = rhs[j]*distance[i]/distance[j];
peastman's avatar
peastman committed
176
177
                    if (fabs(value) > elementCutoff)
                        transposedMatrix[i].push_back(pair<int, double>(j, value));
peastman's avatar
peastman committed
178
179
180
                }
            }
        });
181
        threads.waitForThreads();
182
183
184
185
186

        // For purposes of thread safety we extracted the matrix in transposed form, so we need to transpose it again.

        for (int i = 0; i < numberOfConstraints; i++) {
            for (int j = 0; j < transposedMatrix[i].size(); j++) {
peastman's avatar
peastman committed
187
                pair<int, double> value = transposedMatrix[i][j];
188
189
190
                _matrix[value.first].push_back(make_pair(i, value.second));
            }
        }
191
192
        QUERN_free_result(qRowStart, qColIndex, qValue);
        QUERN_free_result(rRowStart, rColIndex, rValue);
193
194
195
    }
}

196
197
198
199
200
201
ReferenceCCMAAlgorithm::~ReferenceCCMAAlgorithm() {
    if (_numberOfConstraints > 0) {
        SimTKOpenMMUtilities::freeOneDRealOpenMMArray(_d_ij2, "d_ij2");
        SimTKOpenMMUtilities::freeOneDRealOpenMMArray(_distanceTolerance, "distanceTolerance");
        SimTKOpenMMUtilities::freeOneDRealOpenMMArray(_reducedMasses, "reducedMasses");
    }
202
203
}

204
205
int ReferenceCCMAAlgorithm::getNumberOfConstraints() const {
    return _numberOfConstraints;
206
207
}

208
209
int ReferenceCCMAAlgorithm::getMaximumNumberOfIterations() const {
    return _maximumNumberOfIterations;
210
211
}

212
213
void ReferenceCCMAAlgorithm::setMaximumNumberOfIterations(int maximumNumberOfIterations) {
    _maximumNumberOfIterations = maximumNumberOfIterations;
214
215
}

peastman's avatar
peastman committed
216
217
218
void ReferenceCCMAAlgorithm::apply(vector<Vec3>& atomCoordinates,
                                         vector<Vec3>& atomCoordinatesP,
                                         vector<double>& inverseMasses, double tolerance) {
219
    applyConstraints(atomCoordinates, atomCoordinatesP, inverseMasses, false, tolerance);
220
221
}

peastman's avatar
peastman committed
222
223
void ReferenceCCMAAlgorithm::applyToVelocities(std::vector<OpenMM::Vec3>& atomCoordinates,
               std::vector<OpenMM::Vec3>& velocities, std::vector<double>& inverseMasses, double tolerance) {
224
    applyConstraints(atomCoordinates, velocities, inverseMasses, true, tolerance);
225
226
}

peastman's avatar
peastman committed
227
228
229
void ReferenceCCMAAlgorithm::applyConstraints(vector<Vec3>& atomCoordinates,
                                         vector<Vec3>& atomCoordinatesP,
                                         vector<double>& inverseMasses, bool constrainingVelocities, double tolerance) {
230
231
    // temp arrays

peastman's avatar
peastman committed
232
233
234
    vector<Vec3>& r_ij = _r_ij;
    double* d_ij2 = _d_ij2;
    double* reducedMasses = _reducedMasses;
235
236
237
238
239
240
241
242
243
244
245

    // calculate reduced masses on 1st pass

    if (!_hasInitializedMasses) {
        _hasInitializedMasses = true;
        for (int ii = 0; ii < _numberOfConstraints; ii++) {
           int atomI = _atomIndices[ii].first;
           int atomJ = _atomIndices[ii].second;
           reducedMasses[ii] = 0.5/(inverseMasses[atomI] + inverseMasses[atomJ]);
        }
    }
246

247
    // setup: r_ij for each (i,j) constraint
248

249
250
251
252
253
254
    for (int ii = 0; ii < _numberOfConstraints; ii++) {
        int atomI = _atomIndices[ii].first;
        int atomJ = _atomIndices[ii].second;
        r_ij[ii] = atomCoordinates[atomI] - atomCoordinates[atomJ];
        d_ij2[ii] = r_ij[ii].dot(r_ij[ii]);
    }
peastman's avatar
peastman committed
255
256
    double lowerTol = 1-2*tolerance+tolerance*tolerance;
    double upperTol = 1+2*tolerance+tolerance*tolerance;
257
258
259
260
261

    // main loop

    int iterations = 0;
    int numberConverged = 0;
peastman's avatar
peastman committed
262
263
    vector<double> constraintDelta(_numberOfConstraints);
    vector<double> tempDelta(_numberOfConstraints);
264
265
266
267
268
    while (iterations < getMaximumNumberOfIterations()) {
        numberConverged  = 0;
        for (int ii = 0; ii < _numberOfConstraints; ii++) {
            int atomI = _atomIndices[ii].first;
            int atomJ = _atomIndices[ii].second;
peastman's avatar
peastman committed
269
            Vec3 rp_ij = atomCoordinatesP[atomI] - atomCoordinatesP[atomJ];
270
            if (constrainingVelocities) {
peastman's avatar
peastman committed
271
                double rrpr = rp_ij.dot(r_ij[ii]);
272
                constraintDelta[ii] = -2*reducedMasses[ii]*rrpr/d_ij2[ii];
273
                if (fabs(constraintDelta[ii]) <= tolerance)
274
275
276
                    numberConverged++;
            }
            else {
peastman's avatar
peastman committed
277
278
279
                double rp2  = rp_ij.dot(rp_ij);
                double dist2 = _distance[ii]*_distance[ii];
                double diff = dist2 - rp2;
280
                constraintDelta[ii] = 0;
peastman's avatar
peastman committed
281
                double rrpr = DOT3(rp_ij, r_ij[ii]);
282
283
284
285
286
287
288
289
290
291
292
                constraintDelta[ii] = reducedMasses[ii]*diff/rrpr;
                if (rp2 >= lowerTol*dist2 && rp2 <= upperTol*dist2)
                    numberConverged++;
            }
        }
        if (numberConverged == _numberOfConstraints)
            break;
        iterations++;

        if (_matrix.size() > 0) {
            for (int i = 0; i < _numberOfConstraints; i++) {
peastman's avatar
peastman committed
293
                double sum = 0.0;
peastman's avatar
peastman committed
294
                for (auto& element : _matrix[i])
295
296
297
298
299
300
301
302
                    sum += element.second*constraintDelta[element.first];
                tempDelta[i] = sum;
            }
            constraintDelta = tempDelta;
        }
        for (int ii = 0; ii < _numberOfConstraints; ii++) {
            int atomI = _atomIndices[ii].first;
            int atomJ = _atomIndices[ii].second;
peastman's avatar
peastman committed
303
            Vec3 dr = r_ij[ii]*constraintDelta[ii];
304
305
306
307
            atomCoordinatesP[atomI] += dr*inverseMasses[atomI];
            atomCoordinatesP[atomJ] -= dr*inverseMasses[atomJ];
        }
    }
308
}
309

peastman's avatar
peastman committed
310
const vector<vector<pair<int, double> > >& ReferenceCCMAAlgorithm::getMatrix() const {
311
312
    return _matrix;
}