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