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.groovy;
39  
40  import static java.lang.String.format;
41  import static org.apache.commons.io.FileUtils.copyFile;
42  import static org.junit.Assert.assertEquals;
43  import static org.junit.Assert.assertTrue;
44  import static org.junit.Assert.fail;
45  
46  import ffx.potential.utils.PotentialTest;
47  import ffx.utilities.DirectoryUtils;
48  import java.io.BufferedReader;
49  import java.io.File;
50  import java.io.FileReader;
51  import java.io.IOException;
52  import java.nio.file.Files;
53  import java.util.ArrayList;
54  import java.util.Arrays;
55  import java.util.Collection;
56  import java.util.Collections;
57  import java.util.HashMap;
58  import java.util.List;
59  import java.util.Map;
60  import java.util.logging.Level;
61  import java.util.regex.Pattern;
62  import java.util.stream.Collectors;
63  import org.apache.commons.configuration2.Configuration;
64  import org.apache.commons.configuration2.MapConfiguration;
65  import org.apache.commons.io.FilenameUtils;
66  import org.apache.commons.io.IOUtils;
67  import org.junit.After;
68  import org.junit.Test;
69  import org.junit.runner.RunWith;
70  import org.junit.runners.Parameterized;
71  
72  @RunWith(Parameterized.class)
73  public class SolvatorTest extends PotentialTest {
74  
75    /**
76     * Set of default options, such as "-d", "0.5" to specify a half-femtosecond timestep. Can be
77     * over-ridden in test constructors.
78     */
79    private static final Map<String, String> DEFAULT_OPTIONS;
80  
81    static {
82      String[] opts = {
83          "-b", "2.5", // Standard 2.5 A bumpcheck.
84          "-p", "7.0", // Use a shorter padding than ordinary to reduce compute cost.
85      };
86  
87      int nOpts = opts.length;
88      Map<String, String> optMap = new HashMap<>(nOpts);
89      for (int i = 0; i < nOpts; i += 2) {
90        optMap.put(opts[i], opts[i + 1]);
91      }
92      DEFAULT_OPTIONS = Collections.unmodifiableMap(optMap);
93    }
94  
95    private final String info;
96    private final SolvatorTestMode mode;
97    private final File solvatedTestFile;
98    private final Map<String, String> opts;
99    /**
100    * Configuration containing the properties to be used by the solvator.
101    */
102   private final Configuration algorithmConfig;
103 
104   private final List<String> flags;
105   private final File tempDir;
106   private final List<File> copiedFiles = new ArrayList<>();
107   Solvator solvator;
108 
109   public SolvatorTest(
110       String info,
111       SolvatorTestMode mode,
112       String soluteFile,
113       String solvatedTestFileName,
114       String solventFileName,
115       String ionFileName,
116       String[] options,
117       String[] properties,
118       String[] flagArray)
119       throws IOException {
120     this.info = info;
121     this.mode = mode;
122     this.solvatedTestFile = getResourceFile(solvatedTestFileName);
123     if (mode == SolvatorTestMode.HELP) {
124       opts = Collections.emptyMap();
125       algorithmConfig = null;
126       flags = Collections.emptyList();
127       tempDir = null;
128     } else {
129       File temp = null;
130       try {
131         temp = Files.createTempDirectory("Solvator").toFile();
132       } catch (IOException e) {
133         fail(" Could not create a temporary directory.");
134       }
135       tempDir = temp;
136       logger.fine(format(" Running test %s in directory %s", info, tempDir));
137       String[] copiedExtensions = new String[] {"key", "properties", "ions"};
138       String[] filesUsed;
139       if (ionFileName == null) {
140         filesUsed = new String[] {soluteFile, solventFileName};
141       } else {
142         filesUsed = new String[] {soluteFile, solventFileName, ionFileName};
143       }
144       for (String fname : filesUsed) {
145         File srcFile = getResourceFile(fname);
146         File tempFile = new File(tempDir.getAbsolutePath() + "/" + FilenameUtils.getName(fname));
147 
148         copyFile(srcFile, tempFile);
149         copiedFiles.add(tempFile);
150         logger.info(format(" Copied file %s to %s", srcFile, tempFile));
151 
152         for (String ext : copiedExtensions) {
153           srcFile = new File(format("%s.%s", FilenameUtils.removeExtension(srcFile.getPath()), ext));
154           if (srcFile.exists()) {
155             logger.fine(" Copying extension " + ext);
156             tempFile = new File(format("%s.%s", FilenameUtils.removeExtension(tempFile.getPath()), ext));
157             logger.fine(format(" Copied file %s to %s", srcFile, tempFile));
158             copyFile(srcFile, tempFile);
159           }
160         }
161       }
162 
163       int nOpts = options.length;
164       int nProps = properties.length;
165       int nFlags = flagArray.length;
166 
167       if (nOpts > 0) {
168         assertEquals(format("Unmatched option key %s for test %s", options[nOpts - 1], info),
169             0, options.length % 2);
170       }
171       if (nProps > 0) {
172         assertEquals(format("Unmatched property key %s for test %s", properties[nProps - 1], info),
173             0, properties.length % 2);
174       }
175 
176       Pattern validOption = Pattern.compile("^--?[^D]");
177       Pattern validProperty = Pattern.compile("^[^-]");
178 
179       Map<String, String> groovyOpts = new HashMap<>(DEFAULT_OPTIONS);
180       for (int i = 0; i < nOpts; i += 2) {
181         String opti = options[i];
182         assertTrue(
183             format(" Option %s for test %s does not look like a Groovy option!", opti, info),
184             validOption.matcher(opti).find());
185         groovyOpts.put(opti, options[i + 1]);
186       }
187       groovyOpts.put("--sFi", copiedFiles.get(1).getPath());
188       if (ionFileName != null) {
189         groovyOpts.put("--iFi", copiedFiles.get(2).getPath());
190       }
191       this.opts = Collections.unmodifiableMap(groovyOpts);
192 
193       Map<String, String> addedProps = new HashMap<>();
194       for (int i = 0; i < nProps; i += 2) {
195         String propi = properties[i];
196         assertTrue(
197             format(" Property %s for test %s does not look like a property!", propi, info),
198             validProperty.matcher(propi).find());
199         addedProps.put(propi, properties[i + 1]);
200       }
201       algorithmConfig = new MapConfiguration(addedProps);
202 
203       Map<String, Boolean> addedFlags = new HashMap<>();
204       for (int i = 0; i < nFlags; i += 2) {
205         String flagi = flagArray[i];
206         assertTrue(
207             format(" Flag %s for test %s does not look like a flag!", flagi, info),
208             validOption.matcher(flagi).find());
209         String vali = flagArray[i + 1];
210         assertTrue(
211             format(
212                 " Value %s for flag %s in test %s is not a true/false value!", vali, flagi, info),
213             vali.equalsIgnoreCase("TRUE") || vali.equalsIgnoreCase("FALSE"));
214         addedFlags.put(flagi, Boolean.parseBoolean(vali));
215       }
216       this.flags =
217           Collections.unmodifiableList(
218               addedFlags.entrySet().stream()
219                   .filter(Map.Entry::getValue)
220                   .map(Map.Entry::getKey)
221                   .collect(Collectors.toList()));
222     }
223   }
224 
225   @Parameterized.Parameters
226   public static Collection<Object[]> data() {
227     return Arrays.asList(
228         new Object[][] {
229             {
230                 "Solvator Help Message Test",
231                 SolvatorTestMode.HELP,
232                 "",
233                 null,
234                 null,
235                 null,
236                 new String[] {},
237                 new String[] {},
238                 new String[] {"-h"}
239             },
240             {
241                 "Aspartate Pure Water Solvation",
242                 SolvatorTestMode.SOLVATE,
243                 "capAsp.xyz",
244                 "capAsp.pureWater.pdb",
245                 "watertiny.xyz",
246                 null,
247                 new String[] {"-b", "2.5", "-p", "8.0", "-s", "42"},
248                 new String[] {},
249                 new String[] {}
250             },
251             {
252                 "Aspartate Neutralizing NaCl (+200 mM) Solvation",
253                 SolvatorTestMode.SOLVATE,
254                 "capAsp.xyz",
255                 "capAsp.neutNaCl.pdb",
256                 "watertiny.xyz",
257                 "nacl.pdb",
258                 new String[] {"-b", "2.5", "-p", "8.0", "-s", "42"},
259                 new String[] {},
260                 new String[] {}
261             },
262             {
263                 "Aspartate Charged NaCl (200 mM) Solvation",
264                 SolvatorTestMode.SOLVATE,
265                 "capAsp.xyz",
266                 "capAsp.chargedNaCl.pdb",
267                 "watertiny.xyz",
268                 "naclCharged.pdb",
269                 new String[] {"-b", "2.5", "-p", "8.0", "-s", "42"},
270                 new String[] {},
271                 new String[] {}
272             }
273         });
274   }
275 
276   @After
277   public void after() {
278     if (mode != SolvatorTestMode.HELP) {
279       // Clean up the temporary directory if it exists.
280       // Delete temporary directory.
281       try {
282         DirectoryUtils.deleteDirectoryTree(tempDir.toPath());
283       } catch (IOException e) {
284         System.out.println(e);
285         fail(" Exception deleting files created by Cart2Frac.");
286       }
287     }
288   }
289 
290   @Test
291   public void testSolvator() {
292     switch (mode) {
293       case HELP:
294         testHelp();
295         break;
296       case SOLVATE:
297         testSolvate();
298         break;
299     }
300   }
301 
302   @Override
303   public String toString() {
304     return info;
305   }
306 
307   private void testHelp() {
308     String[] args = {"-h"};
309     binding.setVariable("args", args);
310     solvator = new Solvator(binding).run();
311     potentialScript = solvator;
312   }
313 
314   private void testSolvate() {
315     // Prepare the Binding with input arguments.
316     List<String> argList = new ArrayList<>(flags);
317     opts.forEach(
318         (String k, String v) -> {
319           argList.add(k);
320           argList.add(v);
321         });
322     argList.add(copiedFiles.get(0).getPath());
323     String[] args = argList.toArray(new String[0]);
324     binding.setVariable("args", args);
325     solvator = new Solvator(binding);
326     solvator.setProperties(algorithmConfig);
327 
328     solvator.run();
329     potentialScript = solvator;
330 
331     File written = solvator.getWrittenFile();
332 
333     logger.log(Level.INFO, " Written file {0}", written.getAbsolutePath());
334     logger.log(Level.INFO, " Expected file {0}", solvatedTestFile.getAbsolutePath());
335 
336     try (BufferedReader expectedReader = new BufferedReader(new FileReader(solvatedTestFile));
337         BufferedReader writtenReader = new BufferedReader(new FileReader(written))) {
338       boolean same = IOUtils.contentEqualsIgnoreEOL(expectedReader, writtenReader);
339       assertTrue(" File written by test did not match the expected file!", same);
340     } catch (IOException ex) {
341       fail(format(" Exception %s in attempting to compare expected file %s to written file %s",
342           ex, solvatedTestFile, written));
343     }
344   }
345 
346   private enum SolvatorTestMode {
347     HELP,
348     SOLVATE
349   }
350 }