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