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.bonded;
39
40 import static java.lang.String.format;
41 import static org.apache.commons.math3.util.FastMath.acos;
42 import static org.apache.commons.math3.util.FastMath.max;
43 import static org.apache.commons.math3.util.FastMath.min;
44 import static org.apache.commons.math3.util.FastMath.sqrt;
45 import static org.apache.commons.math3.util.FastMath.toDegrees;
46
47 import ffx.numerics.Constraint;
48 import ffx.numerics.atomic.AtomicDoubleArray3D;
49 import ffx.numerics.math.Double3;
50 import ffx.potential.parameters.AngleType;
51 import ffx.potential.parameters.AngleType.AngleMode;
52 import ffx.potential.parameters.AtomType;
53 import ffx.potential.parameters.ForceField;
54
55 import java.io.Serial;
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.logging.Logger;
59
60
61
62
63
64
65
66 public class Angle extends BondedTerm {
67
68 @Serial
69 private static final long serialVersionUID = 1L;
70
71 private static final Logger logger = Logger.getLogger(Angle.class.getName());
72
73
74
75
76 public AngleType angleType;
77
78
79
80 public int nh = 0;
81
82
83
84 private double rigidScale = 1.0;
85
86
87
88 private Atom atom4 = null;
89
90
91
92
93
94
95
96 public Angle(Bond b1, Bond b2) {
97 super();
98 Atom a2 = b1.getCommonAtom(b2);
99 Atom a1 = b1.get1_2(a2);
100 Atom a3 = b2.get1_2(a2);
101 b1.setAngleWith(b2);
102 b2.setAngleWith(b1);
103 atoms = new Atom[3];
104 bonds = new Bond[2];
105 atoms[1] = a2;
106 atoms[0] = a1;
107 atoms[2] = a3;
108 bonds[0] = b1;
109 bonds[1] = b2;
110
111 a1.setAngle(this);
112 a2.setAngle(this);
113 a3.setAngle(this);
114 setID_Key(false);
115 }
116
117
118
119
120
121
122
123
124 public static void logNoAngleType(Atom a1, Atom a2, Atom a3, ForceField forceField) {
125 AtomType atomType1 = a1.getAtomType();
126 AtomType atomType2 = a2.getAtomType();
127 AtomType atomType3 = a3.getAtomType();
128 int c1 = atomType1.atomClass;
129 int c2 = atomType2.atomClass;
130 int c3 = atomType3.atomClass;
131 int[] c = {c1, c2, c3};
132 String key = AngleType.sortKey(c);
133 StringBuilder sb = new StringBuilder(
134 format("No AngleType for key: %s\n %s -> %s\n %s -> %s\n %s -> %s",
135 key, a1, atomType1, a2, atomType2, a3, atomType3));
136 List<AtomType> types1 = forceField.getSimilarAtomTypes(atomType1);
137 List<AtomType> types2 = forceField.getSimilarAtomTypes(atomType2);
138 List<AtomType> types3 = forceField.getSimilarAtomTypes(atomType3);
139 List<AngleType> angleTypes = new ArrayList<>();
140 boolean match = false;
141 for (AtomType type1 : types1) {
142 for (AtomType type2 : types2) {
143
144 if (type2.atomClass != c2) {
145 continue;
146 }
147 for (AtomType type3 : types3) {
148
149 if ((type1.atomClass != c1) && (type1.atomClass != c3) &&
150 (type3.atomClass != c1) && (type3.atomClass != c3)) {
151 continue;
152 }
153 AngleType angleType = forceField.getAngleType(type1, type2, type3);
154 if (angleType != null && !angleTypes.contains(angleType)) {
155 if (!match) {
156 match = true;
157 sb.append("\n Similar Angle Types:");
158 }
159 angleTypes.add(angleType);
160 sb.append(format("\n %s", angleType));
161 }
162 }
163 }
164 }
165 logger.severe(sb.toString());
166 }
167
168
169
170
171
172
173
174
175
176 static Angle angleFactory(Bond b1, Bond b2, ForceField forceField) {
177 Angle newAngle = new Angle(b1, b2);
178 Atom ac = b1.getCommonAtom(b2);
179 Atom a1 = b1.get1_2(ac);
180 Atom a3 = b2.get1_2(ac);
181 AngleType angleType = forceField.getAngleType(a1.getAtomType(), ac.getAtomType(),
182 a3.getAtomType());
183 if (angleType == null) {
184 logNoAngleType(a1, ac, a3, forceField);
185 return null;
186 }
187 newAngle.setAngleType(angleType);
188
189 return newAngle;
190 }
191
192
193
194
195 @Override
196 public int compareTo(BondedTerm a) {
197 if (a == null) {
198 throw new NullPointerException();
199 }
200 if (a == this) {
201 return 0;
202 }
203 if (!a.getClass().isInstance(this)) {
204 return super.compareTo(a);
205 }
206 int this1 = atoms[1].getIndex();
207 int a1 = a.atoms[1].getIndex();
208 if (this1 < a1) {
209 return -1;
210 }
211 if (this1 > a1) {
212 return 1;
213 }
214
215 int this0 = atoms[0].getIndex();
216 int a0 = a.atoms[0].getIndex();
217 if (this0 < a0) {
218 return -1;
219 }
220 if (this0 > a0) {
221 return 1;
222 }
223 int this2 = atoms[2].getIndex();
224 int a2 = a.atoms[2].getIndex();
225
226 return Integer.compare(this2, a2);
227 }
228
229
230
231
232
233
234 @Override
235 public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
236 value = 0.0;
237 energy = 0.0;
238
239 if (!getUse()) {
240 return energy;
241 }
242 var atomA = atoms[0];
243 var atomB = atoms[1];
244 var atomC = atoms[2];
245 var ia = atomA.getIndex() - 1;
246 var ib = atomB.getIndex() - 1;
247 var ic = atomC.getIndex() - 1;
248 var va = atomA.getXYZ();
249 var vb = atomB.getXYZ();
250 var vc = atomC.getXYZ();
251 var prefactor = angleType.angleUnit * rigidScale * angleType.forceConstant;
252 switch (angleType.angleMode) {
253
254 case NORMAL -> {
255 var vab = va.sub(vb);
256 var vcb = vc.sub(vb);
257 var rab2 = vab.length2();
258 var rcb2 = vcb.length2();
259 if (rab2 != 0.0 && rcb2 != 0.0) {
260 var p = vcb.X(vab);
261 var cosine = min(1.0, max(-1.0, vab.dot(vcb) / sqrt(rab2 * rcb2)));
262 value = toDegrees(acos(cosine));
263 var dv = value - angleType.angle[nh];
264 var dv2 = dv * dv;
265 var dv3 = dv2 * dv;
266 var dv4 = dv2 * dv2;
267 energy = prefactor * dv2 * (1.0
268 + angleType.cubic * dv + angleType.quartic * dv2
269 + angleType.pentic * dv3 + angleType.sextic * dv4);
270 if (gradient) {
271
272 var deddt = prefactor * dv * toDegrees(2.0
273 + 3.0 * angleType.cubic * dv + 4.0 * angleType.quartic * dv2
274 + 5.0 * angleType.pentic * dv3 + 6.0 * angleType.sextic * dv4);
275 var rp = max(p.length(), 0.000001);
276 var terma = -deddt / (rab2 * rp);
277 var termc = deddt / (rcb2 * rp);
278 var ga = vab.X(p).scale(terma);
279 var gc = vcb.X(p).scale(termc);
280 grad.add(threadID, ia, ga);
281 grad.sub(threadID, ib, ga.add(gc));
282 grad.add(threadID, ic, gc);
283 }
284 value = dv;
285 }
286 }
287 case IN_PLANE -> {
288
289 var vd = getAtom4XYZ();
290 int id = atom4.getIndex() - 1;
291 var vad = va.sub(vd);
292 var vbd = vb.sub(vd);
293 var vcd = vc.sub(vd);
294 var vp = vad.X(vcd);
295 var rp2 = vp.length2();
296 var delta = -vp.dot(vbd) / rp2;
297 var vip = vp.scale(delta).addI(vbd);
298 var vjp = vad.sub(vip);
299 var vkp = vcd.sub(vip);
300 var jp2 = vjp.length2();
301 var kp2 = vkp.length2();
302 if (jp2 != 0.0 && kp2 != 0.0) {
303 var cosine = min(1.0, max(-1.0, vjp.dot(vkp) / sqrt(jp2 * kp2)));
304 value = toDegrees(acos(cosine));
305 var dv = value - angleType.angle[nh];
306 var dv2 = dv * dv;
307 var dv3 = dv2 * dv;
308 var dv4 = dv2 * dv2;
309 energy = prefactor * dv2 * (1.0
310 + angleType.cubic * dv + angleType.quartic * dv2
311 + angleType.pentic * dv3 + angleType.sextic * dv4);
312 if (gradient) {
313
314 var deddt = prefactor * dv * toDegrees(2.0
315 + 3.0 * angleType.cubic * dv + 4.0 * angleType.quartic * dv2
316 + 5.0 * angleType.pentic * dv3 + 6.0 * angleType.sextic * dv4);
317
318 var lp = vkp.X(vjp);
319 var lpr = max(lp.length(), 0.000001);
320 var ded0 = vjp.X(lp).scaleI(-deddt / (jp2 * lpr));
321 var ded2 = vkp.X(lp).scaleI(deddt / (kp2 * lpr));
322 var dedp = ded0.add(ded2);
323 var gb = dedp.scale(-1.0);
324 var delta2 = 2.0 * delta;
325 var pt2 = dedp.dot(vp) / rp2;
326 var xd2 = vcd.X(gb).scaleI(delta);
327 var xp2 = vp.X(vcd).scaleI(delta2);
328 var x21 = vbd.X(vcd).addI(xp2).scaleI(pt2);
329 var dpd0 = xd2.add(x21);
330 xd2 = gb.X(vad).scaleI(delta);
331 xp2 = vp.X(vad).scaleI(delta2);
332 x21.addI(xp2).scaleI(pt2);
333 var dpd2 = xd2.addI(x21);
334 var ga = ded0.addI(dpd0);
335 var gc = ded2.addI(dpd2);
336
337 grad.add(threadID, ia, ga);
338 grad.add(threadID, ib, gb);
339 grad.add(threadID, ic, gc);
340 grad.sub(threadID, id, ga.addI(gb).addI(gc));
341 }
342 value = dv;
343 }
344 }
345 }
346
347 return energy;
348 }
349
350
351
352
353
354
355
356
357 public Atom get1_3(Atom a) {
358 if (a == atoms[0]) {
359 return atoms[2];
360 }
361 if (a == atoms[2]) {
362 return atoms[0];
363 }
364 return null;
365 }
366
367
368
369
370
371
372 public AngleMode getAngleMode() {
373 return angleType.angleMode;
374 }
375
376
377
378
379
380
381 public AngleType getAngleType() {
382 return angleType;
383 }
384
385
386
387
388
389
390 public void setAngleType(AngleType a) {
391 angleType = a;
392
393
394
395 List<Bond> ba = atoms[1].getBonds();
396 nh = 0;
397 for (Bond b1 : ba) {
398 if (b1 != bonds[0] && b1 != bonds[1]) {
399 Atom atom = b1.get1_2(atoms[1]);
400 if (atom.getAtomType().atomicNumber == 1) {
401 nh += 1;
402 }
403 }
404 }
405
406
407 while (angleType.angle.length <= nh) {
408 nh--;
409 }
410 }
411
412
413
414
415
416
417 public Atom getAtom4() {
418 return atom4;
419 }
420
421
422
423
424
425
426 public Atom getCentralAtom() {
427 return atoms[1];
428 }
429
430
431
432
433 public void log() {
434 switch (angleType.angleMode) {
435 case NORMAL -> logger.info(format(" %-8s %6d-%s %6d-%s %6d-%s %7.4f %7.4f %10.4f", "Angle",
436 atoms[0].getIndex(), atoms[0].getAtomType().name,
437 atoms[1].getIndex(), atoms[1].getAtomType().name,
438 atoms[2].getIndex(), atoms[2].getAtomType().name,
439 angleType.angle[nh], value, energy));
440 case IN_PLANE -> logger.info(format(" %-8s %6d-%s %6d-%s %6d-%s %7.4f %7.4f %10.4f", "Angle-IP",
441 atoms[0].getIndex(), atoms[0].getAtomType().name,
442 atoms[1].getIndex(), atoms[1].getAtomType().name,
443 atoms[2].getIndex(), atoms[2].getAtomType().name,
444 angleType.angle[nh], value, energy));
445 }
446 }
447
448 @Override
449 public void setConstraint(Constraint c) {
450 super.setConstraint(c);
451 for (Bond b : bonds) {
452 b.setConstraint(c);
453 }
454 }
455
456
457
458
459
460
461 public void setRigidScale(double rigidScale) {
462 this.rigidScale = rigidScale;
463 }
464
465
466
467
468
469
470 @Override
471 public String toString() {
472 return format("%s (%7.1f,%7.2f)", id, value, energy);
473 }
474
475
476
477
478
479
480 private Double3 getAtom4XYZ() {
481 try {
482 return atom4.getXYZ();
483 } catch (Exception e) {
484 logger.info(" Atom 4 not found for angle: " + this);
485 for (var atom : atoms) {
486 logger.info(" Atom: " + atom.toString());
487 logger.info(" Type: " + atom.getAtomType().toString());
488 }
489 throw e;
490 }
491 }
492
493
494
495
496
497
498 void setInPlaneAtom(Atom a4) {
499 if (angleType.angleMode != AngleType.AngleMode.IN_PLANE) {
500 logger.severe(" Attempted to set fourth atom for a normal angle.");
501 }
502 atom4 = a4;
503 }
504
505
506
507
508
509
510
511
512 Bond getCommonBond(Angle a) {
513
514
515 if (a == this || a == null) {
516 return null;
517 }
518 if (a.bonds[0] == bonds[0]) {
519 return bonds[0];
520 }
521 if (a.bonds[0] == bonds[1]) {
522 return bonds[1];
523 }
524 if (a.bonds[1] == bonds[0]) {
525 return bonds[0];
526 }
527 if (a.bonds[1] == bonds[1]) {
528 return bonds[1];
529 }
530 return null;
531 }
532
533
534
535
536
537
538
539 Bond getOtherBond(Bond b) {
540 if (b == bonds[0]) {
541 return bonds[1];
542 }
543 if (b == bonds[1]) {
544 return bonds[0];
545 }
546 return null;
547 }
548
549
550
551
552
553
554
555 public Atom getFourthAtomOfTrigonalCenter() {
556 if (atoms[1].isTrigonal()) {
557 for (Bond b : atoms[1].getBonds()) {
558 if (b != bonds[0] && b != bonds[1]) {
559 return b.get1_2(atoms[1]);
560 }
561 }
562 }
563 return null;
564 }
565 }