From 82d48719d584e21313bf78089000bd7d35907aae Mon Sep 17 00:00:00 2001 From: alongd Date: Fri, 12 Jul 2019 09:17:17 -0400 Subject: [PATCH 1/4] Minor: Changes file `open`/`close` to `with open()` in Arkane Thermo --- arkane/thermo.py | 93 ++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/arkane/thermo.py b/arkane/thermo.py index 797dad5196..a12dfb3cdf 100644 --- a/arkane/thermo.py +++ b/arkane/thermo.py @@ -155,54 +155,61 @@ def save(self, outputFile): species = self.species logging.info('Saving thermo for {0}...'.format(species.label)) - f = open(outputFile, 'a') - - f.write('# Thermodynamics for {0}:\n'.format(species.label)) - H298 = species.getThermoData().getEnthalpy(298) / 4184. - S298 = species.getThermoData().getEntropy(298) / 4.184 - f.write('# Enthalpy of formation (298 K) = {0:9.3f} kcal/mol\n'.format(H298)) - f.write('# Entropy of formation (298 K) = {0:9.3f} cal/(mol*K)\n'.format(S298)) - f.write('# =========== =========== =========== =========== ===========\n') - f.write('# Temperature Heat cap. Enthalpy Entropy Free energy\n') - f.write('# (K) (cal/mol*K) (kcal/mol) (cal/mol*K) (kcal/mol)\n') - f.write('# =========== =========== =========== =========== ===========\n') - for T in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]: - try: - Cp = species.getThermoData().getHeatCapacity(T) / 4.184 - H = species.getThermoData().getEnthalpy(T) / 4184. - S = species.getThermoData().getEntropy(T) / 4.184 - G = species.getThermoData().getFreeEnergy(T) / 4184. - f.write('# {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n'.format(T, Cp, H, S, G)) - except ValueError: - logging.debug("Valid thermo for {0} is outside range for temperature {1}".format(species, T)) - f.write('# =========== =========== =========== =========== ===========\n') - - thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format(species.label, species.getThermoData()) - f.write('{0}\n\n'.format(prettify(thermo_string))) - - f.close() - # write chemkin file - f = open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a') - if isinstance(species, Species): - if species.molecule and isinstance(species.molecule[0], Molecule): - elementCounts = retrieveElementCount(species.molecule[0]) - else: + with open(outputFile, 'a') as f: + f.write('# Thermodynamics for {0}:\n'.format(species.label)) + H298 = species.getThermoData().getEnthalpy(298) / 4184. + S298 = species.getThermoData().getEntropy(298) / 4.184 + f.write('# Enthalpy of formation (298 K) = {0:9.3f} kcal/mol\n'.format(H298)) + f.write('# Entropy of formation (298 K) = {0:9.3f} cal/(mol*K)\n'.format(S298)) + f.write('# =========== =========== =========== =========== ===========\n') + f.write('# Temperature Heat cap. Enthalpy Entropy Free energy\n') + f.write('# (K) (cal/mol*K) (kcal/mol) (cal/mol*K) (kcal/mol)\n') + f.write('# =========== =========== =========== =========== ===========\n') + for T in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]: try: - elementCounts = species.props['elementCounts'] - except KeyError: - elementCounts = {'C': 0, 'H': 0} - else: - elementCounts = {'C': 0, 'H': 0} - chemkin_thermo_string = writeThermoEntry(species, elementCounts=elementCounts, verbose=True) - f.write('{0}\n'.format(chemkin_thermo_string)) - f.close() + Cp = species.getThermoData().getHeatCapacity(T) / 4.184 + H = species.getThermoData().getEnthalpy(T) / 4184. + S = species.getThermoData().getEntropy(T) / 4.184 + G = species.getThermoData().getFreeEnergy(T) / 4184. + f.write('# {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n'.format(T, Cp, H, S, G)) + except ValueError: + logging.debug("Valid thermo for {0} is outside range for temperature {1}".format(species, T)) + f.write('# =========== =========== =========== =========== ===========\n') + + thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format(species.label, species.getThermoData()) + f.write('{0}\n\n'.format(prettify(thermo_string))) + + # write Chemkin file + with open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a') as f: + if isinstance(species, Species): + if species.molecule and isinstance(species.molecule[0], Molecule): + element_count = retrieveElementCount(species.molecule[0]) + else: + try: + element_count = species.props['element_count'] + except KeyError: + element_count = {'C': 0, 'H': 0} + else: + element_count = {'C': 0, 'H': 0} + chemkin_thermo_string = writeThermoEntry(species, element_count=element_count, verbose=True) + f.write('{0}\n'.format(chemkin_thermo_string)) # write species dictionary if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): - with open(os.path.join(os.path.dirname(outputFile), 'species_dictionary.txt'), 'a') as f: - f.write(species.molecule[0].toAdjacencyList(removeH=False, label=species.label)) - f.write('\n') + spec_dict_path = os.path.join(os.path.dirname(outputFile), 'species_dictionary.txt') + is_species_in_dict = False + if os.path.isfile(spec_dict_path): + with open(spec_dict_path, 'r') as f: + # check whether the species dictionary contains this species, in which case do not re-append + for line in f.readlines(): + if species.label == line.strip(): + is_species_in_dict = True + break + if not is_species_in_dict: + with open(spec_dict_path, 'a') as f: + f.write(species.molecule[0].toAdjacencyList(removeH=False, label=species.label)) + f.write('\n') return chemkin_thermo_string def plot(self, outputDirectory): From 271b2e92c17a7a8724a9cd1714cc1c95c86ef868 Mon Sep 17 00:00:00 2001 From: alongd Date: Fri, 12 Jul 2019 09:18:07 -0400 Subject: [PATCH 2/4] Get element count for Chemlkin from conformer in Arkane ThermoJob --- arkane/thermo.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/arkane/thermo.py b/arkane/thermo.py index a12dfb3cdf..e26f1654dc 100644 --- a/arkane/thermo.py +++ b/arkane/thermo.py @@ -53,7 +53,7 @@ from rmgpy.molecule.util import retrieveElementCount from arkane.output import prettify -from arkane.common import ArkaneSpecies +from arkane.common import ArkaneSpecies, symbol_by_number ################################################################################ @@ -183,15 +183,15 @@ def save(self, outputFile): with open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a') as f: if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): - element_count = retrieveElementCount(species.molecule[0]) + element_counts = retrieveElementCount(species.molecule[0]) else: try: - element_count = species.props['element_count'] + element_counts = species.props['element_counts'] except KeyError: - element_count = {'C': 0, 'H': 0} + element_counts = self.element_count_from_conformer() else: - element_count = {'C': 0, 'H': 0} - chemkin_thermo_string = writeThermoEntry(species, element_count=element_count, verbose=True) + element_counts = {'C': 0, 'H': 0} + chemkin_thermo_string = writeThermoEntry(species, elementCounts=element_counts, verbose=True) f.write('{0}\n'.format(chemkin_thermo_string)) # write species dictionary @@ -212,6 +212,23 @@ def save(self, outputFile): f.write('\n') return chemkin_thermo_string + def element_count_from_conformer(self): + """ + Get an element count in a dictionary form (e.g., {'C': 3, 'H': 8}) from the species.conformer attribute. + + Returns: + dict: Element count, keys are element symbols, + values are number of occurrences of the element in the molecule. + """ + element_counts = dict() + for number in self.species.conformer.number.value_si: + symbol = symbol_by_number[number] + if symbol in element_counts: + element_counts[symbol] += 1 + else: + element_counts[symbol] = 1 + return element_counts + def plot(self, outputDirectory): """ Plot the heat capacity, enthapy, entropy, and Gibbs free energy of the From 2e52dd5d8e661c581bd2db6aeeb94d1eca1b3e7a Mon Sep 17 00:00:00 2001 From: alongd Date: Fri, 12 Jul 2019 09:18:44 -0400 Subject: [PATCH 3/4] Tests: Added Arkane thermoTest For now only tests the element_count_from_conformer() method, should be expanded. --- arkane/thermoTest.py | 73 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 arkane/thermoTest.py diff --git a/arkane/thermoTest.py b/arkane/thermoTest.py new file mode 100644 index 0000000000..862257e348 --- /dev/null +++ b/arkane/thermoTest.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# # +# RMG - Reaction Mechanism Generator # +# # +# Copyright (c) 2002-2019 Prof. William H. Green (whgreen@mit.edu), # +# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # +# # +# Permission is hereby granted, free of charge, to any person obtaining a # +# copy of this software and associated documentation files (the 'Software'), # +# to deal in the Software without restriction, including without limitation # +# the rights to use, copy, modify, merge, publish, distribute, sublicense, # +# and/or sell copies of the Software, and to permit persons to whom the # +# Software is furnished to do so, subject to the following conditions: # +# # +# The above copyright notice and this permission notice shall be included in # +# all copies or substantial portions of the Software. # +# # +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # +# DEALINGS IN THE SOFTWARE. # +# # +############################################################################### + +""" +This script contains unit tests of the :mod:`arkane.thermo` module. +""" + +import unittest +import os + +from rmgpy.species import Species + +from arkane.gaussian import GaussianLog +from arkane.thermo import ThermoJob + +################################################################################ + + +class TestThermo(unittest.TestCase): + """ + Contains unit tests of the ThermoJob class. + """ + @classmethod + def setUp(cls): + """A method that is run before each unit test in this class""" + spc = Species().fromSMILES('CCO') + log = GaussianLog(os.path.join(os.path.dirname(__file__), 'data', 'ethylene.log')) + spc.conformer = log.loadConformer()[0] + coords, numbers, masses = log.loadGeometry() + spc.conformer.coordinates = coords, 'angstroms' + spc.conformer.number = numbers + spc.conformer.mass = masses, 'amu' + cls.thermo_job = ThermoJob(species=spc, thermoClass='NASA') + + def test_element_count_from_conformer(self): + """Test Getting an element count dictionary from the species.conformer attribute""" + element_count = self.thermo_job.element_count_from_conformer() + self.assertEqual(element_count, {'H': 4, 'C': 2}) + + + +################################################################################ + + +if __name__ == '__main__': + unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) From af10c4f551d1eb2191c6b3b83ad8a4ff9077ce00 Mon Sep 17 00:00:00 2001 From: alongd Date: Sun, 21 Jul 2019 13:57:53 -0400 Subject: [PATCH 4/4] Renamed elementCounts as element_counts THe Species.props['element_counts'] is only used in Arkane. renamed elementCounts to element_counts to be consistent with prior changes --- arkane/commonTest.py | 6 +++--- arkane/statmech.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/arkane/commonTest.py b/arkane/commonTest.py index e0de022492..42efbab977 100644 --- a/arkane/commonTest.py +++ b/arkane/commonTest.py @@ -249,9 +249,9 @@ def testSpeciesStatmech(self): job.includeHinderedRotors = self.useHinderedRotors job.applyBondEnergyCorrections = self.useBondCorrections job.load() - self.assertTrue(isinstance(job.species.props['elementCounts'], dict)) - self.assertEqual(job.species.props['elementCounts']['C'], 2) - self.assertEqual(job.species.props['elementCounts']['H'], 4) + self.assertTrue(isinstance(job.species.props['element_counts'], dict)) + self.assertEqual(job.species.props['element_counts']['C'], 2) + self.assertEqual(job.species.props['element_counts']['H'], 4) def testSpeciesThermo(self): """Test thermo job execution for species from separate input file.""" diff --git a/arkane/statmech.py b/arkane/statmech.py index 6a0da431c8..dc9c840f4f 100644 --- a/arkane/statmech.py +++ b/arkane/statmech.py @@ -399,7 +399,7 @@ def load(self, pdep=False): # Save atoms for use in writing thermo output if isinstance(self.species, Species): - self.species.props['elementCounts'] = atoms + self.species.props['element_counts'] = atoms conformer.coordinates = (coordinates, "angstroms") conformer.number = number