1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.potential.constraint;
39
40 import ffx.numerics.Constraint;
41 import ffx.potential.bonded.Angle;
42 import ffx.potential.bonded.Atom;
43 import ffx.potential.bonded.Bond;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Set;
49 import java.util.logging.Logger;
50 import java.util.stream.IntStream;
51 import org.apache.commons.math3.linear.OpenMapRealMatrix;
52 import org.apache.commons.math3.linear.QRDecomposition;
53 import org.apache.commons.math3.linear.RealMatrix;
54 import org.apache.commons.math3.linear.RealVectorPreservingVisitor;
55 import org.apache.commons.math3.util.FastMath;
56
57 public class CcmaConstraint implements Constraint {
58
59 public static final double DEFAULT_CCMA_NONZERO_CUTOFF = 0.01;
60 private static final Logger logger = Logger.getLogger(CcmaConstraint.class.getName());
61 private static final int DEFAULT_MAX_ITERS = 150;
62
63
64 private final int[] atoms1;
65 private final int[] atoms2;
66 private final double[] lengths;
67 private final int nConstraints;
68 private final int[] uniqueIndices;
69 private final int maxIters = DEFAULT_MAX_ITERS;
70 private final double elementCutoff = DEFAULT_CCMA_NONZERO_CUTOFF;
71 private final double[] reducedMasses;
72 private final RealMatrix kInvSparse;
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 private CcmaConstraint(
95 List<Bond> constrainedBonds,
96 List<Angle> constrainedAngles,
97 final Atom[] allAtoms,
98 final double[] masses,
99 double nonzeroCutoff) {
100
101 long time = -System.nanoTime();
102 int nBonds = constrainedBonds.size();
103 int nAngles = constrainedAngles.size();
104 assert constrainedAngles.stream()
105 .flatMap((Angle a) -> a.getBondList().stream())
106 .noneMatch(constrainedBonds::contains);
107 nConstraints = nBonds + (3 * nAngles);
108 atoms1 = new int[nConstraints];
109 atoms2 = new int[nConstraints];
110 lengths = new double[nConstraints];
111
112 for (int i = 0; i < nBonds; i++) {
113 Bond bi = constrainedBonds.get(i);
114 atoms1[i] = bi.getAtom(0).getXyzIndex() - 1;
115 atoms2[i] = bi.getAtom(1).getXyzIndex() - 1;
116 lengths[i] = bi.bondType.distance;
117 }
118
119
120
121
122
123 for (int i = 0; i < nAngles; i++) {
124 int iAng = nBonds + (3 * i);
125 Angle ai = constrainedAngles.get(i);
126 Atom center = ai.getCentralAtom();
127 Bond b1 = ai.getBond(0);
128 Bond b2 = ai.getBond(1);
129 Atom at0 = b1.get1_2(center);
130 Atom at2 = b2.get1_2(center);
131
132 double angVal = ai.angleType.angle[ai.nh];
133 double dist1 = b1.bondType.distance;
134 double dist2 = b2.bondType.distance;
135 double dist3 = SettleConstraint.lawOfCosines(dist1, dist2, angVal);
136
137 int index0 = at0.getXyzIndex() - 1;
138 int index1 = center.getXyzIndex() - 1;
139 int index2 = at2.getXyzIndex() - 1;
140
141 atoms1[iAng] = index0;
142 atoms2[iAng] = index1;
143 lengths[iAng] = dist1;
144
145 ++iAng;
146 atoms1[iAng] = index1;
147 atoms2[iAng] = index2;
148 lengths[iAng] = dist2;
149
150 ++iAng;
151 atoms1[iAng] = index0;
152 atoms2[iAng] = index2;
153 lengths[iAng] = dist3;
154 }
155
156 uniqueIndices =
157 IntStream.concat(Arrays.stream(atoms1), Arrays.stream(atoms2))
158 .sorted()
159 .distinct()
160 .toArray();
161
162 OpenMapRealMatrix kSparse = new OpenMapRealMatrix(nConstraints, nConstraints);
163
164 int nAtoms = allAtoms.length;
165
166
167 List<Set<Integer>> atomsToConstraints = new ArrayList<>(nAtoms);
168 for (int i = 0; i < nAtoms; i++) {
169
170 atomsToConstraints.add(new HashSet<>(4));
171 }
172
173 for (int i = 0; i < nConstraints; i++) {
174 atomsToConstraints.get(atoms1[i]).add(i);
175 atomsToConstraints.get(atoms2[i]).add(i);
176 }
177
178 logger.info(
179 String.format(" Initial CCMA setup: %10.6g sec", 1.0E-9 * (time + System.nanoTime())));
180 long subTime = -System.nanoTime();
181
182 for (int i = 0; i < nConstraints; i++) {
183 int atomi0 = atoms1[i];
184 int atomi1 = atoms2[i];
185
186 double invMassI0 = 1.0 / masses[atomi0 * 3];
187 double invMassI1 = 1.0 / masses[atomi1 * 3];
188 double sumInv = invMassI0 + invMassI1;
189
190
191 Set<Integer> coupledConstraints = new HashSet<>(atomsToConstraints.get(atomi0));
192 coupledConstraints.addAll(atomsToConstraints.get(atomi1));
193
194
195 for (int j : coupledConstraints) {
196
197 if (i == j) {
198 kSparse.setEntry(i, j, 1.0);
199 continue;
200 }
201
202 double scale;
203 int atomj0 = atoms1[j];
204 int atomj1 = atoms2[j];
205 int atoma;
206 int atomb;
207 int atomc;
208
209 if (atomi0 == atomj0) {
210 assert atomi1 != atomj1;
211 atoma = atomi1;
212 atomb = atomi0;
213 atomc = atomj1;
214 scale = invMassI0 / sumInv;
215 } else if (atomi0 == atomj1) {
216 assert atomi1 != atomj0;
217 atoma = atomi1;
218 atomb = atomi0;
219 atomc = atomj0;
220 scale = invMassI0 / sumInv;
221 } else if (atomi1 == atomj0) {
222
223 assert atomi0 != atomj1;
224 atoma = atomi0;
225 atomb = atomi1;
226 atomc = atomj1;
227 scale = invMassI1 / sumInv;
228 } else if (atomi1 == atomj1) {
229 assert atomi0 != atomj0;
230 atoma = atomi1;
231 atomb = atomi0;
232 atomc = atomj1;
233 scale = invMassI1 / sumInv;
234 } else {
235 throw new IllegalArgumentException(
236 " Despite by necessity sharing an atom, these constraints don't share an atom.");
237 }
238
239
240
241
242 boolean foundAngle = false;
243 for (int constraintK : atomsToConstraints.get(atoma)) {
244 if (atoms1[constraintK] == atomc || atoms2[constraintK] == atomc) {
245 double dab = lengths[i];
246 double dbc = lengths[j];
247 double dac = lengths[constraintK];
248
249
250 double angle = (dab * dab) + (dbc * dbc) - (dac * dac);
251 angle /= (2 * dab * dbc);
252
253
254 double coupling = scale * angle;
255 kSparse.setEntry(i, j, coupling);
256 foundAngle = true;
257 break;
258 }
259 }
260
261
262
263 if (!foundAngle) {
264 Atom atA = allAtoms[atoma];
265 Atom atB = allAtoms[atomb];
266 Atom atC = allAtoms[atomc];
267 Angle angleB = atA.getAngle(atB, atC);
268 double angVal = angleB.angleType.angle[angleB.nh];
269 double coupling = scale * FastMath.cos(FastMath.toRadians(angVal));
270 kSparse.setEntry(i, j, coupling);
271 foundAngle = true;
272 }
273
274 if (!foundAngle) {
275 logger.severe(
276 String.format(" Could not find the angle between coupled constraints %d, %d", i, j));
277 }
278 }
279 }
280
281 subTime += System.nanoTime();
282 logger.info(
283 String.format(" Time to construct K as a sparse matrix: %10.6g sec", 1.0E-9 * subTime));
284
285
286
287 subTime = -System.nanoTime();
288 QRDecomposition qrd = new QRDecomposition(kSparse);
289 RealMatrix kInvDense = qrd.getSolver().getInverse();
290 subTime += System.nanoTime();
291 logger.info(String.format(" Time to invert K: %10.6g sec", 1.0E-9 * subTime));
292 subTime = -System.nanoTime();
293
294 kInvSparse = new OpenMapRealMatrix(nConstraints, nConstraints);
295 IntStream.range(0, nConstraints)
296 .
297
298 forEach(
299 (int i) -> {
300 double[] rowI = kInvDense.getRow(i);
301 for (int j = 0; j < nConstraints; j++) {
302 double val = rowI[j];
303 if (Math.abs(val) > elementCutoff) {
304 kInvSparse.setEntry(i, j, val);
305 }
306 }
307 });
308
309
310 double[][] dataDense = kInvDense.getData();
311 double[][] dataSparse = kInvSparse.getData();
312 logger.fine(" Dense array:");
313 for (int i = 0; i < nConstraints; i++) {
314 logger.fine(Arrays.toString(dataDense[i]));
315 }
316 logger.fine(" Sparse array:");
317 for (int i = 0; i < nConstraints; i++) {
318 logger.fine(Arrays.toString(dataSparse[i]));
319 }
320
321
322 reducedMasses = new double[nConstraints];
323 for (int i = 0; i < nConstraints; i++) {
324 int atI = atoms1[i];
325 atI *= 3;
326 int atJ = atoms2[i];
327 atJ *= 3;
328 double invMassI = 1.0 / masses[atI];
329 double invMassJ = 1.0 / masses[atJ];
330 reducedMasses[i] = 0.5 / (invMassI + invMassJ);
331 }
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353 public static CcmaConstraint ccmaFactory(
354 List<Bond> constrainedBonds,
355 List<Angle> constrainedAngles,
356 final Atom[] allAtoms,
357 final double[] masses,
358 double nonzeroCutoff) {
359 CcmaConstraint newC =
360 new CcmaConstraint(constrainedBonds, constrainedAngles, allAtoms, masses, nonzeroCutoff);
361 constrainedBonds.forEach((Bond b) -> b.setConstraint(newC));
362 constrainedAngles.forEach((Angle a) -> a.setConstraint(newC));
363 return newC;
364 }
365
366
367 @Override
368 public void applyConstraintToStep(double[] xPrior, double[] xNew, double[] masses, double tol) {
369 applyConstraints(xPrior, xNew, masses, false, tol);
370 }
371
372
373 @Override
374 public void applyConstraintToVelocities(double[] x, double[] v, double[] masses, double tol) {
375 applyConstraints(x, v, masses, true, tol);
376 }
377
378
379 @Override
380 public int[] constrainedAtomIndices() {
381 return Arrays.copyOf(uniqueIndices, uniqueIndices.length);
382 }
383
384
385 @Override
386 public boolean constraintSatisfied(double[] x, double tol) {
387 return false;
388 }
389
390
391 @Override
392 public boolean constraintSatisfied(double[] x, double[] v, double xTol, double vTol) {
393 return false;
394 }
395
396
397 @Override
398 public int getNumDegreesFrozen() {
399 return nConstraints;
400 }
401
402
403
404
405
406
407
408
409
410
411
412 private void applyConstraints(
413 double[] xPrior, double[] output, double[] masses, boolean constrainV, double tol) {
414 if (xPrior == output) {
415 throw new IllegalArgumentException(" xPrior and output must be different arrays!");
416 }
417 long time = -System.nanoTime();
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508 }
509
510 private static class MatrixWalker implements RealVectorPreservingVisitor {
511
512 private final double[] constraintDelta;
513 private double sum = 0.0;
514
515 MatrixWalker(final double[] cDelta) {
516 constraintDelta = cDelta;
517 }
518
519
520 @Override
521 public double end() {
522 return sum;
523 }
524
525
526 @Override
527 public void start(int dimension, int start, int end) {
528 }
529
530
531 @Override
532 public void visit(int index, double value) {
533 sum += (value * constraintDelta[index]);
534 }
535 }
536 }