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  package ffx.potential.bonded;
39  
40  import ffx.numerics.Constraint;
41  import ffx.potential.bonded.Atom.Resolution;
42  import org.jogamp.java3d.BranchGroup;
43  import org.jogamp.java3d.Material;
44  import org.jogamp.vecmath.Color3f;
45  
46  import java.io.Serial;
47  import java.util.ArrayList;
48  import java.util.Comparator;
49  import java.util.List;
50  import java.util.Objects;
51  import java.util.logging.Logger;
52  
53  /**
54   * The BondedTerm class is extended by all Valence Geometry classes (bond, angle, dihedral, torsion,
55   * etc.).
56   *
57   * @author Michael J. Schnieders
58   * @since 1.0
59   */
60  public abstract class BondedTerm extends MSNode implements BondedEnergy, Comparable<BondedTerm> {
61  
62    @Serial
63    private static final long serialVersionUID = 1L;
64  
65    private static final Logger logger = Logger.getLogger(BondedTerm.class.getName());
66    /**
67     * This method sets the Term's id and key by concatenating the respective id and keys of the Atoms
68     * that are used in forming the term. Order can be reversed for help in assigning force field
69     * parameters for the Term.
70     */
71    private static final StringBuilder idTemp = new StringBuilder();
72    /**
73     * Constant <code>bondedComparator</code>
74     */
75    private static final BondedComparator bondedComparator = new BondedComparator();
76    /**
77     * ID of this BondedTerm.
78     */
79    protected String id;
80    /**
81     * Atoms that are used to form this term.
82     */
83    protected Atom[] atoms;
84    /**
85     * Bonds that are used to form this term.
86     */
87    protected Bond[] bonds;
88    /**
89     * Value of the term (e.g. bond length, angle, dihedral angle, etc).
90     */
91    protected double value;
92    /**
93     * Energy of the term (kcal/mol).
94     */
95    protected double energy;
96    /**
97     * Flag indicating if this term is constrained.
98     */
99    private boolean isConstrained = false;
100 
101   /**
102    * Default Constructor
103    */
104   public BondedTerm() {
105     super("", 1);
106     setAllowsChildren(false);
107   }
108 
109   /**
110    * Constructor which sets the Term's id.
111    *
112    * @param i a {@link java.lang.String} object.
113    */
114   public BondedTerm(String i) {
115     this();
116     id = i;
117   }
118 
119   /**
120    * Check if any atom of this BondedTerm has the Lambda flag set.
121    *
122    * @return True if Lambda is applied to one of the BondedTerm atoms.
123    */
124   public boolean applyLambda() {
125     for (Atom atom : atoms) {
126       if (atom.applyLambda()) {
127         return true;
128       }
129     }
130     return false;
131   }
132 
133   /**
134    * {@inheritDoc}
135    */
136   @Override
137   public int compareTo(BondedTerm t) {
138     return Objects.compare(this, t, bondedComparator);
139   }
140 
141   /**
142    * containsHydrogen
143    *
144    * @return a boolean.
145    */
146   public boolean containsHydrogen() {
147     for (Atom atom : atoms) {
148       if (atom.isHydrogen()) {
149         return true;
150       }
151     }
152     return false;
153   }
154 
155   /**
156    * This method returns true if any atom is marked as being part of a neural network.
157    *
158    * @return True if any atom is marked as being part of a neural network.
159    */
160   public boolean isNeuralNetwork() {
161     for (Atom atom : atoms) {
162       if (atom.isNeuralNetwork()) {
163         return true;
164       }
165     }
166     return false;
167   }
168 
169   /**
170    * This method removes terms from a list that are marked as being part of a neural network.
171    *
172    * @param list The list to check.
173    * @param <T>  The Bonded term class.
174    */
175   public static <T extends BondedTerm> void removeNeuralNetworkTerms(List<T> list) {
176     // Remove terms handled by a neutral network.
177     List<T> removeList = new ArrayList<>();
178     for (T term : list) {
179       if (term.isNeuralNetwork()) {
180         removeList.add(term);
181       }
182     }
183     list.removeAll(removeList);
184   }
185 
186   /**
187    * Checks if at least one atom in this BondedTerm is of the given resolution.
188    *
189    * @param resolution a {@link ffx.potential.bonded.Atom.Resolution} object.
190    * @return true if at least one atom in this term is of the specified resolution.
191    */
192   public boolean containsResolution(Resolution resolution) {
193     for (Atom atom : atoms) {
194       if (atom.getResolution() == resolution) {
195         return true;
196       }
197     }
198     return false;
199   }
200 
201   /**
202    * {@inheritDoc}
203    */
204   @Override
205   public boolean destroy() {
206     super.destroy();
207     id = null;
208     value = 0;
209     return true;
210   }
211 
212   /**
213    * {@inheritDoc}
214    *
215    * <p>Overridden method that returns true if object is equals to this, is of the same Class and
216    * has the same id.
217    */
218   @Override
219   public final boolean equals(Object object) {
220     if (this == object) {
221       return true;
222     } else if (object == null || getClass() != object.getClass()) {
223       return false;
224     }
225     BondedTerm other = (BondedTerm) object;
226     return getID().equals(other.getID());
227   }
228 
229   /**
230    * Get the constituent Atom specified by index.
231    *
232    * @param index The index of the Atom to return.
233    * @return a {@link ffx.potential.bonded.Atom} object.
234    */
235   public Atom getAtom(int index) {
236     if (index >= 0 && index < atoms.length) {
237       return atoms[index];
238     }
239     return null;
240   }
241 
242   /**
243    * Returns a reference to the Atoms contained in this BondedTerm, regardless of whether they are child nodes in
244    * the tree structure.
245    *
246    * @return Atoms in this BondedTerm
247    */
248   public Atom[] getAtoms() {
249     return atoms;
250   }
251 
252 
253   /**
254    * Returns all Atoms contained in this BondedTerm, regardless of whether they are child nodes in
255    * the tree structure. Returns a new array, not a reference to the original array.
256    *
257    * @return Atoms in this BondedTerm
258    */
259   public Atom[] getAtomArray() {
260     return getAtomArray(true);
261   }
262 
263   /**
264    * Returns all Atoms contained in this BondedTerm, regardless of whether they are child nodes in
265    * the tree structure.
266    *
267    * @param returnCopy If true, return a new copy of the Atom array.
268    * @return Atoms in this BondedTerm
269    */
270   public Atom[] getAtomArray(boolean returnCopy) {
271     if (returnCopy) {
272       int nAtoms = atoms.length;
273       Atom[] retAtoms = new Atom[nAtoms];
274       System.arraycopy(atoms, 0, retAtoms, 0, nAtoms);
275       return retAtoms;
276     } else {
277       return atoms;
278     }
279   }
280 
281   /**
282    * Get the constituent Bond specified by index.
283    *
284    * @param index The index of the Bond to return.
285    * @return a {@link ffx.potential.bonded.Bond} object.
286    */
287   public Bond getBond(int index) {
288     if (index >= 0 && index < atoms.length) {
289       return bonds[index];
290     }
291     return null;
292   }
293 
294   /**
295    * Get the Term's id.
296    *
297    * @return a {@link java.lang.String} object.
298    */
299   public String getID() {
300     return id;
301   }
302 
303   /**
304    * This method returns true if any atom is being used.
305    *
306    * @return True if any atom is being used.
307    */
308   public boolean getUse() {
309     for (Atom atom : atoms) {
310       if (atom.getUse()) {
311         return true;
312       }
313     }
314     return false;
315   }
316 
317   /**
318    * Sets the Term's id.
319    *
320    * @param i a {@link java.lang.String} object.
321    */
322   public void setID(String i) {
323     id = i;
324   }
325 
326   /**
327    * Get the Term's value.
328    *
329    * @return a double.
330    */
331   public double getValue() {
332     return value;
333   }
334 
335   /**
336    * Sets the Term's value.
337    *
338    * @param v a double.
339    */
340   public void setValue(double v) {
341     value = v;
342   }
343 
344   /**
345    * {@inheritDoc}
346    */
347   @Override
348   public int hashCode() {
349     return Objects.hash(getID());
350   }
351 
352   /**
353    * Check if this BondedTerm is constrained.
354    *
355    * @return If constrained.
356    */
357   public boolean isConstrained() {
358     return isConstrained;
359   }
360 
361   /**
362    * Check if this BondedTerm is lambda-sensitive (e.g., a softcore dihedral).
363    *
364    * @return True if Lambda affects the energy of this term.
365    */
366   public boolean isLambdaScaled() {
367     return false;
368   }
369 
370   /**
371    * {@inheritDoc}
372    *
373    * <p>Prints the toString method to stdout
374    */
375   @Override
376   public void print() {
377     logger.info(toString());
378   }
379 
380   /**
381    * Add a constituent Atom to the Term.
382    *
383    * @param a an array of {@link ffx.potential.bonded.Atom} objects.
384    */
385   public void setAtoms(Atom[] a) {
386     atoms = a;
387   }
388 
389   /**
390    * Add constituent Bonds to the Term.
391    *
392    * @param b an array of {@link ffx.potential.bonded.Bond} objects.
393    */
394   public void setBonds(Bond[] b) {
395     bonds = b;
396   }
397 
398   /**
399    * {@inheritDoc}
400    */
401   @Override
402   public void setColor(RendererCache.ColorModel newColorModel, Color3f color, Material mat) {
403     if (atoms == null) {
404       return;
405     }
406     for (Atom atom : atoms) {
407       atom.setColor(newColorModel, color, mat);
408     }
409   }
410 
411   /**
412    * Sets the Constraint on this bond (clearing it if null). May recursively set the Constraint on
413    * component terms (i.e. an Angle will call setConstraint on its component Bonds).
414    *
415    * @param c Constraint or null to clear.
416    */
417   public void setConstraint(Constraint c) {
418     isConstrained = c != null;
419   }
420 
421   /**
422    * setID_Key
423    *
424    * @param reverse a boolean.
425    */
426   public final void setID_Key(boolean reverse) {
427     if (atoms == null) {
428       return;
429     }
430     // Reuse the string buffers
431     idTemp.delete(0, idTemp.length());
432     for (int i = 0; i < atoms.length; i++) {
433       Atom a = (reverse) ? atoms[atoms.length - 1 - i] : atoms[i];
434       if (i != 0) {
435         idTemp.append("  ");
436       }
437       idTemp.append(a.describe(Atom.Descriptions.XyzIndex_Name));
438     }
439     id = idTemp.toString().intern();
440   }
441 
442   /**
443    * {@inheritDoc}
444    */
445   @Override
446   public void setSelected(boolean b) {
447     super.setSelected(b);
448     if (atoms == null) {
449       return;
450     }
451     for (Atom a : atoms) {
452       a.setSelected(b);
453     }
454     if (!(this instanceof Bond)) {
455       if (bonds == null) {
456         return;
457       }
458       for (Bond bond : bonds) {
459         bond.setSelected(b);
460       }
461     }
462   }
463 
464   /**
465    * {@inheritDoc}
466    */
467   @Override
468   public void setView(RendererCache.ViewModel newViewModel, List<BranchGroup> newShapes) {
469     if (atoms == null) {
470       return;
471     }
472     for (Atom atom : atoms) {
473       atom.setView(newViewModel, newShapes);
474     }
475     if (bonds == null) {
476       return;
477     }
478     for (Bond bond : bonds) {
479       bond.setView(newViewModel, newShapes);
480     }
481   }
482 
483   /**
484    * {@inheritDoc}
485    *
486    * <p>Overridden toString Method returns the Term's id.
487    */
488   @Override
489   public String toString() {
490     return String.format("%s  (%7.2f,%7.2f)", id, value, energy);
491   }
492 
493   /**
494    * Check if all atoms of this BondedTerm have the Lambda flag set.
495    *
496    * @return True if Lambda is applied to all BondedTerm atoms.
497    */
498   boolean applyAllLambda() {
499     for (Atom atom : atoms) {
500       if (!atom.applyLambda()) {
501         return false;
502       }
503     }
504     return true;
505   }
506 
507   /**
508    * containsAtom
509    *
510    * @param atom a {@link ffx.potential.bonded.Atom} object.
511    * @return a boolean.
512    */
513   boolean containsAtom(Atom atom) {
514     for (Atom a : atoms) {
515       if (a.equals(atom)) {
516         return true;
517       }
518     }
519     return false;
520   }
521 
522   public static class BondedComparator implements Comparator<BondedTerm> {
523 
524     private static final List<Class<? extends BondedTerm>> naturalOrder =
525         new ArrayList<>() {
526           {
527             add(Bond.class);
528             add(Angle.class);
529             add(StretchBend.class);
530             add(UreyBradley.class);
531             add(OutOfPlaneBend.class);
532             add(Torsion.class);
533             add(ImproperTorsion.class);
534             add(PiOrbitalTorsion.class);
535             add(StretchTorsion.class);
536             add(AngleTorsion.class);
537             add(TorsionTorsion.class);
538           }
539         };
540 
541     private BondedComparator() {
542     } // singleton
543 
544     /**
545      * Sort using position in the naturalOrder list; fallback to alphabetical.
546      */
547     @Override
548     public int compare(BondedTerm bondedTerm1, BondedTerm bondedTerm2) {
549       final Class<? extends BondedTerm> class1 = bondedTerm2.getClass();
550       final Class<? extends BondedTerm> class2 = bondedTerm1.getClass();
551       int order1 = naturalOrder.indexOf(bondedTerm1.getClass());
552       int order2 = naturalOrder.indexOf(bondedTerm2.getClass());
553       if (order1 >= 0 && order2 >= 0) {
554         return Integer.compare(order1, order2);
555       } else {
556         return String.CASE_INSENSITIVE_ORDER.compare(class2.toString(), class1.toString());
557       }
558     }
559   }
560 }