View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  
39  // Substantial sections ported from OpenMM.
40  
41  /* -------------------------------------------------------------------------- *
42   *                                   OpenMM                                   *
43   * -------------------------------------------------------------------------- *
44   * This is part of the OpenMM molecular simulation toolkit originating from   *
45   * Simbios, the NIH National Center for Physics-Based Simulation of           *
46   * Biological Structures at Stanford, funded under the NIH Roadmap for        *
47   * Medical Research, grant U54 GM072970. See https://simtk.org.               *
48   *                                                                            *
49   * Portions copyright (c) 2013 Stanford University and the Authors.           *
50   * Authors: Peter Eastman                                                     *
51   * Contributors:                                                              *
52   *                                                                            *
53   * Permission is hereby granted, free of charge, to any person obtaining a    *
54   * copy of this software and associated documentation files (the "Software"), *
55   * to deal in the Software without restriction, including without limitation  *
56   * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
57   * and/or sell copies of the Software, and to permit persons to whom the      *
58   * Software is furnished to do so, subject to the following conditions:       *
59   *                                                                            *
60   * The above copyright notice and this permission notice shall be included in *
61   * all copies or substantial portions of the Software.                        *
62   *                                                                            *
63   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
64   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
65   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
66   * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,    *
67   * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR      *
68   * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE  *
69   * USE OR OTHER DEALINGS IN THE SOFTWARE.                                     *
70   * -------------------------------------------------------------------------- */
71  
72  package ffx.potential.constraint;
73  
74  import ffx.numerics.Constraint;
75  import ffx.potential.bonded.Angle;
76  import ffx.potential.bonded.Atom;
77  import ffx.potential.bonded.Bond;
78  
79  import java.util.logging.Logger;
80  
81  import static ffx.numerics.math.DoubleMath.dot;
82  import static ffx.numerics.math.DoubleMath.normalize;
83  import static ffx.numerics.math.DoubleMath.sub;
84  import static java.lang.System.arraycopy;
85  import static org.apache.commons.math3.util.FastMath.abs;
86  import static org.apache.commons.math3.util.FastMath.cos;
87  import static org.apache.commons.math3.util.FastMath.sqrt;
88  import static org.apache.commons.math3.util.FastMath.toRadians;
89  
90  /**
91   * SETTLE triatomic distance constraints, intended for rigid water models.
92   *
93   * <p>S. Miyamoto and P.A. Kollman, "SETTLE: An Analytical Version of the SHAKE and RATTLE Algorithm
94   * for Rigid Water Models", Journal of Computational Chemistry, Vol. 13, No. 8, 952-962 (1992)
95   *
96   * @author Michael J. Schnieders
97   * @author Jacob M. Litman
98   * @since 1.0
99   */
100 public class SettleConstraint implements Constraint {
101 
102   private static final Logger logger = Logger.getLogger(SettleConstraint.class.getName());
103 
104   private final int index0;
105   private final int index1;
106   private final int index2;
107   // Typically the O-H bond length.
108   private final double distance1;
109   // Typically a fictitious H-H bond length.
110   private final double distance2;
111 
112   /**
113    * Constructs a SETTLE constraint from an angle and its two bonds. Does not inform a012 that it is
114    * now constrained, which is why there is a factory method wrapping the private constructor.
115    *
116    * @param a012 Angle to construct a SETTLE constraint from.
117    */
118   private SettleConstraint(Angle a012) {
119     Atom center = a012.getCentralAtom();
120     index0 = center.getXyzIndex() - 1;
121 
122     Bond b01 = a012.getBond(0);
123     Bond b02 = a012.getBond(1);
124     // TODO: Determine if SETTLE is possible if this is false.
125     assert b01.bondType.distance == b02.bondType.distance;
126 
127     distance1 = b01.bondType.distance;
128 
129     Atom a1 = b01.get1_2(center);
130     Atom a2 = b02.get1_2(center);
131     index1 = a1.getXyzIndex() - 1;
132     index2 = a2.getXyzIndex() - 1;
133 
134     double angVal = a012.angleType.angle[a012.nh];
135     distance2 = lawOfCosines(distance1, distance1, angVal);
136   }
137 
138   /**
139    * Constructs a SETTLE constraint from an angle and its two bonds. Factory used mostly to avoid a
140    * leaking-this scenario, as this method also passes the new SETTLE constraint to a012.
141    *
142    * @param a012 Angle to construct a SETTLE constraint from.
143    * @return New SettleConstraint.
144    */
145   public static SettleConstraint settleFactory(Angle a012) {
146     SettleConstraint newC = new SettleConstraint(a012);
147     a012.setConstraint(newC);
148     return newC;
149   }
150 
151   /**
152    * Calculates the distance AC based on known side lengths AB, BC, and angle ABC. Intended to get
153    * the H-H pseudo-bond for rigid water molecules.
154    *
155    * @param distAB Distance between points A and B
156    * @param distBC Distance between points B and C
157    * @param angABC Angle between points A, B, and C, in degrees.
158    * @return Distance between points A and C
159    */
160   static double lawOfCosines(double distAB, double distBC, double angABC) {
161     double val = distAB * distAB;
162     val += distBC * distBC;
163     val -= (2.0 * distAB * distBC * cos(toRadians(angABC)));
164     val = sqrt(val);
165     return val;
166   }
167 
168   @Override
169   public void applyConstraintToStep(
170       final double[] xPrior, double[] xNew, final double[] masses, double tol) {
171     // Ported from OpenMM's ReferenceSETTLEAlgorithm.cpp
172     // Pulled from OpenMM commit a783b996fc42d023ebfd17c5591508da01dde03a
173 
174     // Mapping from atom index to the start of their Cartesian coordinates.
175     int xi0 = 3 * index0;
176     int xi1 = 3 * index1;
177     int xi2 = 3 * index2;
178 
179     // Initial positions of the constrained atoms.
180     double[] apos0 = new double[3];
181     double[] apos1 = new double[3];
182     double[] apos2 = new double[3];
183 
184     // Deltas from the original state (xPrior) to the partially calculated new state (xNew).
185     double[] xp0 = new double[3];
186     double[] xp1 = new double[3];
187     double[] xp2 = new double[3];
188 
189     for (int i = 0; i < 3; i++) {
190       apos0[i] = xPrior[xi0 + i];
191       xp0[i] = xNew[xi0 + i] - apos0[i];
192       apos1[i] = xPrior[xi1 + i];
193       xp1[i] = xNew[xi1 + i] - apos1[i];
194       apos2[i] = xPrior[xi2 + i];
195       xp2[i] = xNew[xi2 + i] - apos2[i];
196     }
197 
198     double m0 = masses[xi0];
199     double m1 = masses[xi1];
200     double m2 = masses[xi2];
201 
202     // Apply the SETTLE algorithm.
203 
204     double xb0 = apos1[0] - apos0[0];
205     double yb0 = apos1[1] - apos0[1];
206     double zb0 = apos1[2] - apos0[2];
207     double xc0 = apos2[0] - apos0[0];
208     double yc0 = apos2[1] - apos0[1];
209     double zc0 = apos2[2] - apos0[2];
210 
211     double invTotalMass = 1.0 / (m0 + m1 + m2);
212     double xcom = (xp0[0] * m0 + (xb0 + xp1[0]) * m1 + (xc0 + xp2[0]) * m2) * invTotalMass;
213     double ycom = (xp0[1] * m0 + (yb0 + xp1[1]) * m1 + (yc0 + xp2[1]) * m2) * invTotalMass;
214     double zcom = (xp0[2] * m0 + (zb0 + xp1[2]) * m1 + (zc0 + xp2[2]) * m2) * invTotalMass;
215 
216     double xa1 = xp0[0] - xcom;
217     double ya1 = xp0[1] - ycom;
218     double za1 = xp0[2] - zcom;
219     double xb1 = xb0 + xp1[0] - xcom;
220     double yb1 = yb0 + xp1[1] - ycom;
221     double zb1 = zb0 + xp1[2] - zcom;
222     double xc1 = xc0 + xp2[0] - xcom;
223     double yc1 = yc0 + xp2[1] - ycom;
224     double zc1 = zc0 + xp2[2] - zcom;
225 
226     double xaksZd = yb0 * zc0 - zb0 * yc0;
227     double yaksZd = zb0 * xc0 - xb0 * zc0;
228     double zaksZd = xb0 * yc0 - yb0 * xc0;
229     double xaksXd = ya1 * zaksZd - za1 * yaksZd;
230     double yaksXd = za1 * xaksZd - xa1 * zaksZd;
231     double zaksXd = xa1 * yaksZd - ya1 * xaksZd;
232     double xaksYd = yaksZd * zaksXd - zaksZd * yaksXd;
233     double yaksYd = zaksZd * xaksXd - xaksZd * zaksXd;
234     double zaksYd = xaksZd * yaksXd - yaksZd * xaksXd;
235 
236     double axlng = sqrt(xaksXd * xaksXd + yaksXd * yaksXd + zaksXd * zaksXd);
237     double aylng = sqrt(xaksYd * xaksYd + yaksYd * yaksYd + zaksYd * zaksYd);
238     double azlng = sqrt(xaksZd * xaksZd + yaksZd * yaksZd + zaksZd * zaksZd);
239     double trns11 = xaksXd / axlng;
240     double trns21 = yaksXd / axlng;
241     double trns31 = zaksXd / axlng;
242     double trns12 = xaksYd / aylng;
243     double trns22 = yaksYd / aylng;
244     double trns32 = zaksYd / aylng;
245     double trns13 = xaksZd / azlng;
246     double trns23 = yaksZd / azlng;
247     double trns33 = zaksZd / azlng;
248 
249     double xb0d = trns11 * xb0 + trns21 * yb0 + trns31 * zb0;
250     double yb0d = trns12 * xb0 + trns22 * yb0 + trns32 * zb0;
251     double xc0d = trns11 * xc0 + trns21 * yc0 + trns31 * zc0;
252     double yc0d = trns12 * xc0 + trns22 * yc0 + trns32 * zc0;
253     double za1d = trns13 * xa1 + trns23 * ya1 + trns33 * za1;
254     double xb1d = trns11 * xb1 + trns21 * yb1 + trns31 * zb1;
255     double yb1d = trns12 * xb1 + trns22 * yb1 + trns32 * zb1;
256     double zb1d = trns13 * xb1 + trns23 * yb1 + trns33 * zb1;
257     double xc1d = trns11 * xc1 + trns21 * yc1 + trns31 * zc1;
258     double yc1d = trns12 * xc1 + trns22 * yc1 + trns32 * zc1;
259     double zc1d = trns13 * xc1 + trns23 * yc1 + trns33 * zc1;
260 
261     //                                        --- Step2  A2' ---
262 
263     double rc = 0.5 * distance2;
264     double rb = sqrt(distance1 * distance1 - rc * rc);
265     double ra = rb * (m1 + m2) * invTotalMass;
266     rb -= ra;
267     double sinphi = za1d / ra;
268     double cosphi = sqrt(1 - sinphi * sinphi);
269     double sinpsi = (zb1d - zc1d) / (2 * rc * cosphi);
270     double cospsi = sqrt(1 - sinpsi * sinpsi);
271 
272     double ya2d = ra * cosphi;
273     double xb2d = -rc * cospsi;
274     double yb2d = -rb * cosphi - rc * sinpsi * sinphi;
275     double yc2d = -rb * cosphi + rc * sinpsi * sinphi;
276     double xb2d2 = xb2d * xb2d;
277     double hh2 = 4.0f * xb2d2 + (yb2d - yc2d) * (yb2d - yc2d) + (zb1d - zc1d) * (zb1d - zc1d);
278     double deltx = 2.0f * xb2d + sqrt(4.0f * xb2d2 - hh2 + distance2 * distance2);
279     xb2d -= deltx * 0.5;
280 
281     //                                        --- Step3  al,be,ga ---
282 
283     double alpha = (xb2d * (xb0d - xc0d) + yb0d * yb2d + yc0d * yc2d);
284     double beta = (xb2d * (yc0d - yb0d) + xb0d * yb2d + xc0d * yc2d);
285     double gamma = xb0d * yb1d - xb1d * yb0d + xc0d * yc1d - xc1d * yc0d;
286 
287     double al2be2 = alpha * alpha + beta * beta;
288     double sintheta = (alpha * gamma - beta * sqrt(al2be2 - gamma * gamma)) / al2be2;
289 
290     //                                        --- Step4  A3' ---
291 
292     double costheta = sqrt(1 - sintheta * sintheta);
293     double xa3d = -ya2d * sintheta;
294     double ya3d = ya2d * costheta;
295     double za3d = za1d;
296     double xb3d = xb2d * costheta - yb2d * sintheta;
297     double yb3d = xb2d * sintheta + yb2d * costheta;
298     double zb3d = zb1d;
299     double xc3d = -xb2d * costheta - yc2d * sintheta;
300     double yc3d = -xb2d * sintheta + yc2d * costheta;
301     double zc3d = zc1d;
302 
303     //                                        --- Step5  A3 ---
304 
305     double xa3 = trns11 * xa3d + trns12 * ya3d + trns13 * za3d;
306     double ya3 = trns21 * xa3d + trns22 * ya3d + trns23 * za3d;
307     double za3 = trns31 * xa3d + trns32 * ya3d + trns33 * za3d;
308     double xb3 = trns11 * xb3d + trns12 * yb3d + trns13 * zb3d;
309     double yb3 = trns21 * xb3d + trns22 * yb3d + trns23 * zb3d;
310     double zb3 = trns31 * xb3d + trns32 * yb3d + trns33 * zb3d;
311     double xc3 = trns11 * xc3d + trns12 * yc3d + trns13 * zc3d;
312     double yc3 = trns21 * xc3d + trns22 * yc3d + trns23 * zc3d;
313     double zc3 = trns31 * xc3d + trns32 * yc3d + trns33 * zc3d;
314 
315     xp0[0] = xcom + xa3;
316     xp0[1] = ycom + ya3;
317     xp0[2] = zcom + za3;
318     xp1[0] = xcom + xb3 - xb0;
319     xp1[1] = ycom + yb3 - yb0;
320     xp1[2] = zcom + zb3 - zb0;
321     xp2[0] = xcom + xc3 - xc0;
322     xp2[1] = ycom + yc3 - yc0;
323     xp2[2] = zcom + zc3 - zc0;
324 
325     for (int i = 0; i < 3; i++) {
326       xNew[xi0 + i] = xp0[i] + apos0[i];
327       xNew[xi1 + i] = xp1[i] + apos1[i];
328       xNew[xi2 + i] = xp2[i] + apos2[i];
329     }
330   }
331 
332   @Override
333   public void applyConstraintToVelocities(
334       final double[] x, double[] v, final double[] masses, double tol) {
335     // Ported from OpenMM's ReferenceSETTLEAlgorithm.cpp
336     // Pulled from OpenMM commit a783b996fc42d023ebfd17c5591508da01dde03a
337 
338     // Mapping from atom index to the start of their Cartesian coordinates.
339     int xi0 = 3 * index0;
340     int xi1 = 3 * index1;
341     int xi2 = 3 * index2;
342 
343     // Positions of the constrained atoms.
344     double[] apos0 = new double[3];
345     double[] apos1 = new double[3];
346     double[] apos2 = new double[3];
347 
348     // Pre-constraint velocities.
349     double[] v0 = new double[3];
350     double[] v1 = new double[3];
351     double[] v2 = new double[3];
352 
353     for (int i = 0; i < 3; i++) {
354       apos0[i] = x[xi0 + i];
355       apos1[i] = x[xi1 + i];
356       apos2[i] = x[xi2 + i];
357     }
358 
359     for (int i = 0; i < 3; i++) {
360       v0[i] = v[xi0 + i];
361       v1[i] = v[xi1 + i];
362       v2[i] = v[xi2 + i];
363     }
364 
365     double mA = masses[xi0];
366     double mB = masses[xi1];
367     double mC = masses[xi2];
368 
369     double[] eAB = new double[3];
370     double[] eBC = new double[3];
371     double[] eCA = new double[3];
372     for (int i = 0; i < 3; i++) {
373       eAB[i] = apos1[i] - apos0[i];
374       eBC[i] = apos2[i] - apos1[i];
375       eCA[i] = apos0[i] - apos2[i];
376     }
377     normalize(eAB, eAB);
378     normalize(eBC, eBC);
379     normalize(eCA, eCA);
380     /*eAB /= sqrt(eAB[0]*eAB[0] + eAB[1]*eAB[1] + eAB[2]*eAB[2]);
381     eBC /= sqrt(eBC[0]*eBC[0] + eBC[1]*eBC[1] + eBC[2]*eBC[2]);
382     eCA /= sqrt(eCA[0]*eCA[0] + eCA[1]*eCA[1] + eCA[2]*eCA[2]);*/
383     double vAB = (v1[0] - v0[0]) * eAB[0] + (v1[1] - v0[1]) * eAB[1] + (v1[2] - v0[2]) * eAB[2];
384     double vBC = (v2[0] - v1[0]) * eBC[0] + (v2[1] - v1[1]) * eBC[1] + (v2[2] - v1[2]) * eBC[2];
385     double vCA = (v0[0] - v2[0]) * eCA[0] + (v0[1] - v2[1]) * eCA[1] + (v0[2] - v2[2]) * eCA[2];
386     double cA = -(eAB[0] * eCA[0] + eAB[1] * eCA[1] + eAB[2] * eCA[2]);
387     double cB = -(eAB[0] * eBC[0] + eAB[1] * eBC[1] + eAB[2] * eBC[2]);
388     double cC = -(eBC[0] * eCA[0] + eBC[1] * eCA[1] + eBC[2] * eCA[2]);
389     double s2A = 1 - cA * cA;
390     double s2B = 1 - cB * cB;
391     double s2C = 1 - cC * cC;
392 
393     // Solve the equations.  These are different from those in the SETTLE paper (JCC 13(8), pp.
394     // 952-962, 1992), because
395     // in going from equations B1 to B2, they make the assumption that mB=mC (but don't bother to
396     // mention they're
397     // making that assumption).  We allow all three atoms to have different masses.
398 
399     double mABCinv = 1 / (mA * mB * mC);
400     double denom =
401         (((s2A * mB + s2B * mA) * mC
402             + (s2A * mB * mB + 2 * (cA * cB * cC + 1) * mA * mB + s2B * mA * mA))
403             * mC
404             + s2C * mA * mB * (mA + mB))
405             * mABCinv;
406     double tab =
407         ((cB * cC * mA - cA * mB - cA * mC) * vCA
408             + (cA * cC * mB - cB * mC - cB * mA) * vBC
409             + (s2C * mA * mA * mB * mB * mABCinv + (mA + mB + mC)) * vAB)
410             / denom;
411     double tbc =
412         ((cA * cB * mC - cC * mB - cC * mA) * vCA
413             + (s2A * mB * mB * mC * mC * mABCinv + (mA + mB + mC)) * vBC
414             + (cA * cC * mB - cB * mA - cB * mC) * vAB)
415             / denom;
416     double tca =
417         ((s2B * mA * mA * mC * mC * mABCinv + (mA + mB + mC)) * vCA
418             + (cA * cB * mC - cC * mB - cC * mA) * vBC
419             + (cB * cC * mA - cA * mB - cA * mC) * vAB)
420             / denom;
421 
422     double invMA = 1.0 / mA;
423     double invMB = 1.0 / mB;
424     double invMC = 1.0 / mC;
425     for (int i = 0; i < 3; i++) {
426       v0[i] += (eAB[i] * tab - eCA[i] * tca) * invMA;
427       v1[i] += (eBC[i] * tbc - eAB[i] * tab) * invMB;
428       v2[i] += (eCA[i] * tca - eBC[i] * tbc) * invMC;
429     }
430     /*v0 += (eAB*tab - eCA*tca)*inverseMasses[atom1[index]];
431     v1 += (eBC*tbc - eAB*tab)*inverseMasses[atom2[index]];
432     v2 += (eCA*tca - eBC*tbc)*inverseMasses[atom3[index]];*/
433 
434     arraycopy(v0, 0, v, xi0, 3);
435     arraycopy(v1, 0, v, xi1, 3);
436     arraycopy(v2, 0, v, xi2, 3);
437     /* velocities[atom1[index]] = v0;
438     velocities[atom2[index]] = v1;
439     velocities[atom3[index]] = v2; */
440   }
441 
442   @Override
443   public int[] constrainedAtomIndices() {
444     return new int[] {index0, index1, index2};
445   }
446 
447   @Override
448   public boolean constraintSatisfied(double[] x, double tol) {
449     return constraintSatisfied(x, null, tol, 0.0);
450   }
451 
452   @Override
453   public boolean constraintSatisfied(double[] x, double[] v, double xTol, double vTol) {
454     int xi0 = 3 * index0;
455     int xi1 = 3 * index1;
456     int xi2 = 3 * index2;
457 
458     // O-H bonds.
459     double dist01 = 0;
460     double dist02 = 0;
461     // H-H pseudo-bond.
462     double dist12 = 0;
463 
464     double[] x0 = new double[3];
465     double[] x1 = new double[3];
466     double[] x2 = new double[3];
467     arraycopy(x, xi0, x0, 0, 3);
468     arraycopy(x, xi1, x1, 0, 3);
469     arraycopy(x, xi2, x2, 0, 3);
470 
471     for (int i = 0; i < 3; i++) {
472       // 0-1 bond.
473       double dx = x0[i] - x1[i];
474       dx *= dx;
475       dist01 += dx;
476 
477       // 0-2 bond.
478       dx = x0[i] - x2[i];
479       dx *= dx;
480       dist02 += dx;
481 
482       // 1-2 bond.
483       dx = x1[i] - x2[i];
484       dx *= dx;
485       dist12 += dx;
486     }
487     dist01 = sqrt(dist01);
488     dist02 = sqrt(dist02);
489     dist12 = sqrt(dist12);
490 
491     // Check that positions are satisfied.
492     double deltaIdeal = Math.abs((dist01 - distance1) / distance1);
493     if (deltaIdeal > xTol) {
494       logger.finer(" delId 01: " + deltaIdeal);
495       return false;
496     }
497     deltaIdeal = Math.abs((dist02 - distance1) / distance1);
498     if (deltaIdeal > xTol) {
499       logger.finer(" delId 02: " + deltaIdeal);
500       return false;
501     }
502     deltaIdeal = Math.abs((dist12 - distance2) / distance2);
503     if (deltaIdeal > xTol) {
504       logger.finer(" delId 12: " + deltaIdeal);
505       return false;
506     }
507 
508     if (v != null && vTol > 0) {
509       double[] v0 = new double[3];
510       double[] v1 = new double[3];
511       double[] v2 = new double[3];
512       arraycopy(v, xi0, v0, 0, 3);
513       arraycopy(v, xi1, v1, 0, 3);
514       arraycopy(v, xi2, v2, 0, 3);
515 
516       // Obtain relative velocities.
517       double[] v01 = new double[3];
518       double[] v02 = new double[3];
519       double[] v12 = new double[3];
520       sub(v1, v0, v01);
521       sub(v2, v0, v02);
522       sub(v2, v1, v12);
523 
524       // Obtain bond vectors.
525       double[] x01 = new double[3];
526       double[] x02 = new double[3];
527       double[] x12 = new double[3];
528       sub(x1, x0, x01);
529       sub(x2, x0, x02);
530       sub(x2, x1, x12);
531 
532       double xv01 = dot(v01, x01);
533       double xv02 = dot(v02, x02);
534       double xv12 = dot(v12, x12);
535 
536       if (abs(xv01) > vTol) {
537         return false;
538       }
539       if (abs(xv02) > vTol) {
540         return false;
541       }
542       if (abs(xv12) > vTol) {
543         return false;
544       }
545     }
546     return true;
547   }
548 
549   @Override
550   public int getNumDegreesFrozen() {
551     return 3; // 2 bonds and an angle are frozen; for water, this leaves just 3x rotation 3x
552     // translation DoF.
553   }
554 }