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 ffx.numerics.atomic.AtomicDoubleArray3D;
41 import ffx.numerics.math.DoubleMath;
42 import ffx.potential.bonded.RendererCache.ViewModel;
43 import ffx.potential.parameters.AtomType;
44 import ffx.potential.parameters.BondType;
45 import ffx.potential.parameters.ForceField;
46 import org.jogamp.java3d.BranchGroup;
47 import org.jogamp.java3d.Geometry;
48 import org.jogamp.java3d.LineArray;
49 import org.jogamp.java3d.Shape3D;
50 import org.jogamp.java3d.Transform3D;
51 import org.jogamp.java3d.TransformGroup;
52 import org.jogamp.vecmath.AxisAngle4d;
53 import org.jogamp.vecmath.Vector3d;
54
55 import java.io.Serial;
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.logging.Logger;
59
60 import static ffx.numerics.math.DoubleMath.X;
61 import static ffx.numerics.math.DoubleMath.angle;
62 import static ffx.numerics.math.DoubleMath.length;
63 import static ffx.numerics.math.DoubleMath.normalize;
64 import static ffx.numerics.math.DoubleMath.scale;
65 import static ffx.numerics.math.DoubleMath.sub;
66 import static java.lang.String.format;
67 import static org.apache.commons.math3.util.FastMath.max;
68 import static org.apache.commons.math3.util.FastMath.min;
69
70
71
72
73
74
75
76 @SuppressWarnings("CloneableImplementsClone")
77 public class Bond extends BondedTerm {
78
79 @Serial
80 private static final long serialVersionUID = 1L;
81
82
83
84
85
86 static final float BUFF = 0.7f;
87
88 private static final Logger logger = Logger.getLogger(Bond.class.getName());
89
90 private static final float[] a0col = {
91 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f
92 };
93 private static final float[] f4a = {0.0f, 0.0f, 0.0f, 0.9f};
94 private static final float[] f4b = {0.0f, 0.0f, 0.0f, 0.9f};
95 private static final float[] f16 = {
96 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f
97 };
98 private static final double[] a13d = new double[3];
99 private static final double[] a23d = new double[3];
100 private static final double[] mid = new double[3];
101 private static final double[] diff3d = new double[3];
102 private static final double[] sum3d = new double[3];
103 private static final double[] coord = new double[12];
104 private static final double[] y = {0.0d, 1.0d, 0.0d};
105 private static final AxisAngle4d axisAngle = new AxisAngle4d();
106 private static final double[] bcross = new double[4];
107 private static final double[] cstart = new double[3];
108 private static final Vector3d pos3d = new Vector3d();
109
110
111
112 private final ArrayList<Bond> formsAngleWith = new ArrayList<>();
113
114
115
116 public BondType bondType = null;
117
118
119
120 private double rigidScale = 1.0;
121
122 private RendererCache.ViewModel viewModel = RendererCache.ViewModel.INVISIBLE;
123 private BranchGroup branchGroup;
124 private TransformGroup cy1tg, cy2tg;
125 private Transform3D cy1t3d, cy2t3d;
126 private Shape3D cy1, cy2;
127 private Vector3d scale;
128 private int detail = 3;
129 private LineArray la;
130 private int lineIndex;
131 private boolean wireVisible = true;
132
133
134
135
136
137
138 public Bond(String n) {
139 super(n);
140 }
141
142
143
144
145
146
147
148 public Bond(Atom a1, Atom a2) {
149 atoms = new Atom[2];
150 int i1 = a1.getIndex();
151 int i2 = a2.getIndex();
152 if (i1 < i2) {
153 atoms[0] = a1;
154 atoms[1] = a2;
155 } else {
156 atoms[0] = a2;
157 atoms[1] = a1;
158 }
159 setID_Key(false);
160 viewModel = RendererCache.ViewModel.WIREFRAME;
161 a1.setBond(this);
162 a2.setBond(this);
163 }
164
165
166
167
168
169
170
171
172 public static void logNoBondType(Atom a1, Atom a2, ForceField forceField) {
173 AtomType atomType1 = a1.getAtomType();
174 AtomType atomType2 = a2.getAtomType();
175 int[] c = {atomType1.atomClass, atomType2.atomClass};
176 String key = BondType.sortKey(c);
177 StringBuilder sb = new StringBuilder(
178 format(" No BondType for key: %s\n %s -> %s\n %s -> %s", key,
179 a1, atomType1, a2, atomType2));
180 int c1 = atomType1.atomClass;
181 int c2 = atomType2.atomClass;
182 List<AtomType> types1 = forceField.getSimilarAtomTypes(atomType1);
183 List<AtomType> types2 = forceField.getSimilarAtomTypes(atomType2);
184 List<BondType> bondTypes = new ArrayList<>();
185 boolean match = false;
186 for (AtomType type1 : types1) {
187 for (AtomType type2 : types2) {
188
189 if ((type1.atomClass != c1) && (type1.atomClass != c2) &&
190 (type2.atomClass != c1) && (type2.atomClass != c2)) {
191 continue;
192 }
193 BondType bondType = forceField.getBondType(type1, type2);
194 if (bondType != null && !bondTypes.contains(bondType)) {
195 if (!match) {
196 match = true;
197 sb.append("\n Similar Bond Types:");
198 }
199 bondTypes.add(bondType);
200 sb.append(format("\n %s", bondType));
201 }
202 }
203 }
204
205 logger.severe(sb.toString());
206 }
207
208
209
210
211 @Override
212 public int compareTo(BondedTerm b) {
213 if (b == null) {
214 throw new NullPointerException();
215 }
216 if (b == this) {
217 return 0;
218 }
219 if (!b.getClass().isInstance(this)) {
220 return super.compareTo(b);
221 }
222 int this0 = atoms[0].getIndex();
223 int a0 = b.atoms[0].getIndex();
224 if (this0 < a0) {
225 return -1;
226 }
227 if (this0 > a0) {
228 return 1;
229 }
230 int this1 = atoms[1].getIndex();
231 int a1 = b.atoms[1].getIndex();
232
233 return Integer.compare(this1, a1);
234 }
235
236
237
238
239
240
241 @Override
242 public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
243 value = 0.0;
244 energy = 0.0;
245
246 if (!getUse()) {
247 return energy;
248 }
249 var atomA = atoms[0];
250 var atomB = atoms[1];
251 var va = atomA.getXYZ();
252 var vb = atomB.getXYZ();
253 var vab = va.sub(vb);
254 value = vab.length();
255 var prefactor = bondType.bondUnit * rigidScale * bondType.forceConstant;
256
257 var dv = value - bondType.distance;
258 if (bondType.bondFunction.hasFlatBottom()) {
259 if (dv > 0) {
260 dv = max(0, dv - bondType.flatBottomRadius);
261 } else if (dv < 0) {
262 dv = min(0, dv + bondType.flatBottomRadius);
263 }
264 }
265 var dv2 = dv * dv;
266
267 energy = prefactor * dv2 * (1.0 + bondType.cubic * dv + bondType.quartic * dv2);
268 if (gradient) {
269
270 var dedr = 2.0 * prefactor * dv * (1.0 + 1.5 * bondType.cubic * dv + 2.0 * bondType.quartic * dv2);
271 var de = 0.0;
272 if (value > 0.0) {
273 de = dedr / value;
274 }
275 var ga = vab.scale(de);
276 var ia = atomA.getIndex() - 1;
277 var ib = atomB.getIndex() - 1;
278 grad.add(threadID, ia, ga);
279 grad.sub(threadID, ib, ga);
280 }
281 value = dv;
282 return energy;
283 }
284
285
286
287
288
289
290
291
292
293 public Atom get1_2(Atom a) {
294 if (a == atoms[0]) {
295 return atoms[1];
296 }
297 if (a == atoms[1]) {
298 return atoms[0];
299 }
300 return null;
301 }
302
303
304
305
306
307
308 public double getCurrentDistance() {
309 double[] x1 = new double[3];
310 x1 = atoms[0].getXYZ(x1);
311 double[] x2 = new double[3];
312 x2 = atoms[1].getXYZ(x2);
313 return DoubleMath.dist(x1, x2);
314 }
315
316
317
318
319 public void log() {
320 logger.info(
321 format(
322 " %-8s %6d-%s %6d-%s %6.4f %6.4f %10.4f",
323 "Bond",
324 atoms[0].getIndex(),
325 atoms[0].getAtomType().name,
326 atoms[1].getIndex(),
327 atoms[1].getAtomType().name,
328 bondType.distance,
329 value,
330 energy));
331 }
332
333
334
335
336 @Override
337 public void removeFromParent() {
338 super.removeFromParent();
339 cy1 = null;
340 cy2 = null;
341 cy1tg = null;
342 cy2tg = null;
343 if (cy1t3d != null) {
344 RendererCache.poolTransform3D(cy1t3d);
345 RendererCache.poolTransform3D(cy2t3d);
346 cy1t3d = null;
347 cy2t3d = null;
348 }
349 if (branchGroup != null) {
350 branchGroup.detach();
351 branchGroup.setUserData(null);
352 RendererCache.poolDoubleCylinder(branchGroup);
353 branchGroup = null;
354 }
355 }
356
357
358
359
360
361
362 public void setBondType(BondType bondType) {
363 this.bondType = bondType;
364 }
365
366
367
368
369
370
371 public BondType getBondType() {
372 return bondType;
373 }
374
375
376
377
378
379
380 public void setColor(Atom a) {
381 if (viewModel != ViewModel.INVISIBLE
382 && viewModel != ViewModel.WIREFRAME
383 && branchGroup != null) {
384 if (a == atoms[0]) {
385 cy1.setAppearance(a.getAtomAppearance());
386 } else if (a == atoms[1]) {
387 cy2.setAppearance(a.getAtomAppearance());
388 }
389 }
390 setWireVisible(wireVisible);
391 }
392
393
394
395
396
397
398 public void setRigidScale(double rigidScale) {
399 this.rigidScale = rigidScale;
400 }
401
402
403
404
405
406
407 @Override
408 public void setView(RendererCache.ViewModel newViewModel, List<BranchGroup> newShapes) {
409 switch (newViewModel) {
410 case WIREFRAME -> {
411 viewModel = ViewModel.WIREFRAME;
412 setWireVisible(true);
413 setCylinderVisible(false, newShapes);
414 }
415 case SPACEFILL, INVISIBLE, RMIN -> {
416 viewModel = ViewModel.INVISIBLE;
417 setWireVisible(false);
418 setCylinderVisible(false, newShapes);
419 }
420 case RESTRICT -> {
421 if (!atoms[0].isSelected() || !atoms[1].isSelected()) {
422 viewModel = ViewModel.INVISIBLE;
423 setWireVisible(false);
424 setCylinderVisible(false, newShapes);
425 }
426 }
427 case BALLANDSTICK, TUBE -> {
428 viewModel = newViewModel;
429
430 double rad;
431 double len = getValue() / 2.0d;
432 if (viewModel == ViewModel.BALLANDSTICK) {
433 rad = 0.1d * RendererCache.radius;
434 } else {
435 rad = 0.2d * RendererCache.radius;
436 }
437 if (scale == null) {
438 scale = new Vector3d();
439 }
440 scale.set(rad, len, rad);
441 setWireVisible(false);
442 setCylinderVisible(true, newShapes);
443 }
444 case DETAIL -> {
445 int res = RendererCache.detail;
446 if (res != detail) {
447 detail = res;
448 if (branchGroup != null) {
449 Geometry geom1 = RendererCache.getCylinderGeom(0, detail);
450 Geometry geom2 = RendererCache.getCylinderGeom(1, detail);
451 Geometry geom3 = RendererCache.getCylinderGeom(2, detail);
452 cy1.removeAllGeometries();
453 cy2.removeAllGeometries();
454 cy1.addGeometry(geom1);
455 cy1.addGeometry(geom2);
456 cy1.addGeometry(geom3);
457 cy2.addGeometry(geom1);
458 cy2.addGeometry(geom2);
459 cy2.addGeometry(geom3);
460 }
461 }
462 if (scale == null) {
463 scale = new Vector3d();
464 }
465 double newRadius;
466 if (viewModel == ViewModel.BALLANDSTICK) {
467 newRadius = 0.1d * RendererCache.radius;
468 } else if (viewModel == ViewModel.TUBE) {
469 newRadius = 0.2d * RendererCache.radius;
470 } else {
471 break;
472 }
473 if (newRadius != scale.x) {
474 scale.x = newRadius;
475 scale.y = newRadius;
476 if (branchGroup != null) {
477 setView(viewModel, newShapes);
478 }
479 }
480 }
481 case SHOWHYDROGEN -> {
482 if (atoms[0].getAtomicNumber() == 1 || atoms[1].getAtomicNumber() == 1) {
483 setView(viewModel, newShapes);
484 }
485 }
486 case HIDEHYDROGEN -> {
487 if (atoms[0].getAtomicNumber() == 1 || atoms[1].getAtomicNumber() == 1) {
488 viewModel = ViewModel.INVISIBLE;
489 setWireVisible(false);
490 setCylinderVisible(false, newShapes);
491 }
492 }
493 case FILL, POINTS, LINES -> {
494 if (branchGroup != null && viewModel != ViewModel.INVISIBLE) {
495 cy1.setAppearance(atoms[0].getAtomAppearance());
496 cy2.setAppearance(atoms[1].getAtomAppearance());
497 }
498 }
499 }
500 }
501
502
503
504
505
506
507
508 public void setWire(LineArray l, int i) {
509 la = l;
510 lineIndex = i;
511 }
512
513
514
515
516
517
518 @Override
519 public void update() {
520
521 atoms[0].getXYZ(a13d);
522 atoms[1].getXYZ(a23d);
523 sub(a13d, a23d, diff3d);
524 double d = length(diff3d);
525 setValue(d);
526 DoubleMath.add(a13d, a23d, sum3d);
527 scale(sum3d, 0.5d, mid);
528
529 if (la != null) {
530 for (int i = 0; i < 3; i++) {
531 coord[i] = a13d[i];
532 coord[3 + i] = mid[i];
533 coord[6 + i] = mid[i];
534 coord[9 + i] = a23d[i];
535 }
536 la.setCoordinates(lineIndex, coord);
537 }
538
539 if (branchGroup != null) {
540 normalize(diff3d, diff3d);
541 scale.y = d / 2.0d;
542 setBondTransform3d(cy1t3d, mid, diff3d, d, true);
543 scale(diff3d, -1.0d, diff3d);
544 setBondTransform3d(cy2t3d, mid, diff3d, d, false);
545 cy1tg.setTransform(cy1t3d);
546 cy2tg.setTransform(cy2t3d);
547 }
548 }
549
550
551
552
553
554
555
556 boolean formsAngleWith(Bond b) {
557 for (Bond bond : formsAngleWith) {
558 if (b == bond) {
559 return true;
560 }
561 }
562 return false;
563 }
564
565
566
567
568
569
570
571
572 Atom getCommonAtom(Bond b) {
573 if (b == this || b == null) {
574 return null;
575 }
576 if (b.atoms[0] == atoms[0]) {
577 return atoms[0];
578 }
579 if (b.atoms[0] == atoms[1]) {
580 return atoms[1];
581 }
582 if (b.atoms[1] == atoms[0]) {
583 return atoms[0];
584 }
585 if (b.atoms[1] == atoms[1]) {
586 return atoms[1];
587 }
588 return null;
589 }
590
591
592
593
594
595
596
597
598 Atom getOtherAtom(Bond b) {
599 if (b == this || b == null) {
600 return null;
601 }
602 if (b.atoms[0] == atoms[0]) {
603 return atoms[1];
604 }
605 if (b.atoms[0] == atoms[1]) {
606 return atoms[0];
607 }
608 if (b.atoms[1] == atoms[0]) {
609 return atoms[1];
610 }
611 if (b.atoms[1] == atoms[1]) {
612 return atoms[0];
613 }
614 return null;
615 }
616
617
618
619
620
621
622 boolean sameGroup() {
623 return atoms[0].getParent() == atoms[1].getParent();
624 }
625
626
627
628
629
630
631 void setAngleWith(Bond b) {
632 formsAngleWith.add(b);
633 }
634
635
636
637
638
639
640 private void initJ3D(List<BranchGroup> newShapes) {
641 detail = RendererCache.detail;
642 branchGroup = RendererCache.doubleCylinderFactory(atoms[0], atoms[1], detail);
643 cy1tg = (TransformGroup) branchGroup.getChild(0);
644 cy2tg = (TransformGroup) branchGroup.getChild(1);
645 cy1 = (Shape3D) cy1tg.getChild(0);
646 cy2 = (Shape3D) cy2tg.getChild(0);
647 newShapes.add(branchGroup);
648 cy1t3d = RendererCache.transform3DFactory();
649 cy2t3d = RendererCache.transform3DFactory();
650 update();
651 }
652
653
654
655
656
657
658
659
660
661
662 private void setBondTransform3d(
663 Transform3D t3d, double[] pos, double[] orient, double len, boolean newRot) {
664
665 if (newRot) {
666 double angle = angle(orient, y);
667 X(y, orient, bcross);
668 bcross[3] = angle - Math.PI;
669 axisAngle.set(bcross);
670 }
671
672
673 scale(orient, len / 4.0d, cstart);
674 DoubleMath.add(cstart, pos, cstart);
675 pos3d.set(cstart);
676 t3d.setTranslation(pos3d);
677 t3d.setRotation(axisAngle);
678 t3d.setScale(scale);
679 }
680
681
682
683
684
685
686
687 private void setCylinderVisible(boolean visible, List<BranchGroup> newShapes) {
688 if (!visible) {
689
690 if (branchGroup != null) {
691 cy1.setPickable(false);
692 cy1.setAppearance(RendererCache.nullAp);
693 cy2.setPickable(false);
694 cy2.setAppearance(RendererCache.nullAp);
695
696 }
697 } else if (branchGroup == null) {
698
699 initJ3D(newShapes);
700 } else {
701
702 cy1t3d.setScale(scale);
703 cy1tg.setTransform(cy1t3d);
704 cy2t3d.setScale(scale);
705 cy2tg.setTransform(cy2t3d);
706 cy1.setAppearance(atoms[0].getAtomAppearance());
707 cy2.setAppearance(atoms[1].getAtomAppearance());
708 }
709 }
710
711
712
713
714
715
716 private void setWireVisible(boolean visible) {
717 if (!visible) {
718 wireVisible = false;
719 la.setColors(lineIndex, a0col);
720 } else {
721 wireVisible = true;
722 float[] cols = f16;
723 float[] col1 = f4a;
724 float[] col2 = f4b;
725 atoms[0].getAtomColor().get(col1);
726 atoms[1].getAtomColor().get(col2);
727 for (int i = 0; i < 3; i++) {
728 cols[i] = col1[i];
729 cols[4 + i] = col1[i];
730 cols[8 + i] = col2[i];
731 cols[12 + i] = col2[i];
732 }
733 la.setColors(lineIndex, cols);
734 }
735 }
736 }