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;
39  
40  import edu.rit.pj.ParallelTeam;
41  import ffx.crystal.Crystal;
42  import ffx.crystal.SymOp;
43  import ffx.potential.bonded.Atom;
44  import ffx.potential.bonded.Bond;
45  import ffx.potential.bonded.MSGroup;
46  import ffx.potential.bonded.MSNode;
47  import ffx.potential.bonded.Molecule;
48  import ffx.potential.bonded.Polymer;
49  import ffx.potential.bonded.RendererCache;
50  import ffx.potential.bonded.Residue;
51  import ffx.potential.bonded.Residue.ResidueType;
52  import ffx.potential.parameters.ForceField;
53  import ffx.utilities.StringUtils;
54  import org.apache.commons.configuration2.CompositeConfiguration;
55  import org.jogamp.java3d.Appearance;
56  import org.jogamp.java3d.BoundingSphere;
57  import org.jogamp.java3d.BranchGroup;
58  import org.jogamp.java3d.ColoringAttributes;
59  import org.jogamp.java3d.GeometryArray;
60  import org.jogamp.java3d.Group;
61  import org.jogamp.java3d.LineArray;
62  import org.jogamp.java3d.LineAttributes;
63  import org.jogamp.java3d.Link;
64  import org.jogamp.java3d.Material;
65  import org.jogamp.java3d.Node;
66  import org.jogamp.java3d.RenderingAttributes;
67  import org.jogamp.java3d.Shape3D;
68  import org.jogamp.java3d.SharedGroup;
69  import org.jogamp.java3d.Switch;
70  import org.jogamp.java3d.Transform3D;
71  import org.jogamp.java3d.TransformGroup;
72  import org.jogamp.java3d.utils.picking.PickTool;
73  import org.jogamp.vecmath.Color3f;
74  import org.jogamp.vecmath.Matrix3d;
75  import org.jogamp.vecmath.Point3d;
76  import org.jogamp.vecmath.Vector3d;
77  
78  import java.awt.GraphicsEnvironment;
79  import java.io.File;
80  import java.io.Serial;
81  import java.util.ArrayList;
82  import java.util.Arrays;
83  import java.util.HashMap;
84  import java.util.HashSet;
85  import java.util.Iterator;
86  import java.util.List;
87  import java.util.ListIterator;
88  import java.util.Set;
89  import java.util.logging.Level;
90  import java.util.logging.Logger;
91  import java.util.stream.Collectors;
92  import java.util.stream.Stream;
93  
94  import static ffx.crystal.SymOp.applyCartesianSymOp;
95  import static ffx.numerics.math.DoubleMath.length;
96  import static ffx.numerics.math.DoubleMath.sub;
97  import static java.lang.String.format;
98  
99  /**
100  * The MolecularAssembly class is a collection of Polymers, Hetero Molecules, Ions and Water
101  *
102  * @author Michael J. Schnieders
103  * @since 1.0
104  */
105 public class MolecularAssembly extends MSGroup {
106 
107   @Serial
108   private static final long serialVersionUID = 1L;
109 
110   private static final Logger logger = Logger.getLogger(MolecularAssembly.class.getName());
111   private static final double[] a = new double[3];
112   private Character alternateLocation = ' ';
113   private final List<String> headerLines = new ArrayList<>();
114   // Data Nodes
115   private final MSNode ions = new MSNode("Ions");
116   private final HashMap<String, Molecule> ionHashMap = new HashMap<>();
117   private final MSNode water = new MSNode("Water");
118   private final HashMap<String, Molecule> waterHashMap = new HashMap<>();
119   private final MSNode molecules = new MSNode("Hetero Molecules");
120   private final HashMap<String, Molecule> moleculeHashMap = new HashMap<>();
121   private final List<BranchGroup> myNewShapes = new ArrayList<>();
122   // MolecularAssembly member variables
123   protected ForceField forceField;
124   // The File that was read to create this MolecularAssembly.
125   private File file;
126   // The File to use for writing archives.
127   private File archiveFile;
128   private ForceFieldEnergy potentialEnergy;
129   private CompositeConfiguration properties;
130   private Vector3d offset;
131   private int cycles = 1;
132   private int currentCycle = 1;
133   // 3D Graphics Nodes - There is a diagram explaining the MolecularAssembly
134   // Scenegraph below
135   private BranchGroup branchGroup;
136   private TransformGroup originToRot;
137   private Transform3D originToRotT3D;
138   private Vector3d originToRotV3D;
139   private TransformGroup rotToCOM;
140   private Transform3D rotToCOMT3D;
141   private Vector3d rotToCOMV3D;
142   private BranchGroup base;
143   private Switch switchGroup;
144   private Shape3D wire;
145   private BranchGroup vrml;
146   private TransformGroup vrmlTG;
147   private Transform3D vrmlTd;
148   private BranchGroup childNodes;
149   private Atom[] atomLookUp;
150   private LineAttributes lineAttributes;
151   private boolean visible = false;
152   private FractionalMode fractionalMode = FractionalMode.MOLECULE;
153   private double[][] fractionalCoordinates;
154   private boolean titrateConformer = false;
155   private Atom atomInitial;
156 
157   /**
158    * Constructor for MolecularAssembly.
159    *
160    * @param name a {@link java.lang.String} object.
161    */
162   public MolecularAssembly(String name) {
163     super(name);
164     getAtomNode().setName("MacroMolecules");
165     add(molecules);
166     add(ions);
167     add(water);
168   }
169 
170   /**
171    * Constructor for MolecularAssembly.
172    *
173    * @param name     a {@link java.lang.String} object.
174    * @param Polymers a {@link ffx.potential.bonded.MSNode} object.
175    */
176   public MolecularAssembly(String name, MSNode Polymers) {
177     super(name, Polymers);
178   }
179 
180   /**
181    * Constructor for MolecularAssembly.
182    *
183    * @param name       a {@link java.lang.String} object.
184    * @param Polymers   a {@link ffx.potential.bonded.MSNode} object.
185    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
186    *                   object.
187    */
188   public MolecularAssembly(String name, MSNode Polymers, CompositeConfiguration properties) {
189     this(name, Polymers);
190     this.properties = properties;
191   }
192 
193   /**
194    * Set the alternate location label for the MolecularAssembly.
195    * Atomic alternate locations are not modified.
196    *
197    * @param alternateLocation The alternate location.
198    */
199   public void setAlternateLocation(Character alternateLocation) {
200     this.alternateLocation = alternateLocation;
201   }
202 
203   /**
204    * Get the alternate location label for the MolecularAssembly.
205    *
206    * @return The alternate location.
207    */
208   public Character getAlternateLocation() {
209     return alternateLocation;
210   }
211 
212   /**
213    * Set the occupancy for all atoms of the Assembly.
214    *
215    * @param occupancy the occupancy to use.
216    */
217   public void setOccupancy(double occupancy) {
218     List<Atom> atoms = getAtomList();
219     for (Atom atom : atoms) {
220       atom.setOccupancy(occupancy);
221     }
222   }
223 
224   /**
225    * Set the occupancy for all atoms of the Assembly that have the given alternate location.
226    *
227    * @param occupancy         the occupancy to use.
228    * @param alternateLocation the alternate location to filter by.
229    */
230   public void setOccupancy(double occupancy, Character alternateLocation) {
231     List<Atom> atoms = getAtomList();
232     for (Atom atom : atoms) {
233       if (atom.getAltLoc().equals(alternateLocation)) {
234         atom.setOccupancy(occupancy);
235       }
236     }
237   }
238 
239   /**
240    * Adds a header line to this MolecularAssembly (particularly for PDB formats)
241    *
242    * @param line Line to add.
243    */
244   public void addHeaderLine(String line) {
245     headerLines.add(line);
246   }
247 
248   /**
249    * {@inheritDoc}
250    */
251   @Override
252   public MSNode addMSNode(MSNode o) {
253     List<MSNode> Polymers = getAtomNodeList();
254     if (o instanceof Atom) {
255       Atom atom = (Atom) o;
256       if (atom.isModRes()) {
257         return getResidue(atom, true, Residue.ResidueType.AA);
258       } else if (!atom.isHetero()) {
259         String resName = atom.getResidueName();
260         switch (resName) {
261           case "HIS":
262           case "HIE":
263           case "HID":
264           case "ASP":
265           case "ASH":
266           case "GLU":
267           case "GLH":
268           case "LYS":
269           case "LYD":
270             for (Residue residue : getResidueList()) {
271               if (residue.getResidueNumber() == atom.getResidueNumber()) {
272                 for (Atom currentAtom : residue.getAtomList()) {
273                   if (atom.getResidueNumber() == currentAtom.getResidueNumber() && atom.getChainID() == currentAtom.getChainID()
274                       && atom.getAltLoc() != currentAtom.getAltLoc() && !atom.getResidueName().equals(currentAtom.getResidueName()) &&
275                       atom.getName().equals(currentAtom.getName())) {
276                     titrateConformer = true;
277                     atomInitial = currentAtom;
278                     return getResidue(atom, true);
279                   } else {
280                     titrateConformer = false;
281                     atomInitial = null;
282                   }
283                 }
284               }
285             }
286             break;
287           default:
288             break;
289         }
290         return getResidue(atom, true);
291       } else {
292         return getMolecule(atom, true);
293       }
294     } else if (o instanceof Residue) {
295       Residue residue = (Residue) o;
296       Character chainID = residue.getChainID();
297       String segID = residue.getSegID();
298       int index = Polymers.indexOf(new Polymer(chainID, segID));
299       // See if the polymer already exists.
300       if (index != -1) {
301         Polymer c = (Polymer) Polymers.get(index);
302         setFinalized(false);
303         return c.addMSNode(residue);
304       } else {
305         Polymer newc = new Polymer(chainID, segID);
306         getAtomNode().add(newc);
307         setFinalized(false);
308         return newc.addMSNode(residue);
309       }
310     } else if (o instanceof Polymer) {
311       Polymer c = (Polymer) o;
312       int index = Polymers.indexOf(c);
313       if (index == -1) {
314         getAtomNode().add(c);
315         setFinalized(false);
316         return c;
317       } else {
318         return Polymers.get(index);
319       }
320     } else if (o instanceof Molecule) {
321       Molecule m = (Molecule) o;
322       String key = m.getKey();
323       if (m.getAtomNode().getChildCount() == 1) {
324         ions.add(m);
325         if (ionHashMap.containsKey(key)) {
326           logger.info(" Ion map already contains " + m);
327         } else {
328           ionHashMap.put(m.getKey(), m);
329         }
330         return m;
331       } else if (Utilities.isWaterOxygen((Atom) m.getAtomNode().getChildAt(0))) {
332         water.add(m);
333         if (waterHashMap.containsKey(key)) {
334           logger.info(" Water map already contains " + m);
335         } else {
336           waterHashMap.put(m.getKey(), m);
337         }
338         return m;
339       } else {
340         molecules.add(m);
341         if (moleculeHashMap.containsKey(key)) {
342           logger.info(" Molecule map already contains " + m);
343         } else {
344           moleculeHashMap.put(m.getKey(), m);
345         }
346         return m;
347       }
348     } else {
349       String message = "Programming error in MolecularAssembly addNode";
350       logger.log(Level.SEVERE, message);
351       return o;
352     }
353   }
354 
355   /**
356    * Applies a randomly drawn density to a molecular system's crystal.
357    *
358    * @param ucDensity Target density.
359    */
360   public void applyRandomDensity(double ucDensity) {
361     if (ucDensity > 0) {
362       logger.info(format("\n Applying random unit cell axes with target density %6.3f (g/cc).", ucDensity));
363       Crystal crystal = getCrystal();
364       if (!crystal.aperiodic()) {
365         double mass = getMass();
366         crystal.randomParameters(ucDensity, mass);
367         logger.info(crystal.toString());
368         potentialEnergy.setCrystal(crystal);
369       } else {
370         logger.fine(String.format(" Potential %s is an aperiodic system!", potentialEnergy));
371       }
372     }
373   }
374 
375   /**
376    * Randomizes position in the unit cell of each molecule by applying a Cartesian SymOp with a
377    * random translation.
378    *
379    * @param x SymOp with translation range -x/2 .. x/2 (0 for random placement in the unit cell,
380    *          negative for no SymOp)
381    */
382   public void applyRandomSymOp(double x) {
383     // The Unit Cell is needed here (not the ReplicatesCrystal).
384     Crystal crystal = getCrystal().getUnitCell();
385     if (crystal.aperiodic() || x < 0) {
386       return;
387     }
388     double[] xyz = new double[3];
389     List<MSNode> molecules = getMolecules();
390     int moleculeNum = 1;
391     for (MSNode msNode : molecules) {
392       List<Atom> atoms = msNode.getAtomList();
393       SymOp symOp;
394       if (x == 0) {
395         double[] translation = crystal.getRandomCartTranslation();
396         symOp = SymOp.randomSymOpFactory(translation);
397       } else {
398         symOp = SymOp.randomSymOpFactory(x);
399       }
400       logger.info(
401           format(
402               "\n Applying random Cartesian SymOp to molecule %d:\n%s",
403               moleculeNum, symOp));
404       for (Atom atom : atoms) {
405         atom.getXYZ(xyz);
406         applyCartesianSymOp(xyz, xyz, symOp);
407         atom.setXYZ(xyz);
408       }
409       moleculeNum++;
410     }
411   }
412 
413   /**
414    * center
415    */
416   public void center() {
417     double[] center = getMultiScaleCenter(false);
418     offset = new Vector3d(center);
419     if (vrml != null) {
420       vrmlTd.set(offset);
421       vrmlTG.setTransform(vrmlTd);
422     }
423     offset.negate();
424     originToRotV3D.set(offset);
425     originToRotT3D.setTranslation(originToRotV3D);
426     originToRot.setTransform(originToRotT3D);
427     rotToCOMT3D.setIdentity();
428     rotToCOM.setTransform(rotToCOMT3D);
429     offset.negate();
430     rotateAbout(offset);
431     originToRotT3D.get(offset);
432   }
433 
434   /**
435    * centerAt
436    *
437    * @param d an array of double.
438    */
439   public void centerAt(double[] d) {
440     double[] Rc = {0, 0, 0};
441     double[] c = new double[3];
442     ListIterator<Atom> li;
443     int i, num = getAtomList().size();
444     for (li = getAtomList().listIterator(); li.hasNext(); ) {
445       (li.next()).getXYZ(a);
446       Rc[0] += a[0];
447       Rc[1] += a[1];
448       Rc[2] += a[2];
449     }
450     for (i = 0; i < 3; i++) {
451       Rc[i] /= num;
452     }
453     sub(d, Rc, c);
454     for (li = getAtomList().listIterator(); li.hasNext(); ) {
455       (li.next()).move(c);
456     }
457   }
458 
459   /**
460    * centerView
461    *
462    * @param rot   a boolean.
463    * @param trans a boolean.
464    */
465   public void centerView(boolean rot, boolean trans) {
466     originToRot.getTransform(originToRotT3D);
467     if (rot) {
468       Matrix3d m3d = new Matrix3d();
469       m3d.setIdentity();
470       originToRotT3D.setRotation(m3d);
471       // rotToCOMT3D.setRotation(m3d);
472     }
473     if (trans) {
474       originToRotV3D.set(offset);
475       originToRotT3D.set(originToRotV3D);
476     }
477     originToRot.setTransform(originToRotT3D);
478     // rotToCOM.setTransform(rotToCOMT3D);
479   }
480 
481   /**
482    * Compute fractional coordinates.
483    */
484   public void computeFractionalCoordinates() {
485 
486     // Count up the number of fractional coordinate entities.
487     fractionalCount();
488 
489     Crystal unitCell = getCrystal().getUnitCell();
490     double[] com = new double[3];
491 
492     switch (fractionalMode) {
493       case MOLECULE:
494         int iMolecule = 0;
495         Polymer[] polymers = getChains();
496         if (polymers != null && polymers.length > 0) {
497           // Find the center of mass
498           for (Polymer polymer : polymers) {
499             List<Atom> list = polymer.getAtomList();
500             com[0] = 0.0;
501             com[1] = 0.0;
502             com[2] = 0.0;
503             double totalMass = 0.0;
504             for (Atom atom : list) {
505               double m = atom.getMass();
506               com[0] += atom.getX() * m;
507               com[1] += atom.getY() * m;
508               com[2] += atom.getZ() * m;
509               totalMass += m;
510             }
511             com[0] /= totalMass;
512             com[1] /= totalMass;
513             com[2] /= totalMass;
514             unitCell.toFractionalCoordinates(com, fractionalCoordinates[iMolecule++]);
515           }
516         }
517 
518         // Loop over each molecule
519         List<MSNode> molecules = getMolecules();
520         for (MSNode molecule : molecules) {
521           List<Atom> list = molecule.getAtomList();
522           // Find the center of mass
523           com[0] = 0.0;
524           com[1] = 0.0;
525           com[2] = 0.0;
526           double totalMass = 0.0;
527           for (Atom atom : list) {
528             double m = atom.getMass();
529             com[0] += atom.getX() * m;
530             com[1] += atom.getY() * m;
531             com[2] += atom.getZ() * m;
532             totalMass += m;
533           }
534           com[0] /= totalMass;
535           com[1] /= totalMass;
536           com[2] /= totalMass;
537           unitCell.toFractionalCoordinates(com, fractionalCoordinates[iMolecule++]);
538         }
539 
540         // Loop over each water
541         List<MSNode> water = getWater();
542         for (MSNode wat : water) {
543           List<Atom> list = wat.getAtomList();
544           // Find the center of mass
545           com[0] = 0.0;
546           com[1] = 0.0;
547           com[2] = 0.0;
548           double totalMass = 0.0;
549           for (Atom atom : list) {
550             double m = atom.getMass();
551             com[0] += atom.getX() * m;
552             com[1] += atom.getY() * m;
553             com[2] += atom.getZ() * m;
554             totalMass += m;
555           }
556           com[0] /= totalMass;
557           com[1] /= totalMass;
558           com[2] /= totalMass;
559           unitCell.toFractionalCoordinates(com, fractionalCoordinates[iMolecule++]);
560         }
561 
562         // Loop over each ion
563         List<MSNode> ions = getIons();
564         for (MSNode ion : ions) {
565           List<Atom> list = ion.getAtomList();
566           // Find the center of mass
567           com[0] = 0.0;
568           com[1] = 0.0;
569           com[2] = 0.0;
570           double totalMass = 0.0;
571           for (Atom atom : list) {
572             double m = atom.getMass();
573             com[0] += atom.getX() * m;
574             com[1] += atom.getY() * m;
575             com[2] += atom.getZ() * m;
576             totalMass += m;
577           }
578           com[0] /= totalMass;
579           com[1] /= totalMass;
580           com[2] /= totalMass;
581           unitCell.toFractionalCoordinates(com, fractionalCoordinates[iMolecule++]);
582         }
583         break;
584       case ATOM:
585         Atom[] atoms = getAtomArray();
586         int nAtoms = atoms.length;
587         for (int i = 0; i < nAtoms; i++) {
588           atoms[i].getXYZ(com);
589           unitCell.toFractionalCoordinates(com, fractionalCoordinates[i]);
590         }
591         break;
592       case OFF:
593         break;
594     }
595   }
596 
597   /**
598    * createBox
599    */
600   public void createBox() {
601     int vertices = 8;
602     LineArray la =
603         new LineArray(
604             4 * vertices,
605             GeometryArray.COORDINATES | GeometryArray.COLOR_4 | GeometryArray.NORMALS);
606     la.setCapability(LineArray.ALLOW_COORDINATE_WRITE);
607     la.setCapability(LineArray.ALLOW_COORDINATE_READ);
608     la.setCapability(LineArray.ALLOW_COLOR_WRITE);
609     la.setCapability(LineArray.ALLOW_COUNT_READ);
610     la.setCapability(LineArray.ALLOW_INTERSECT);
611     la.setCapability(LineArray.ALLOW_FORMAT_READ);
612 
613     ColoringAttributes cola =
614         new ColoringAttributes(new Color3f(), ColoringAttributes.SHADE_GOURAUD);
615     Appearance app = new Appearance();
616     lineAttributes = new LineAttributes();
617     lineAttributes.setLineWidth(RendererCache.bondwidth);
618     lineAttributes.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
619     lineAttributes.setLineAntialiasingEnable(true);
620     app.setLineAttributes(lineAttributes);
621     app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
622     app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_WRITE);
623     RenderingAttributes ra = new RenderingAttributes();
624     ra.setAlphaTestValue(0.1f);
625     ra.setAlphaTestFunction(RenderingAttributes.GREATER);
626     ra.setDepthBufferEnable(true);
627     ra.setDepthBufferWriteEnable(true);
628     app.setRenderingAttributes(ra);
629     app.setColoringAttributes(cola);
630     Shape3D wireframe = new Shape3D(la, app);
631     // PickTool.setCapabilities(wire, PickTool.INTERSECT_COORD);
632     wireframe.setUserData(this);
633     wireframe.setBounds(new BoundingSphere(new Point3d(0, 0, 0), 10.0));
634     try {
635       wireframe.setBoundsAutoCompute(false);
636     } catch (Exception e) {
637       logger.warning(" Exception in setting bounds for wireframe:\n" + e);
638     }
639     wireframe.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
640     wireframe.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
641     // return wire;
642   }
643 
644   /**
645    * deleteMolecule
646    *
647    * @param molecule a {@link ffx.potential.bonded.Molecule} object.
648    */
649   public void deleteMolecule(Molecule molecule) {
650     List<MSNode> list = ions.getChildList();
651     for (MSNode node : list) {
652       Molecule m = (Molecule) node;
653       if (molecule == m) {
654         ions.remove(m);
655         ionHashMap.remove(m.getKey());
656         return;
657       }
658     }
659     list = water.getChildList();
660     for (MSNode node : list) {
661       Molecule m = (Molecule) node;
662       if (molecule == m) {
663         water.remove(m);
664         waterHashMap.remove(m.getKey());
665         return;
666       }
667     }
668     list = molecules.getChildList();
669     for (MSNode node : list) {
670       Molecule m = (Molecule) node;
671       if (molecule == m) {
672         molecules.remove(m);
673         moleculeHashMap.remove(m.getKey());
674         return;
675       }
676     }
677   }
678 
679   /**
680    * {@inheritDoc}
681    */
682   @Override
683   public boolean destroy() {
684     try {
685       if (potentialEnergy == null) {
686         finishDestruction();
687         return true;
688       } else {
689         // potentialEnergy.destroy() will loop around to call finishDestruction().
690         // This is a poor construction, but is necessary due to prior decisions.
691         return potentialEnergy.destroy();
692       }
693     } catch (Exception ex) {
694       logger.warning(String.format(" Exception in destroying a MolecularAssembly: %s", ex));
695       logger.info(Utilities.stackTraceToString(ex));
696       return false;
697     }
698   }
699 
700   /**
701    * detach
702    */
703   public void detach() {
704     synchronized (this) {
705       if (branchGroup != null && branchGroup.isLive()) {
706         branchGroup.detach();
707       }
708     }
709   }
710 
711   /**
712    * {@inheritDoc}
713    */
714   @Override
715   public void finalize(boolean finalizeGroups, ForceField forceField) {
716     setFinalized(false);
717     if (finalizeGroups) {
718       bondTime = 0;
719       angleTime = 0;
720       stretchBendTime = 0;
721       ureyBradleyTime = 0;
722       outOfPlaneBendTime = 0;
723       torsionTime = 0;
724       piOrbitalTorsionTime = 0;
725       torsionTorsionTime = 0;
726       List<MSNode> Polymers = getAtomNodeList();
727       for (MSNode msNode : Polymers) {
728         MSGroup group = (MSGroup) msNode;
729         if (logger.isLoggable(Level.FINE)) {
730           logger.fine(" Finalizing bonded terms for polymer " + group.toString());
731         }
732         try {
733           group.finalize(true, forceField);
734         } catch (Exception e) {
735           String message = "Fatal exception finalizing " + group.toString();
736           logger.log(Level.SEVERE, message, e);
737           System.exit(-1);
738         }
739         if (logger.isLoggable(Level.FINE)) {
740           Runtime runtime = Runtime.getRuntime();
741           long occupiedMemory = runtime.totalMemory() - runtime.freeMemory();
742           long MB = 1024 * 1024;
743           logger.fine(
744               "\n In-Use Memory   (Mb): "
745                   + occupiedMemory / MB
746                   + "\n Free Memory     (Mb): "
747                   + runtime.freeMemory() / MB
748                   + "\n Total Memory    (Mb): "
749                   + runtime.totalMemory() / MB);
750         }
751       }
752       for (MSNode m : molecules.getChildList()) {
753         Molecule molecule = (Molecule) m;
754         molecule.finalize(true, forceField);
755       }
756       for (MSNode m : water.getChildList()) {
757         Molecule molecule = (Molecule) m;
758         molecule.finalize(true, forceField);
759       }
760       for (MSNode m : ions.getChildList()) {
761         Molecule molecule = (Molecule) m;
762         molecule.finalize(true, forceField);
763       }
764       if (logger.isLoggable(Level.FINE)) {
765         StringBuilder sb = new StringBuilder("\n Time to create bonded energy terms\n\n");
766         sb.append(format(" Bond Streching     %10.3f\n", bondTime * 1.0e-9));
767         sb.append(format(" Angle Bending      %10.3f\n", angleTime * 1.0e-9));
768         sb.append(format(" Stretch-Bend       %10.3f\n", stretchBendTime * 1.0e-9));
769         sb.append(format(" Urey-Bradley       %10.3f\n", ureyBradleyTime * 1.0e-9));
770         sb.append(format(" Out-of-Plane Bend  %10.3f\n", outOfPlaneBendTime * 1.0e-9));
771         sb.append(format(" Torsionanl Angle   %10.3f\n", torsionTime * 1.0e-9));
772         sb.append(format(" Pi-Orbital Torsion %10.3f\n", piOrbitalTorsionTime * 1.0e-9));
773         sb.append(format(" Torsion-Torsion    %10.3f\n", torsionTorsionTime * 1.0e-9));
774         logger.fine(sb.toString());
775       }
776     }
777     if (!GraphicsEnvironment.isHeadless()) {
778       createScene(!finalizeGroups);
779       center();
780     }
781     removeLeaves();
782 
783     // Apply the Heavy Hydrogen flag.
784     boolean heavyHydrogen = forceField.getBoolean("HEAVY_HYDROGENS", false);
785     heavyHydrogen = forceField.getBoolean("HEAVY_HYDROGEN", heavyHydrogen);
786     if (heavyHydrogen) {
787       applyHeavyHydrogen();
788     }
789 
790     setFinalized(true);
791   }
792 
793   /**
794    * findAtom
795    *
796    * @param atom a {@link ffx.potential.bonded.Atom} object.
797    * @return a {@link ffx.potential.bonded.Atom} object.
798    */
799   public Atom findAtom(Atom atom) {
800     return findAtom(atom, false);
801   }
802 
803   /**
804    * findAtom
805    *
806    * @param atom a {@link ffx.potential.bonded.Atom} object.
807    * @return a {@link ffx.potential.bonded.Atom} object.
808    */
809   public Atom findAtom(Atom atom, boolean matchDeuterium) {
810     if (!atom.isHetero() || atom.isModRes()) {
811       Polymer polymer = getPolymer(atom.getChainID(), atom.getSegID(), false);
812       if (polymer != null) {
813         Residue res = polymer.getResidue(atom.getResidueName(), atom.getResidueNumber(), false);
814         if (res != null) {
815           MSNode node = res.getAtomNode();
816           Atom foundAtom = (Atom) node.contains(atom);
817           // Match deuterium
818           if (foundAtom == null && matchDeuterium && atom.getAtomicNumber() == 1) {
819             String atomName = atom.getName().toUpperCase();
820             if (atomName.startsWith("H")) {
821               atom.setName(atomName.replaceFirst("H", "D"));
822               foundAtom = (Atom) node.contains(atom);
823               atom.setName(atomName);
824             } else if (atomName.startsWith("D")) {
825               atom.setName(atomName.replaceFirst("D", "H"));
826               foundAtom = (Atom) node.contains(atom);
827               atom.setName(atomName);
828             }
829           }
830           return foundAtom;
831         }
832       }
833       return null;
834     } else {
835       String resName = atom.getResidueName();
836       int resNum = atom.getResidueNumber();
837       String segID = atom.getSegID();
838       String key = resNum + resName + segID;
839       Molecule m = ionHashMap.get(key);
840       if (m == null) {
841         m = waterHashMap.get(key);
842         if (m == null) {
843           m = moleculeHashMap.get(key);
844           if (m == null) {
845             return null;
846           }
847         }
848       }
849       return (Atom) m.contains(atom);
850     }
851   }
852 
853   /**
854    * Count the number of fractional coordinate entities in the system. If fractionalMode is MOLECULE,
855    * then the count is equal to the number of molecules. If fractionalMode is ATOM, then the count is
856    * the number of atoms. Otherwise the count is zero.
857    *
858    * @return The number of fractional coordinate entities.
859    */
860   public int fractionalCount() {
861     int count = 0;
862     switch (fractionalMode) {
863       case MOLECULE:
864         // Move polymers togethers.
865         Polymer[] polymers = getChains();
866         if (polymers != null && polymers.length > 0) {
867           count += polymers.length;
868         }
869         List<MSNode> molecules = getMolecules();
870         if (molecules != null) {
871           count += molecules.size();
872         }
873 
874         List<MSNode> water = getWater();
875         if (water != null) {
876           count += water.size();
877         }
878 
879         List<MSNode> ions = getIons();
880         if (ions != null) {
881           count += ions.size();
882         }
883         break;
884       case ATOM:
885         count = getAtomArray().length;
886         break;
887       case OFF:
888         count = 0;
889         break;
890     }
891 
892     if (fractionalCoordinates == null || fractionalCoordinates.length != count) {
893       fractionalCoordinates = new double[count][3];
894     }
895 
896     return count;
897   }
898 
899   /**
900    * getActiveAtomArray
901    *
902    * @return an array of active {@link ffx.potential.bonded.Atom} objects.
903    */
904   public Atom[] getActiveAtomArray() {
905     List<Atom> atoms = getAtomList();
906     List<Atom> activeAtoms = new ArrayList<>();
907     for (Atom a : atoms) {
908       if (a.isActive()) {
909         activeAtoms.add(a);
910       }
911     }
912     Atom[] atomArray = activeAtoms.toArray(new Atom[0]);
913     Arrays.sort(atomArray);
914     return atomArray;
915   }
916 
917   /**
918    * Gets all bonded entities in this MolecularAssembly, where an entity can be a polymer, molecule,
919    * monoatomic ion, or monoatomic gas (i.e. noble gas atoms).
920    *
921    * @return All bonded groups of atoms and all singleton atoms.
922    */
923   public List<MSNode> getAllBondedEntities() {
924     // Initial construction via HashSet to eliminate duplicates.
925     Set<MSNode> allBondedNodes = new HashSet<>(getIons());
926     allBondedNodes.addAll(getMolecules());
927     allBondedNodes.addAll(getWater());
928     Polymer[] polys = getChains();
929     if (polys != null && polys.length > 0) {
930       allBondedNodes.addAll(Arrays.asList(polys));
931     }
932     return new ArrayList<>(allBondedNodes);
933   }
934 
935   /**
936    * Return an Array of all atoms in the System.
937    *
938    * @return an array of {@link ffx.potential.bonded.Atom} objects.
939    */
940   public Atom[] getAtomArray() {
941     List<Atom> atoms = getAtomList();
942     Atom[] atomArray = atoms.toArray(new Atom[0]);
943     for (int i = 0; i < atoms.size(); i++) {
944       atomArray[i].setXyzIndex(i + 1);
945     }
946     return atomArray;
947   }
948 
949   /**
950    * getAtomFromWireVertex
951    *
952    * @param i a int.
953    * @return a {@link ffx.potential.bonded.Atom} object.
954    */
955   public Atom getAtomFromWireVertex(int i) {
956     if (atomLookUp != null && atomLookUp.length > i) {
957       return atomLookUp[i];
958     }
959     return null;
960   }
961 
962   /**
963    * getBackBoneAtoms
964    *
965    * @return a {@link java.util.List} object.
966    */
967   public List<Atom> getBackBoneAtoms() {
968     List<Atom> backbone = new ArrayList<>();
969     List<Residue> residues = getResidueList();
970     for (Residue residue : residues) {
971       backbone.addAll(residue.getBackboneAtoms());
972     }
973     return backbone;
974   }
975 
976   /**
977    * Getter for the field <code>branchGroup</code>.
978    *
979    * @return a {@link org.jogamp.java3d.BranchGroup} object.
980    */
981   public BranchGroup getBranchGroup() {
982     return branchGroup;
983   }
984 
985   /**
986    * getChain
987    *
988    * @param name a {@link java.lang.String} object.
989    * @return a {@link ffx.potential.bonded.Polymer} object.
990    */
991   public Polymer getChain(String name) {
992     for (MSNode node : getAtomNodeList()) {
993       if (node instanceof Polymer) {
994         String chainName = node.getName();
995         if (chainName.equalsIgnoreCase(name)) {
996           return (Polymer) node;
997         } else if (name.contentEquals(" ")) {
998           return (Polymer) node;
999         }
1000         /* TODO: Right now if a molecular assembly has no chain ID, the first node is returned.
1001         This functionality will not work for cases where multiple chains exist and one of those chains has no name.*/
1002       }
1003     }
1004     return null;
1005   }
1006 
1007   /**
1008    * getChainNames
1009    *
1010    * @return an array of {@link java.lang.String} objects.
1011    */
1012   public String[] getChainNames() {
1013     List<String> temp = new ArrayList<>();
1014     for (MSNode node : getAtomNodeList()) {
1015       if (node instanceof Polymer) {
1016         temp.add(node.getName());
1017       }
1018     }
1019     if (temp.isEmpty()) {
1020       return null;
1021     }
1022 
1023     String[] names = new String[temp.size()];
1024     for (int i = 0; i < temp.size(); i++) {
1025       names[i] = temp.get(i);
1026     }
1027 
1028     return names;
1029   }
1030 
1031   /**
1032    * getChains
1033    *
1034    * @return an array of {@link ffx.potential.bonded.Polymer} objects.
1035    */
1036   public Polymer[] getChains() {
1037     List<Polymer> polymers = new ArrayList<>();
1038     for (MSNode node : getAtomNodeList()) {
1039       if (node instanceof Polymer) {
1040         polymers.add((Polymer) node);
1041       }
1042     }
1043     if (polymers.isEmpty()) {
1044       return null;
1045     }
1046     return polymers.toArray(new Polymer[0]);
1047   }
1048 
1049   /**
1050    * Sums up charge of the system, checking nonstandard residues for non-unitary charges.
1051    *
1052    * @param alwaysLog Log non-unitary charge warnings for all nodes
1053    * @return System charge
1054    */
1055   public double getCharge(boolean alwaysLog) {
1056     double totalCharge = 0;
1057     for (MSNode node : getNodeList()) {
1058       double charge = 0;
1059       boolean isNonstandard = false;
1060       for (Atom atom : node.getAtomList()) {
1061         charge += atom.getCharge(forceField);
1062         if (atom.isModRes()) {
1063           isNonstandard = true;
1064         }
1065       }
1066       if ((alwaysLog || isNonstandard) && (Math.abs(Math.round(charge) - charge) > 1.0E-5)) {
1067         logger.warning(
1068             String.format(" Node %s has non-unitary charge %12.8f", node, charge));
1069       }
1070       totalCharge += charge;
1071     }
1072     return totalCharge;
1073   }
1074 
1075   /**
1076    * Check if this Assembly contains Deuterium.
1077    *
1078    * @return True if one or more atoms is deuterium.
1079    */
1080   public boolean hasDeuterium() {
1081     Atom[] atoms = getAtomArray();
1082     for (Atom atom : atoms) {
1083       if (atom.isDeuterium()) {
1084         return true;
1085       }
1086     }
1087     return false;
1088   }
1089 
1090   /**
1091    * getCrystal
1092    *
1093    * @return a {@link ffx.crystal.Crystal} object.
1094    */
1095   public Crystal getCrystal() {
1096     if (potentialEnergy == null) {
1097       return null;
1098     }
1099     return potentialEnergy.getCrystal();
1100   }
1101 
1102   /**
1103    * Set the Crystal for the Potential of this MolecularAssembly.
1104    *
1105    * @param crystal Crystal instance.
1106    */
1107   public void setCrystal(Crystal crystal) {
1108     if (potentialEnergy != null) {
1109       potentialEnergy.setCrystal(crystal);
1110     }
1111   }
1112 
1113   /**
1114    * Getter for the field <code>currentCycle</code>.
1115    *
1116    * @return a int.
1117    */
1118   public int getCurrentCycle() {
1119     return currentCycle;
1120   }
1121 
1122   /**
1123    * Setter for the field <code>currentCycle</code>.
1124    *
1125    * @param c a int.
1126    */
1127   public void setCurrentCycle(int c) {
1128     if (c <= cycles && c > 0) {
1129       currentCycle = c;
1130       for (Atom atom : getAtomList()) {
1131         atom.setCurrentCycle(currentCycle);
1132       }
1133     }
1134   }
1135 
1136   /**
1137    * Getter for the field <code>cycles</code>.
1138    *
1139    * @return a int.
1140    */
1141   public int getCycles() {
1142     return cycles;
1143   }
1144 
1145   /**
1146    * Setter for the field <code>cycles</code>.
1147    *
1148    * @param c a int.
1149    */
1150   public void setCycles(int c) {
1151     cycles = c;
1152   }
1153 
1154   /**
1155    * {@inheritDoc}
1156    */
1157   @Override
1158   public double getExtent() {
1159     double[] Rc = {0, 0, 0};
1160     int num = getAtomList().size();
1161     for (Atom atom : getAtomList()) {
1162       atom.getXYZ(a);
1163       Rc[0] += a[0];
1164       Rc[1] += a[1];
1165       Rc[2] += a[2];
1166     }
1167 
1168     for (int i = 0; i < 3; i++) {
1169       Rc[i] /= num;
1170     }
1171 
1172     double r, d = 0;
1173     double[] xyz = new double[3];
1174     for (Atom atom : getAtomList()) {
1175       atom.getXYZ(xyz);
1176       sub(xyz, Rc, xyz);
1177       r = length(xyz);
1178       if (d < r) {
1179         d = r;
1180       }
1181     }
1182     return d;
1183   }
1184 
1185   /**
1186    * Getter for the field <code>file</code>.
1187    *
1188    * @return a {@link java.io.File} object.
1189    */
1190   public File getFile() {
1191     return file;
1192   }
1193 
1194   /**
1195    * Setter for the field <code>file</code>.
1196    *
1197    * @param file a {@link java.io.File} object.
1198    */
1199   public void setFile(File file) {
1200     if (file == null) {
1201       // Keep the current file.
1202       return;
1203     }
1204     this.file = file;
1205   }
1206 
1207   /**
1208    * Getter for the field <code>archiveFile</code>.
1209    *
1210    * @return a {@link java.io.File} object.
1211    */
1212   public File getArchiveFile() {
1213     return archiveFile;
1214   }
1215 
1216   /**
1217    * Set the File for writing out an archive.
1218    *
1219    * @param archiveFile The archive file.
1220    */
1221   public void setArchiveFile(File archiveFile) {
1222     if (archiveFile == null) {
1223       // Keep the current archive file.
1224       return;
1225     }
1226     this.archiveFile = archiveFile;
1227   }
1228 
1229   /**
1230    * Getter for the field <code>forceField</code>.
1231    *
1232    * @return a {@link ffx.potential.parameters.ForceField} object.
1233    */
1234   public ForceField getForceField() {
1235     return forceField;
1236   }
1237 
1238   /**
1239    * Setter for the field <code>forceField</code>.
1240    *
1241    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
1242    */
1243   public void setForceField(ForceField forceField) {
1244     this.forceField = forceField;
1245   }
1246 
1247   /**
1248    * Getter for the field <code>fractionalMode</code>.
1249    *
1250    * @return a {@link ffx.potential.MolecularAssembly.FractionalMode} object.
1251    */
1252   public FractionalMode getFractionalMode() {
1253     return fractionalMode;
1254   }
1255 
1256   /**
1257    * Setter for the field <code>fractionalMode</code>.
1258    *
1259    * @param mode a {@link ffx.potential.MolecularAssembly.FractionalMode} object.
1260    */
1261   public void setFractionalMode(FractionalMode mode) {
1262     fractionalMode = mode;
1263   }
1264 
1265   /**
1266    * Gets the header lines associated with this MolecularAssembly (particularly for PDB)
1267    *
1268    * @return Header lines.
1269    */
1270   public String[] getHeaderLines() {
1271     String[] ret = new String[headerLines.size()];
1272     headerLines.toArray(ret);
1273     return ret;
1274   }
1275 
1276   /**
1277    * Getter for the field <code>ions</code>.
1278    *
1279    * @return a {@link java.util.List} object.
1280    */
1281   public List<MSNode> getIons() {
1282     return ions.getChildList();
1283   }
1284 
1285   /**
1286    * getMass.
1287    *
1288    * @return a double.
1289    */
1290   public double getMass() {
1291     double mass = 0;
1292     for (Atom atom : getAtomArray()) {
1293       mass += atom.getMass();
1294     }
1295     return mass;
1296   }
1297 
1298   /**
1299    * Construct a boolean orray of flags to indicate atoms treated by a neural network.
1300    *
1301    * @return The boolean orray of flags to indicate atoms treated by a neural network.
1302    */
1303   public boolean[] getNeuralNetworkIdentity() {
1304     Atom[] atomArray = getAtomArray();
1305     int nAtoms = atomArray.length;
1306     boolean[] neuralNetwork = new boolean[nAtoms];
1307     for (int i = 0; i < nAtoms; i++) {
1308       neuralNetwork[i] = atomArray[i].isNeuralNetwork();
1309     }
1310     return neuralNetwork;
1311   }
1312 
1313   /**
1314    * This method assigns a unique integer to every molecule in the MolecularAssembly beginning at 0.
1315    * An integer array with these values for each atom is returned.
1316    *
1317    * @return an array of molecule numbers for each atom.
1318    */
1319   public int[] getMoleculeNumbers() {
1320     int[] moleculeNumber = new int[getAtomList().size()];
1321     int current = 0;
1322     // Loop over polymers together
1323     Polymer[] polymers = getChains();
1324     if (polymers != null) {
1325       for (Polymer polymer : polymers) {
1326         List<Atom> atomList = polymer.getAtomList();
1327         for (Atom atom : atomList) {
1328           moleculeNumber[atom.getXyzIndex() - 1] = current;
1329           atom.setMoleculeNumber(current);
1330         }
1331         current++;
1332       }
1333     }
1334 
1335     // Loop over each molecule
1336     for (MSNode molecule : molecules.getChildList()) {
1337       List<Atom> atomList = molecule.getAtomList();
1338       for (Atom atom : atomList) {
1339         moleculeNumber[atom.getXyzIndex() - 1] = current;
1340         atom.setMoleculeNumber(current);
1341       }
1342       current++;
1343     }
1344 
1345     // Loop over each water
1346     for (MSNode wat : water.getChildList()) {
1347       List<Atom> atomList = wat.getAtomList();
1348       for (Atom atom : atomList) {
1349         moleculeNumber[atom.getXyzIndex() - 1] = current;
1350         atom.setMoleculeNumber(current);
1351       }
1352       current++;
1353     }
1354 
1355     // Loop over each ion
1356     for (MSNode ion : ions.getChildList()) {
1357       List<Atom> atomList = ion.getAtomList();
1358       for (Atom atom : atomList) {
1359         moleculeNumber[atom.getXyzIndex() - 1] = current;
1360         atom.setMoleculeNumber(current);
1361       }
1362       current++;
1363     }
1364 
1365     return moleculeNumber;
1366   }
1367 
1368   /**
1369    * Getter for the field <code>molecules</code>.
1370    *
1371    * @return a {@link java.util.List} object.
1372    */
1373   public List<MSNode> getMolecules() {
1374     return molecules.getChildList();
1375   }
1376 
1377   public Molecule[] getMoleculeArray() {
1378     Molecule[] m = new Molecule[molecules.getChildCount()];
1379     int i = 0;
1380     for (MSNode msNode : molecules.getChildList()) {
1381       m[i++] = (Molecule) msNode;
1382     }
1383     return m;
1384   }
1385 
1386   /**
1387    * This method sets all HETATM molecules, including water and ions, to use the given chainID and
1388    * then renumbers the molecules.
1389    * <p>
1390    * If there is a polymer with the given chainID, then numbering begins after the final ResID.
1391    * Otherwise numbering begins at 1.
1392    * <p>
1393    * This is useful for producing consistent PDB file output.
1394    *
1395    * @param chainID The Character chainID to use for all HETATM molecules.
1396    */
1397   public void setChainIDAndRenumberMolecules(Character chainID) {
1398     int resID = 1;
1399 
1400     Polymer polymer = getPolymer(chainID, chainID.toString(), false);
1401     if (polymer != null) {
1402       List<Residue> residues = polymer.getResidues();
1403       for (Residue residue : residues) {
1404         int resID2 = residue.getResidueNumber();
1405         if (resID2 >= resID) {
1406           resID = resID2 + 1;
1407         }
1408       }
1409     }
1410     for (MSNode m : getMolecules()) {
1411       Molecule molecule = (Molecule) m;
1412       molecule.setChainID(chainID);
1413       molecule.setResidueNum(resID++);
1414     }
1415     for (MSNode ion : getIons()) {
1416       Molecule m = (Molecule) ion;
1417       m.setChainID(chainID);
1418       m.setResidueNum(resID++);
1419     }
1420     for (MSNode wat : getWater()) {
1421       Molecule water = (Molecule) wat;
1422       water.setChainID(chainID);
1423       water.setResidueNum(resID++);
1424     }
1425   }
1426 
1427   /**
1428    * A list that includes residues, molecules, water and ions.
1429    *
1430    * @return a {@link java.util.List} of residues, molecules, water and ions.
1431    */
1432   public List<MSNode> getNodeList() {
1433     return getNodeList(false);
1434   }
1435 
1436   /**
1437    * A list that includes residues, molecules, water and ions.
1438    *
1439    * @param ignorePolymers If true, the list will not include residues.
1440    * @return a {@link java.util.List} of residues, molecules, water and ions.
1441    */
1442   public List<MSNode> getNodeList(boolean ignorePolymers) {
1443     List<MSNode> nodeList = new ArrayList<>();
1444     if (!ignorePolymers) {
1445       Polymer[] polymers = getChains();
1446       if (polymers != null) {
1447         for (Polymer polymer : polymers) {
1448           nodeList.addAll(polymer.getResidues());
1449         }
1450       }
1451     }
1452     nodeList.addAll(ions.getChildList());
1453     nodeList.addAll(water.getChildList());
1454     nodeList.addAll(molecules.getChildList());
1455     return nodeList;
1456   }
1457 
1458   /**
1459    * Getter for the field <code>offset</code>.
1460    *
1461    * @return a {@link org.jogamp.vecmath.Vector3d} object.
1462    */
1463   public Vector3d getOffset() {
1464     if (offset == null) {
1465       offset = new Vector3d(0.0, 0.0, 0.0);
1466     }
1467 
1468     return offset;
1469   }
1470 
1471   /**
1472    * Setter for the field <code>offset</code>.
1473    *
1474    * @param o a {@link org.jogamp.vecmath.Vector3d} object.
1475    */
1476   public void setOffset(Vector3d o) {
1477     offset = o;
1478   }
1479 
1480   /**
1481    * Getter for the field <code>originToRot</code>.
1482    *
1483    * @return a {@link org.jogamp.java3d.TransformGroup} object.
1484    */
1485   public TransformGroup getOriginToRot() {
1486     return originToRot;
1487   }
1488 
1489   /**
1490    * getParallelTeam.
1491    *
1492    * @return a {@link edu.rit.pj.ParallelTeam} object.
1493    */
1494   public ParallelTeam getParallelTeam() {
1495     if (potentialEnergy != null) {
1496       return potentialEnergy.getParallelTeam();
1497     } else {
1498       return null;
1499     }
1500   }
1501 
1502   /**
1503    * getPolymer
1504    *
1505    * @param chainID a {@link java.lang.Character} object.
1506    * @param segID   a {@link java.lang.String} object.
1507    * @param create  a boolean.
1508    * @return a {@link ffx.potential.bonded.Polymer} object.
1509    */
1510   public Polymer getPolymer(Character chainID, String segID, boolean create) {
1511     for (MSNode node : getAtomNodeList()) {
1512       if (node instanceof Polymer) {
1513         Polymer polymer = (Polymer) node;
1514         if (polymer.getName().equals(segID) && polymer.getChainID().equals(chainID)) {
1515           return (Polymer) node;
1516         }
1517       }
1518     }
1519     if (create) {
1520       Polymer polymer = new Polymer(chainID, segID, true);
1521       addMSNode(polymer);
1522       return polymer;
1523     }
1524 
1525     return null;
1526   }
1527 
1528   /**
1529    * Getter for the field <code>potentialEnergy</code>.
1530    *
1531    * @return a {@link ffx.potential.ForceFieldEnergy} object.
1532    */
1533   public ForceFieldEnergy getPotentialEnergy() {
1534     return potentialEnergy;
1535   }
1536 
1537   /**
1538    * Getter for the field <code>properties</code>.
1539    *
1540    * @return a {@link org.apache.commons.configuration2.CompositeConfiguration} object.
1541    */
1542   public CompositeConfiguration getProperties() {
1543     return properties == null ? forceField.getProperties() : properties;
1544   }
1545 
1546   /**
1547    * getResidueList
1548    *
1549    * @return a {@link java.util.List} object.
1550    */
1551   public List<Residue> getResidueList() {
1552     List<Residue> residues = new ArrayList<>();
1553     ListIterator<MSNode> li, lj;
1554     MSNode o;
1555     Polymer c;
1556     for (li = getAtomNodeList().listIterator(); li.hasNext(); ) {
1557       o = li.next();
1558       if (o instanceof Polymer) {
1559         c = (Polymer) o;
1560         for (lj = c.getAtomNodeList().listIterator(); lj.hasNext(); ) {
1561           o = lj.next();
1562           if (o instanceof Residue) {
1563             residues.add((Residue) o);
1564           }
1565         }
1566       }
1567     }
1568     return residues;
1569   }
1570 
1571   /**
1572    * getTransformGroup
1573    *
1574    * @return a {@link org.jogamp.java3d.TransformGroup} object.
1575    */
1576   public TransformGroup getTransformGroup() {
1577     return originToRot;
1578   }
1579 
1580   /**
1581    * getWater
1582    *
1583    * @return a {@link java.util.List} object.
1584    */
1585   public List<MSNode> getWater() {
1586     return water.getChildList();
1587   }
1588 
1589   /**
1590    * getWireFrame
1591    *
1592    * @return a {@link org.jogamp.java3d.Node} object.
1593    */
1594   public Node getWireFrame() {
1595     return wire;
1596   }
1597 
1598   /**
1599    * isVisible
1600    *
1601    * @return a boolean.
1602    */
1603   public boolean isVisible() {
1604     return visible;
1605   }
1606 
1607   /**
1608    * loadVRML
1609    *
1610    * @return a {@link org.jogamp.java3d.BranchGroup} object.
1611    */
1612   public BranchGroup loadVRML() {
1613     //        try {
1614     //            VrmlLoader loader = new VrmlLoader();
1615     //            VrmlScene scene = null;
1616     //            if (vrmlFile != null && vrmlFile.exists()) {
1617     //                scene = (VrmlScene) loader.load(vrmlFile.getAbsolutePath());
1618     //            } else if (vrmlURL != null) {
1619     //                scene = (VrmlScene) loader.load(vrmlURL);
1620     //            } else {
1621     //                return null;
1622     //            }
1623     //            BranchGroup bg = scene.getSceneGroup();
1624     //            recurseVRML(bg);
1625     //            bg.setCapability(BranchGroup.ALLOW_DETACH);
1626     //            bg.setCapability(BranchGroup.ALLOW_BOUNDS_READ);
1627     //            bg.compile();
1628     //            return bg;
1629     //        } catch (Exception e) {
1630     //            String message = "Fatal exception loading VRML.\n";
1631     //            logger.log(Level.SEVERE, message, e);
1632     //            System.exit(-1);
1633     //            return null;
1634     //        }
1635     return null;
1636   }
1637 
1638   /**
1639    * Moves the center of all chemical entities into the primary unit cell. Somewhat experimental
1640    * feature; use with caution.
1641    */
1642   public void moveAllIntoUnitCell() {
1643     moveIntoUnitCell(getChains());
1644     moveIntoUnitCell(getWater());
1645     moveIntoUnitCell(getIons());
1646     moveIntoUnitCell(getMolecules());
1647   }
1648 
1649   /**
1650    * moveCenter
1651    *
1652    * @param d an array of double.
1653    */
1654   public void moveCenter(double[] d) {
1655     for (Atom atom : getAtomList()) {
1656       atom.move(d);
1657     }
1658   }
1659 
1660   /**
1661    * Move to fractional coordinates.
1662    */
1663   public void moveToFractionalCoordinates() {
1664 
1665     if (fractionalCoordinates == null) {
1666       return;
1667     }
1668 
1669     Crystal unitCell = getCrystal().getUnitCell();
1670     double[] com = new double[3];
1671 
1672     switch (fractionalMode) {
1673       case MOLECULE:
1674         int iMolecule = 0;
1675         Polymer[] polymers = getChains();
1676         if (polymers != null && polymers.length > 0) {
1677           // Find the center of mass
1678           for (Polymer polymer : polymers) {
1679             List<Atom> list = polymer.getAtomList();
1680             double totalMass = 0.9;
1681             com[0] = 0.0;
1682             com[1] = 0.0;
1683             com[2] = 0.0;
1684             for (Atom atom : list) {
1685               double m = atom.getMass();
1686               com[0] += atom.getX() * m;
1687               com[1] += atom.getY() * m;
1688               com[2] += atom.getZ() * m;
1689               totalMass += m;
1690             }
1691             com[0] /= totalMass;
1692             com[1] /= totalMass;
1693             com[2] /= totalMass;
1694             // Find the new center of mass in fractional coordinates.
1695             unitCell.toFractionalCoordinates(com, com);
1696             // Find the reciprocal translation vector.
1697             double[] frac = fractionalCoordinates[iMolecule++];
1698             com[0] = frac[0] - com[0];
1699             com[1] = frac[1] - com[1];
1700             com[2] = frac[2] - com[2];
1701             // Convert the fractional translation vector to Cartesian coordinates.
1702             unitCell.toCartesianCoordinates(com, com);
1703             // Move all atoms.
1704             for (Atom atom : list) {
1705               atom.move(com);
1706             }
1707           }
1708         }
1709 
1710         // Loop over each molecule
1711         List<MSNode> molecules = getMolecules();
1712         for (MSNode molecule : molecules) {
1713           List<Atom> list = molecule.getAtomList();
1714           // Find the center of mass
1715           com[0] = 0.0;
1716           com[1] = 0.0;
1717           com[2] = 0.0;
1718           double totalMass = 0.0;
1719           for (Atom atom : list) {
1720             double m = atom.getMass();
1721             com[0] += atom.getX() * m;
1722             com[1] += atom.getY() * m;
1723             com[2] += atom.getZ() * m;
1724             totalMass += m;
1725           }
1726           com[0] /= totalMass;
1727           com[1] /= totalMass;
1728           com[2] /= totalMass;
1729           // Find the new center of mass in fractional coordinates.
1730           unitCell.toFractionalCoordinates(com, com);
1731           // Find the reciprocal translation vector to the previous COM.
1732           double[] frac = fractionalCoordinates[iMolecule++];
1733           com[0] = frac[0] - com[0];
1734           com[1] = frac[1] - com[1];
1735           com[2] = frac[2] - com[2];
1736           // Convert the fractional translation vector to Cartesian coordinates.
1737           unitCell.toCartesianCoordinates(com, com);
1738           // Move all atoms.
1739           for (Atom atom : list) {
1740             atom.move(com);
1741           }
1742         }
1743 
1744         // Loop over each water
1745         List<MSNode> water = getWater();
1746         for (MSNode wat : water) {
1747           List<Atom> list = wat.getAtomList();
1748           // Find the center of mass
1749           com[0] = 0.0;
1750           com[1] = 0.0;
1751           com[2] = 0.0;
1752           double totalMass = 0.0;
1753           for (Atom atom : list) {
1754             double m = atom.getMass();
1755             com[0] += atom.getX() * m;
1756             com[1] += atom.getY() * m;
1757             com[2] += atom.getZ() * m;
1758             totalMass += m;
1759           }
1760           com[0] /= totalMass;
1761           com[1] /= totalMass;
1762           com[2] /= totalMass;
1763           // Find the new center of mass in fractional coordinates.
1764           unitCell.toFractionalCoordinates(com, com);
1765           // Find the reciprocal translation vector to the previous COM.
1766           double[] frac = fractionalCoordinates[iMolecule++];
1767           com[0] = frac[0] - com[0];
1768           com[1] = frac[1] - com[1];
1769           com[2] = frac[2] - com[2];
1770           // Convert the fractional translation vector to Cartesian coordinates.
1771           unitCell.toCartesianCoordinates(com, com);
1772 
1773           double r = length(com);
1774 
1775           // Warn if an atom is moved more than 1 Angstrom.
1776           if (r > 1.0) {
1777             int i = iMolecule - 1;
1778             logger.info(String.format(" %d R: %16.8f", i, r));
1779             logger.info(
1780                 String.format(" %d FRAC %16.8f %16.8f %16.8f", i, frac[0], frac[1], frac[2]));
1781             logger.info(String.format(" %d COM  %16.8f %16.8f %16.8f", i, com[0], com[1], com[2]));
1782           }
1783 
1784           // Move all atoms.
1785           for (Atom atom : list) {
1786             atom.move(com);
1787           }
1788         }
1789 
1790         // Loop over each ion
1791         List<MSNode> ions = getIons();
1792         for (MSNode ion : ions) {
1793           List<Atom> list = ion.getAtomList();
1794           // Find the center of mass
1795           com[0] = 0.0;
1796           com[1] = 0.0;
1797           com[2] = 0.0;
1798           double totalMass = 0.0;
1799           for (Atom atom : list) {
1800             double m = atom.getMass();
1801             com[0] += atom.getX() * m;
1802             com[1] += atom.getY() * m;
1803             com[2] += atom.getZ() * m;
1804             totalMass += m;
1805           }
1806           com[0] /= totalMass;
1807           com[1] /= totalMass;
1808           com[2] /= totalMass;
1809           // Find the new center of mass in fractional coordinates.
1810           unitCell.toFractionalCoordinates(com, com);
1811           // Find the reciprocal translation vector to the previous COM.
1812           double[] frac = fractionalCoordinates[iMolecule++];
1813           com[0] = frac[0] - com[0];
1814           com[1] = frac[1] - com[1];
1815           com[2] = frac[2] - com[2];
1816           // Convert the fractional translation vector to Cartesian coordinates.
1817           unitCell.toCartesianCoordinates(com, com);
1818           // Move all atoms.
1819           for (Atom atom : list) {
1820             atom.move(com);
1821           }
1822         }
1823         break;
1824       case ATOM:
1825         Atom[] atoms = getAtomArray();
1826         int nAtoms = atoms.length;
1827         for (int i = 0; i < nAtoms; i++) {
1828           // Convert the stored factional coordinates to Cartesian coordinates in the current
1829           // unitcell.
1830           unitCell.toCartesianCoordinates(fractionalCoordinates[i], com);
1831           atoms[i].moveTo(com);
1832         }
1833         break;
1834       case OFF:
1835         break;
1836     }
1837   }
1838 
1839   /**
1840    * Rotate about a point in given in the System's Local Coordinates
1841    *
1842    * @param v Vector3d
1843    */
1844   public void rotateAbout(Vector3d v) {
1845     Vector3d newRotPoint = new Vector3d(v);
1846     originToRot.getTransform(originToRotT3D);
1847     originToRotT3D.get(originToRotV3D);
1848     originToRotT3D.setTranslation(new Vector3d(0, 0, 0));
1849     rotToCOM.getTransform(rotToCOMT3D);
1850     rotToCOMT3D.get(rotToCOMV3D);
1851     newRotPoint.add(rotToCOMV3D);
1852     originToRotT3D.transform(newRotPoint);
1853     newRotPoint.add(originToRotV3D);
1854     originToRotT3D.setTranslation(newRotPoint);
1855     rotToCOMV3D.set(v);
1856     rotToCOMV3D.negate();
1857     rotToCOMT3D.setTranslation(rotToCOMV3D);
1858     originToRot.setTransform(originToRotT3D);
1859     rotToCOM.setTransform(rotToCOMT3D);
1860   }
1861 
1862   /**
1863    * sceneGraphChange
1864    *
1865    * @param newShapes a {@link java.util.List} object.
1866    */
1867   public void sceneGraphChange(List<BranchGroup> newShapes) {
1868     if (newShapes == null) {
1869       newShapes = myNewShapes;
1870     }
1871 
1872     if (newShapes.isEmpty()) {
1873       return;
1874     }
1875 
1876     boolean reCompile = false;
1877     // Check for nodes (new and/or recycled) being added to this
1878     // MolecularAssembly
1879     for (ListIterator<BranchGroup> li = newShapes.listIterator(); li.hasNext(); ) {
1880       BranchGroup group = li.next();
1881       li.remove();
1882       // This is code for cycling between two MolecularAssemblies
1883       if (group.getUserData() != null) {
1884         logger.info(format("%s %s", group, group.getUserData().toString()));
1885         /*
1886          * Object userData = group.getUserData(); if (userData!=this) {
1887          * // The appearance has already been set during a recursive
1888          * call to setView, // although we need to turn back on
1889          * Pickablility. TransformGroup tg = (TransformGroup)
1890          * group.getChild(0); Shape3D shape = (Shape3D) tg.getChild(0);
1891          * shape.setPickable(true); group.setUserData(this); if
1892          * (!reCompile) { if (childNodes.isLive()) {
1893          * childNodes.detach(); } reCompile = true; }
1894          * childNodes.moveTo(group);
1895          */
1896       } else {
1897         // This is a new group since it has no userData.
1898         // We can not query for the identity of its parent later, so
1899         // we will store it as a userData reference.
1900         group.setUserData(this);
1901         if (!reCompile) {
1902           if (childNodes.isLive()) {
1903             childNodes.detach();
1904           }
1905 
1906           reCompile = true;
1907         }
1908 
1909         childNodes.addChild(group);
1910       }
1911     }
1912     if (reCompile) {
1913       childNodes.compile();
1914       base.addChild(childNodes);
1915     }
1916   }
1917 
1918   /**
1919    * {@inheritDoc}
1920    */
1921   @Override
1922   public void setColor(RendererCache.ColorModel newColorModel, Color3f color, Material mat) {
1923     for (MSNode msNode : getAtomNodeList()) {
1924       MSGroup group = (MSGroup) msNode;
1925       group.setColor(newColorModel, color, mat);
1926     }
1927 
1928     for (MSNode m : molecules.getChildList()) {
1929       m.setColor(newColorModel, color, mat);
1930     }
1931 
1932     for (MSNode m : water.getChildList()) {
1933       m.setColor(newColorModel, color, mat);
1934     }
1935 
1936     for (MSNode m : ions.getChildList()) {
1937       m.setColor(newColorModel, color, mat);
1938     }
1939   }
1940 
1941   /**
1942    * setPotential
1943    *
1944    * @param potentialEnergy a {@link ffx.potential.ForceFieldEnergy} object.
1945    */
1946   public void setPotential(ForceFieldEnergy potentialEnergy) {
1947     this.potentialEnergy = potentialEnergy;
1948   }
1949 
1950   /**
1951    * {@inheritDoc}
1952    */
1953   @Override
1954   public void setView(RendererCache.ViewModel newViewModel, List<BranchGroup> newShapes) {
1955     // Just Detach the whole system branch group
1956     if (newViewModel == RendererCache.ViewModel.DESTROY) {
1957       if (switchGroup != null) {
1958         switchGroup.setWhichChild(Switch.CHILD_NONE);
1959       }
1960       visible = false;
1961     } else if (newViewModel == RendererCache.ViewModel.SHOWVRML) {
1962       switchGroup.setWhichChild(Switch.CHILD_ALL);
1963     } else if (newViewModel == RendererCache.ViewModel.HIDEVRML) {
1964       switchGroup.setWhichChild(0);
1965     } else {
1966       setWireWidth(RendererCache.bondwidth);
1967       if (newViewModel == RendererCache.ViewModel.DETAIL && childNodes.isLive()) {
1968         childNodes.detach();
1969       }
1970       /*
1971        We'll collect new Scenegraph Shapes in our newShapeNode This is to avoid the case where
1972        setView is called from the root node and all new shapes for every MolecularAssembly would
1973        then be put into the same List.
1974        */
1975       super.setView(newViewModel, myNewShapes);
1976       List<Molecule> moleculeList = getList(Molecule.class, new ArrayList<>());
1977       for (Molecule m : moleculeList) {
1978         m.setView(newViewModel, myNewShapes);
1979       }
1980       for (MSNode m : molecules.getChildList()) {
1981         m.setView(newViewModel, myNewShapes);
1982       }
1983       for (MSNode m : water.getChildList()) {
1984         m.setView(newViewModel, myNewShapes);
1985       }
1986       for (MSNode m : ions.getChildList()) {
1987         m.setView(newViewModel, myNewShapes);
1988       }
1989       if (newViewModel == RendererCache.ViewModel.INVISIBLE) {
1990         switchGroup.setWhichChild(0);
1991       }
1992       if (newViewModel == RendererCache.ViewModel.DETAIL) {
1993         childNodes.compile();
1994         base.addChild(childNodes);
1995       }
1996     }
1997   }
1998 
1999   /**
2000    * setWireWidth
2001    *
2002    * @param f a float.
2003    */
2004   public void setWireWidth(float f) {
2005     if (wire == null) {
2006       return;
2007     }
2008 
2009     lineAttributes.setLineWidth(f);
2010   }
2011 
2012   /**
2013    * The MolecularAssembly BranchGroup has two TransformGroups between it and the "base" node where
2014    * geometry is attached. If the point between the two transformations is where user rotation
2015    * occurs. For example, if rotating about the center of mass of the system, the RotToCOM
2016    * transformation will be an identity transformation (ie. none). If rotation is about some atom or
2017    * group of atoms within the system, then the RotToCOM transformation will be a translation from
2018    * that point to the COM.
2019    *
2020    * @param zero boolean
2021    * @return BranchGroup
2022    */
2023   private BranchGroup createScene(boolean zero) {
2024     originToRotT3D = new Transform3D();
2025     originToRotV3D = new Vector3d();
2026     originToRot = new TransformGroup(originToRotT3D);
2027     branchGroup = new BranchGroup();
2028     rotToCOM = new TransformGroup();
2029     rotToCOMT3D = new Transform3D();
2030     rotToCOMV3D = new Vector3d();
2031     // Set capabilities needed for picking and moving the MolecularAssembly
2032     branchGroup.setCapability(BranchGroup.ALLOW_DETACH);
2033     originToRot.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
2034     originToRot.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
2035     originToRot.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
2036     rotToCOM.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
2037     rotToCOM.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
2038     // Put the MolecularAssembly in the middle of the scene
2039     if (zero) {
2040       originToRotV3D.set(0.0, 0.0, 0.0);
2041       originToRotT3D.set(originToRotV3D);
2042       originToRot.setTransform(originToRotT3D);
2043     }
2044     wire = renderWire();
2045     switchGroup = new Switch(Switch.CHILD_NONE);
2046     switchGroup.setCapability(Switch.ALLOW_SWITCH_WRITE);
2047     base = new BranchGroup();
2048     base.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
2049     base.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
2050     childNodes = new BranchGroup();
2051     childNodes.setCapability(BranchGroup.ALLOW_DETACH);
2052     childNodes.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
2053     childNodes.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
2054     switchGroup.addChild(base);
2055     if (wire != null) {
2056       base.addChild(wire);
2057     }
2058     vrml = loadVRML();
2059     if (vrml != null) {
2060       vrmlTG = new TransformGroup();
2061       vrmlTd = new Transform3D();
2062       vrmlTG.setTransform(vrmlTd);
2063       vrmlTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
2064       vrmlTG.addChild(vrml);
2065       switchGroup.addChild(vrmlTG);
2066       setView(RendererCache.ViewModel.INVISIBLE, null);
2067     }
2068     switchGroup.setWhichChild(Switch.CHILD_ALL);
2069     rotToCOM.addChild(switchGroup);
2070     originToRot.addChild(rotToCOM);
2071     branchGroup.addChild(originToRot);
2072     branchGroup.compile();
2073     return branchGroup;
2074   }
2075 
2076   void finishDestruction() {
2077     detach();
2078     super.destroy();
2079   }
2080 
2081   /**
2082    * Move mass from heavy atoms to their attached hydrogens.
2083    *
2084    * <p>The mass of each hydrogen is scaled by a factor of 3. The mass of the heavy atom is reduced
2085    * by 2 AMU.
2086    */
2087   private void applyHeavyHydrogen() {
2088     List<Bond> bonds = getBondList();
2089     for (Bond bond : bonds) {
2090       Atom a1 = bond.getAtom(0);
2091       Atom a2 = bond.getAtom(1);
2092       if (a1.isHydrogen() && a2.isHeavy()) {
2093         double hydrogenMass = a1.getMass();
2094         double heavyAtomMass = a2.getMass();
2095         if (hydrogenMass < 1.1) {
2096           a2.setMass(heavyAtomMass - 2.0 * hydrogenMass);
2097           a1.setMass(3.0 * hydrogenMass);
2098         }
2099       } else if (a1.isHeavy() && a2.isHydrogen()) {
2100         double heavyAtomMass = a1.getMass();
2101         double hydrogenMass = a2.getMass();
2102         if (hydrogenMass < 1.1) {
2103           a1.setMass(heavyAtomMass - 2.0 * hydrogenMass);
2104           a2.setMass(3.0 * hydrogenMass);
2105         }
2106       }
2107     }
2108   }
2109 
2110   private Atom getResidue(Atom atom, boolean create) {
2111     return getResidue(atom, create, Residue.ResidueType.UNK);
2112   }
2113 
2114   private Atom getResidue(Atom atom, boolean create, ResidueType defaultRT) {
2115     Character chainID = atom.getChainID();
2116     String resName = atom.getResidueName();
2117     int resNum = atom.getResidueNumber();
2118     String segID = atom.getSegID();
2119     // Find/Create the chain
2120     Polymer polymer = getPolymer(chainID, segID, create);
2121     if (polymer == null) {
2122       return null;
2123     }
2124 
2125     Residue res;
2126     if (titrateConformer) {
2127       res = polymer.getResidue(atomInitial.getResidueName(), atomInitial.getResidueNumber(), create, defaultRT);
2128       res.setName(atom.getResidueName());
2129     } else {
2130       res = polymer.getResidue(resName, resNum, create, defaultRT);
2131     }
2132     if (create && res != null) {
2133       res.setTitrateConformers(titrateConformer);
2134       if (titrateConformer) {
2135         res.setAtomInitial(atomInitial);
2136       }
2137       return (Atom) res.addMSNode(atom);
2138     }
2139     return null;
2140   }
2141 
2142   private Atom getMolecule(Atom atom, boolean create) {
2143     String resName = atom.getResidueName();
2144     int resNum = atom.getResidueNumber();
2145     Character chainID = atom.getChainID();
2146     String segID = atom.getSegID();
2147 
2148     String key = resNum + resName + segID;
2149     Molecule m = ionHashMap.get(key);
2150     if (m == null) {
2151       m = waterHashMap.get(key);
2152       if (m == null) {
2153         m = moleculeHashMap.get(key);
2154       }
2155     }
2156     if (m != null) {
2157       return (Atom) m.addMSNode(atom);
2158     }
2159 
2160     if (create) {
2161       boolean isWater = false;
2162       boolean isIon = false;
2163       if (StringUtils.looksLikeWater(resName)) {
2164         if (!resName.equalsIgnoreCase("DOD")) {
2165           resName = StringUtils.STANDARD_WATER_NAME;
2166         }
2167         isWater = true;
2168       } else if (StringUtils.looksLikeIon(resName)) {
2169         resName = StringUtils.tryParseIon(resName);
2170         isIon = true;
2171       }
2172       atom.setResName(resName);
2173       m = new Molecule(resName, resNum, chainID, segID);
2174       m.addMSNode(atom);
2175       if (resName == null) {
2176         logger.warning(format(" Attempting to create a molecule %s with a null name on atom %s! Defaulting to creating a generic Molecule.",
2177             m, atom));
2178         molecules.add(m);
2179         moleculeHashMap.put(key, m);
2180       } else if (isWater) {
2181         water.add(m);
2182         waterHashMap.put(key, m);
2183       } else if (isIon) {
2184         ions.add(m);
2185         ionHashMap.put(key, m);
2186       } else {
2187         molecules.add(m);
2188         moleculeHashMap.put(key, m);
2189       }
2190       return atom;
2191     } else {
2192       return null;
2193     }
2194   }
2195 
2196   private void recurseVRML(Node node) {
2197     if (node instanceof Shape3D) {
2198       Shape3D s3d = (Shape3D) node;
2199       PickTool.setCapabilities(s3d, PickTool.INTERSECT_COORD);
2200     } else if (node instanceof SharedGroup) {
2201       SharedGroup sg = (SharedGroup) node;
2202       for (Iterator<Node> e = sg.getAllChildren(); e.hasNext(); ) {
2203         recurseVRML(e.next());
2204       }
2205     } else if (node instanceof BranchGroup) {
2206       BranchGroup bg = (BranchGroup) node;
2207       for (Iterator<Node> e = bg.getAllChildren(); e.hasNext(); ) {
2208         recurseVRML(e.next());
2209       }
2210     } else if (node instanceof TransformGroup) {
2211       TransformGroup vrmlTG1 = (TransformGroup) node;
2212       for (Iterator<Node> e = vrmlTG1.getAllChildren(); e.hasNext(); ) {
2213         node = e.next();
2214         recurseVRML(node);
2215       }
2216     } else if (node instanceof Link) {
2217       Link link = (Link) node;
2218       recurseVRML(link.getSharedGroup());
2219     } else if (node instanceof Group) {
2220       Group group = (Group) node;
2221       for (Iterator<Node> e = group.getAllChildren(); e.hasNext(); ) {
2222         recurseVRML(e.next());
2223       }
2224     }
2225   }
2226 
2227   /**
2228    * {@inheritDoc}
2229    */
2230   @Override
2231   protected void removeLeaves() {
2232     super.removeLeaves();
2233     MSNode macroNode = getAtomNode();
2234     if (macroNode != null) {
2235       if (macroNode.getChildCount() > 0) {
2236         getAtomNode().setName("Macromolecules " + "(" + macroNode.getChildCount() + ")");
2237       } else if (macroNode.getParent() == this) {
2238         removeChild(macroNode);
2239       }
2240     }
2241 
2242     if (molecules.getChildCount() == 0) {
2243       removeChild(molecules);
2244     } else {
2245       molecules.setName("Hetero Molecules " + "(" + molecules.getChildCount() + ")");
2246     }
2247 
2248     if (ions.getChildCount() == 0) {
2249       removeChild(ions);
2250     } else {
2251       ions.setName("Ions " + "(" + ions.getChildCount() + ")");
2252     }
2253 
2254     if (water.getChildCount() == 0) {
2255       removeChild(water);
2256     } else {
2257       water.setName("Water " + "(" + water.getChildCount() + ")");
2258     }
2259   }
2260 
2261   private Shape3D renderWire() {
2262     List<Bond> bonds = getBondList();
2263     int numbonds = bonds.size();
2264     if (numbonds < 1) {
2265       return null;
2266     }
2267 
2268     Vector3d bondmidpoint = new Vector3d();
2269     double[] mid = {0, 0, 0};
2270     Vector3d v1 = new Vector3d();
2271     Vector3d v2 = new Vector3d();
2272     float[] a1 = {0, 0, 0};
2273     float[] a2 = {0, 0, 0};
2274     float[] col = new float[4];
2275 
2276     Atom atom1, atom2;
2277     LineArray la =
2278         new LineArray(
2279             4 * numbonds,
2280             GeometryArray.COORDINATES | GeometryArray.COLOR_4 | GeometryArray.NORMALS);
2281     la.setCapability(LineArray.ALLOW_COORDINATE_WRITE);
2282     la.setCapability(LineArray.ALLOW_COORDINATE_READ);
2283     la.setCapability(LineArray.ALLOW_COLOR_WRITE);
2284     la.setCapability(LineArray.ALLOW_COUNT_READ);
2285     la.setCapability(LineArray.ALLOW_INTERSECT);
2286     la.setCapability(LineArray.ALLOW_FORMAT_READ);
2287     atomLookUp = new Atom[4 * numbonds];
2288     int i = 0;
2289     col[3] = 0.9f;
2290     for (Bond bond : bonds) {
2291       bond.setWire(la, i);
2292       atom1 = bond.getAtom(0);
2293       atom2 = bond.getAtom(1);
2294       atom1.getV3D(v1);
2295       atom2.getV3D(v2);
2296       a1[0] = (float) v1.x;
2297       a1[1] = (float) v1.y;
2298       a1[2] = (float) v1.z;
2299       a2[0] = (float) v2.x;
2300       a2[1] = (float) v2.y;
2301       a2[2] = (float) v2.z;
2302       // Find the bond center
2303       bondmidpoint.add(v1, v2);
2304       bondmidpoint.scale(0.5d);
2305       bondmidpoint.get(mid);
2306 
2307       // Atom #1
2308       Atom.AtomColor.get(atom1.getAtomicNumber()).get(col);
2309       atomLookUp[i] = atom1;
2310       la.setCoordinate(i, a1);
2311       la.setColor(i, col);
2312       la.setNormal(i, a2);
2313       i++;
2314 
2315       atomLookUp[i] = atom1;
2316       la.setCoordinate(i, mid);
2317       la.setColor(i, col);
2318       la.setNormal(i, a2);
2319       i++;
2320 
2321       // Atom #2
2322       Atom.AtomColor.get(atom2.getAtomicNumber()).get(col);
2323       atomLookUp[i] = atom2;
2324       la.setCoordinate(i, a2);
2325       la.setColor(i, col);
2326       la.setNormal(i, a1);
2327       i++;
2328 
2329       atomLookUp[i] = atom2;
2330       la.setCoordinate(i, mid);
2331       la.setColor(i, col);
2332       la.setNormal(i, a1);
2333       i++;
2334     }
2335 
2336     ColoringAttributes cola =
2337         new ColoringAttributes(new Color3f(), ColoringAttributes.SHADE_GOURAUD);
2338     Appearance app = new Appearance();
2339     lineAttributes = new LineAttributes();
2340     lineAttributes.setLineWidth(RendererCache.bondwidth);
2341     lineAttributes.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
2342     lineAttributes.setLineAntialiasingEnable(true);
2343     app.setLineAttributes(lineAttributes);
2344     app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
2345     app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_WRITE);
2346     RenderingAttributes ra = new RenderingAttributes();
2347     ra.setAlphaTestValue(0.1f);
2348     ra.setAlphaTestFunction(RenderingAttributes.GREATER);
2349     ra.setDepthBufferEnable(true);
2350     ra.setDepthBufferWriteEnable(true);
2351     app.setRenderingAttributes(ra);
2352     app.setColoringAttributes(cola);
2353     Shape3D wireframe = new Shape3D(la, app);
2354     // PickTool.setCapabilities(wire, PickTool.INTERSECT_COORD);
2355     wireframe.setUserData(this);
2356     wireframe.setBounds(new BoundingSphere(new Point3d(0, 0, 0), 1000.0));
2357     try {
2358       wireframe.setBoundsAutoCompute(false);
2359     } catch (Exception e) {
2360       logger.warning("Unable to set boundsAutoCompute to false.\n" + e);
2361     }
2362 
2363     wireframe.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
2364     wireframe.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
2365     wireframe.setCapability(Shape3D.ALLOW_LOCAL_TO_VWORLD_READ);
2366     return wireframe;
2367   }
2368 
2369   /**
2370    * Return the residue given a polymer ID and residue ID.
2371    *
2372    * @param polymerID The polymer ID.
2373    * @param resID     The residue ID.
2374    * @return The requested Residue or null if it can't be found.
2375    */
2376   public Residue getResidue(int polymerID, int resID) {
2377     Polymer[] polymers = getChains();
2378     if (polymers == null || polymers.length <= polymerID) {
2379       return null;
2380     }
2381     Polymer polymer = polymers[polymerID];
2382     List<Residue> residues = polymer.getResidues();
2383     if (residues == null || residues.size() <= resID) {
2384       return null;
2385     }
2386     return residues.get(resID);
2387   }
2388 
2389   /**
2390    * Returns a list of all water molecules in the system, including both those properly under the
2391    * water node and those under the molecules node.
2392    *
2393    * @return All water in the system
2394    */
2395   private List<Molecule> getAllWaterInclMistyped() {
2396     Stream<Molecule> mistyped =
2397         getMolecules()
2398             .parallelStream()
2399             .map((MSNode m) -> (Molecule) m)
2400             .filter(
2401                 (Molecule m) -> {
2402                   List<Atom> atoms = m.getAtomList();
2403                   if (atoms.size() != 3) {
2404                     return false;
2405                   }
2406                   int nO = 0;
2407                   int nH = 0;
2408                   for (Atom atom : atoms) {
2409                     int el = atom.getAtomicNumber();
2410                     if (el == 1) {
2411                       ++nH;
2412                     } else if (nH == 8) {
2413                       ++nO;
2414                     }
2415                   }
2416                   return nO == 1 && nH == 2;
2417                 });
2418     return Stream.concat(mistyped, getWater().stream().map((MSNode m) -> (Molecule) m))
2419         .distinct()
2420         .collect(Collectors.toList());
2421   }
2422 
2423   /**
2424    * Renames water protons to H1 and H2.
2425    */
2426   void renameWaterProtons() {
2427     for (Molecule water : getAllWaterInclMistyped()) {
2428       Atom H1 = water.getAtomByName("H1", false);
2429       Atom H2 = water.getAtomByName("H2", false);
2430       if (H1 != null && H2 != null) {
2431         continue;
2432       }
2433       for (Atom a : water.getAtomList()) {
2434         if (a.getAtomicNumber() == 1) {
2435           if (H1 == null) {
2436             H1 = a;
2437             H1.setName("H1");
2438           } else if (H2 == null) {
2439             H2 = a;
2440             H2.setName("H2");
2441           }
2442         }
2443       }
2444     }
2445   }
2446 
2447   private void moveIntoUnitCell(MSNode[] groups) {
2448     if (groups != null && groups.length > 0) {
2449       moveIntoUnitCell(Arrays.asList(groups));
2450     }
2451   }
2452 
2453   /**
2454    * Move the center of each listed chemical entity into the primary unit cell.
2455    *
2456    * @param groups Move each MSNode into the primary unit cell.
2457    */
2458   private void moveIntoUnitCell(List<MSNode> groups) {
2459     Crystal cryst = getCrystal().getUnitCell();
2460     if (cryst.aperiodic()) {
2461       return;
2462     }
2463     for (MSNode group : groups) {
2464       double[] com = new double[3];
2465       double[] xyz = new double[3];
2466       double[] translate = new double[3];
2467       List<Atom> atoms = group.getAtomList();
2468       double totMass = 0;
2469       for (Atom atom : atoms) {
2470         double mass = atom.getMass();
2471         totMass += mass;
2472         xyz = atom.getXYZ(xyz);
2473         com[0] += mass * xyz[0];
2474         com[1] += mass * xyz[1];
2475         com[2] += mass * xyz[2];
2476       }
2477       com[0] /= totMass;
2478       com[1] /= totMass;
2479       com[2] /= totMass;
2480 
2481       // Move the COM to the primary unit cell
2482       cryst.toPrimaryCell(com, translate);
2483 
2484       // The translation vector is difference between the new location and the current COM.
2485       translate[0] -= com[0];
2486       translate[1] -= com[1];
2487       translate[2] -= com[2];
2488 
2489       for (Atom atom : atoms) {
2490         atom.move(translate);
2491       }
2492     }
2493   }
2494 
2495   public enum FractionalMode {
2496     OFF,
2497     MOLECULE,
2498     ATOM
2499   }
2500 }