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