Skip to content

Commit

Permalink
Merge pull request #1643 from ReactionMechanismGenerator/recursive_as…
Browse files Browse the repository at this point in the history
…_dictionary

Recursive as dictionary
  • Loading branch information
alongd authored Jul 18, 2019
2 parents 7c1f46c + 13b1cb6 commit 2a4bef9
Show file tree
Hide file tree
Showing 19 changed files with 672 additions and 226 deletions.
75 changes: 50 additions & 25 deletions arkane/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
from rmgpy.statmech.vibration import HarmonicOscillator
from rmgpy.pdep.collision import SingleExponentialDown
from rmgpy.transport import TransportData
from rmgpy.thermo import NASA, Wilhoit
from rmgpy.thermo import NASA, Wilhoit, ThermoData, NASAPolynomial
from rmgpy.species import Species, TransitionState
import rmgpy.constants as constants

Expand Down Expand Up @@ -180,16 +180,19 @@ def update_species_attributes(self, species=None):
self.energy_transfer_model = species.energyTransferModel
if species.thermo is not None:
self.thermo = species.thermo.as_dict()
thermo_data = species.getThermoData()
h298 = thermo_data.getEnthalpy(298) / 4184.
s298 = thermo_data.getEntropy(298) / 4.184
cp = dict()
for t in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]:
temp_str = '{0} K'.format(t)
cp[temp_str] = '{0:.2f}'.format(thermo_data.getHeatCapacity(t) / 4.184)
self.thermo_data = {'H298': '{0:.2f} kcal/mol'.format(h298),
'S298': '{0:.2f} cal/mol*K'.format(s298),
'Cp (cal/mol*K)': cp}
data = species.getThermoData()
h298 = data.getEnthalpy(298) / 4184.
s298 = data.getEntropy(298) / 4.184
temperatures = numpy.array([300, 400, 500, 600, 800, 1000, 1500, 2000, 2400])
cp = []
for t in temperatures:
cp.append(data.getHeatCapacity(t) / 4.184)

self.thermo_data = ThermoData(H298=(h298, 'kcal/mol'),
S298=(s298, 'cal/(mol*K)'),
Tdata=(temperatures, 'K'),
Cpdata=(cp, 'cal/(mol*K)'),
)

def update_xyz_string(self):
"""
Expand Down Expand Up @@ -228,21 +231,30 @@ def save_yaml(self, path):
yaml.dump(data=self.as_dict(), stream=f)
logging.debug('Dumping species {0} data as {1}'.format(self.label, filename))

def load_yaml(self, path, species, pdep=False):
def load_yaml(self, path, label=None, pdep=False):
"""
Load the all statMech data from the .yml file in `path` into `species`
`pdep` is a boolean specifying whether or not jobList includes a pressureDependentJob.
"""
logging.info('Loading statistical mechanics parameters for {0} from .yml file...'.format(species.label))
yml_file = os.path.basename(path)
if label:
logging.info('Loading statistical mechanics parameters for {0} from {1} file...'.format(label, yml_file))
else:
logging.info('Loading statistical mechanics parameters from {0} file...'.format(yml_file))
with open(path, 'r') as f:
data = yaml.safe_load(stream=f)
try:
if species.label != data['label']:
logging.debug('Found different labels for species: {0} in input file, and {1} in the .yml file. '
'Using the label "{0}" for this species.'.format(species.label, data['label']))
except KeyError:
# Lacking label in the YAML file is strange, but accepted
logging.debug('Did not find label for species {0} in .yml file.'.format(species.label))
if label:
# First, warn the user if the label doesn't match
try:
if label != data['label']:
logging.debug('Found different labels for species: {0} in input file, and {1} in the .yml file. '
'Using the label "{0}" for this species.'.format(label, data['label']))
except KeyError:
# Lacking label in the YAML file is strange, but accepted
logging.debug('Did not find label for species {0} in .yml file.'.format(label))

# Then, set the ArkaneSpecies label to the user supplied label
data['label'] = label
try:
class_name = data['class']
except KeyError:
Expand All @@ -265,19 +277,32 @@ def load_yaml(self, path, species, pdep=False):
'SingleExponentialDown': SingleExponentialDown,
'Wilhoit': Wilhoit,
'NASA': NASA,
'NASAPolynomial': NASAPolynomial,
'ThermoData': ThermoData,
'np_array': numpy.array,
}
freq_data = None
if 'imaginary_frequency' in data:
freq_data = data['imaginary_frequency']
del data['imaginary_frequency']
if not data['is_ts']:
if 'smiles' in data:
data['species'] = Species(SMILES=data['smiles'])
elif 'adjacency_list' in data:
data['species'] = Species().fromAdjacencyList(data['adjacency_list'])
elif 'inchi' in data:
data['species'] = Species(InChI=data['inchi'])
else:
raise ValueError('Cannot load ArkaneSpecies from YAML file {0}. Either `smiles`, `adjacency_list`, or '
'InChI must be specified'.format(path))
# Finally, set the species label so that the special attributes are updated properly
data['species'].label = data['label']

self.make_object(data=data, class_dict=class_dict)
if freq_data is not None:
self.imaginary_frequency = ScalarQuantity()
self.imaginary_frequency.make_object(data=freq_data, class_dict=dict())
self.adjacency_list = data['adjacency_list'] if 'adjacency_list' in data else None
self.inchi = data['inchi'] if 'inchi' in data else None
self.smiles = data['smiles'] if 'smiles' in data else None
self.is_ts = data['is_ts'] if 'is_ts' in data else False
self.imaginary_frequency.make_object(data=freq_data, class_dict=class_dict)

if pdep and not self.is_ts and (self.transport_data is None or self.energy_transfer_model is None):
raise ValueError('Transport data and an energy transfer model must be given if pressure-dependent '
'calculations are requested. Check file {0}'.format(path))
Expand Down
51 changes: 46 additions & 5 deletions arkane/commonTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@

import rmgpy
import rmgpy.constants as constants
from rmgpy.pdep.collision import SingleExponentialDown
from rmgpy.species import Species, TransitionState
from rmgpy.quantity import ScalarQuantity
from rmgpy.thermo import NASA
from rmgpy.thermo import NASA, ThermoData

from arkane import Arkane, input
from arkane.common import ArkaneSpecies, get_element_mass
Expand Down Expand Up @@ -334,25 +335,65 @@ def test_dump_yaml(self):
job.execute(outputFile=self.dump_output_file)
self.assertTrue(os.path.isfile(self.dump_yaml_file))

def test_load_yaml(self):
def test_create_and_load_yaml(self):
"""
Test properly loading the ArkaneSpecies object and respective sub-objects
"""
jobList = self.arkane.loadInputFile(self.load_input_path)
# Create YAML file by running Arkane
jobList = self.arkane.loadInputFile(self.dump_input_path)
for job in jobList:
job.execute(outputFile=self.load_output_file)
arkane_spc = jobList[0].arkane_species
job.execute(outputFile=self.dump_output_file)

# Load in newly created YAML file
arkane_spc_old = jobList[0].arkane_species
arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies)
arkane_spc.load_yaml(path=os.path.join(self.dump_path, 'species', arkane_spc_old.label + '.yml'))

self.assertIsInstance(arkane_spc, ArkaneSpecies) # checks make_object
self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity)
self.assertIsInstance(arkane_spc.thermo, NASA)
self.assertNotEqual(arkane_spc.author, '')
self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3')
self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N')
self.assertEqual(arkane_spc.smiles, 'CC')
self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list)
self.assertEqual(arkane_spc.label, 'C2H6')
self.assertEqual(arkane_spc.frequency_scale_factor, 1.00386) # checks float conversion
self.assertFalse(arkane_spc.use_bond_corrections)
self.assertAlmostEqual(arkane_spc.conformer.modes[2].frequencies.value_si[0], 830.38202, 4) # HarmonicOsc.
self.assertIsInstance(arkane_spc.energy_transfer_model, SingleExponentialDown)
self.assertFalse(arkane_spc.is_ts)
self.assertEqual(arkane_spc.level_of_theory, 'cbs-qb3')
self.assertIsInstance(arkane_spc.thermo_data, ThermoData)
self.assertTrue(arkane_spc.use_hindered_rotors)
self.assertTrue('C 7.54e-14 1.193e-13 5.52e-14' in arkane_spc.xyz)
self.assertIsInstance(arkane_spc.chemkin_thermo_string, str)

def test_load_existing_yaml(self):
"""
Test that existing Arkane YAML files can still be loaded
"""
# Load in YAML file
arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies)
arkane_spc.load_yaml(path=os.path.join(self.load_path, 'C2H6.yml'))

self.assertIsInstance(arkane_spc, ArkaneSpecies) # checks make_object
self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity)
self.assertIsInstance(arkane_spc.thermo, NASA)
self.assertNotEqual(arkane_spc.author, '')
self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3')
self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N')
self.assertEqual(arkane_spc.smiles, 'CC')
self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list)
self.assertEqual(arkane_spc.label, 'C2H6')
self.assertEqual(arkane_spc.frequency_scale_factor, 0.99) # checks float conversion
self.assertFalse(arkane_spc.use_bond_corrections)
self.assertAlmostEqual(arkane_spc.conformer.modes[2].frequencies.value_si[0], 818.91718, 4) # HarmonicOsc.
self.assertIsInstance(arkane_spc.energy_transfer_model, SingleExponentialDown)
self.assertFalse(arkane_spc.is_ts)
self.assertTrue(arkane_spc.use_hindered_rotors)
self.assertTrue('C 7.54e-14 1.193e-13 5.52e-14' in arkane_spc.xyz)
self.assertIsInstance(arkane_spc.chemkin_thermo_string, str)

@classmethod
def tearDownClass(cls):
Expand Down
2 changes: 1 addition & 1 deletion arkane/statmech.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def load(self, pdep=False):
is_ts = isinstance(self.species, TransitionState)
_, file_extension = os.path.splitext(path)
if file_extension in ['.yml', '.yaml']:
self.arkane_species.load_yaml(path=path, species=self.species, pdep=pdep)
self.arkane_species.load_yaml(path=path, label=self.species.label, pdep=pdep)
self.species.conformer = self.arkane_species.conformer
if is_ts:
self.species.frequency = self.arkane_species.imaginary_frequency
Expand Down
8 changes: 3 additions & 5 deletions rmgpy/quantity.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ cimport numpy

cpdef list NOT_IMPLEMENTED_UNITS

from rmgpy.rmgobject cimport RMGObject

################################################################################

cdef class Units(object):
cdef class Units(RMGObject):

cdef public str units

Expand All @@ -51,8 +53,6 @@ cdef class ScalarQuantity(Units):

cpdef dict as_dict(self)

cpdef make_object(self, dict data, dict class_dict)

cpdef str getUncertaintyType(self)
cpdef setUncertaintyType(self, str v)

Expand All @@ -72,8 +72,6 @@ cdef class ArrayQuantity(Units):

cpdef dict as_dict(self)

cpdef make_object(self, dict data, dict class_dict)

cpdef str getUncertaintyType(self)
cpdef setUncertaintyType(self, str v)

Expand Down
33 changes: 4 additions & 29 deletions rmgpy/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

import rmgpy.constants as constants
from rmgpy.exceptions import QuantityError
from rmgpy.rmgobject import RMGObject, expand_to_dict

################################################################################

Expand Down Expand Up @@ -70,7 +71,7 @@

################################################################################

class Units(object):
class Units(RMGObject):
"""
The :class:`Units` class provides a representation of the units of a
physical quantity. The attributes are:
Expand Down Expand Up @@ -227,19 +228,6 @@ def as_dict(self):
output_dict['uncertainty'] = self.uncertainty
output_dict['uncertaintyType'] = self.uncertaintyType
return output_dict

def make_object(self, data, class_dict):
"""
A helper function for YAML parsing
"""
# the `class_dict` parameter isn't used here, it is passed by default when calling the `make_object()` methods
if 'units' in data:
self.units = data['units']
self.value = data['value']
if 'uncertaintyType' in data:
self.uncertaintyType = data['uncertaintyType']
if 'uncertainty' in data:
self.uncertainty = data['uncertainty']

def copy(self):
"""
Expand Down Expand Up @@ -461,29 +449,16 @@ def as_dict(self):
"""
output_dict = dict()
output_dict['class'] = self.__class__.__name__
output_dict['value'] = self.value.tolist()
output_dict['value'] = expand_to_dict(self.value)
if self.units != '':
output_dict['units'] = self.units
if self.uncertainty is not None and any([val != 0.0 for val in numpy.nditer(self.uncertainty)]):
logging.info(self.uncertainty)
logging.info(type(self.uncertainty))
output_dict['uncertainty'] = self.uncertainty.tolist()
output_dict['uncertainty'] = expand_to_dict(self.uncertainty)
output_dict['uncertaintyType'] = self.uncertaintyType
return output_dict

def make_object(self, data, class_dict):
"""
A helper function for YAML parsing
"""
# the `class_dict` parameter isn't used here, it is passed by default when calling the `make_object()` methods
if 'units' in data:
self.units = data['units']
self.value = data['value']
if 'uncertaintyType' in data:
self.uncertaintyType = data['uncertaintyType']
if 'uncertainty' in data:
self.uncertainty = data['uncertainty']

def copy(self):
"""
Return a copy of the quantity.
Expand Down
Loading

0 comments on commit 2a4bef9

Please sign in to comment.