View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential.parsers;
39  
40  import ffx.potential.MolecularAssembly;
41  import ffx.potential.Utilities.FileType;
42  import ffx.potential.bonded.Atom;
43  import ffx.potential.bonded.Bond;
44  import ffx.potential.nonbonded.RestrainPosition;
45  import ffx.potential.parameters.ForceField;
46  import org.apache.commons.configuration2.CompositeConfiguration;
47  
48  import java.io.File;
49  import java.util.ArrayList;
50  import java.util.HashSet;
51  import java.util.List;
52  import java.util.OptionalDouble;
53  import java.util.Set;
54  import java.util.Vector;
55  import java.util.logging.Level;
56  import java.util.logging.Logger;
57  import java.util.regex.Pattern;
58  
59  import static ffx.utilities.StringUtils.parseAtomRange;
60  import static java.lang.String.format;
61  
62  /**
63   * The SystemFilter class is the base class for most Force Field X file parsers.
64   *
65   * @author Michael J. Schnieders
66   * @since 1.0
67   */
68  public abstract class SystemFilter {
69  
70    protected static final Pattern lambdaPattern = Pattern.compile("Lambda: +([01]\\.\\d+)");
71    private static final Logger logger = Logger.getLogger(SystemFilter.class.getName());
72    private static Versioning vers = Versioning.TINKER;
73    private static int absoluteCounter = 0;
74    /**
75     * Constant <code>dieOnMissingAtom=</code>
76     */
77    protected final boolean dieOnMissingAtom;
78    /**
79     * Standardize atom names to PDB standard by default.
80     */
81    protected final boolean standardizeAtomNames;
82    /**
83     * True if atoms are to be printed to their van der Waals centers instead of nuclear centers
84     * (applies primarily to hydrogen).
85     */
86    protected final boolean vdwH;
87    /**
88     * The atomList is filled by filters that extend SystemFilter.
89     */
90    protected List<Atom> atomList = null;
91    /**
92     * The bondList may be filled by the filters that extend SystemFilter.
93     */
94    protected List<Bond> bondList = null;
95    /**
96     * All MolecularAssembly instances defined. More than one MolecularAssembly should be defined for
97     * PDB entries with alternate locations.
98     */
99    protected List<MolecularAssembly> systems = new Vector<>();
100   /**
101    * Append multiple files into one MolecularAssembly.
102    */
103   protected List<File> files;
104   /**
105    * The file format being handled.
106    */
107   protected FileType fileType = FileType.UNK;
108   /**
109    * Properties associated with this file.
110    */
111   protected CompositeConfiguration properties;
112   /**
113    * The molecular mechanics force field being used.
114    */
115   protected ForceField forceField;
116   /**
117    * True after the file has been read successfully.
118    */
119   protected boolean fileRead = false;
120   /**
121    * The current MolecularAssembly being populated. Note that more than one MolecularAssembly should
122    * be defined for PDB files with alternate locations.
123    */
124   MolecularAssembly activeMolecularAssembly;
125   /**
126    * File currently being read.
127    */
128   File currentFile = null;
129 
130   /**
131    * Initializations common to the all the constructors.
132    *
133    * @param forceField The force field.
134    * @param properties The CompositeConfiguration properties.
135    */
136   private SystemFilter(ForceField forceField, CompositeConfiguration properties) {
137     this.forceField = forceField;
138     this.properties = properties;
139     if (properties != null) {
140       vdwH = properties.getBoolean("vdwHydrogens", false);
141       dieOnMissingAtom = properties.getBoolean("trajectory-dieOnMissing", false);
142       standardizeAtomNames = properties.getBoolean("standardizeAtomNames", true);
143     } else {
144       vdwH = false;
145       dieOnMissingAtom = false;
146       standardizeAtomNames = true;
147     }
148   }
149 
150   /**
151    * Constructor for SystemFilter.
152    *
153    * @param files             a {@link java.util.List} object.
154    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
155    * @param forceField        a {@link ffx.potential.parameters.ForceField} object.
156    * @param properties        a {@link org.apache.commons.configuration2.CompositeConfiguration}
157    *                          object.
158    */
159   public SystemFilter(List<File> files, MolecularAssembly molecularAssembly, ForceField forceField,
160                       CompositeConfiguration properties) {
161     this(forceField, properties);
162     this.files = files;
163     if (files != null) {
164       this.currentFile = files.get(0);
165     }
166     this.activeMolecularAssembly = molecularAssembly;
167   }
168 
169   /**
170    * Constructor for SystemFilter.
171    *
172    * @param file              a {@link java.io.File} object.
173    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
174    * @param forceField        a {@link ffx.potential.parameters.ForceField} object.
175    * @param properties        a {@link org.apache.commons.configuration2.CompositeConfiguration}
176    *                          object.
177    */
178   public SystemFilter(File file, MolecularAssembly molecularAssembly, ForceField forceField,
179                       CompositeConfiguration properties) {
180     this(forceField, properties);
181     files = new ArrayList<>();
182     if (file != null) {
183       files.add(file);
184     }
185     this.currentFile = file;
186     this.activeMolecularAssembly = molecularAssembly;
187   }
188 
189   /**
190    * Constructor for SystemFilter.
191    *
192    * @param file                a {@link java.io.File} object.
193    * @param molecularAssemblies a {@link java.util.List} object.
194    * @param forceField          a {@link ffx.potential.parameters.ForceField} object.
195    * @param properties          a {@link org.apache.commons.configuration2.CompositeConfiguration}
196    *                            object.
197    */
198   public SystemFilter(File file, List<MolecularAssembly> molecularAssemblies, ForceField forceField,
199                       CompositeConfiguration properties) {
200     this(forceField, properties);
201     files = new ArrayList<>();
202     if (file != null) {
203       files.add(file);
204     }
205     this.currentFile = file;
206     this.systems = new ArrayList<>(molecularAssemblies);
207     this.activeMolecularAssembly = systems.get(0);
208   }
209 
210   /**
211    * previousVersion
212    *
213    * @param file a {@link java.io.File} object.
214    * @return a {@link java.io.File} object.
215    */
216   public static File previousVersion(File file) {
217     if (file == null) {
218       return null;
219     }
220     String fileName = file.getAbsolutePath();
221     int dot = file.getAbsolutePath().lastIndexOf(".");
222     int under = file.getAbsolutePath().lastIndexOf("_");
223     File newFile = file;
224     if (under > dot) {
225       String name = fileName.substring(0, under);
226       newFile = new File(name);
227     }
228     File baseFile = newFile;
229     File previousFile = null;
230     int i = 1;
231     while (newFile.exists()) {
232       i = i + 1;
233       previousFile = newFile;
234       newFile = baseFile;
235       int thousand = i / 1000;
236       int hundred = (i - 1000 * thousand) / 100;
237       int tens = (i - 1000 * thousand - 100 * hundred) / 10;
238       int ones = i - 1000 * thousand - 100 * hundred - 10 * tens;
239       StringBuilder newFileString = new StringBuilder(baseFile.getAbsolutePath());
240       if (thousand != 0) {
241         newFileString.append('_').append(thousand).append(hundred).append(tens).append(ones);
242       } else if (hundred != 0) {
243         newFileString.append('_').append(hundred).append(tens).append(ones);
244       } else if (tens != 0) {
245         newFileString.append('_').append(tens).append(ones);
246       } else {
247         newFileString.append('_').append(ones);
248       }
249       newFile = new File(newFileString.toString());
250     }
251     return previousFile;
252   }
253 
254   /**
255    * Negative: prefix a version number; Positive: postfix; Zero: TINKER-style.
256    *
257    * @param vers a {@link ffx.potential.parsers.SystemFilter.Versioning} object.
258    */
259   public static void setVersioning(Versioning vers) {
260     SystemFilter.vers = vers;
261   }
262 
263   /**
264    * Use setVersioning() to choose between prefix/postfix.
265    *
266    * @param file a {@link java.io.File} object.
267    * @return a {@link java.io.File} object.
268    */
269   public static File version(File file) {
270     if (vers == Versioning.TINKER) {
271       return versionTinker(file);
272     } else {
273       if (vers == Versioning.PREFIX_ABSOLUTE || vers == Versioning.POSTFIX_ABSOLUTE) {
274         return versionAbsolute(file, (vers == Versioning.PREFIX_ABSOLUTE));
275       } else {
276         return version(file, (vers == Versioning.PREFIX));
277       }
278     }
279   }
280 
281   private static File version(File file, boolean prefix) {
282     if (file == null || !(file.exists())) {
283       return file;
284     }
285     String fn = file.getAbsolutePath();
286     int dot = fn.lastIndexOf(".");
287     int under = fn.lastIndexOf("_");
288     if (dot < 0) {
289       fn += ".unk";
290       dot = fn.lastIndexOf(".");
291     }
292     if (under < 0) {
293       under = dot;
294     }
295     String name = (prefix) ? fn.substring(0, under) : fn.substring(0, dot);
296     String extension = (prefix) ? fn.substring(dot + 1) : fn.substring(dot + 1, under);
297     int number = 0;
298     String newFn = (prefix) ? format("%s_%d.%s", name, number, extension)
299         : format("%s.%s_%d", name, extension, number);
300     if (prefix && under < dot) {
301       try {
302         number = Integer.parseInt(fn.substring(under + 1, dot));
303       } catch (NumberFormatException ex) {
304         // Then we have something like "AKA_dyn.pdb"
305         name = fn.substring(0, dot);
306         number++;
307         newFn = format("%s_%d.%s", name, number, extension);
308       }
309     } else if (!prefix && under > dot) {
310       try {
311         number = Integer.parseInt(fn.substring(under + 1));
312         number++;
313       } catch (NumberFormatException ex) {
314         //
315       }
316     }
317     File newFile = new File(newFn);
318     while (newFile.exists()) {
319       ++number;
320       newFn = (prefix) ? format("%s_%d.%s", name, number, extension)
321           : format("%s.%s_%d", name, extension, number);
322       newFile = new File(newFn);
323     }
324     return newFile;
325   }
326 
327   private static synchronized File versionAbsolute(File file, boolean prefix) {
328     if (file == null || !(file.exists())) {
329       return file;
330     }
331     String fn = file.getAbsolutePath();
332     int dot = fn.lastIndexOf(".");
333     int under = fn.lastIndexOf("_");
334     if (dot < 0) {
335       fn += ".unk";
336       dot = fn.lastIndexOf(".");
337     }
338     if (under < 0) {
339       under = dot;
340     }
341     String name = (prefix) ? fn.substring(0, under) : fn.substring(0, dot);
342     String extension = (prefix) ? fn.substring(dot + 1) : fn.substring(dot + 1, under);
343     String newFn = (prefix) ? format("%s_%d.%s", name, absoluteCounter, extension)
344         : format("%s.%s_%d", name, extension, absoluteCounter);
345     File newFile = new File(newFn);
346     while (newFile.exists()) {
347       absoluteCounter++;
348       newFn = (prefix) ? format("%s_%d.%s", name, absoluteCounter, extension)
349           : format("%s.%s_%d", name, extension, absoluteCounter);
350       newFile = new File(newFn);
351     }
352     return newFile;
353   }
354 
355   /**
356    * This follows the TINKER file versioning scheme.
357    *
358    * @param file File to find a version for.
359    * @return File Versioned File.
360    */
361   private static File versionTinker(File file) {
362     if (file == null) {
363       return null;
364     }
365     if (!file.exists()) {
366       return file;
367     }
368     String fileName = file.getAbsolutePath();
369     int dot = file.getAbsolutePath().lastIndexOf(".");
370     int under = file.getAbsolutePath().lastIndexOf("_");
371     File newFile = file;
372     if (under > dot) {
373       String name = fileName.substring(0, under);
374       newFile = new File(name);
375     }
376     File oldFile = newFile;
377     int i = 1;
378     while (newFile.exists()) {
379       i = i + 1;
380       String newFileString = format("%s_%d", oldFile.getAbsolutePath(), i);
381       newFile = new File(newFileString);
382     }
383     return newFile;
384   }
385 
386   /**
387    * Converts a list of atom indices to an array of atoms.
388    *
389    * @param atomList List of atom indices.
390    * @param atoms    Array of atoms.
391    * @return Array of atoms.
392    */
393   public static Set<Atom> atomListToSet(List<Integer> atomList, Atom[] atoms) {
394     Set<Atom> atomSet = new HashSet<>();
395     for (int i = 0; i < atomList.size(); i++) {
396       atomSet.add(atoms[atomList.get(i)]);
397     }
398     return atomSet;
399   }
400 
401   /**
402    * Automatically sets atom-specific flags, particularly nouse and inactive, and apply harmonic
403    * restraints. Intended to be called at the end of readFile() implementations.
404    *
405    * <p>Supported syntax: "(\\d+)-(\\d+)"
406    */
407   public void applyAtomProperties() {
408     /*
409       What may be a more elegant implementation is to make readFile() a
410       public concrete, but skeletal method, and then have readFile() call a
411       protected abstract readFile method for each implementation.
412     */
413     Atom[] atomArray = activeMolecularAssembly.getAtomArray();
414     int nAtoms = atomArray.length;
415     String[] nouseKeys = properties.getStringArray("nouse");
416     for (String nouseKey : nouseKeys) {
417       String[] toks = nouseKey.split("\\s+");
418       for (String tok : toks) {
419         try {
420           List<Integer> nouseRange = parseAtomRange("nouse", tok, nAtoms);
421           for (int j : nouseRange) {
422             atomArray[j].setUse(false);
423           }
424         } catch (IllegalArgumentException ex) {
425           boolean atomFound = false;
426           for (Atom atom : atomArray) {
427             if (atom.getName().equalsIgnoreCase(tok)) {
428               atomFound = true;
429               atom.setUse(false);
430             }
431           }
432           if (atomFound) {
433             logger.info(format(" Setting atoms with name %s to not be used", tok));
434           } else {
435             logger.log(Level.INFO, ex.getLocalizedMessage());
436           }
437         }
438       }
439     }
440 
441     if (properties.containsKey("active")) {
442       for (Atom atom : atomArray) {
443         atom.setActive(false);
444       }
445       String[] activeKeys = properties.getStringArray("active");
446       for (String inactiveKey : activeKeys) {
447         try {
448           List<Integer> inactiveRange = parseAtomRange("inactive", inactiveKey, nAtoms);
449           for (int i : inactiveRange) {
450             atomArray[i].setActive(false);
451           }
452         } catch (IllegalArgumentException ex) {
453           logger.log(Level.INFO, ex.getLocalizedMessage());
454         }
455       }
456     } else if (properties.containsKey("inactive")) {
457       for (Atom atom : atomArray) {
458         atom.setActive(true);
459       }
460       String[] inactiveKeys = properties.getStringArray("inactive");
461       for (String inactiveKey : inactiveKeys) {
462         try {
463           List<Integer> inactiveRange = parseAtomRange("inactive", inactiveKey, nAtoms);
464           for (int i : inactiveRange) {
465             atomArray[i].setActive(false);
466           }
467         } catch (IllegalArgumentException ex) {
468           logger.log(Level.INFO, ex.getLocalizedMessage());
469         }
470       }
471     }
472 
473     String[] noElStrings = properties.getStringArray("noElectro");
474     for (String noE : noElStrings) {
475       String[] toks = noE.split("\\s+");
476       for (String tok : toks) {
477         try {
478           List<Integer> noERange = parseAtomRange("noElectro", tok, nAtoms);
479           for (int i : noERange) {
480             atomArray[i].setElectrostatics(false);
481           }
482         } catch (IllegalArgumentException ex) {
483           boolean atomFound = false;
484           for (Atom atom : atomArray) {
485             if (atom.getName().equalsIgnoreCase(tok)) {
486               atomFound = true;
487               atom.setElectrostatics(false);
488             }
489           }
490           if (atomFound) {
491             logger.info(format(" Disabled electrostatics for atoms with name %s", tok));
492           } else {
493             logger.log(Level.INFO, format(" No electrostatics input %s could not be parsed as a numerical "
494                 + "range or atom type present in assembly", tok));
495           }
496         }
497       }
498     }
499   }
500 
501   /**
502    * Attempts to close any open resources associated with the underlying file; primarily to be used
503    * when finished reading a trajectory.
504    */
505   public abstract void closeReader();
506 
507   public int countNumModels() {
508     return -1;
509   }
510 
511   /**
512    * Returns true if the read was successful
513    *
514    * @return a boolean.
515    */
516   public boolean fileRead() {
517     return fileRead;
518   }
519 
520   /**
521    * Return the MolecularSystem that has been read in
522    *
523    * @return a {@link ffx.potential.MolecularAssembly} object.
524    */
525   public MolecularAssembly getActiveMolecularSystem() {
526     return activeMolecularAssembly;
527   }
528 
529   /**
530    * Getter for the field <code>atomList</code>.
531    *
532    * @return a {@link java.util.List} object.
533    */
534   public List<Atom> getAtomList() {
535     return atomList;
536   }
537 
538   /**
539    * getFile
540    *
541    * @return a {@link java.io.File} object.
542    */
543   public File getFile() {
544     return currentFile;
545   }
546 
547   /**
548    * setFile
549    *
550    * @param file a {@link java.io.File} object.
551    */
552   public void setFile(File file) {
553     this.currentFile = file;
554     files = new ArrayList<>();
555     if (file != null) {
556       files.add(file);
557     }
558   }
559 
560   /**
561    * Getter for the field <code>files</code>.
562    *
563    * @return a {@link java.util.List} object.
564    */
565   public List<File> getFiles() {
566     return files;
567   }
568 
569   /**
570    * Setter for the field <code>files</code>.
571    *
572    * @param files a {@link java.util.List} object.
573    */
574   public void setFiles(List<File> files) {
575     this.files = files;
576     if (files != null && !files.isEmpty()) {
577       this.currentFile = files.get(0);
578     } else {
579       this.currentFile = null;
580     }
581   }
582 
583   /**
584    * Gets the last read lambda value read by the filter, if any.
585    *
586    * @return Last lambda value read by this filter.
587    */
588   public OptionalDouble getLastReadLambda() {
589     return OptionalDouble.empty();
590   }
591 
592   /**
593    * Get the MolecularAssembly array.
594    *
595    * @return an array of {@link ffx.potential.MolecularAssembly} objects.
596    */
597   public MolecularAssembly[] getMolecularAssemblyArray() {
598     if (!systems.isEmpty()) {
599       return systems.toArray(new MolecularAssembly[0]);
600     } else {
601       return new MolecularAssembly[]{activeMolecularAssembly};
602     }
603   }
604 
605   /**
606    * Gets all remark lines read by the last readFile or readNext call.
607    *
608    * @return Array of Strings representing remark lines, if any.
609    */
610   public String[] getRemarkLines() {
611     return new String[0];
612   }
613 
614   /**
615    * Return snapshot number.
616    *
617    * @return The snapshot number.
618    */
619   public int getSnapshot() {
620     return -1;
621   }
622 
623   /**
624    * getType
625    *
626    * @return a {@link ffx.potential.Utilities.FileType} object.
627    */
628   public FileType getType() {
629     return fileType;
630   }
631 
632   /**
633    * setType
634    *
635    * @param fileType a {@link ffx.potential.Utilities.FileType} object.
636    */
637   public void setType(FileType fileType) {
638     this.fileType = fileType;
639   }
640 
641   /**
642    * This method is different for each subclass and must be overridden.
643    *
644    * @return a boolean.
645    */
646   public abstract boolean readFile();
647 
648   /**
649    * Reads the next model if applicable (currently, ARC and PDB files only).
650    *
651    * @return If next model read.
652    */
653   public abstract boolean readNext();
654 
655   /**
656    * Reads the next model if applicable (currently, ARC files only).
657    *
658    * @param resetPosition Resets to first frame.
659    * @return If next model read.
660    */
661   public abstract boolean readNext(boolean resetPosition);
662 
663   /**
664    * Reads the next model if applicable (currently, ARC files only).
665    *
666    * @param resetPosition Resets to first frame.
667    * @param print         Flag to print.
668    * @return If next model read.
669    */
670   public abstract boolean readNext(boolean resetPosition, boolean print);
671 
672   /**
673    * Reads the next model if applicable (currently, ARC files only).
674    *
675    * @param resetPosition Resets to first frame.
676    * @param print         Flag to print.
677    * @param parse         Parse data in file. May want to skip structures for parallel jobs.
678    * @return If next model read.
679    */
680   public abstract boolean readNext(boolean resetPosition, boolean print, boolean parse);
681 
682   /**
683    * Setter for the field <code>forceField</code>.
684    *
685    * @param forceField a {@link ffx.potential.parameters.ForceField} object.
686    */
687   public void setForceField(ForceField forceField) {
688     this.forceField = forceField;
689   }
690 
691   /**
692    * Setter for the field <code>properties</code>.
693    *
694    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
695    *                   object.
696    */
697   public void setProperties(CompositeConfiguration properties) {
698     this.properties = properties;
699   }
700 
701   /**
702    * This method is different for each subclass and must be overridden.
703    *
704    * <p>If the append flag is true, "saveFile" will be appended to. Otherwise, the default
705    * versioning
706    * scheme will be applied.
707    *
708    * @param saveFile a {@link java.io.File} object.
709    * @param append   a boolean.
710    * @return a boolean.
711    */
712   public boolean writeFile(File saveFile, boolean append) {
713     return writeFile(saveFile, append, null);
714   }
715 
716   /**
717    * This method is different for each subclass and must be overridden.
718    *
719    * <p>If the append flag is true, "saveFile" will be appended to. Otherwise, the default
720    * versioning
721    * scheme will be applied.
722    *
723    * @param saveFile   a {@link java.io.File} object.
724    * @param append     a boolean.
725    * @param extraLines Additional lines to append to a comments section, or null.
726    * @return a boolean.
727    */
728   public abstract boolean writeFile(File saveFile, boolean append, String[] extraLines);
729 
730   /**
731    * Setter for the field <code>fileRead</code>.
732    *
733    * @param fileRead a boolean.
734    */
735   protected void setFileRead(boolean fileRead) {
736     this.fileRead = fileRead;
737   }
738 
739   /**
740    * setMolecularSystem
741    *
742    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
743    */
744   void setMolecularSystem(MolecularAssembly molecularAssembly) {
745     activeMolecularAssembly = molecularAssembly;
746   }
747 
748   public enum Versioning {
749     TINKER, PREFIX, POSTFIX, PREFIX_ABSOLUTE, POSTFIX_ABSOLUTE, NONE
750   }
751 }