Skip to content

Commit ed4ec69

Browse files
author
Charlles Abreu
authored
Implements concerted rmsd calculation (#3)
* Changes API to deal with groups of particles * Updated serialization code * Updated Python API * Improved particle group handling * Implemented concerted RMSD calculation proper * Store positions of group particles only
1 parent 70379c6 commit ed4ec69

File tree

9 files changed

+219
-137
lines changed

9 files changed

+219
-137
lines changed

.github/workflows/Linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ on:
1414
jobs:
1515
unix:
1616
runs-on: ubuntu-20.04
17-
name: Linux (Python ${{ matrix.python-version }}, OpenMM ${{ matrix.openmm-version }}, CUDA ${{ matrix.cuda-version }})
17+
name: Linux (Python ${{ matrix.python-version }}, OpenMM ${{ matrix.openmm-version }}, GCC ${{ matrix.gcc-version }})
1818
strategy:
1919
fail-fast: false
2020
matrix:

customcppforces/include/ConcertedRMSDForce.h

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@
1111
* https://github.com/craabreu/customcppforces *
1212
* -------------------------------------------------------------------------- */
1313

14+
#include "internal/windowsExportCustomCPPForces.h"
15+
1416
#include "openmm/Force.h"
1517
#include "openmm/Vec3.h"
18+
#include "openmm/internal/AssertionUtilities.h"
1619
#include <vector>
17-
#include "internal/windowsExportCustomCPPForces.h"
1820

1921
using namespace OpenMM;
22+
using namespace std;
2023

2124
namespace CustomCPPForces {
2225

2326
/**
24-
* This is a force whose energy equals the root mean squared deviation (RMSD)
25-
* between the current coordinates and a reference structure. It is intended for
27+
* This is a force whose energy equals a special type of root mean squared deviation
28+
* (RMSD) between the current coordinates and a reference structure. It is intended for
2629
* use with CustomCVForce. You will not normally want a force that exactly equals
2730
* the RMSD, but there are many situations where it is useful to have a restraining
2831
* or biasing force that depends on the RMSD in some way.
@@ -38,43 +41,54 @@ class CUSTOM_CPP_FORCES_EXPORT ConcertedRMSDForce : public Force {
3841
/**
3942
* Create an ConcertedRMSDForce.
4043
*
41-
* @param referencePositions the reference positions to compute the deviation
42-
* from. The length of this vector must equal the
43-
* number of particles in the system, even if not
44-
* all particles are used in computing the RMSD.
45-
* @param particles the indices of the particles to use when computing
46-
* the RMSD. If this is empty (the default), all
47-
* particles in the system will be used.
44+
* @param referencePositions the reference positions to compute the deviation from.
45+
* The length of this vector must equal the number of
46+
* particles in the system, even if not all particles are
47+
* used in computing the Concerted RMSD.
4848
*/
49-
explicit ConcertedRMSDForce(const std::vector<Vec3>& referencePositions,
50-
const std::vector<int>& particles=std::vector<int>());
49+
explicit ConcertedRMSDForce(const vector<Vec3>& referencePositions);
5150
/**
5251
* Get the reference positions to compute the deviation from.
5352
*/
54-
const std::vector<Vec3>& getReferencePositions() const {
53+
const vector<Vec3>& getReferencePositions() const {
5554
return referencePositions;
5655
}
5756
/**
5857
* Set the reference positions to compute the deviation from.
58+
*
59+
* @param positions the reference positions to compute the deviation from.
60+
* The length of this vector must equal the number of
61+
* particles in the system, even if not all particles are
62+
* used in computing the concerted RMSD.
63+
*/
64+
void setReferencePositions(const vector<Vec3>& positions);
65+
/**
66+
* Add a group of particles to be included in the concerted RMSD calculation.
67+
*
68+
* @param particles the indices of the particles to include
69+
*
70+
* @return the index of the group that was added
5971
*/
60-
void setReferencePositions(const std::vector<Vec3>& positions);
72+
int addGroup(const vector<int>& particles);
6173
/**
62-
* Get the indices of the particles to use when computing the RMSD. If this
63-
* is empty, all particles in the system will be used.
74+
* Get the number of particle groups included in the concerted RMSD calculation.
6475
*/
65-
const std::vector<int>& getParticles() const {
66-
return particles;
67-
}
76+
int getNumGroups() const;
77+
/**
78+
* Get the particles of a group included in the concerted RMSD calculation.
79+
*
80+
* @param index the index of the group whose particles are to be retrieved
81+
*/
82+
const vector<int>& getGroup(int index) const;
6883
/**
69-
* Set the indices of the particles to use when computing the RMSD. If this
70-
* is empty, all particles in the system will be used.
84+
* Set the particles of a group included in the concerted RMSD calculation.
7185
*/
72-
void setParticles(const std::vector<int>& particles);
86+
void setGroup(int index, const vector<int>& particles);
7387
/**
74-
* Update the reference positions and particle indices in a Context to match those stored
75-
* in this Force object. This method provides an efficient method to update certain parameters
88+
* Update the reference positions and particle groups in a Context to match those stored
89+
* in this Force object. This method provides an efficient way to update these parameters
7690
* in an existing Context without needing to reinitialize it. Simply call setReferencePositions()
77-
* and setParticles() to modify this object's parameters, then call updateParametersInContext()
91+
* and setGroup() to modify this object's parameters, then call updateParametersInContext()
7892
* to copy them over to the Context.
7993
*/
8094
void updateParametersInContext(Context& context);
@@ -90,8 +104,8 @@ class CUSTOM_CPP_FORCES_EXPORT ConcertedRMSDForce : public Force {
90104
protected:
91105
ForceImpl* createImpl() const;
92106
private:
93-
std::vector<Vec3> referencePositions;
94-
std::vector<int> particles;
107+
vector<Vec3> referencePositions;
108+
vector<vector<int>> groups;
95109
};
96110

97111
} // namespace CustomCPPForces

customcppforces/include/internal/ConcertedRMSDForceImpl.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ class ConcertedRMSDForceImpl : public CustomCPPForceImpl {
3636
}
3737
void updateParametersInContext(ContextImpl& context);
3838
private:
39+
void updateParameters(int systemSize);
3940
const ConcertedRMSDForce& owner;
40-
int numParticles;
41-
vector<int> particles;
41+
vector<vector<int>> groups;
4242
vector<Vec3> referencePos;
4343
};
4444

customcppforces/src/ConcertedRMSDForce.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,37 @@
1111
#include "ConcertedRMSDForce.h"
1212
#include "internal/ConcertedRMSDForceImpl.h"
1313

14+
#include "openmm/internal/AssertionUtilities.h"
15+
1416
using namespace CustomCPPForces;
1517
using namespace OpenMM;
1618
using namespace std;
1719

18-
ConcertedRMSDForce::ConcertedRMSDForce(const vector<Vec3>& referencePositions, const vector<int>& particles) :
19-
referencePositions(referencePositions), particles(particles) {
20+
ConcertedRMSDForce::ConcertedRMSDForce(const vector<Vec3>& referencePositions) :
21+
referencePositions(referencePositions) {
2022
}
2123

2224
void ConcertedRMSDForce::setReferencePositions(const std::vector<Vec3>& positions) {
2325
referencePositions = positions;
2426
}
2527

26-
void ConcertedRMSDForce::setParticles(const std::vector<int>& particles) {
27-
this->particles = particles;
28+
int ConcertedRMSDForce::addGroup(const vector<int>& particles) {
29+
groups.push_back(particles);
30+
return groups.size()-1;
31+
}
32+
33+
int ConcertedRMSDForce::getNumGroups() const {
34+
return groups.size();
35+
}
36+
37+
const vector<int>& ConcertedRMSDForce::getGroup(int index) const {
38+
ASSERT_VALID_INDEX(index, groups);
39+
return groups[index];
40+
}
41+
42+
void ConcertedRMSDForce::setGroup(int index, const std::vector<int>& particles) {
43+
ASSERT_VALID_INDEX(index, groups);
44+
groups[index] = particles;
2845
}
2946

3047
void ConcertedRMSDForce::updateParametersInContext(Context& context) {

customcppforces/src/ConcertedRMSDForceImpl.cpp

Lines changed: 68 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -21,68 +21,85 @@ using namespace CustomCPPForces;
2121
using namespace OpenMM;
2222
using namespace std;
2323

24-
void ConcertedRMSDForceImpl::initialize(ContextImpl& context) {
25-
CustomCPPForceImpl::initialize(context);
2624

25+
void ConcertedRMSDForceImpl::updateParameters(int systemSize) {
2726
// Check for errors in the specification of particles.
28-
const System& system = context.getSystem();
29-
int systemSize = system.getNumParticles();
3027
if (owner.getReferencePositions().size() != systemSize)
3128
throw OpenMMException(
32-
"RMSDForce: Number of reference positions does not equal number of particles in the System"
29+
"ConcertedRMSDForce: Number of reference positions does not equal number of particles in the System"
3330
);
3431

35-
particles = owner.getParticles();
36-
numParticles = particles.size();
32+
int numGroups = owner.getNumGroups();
33+
if (numGroups == 0)
34+
throw OpenMMException("ConcertedRMSDForce: No particle groups have been specified");
35+
36+
groups.resize(numGroups);
37+
for (int i = 0; i < numGroups; i++) {
38+
groups[i] = owner.getGroup(i);
39+
if (groups[i].size() == 0) {
40+
groups[i].resize(systemSize);
41+
for (int j = 0; j < systemSize; j++)
42+
groups[i][j] = j;
43+
}
44+
}
3745

3846
set<int> distinctParticles;
39-
for (int i : particles) {
40-
if (i < 0 || i >= systemSize) {
41-
stringstream msg;
42-
msg << "ConcertedRMSDForce: Illegal particle index for RMSD: ";
43-
msg << i;
44-
throw OpenMMException(msg.str());
45-
}
46-
if (distinctParticles.find(i) != distinctParticles.end()) {
47-
stringstream msg;
48-
msg << "ConcertedRMSDForce: Duplicated particle index for RMSD: ";
49-
msg << i;
50-
throw OpenMMException(msg.str());
47+
for (int k = 0; k < numGroups; k++)
48+
for (int i : groups[k]) {
49+
if (i < 0 || i >= systemSize) {
50+
stringstream msg;
51+
msg << "ConcertedRMSDForce: Illegal particle index " << i << " in group " << k;
52+
throw OpenMMException(msg.str());
53+
}
54+
if (distinctParticles.find(i) != distinctParticles.end()) {
55+
stringstream msg;
56+
msg << "ConcertedRMSDForce: Duplicated particle index " << i << " in group " << k;
57+
throw OpenMMException(msg.str());
58+
}
59+
distinctParticles.insert(i);
5160
}
52-
distinctParticles.insert(i);
61+
62+
referencePos.resize(0);
63+
const vector<Vec3>& positions = owner.getReferencePositions();
64+
for (auto& group : groups) {
65+
Vec3 center(0.0, 0.0, 0.0);
66+
for (int i : group)
67+
center += positions[i];
68+
center /= group.size();
69+
for (int i : group)
70+
referencePos.push_back(positions[i] - center);
5371
}
72+
}
5473

55-
referencePos = owner.getReferencePositions();
56-
Vec3 center(0.0, 0.0, 0.0);
57-
for (int i : particles)
58-
center += referencePos[i];
59-
center /= numParticles;
60-
for (Vec3& p : referencePos)
61-
p -= center;
74+
void ConcertedRMSDForceImpl::initialize(ContextImpl& context) {
75+
CustomCPPForceImpl::initialize(context);
76+
updateParameters(context.getSystem().getNumParticles());
6277
}
6378

6479
double ConcertedRMSDForceImpl::computeForce(ContextImpl& context, const vector<Vec3>& positions, vector<Vec3>& forces) {
6580
// Compute the RMSD and its gradient using the algorithm described in Coutsias et al,
6681
// "Using quaternions to calculate RMSD" (doi: 10.1002/jcc.20110). First subtract
6782
// the centroid from the atom positions. The reference positions have already been centered.
6883

69-
Vec3 center(0.0, 0.0, 0.0);
70-
for (int i : particles)
71-
center += positions[i];
72-
center /= numParticles;
84+
int numParticles = referencePos.size();
7385
vector<Vec3> centeredPos(numParticles);
74-
for (int i = 0; i < numParticles; i++)
75-
centeredPos[i] = positions[particles[i]] - center;
86+
int index = 0;
87+
for (auto& group : groups) {
88+
Vec3 center(0.0, 0.0, 0.0);
89+
for (int i : group)
90+
center += positions[i];
91+
center /= group.size();
92+
for (int i : group)
93+
centeredPos[index++] = positions[i] - center;
94+
}
7695

7796
// Compute the correlation matrix.
7897

7998
double R[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
8099
for (int i = 0; i < 3; i++)
81100
for (int j = 0; j < 3; j++)
82-
for (int k = 0; k < numParticles; k++) {
83-
int index = particles[k];
84-
R[i][j] += centeredPos[k][i]*referencePos[index][j];
85-
}
101+
for (int index = 0; index < numParticles; index++)
102+
R[i][j] += centeredPos[index][i]*referencePos[index][j];
86103

87104
// Compute the F matrix.
88105

@@ -118,10 +135,10 @@ double ConcertedRMSDForceImpl::computeForce(ContextImpl& context, const vector<V
118135
// Compute the RMSD.
119136

120137
double sum = 0.0;
121-
for (int i = 0; i < numParticles; i++) {
122-
int index = particles[i];
123-
sum += centeredPos[i].dot(centeredPos[i]) + referencePos[index].dot(referencePos[index]);
124-
}
138+
for (auto& group : groups)
139+
for (int index = 0; index < group.size(); index++)
140+
sum += centeredPos[index].dot(centeredPos[index]) + referencePos[index].dot(referencePos[index]);
141+
125142
double msd = (sum - 2*values[3])/numParticles;
126143
if (msd < 1e-20) {
127144
// The particles are perfectly aligned, so all the forces should be zero.
@@ -143,30 +160,20 @@ double ConcertedRMSDForceImpl::computeForce(ContextImpl& context, const vector<V
143160

144161
// Rotate the reference positions and compute the forces.
145162

146-
for (int i = 0; i < numParticles; i++) {
147-
const Vec3& p = referencePos[particles[i]];
148-
Vec3 rotatedRef(U[0][0]*p[0] + U[1][0]*p[1] + U[2][0]*p[2],
149-
U[0][1]*p[0] + U[1][1]*p[1] + U[2][1]*p[2],
150-
U[0][2]*p[0] + U[1][2]*p[1] + U[2][2]*p[2]);
151-
forces[particles[i]] = -(centeredPos[i] - rotatedRef) / (rmsd*numParticles);
163+
index = 0;
164+
for (auto& group : groups)
165+
for (int i : group) {
166+
const Vec3& p = referencePos[index];
167+
Vec3 rotatedRef(U[0][0]*p[0] + U[1][0]*p[1] + U[2][0]*p[2],
168+
U[0][1]*p[0] + U[1][1]*p[1] + U[2][1]*p[2],
169+
U[0][2]*p[0] + U[1][2]*p[1] + U[2][2]*p[2]);
170+
forces[i] = -(centeredPos[index] - rotatedRef) / (rmsd*groups[0].size());
171+
index++;
152172
}
153173
return rmsd;
154174
}
155175

156176
void ConcertedRMSDForceImpl::updateParametersInContext(ContextImpl& context) {
157-
if (referencePos.size() != owner.getReferencePositions().size())
158-
throw OpenMMException("updateParametersInContext: The number of reference positions has changed");
159-
particles = owner.getParticles();
160-
if (particles.size() == 0)
161-
for (int i = 0; i < referencePos.size(); i++)
162-
particles.push_back(i);
163-
numParticles = particles.size();
164-
referencePos = owner.getReferencePositions();
165-
Vec3 center(0.0, 0.0, 0.0);
166-
for (int i : particles)
167-
center += referencePos[i];
168-
center /= numParticles;
169-
for (Vec3& p : referencePos)
170-
p -= center;
177+
updateParameters(context.getSystem().getNumParticles());
171178
context.systemChanged();
172179
}

0 commit comments

Comments
 (0)