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 }