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-2021.
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.parsers;
39  
40  import static ffx.potential.bonded.Bond.logNoBondType;
41  import static java.lang.Double.parseDouble;
42  import static java.lang.Integer.parseInt;
43  import static java.lang.String.format;
44  
45  import ffx.crystal.Crystal;
46  import ffx.crystal.SymOp;
47  import ffx.potential.MolecularAssembly;
48  import ffx.potential.Utilities.FileType;
49  import ffx.potential.bonded.Atom;
50  import ffx.potential.bonded.Bond;
51  import ffx.potential.parameters.AtomType;
52  import ffx.potential.parameters.BondType;
53  import ffx.potential.parameters.ForceField;
54  import java.io.BufferedReader;
55  import java.io.BufferedWriter;
56  import java.io.File;
57  import java.io.FileNotFoundException;
58  import java.io.FileReader;
59  import java.io.FileWriter;
60  import java.io.IOException;
61  import java.util.ArrayList;
62  import java.util.HashMap;
63  import java.util.List;
64  import java.util.OptionalDouble;
65  import java.util.logging.Level;
66  import java.util.logging.Logger;
67  import java.util.regex.Matcher;
68  import java.util.regex.Pattern;
69  import org.apache.commons.configuration2.CompositeConfiguration;
70  import org.jogamp.vecmath.Vector3d;
71  
72  /**
73   * The XYZFilter class parses TINKER Cartesian coordinate (*.XYZ) files.
74   *
75   * @author Michael J. Schnieders
76   * @since 1.0
77   */
78  public class XYZFilter extends SystemFilter {
79  
80    private static final Logger logger = Logger.getLogger(XYZFilter.class.getName());
81    private BufferedReader bufferedReader = null;
82    private int snapShot;
83    private String remarkLine;
84  
85    /**
86     * Constructor for XYZFilter.
87     *
88     * @param files a {@link java.util.List} object.
89     * @param system a {@link ffx.potential.MolecularAssembly} object.
90     * @param forceField a {@link ffx.potential.parameters.ForceField} object.
91     * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
92     *     object.
93     */
94    public XYZFilter(
95        List<File> files,
96        MolecularAssembly system,
97        ForceField forceField,
98        CompositeConfiguration properties) {
99      super(files, system, forceField, properties);
100     this.fileType = FileType.XYZ;
101   }
102 
103   /**
104    * Constructor for XYZFilter.
105    *
106    * @param file a {@link java.io.File} object.
107    * @param system a {@link ffx.potential.MolecularAssembly} object.
108    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
109    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
110    *     object.
111    */
112   public XYZFilter(
113       File file,
114       MolecularAssembly system,
115       ForceField forceField,
116       CompositeConfiguration properties) {
117     super(file, system, forceField, properties);
118     this.fileType = FileType.XYZ;
119   }
120 
121   /**
122    * readOnto
123    *
124    * @param newFile a {@link java.io.File} object.
125    * @param oldSystem a {@link ffx.potential.MolecularAssembly} object.
126    * @return a boolean.
127    */
128   public static boolean readOnto(File newFile, MolecularAssembly oldSystem) {
129     if (newFile == null || !newFile.exists() || oldSystem == null) {
130       return false;
131     }
132     try (BufferedReader br = new BufferedReader(new FileReader(newFile))) {
133       String data = br.readLine();
134       if (data == null) {
135         return false;
136       }
137       String[] tokens = data.trim().split(" +");
138       int num_atoms = parseInt(tokens[0]);
139       if (num_atoms != oldSystem.getAtomList().size()) {
140         return false;
141       }
142 
143       br.mark(10000);
144       data = br.readLine();
145       if (!readPBC(data, oldSystem)) {
146         br.reset();
147       }
148 
149       double[][] d = new double[num_atoms][3];
150       for (int i = 0; i < num_atoms; i++) {
151         if (!br.ready()) {
152           return false;
153         }
154         data = br.readLine();
155         if (data == null) {
156           logger.warning(format(" Check atom %d.", (i + 1)));
157           return false;
158         }
159         tokens = data.trim().split(" +");
160         if (tokens.length < 6) {
161           logger.warning(format(" Check atom %d.", (i + 1)));
162           return false;
163         }
164         d[i][0] = parseDouble(tokens[2]);
165         d[i][1] = parseDouble(tokens[3]);
166         d[i][2] = parseDouble(tokens[4]);
167       }
168       List<Atom> atoms = oldSystem.getAtomList();
169       for (Atom a : atoms) {
170         int index = a.getIndex() - 1;
171         a.setXYZ(d[index]);
172       }
173       oldSystem.center();
174       oldSystem.setFile(newFile);
175       return true;
176     } catch (Exception e) {
177       return false;
178     }
179   }
180 
181   private static boolean firstTokenIsInteger(String data) {
182     if (data == null) {
183       return false;
184     }
185 
186     // Check for a blank line.
187     data = data.trim();
188     if (data.equals("")) {
189       return false;
190     }
191 
192     // Check if the first token in an integer.
193     try {
194       String[] tokens = data.split(" +");
195       parseInt(tokens[0]);
196       return true;
197     } catch (NumberFormatException e) {
198       return false;
199     }
200   }
201 
202   /**
203    * Attempt to parse the String as unit cell parameters.
204    *
205    * @param data The String to parse.
206    * @return false if the first token in the String is an integer and true otherwise.
207    */
208   private static boolean readPBC(String data, MolecularAssembly activeMolecularAssembly) {
209     if (firstTokenIsInteger(data)) {
210       return false;
211     }
212 
213     String[] tokens = data.trim().split(" +");
214     if (tokens.length == 6) {
215       CompositeConfiguration config = activeMolecularAssembly.getProperties();
216       double a = parseDouble(tokens[0]);
217       double b = parseDouble(tokens[1]);
218       double c = parseDouble(tokens[2]);
219       double alpha = parseDouble(tokens[3]);
220       double beta = parseDouble(tokens[4]);
221       double gamma = parseDouble(tokens[5]);
222       config.setProperty("a-axis", a);
223       config.setProperty("b-axis", b);
224       config.setProperty("c-axis", c);
225       config.setProperty("alpha", alpha);
226       config.setProperty("beta", beta);
227       config.setProperty("gamma", gamma);
228 
229       Crystal crystal = activeMolecularAssembly.getCrystal();
230       if (crystal != null) {
231         crystal.changeUnitCellParameters(a, b, c, alpha, beta, gamma);
232       }
233     }
234     return true;
235   }
236 
237   /** {@inheritDoc} */
238   @Override
239   public void closeReader() {
240     if (bufferedReader != null) {
241       try {
242         bufferedReader.close();
243       } catch (IOException ex) {
244         logger.warning(format(" Exception in closing XYZ filter: %s", ex));
245       }
246     }
247   }
248 
249   @Override
250   public int countNumModels() {
251     File xyzFile = activeMolecularAssembly.getFile();
252     int nAtoms = activeMolecularAssembly.getAtomArray().length;
253     Pattern crystInfoPattern =
254         Pattern.compile(
255             "^ *(?:[0-9]+\\.[0-9]+ +){3}(?:-?[0-9]+\\.[0-9]+ +){2}(?:-?[0-9]+\\.[0-9]+) *$");
256 
257     try (BufferedReader br = new BufferedReader(new FileReader(xyzFile))) {
258       String line = br.readLine();
259       int nSnaps = 0;
260       // For each header line, will read either X or X+1 lines, where X is the number of atoms.
261       while (line != null) {
262         assert parseInt(line.trim().split("\\s+")[0]) == nAtoms;
263         // Read either the crystal information *or* the first line of the snapshot.
264         line = br.readLine();
265         Matcher m = crystInfoPattern.matcher(line);
266         if (m.matches()) {
267           // If this is crystal information, move onto the first line of the snapshot.
268           br.readLine();
269         }
270         // Read lines 2-X of the XYZ.
271         for (int i = 1; i < nAtoms; i++) {
272           br.readLine();
273         }
274 
275         ++nSnaps;
276         line = br.readLine();
277       }
278       return nSnaps;
279     } catch (Exception ex) {
280       logger.log(
281           Level.WARNING, String.format(" Exception reading trajectory file %s: %s", xyzFile, ex));
282       return 1;
283     }
284   }
285 
286   /** {@inheritDoc} */
287   @Override
288   public OptionalDouble getLastReadLambda() {
289     String[] toks = remarkLine.split("\\s+");
290     int nToks = toks.length;
291     for (int i = 0; i < (nToks - 1); i++) {
292       if (toks[i].equals("Lambda:")) {
293         return OptionalDouble.of(Double.parseDouble(toks[i + 1]));
294       }
295     }
296     return OptionalDouble.empty();
297   }
298 
299   @Override
300   public String[] getRemarkLines() {
301     return new String[] {remarkLine};
302   }
303 
304   @Override
305   public int getSnapshot() {
306     return snapShot;
307   }
308 
309   /**
310    * {@inheritDoc}
311    *
312    * <p>Parse the XYZ File
313    */
314   @Override
315   public boolean readFile() {
316     File xyzFile = activeMolecularAssembly.getFile();
317 
318     if (forceField == null) {
319       logger.warning(format(" No force field is associated with %s.", xyzFile.toString()));
320       return false;
321     }
322     try (BufferedReader br = new BufferedReader(new FileReader(xyzFile))) {
323       String data = br.readLine();
324       // Read blank lines at the top of the file
325       while (data != null && data.trim().equals("")) {
326         data = br.readLine();
327       }
328       if (data == null) {
329         return false;
330       }
331       String[] tokens = data.trim().split(" +", 2);
332       int numberOfAtoms = parseInt(tokens[0]);
333       if (numberOfAtoms < 1) {
334         return false;
335       }
336       if (tokens.length == 2) {
337         getActiveMolecularSystem().setName(tokens[1]);
338       }
339       logger.info(format(" Opening %s with %d atoms\n", xyzFile.getName(), numberOfAtoms));
340       remarkLine = data.trim();
341 
342       // The header line is reasonable. Check for periodic box dimensions.
343       br.mark(10000);
344       data = br.readLine();
345       if (!readPBC(data, activeMolecularAssembly)) {
346         br.reset();
347       }
348 
349       // Prepare to parse atom lines.
350       HashMap<Integer, Integer> labelHash = new HashMap<>();
351       int[] label = new int[numberOfAtoms];
352       int[][] bonds = new int[numberOfAtoms][8];
353       double[][] d = new double[numberOfAtoms][3];
354       boolean renumber = false;
355       atomList = new ArrayList<>();
356       // Loop over the expected number of atoms.
357       for (int i = 0; i < numberOfAtoms; i++) {
358         if (!br.ready()) {
359           return false;
360         }
361         data = br.readLine();
362         if (data == null) {
363           logger.warning(
364               format(
365                   " Check atom %d in %s.", (i + 1), activeMolecularAssembly.getFile().getName()));
366           return false;
367         }
368         tokens = data.trim().split(" +");
369         if (tokens.length < 6) {
370           logger.warning(
371               format(
372                   " Check atom %d in %s.", (i + 1), activeMolecularAssembly.getFile().getName()));
373           return false;
374         }
375         // Valid number of tokens, so try to parse this line.
376         label[i] = parseInt(tokens[0]);
377         // Check for valid atom numbering, or flag for re-numbering.
378         if (label[i] != i + 1) {
379           renumber = true;
380         }
381         String atomName = tokens[1];
382         d[i][0] = parseDouble(tokens[2]);
383         d[i][1] = parseDouble(tokens[3]);
384         d[i][2] = parseDouble(tokens[4]);
385         int type = parseInt(tokens[5]);
386         AtomType atomType = forceField.getAtomType(Integer.toString(type));
387         if (atomType == null) {
388           StringBuilder message = new StringBuilder("Check atom type ");
389           message.append(type).append(" for Atom ").append(i + 1);
390           message.append(" in ").append(activeMolecularAssembly.getFile().getName());
391           logger.warning(message.toString());
392           return false;
393         }
394         Atom a = new Atom(i + 1, atomName, atomType, d[i]);
395         atomList.add(a);
396         // Bond Data
397         int numberOfBonds = tokens.length - 6;
398         for (int b = 0; b < 8; b++) {
399           if (b < numberOfBonds) {
400             int bond = parseInt(tokens[6 + b]);
401             bonds[i][b] = bond;
402           } else {
403             bonds[i][b] = 0;
404           }
405         }
406       }
407       // Check if this is an archive.
408       if (br.ready()) {
409         // Read past blank lines between archive files
410         data = br.readLine().trim();
411         while (data.equals("") && br.ready()) {
412           data = br.readLine().trim();
413         }
414         tokens = data.split(" +", 2);
415         if (tokens.length > 0) {
416           try {
417             int archiveNumberOfAtoms = parseInt(tokens[0]);
418             if (archiveNumberOfAtoms == numberOfAtoms) {
419               setType(FileType.ARC);
420             }
421           } catch (NumberFormatException e) {
422             //
423           }
424         }
425       }
426       // Try to renumber
427       if (renumber) {
428         for (int i = 0; i < numberOfAtoms; i++) {
429           if (labelHash.containsKey(label[i])) {
430             logger.warning(format(" Two atoms have the same index: %d.", label[i]));
431             return false;
432           }
433           labelHash.put(label[i], i + 1);
434         }
435         for (int i = 0; i < numberOfAtoms; i++) {
436           int j = -1;
437           while (j < 3 && bonds[i][++j] > 0) {
438             bonds[i][j] = labelHash.get(bonds[i][j]);
439           }
440         }
441       }
442       bondList = new ArrayList<>();
443       int[] c = new int[2];
444       for (int a1 = 1; a1 <= numberOfAtoms; a1++) {
445         int j = -1;
446         while (j < 7 && bonds[a1 - 1][++j] > 0) {
447           int a2 = bonds[a1 - 1][j];
448           if (a1 < a2) {
449             if (a2 > numberOfAtoms) {
450               logger.warning(
451                   format(
452                       " Check the bond between %d and %d in %s.",
453                       a1, a2, activeMolecularAssembly.getFile().getName()));
454               return false;
455             }
456             // Check for bidirectional connection
457             boolean bidirectional = false;
458             int k = -1;
459             while (k < 7 && bonds[a2 - 1][++k] > 0) {
460               int a3 = bonds[a2 - 1][k];
461               if (a3 == a1) {
462                 bidirectional = true;
463                 break;
464               }
465             }
466             if (!bidirectional) {
467               logger.warning(
468                   format(
469                       " Check the bond between %d and %d in %s.",
470                       a1, a2, activeMolecularAssembly.getFile().getName()));
471               return false;
472             }
473             Atom atom1 = atomList.get(a1 - 1);
474             Atom atom2 = atomList.get(a2 - 1);
475             if (atom1 == null || atom2 == null) {
476               logger.warning(
477                   format(
478                       " Check the bond between %d and %d in %s.",
479                       a1, a2, activeMolecularAssembly.getFile().getName()));
480               return false;
481             }
482             Bond bond = new Bond(atom1, atom2);
483             BondType bondType = forceField.getBondType(atom1.getAtomType(), atom2.getAtomType());
484             if (bondType == null) {
485               logNoBondType(atom1, atom2, forceField);
486             } else {
487               bond.setBondType(bondType);
488             }
489             bondList.add(bond);
490           }
491         }
492       }
493       return true;
494     } catch (IOException e) {
495       logger.severe(e.toString());
496     }
497     return false;
498   }
499 
500   /** {@inheritDoc} */
501   @Override
502   public boolean readNext() {
503     return readNext(false);
504   }
505 
506   /**
507    * {@inheritDoc}
508    *
509    * <p>Reads the next snap-shot of an archive into the activeMolecularAssembly. After calling this
510    * function, a BufferedReader will remain open until the <code>close</code> method is called.
511    */
512   @Override
513   public boolean readNext(boolean resetPosition) {
514     return readNext(resetPosition, true);
515   }
516 
517   /**
518    * {@inheritDoc}
519    *
520    * <p>Reads the next snap-shot of an archive into the activeMolecularAssembly. After calling this
521    * function, a BufferedReader will remain open until the <code>close</code> method is called.
522    */
523   @Override
524   public boolean readNext(boolean resetPosition, boolean print) {
525     return readNext(resetPosition, print, true);
526   }
527 
528   /**
529    * Reads the next snap-shot of an archive into the activeMolecularAssembly. After calling this
530    * function, a BufferedReader will remain open until the <code>close</code> method is called.
531    */
532   public boolean readNext(boolean resetPosition, boolean print, boolean parse) {
533     try {
534       String data;
535       Atom[] atoms = activeMolecularAssembly.getAtomArray();
536       int nSystem = atoms.length;
537 
538       if (bufferedReader == null && !resetPosition) {
539         bufferedReader = new BufferedReader(new FileReader(currentFile));
540         // Read past the first N + 1 lines that begin with an integer.
541         for (int i = 0; i < nSystem + 1; i++) {
542           data = bufferedReader.readLine();
543           while (!firstTokenIsInteger(data)) {
544             data = bufferedReader.readLine();
545           }
546         }
547         snapShot = 1;
548       } else if (resetPosition) {
549         // Reset the reader to the beginning of the file. Do not skip reading the first entry if resetPostion is true.
550         bufferedReader = new BufferedReader(new FileReader(currentFile));
551         snapShot = 0;
552       }
553 
554       snapShot++;
555 
556       data = bufferedReader.readLine();
557       // Read past blank lines
558       while (data != null && data.trim().equals("")) {
559         data = bufferedReader.readLine();
560       }
561       if (data == null) {
562         return false;
563       }
564 
565       if (print) {
566         if (parse) {
567           logger.info(format("\n Attempting to read snapshot %d.", snapShot));
568         } else {
569           logger.info(format("\n Skipping snapshot %d.", snapShot));
570         }
571       }
572       if (parse) {
573         try {
574           String[] tokens = data.trim().split(" +");
575           int nArchive = parseInt(tokens[0]);
576           if (nArchive != nSystem) {
577             String message =
578                     format("Number of atoms mismatch (Archive: %d, System: %d).", nArchive, nSystem);
579             if (dieOnMissingAtom) {
580               logger.severe(message);
581             }
582             logger.warning(message);
583             return false;
584           }
585           if(tokens.length > 1) {
586             activeMolecularAssembly.setName(tokens[1]);
587           }
588         } catch (NumberFormatException e) {
589           logger.warning(e.toString());
590           return false;
591         }
592 
593         remarkLine = data;
594 
595         // The header line is reasonable. Check for periodic box dimensions.
596         bufferedReader.mark(10000);
597         data = bufferedReader.readLine();
598         if (!readPBC(data, activeMolecularAssembly)) {
599           bufferedReader.reset();
600         }
601 
602         for (int i = 0; i < nSystem; i++) {
603           data = bufferedReader.readLine();
604           // Read past blank lines
605           while (data != null && data.trim().equals("")) {
606             data = bufferedReader.readLine();
607           }
608           String[] tokens = data.trim().split(" +");
609           if (tokens.length < 6) {
610             String message = format("Check atom %d in %s.", (i + 1), currentFile.getName());
611             logger.warning(message);
612             return false;
613           }
614           double x = parseDouble(tokens[2]);
615           double y = parseDouble(tokens[3]);
616           double z = parseDouble(tokens[4]);
617           int xyzIndex = atoms[i].getIndex();
618           if (xyzIndex != i + 1) {
619             String message =
620                     format(
621                             "Archive atom index %d being read onto system atom index %d.", i + 1, xyzIndex);
622             logger.warning(message);
623           }
624           atoms[i].moveTo(x, y, z);
625         }
626       }else{
627         // Header line skipped. Check for crstal information.
628         bufferedReader.mark(10000);
629         data = bufferedReader.readLine();
630         if (!readPBC(data, activeMolecularAssembly)) {
631           bufferedReader.reset();
632         }
633         // Read ahead n lines and skip blanks.
634         for (int i = 0; i < nSystem; i++) {
635           data = bufferedReader.readLine();
636           // Read past blank lines
637           while (data != null && data.trim().equals("")) {
638             data = bufferedReader.readLine();
639           }
640         }
641       }
642       return true;
643     } catch (FileNotFoundException e) {
644       String message = format("Exception opening file %s.", currentFile);
645       logger.log(Level.WARNING, message, e);
646     } catch (IOException e) {
647       String message = format("Exception reading from file %s.", currentFile);
648       logger.log(Level.WARNING, message, e);
649     }
650     return false;
651   }
652 
653   /** {@inheritDoc} */
654   @Override
655   public boolean writeFile(File saveFile, boolean append, String[] extraLines) {
656     if (saveFile == null) {
657       return false;
658     }
659 
660     File newFile = saveFile;
661     if (!append) {
662       newFile = version(saveFile);
663     }
664     activeMolecularAssembly.setFile(newFile);
665     if (activeMolecularAssembly.getName() == null) {
666       activeMolecularAssembly.setName(newFile.getName());
667     }
668 
669     try (FileWriter fw = new FileWriter(newFile, append && newFile.exists());
670         BufferedWriter bw = new BufferedWriter(fw)) {
671       // XYZ File First Line
672       int numberOfAtoms = activeMolecularAssembly.getAtomList().size();
673       StringBuilder sb =
674           new StringBuilder(format("%7d  %s", numberOfAtoms, activeMolecularAssembly.getName()));
675       if (extraLines != null) {
676         for (String line : extraLines) {
677           line = line.replaceAll("\n", " ");
678           sb.append(" ").append(line);
679         }
680       }
681       String output = sb.append("\n").toString();
682       bw.write(output);
683 
684       Crystal crystal = activeMolecularAssembly.getCrystal();
685       if (crystal!=null && !crystal.aperiodic()) {
686         Crystal uc = crystal.getUnitCell();
687         String params =
688             format("%14.8f%14.8f%14.8f%14.8f%14.8f%14.8f\n",
689                 uc.a, uc.b, uc.c, uc.alpha, uc.beta, uc.gamma);
690         bw.write(params);
691       }
692 
693       Atom a2;
694       StringBuilder line;
695       StringBuilder[] lines = new StringBuilder[numberOfAtoms];
696       // XYZ File Atom Lines
697       List<Atom> atoms = activeMolecularAssembly.getAtomList();
698       Vector3d offset = activeMolecularAssembly.getOffset();
699       for (Atom a : atoms) {
700         if (vdwH) {
701           line =
702               new StringBuilder(
703                   format(
704                       "%7d %3s%14.8f%14.8f%14.8f%6d",
705                       a.getIndex(),
706                       a.getAtomType().name,
707                       a.getRedX() - offset.x,
708                       a.getRedY() - offset.y,
709                       a.getRedZ() - offset.z,
710                       a.getType()));
711         } else {
712           line =
713               new StringBuilder(
714                   format(
715                       "%7d %3s%14.8f%14.8f%14.8f%6d",
716                       a.getIndex(),
717                       a.getAtomType().name,
718                       a.getX() - offset.x,
719                       a.getY() - offset.y,
720                       a.getZ() - offset.z,
721                       a.getType()));
722         }
723         for (Bond b : a.getBonds()) {
724           a2 = b.get1_2(a);
725           line.append(format("%8d", a2.getIndex()));
726         }
727         lines[a.getIndex() - 1] = line.append("\n");
728       }
729       try {
730         for (int i = 0; i < numberOfAtoms; i++) {
731           bw.write(lines[i].toString());
732         }
733       } catch (IOException e) {
734         String message =
735             format(
736                 " There was an unexpected error writing to %s.",
737                 getActiveMolecularSystem().toString());
738         logger.log(Level.WARNING, message, e);
739         return false;
740       }
741     } catch (IOException e) {
742       String message =
743           format(
744               " There was an unexpected error writing to %s.",
745               getActiveMolecularSystem().toString());
746       logger.log(Level.WARNING, message, e);
747       return false;
748     }
749     return true;
750   }
751 
752   /**
753    * writeFileAsP1
754    *
755    * @param saveFile a {@link java.io.File} object.
756    * @param append a boolean.
757    * @param crystal a {@link ffx.crystal.Crystal} object.
758    * @return a boolean.
759    */
760   public boolean writeFileAsP1(File saveFile, boolean append, Crystal crystal) {
761     return writeFileAsP1(saveFile, append, crystal, null);
762   }
763 
764   /**
765    * writeFileAsP1
766    *
767    * @param saveFile a {@link java.io.File} object.
768    * @param append a boolean.
769    * @param crystal a {@link ffx.crystal.Crystal} object.
770    * @param extraLines Additional lines to print in the header.
771    * @return a boolean.
772    */
773   public boolean writeFileAsP1(
774       File saveFile, boolean append, Crystal crystal, String[] extraLines) {
775     if (saveFile == null) {
776       return false;
777     }
778 
779     File newFile = saveFile;
780     if (!append) {
781       newFile = version(saveFile);
782     }
783     activeMolecularAssembly.setFile(newFile);
784     activeMolecularAssembly.setName(newFile.getName());
785 
786     try (FileWriter fw = new FileWriter(newFile, append && newFile.exists());
787         BufferedWriter bw = new BufferedWriter(fw)) {
788       int nSymm = crystal.spaceGroup.symOps.size();
789       // XYZ File First Line
790       int numberOfAtoms = activeMolecularAssembly.getAtomList().size() * nSymm;
791       StringBuilder sb =
792           new StringBuilder(format("%7d  %s", numberOfAtoms, activeMolecularAssembly.toString()));
793       if (extraLines != null) {
794         for (String line : extraLines) {
795           line = line.replaceAll("\n", " ");
796           sb.append(" ").append(line);
797         }
798       }
799       String output = sb.append("\n").toString();
800       bw.write(output);
801 
802       if (!crystal.aperiodic()) {
803         Crystal uc = crystal.getUnitCell();
804         String params = format("%14.8f%14.8f%14.8f%14.8f%14.8f%14.8f\n",
805             uc.a, uc.b, uc.c, uc.alpha, uc.beta, uc.gamma);
806         bw.write(params);
807       }
808 
809       Atom a2;
810       StringBuilder line;
811       StringBuilder[] lines = new StringBuilder[numberOfAtoms];
812       // XYZ File Atom Lines
813       Atom[] atoms = activeMolecularAssembly.getAtomArray();
814       double[] xyz = new double[3];
815       for (int iSym = 0; iSym < nSymm; iSym++) {
816         SymOp symOp = crystal.spaceGroup.getSymOp(iSym);
817         int indexOffset = iSym * atoms.length;
818         for (Atom a : atoms) {
819           int index = a.getIndex() + indexOffset;
820           String id = a.getAtomType().name;
821           if (vdwH) {
822             a.getRedXYZ(xyz);
823           } else {
824             xyz[0] = a.getX();
825             xyz[1] = a.getY();
826             xyz[2] = a.getZ();
827           }
828           crystal.applySymOp(xyz, xyz, symOp);
829           int type = a.getType();
830           line =
831               new StringBuilder(
832                   format("%7d %3s%14.8f%14.8f%14.8f%6d", index, id, xyz[0], xyz[1], xyz[2], type));
833           for (Bond b : a.getBonds()) {
834             a2 = b.get1_2(a);
835             line.append(format("%8d", a2.getIndex() + indexOffset));
836           }
837           lines[index - 1] = line.append("\n");
838         }
839       }
840       try {
841         for (int i = 0; i < numberOfAtoms; i++) {
842           bw.write(lines[i].toString());
843         }
844       } catch (IOException e) {
845         String message =
846             format(
847                 " There was an unexpected error writing to %s.",
848                 getActiveMolecularSystem().toString());
849         logger.log(Level.WARNING, message, e);
850         return false;
851       }
852     } catch (IOException e) {
853       String message =
854           format(
855               " There was an unexpected error writing to %s.",
856               getActiveMolecularSystem().toString());
857       logger.log(Level.WARNING, message, e);
858       return false;
859     }
860     return true;
861   }
862 }