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     logger.info(" Setting Hydrogen Mass to 3 AMU");
2089     List<Bond> bonds = getBondList();
2090     for (Bond bond : bonds) {
2091       Atom a1 = bond.getAtom(0);
2092       Atom a2 = bond.getAtom(1);
2093       if (a1.isHydrogen() && a2.isHeavy()) {
2094         double hydrogenMass = a1.getMass();
2095         double heavyAtomMass = a2.getMass();
2096         if (hydrogenMass < 1.1) {
2097           a2.setMass(heavyAtomMass - 2.0 * hydrogenMass);
2098           a1.setMass(3.0 * hydrogenMass);
2099         }
2100       } else if (a1.isHeavy() && a2.isHydrogen()) {
2101         double heavyAtomMass = a1.getMass();
2102         double hydrogenMass = a2.getMass();
2103         if (hydrogenMass < 1.1) {
2104           a1.setMass(heavyAtomMass - 2.0 * hydrogenMass);
2105           a2.setMass(3.0 * hydrogenMass);
2106         }
2107       }
2108     }
2109   }
2110 
2111   private Atom getResidue(Atom atom, boolean create) {
2112     return getResidue(atom, create, Residue.ResidueType.UNK);
2113   }
2114 
2115   private Atom getResidue(Atom atom, boolean create, ResidueType defaultRT) {
2116     Character chainID = atom.getChainID();
2117     String resName = atom.getResidueName();
2118     int resNum = atom.getResidueNumber();
2119     String segID = atom.getSegID();
2120     // Find/Create the chain
2121     Polymer polymer = getPolymer(chainID, segID, create);
2122     if (polymer == null) {
2123       return null;
2124     }
2125 
2126     Residue res;
2127     if (titrateConformer) {
2128       res = polymer.getResidue(atomInitial.getResidueName(), atomInitial.getResidueNumber(), create, defaultRT);
2129       res.setName(atom.getResidueName());
2130     } else {
2131       res = polymer.getResidue(resName, resNum, create, defaultRT);
2132     }
2133     if (create && res != null) {
2134       res.setTitrateConformers(titrateConformer);
2135       if (titrateConformer) {
2136         res.setAtomInitial(atomInitial);
2137       }
2138       return (Atom) res.addMSNode(atom);
2139     }
2140     return null;
2141   }
2142 
2143   private Atom getMolecule(Atom atom, boolean create) {
2144     String resName = atom.getResidueName();
2145     int resNum = atom.getResidueNumber();
2146     Character chainID = atom.getChainID();
2147     String segID = atom.getSegID();
2148 
2149     String key = resNum + resName + segID;
2150     Molecule m = ionHashMap.get(key);
2151     if (m == null) {
2152       m = waterHashMap.get(key);
2153       if (m == null) {
2154         m = moleculeHashMap.get(key);
2155       }
2156     }
2157     if (m != null) {
2158       return (Atom) m.addMSNode(atom);
2159     }
2160 
2161     if (create) {
2162       boolean isWater = false;
2163       boolean isIon = false;
2164       if (StringUtils.looksLikeWater(resName)) {
2165         if (!resName.equalsIgnoreCase("DOD")) {
2166           resName = StringUtils.STANDARD_WATER_NAME;
2167         }
2168         isWater = true;
2169       } else if (StringUtils.looksLikeIon(resName)) {
2170         resName = StringUtils.tryParseIon(resName);
2171         isIon = true;
2172       }
2173       atom.setResName(resName);
2174       m = new Molecule(resName, resNum, chainID, segID);
2175       m.addMSNode(atom);
2176       if (resName == null) {
2177         logger.warning(format(" Attempting to create a molecule %s with a null name on atom %s! Defaulting to creating a generic Molecule.",
2178             m, atom));
2179         molecules.add(m);
2180         moleculeHashMap.put(key, m);
2181       } else if (isWater) {
2182         water.add(m);
2183         waterHashMap.put(key, m);
2184       } else if (isIon) {
2185         ions.add(m);
2186         ionHashMap.put(key, m);
2187       } else {
2188         molecules.add(m);
2189         moleculeHashMap.put(key, m);
2190       }
2191       return atom;
2192     } else {
2193       return null;
2194     }
2195   }
2196 
2197   private void recurseVRML(Node node) {
2198     if (node instanceof Shape3D) {
2199       Shape3D s3d = (Shape3D) node;
2200       PickTool.setCapabilities(s3d, PickTool.INTERSECT_COORD);
2201     } else if (node instanceof SharedGroup) {
2202       SharedGroup sg = (SharedGroup) node;
2203       for (Iterator<Node> e = sg.getAllChildren(); e.hasNext(); ) {
2204         recurseVRML(e.next());
2205       }
2206     } else if (node instanceof BranchGroup) {
2207       BranchGroup bg = (BranchGroup) node;
2208       for (Iterator<Node> e = bg.getAllChildren(); e.hasNext(); ) {
2209         recurseVRML(e.next());
2210       }
2211     } else if (node instanceof TransformGroup) {
2212       TransformGroup vrmlTG1 = (TransformGroup) node;
2213       for (Iterator<Node> e = vrmlTG1.getAllChildren(); e.hasNext(); ) {
2214         node = e.next();
2215         recurseVRML(node);
2216       }
2217     } else if (node instanceof Link) {
2218       Link link = (Link) node;
2219       recurseVRML(link.getSharedGroup());
2220     } else if (node instanceof Group) {
2221       Group group = (Group) node;
2222       for (Iterator<Node> e = group.getAllChildren(); e.hasNext(); ) {
2223         recurseVRML(e.next());
2224       }
2225     }
2226   }
2227 
2228   /**
2229    * {@inheritDoc}
2230    */
2231   @Override
2232   protected void removeLeaves() {
2233     super.removeLeaves();
2234     MSNode macroNode = getAtomNode();
2235     if (macroNode != null) {
2236       if (macroNode.getChildCount() > 0) {
2237         getAtomNode().setName("Macromolecules " + "(" + macroNode.getChildCount() + ")");
2238       } else if (macroNode.getParent() == this) {
2239         removeChild(macroNode);
2240       }
2241     }
2242 
2243     if (molecules.getChildCount() == 0) {
2244       removeChild(molecules);
2245     } else {
2246       molecules.setName("Hetero Molecules " + "(" + molecules.getChildCount() + ")");
2247     }
2248 
2249     if (ions.getChildCount() == 0) {
2250       removeChild(ions);
2251     } else {
2252       ions.setName("Ions " + "(" + ions.getChildCount() + ")");
2253     }
2254 
2255     if (water.getChildCount() == 0) {
2256       removeChild(water);
2257     } else {
2258       water.setName("Water " + "(" + water.getChildCount() + ")");
2259     }
2260   }
2261 
2262   private Shape3D renderWire() {
2263     List<Bond> bonds = getBondList();
2264     int numbonds = bonds.size();
2265     if (numbonds < 1) {
2266       return null;
2267     }
2268 
2269     Vector3d bondmidpoint = new Vector3d();
2270     double[] mid = {0, 0, 0};
2271     Vector3d v1 = new Vector3d();
2272     Vector3d v2 = new Vector3d();
2273     float[] a1 = {0, 0, 0};
2274     float[] a2 = {0, 0, 0};
2275     float[] col = new float[4];
2276 
2277     Atom atom1, atom2;
2278     LineArray la =
2279         new LineArray(
2280             4 * numbonds,
2281             GeometryArray.COORDINATES | GeometryArray.COLOR_4 | GeometryArray.NORMALS);
2282     la.setCapability(LineArray.ALLOW_COORDINATE_WRITE);
2283     la.setCapability(LineArray.ALLOW_COORDINATE_READ);
2284     la.setCapability(LineArray.ALLOW_COLOR_WRITE);
2285     la.setCapability(LineArray.ALLOW_COUNT_READ);
2286     la.setCapability(LineArray.ALLOW_INTERSECT);
2287     la.setCapability(LineArray.ALLOW_FORMAT_READ);
2288     atomLookUp = new Atom[4 * numbonds];
2289     int i = 0;
2290     col[3] = 0.9f;
2291     for (Bond bond : bonds) {
2292       bond.setWire(la, i);
2293       atom1 = bond.getAtom(0);
2294       atom2 = bond.getAtom(1);
2295       atom1.getV3D(v1);
2296       atom2.getV3D(v2);
2297       a1[0] = (float) v1.x;
2298       a1[1] = (float) v1.y;
2299       a1[2] = (float) v1.z;
2300       a2[0] = (float) v2.x;
2301       a2[1] = (float) v2.y;
2302       a2[2] = (float) v2.z;
2303       // Find the bond center
2304       bondmidpoint.add(v1, v2);
2305       bondmidpoint.scale(0.5d);
2306       bondmidpoint.get(mid);
2307 
2308       // Atom #1
2309       Atom.AtomColor.get(atom1.getAtomicNumber()).get(col);
2310       atomLookUp[i] = atom1;
2311       la.setCoordinate(i, a1);
2312       la.setColor(i, col);
2313       la.setNormal(i, a2);
2314       i++;
2315 
2316       atomLookUp[i] = atom1;
2317       la.setCoordinate(i, mid);
2318       la.setColor(i, col);
2319       la.setNormal(i, a2);
2320       i++;
2321 
2322       // Atom #2
2323       Atom.AtomColor.get(atom2.getAtomicNumber()).get(col);
2324       atomLookUp[i] = atom2;
2325       la.setCoordinate(i, a2);
2326       la.setColor(i, col);
2327       la.setNormal(i, a1);
2328       i++;
2329 
2330       atomLookUp[i] = atom2;
2331       la.setCoordinate(i, mid);
2332       la.setColor(i, col);
2333       la.setNormal(i, a1);
2334       i++;
2335     }
2336 
2337     ColoringAttributes cola =
2338         new ColoringAttributes(new Color3f(), ColoringAttributes.SHADE_GOURAUD);
2339     Appearance app = new Appearance();
2340     lineAttributes = new LineAttributes();
2341     lineAttributes.setLineWidth(RendererCache.bondwidth);
2342     lineAttributes.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
2343     lineAttributes.setLineAntialiasingEnable(true);
2344     app.setLineAttributes(lineAttributes);
2345     app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
2346     app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_WRITE);
2347     RenderingAttributes ra = new RenderingAttributes();
2348     ra.setAlphaTestValue(0.1f);
2349     ra.setAlphaTestFunction(RenderingAttributes.GREATER);
2350     ra.setDepthBufferEnable(true);
2351     ra.setDepthBufferWriteEnable(true);
2352     app.setRenderingAttributes(ra);
2353     app.setColoringAttributes(cola);
2354     Shape3D wireframe = new Shape3D(la, app);
2355     // PickTool.setCapabilities(wire, PickTool.INTERSECT_COORD);
2356     wireframe.setUserData(this);
2357     wireframe.setBounds(new BoundingSphere(new Point3d(0, 0, 0), 1000.0));
2358     try {
2359       wireframe.setBoundsAutoCompute(false);
2360     } catch (Exception e) {
2361       logger.warning("Unable to set boundsAutoCompute to false.\n" + e);
2362     }
2363 
2364     wireframe.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
2365     wireframe.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
2366     wireframe.setCapability(Shape3D.ALLOW_LOCAL_TO_VWORLD_READ);
2367     return wireframe;
2368   }
2369 
2370   /**
2371    * Return the residue given a polymer ID and residue ID.
2372    *
2373    * @param polymerID The polymer ID.
2374    * @param resID     The residue ID.
2375    * @return The requested Residue or null if it can't be found.
2376    */
2377   public Residue getResidue(int polymerID, int resID) {
2378     Polymer[] polymers = getChains();
2379     if (polymers == null || polymers.length <= polymerID) {
2380       return null;
2381     }
2382     Polymer polymer = polymers[polymerID];
2383     List<Residue> residues = polymer.getResidues();
2384     if (residues == null || residues.size() <= resID) {
2385       return null;
2386     }
2387     return residues.get(resID);
2388   }
2389 
2390   /**
2391    * Returns a list of all water molecules in the system, including both those properly under the
2392    * water node and those under the molecules node.
2393    *
2394    * @return All water in the system
2395    */
2396   private List<Molecule> getAllWaterInclMistyped() {
2397     Stream<Molecule> mistyped =
2398         getMolecules()
2399             .parallelStream()
2400             .map((MSNode m) -> (Molecule) m)
2401             .filter(
2402                 (Molecule m) -> {
2403                   List<Atom> atoms = m.getAtomList();
2404                   if (atoms.size() != 3) {
2405                     return false;
2406                   }
2407                   int nO = 0;
2408                   int nH = 0;
2409                   for (Atom atom : atoms) {
2410                     int el = atom.getAtomicNumber();
2411                     if (el == 1) {
2412                       ++nH;
2413                     } else if (nH == 8) {
2414                       ++nO;
2415                     }
2416                   }
2417                   return nO == 1 && nH == 2;
2418                 });
2419     return Stream.concat(mistyped, getWater().stream().map((MSNode m) -> (Molecule) m))
2420         .distinct()
2421         .collect(Collectors.toList());
2422   }
2423 
2424   /**
2425    * Renames water protons to H1 and H2.
2426    */
2427   void renameWaterProtons() {
2428     for (Molecule water : getAllWaterInclMistyped()) {
2429       Atom H1 = water.getAtomByName("H1", false);
2430       Atom H2 = water.getAtomByName("H2", false);
2431       if (H1 != null && H2 != null) {
2432         continue;
2433       }
2434       for (Atom a : water.getAtomList()) {
2435         if (a.getAtomicNumber() == 1) {
2436           if (H1 == null) {
2437             H1 = a;
2438             H1.setName("H1");
2439           } else if (H2 == null) {
2440             H2 = a;
2441             H2.setName("H2");
2442           }
2443         }
2444       }
2445     }
2446   }
2447 
2448   private void moveIntoUnitCell(MSNode[] groups) {
2449     if (groups != null && groups.length > 0) {
2450       moveIntoUnitCell(Arrays.asList(groups));
2451     }
2452   }
2453 
2454   /**
2455    * Move the center of each listed chemical entity into the primary unit cell.
2456    *
2457    * @param groups Move each MSNode into the primary unit cell.
2458    */
2459   private void moveIntoUnitCell(List<MSNode> groups) {
2460     Crystal cryst = getCrystal().getUnitCell();
2461     if (cryst.aperiodic()) {
2462       return;
2463     }
2464     for (MSNode group : groups) {
2465       double[] com = new double[3];
2466       double[] xyz = new double[3];
2467       double[] translate = new double[3];
2468       List<Atom> atoms = group.getAtomList();
2469       double totMass = 0;
2470       for (Atom atom : atoms) {
2471         double mass = atom.getMass();
2472         totMass += mass;
2473         xyz = atom.getXYZ(xyz);
2474         com[0] += mass * xyz[0];
2475         com[1] += mass * xyz[1];
2476         com[2] += mass * xyz[2];
2477       }
2478       com[0] /= totMass;
2479       com[1] /= totMass;
2480       com[2] /= totMass;
2481 
2482       // Move the COM to the primary unit cell
2483       cryst.toPrimaryCell(com, translate);
2484 
2485       // The translation vector is difference between the new location and the current COM.
2486       translate[0] -= com[0];
2487       translate[1] -= com[1];
2488       translate[2] -= com[2];
2489 
2490       for (Atom atom : atoms) {
2491         atom.move(translate);
2492       }
2493     }
2494   }
2495 
2496   public enum FractionalMode {
2497     OFF,
2498     MOLECULE,
2499     ATOM
2500   }
2501 }