Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load and dump TS YAML files in Arkane #1551

Merged
merged 15 commits into from Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
91 changes: 61 additions & 30 deletions arkane/common.py
Expand Up @@ -35,10 +35,6 @@
import string

import yaml
try:
from yaml import CDumper as Dumper, CLoader as Loader, CSafeLoader as SafeLoader
except ImportError:
from yaml import Dumper, Loader, SafeLoader

from rmgpy.rmgobject import RMGObject
from rmgpy import __version__
Expand All @@ -53,13 +49,24 @@
from rmgpy.pdep.collision import SingleExponentialDown
from rmgpy.transport import TransportData
from rmgpy.thermo import NASA, Wilhoit
from rmgpy.species import Species, TransitionState
import rmgpy.constants as constants

from arkane.pdep import PressureDependenceJob

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


# Add a custom string representer to use block literals for multiline strings
def str_repr(dumper, data):
if len(data.splitlines()) > 1:
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
return dumper.represent_scalar('tag:yaml.org,2002:str', data)


yaml.add_representer(str, str_repr)


class ArkaneSpecies(RMGObject):
"""
A class for archiving an Arkane species including its statmech data into .yml files
Expand All @@ -68,11 +75,14 @@ def __init__(self, species=None, conformer=None, author='', level_of_theory='',
frequency_scale_factor=None, use_hindered_rotors=None, use_bond_corrections=None, atom_energies='',
chemkin_thermo_string='', smiles=None, adjacency_list=None, inchi=None, inchi_key=None, xyz=None,
molecular_weight=None, symmetry_number=None, transport_data=None, energy_transfer_model=None,
thermo=None, thermo_data=None, label=None, datetime=None, RMG_version=None):
thermo=None, thermo_data=None, label=None, datetime=None, RMG_version=None, reactants=None,
products=None, reaction_label=None, is_ts=None):
# reactants/products/reaction_label need to be in the init() to avoid error when loading a TS YAML file,
# but we don't use them
if species is None and conformer is None:
# Expecting to get a `species` when generating the object within Arkane,
# or a `conformer` when parsing from YAML.
raise ValueError('No species or conformer was passed to the ArkaneSpecies object')
# Expecting to get a species or a TS when generating the object within Arkane,
# or a conformer when parsing from YAML.
raise ValueError('No species (or TS) or conformer was passed to the ArkaneSpecies object')
if conformer is not None:
self.conformer = conformer
if label is None and species is not None:
Expand All @@ -86,18 +96,26 @@ def __init__(self, species=None, conformer=None, author='', level_of_theory='',
self.use_hindered_rotors = use_hindered_rotors
self.use_bond_corrections = use_bond_corrections
self.atom_energies = atom_energies
self.chemkin_thermo_string = chemkin_thermo_string
self.smiles = smiles
self.adjacency_list = adjacency_list
self.inchi = inchi
self.inchi_key = inchi_key
self.xyz = xyz
self.molecular_weight = molecular_weight
self.symmetry_number = symmetry_number
self.transport_data = transport_data
self.energy_transfer_model = energy_transfer_model # check pdep flag
self.thermo = thermo
self.thermo_data = thermo_data
self.is_ts = is_ts if is_ts is not None else isinstance(species, TransitionState)
if not self.is_ts:
self.chemkin_thermo_string = chemkin_thermo_string
self.smiles = smiles
self.adjacency_list = adjacency_list
self.inchi = inchi
self.inchi_key = inchi_key
self.transport_data = transport_data
self.energy_transfer_model = energy_transfer_model
self.thermo = thermo
self.thermo_data = thermo_data
else:
# initialize TS-related attributes
self.imaginary_frequency = None
self.reaction_label = ''
self.reactants = list()
self.products = list()
if species is not None:
self.update_species_attributes(species)
self.RMG_version = RMG_version if RMG_version is not None else __version__
Expand All @@ -117,12 +135,17 @@ def __repr__(self):

def update_species_attributes(self, species=None):
"""
Update the object with a new species (while keeping non-species-dependent attributes unchanged)
Update the object with a new species/TS (while keeping non-species-dependent attributes unchanged)
"""
if species is None:
raise ValueError('No species was passed to ArkaneSpecies')
self.label = species.label
if species.molecule is not None and len(species.molecule) > 0:
if isinstance(species, TransitionState):
self.imaginary_frequency = species.frequency
if species.conformer is not None:
self.conformer = species.conformer
self.xyz = self.update_xyz_string()
elif species.molecule is not None and len(species.molecule) > 0:
self.smiles = species.molecule[0].toSMILES()
self.adjacency_list = species.molecule[0].toAdjacencyList()
try:
Expand Down Expand Up @@ -182,17 +205,14 @@ def save_yaml(self, path):
"""
Save the species with all statMech data to a .yml file
"""
if not os.path.exists(os.path.join(os.path.abspath(path),'ArkaneSpecies', '')):
os.mkdir(os.path.join(os.path.abspath(path),'ArkaneSpecies', ''))
if not os.path.exists(os.path.join(os.path.abspath(path), 'species', '')):
os.mkdir(os.path.join(os.path.abspath(path), 'species', ''))
valid_chars = "-_.()<=>+ %s%s" % (string.ascii_letters, string.digits)
filename = os.path.join('ArkaneSpecies',
filename = os.path.join('species',
''.join(c for c in self.label if c in valid_chars) + '.yml')
full_path = os.path.join(path, filename)
content = yaml.dump(data=self.as_dict(), Dumper=Dumper)
# remove empty lines from the file (multi-line strings have excess new line brakes for some reason):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I was worried about this. I probably should have followed up on that.

Copy link
Member Author

@alongd alongd Feb 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looks ugly, but works....
Another option for human input is to use a pipe symbol in yaml, e.g. (in ARC's format):


species:
  - label: vinoxy
    smiles: C=C[O]
    multiplicity: 2
    charge: 0

  - label: OH
    xyz: |
      O       0.00000000    0.00000000   -0.12002167
      H       0.00000000    0.00000000    0.85098324

  - label: methylamine
    adjlist: |
      1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S}
      2 N u0 p1 c0 {1,S} {6,S} {7,S}
      3 H u0 p0 c0 {1,S}
      4 H u0 p0 c0 {1,S}
      5 H u0 p0 c0 {1,S}
      6 H u0 p0 c0 {2,S}
      7 H u0 p0 c0 {2,S}

  - label: propene
    smiles: C=CC

  - label: N2H4
    smiles: NN

content = content.replace('\n\n', '\n')
with open(full_path, 'w') as f:
f.write(content)
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):
Expand All @@ -205,8 +225,8 @@ def load_yaml(self, path, species, pdep=False):
data = yaml.safe_load(stream=f)
try:
if species.label != data['label']:
logging.warning('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']))
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))
Expand All @@ -233,11 +253,22 @@ def load_yaml(self, path, species, pdep=False):
'Wilhoit': Wilhoit,
'NASA': NASA,
}
freq_data = None
if 'imaginary_frequency' in data:
freq_data = data['imaginary_frequency']
del data['imaginary_frequency']
self.make_object(data=data, class_dict=class_dict)
if pdep and (self.transport_data is None or self.energy_transfer_model is None):
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
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))
if pdep and self.smiles is None and self.adjacency_list is None\
if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None\
and self.inchi is None and self.molecular_weight is None:
raise ValueError('The molecular weight was not specified, and a structure was not given so it could '
'not be calculated. Specify either the molecular weight or structure if '
Expand Down
2 changes: 1 addition & 1 deletion arkane/commonTest.py
Expand Up @@ -308,7 +308,7 @@ def setUpClass(cls):
cls.dump_path = os.path.join(path, 'C2H6')
cls.dump_input_path = os.path.join(cls.dump_path, 'input.py')
cls.dump_output_file = os.path.join(cls.dump_path, 'output.py')
cls.dump_yaml_file = os.path.join(cls.dump_path, 'ArkaneSpecies', 'C2H6.yml')
cls.dump_yaml_file = os.path.join(cls.dump_path, 'species', 'C2H6.yml')

cls.load_path = os.path.join(path, 'C2H6_from_yaml')
cls.load_input_path = os.path.join(cls.load_path, 'input.py')
Expand Down