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