PageRenderTime 59ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 2ms

/source/RMG/jing/chem/QMTP.java

https://github.com/rwest/RMG-Java
Java | 3717 lines | 2641 code | 57 blank | 1019 comment | 766 complexity | 7d2a8e4aaaf0e894bcd0c17001f4c8b5 MD5 | raw file
Possible License(s): LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. // //////////////////////////////////////////////////////////////////////////////
  2. //
  3. // RMG - Reaction Mechanism Generator
  4. //
  5. // Copyright (c) 2002-2011 Prof. William H. Green (whgreen@mit.edu) and the
  6. // RMG Team (rmg_dev@mit.edu)
  7. //
  8. // Permission is hereby granted, free of charge, to any person obtaining a
  9. // copy of this software and associated documentation files (the "Software"),
  10. // to deal in the Software without restriction, including without limitation
  11. // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  12. // and/or sell copies of the Software, and to permit persons to whom the
  13. // Software is furnished to do so, subject to the following conditions:
  14. //
  15. // The above copyright notice and this permission notice shall be included in
  16. // all copies or substantial portions of the Software.
  17. //
  18. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  23. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  24. // DEALINGS IN THE SOFTWARE.
  25. //
  26. // //////////////////////////////////////////////////////////////////////////////
  27. package jing.chem;
  28. import java.util.*;
  29. import jing.chemParser.ChemParser;
  30. import jing.chemUtil.*;
  31. import jing.param.*;
  32. import java.io.File;
  33. import java.io.FileReader;
  34. import java.io.FileWriter;
  35. import java.io.IOException;
  36. import java.io.BufferedReader;
  37. import java.io.InputStream;
  38. import java.io.InputStreamReader;
  39. import jing.rxnSys.Logger;
  40. // quantum mechanics thermo property estimator; analog of GATP
  41. public class QMTP implements GeneralGAPP {
  42. final protected static double ENTHALPY_HYDROGEN = 52.1; // needed for HBI
  43. private static QMTP INSTANCE = new QMTP(); // ## attribute INSTANCE
  44. protected static PrimaryThermoLibrary primaryLibrary;// Note: may be able to separate this out into GeneralGAPP, as
  45. // this is common to both GATP and QMTP
  46. public static String qmfolder = System.getProperty("RMG.qmCalculationsDir"); // location for Gaussian/mopac output
  47. protected LinkedHashMap<String, ThermoData> qmLibrary = new LinkedHashMap<String, ThermoData>();
  48. // protected static LinkedHashMap library; //as above, may be able to move this and associated functions to GeneralGAPP
  49. // (and possibly change from "x implements y" to "x extends y"), as it is common to both GATP and QMTP
  50. protected Vector<String> failedQm = new Vector<String>();
  51. protected ThermoGAGroupLibrary thermoLibrary; // needed for HBI
  52. public static String qmprogram = "both";// the qmprogram can be "mopac", "gaussian03", "both" (MOPAC and Gaussian),
  53. // or "mm4"/"mm4hr"
  54. public static boolean usePolar = false; // use polar keyword in MOPAC
  55. public static boolean useCanTherm = true; // whether to use CanTherm in MM4 cases for interpreting output via
  56. // force-constant matrix; this will hopefully avoid zero frequency issues
  57. public static boolean useHindRot = false;// whether to use HinderedRotor scans with MM4 (requires useCanTherm=true)
  58. public static boolean keepQMfiles = true; // whether to keep all the Qmfiles generated by Gaussian/mopac or not
  59. public static int connectivityCheck = 0; // level of connectivity checking; 0: "off": no checking; 1: "check":
  60. // (print warning if connectivity doesn't seem to match); 2: "confirm": consider the run a failure if the connectivity
  61. // doesn't seem to match
  62. public static double R = 1.9872; // ideal gas constant in cal/mol-K (does this appear elsewhere in RMG, so I don't
  63. // need to reuse it?)
  64. public static double Hartree_to_kcal = 627.5095; // conversion from Hartree to kcal/mol taken from Gaussian thermo
  65. // white paper
  66. public static double Na = 6.02214179E23;// Avagadro's number; cf.
  67. // http://physics.nist.gov/cgi-bin/cuu/Value?na|search_for=physchem_in!
  68. public static double k = 1.3806504E-23;// Boltzmann's constant in J/K; cf.
  69. // http://physics.nist.gov/cgi-bin/cuu/Value?na|search_for=physchem_in!
  70. public static double h = 6.62606896E-34;// Planck's constant in J-s; cf.
  71. // http://physics.nist.gov/cgi-bin/cuu/Value?h|search_for=universal_in!
  72. public static double c = 299792458. * 100;// speed of light in vacuum in cm/s, cf.
  73. // http://physics.nist.gov/cgi-bin/cuu/Value?c|search_for=universal_in!
  74. public static double deltaTheta = 5.0;// degree increment for rotor scans when using useHindRot
  75. // Constructors
  76. // ## operation QMTP()
  77. private QMTP() {
  78. initializePrimaryThermoLibrary();
  79. initalizeQmLibrary();
  80. }
  81. public String getQmMethod()
  82. throws CouldNotDetermineQMMethodException {
  83. String qmProgram = qmprogram;
  84. String qmMethod = "";
  85. if (qmProgram.equals("mm4") || qmProgram.equals("mm4hr")) {
  86. qmMethod = "mm4";
  87. if (qmProgram.equals("mm4hr"))
  88. useHindRot = true;
  89. } else if (qmProgram.equals("mopac")) {
  90. qmMethod = "pm7"; // may eventually want to pass this to various functions to choose which "sub-function" to
  91. // call
  92. } else if (qmProgram.equals("gaussian03") || qmProgram.equals("both")) {
  93. qmMethod = "pm3";
  94. } else {
  95. throw new CouldNotDetermineQMMethodException();
  96. }
  97. return qmMethod;
  98. }
  99. public ThermoData generateFakeThermoData() {
  100. ThermoData result = null;
  101. ThermoGAValue impossible = new ThermoGAValue(1000
  102. , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null);
  103. result.plus(impossible);
  104. return result;
  105. }
  106. // ## operation generateThermoData(ChemGraph)
  107. public ThermoData generateThermoData(ChemGraph p_chemGraph) {
  108. if (!keepQMfiles) {
  109. cleanQmFiles();
  110. }
  111. // #[ operation generateThermoData(ChemGraph)
  112. // first, check for thermo data in the primary thermo library and library (?); if it is there, use it
  113. ThermoData result = null;
  114. ThermoData tmpTherm = primaryLibrary.getThermoData(p_chemGraph
  115. .getGraph());
  116. // Logger.info(result);
  117. if (tmpTherm != null) {
  118. result = tmpTherm.copyWithExtraInfo();// use a copy of the object!; that way, subsequent modifications of
  119. // this object don't change the primary thermo library
  120. p_chemGraph.fromprimarythermolibrary = true;
  121. return result;
  122. }
  123. result = new ThermoData();
  124. int maxRadNumForQM = Global.maxRadNumForQM;
  125. if (p_chemGraph.getRadicalNumber() > maxRadNumForQM)// use HBI if the molecule has more radicals than
  126. // maxRadNumForQM; this is helpful because ; also MM4 (and MM3) look like they may have issues with radicals
  127. {// this code is based closely off of GATP saturation (in getGAGroup()), but there are some modifications,
  128. // particularly for symmetry correction
  129. // find the initial symmetry number
  130. int sigmaRadical = p_chemGraph.getSymmetryNumber();
  131. Graph g = p_chemGraph.getGraph();
  132. LinkedHashMap oldCentralNode = (LinkedHashMap) (p_chemGraph.getCentralNode())
  133. .clone();
  134. // saturate radical site
  135. int max_radNum_molecule = ChemGraph.getMAX_RADICAL_NUM();
  136. int max_radNum_atom = Math.min(8, max_radNum_molecule);
  137. int[] idArray = new int[max_radNum_molecule];
  138. Atom[] atomArray = new Atom[max_radNum_molecule];
  139. Node[][] newnode = new Node[max_radNum_molecule][max_radNum_atom];
  140. int radicalSite = 0;
  141. Iterator iter = p_chemGraph.getNodeList();
  142. FreeElectron satuated = FreeElectron.make("0");
  143. while (iter.hasNext()) {
  144. Node node = (Node) iter.next();
  145. Atom atom = (Atom) node.getElement();
  146. if (atom.isRadical()) {
  147. radicalSite++;
  148. // save the old radical atom
  149. idArray[radicalSite - 1] = node.getID().intValue();
  150. atomArray[radicalSite - 1] = atom;
  151. // new a satuated atom and replace the old one
  152. Atom newAtom = Atom.make(atom.getChemElement(), satuated);
  153. node.setElement(newAtom);
  154. node.updateFeElement();
  155. }
  156. }
  157. // add H to saturate chem graph
  158. Atom H = Atom.make(ChemElement.make("H"), satuated);
  159. Bond S = Bond.make("S");
  160. for (int i = 0; i < radicalSite; i++) {
  161. Node node = p_chemGraph.getNodeAt(idArray[i]);
  162. Atom atom = atomArray[i];
  163. int HNum = atom.getRadicalNumber();
  164. for (int j = 0; j < HNum; j++) {
  165. newnode[i][j] = g.addNode(H);
  166. g.addArcBetween(node, S, newnode[i][j]);
  167. }
  168. node.updateFgElement();
  169. }
  170. // find the saturated symmetry number
  171. int sigmaSaturated = p_chemGraph.getSymmetryNumber();
  172. // result = generateThermoData(g);//I'm not sure what GATP does, but this recursive calling will use HBIs on
  173. // saturated species if it exists in PrimaryThermoLibrary
  174. // check the primary thermo library for the saturated graph
  175. tmpTherm = primaryLibrary.getThermoData(p_chemGraph.getGraph());
  176. // Logger.info(result);
  177. if (tmpTherm != null) {
  178. result = tmpTherm.copyWithExtraInfo();// use a copy of the object!; that way, subsequent modifications
  179. // of this object don't change the primary thermo library
  180. p_chemGraph.fromprimarythermolibrary = false;// we don't want to set fromprimarythermolibrary to true,
  181. // because the result is not directly from the PTL, but comes via PTL + HBI corrections; if true is set here, this would
  182. // affect two things, both in Species.java: 1) the naming; in the HBI case, we don't want to use a name derived from the
  183. // thermo name as weird things can happen 2)findStablestThermoData considers the value to be the final word; however,
  184. // since this is a radical that is not directly in the primaryThermoLibrary, we want to consider alternative thermo for
  185. // all possible resonance isomers
  186. } else {
  187. tmpTherm = getQMThermoData(p_chemGraph);
  188. result = tmpTherm.copyWithExtraInfo();
  189. }
  190. // find the BDE for all radical groups
  191. if (thermoLibrary == null)
  192. initGAGroupLibrary();
  193. for (int i = 0; i < radicalSite; i++) {
  194. int id = idArray[i];
  195. Node node = g.getNodeAt(id);
  196. Atom old = (Atom) node.getElement();
  197. node.setElement(atomArray[i]);
  198. node.updateFeElement();
  199. // get rid of the extra H at ith site
  200. int HNum = atomArray[i].getRadicalNumber();
  201. for (int j = 0; j < HNum; j++) {
  202. g.removeNode(newnode[i][j]);
  203. }
  204. node.updateFgElement();
  205. p_chemGraph.resetThermoSite(node);
  206. ThermoGAValue thisGAValue = thermoLibrary
  207. .findRadicalGroup(p_chemGraph);
  208. if (thisGAValue == null) {
  209. Logger.error("Radical group not found: " + node.getID());
  210. } else {
  211. // Logger.info(node.getID() + " radical correction: " + thisGAValue.getName() +
  212. // " "+thisGAValue.toString());
  213. result.plus(thisGAValue);
  214. }
  215. // recover the saturated site for next radical site calculation
  216. node.setElement(old);
  217. node.updateFeElement();
  218. for (int j = 0; j < HNum; j++) {
  219. newnode[i][j] = g.addNode(H);
  220. g.addArcBetween(node, S, newnode[i][j]);
  221. }
  222. node.updateFgElement();
  223. }
  224. // recover the chem graph structure
  225. // recover the radical
  226. for (int i = 0; i < radicalSite; i++) {
  227. int id = idArray[i];
  228. Node node = g.getNodeAt(id);
  229. node.setElement(atomArray[i]);
  230. node.updateFeElement();
  231. int HNum = atomArray[i].getRadicalNumber();
  232. // get rid of extra H
  233. for (int j = 0; j < HNum; j++) {
  234. g.removeNode(newnode[i][j]);
  235. }
  236. node.updateFgElement();
  237. }
  238. // subtract the enthalphy of H from the result
  239. int rad_number = p_chemGraph.getRadicalNumber();
  240. ThermoGAValue enthalpy_H = new ThermoGAValue(ENTHALPY_HYDROGEN
  241. * rad_number, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null);
  242. result.minus(enthalpy_H);
  243. // correct the symmetry number based on the relative radical and saturated symmetry number; this should
  244. // hopefully sidestep potential complications based on the fact that certain symmetry effects could be included in HBI
  245. // value itself, and the fact that the symmetry number correction for saturated molecule has already been implemented,
  246. // and it is likely to be different than symmetry number considered here, since the correction for the saturated
  247. // molecule will have been external symmetry number, whereas RMG's ChemGraph symmetry number estimator includes both
  248. // internal and external symmetry contributions; even so, I don't know if this will handle a change from chiral to
  249. // achiral (or vice versa) properly
  250. ThermoGAValue symmetryNumberCorrection = new ThermoGAValue(0, -1
  251. * GasConstant.getCalMolK()
  252. * Math.log((double) (sigmaRadical)
  253. / (double) (sigmaSaturated)), 0, 0, 0, 0, 0, 0, 0,
  254. 0, 0, 0, null);
  255. result.plus(symmetryNumberCorrection);
  256. p_chemGraph.setCentralNode(oldCentralNode);
  257. // display corrected thermo to user
  258. String[] InChInames = getQMFileName(p_chemGraph);// determine the filename (InChIKey) and InChI with
  259. // appended info for triplets, etc.
  260. String name = InChInames[0];
  261. String InChIaug = InChInames[1];
  262. Logger.info("HBI-based thermo for " + name + "(" + InChIaug + "): "
  263. + result.toString());// print result, at least for debugging purposes
  264. } else {
  265. // no need for HBI radical corrections, just get the QM result
  266. tmpTherm = getQMThermoData(p_chemGraph);
  267. result = tmpTherm.copyWithExtraInfo();
  268. }
  269. return result;
  270. // #]
  271. }
  272. public ThermoData getQMThermoData(ChemGraph p_chemGraph) {
  273. // Try to get the QMThermoData from the qmLibrary, if it's not there then call getQMThermoData and save it for
  274. // future.
  275. ThermoData result = null;
  276. // First to get from qmLibrary
  277. String[] InChInames = getQMFileName(p_chemGraph);// determine the filename (InChIKey) and InChI with appended
  278. // info for triplets, etc.
  279. String name = InChInames[0];
  280. String InChIaug = InChInames[1];
  281. ThermoData tempTherm = qmLibrary.get(InChIaug);
  282. if (tempTherm != null) {
  283. result = tempTherm.copyWithExtraInfo(); // use a copy of the object!; that way, subsequent modifications of
  284. // this object don't change the QM library
  285. Logger.info("QM calculation for "
  286. + InChIaug
  287. + " previously performed. Using Thermo from previous results in QMlibrary.");
  288. result.setSource(result.comments);
  289. // Added for debugging
  290. Logger.info("Thermo Data for " + InChIaug + " is " + result);
  291. } else { // couldn't find it in qmLibrary
  292. // Check to see if QMTP has failed all attempts on this molecule before
  293. // If so, generate Thermo using Benson groups
  294. if (failedQm.contains(InChIaug)) {
  295. Logger.info("All attempts at QMTP for "
  296. + name
  297. + "("
  298. + InChIaug
  299. + ") have previously failed. Falling back to Benson Group Additivity.");
  300. TDGenerator gen = new BensonTDGenerator();
  301. return gen.generateFakeThermo();
  302. // return gen.generateThermo(p_chemGraph);
  303. }
  304. // generate new QM Thermo Data
  305. try {
  306. String qmMethod = getQmMethod();
  307. result = generateQMThermoData(p_chemGraph, qmMethod);
  308. // now save it for next time
  309. QMLibraryEditor.addQMTPThermo(p_chemGraph, InChIaug, result,
  310. qmMethod, qmprogram);
  311. qmLibrary.put(InChIaug, result);
  312. } catch (AllQmtpAttemptsFailedException e) {
  313. Logger.warning("Falling back to Benson group additivity due to repeated failure in QMTP calculations");
  314. TDGenerator gen = new BensonTDGenerator();
  315. // return gen.generateThermo(p_chemGraph);
  316. return gen.generateFakeThermo();
  317. }
  318. }
  319. return result;
  320. }
  321. private Thread setHoldAndGetClearerThread(final String name) {
  322. /*
  323. * Creates a .hold file for the given name, and creates a shutdown hook to delete the hold file. Returns the
  324. * hook, which is a thread which you can later run and de-register.
  325. */
  326. Logger.debug("Creating hold file for " + name);
  327. final File holdFile = new File(qmfolder, name + ".hold");
  328. try {
  329. holdFile.createNewFile();
  330. } catch (Exception e) {
  331. Logger.error("Error creating .hold file for " + name + ": "
  332. + e.toString());
  333. System.exit(0);
  334. throw new RuntimeException("Error creating .hold file for " + name,
  335. e);
  336. }
  337. Thread hook = new Thread() {
  338. public void run() {
  339. Logger.debug("Deleting hold file for " + name);
  340. holdFile.delete();
  341. };
  342. };
  343. Runtime.getRuntime().addShutdownHook(hook);
  344. return hook;
  345. }
  346. private boolean checkHoldExists(String name) {
  347. /*
  348. * Checks for existance of a .hold file for the given name. Returns true if it exists (and you shouldn't run a
  349. * new job).
  350. */
  351. Logger.debug("Checking hold file for " + name);
  352. final File holdFile = new File(qmfolder, name + ".hold");
  353. return holdFile.exists();
  354. }
  355. private void clearHold(Thread holdClearerThread) {
  356. /*
  357. * Clears the hold, by running and unregistering the hold-clearing thread.
  358. */
  359. Runtime.getRuntime().removeShutdownHook(holdClearerThread);
  360. holdClearerThread.start();
  361. try {
  362. holdClearerThread.join(); // wait for it to finish
  363. } catch (InterruptedException e) {
  364. Thread.currentThread().interrupt();
  365. }
  366. }
  367. public ThermoData generateQMThermoData(ChemGraph p_chemGraph, String qmMethod)
  368. throws AllQmtpAttemptsFailedException {
  369. // if there is no data in the libraries, calculate the result based on QM or MM calculations; the below steps
  370. // will be generalized later to allow for other quantum mechanics packages, etc.
  371. String qmProgram = qmprogram;
  372. ThermoData result = new ThermoData();
  373. double[] dihedralMinima = null;
  374. String[] InChInames = getQMFileName(p_chemGraph);// determine the filename (InChIKey) and InChI with appended
  375. // info for triplets, etc.
  376. String name = InChInames[0];
  377. String InChIaug = InChInames[1];
  378. String directory = qmfolder;
  379. File dir = new File(directory);
  380. directory = dir.getAbsolutePath();// this and previous three lines get the absolute path for the directory
  381. // check for existing hold file before starting calculations (to avoid the possibility of interference with
  382. // other jobs using the same QMfiles folder)
  383. try {
  384. while (checkHoldExists(name)) {
  385. Logger.info("Existence of hold file for "
  386. + name
  387. + " suggests that another RMG process is currently running calculations on this molecule; waiting for other RMG process to finish; will check again in 60 seconds...");
  388. Thread.sleep(60000);
  389. }
  390. } catch (Exception e) {
  391. Logger.error("Unexpected error while waiting for hold to be lifted: "
  392. + e.toString());
  393. System.exit(0);
  394. }
  395. if (qmMethod.equals("pm3") || qmMethod.equals("pm7")) {
  396. // first, check to see if the result already exists and the job terminated successfully
  397. boolean gaussianResultExists = successfulGaussianResultExistsQ(
  398. name, directory, InChIaug);
  399. boolean mopacResultExists = successfulMopacResultExistsQ(name,
  400. directory, InChIaug);
  401. if (!gaussianResultExists && !mopacResultExists) {// if a successful result doesn't exist from previous run
  402. // (or from this run), run the calculation; if a successful result exists, we will skip directly to parsing the file
  403. // step 0: create a .hold file to prevent other jobs running with the same directory from interfering by
  404. // trying to run with the same molecule
  405. Thread holdClearer = setHoldAndGetClearerThread(name);
  406. // steps 1 and 2: create 2D and 3D mole files
  407. molFile p_3dfile = create3Dmolfile(name, p_chemGraph);
  408. // 3. create the Gaussian or MOPAC input file
  409. directory = qmfolder;
  410. dir = new File(directory);
  411. directory = dir.getAbsolutePath();// this and previous three lines get the absolute path for the
  412. // directory
  413. int attemptNumber = 1;// counter for attempts using different keywords
  414. int successFlag = 0;// flag for success of Gaussian run; 0 means it failed, 1 means it succeeded
  415. int maxAttemptNumber = 1;
  416. int multiplicity = p_chemGraph.getRadicalNumber() + 1; // multiplicity = radical number + 1
  417. while (successFlag == 0 && attemptNumber <= maxAttemptNumber) {
  418. // IF block to check which program to use
  419. if (qmProgram.equals("gaussian03")) {
  420. if (p_chemGraph.getAtomNumber() > 1) {
  421. maxAttemptNumber = createGaussianInput(name,
  422. directory, p_3dfile, attemptNumber,
  423. InChIaug, multiplicity, qmMethod);
  424. } else {
  425. maxAttemptNumber = createGaussianInput(name,
  426. directory, p_3dfile, -1, InChIaug,
  427. multiplicity, qmMethod);// use -1 for attemptNumber for monoatomic case
  428. }
  429. // 4. run Gaussian
  430. successFlag = runGaussian(name, directory, InChIaug);
  431. } else if (qmProgram.equals("mopac")
  432. || qmProgram.equals("both")) {
  433. // Modified by Aaron
  434. maxAttemptNumber = createMopacInput(name, directory,
  435. p_3dfile, attemptNumber, InChIaug, multiplicity, qmMethod);
  436. successFlag = runMOPAC(name, directory, InChIaug);
  437. } else {
  438. Logger.critical("Unsupported quantum chemistry program");
  439. System.exit(0);
  440. }
  441. // new IF block to check success
  442. if (successFlag == 1) {
  443. clearHold(holdClearer);
  444. Logger.info("Attempt #" + attemptNumber
  445. + " on species " + name + " (" + InChIaug
  446. + ") succeeded.");
  447. } else if (successFlag == 0) {
  448. if (attemptNumber == maxAttemptNumber) {// if this is the last possible attempt, and the
  449. // calculation fails, exit with an error message
  450. if (qmProgram.equals("both")) { // if we are running with "both" option and all keywords
  451. // fail, try with Gaussian
  452. qmProgram = "gaussian03";
  453. Logger.info("*****Final MOPAC attempt (#"
  454. + maxAttemptNumber + ") on species "
  455. + name + " (" + InChIaug
  456. + ") failed. Trying to use Gaussian.");
  457. attemptNumber = 0;// this needs to be 0 so that when we increment attemptNumber below,
  458. // it becomes 1 when returning to the beginning of the for loop
  459. maxAttemptNumber = 1;
  460. } else {
  461. Logger.info("*****Final attempt (#"
  462. + maxAttemptNumber + ") on species "
  463. + name + " (" + InChIaug + ") failed.");
  464. Logger.info(p_chemGraph.toString());
  465. clearHold(holdClearer);
  466. // Add to augmented InChI failedQm so that RMG will not try to run this molecule in QMTP
  467. // again
  468. failedQm.add(InChIaug);
  469. throw new AllQmtpAttemptsFailedException();
  470. }
  471. }
  472. Logger.info("*****Attempt #" + attemptNumber
  473. + " on species " + name + " (" + InChIaug
  474. + ") failed. Will attempt a new keyword.");
  475. attemptNumber++;// try again with new keyword
  476. }
  477. }
  478. }
  479. // 5. parse QM output and record as thermo data (function includes symmetry/point group calcs, etc.); if
  480. // both Gaussian and MOPAC results exist, Gaussian result is used
  481. if (gaussianResultExists
  482. || (qmProgram.equals("gaussian03") && !mopacResultExists)) {
  483. result = parseGaussianPM3(name, directory, p_chemGraph);
  484. } else if (mopacResultExists || qmProgram.equals("mopac")
  485. || qmProgram.equals("both")) {
  486. result = parseMopac(name, directory, p_chemGraph, qmMethod);
  487. } else {
  488. Logger.critical("Unexpected situation in QMTP thermo estimation");
  489. System.exit(0);
  490. }
  491. } else {// mm4 case
  492. // first, check to see if the result already exists and the job terminated successfully
  493. boolean mm4ResultExists = successfulMM4ResultExistsQ(name,
  494. directory, InChIaug);
  495. if (!mm4ResultExists) {// if a successful result doesn't exist from previous run (or from this run), run the
  496. // calculation; if a successful result exists, we will skip directly to parsing the file
  497. // step 0: create a .hold file to prevent other jobs running with the same directory from interfering by
  498. // trying to run with the same molecule
  499. Thread holdClearer = setHoldAndGetClearerThread(name);
  500. // steps 1 and 2: create 2D and 3D mole files
  501. molFile p_3dfile = create3Dmolfile(name, p_chemGraph);
  502. // 3. create the MM4 input file
  503. directory = qmfolder;
  504. dir = new File(directory);
  505. directory = dir.getAbsolutePath();// this and previous three lines get the absolute path for the
  506. // directory
  507. int attemptNumber = 1;// counter for attempts using different keywords
  508. int successFlag = 0;// flag for success of MM4 run; 0 means it failed, 1 means it succeeded
  509. int maxAttemptNumber = 1;
  510. int multiplicity = p_chemGraph.getRadicalNumber() + 1; // multiplicity = radical number + 1
  511. while (successFlag == 0 && attemptNumber <= maxAttemptNumber) {
  512. maxAttemptNumber = createMM4Input(name, directory,
  513. p_3dfile, attemptNumber, InChIaug, multiplicity);
  514. // 4. run MM4
  515. successFlag = runMM4(name, directory, InChIaug);
  516. // new IF block to check success
  517. if (successFlag == 1) {
  518. clearHold(holdClearer);
  519. Logger.info("Attempt #" + attemptNumber
  520. + " on species " + name + " (" + InChIaug
  521. + ") succeeded.");
  522. // run rotor calculations if necessary
  523. int rotors = p_chemGraph.getInternalRotor();
  524. if (useHindRot && rotors > 0) {
  525. // we should re-run scans even if pre-existing scans exist because atom numbering may change
  526. // from case to case; a better solution would be to check for stored CanTherm output and use that if available
  527. Logger.info("Running rotor scans on " + name
  528. + "...");
  529. dihedralMinima = createMM4RotorInput(name,
  530. directory, p_chemGraph, rotors);// we don't worry about checking InChI here; if
  531. // there is an InChI mismatch it should be caught
  532. runMM4Rotor(name, directory, rotors);
  533. }
  534. } else if (successFlag == 0) {
  535. if (attemptNumber == maxAttemptNumber) {// if this is the last possible attempt, and the
  536. // calculation fails, exit with an error message
  537. Logger.info("*****Final attempt (#"
  538. + maxAttemptNumber + ") on species " + name
  539. + " (" + InChIaug + ") failed.");
  540. Logger.info(p_chemGraph.toString());
  541. // delete the hold file
  542. clearHold(holdClearer);
  543. throw new AllQmtpAttemptsFailedException();
  544. }
  545. Logger.info("*****Attempt #" + attemptNumber
  546. + " on species " + name + " (" + InChIaug
  547. + ") failed. Will attempt a new keyword.");
  548. attemptNumber++;// try again with new keyword
  549. }
  550. }
  551. if (useCanTherm) {
  552. performCanThermCalcs(name, directory, p_chemGraph,
  553. dihedralMinima, false);
  554. if (p_chemGraph.getInternalRotor() > 0)
  555. performCanThermCalcs(name, directory, p_chemGraph,
  556. dihedralMinima, true);// calculate RRHO case for comparison
  557. }
  558. }
  559. // 5. parse MM4 output and record as thermo data (function includes symmetry/point group calcs, etc.)
  560. if (!useCanTherm)
  561. result = parseMM4(name, directory, p_chemGraph);
  562. else {
  563. // if (qmdata==null) qmdata = getQMDataWithCClib(name, directory, p_chemGraph, true);//get qmdata if it
  564. // is null (i.e. if a pre-existing successful result exists and it wasn't read in above)
  565. result = parseCanThermFile(name, directory, p_chemGraph);
  566. if (p_chemGraph.getInternalRotor() > 0)
  567. parseCanThermFile(name + "_RRHO", directory, p_chemGraph);// print the RRHO result for comparison
  568. }
  569. }
  570. return result;
  571. }
  572. protected static QMTP getINSTANCE() {
  573. return INSTANCE;
  574. }
  575. public void initializePrimaryThermoLibrary() {
  576. primaryLibrary = PrimaryThermoLibrary.getINSTANCE();
  577. }
  578. // Checks to see if QmLibrary already exists. If not, creates the necessary directorys and files
  579. public void initalizeQmLibrary() {
  580. try {
  581. qmLibrary = QMLibraryEditor
  582. .readLibrary("QMThermoLibrary/Library.txt");
  583. QMLibraryEditor.initialize();
  584. } catch (IOException e) {
  585. Logger.info("No QM Thermo Library detected. New QM Library being created");
  586. QMLibraryEditor.initialize();
  587. }
  588. }
  589. // Deletes the contents of the QmFiles directory
  590. public void cleanQmFiles() {
  591. File qmFolder = new File(qmfolder);
  592. // Deletes folder and everything inside
  593. ChemParser.deleteDir(qmFolder);
  594. // Remake empty directory
  595. qmFolder.mkdir();
  596. }
  597. // creates a 3D molFile; for monoatomic species, it just returns the 2D molFile
  598. public molFile create3Dmolfile(String name, ChemGraph p_chemGraph) {
  599. // 1. create a 2D file
  600. // use the absolute path for directory, so we can easily reference from other directories in command-line paths
  601. String directory = System.getProperty("RMG.2DmolfilesDir");
  602. directory = new File(directory).getAbsolutePath();
  603. molFile p_2dfile = new molFile(name, directory, p_chemGraph);
  604. molFile p_3dfile = new molFile();// it seems this must be initialized, so we initialize to empty object
  605. // 2. convert from 2D to 3D using RDKit if the 2D molfile is for a molecule with 2 or more atoms
  606. int atoms = p_chemGraph.getAtomNumber();
  607. if (atoms > 1) {
  608. int distGeomAttempts = 1;
  609. if (atoms > 3) {// this check prevents the number of attempts from being negative
  610. distGeomAttempts = 5 * (p_chemGraph.getAtomNumber() - 3); // number of conformer attempts is just a
  611. // linear scaling with molecule size, due to time considerations; in practice, it is probably more like 3^(n-3) or
  612. // something like that
  613. }
  614. p_3dfile = embed3D(p_2dfile, distGeomAttempts);
  615. return p_3dfile;
  616. } else {
  617. return p_2dfile;
  618. }
  619. }
  620. // embed a molecule in 3D, using RDKit
  621. public molFile embed3D(molFile twoDmolFile, int numConfAttempts) {
  622. // convert to 3D MOL file using RDKit script
  623. int flag = 0;
  624. String directory = System.getProperty("RMG.3DmolfilesDir");
  625. directory = new File(directory).getAbsolutePath(); // get the absolute path for the directory
  626. String name = twoDmolFile.getName();
  627. String rdbase = System.getenv("RDBASE");
  628. if (rdbase == null) {
  629. Logger.critical("Please set your RDBASE environment variable to the directory containing RDKit.");
  630. System.exit(0);
  631. }
  632. try {
  633. File runningdir = new File(directory);
  634. String command = "";
  635. if (System.getProperty("os.name").toLowerCase().contains("windows")) {// special windows case where paths
  636. // can have spaces and are allowed to be surrounded by quotes
  637. command = "python \""
  638. + System.getProperty("RMG.workingDirectory")
  639. + "/scripts/distGeomScriptMolLowestEnergyConf.py\" ";
  640. String twoDmolpath = twoDmolFile.getPath();
  641. command = command.concat("\"" + twoDmolpath + "\" ");
  642. command = command.concat("\"" + name + ".mol\" ");// this is the target file name; use the same name as
  643. // the twoDmolFile (but it will be in he 3Dmolfiles folder
  644. command = command.concat("\"" + name + ".cmol\" ");// this is the target file name for crude coordinates
  645. // (corresponding to the minimum energy conformation based on UFF refinement); use the same name as the twoDmolFile (but
  646. // it will be in he 3Dmolfiles folder) and have suffix .cmol
  647. command = command.concat(numConfAttempts + " ");
  648. command = command.concat("\"" + rdbase + "\"");// pass the $RDBASE environment variable
  649. // to the script so it can use the approprate directory when importing rdkit
  650. } else {// non-Windows case
  651. command = "python "
  652. + System.getProperty("RMG.workingDirectory")
  653. + "/scripts/distGeomScriptMolLowestEnergyConf.py ";
  654. String twoDmolpath = twoDmolFile.getPath();
  655. command = command.concat("" + twoDmolpath + " ");
  656. command = command.concat(name + ".mol ");// this is the target file name; use the same name as the
  657. // twoDmolFile (but it will be in he 3Dmolfiles folder
  658. command = command.concat(name + ".cmol ");// this is the target file name for crude coordinates
  659. // (corresponding to the minimum energy conformation based on UFF refinement); use the same name as the twoDmolFile (but
  660. // it will be in he 3Dmolfiles folder) and have suffix .cmol
  661. command = command.concat(numConfAttempts + " ");
  662. command = command.concat(rdbase);// pass the $RDBASE environment variable to the script
  663. // so it can use the approprate directory when importing rdkit
  664. }
  665. Process pythonProc = Runtime.getRuntime().exec(command, null,
  666. runningdir);
  667. String killmsg = "Python process for "
  668. + twoDmolFile.getName()
  669. + " did not complete within 120 seconds, and the process was killed. File was probably not written.";// message
  670. // to print if the process times out
  671. // check for errors and display the error if there is one
  672. InputStream is = pythonProc.getErrorStream();
  673. InputStreamReader isr = new InputStreamReader(is);
  674. BufferedReader br = new BufferedReader(isr);
  675. String line = null;
  676. while ((line = br.readLine()) != null) {
  677. line = line.trim();
  678. Logger.error(line);
  679. flag = 1;
  680. }
  681. // if there was an error, indicate the file and InChI
  682. if (flag == 1) {
  683. Logger.info("RDKit received error (see above) on "
  684. + twoDmolFile.getName()
  685. + ". File was probably not written.");
  686. }
  687. int exitValue = pythonProc.waitFor();
  688. pythonProc.getInputStream().close();
  689. pythonProc.getOutputStream().close();
  690. br.close();
  691. isr.close();
  692. is.close();
  693. } catch (Exception e) {
  694. Logger.logStackTrace(e);
  695. String err = "Error in running RDKit Python process \n";
  696. err += e.toString();
  697. Logger.critical(err);
  698. System.exit(0);
  699. }
  700. // construct molFile pointer to new file (name will be same as 2D mol file
  701. return new molFile(name, directory);
  702. }
  703. // creates Gaussian PM3 input file in directory with filename name.gjf by using OpenBabel to convert p_molfile
  704. // attemptNumber determines which keywords to try
  705. // the function returns the maximum number of keywords that can be attempted; this will be the same throughout the
  706. // evaluation of the code, so it may be more appropriate to have this as a "constant" attribute of some sort
  707. // attemptNumber=-1 will call a special set of keywords for the monoatomic case
  708. public int createGaussianInput(String name, String directory,
  709. molFile p_molfile, int attemptNumber, String InChIaug,
  710. int multiplicity, String qmMethod) {
  711. // write a file with the input keywords
  712. int scriptAttempts = 18;// the number of keyword permutations available; update as additional options are added
  713. int maxAttemptNumber = 2 * scriptAttempts;// we will try a second time with crude coordinates if the UFF refined
  714. // coordinates do not work
  715. // String inpKeyStr="%chk="+directory+"/RMGrunCHKfile.chk\n";//by not specifying a filename, Gaussian will
  716. // create its own pseudo-random filename, and it will be deleted at the conclusion of a successful calculation; an
  717. // alternative would be to specify a checkpoint filename based on the modified InChIKey... however, it wouldn't be
  718. // automatically deleted after the completion of the calculation
  719. String inpKeyStr = "%mem=6MW\n";
  720. inpKeyStr += "%nproc=1\n";
  721. if (attemptNumber == -1)
  722. inpKeyStr += "# " + qmMethod + " freq";// keywords for monoatomic case (still need to use freq keyword to get molecular
  723. // mass)
  724. else if (attemptNumber % scriptAttempts == 1)
  725. inpKeyStr += "# " + qmMethod + " opt=(verytight,gdiis) freq IOP(2/16=3)";// added IOP option to avoid aborting when
  726. // symmetry changes; 3 is supposed to be default according to documentation, but it seems that 0 (the default) is the
  727. // only option that doesn't work from 0-4; also, it is interesting to note that all 4 options seem to work for test case
  728. // with z-matrix input rather than xyz coords; cf. http://www.ccl.net/cgi-bin/ccl/message-new?2006+10+17+005 for
  729. // original idea for solution
  730. else if (attemptNumber % scriptAttempts == 2)
  731. inpKeyStr += "# " + qmMethod + " opt=(verytight,gdiis) freq IOP(2/16=3) IOP(4/21=2)";// use different SCF method; this
  732. // addresses at least one case of failure for a C4H7J species
  733. else if (attemptNumber % scriptAttempts == 3)
  734. inpKeyStr += "# " + qmMethod + " opt=(verytight,calcfc,maxcyc=200) freq IOP(2/16=3) nosymm";// try multiple different
  735. // options (no gdiis, use calcfc, nosymm); 7/21/09: added maxcyc option to fix case of MPTBUKVAJYJXDE-UHFFFAOYAPmult3
  736. // (InChI=1/C4H10O5Si/c1-3-7-9-10(5,6)8-4-2/h4-5H,3H2,1-2H3/mult3) (file manually copied to speed things along)
  737. else if (attemptNumber % scriptAttempts == 4)
  738. inpKeyStr += "# " + qmMethod + " opt=(verytight,calcfc,maxcyc=200) freq=numerical IOP(2/16=3) nosymm";// 7/8/09:
  739. // numerical frequency keyword version of keyword #3; used to address GYFVJYRUZAKGFA-UHFFFAOYALmult3
  740. // (InChI=1/C6H14O6Si/c1-3-10-13(8,11-4-2)12-6-5-9-7/h6-7H,3-5H2,1-2H3/mult3) case; (none of the existing Gaussian or
  741. // MOPAC combinations worked with it)
  742. else if (attemptNumber % scriptAttempts == 5)
  743. inpKeyStr += "# " + qmMethod + " opt=(verytight,gdiis,small) freq IOP(2/16=3)";// 7/10/09: somehow, this worked for
  744. // problematic case of ZGAWAHRALACNPM-UHFFFAOYAF
  745. // (InChI=1/C8H17O5Si/c1-3-11-14(10,12-4-2)13-8-5-7(9)6-8/h7-9H,3-6H2,1-2H3); (was otherwise giving l402 errors); even
  746. // though I had a keyword that worked for this case, I manually copied the fixed log file to QMfiles folder to speed
  747. // things along; note that there are a couple of very low frequencies (~5-6 cm^-1 for this case)
  748. else if (attemptNumber % scriptAttempts == 6)
  749. inpKeyStr += "# " + qmMethod + " opt=(verytight,nolinear,calcfc,small) freq IOP(2/16=3)";// used for troublesome C5H7J2
  750. // case (similar error to C5H7J below); calcfc is not necessary for this particular species, but it speeds convergence
  751. // and probably makes it more robust for other species
  752. else if (attemptNumber % scriptAttempts == 7)
  753. inpKeyStr += "# " + qmMethod + " opt=(verytight,gdiis,maxcyc=200) freq=numerical IOP(2/16=3)"; // use numerical
  754. // frequencies; this takes a relatively long time, so should only be used as one of the last resorts; this seemed to
  755. // address at least one case of failure for a C6H10JJ species; 7/15/09: maxcyc=200 added to address
  756. // GVCMURUDAUQXEY-UHFFFAOYAVmult3 (InChI=1/C3H4O7Si/c1-2(9-6)10-11(7,8)3(4)5/h6-7H,1H2/mult3)...however, result was
  757. // manually pasted in QMfiles folder to speed things along
  758. else if (attemptNumber % scriptAttempts == 8)
  759. inpKeyStr += "# " + qmMethod + " opt=tight freq IOP(2/16=3)";// 7/10/09: this worked for problematic case of
  760. // SZSSHFMXPBKYPR-UHFFFAOYAF (InChI=1/C7H15O5Si/c1-3-10-13(8,11-4-2)12-7-5-6-9-7/h7H,3-6H2,1-2H3) (otherwise, it had
  761. // l402.exe errors); corrected log file was manually copied to QMfiles to speed things along; we could also add a
  762. // freq=numerical version of this keyword combination for added robustness; UPDATE: see below
  763. else if (attemptNumber % scriptAttempts == 9)
  764. inpKeyStr += "# " + qmMethod + " opt=tight freq=numerical IOP(2/16=3)";// 7/10/09: used for problematic case of
  765. // CIKDVMUGTARZCK-UHFFFAOYAImult4 (InChI=1/C8H15O6Si/c1-4-12-15(10,13-5-2)14-7-6-11-8(7,3)9/h7H,3-6H2,1-2H3/mult4 (most
  766. // other cases had l402.exe errors); corrected log file was manually copied to QMfiles to speed things along
  767. else if (attemptNumber % scriptAttempts == 10)
  768. inpKeyStr += "# " + qmMethod + " opt=(tight,nolinear,calcfc,small,maxcyc=200) freq IOP(2/16=3)";// 7/8/09: similar to
  769. // existing #5, but uses tight rather than verytight; used for ADMPQLGIEMRGAT-UHFFFAOYAUmult3
  770. // (InChI=1/C6H14O5Si/c1-4-9-12(8,10-5-2)11-6(3)7/h6-7H,3-5H2,1-2H3/mult3)
  771. else if (attemptNumber % scriptAttempts == 11)
  772. inpKeyStr += "# " + qmMethod + " opt freq IOP(2/16=3)"; // use default (not verytight) convergence criteria; use this as
  773. // last resort
  774. else if (attemptNumber % scriptAttempts == 12)
  775. inpKeyStr += "# " + qmMethod + " opt=(verytight,gdiis) freq=numerical IOP(2/16=3) IOP(4/21=200)";// to address
  776. // problematic C10H14JJ case
  777. else if (attemptNumber % scriptAttempts == 13)
  778. inpKeyStr += "# " + qmMethod + " opt=(calcfc,verytight,newton,notrustupdate,small,maxcyc=100,maxstep=100) freq=(numerical,step=10) IOP(2/16=3) nosymm";// added
  779. // 6/10/09 for very troublesome RRMZRNPRCUANER-UHFFFAOYAQ (InChI=1/C5H7/c1-3-5-4-2/h3H,1-2H3) case...there were troubles
  780. // with negative frequencies, where I don't think they should have been; step size of numerical frequency was adjusted
  781. // to give positive result; accuracy of result is questionable; it is possible that not all of these keywords are
  782. // needed; note that for this and other nearly free rotor cases, I think heat capacity will be overestimated by R/2 (R
  783. // vs. R/2) (but this is a separate issue)
  784. else if (attemptNumber % scriptAttempts == 14)
  785. inpKeyStr += "# " + qmMethod + " opt=(tight,gdiis,small,maxcyc=200,maxstep=100) freq=numerical IOP(2/16=3) nosymm";// added
  786. // 6/22/09 for troublesome
  787. // QDERTVAGQZYPHT-UHFFFAOYAHmult3(InChI=1/C6H14O4Si/c1-4-8-11(7,9-5-2)10-6-3/h4H,5-6H2,1-3H3/mult3); key aspects appear
  788. // to be tight (rather than verytight) convergence criteria, no calculation of frequencies during optimization, use of
  789. // numerical frequencies, and probably also the use of opt=small
  790. // gmagoon 7/9/09: commented out since although this produces a "reasonable" result for the problematic case,
  791. // there is a large amount of spin contamination, apparently introducing 70+ kcal/mol of instability else
  792. // if(attemptNumber==12)
  793. // inpKeyStr+="# pm3 opt=(verytight,gdiis,small) freq=numerical IOP(2/16=3) IOP(4/21=200)";//7/9/09: similar to current
  794. // number 9 with keyword small; this addresses case of VCSJVABXVCFDRA-UHFFFAOYAI
  795. // (InChI=1/C8H19O5Si/c1-5-10-8(4)13-14(9,11-6-2)12-7-3/h8H,5-7H2,1-4H3)
  796. else if (attemptNumber % scriptAttempts == 15)
  797. inpKeyStr += "# " + qmMethod + " opt=(verytight,gdiis,calcall) IOP(2/16=3)";// used for troublesome C5H7J case; note that
  798. // before fixing, I got errors like the following:
  799. // "Incomplete coordinate system. Try restarting with Geom=Check Guess=Read Opt=(ReadFC,NewRedundant) Incomplete coordinate system. Error termination via Lnk1e in l103.exe";
  800. // we could try to restart, but it is probably preferrable to have each keyword combination standalone; another keyword
  801. // that may be helpful if additional problematic cases are encountered is opt=small; 6/9/09 note: originally, this had #
  802. // pm3 opt=(verytight,gdiis,calcall) freq IOP(2/16=3)" (with freq keyword), but I discovered that in this case, there
  803. // are two thermochemistry sections and cclib parses frequencies twice, giving twice the number of desired frequencies
  804. // and hence produces incorrect thermo; this turned up on C5H6JJ isomer
  805. // gmagoon 7/3/09: it is probably best to retire this keyword combination in light of the similar combination below
  806. // //else if(attemptNumber==6) inpKeyStr+="# pm3 opt=(verytight,gdiis,calcall,small) IOP(2/16=3) IOP(4/21=2)";//6/10/09:
  807. // worked for OJZYSFFHCAPVGA-UHFFFAOYAK (InChI=1/C5H7/c1-3-5-4-2/h1,4H2,2H3) case; IOP(4/21) keyword was key
  808. else if (attemptNumber % scriptAttempts == 16)
  809. inpKeyStr += "# " + qmMethod + " opt=(verytight,gdiis,calcall,small,maxcyc=200) IOP(2/16=3) IOP(4/21=2) nosymm";// 6/29/09:
  810. // worked for troublesome ketene case: CCGKOQOJPYTBIH-UHFFFAOYAO (InChI=1/C2H2O/c1-2-3/h1H2) (could just increase number
  811. // of iterations for similar keyword combination above (#6 at the time of this writing), allowing symmetry, but nosymm
  812. // seemed to reduce # of iterations; I think one of nosymm or higher number of iterations would allow the similar
  813. // keyword combination to converge; both are included here for robustness)
  814. else if (attemptNumber % scriptAttempts == 17)
  815. inpKeyStr += "# " + qmMethod + " opt=(verytight,gdiis,calcall,small) IOP(2/16=3) nosymm";// 7/1/09: added for case of
  816. // ZWMVZWMBTVHPBS-UHFFFAOYAEmult3 (InChI=1/C4H4O2/c1-3-5-6-4-2/h1-2H2/mult3)
  817. else if (attemptNumber % scriptAttempts == 0)
  818. inpKeyStr += "# " + qmMethod + " opt=(calcall,small,maxcyc=100) IOP(2/16=3)"; // 6/10/09: used to address troublesome
  819. // FILUFGAZMJGNEN-UHFFFAOYAImult3 case (InChI=1/C5H6/c1-3-5-4-2/h3H,1H2,2H3/mult3)
  820. else {// this point should not be reached
  821. Logger.error("Unexpected error in determining which G03 PM3 keyword set to use");
  822. System.exit(0);
  823. }
  824. // if(m…

Large files files are truncated, but you can click here to view the full file