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    * getAtomList.
198    *
199    * @param originalOrder a boolean.
200    * @return a {@link java.util.List} object.
201    */
202   public List<Atom> getAtomList(boolean originalOrder) {
203     // As of now, for generic MSNode objects, atoms remain in their original
204     // order. It is presently only a concern for MultiResidue.
205     return getAtomList();
206   }
207 
208   /**
209    * Returns a List of all Bonds below the present MSNode.
210    *
211    * @return a {@link java.util.List} object.
212    */
213   public List<Bond> getBondList() {
214     return getList(Bond.class);
215   }
216 
217   /** {@inheritDoc} */
218   @Override
219   public double[] getCenter(boolean w) {
220     double[] Rc = {0, 0, 0};
221     double sum = 0, mass = 1;
222     List<Atom> atomList = getAtomList();
223     for (Atom a : atomList) {
224       if (w) {
225         mass = a.getMass();
226         sum += mass;
227       }
228       Rc[0] += mass * a.getX();
229       Rc[1] += mass * a.getY();
230       Rc[2] += mass * a.getZ();
231     }
232     if (!w) {
233       sum = atomList.size();
234     }
235     for (int i = 0; i < 3; i++) {
236       Rc[i] /= sum;
237     }
238     return Rc;
239   }
240 
241   /**
242    * Returns a List of the MSNode's Children (instead of using an Enumeration).
243    *
244    * @return a {@link java.util.List} object.
245    */
246   public List<MSNode> getChildList() {
247     List<MSNode> l = new ArrayList<>();
248     Enumeration<TreeNode> e = children();
249     while (e.hasMoreElements()) {
250       l.add((MSNode) e.nextElement());
251     }
252     return l;
253   }
254 
255   /**
256    * getExtent
257    *
258    * @return a double.
259    */
260   public double getExtent() {
261     double extent = 0.0;
262     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
263       MSNode node = (MSNode) e.nextElement();
264       double temp = node.getExtent();
265       if (temp > extent) {
266         extent = temp;
267       }
268     }
269     return extent;
270   }
271 
272   /**
273    * Returns a List of all ImproperTorsions below the present MSNode.
274    *
275    * @return a {@link java.util.List} object.
276    */
277   public List<ImproperTorsion> getImproperTorsionList() {
278     return getList(ImproperTorsion.class);
279   }
280 
281   /**
282    * Returns a List of all MSNodes below the present MSNode.
283    *
284    * @return a {@link java.util.List} object.
285    */
286   public <T extends TreeNode> List<T> getList(Class<T> c) {
287     return getList(c, new ArrayList<>());
288   }
289 
290   /** {@inheritDoc} */
291   @Override
292   public <T extends TreeNode> List<T> getList(Class<T> c, List<T> nodes) {
293     if (c.isInstance(this)) {
294       nodes.add(c.cast(this));
295     }
296     if (isLeaf() || !canBeChild(c)) {
297       return nodes;
298     }
299     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
300       MSNode node = (MSNode) e.nextElement();
301       node.getList(c, nodes);
302     }
303     return nodes;
304   }
305 
306   /** {@inheritDoc} */
307   @Override
308   public <T extends TreeNode> long getMSCount(Class<T> c, long count) {
309     if (c.isInstance(this)) {
310       count++;
311     }
312     if (!canBeChild(c)) {
313       return count;
314     }
315     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
316       MSNode node = (MSNode) e.nextElement();
317       count += node.getMSCount(c, count);
318     }
319     return count;
320   }
321 
322   /** {@inheritDoc} */
323   @Override
324   public <T extends TreeNode> T getMSNode(Class<T> c) {
325     TreeNode[] nodes = getPath();
326     for (TreeNode n : nodes) {
327       if (c.isInstance(n)) {
328         return c.cast(n);
329       }
330     }
331     return null;
332   }
333 
334   /** {@inheritDoc} */
335   @Override
336   public double getMW() {
337     double weight = 0.0;
338     for (Atom atom : getAtomList()) {
339       weight += atom.getMass();
340     }
341     return weight;
342   }
343 
344   /**
345    * Returns the name of this MSNode.
346    *
347    * @return a {@link java.lang.String} object.
348    */
349   public String getName() {
350     return name;
351   }
352 
353   /**
354    * Sets the name of this NodeObject to n.
355    *
356    * @param n a {@link java.lang.String} object.
357    */
358   public void setName(String n) {
359     name = n;
360   }
361 
362   /**
363    * Returns a List of all Out-of-Plane Bends below the present MSNode.
364    *
365    * @return a {@link java.util.List} object.
366    */
367   public List<OutOfPlaneBend> getOutOfPlaneBendList() {
368     return getList(OutOfPlaneBend.class);
369   }
370 
371   /**
372    * Returns a List of all Pi-Orbital Torsions below the present MSNode.
373    *
374    * @return a {@link java.util.List} object.
375    */
376   public List<PiOrbitalTorsion> getPiOrbitalTorsionList() {
377     return getList(PiOrbitalTorsion.class);
378   }
379 
380   /**
381    * Returns a List of all Stretch-Bends below the present MSNode.
382    *
383    * @return a {@link java.util.List} object.
384    */
385   public List<StretchBend> getStretchBendList() {
386     return getList(StretchBend.class);
387   }
388 
389   /**
390    * Returns a List of all StretchTorsions below the present MSNode.
391    *
392    * @return a {@link java.util.List} object.
393    */
394   public List<StretchTorsion> getStretchTorsionList() {
395     return getList(StretchTorsion.class);
396   }
397 
398   /**
399    * Returns a List of all Torsions below the present MSNode.
400    *
401    * @return a {@link java.util.List} object.
402    */
403   public List<Torsion> getTorsionList() {
404     return getList(Torsion.class);
405   }
406 
407   /**
408    * Returns a List of all Torsion-Torsions below the present MSNode.
409    *
410    * @return a {@link java.util.List} object.
411    */
412   public List<TorsionTorsion> getTorsionTorsionList() {
413     return getList(TorsionTorsion.class);
414   }
415 
416   /**
417    * Returns the total mass of all atoms in the MolecularAssembly, calculating the mass if it has not
418    * already been done, defaulting to simple addition.
419    *
420    * @return Total mass of atoms in system.
421    */
422   public double getTotalMass() {
423     if (totalMass == 0.0) {
424       return getTotalMass(true, false);
425     }
426     return totalMass;
427   }
428 
429   /**
430    * Returns a List of all Urey-Bradleys below the present MSNode.
431    *
432    * @return a {@link java.util.List} object.
433    */
434   public List<UreyBradley> getUreyBradleyList() {
435     return getList(UreyBradley.class);
436   }
437 
438   /** {@inheritDoc} */
439   @Override
440   public int hashCode() {
441     return Objects.hash(name);
442   }
443 
444   /**
445    * isSelected
446    *
447    * @return a boolean.
448    */
449   public boolean isSelected() {
450     return selected;
451   }
452 
453   /**
454    * Setter for the field <code>selected</code>.
455    *
456    * @param b a boolean.
457    */
458   public void setSelected(boolean b) {
459     selected = b;
460     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
461       MSNode node = (MSNode) e.nextElement();
462       node.setSelected(b);
463     }
464   }
465 
466   /** Prints the MSNode's name */
467   public void print() {
468     System.out.println(name);
469   }
470 
471   /**
472    * removeChild.
473    *
474    * @param child a {@link ffx.potential.bonded.MSNode} object.
475    */
476   public void removeChild(MSNode child) {
477     if (child != null && child.getParent() == this) {
478       remove(child);
479     }
480   }
481 
482   /** {@inheritDoc} */
483   @Override
484   public void setColor(RendererCache.ColorModel colorModel, Color3f color, Material mat) {
485     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
486       MSNode node = (MSNode) e.nextElement();
487       node.setColor(colorModel, color, mat);
488     }
489   }
490 
491   /** {@inheritDoc} */
492   @Override
493   public void setView(RendererCache.ViewModel viewModel, List<BranchGroup> newShapes) {
494     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
495       MSNode node = (MSNode) e.nextElement();
496       node.setView(viewModel, newShapes);
497     }
498   }
499 
500   /**
501    * {@inheritDoc}
502    *
503    * <p>Overridden toString method returns the MSNode's name
504    */
505   @Override
506   public String toString() {
507     return name;
508   }
509 
510   /** {@inheritDoc} */
511   @Override
512   public void update() {
513     for (Enumeration<TreeNode> e = children(); e.hasMoreElements(); ) {
514       MSNode node = (MSNode) e.nextElement();
515       node.update();
516     }
517   }
518 
519   /**
520    * Calculates the total mass of all atoms in the MolecularAssembly, using either a simple addition
521    * or the Kahan summation algorithm. The simple algorithm is a standard loop to add up the masses.
522    *
523    * @param recalculate Force recalculation
524    * @param useKahan Use Kahan or simple addition algorithms
525    * @return Total mass of all atoms in system.
526    */
527   private double getTotalMass(boolean recalculate, boolean useKahan) {
528     if (recalculate) {
529       List<Atom> atoms = getAtomList();
530       if (atoms.isEmpty()) {
531         totalMass = 0.0;
532       } else if (useKahan) {
533         totalMass = kahanSumMasses(atoms);
534       } else {
535         totalMass = sumMasses(atoms);
536       }
537     }
538     return totalMass;
539   }
540 
541   /**
542    * Iterative summation of atomic masses.
543    *
544    * @param atoms List of atoms.
545    * @return Mass of atoms.
546    */
547   private double sumMasses(List<Atom> atoms) {
548     double sumMasses = 0.0;
549     for (Atom atom : atoms) {
550       sumMasses += atom.getMass();
551     }
552     return sumMasses;
553   }
554 
555   /**
556    * Implements the Kahan algorithm to very accurately sum the masses of all the atoms provided,
557    * minimizing rounding error.
558    *
559    * @param atoms Atoms to sum the mass of.
560    * @return Total mass.
561    */
562   private double kahanSumMasses(List<Atom> atoms) {
563     double sum = 0.0;
564     double comp = 0.0; // Running compensation
565     for (Atom atom : atoms) {
566       double atomMass = atom.getMass() - comp;
567       double temp = sum + atomMass;
568       comp = (temp - sum) - atomMass;
569       sum = temp;
570     }
571     return sum;
572   }
573 
574   /**
575    * Returns true if Class c can be below this Class in the Hierarchy
576    *
577    * @param c Class
578    * @return boolean
579    */
580   private <T extends TreeNode> boolean canBeChild(Class<T> c) {
581     try {
582       int multiScaleLevel = c.getDeclaredField("MultiScaleLevel").getInt(null);
583       if (multiScaleLevel >= this.MultiScaleLevel) {
584         return false;
585       }
586     } catch (NoSuchFieldException
587              | SecurityException
588              | IllegalArgumentException
589              | IllegalAccessException e) {
590       return true;
591     }
592     return true;
593   }
594 }