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 org.jogamp.java3d.BranchGroup;
41  import org.jogamp.java3d.Canvas3D;
42  import org.jogamp.java3d.J3DGraphics2D;
43  import org.jogamp.java3d.Material;
44  import org.jogamp.java3d.Node;
45  import org.jogamp.vecmath.Color3f;
46  
47  import javax.swing.tree.DefaultMutableTreeNode;
48  import javax.swing.tree.TreeNode;
49  import java.io.Serial;
50  import java.util.ArrayList;
51  import java.util.Collections;
52  import java.util.Enumeration;
53  import java.util.List;
54  import java.util.Objects;
55  
56  /**
57   * The MSNode class forms the basic unit that all data classes extend.
58   *
59   * @author Michael J. Schnieders
60   * @since 1.0
61   */
62  @SuppressWarnings("CloneableImplementsClone")
63  public class MSNode extends DefaultMutableTreeNode implements ROLS {
64  
65    @Serial
66    private static final long serialVersionUID = 1L;
67  
68    /** The multiscale level of this node. */
69    private final int MultiScaleLevel;
70    /** True if this node is selected. */
71    protected boolean selected = false;
72    /** The name of this node. */
73    private String name;
74    /** Total mass of this node and its children. */
75    private double totalMass;
76  
77    /** Default MSNode Constructor */
78    public MSNode() {
79      name = "";
80      MultiScaleLevel = ROLS.MaxLengthScale;
81    }
82  
83    /**
84     * Constructs a MSNode with the name n.
85     *
86     * @param n a {@link java.lang.String} object.
87     */
88    public MSNode(String n) {
89      name = n;
90      MultiScaleLevel = ROLS.MaxLengthScale;
91    }
92  
93    /**
94     * Constructor for MSNode.
95     *
96     * @param n a {@link java.lang.String} object.
97     * @param multiScaleLevel The multiscale level of this node.
98     */
99    public MSNode(String n, int multiScaleLevel) {
100     this.name = n;
101     this.MultiScaleLevel = multiScaleLevel;
102   }
103 
104   /**
105    * If <code>this</code> MSNode or any MSNode below it <code>equals</code> the argument, that MSNode
106    * is returned.
107    *
108    * @param msNode a {@link ffx.potential.bonded.MSNode} object.
109    * @return a {@link ffx.potential.bonded.MSNode} object.
110    */
111   public MSNode contains(MSNode msNode) {
112     @SuppressWarnings("unchecked")
113     Enumeration<TreeNode> e = depthFirstEnumeration();
114     List<TreeNode> list = Collections.list(e);
115     for (TreeNode node : list) {
116       if (node.equals(msNode)) {
117         return (MSNode) node;
118       }
119     }
120     return null;
121   }
122 
123   /**
124    * destroy
125    *
126    * @return a boolean.
127    */
128   public boolean destroy() {
129     if (getParent() != null) {
130       removeFromParent();
131     }
132     name = null;
133     selected = false;
134     return true;
135   }
136 
137   /** {@inheritDoc} */
138   @Override
139   public void drawLabel(Canvas3D graphics, J3DGraphics2D g2d, Node node) {
140     if (!isSelected()) {
141       return;
142     }
143     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
144       MSNode dataNode = (MSNode) e.nextElement();
145       dataNode.drawLabel(graphics, g2d, node);
146     }
147   }
148 
149   /**
150    * {@inheritDoc}
151    *
152    * <p>MSNode equality := same class and same name. Consider replacing with a
153    * Comparator&lt;MSNode&gt; for cases where non-reference equality is desired.
154    */
155   @Override
156   public boolean equals(Object o) {
157     if (this == o) {
158       return true;
159     }
160     if (o == null || getClass() != o.getClass()) {
161       return false;
162     }
163     MSNode msNode = (MSNode) o;
164     return Objects.equals(name, msNode.getName());
165   }
166 
167   /**
168    * Returns a List of all Angles below the present MSNode.
169    *
170    * @return a {@link java.util.List} object.
171    */
172   public List<Angle> getAngleList() {
173     return getList(Angle.class);
174   }
175 
176   /**
177    * Returns a List of all AngleTorsions below the present MSNode.
178    *
179    * @return a {@link java.util.List} object.
180    */
181   public List<AngleTorsion> getAngleTorsionList() {
182     return getList(AngleTorsion.class);
183   }
184 
185   /**
186    * Returns a List of all Atoms below the present MSNode.
187    *
188    * @return a new {@link java.util.List} object.
189    */
190   public List<Atom> getAtomList() {
191     List<Atom> atomList = getList(Atom.class);
192     Collections.sort(atomList);
193     return atomList;
194   }
195 
196   /**
197    * Returns the first active heavy atom in the list of atoms for the current structure.
198    * Heavy atoms are defined as non-hydrogen atoms.
199    *
200    * @return The first heavy atom found in the structure or null if no active heavy atom is present.
201    */
202   public Atom getFirstActiveHeavyAtom() {
203     List<Atom> atomList = getAtomList();
204     for (Atom atom : atomList) {
205       if (atom.isHeavy() && atom.isActive()) {
206         return atom;
207       }
208     }
209     return null;
210   }
211 
212   /**
213    * getAtomList.
214    *
215    * @param originalOrder a boolean.
216    * @return a {@link java.util.List} object.
217    */
218   public List<Atom> getAtomList(boolean originalOrder) {
219     // As of now, for generic MSNode objects, atoms remain in their original
220     // order. It is presently only a concern for MultiResidue.
221     return getAtomList();
222   }
223 
224   /**
225    * Returns a List of all Bonds below the present MSNode.
226    *
227    * @return a {@link java.util.List} object.
228    */
229   public List<Bond> getBondList() {
230     return getList(Bond.class);
231   }
232 
233   /** {@inheritDoc} */
234   @Override
235   public double[] getCenter(boolean w) {
236     double[] Rc = {0, 0, 0};
237     double sum = 0, mass = 1;
238     List<Atom> atomList = getAtomList();
239     for (Atom a : atomList) {
240       if (w) {
241         mass = a.getMass();
242         sum += mass;
243       }
244       Rc[0] += mass * a.getX();
245       Rc[1] += mass * a.getY();
246       Rc[2] += mass * a.getZ();
247     }
248     if (!w) {
249       sum = atomList.size();
250     }
251     for (int i = 0; i < 3; i++) {
252       Rc[i] /= sum;
253     }
254     return Rc;
255   }
256 
257   /**
258    * Returns a List of the MSNode's Children (instead of using an Enumeration).
259    *
260    * @return a {@link java.util.List} object.
261    */
262   public List<MSNode> getChildList() {
263     List<MSNode> l = new ArrayList<>();
264     Enumeration<TreeNode> e = children();
265     while (e.hasMoreElements()) {
266       l.add((MSNode) e.nextElement());
267     }
268     return l;
269   }
270 
271   /**
272    * getExtent
273    *
274    * @return a double.
275    */
276   public double getExtent() {
277     double extent = 0.0;
278     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
279       MSNode node = (MSNode) e.nextElement();
280       double temp = node.getExtent();
281       if (temp > extent) {
282         extent = temp;
283       }
284     }
285     return extent;
286   }
287 
288   /**
289    * Returns a List of all ImproperTorsions below the present MSNode.
290    *
291    * @return a {@link java.util.List} object.
292    */
293   public List<ImproperTorsion> getImproperTorsionList() {
294     return getList(ImproperTorsion.class);
295   }
296 
297   /**
298    * Returns a List of all MSNodes below the present MSNode.
299    *
300    * @return a {@link java.util.List} object.
301    */
302   public <T extends TreeNode> List<T> getList(Class<T> c) {
303     return getList(c, new ArrayList<>());
304   }
305 
306   /** {@inheritDoc} */
307   @Override
308   public <T extends TreeNode> List<T> getList(Class<T> c, List<T> nodes) {
309     if (c.isInstance(this)) {
310       nodes.add(c.cast(this));
311     }
312     if (isLeaf() || !canBeChild(c)) {
313       return nodes;
314     }
315     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
316       MSNode node = (MSNode) e.nextElement();
317       node.getList(c, nodes);
318     }
319     return nodes;
320   }
321 
322   /** {@inheritDoc} */
323   @Override
324   public <T extends TreeNode> long getMSCount(Class<T> c, long count) {
325     if (c.isInstance(this)) {
326       count++;
327     }
328     if (!canBeChild(c)) {
329       return count;
330     }
331     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
332       MSNode node = (MSNode) e.nextElement();
333       count += node.getMSCount(c, count);
334     }
335     return count;
336   }
337 
338   /** {@inheritDoc} */
339   @Override
340   public <T extends TreeNode> T getMSNode(Class<T> c) {
341     TreeNode[] nodes = getPath();
342     for (TreeNode n : nodes) {
343       if (c.isInstance(n)) {
344         return c.cast(n);
345       }
346     }
347     return null;
348   }
349 
350   /** {@inheritDoc} */
351   @Override
352   public double getMW() {
353     double weight = 0.0;
354     for (Atom atom : getAtomList()) {
355       weight += atom.getMass();
356     }
357     return weight;
358   }
359 
360   /**
361    * Returns the name of this MSNode.
362    *
363    * @return a {@link java.lang.String} object.
364    */
365   public String getName() {
366     return name;
367   }
368 
369   /**
370    * Sets the name of this NodeObject to n.
371    *
372    * @param n a {@link java.lang.String} object.
373    */
374   public void setName(String n) {
375     name = n;
376   }
377 
378   /**
379    * Returns a List of all Out-of-Plane Bends below the present MSNode.
380    *
381    * @return a {@link java.util.List} object.
382    */
383   public List<OutOfPlaneBend> getOutOfPlaneBendList() {
384     return getList(OutOfPlaneBend.class);
385   }
386 
387   /**
388    * Returns a List of all Pi-Orbital Torsions below the present MSNode.
389    *
390    * @return a {@link java.util.List} object.
391    */
392   public List<PiOrbitalTorsion> getPiOrbitalTorsionList() {
393     return getList(PiOrbitalTorsion.class);
394   }
395 
396   /**
397    * Returns a List of all Stretch-Bends below the present MSNode.
398    *
399    * @return a {@link java.util.List} object.
400    */
401   public List<StretchBend> getStretchBendList() {
402     return getList(StretchBend.class);
403   }
404 
405   /**
406    * Returns a List of all StretchTorsions below the present MSNode.
407    *
408    * @return a {@link java.util.List} object.
409    */
410   public List<StretchTorsion> getStretchTorsionList() {
411     return getList(StretchTorsion.class);
412   }
413 
414   /**
415    * Returns a List of all Torsions below the present MSNode.
416    *
417    * @return a {@link java.util.List} object.
418    */
419   public List<Torsion> getTorsionList() {
420     return getList(Torsion.class);
421   }
422 
423   /**
424    * Returns a List of all Torsion-Torsions below the present MSNode.
425    *
426    * @return a {@link java.util.List} object.
427    */
428   public List<TorsionTorsion> getTorsionTorsionList() {
429     return getList(TorsionTorsion.class);
430   }
431 
432   /**
433    * Returns the total mass of all atoms in the MolecularAssembly, calculating the mass if it has not
434    * already been done, defaulting to simple addition.
435    *
436    * @return Total mass of atoms in system.
437    */
438   public double getTotalMass() {
439     if (totalMass == 0.0) {
440       return getTotalMass(true, false);
441     }
442     return totalMass;
443   }
444 
445   /**
446    * Returns a List of all Urey-Bradleys below the present MSNode.
447    *
448    * @return a {@link java.util.List} object.
449    */
450   public List<UreyBradley> getUreyBradleyList() {
451     return getList(UreyBradley.class);
452   }
453 
454   /** {@inheritDoc} */
455   @Override
456   public int hashCode() {
457     return Objects.hash(name);
458   }
459 
460   /**
461    * isSelected
462    *
463    * @return a boolean.
464    */
465   public boolean isSelected() {
466     return selected;
467   }
468 
469   /**
470    * Setter for the field <code>selected</code>.
471    *
472    * @param b a boolean.
473    */
474   public void setSelected(boolean b) {
475     selected = b;
476     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
477       MSNode node = (MSNode) e.nextElement();
478       node.setSelected(b);
479     }
480   }
481 
482   /** Prints the MSNode's name */
483   public void print() {
484     System.out.println(name);
485   }
486 
487   /**
488    * removeChild.
489    *
490    * @param child a {@link ffx.potential.bonded.MSNode} object.
491    */
492   public void removeChild(MSNode child) {
493     if (child != null && child.getParent() == this) {
494       remove(child);
495     }
496   }
497 
498   /** {@inheritDoc} */
499   @Override
500   public void setColor(RendererCache.ColorModel colorModel, Color3f color, Material mat) {
501     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
502       MSNode node = (MSNode) e.nextElement();
503       node.setColor(colorModel, color, mat);
504     }
505   }
506 
507   /** {@inheritDoc} */
508   @Override
509   public void setView(RendererCache.ViewModel viewModel, List<BranchGroup> newShapes) {
510     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
511       MSNode node = (MSNode) e.nextElement();
512       node.setView(viewModel, newShapes);
513     }
514   }
515 
516   /**
517    * {@inheritDoc}
518    *
519    * <p>Overridden toString method returns the MSNode's name
520    */
521   @Override
522   public String toString() {
523     return name;
524   }
525 
526   /** {@inheritDoc} */
527   @Override
528   public void update() {
529     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
530       MSNode node = (MSNode) e.nextElement();
531       node.update();
532     }
533   }
534 
535   /**
536    * Calculates the total mass of all atoms in the MolecularAssembly, using either a simple addition
537    * or the Kahan summation algorithm. The simple algorithm is a standard loop to add up the masses.
538    *
539    * @param recalculate Force recalculation
540    * @param useKahan Use Kahan or simple addition algorithms
541    * @return Total mass of all atoms in system.
542    */
543   private double getTotalMass(boolean recalculate, boolean useKahan) {
544     if (recalculate) {
545       List<Atom> atoms = getAtomList();
546       if (atoms.isEmpty()) {
547         totalMass = 0.0;
548       } else if (useKahan) {
549         totalMass = kahanSumMasses(atoms);
550       } else {
551         totalMass = sumMasses(atoms);
552       }
553     }
554     return totalMass;
555   }
556 
557   /**
558    * Iterative summation of atomic masses.
559    *
560    * @param atoms List of atoms.
561    * @return Mass of atoms.
562    */
563   private double sumMasses(List<Atom> atoms) {
564     double sumMasses = 0.0;
565     for (Atom atom : atoms) {
566       sumMasses += atom.getMass();
567     }
568     return sumMasses;
569   }
570 
571   /**
572    * Implements the Kahan algorithm to very accurately sum the masses of all the atoms provided,
573    * minimizing rounding error.
574    *
575    * @param atoms Atoms to sum the mass of.
576    * @return Total mass.
577    */
578   private double kahanSumMasses(List<Atom> atoms) {
579     double sum = 0.0;
580     double comp = 0.0; // Running compensation
581     for (Atom atom : atoms) {
582       double atomMass = atom.getMass() - comp;
583       double temp = sum + atomMass;
584       comp = (temp - sum) - atomMass;
585       sum = temp;
586     }
587     return sum;
588   }
589 
590   /**
591    * Returns true if Class c can be below this Class in the Hierarchy
592    *
593    * @param c Class
594    * @return boolean
595    */
596   private <T extends TreeNode> boolean canBeChild(Class<T> c) {
597     try {
598       int multiScaleLevel = c.getDeclaredField("MultiScaleLevel").getInt(null);
599       if (multiScaleLevel >= this.MultiScaleLevel) {
600         return false;
601       }
602     } catch (NoSuchFieldException
603              | SecurityException
604              | IllegalArgumentException
605              | IllegalAccessException e) {
606       return true;
607     }
608     return true;
609   }
610 }