def to_rmg(self): return kinetics.ArrheniusEP( A=ScalarQuantity(self.a, self.a_units), n=self.n, alpha=self.alpha, E0=ScalarQuantity(self.e0, self.e0_units), )
def load_yaml(self, path, species, 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)) 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)) try: class_name = data['class'] except KeyError: raise KeyError("Can only make objects if the `class` attribute in the dictionary is known") if class_name != 'ArkaneSpecies': raise KeyError("Expected a ArkaneSpecies object, but got {0}".format(class_name)) del data['class'] class_dict = {'ScalarQuantity': ScalarQuantity, 'ArrayQuantity': ArrayQuantity, 'Conformer': Conformer, 'LinearRotor': LinearRotor, 'NonlinearRotor': NonlinearRotor, 'KRotor': KRotor, 'SphericalTopRotor': SphericalTopRotor, 'HinderedRotor': HinderedRotor, 'FreeRotor': FreeRotor, 'IdealGasTranslation': IdealGasTranslation, 'HarmonicOscillator': HarmonicOscillator, 'TransportData': TransportData, 'SingleExponentialDown': SingleExponentialDown, '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 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 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 ' 'pressure-dependent calculations are requested. Check file {0}'.format(path)) logging.debug("Parsed all YAML objects")
def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args): return kinetics.MultiArrhenius( arrhenius=[a.arrhenius.to_rmg() for a in self.arrhenius_set], Tmin=ScalarQuantity(min_temp, "K"), Tmax=ScalarQuantity(max_temp, "K"), Pmin=ScalarQuantity(min_pressure, "Pa"), Pmax=ScalarQuantity(max_pressure, "Pa"), )
def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args): return kinetics.ThirdBody( arrheniusLow=self.low_arrhenius.to_rmg(), Tmin=ScalarQuantity(min_temp, "K"), Tmax=ScalarQuantity(max_temp, "K"), Pmin=ScalarQuantity(min_pressure, "Pa"), Pmax=ScalarQuantity(max_pressure, "Pa"), )
def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args): return kinetics.Chebyshev( coeffs=self.coefficient_matrix, kunits=self.units, Tmin=ScalarQuantity(min_temp, "K"), Tmax=ScalarQuantity(max_temp, "K"), Pmin=ScalarQuantity(min_pressure, "Pa"), Pmax=ScalarQuantity(max_pressure, "Pa"), )
def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args): return kinetics.PDepArrhenius( pressures=ArrayQuantity([p.pressure for p in self.pressure_set], "Pa"), arrhenius=[p.arrhenius.to_rmg() for p in self.pressure_set], Tmin=ScalarQuantity(min_temp, "K"), Tmax=ScalarQuantity(max_temp, "K"), Pmin=ScalarQuantity(min_pressure, "Pa"), Pmax=ScalarQuantity(max_pressure, "Pa"), )
def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args): return kinetics.MultiPdepArrhenius( arrhenius=[ p.arrhenius.to_rmg() for pda in self.pdep_arrhenius_set for p in pda.pressure_set ], Tmin=ScalarQuantity(min_temp, "K"), Tmax=ScalarQuantity(max_temp, "K"), Pmin=ScalarQuantity(min_pressure, "Pa"), Pmax=ScalarQuantity(max_pressure, "Pa"), )
def __init__(self, molecule, low_level_hf298, model_chemistry, high_level_hf298=None, source=None): """ Args: molecule (Molecule): The RMG Molecule object with connectivity information low_level_hf298 (ScalarQuantity): evaluated using a lower level of theory (e.g. DFT) model_chemistry (str): Level of theory used to calculate the low level thermo high_level_hf298 (ScalarQuantity, optional): evaluated using experimental data or a high level of theory that is serving as the "reference" for the isodesmic calculation source (str): Literature source from which the high level data was taken """ if isinstance(molecule, Molecule): self.molecule = molecule else: raise ValueError( f'ErrorCancelingSpecies molecule attribute must be an rmgpy Molecule object. Instead a ' f'{type(molecule)} object was given') if isinstance(model_chemistry, str): self.model_chemistry = model_chemistry else: raise ValueError( f'The model chemistry string used to calculate the low level Hf298 must be provided ' f'consistency checks. Instead, a {type(model_chemistry)} object was given' ) if not isinstance(low_level_hf298, ScalarQuantity): if isinstance(low_level_hf298, tuple): low_level_hf298 = ScalarQuantity(*low_level_hf298) else: raise TypeError( f'Low level Hf298 should be a ScalarQuantity object or its tuple representation, but ' f'received {low_level_hf298} instead.') self.low_level_hf298 = low_level_hf298 # If the species is a reference species, then the high level data is already known if high_level_hf298 is not None and not isinstance( high_level_hf298, ScalarQuantity): if isinstance(high_level_hf298, tuple): high_level_hf298 = ScalarQuantity(*high_level_hf298) else: raise TypeError( f'High level Hf298 should be a ScalarQuantity object or its tuple representation, but ' f'received {high_level_hf298} instead.') self.high_level_hf298 = high_level_hf298 self.source = source
def draw_kinetics_plots(rxn_list, path=None, t_min=(300, 'K'), t_max=(3000, 'K'), t_count=50): """ Draws plots of calculated rates and RMG's best values for reaction rates in rxn_list `rxn_list` has a .kinetics attribute calculated by ARC and an .rmg_reactions list with RMG rates """ plt.style.use(str('seaborn-talk')) t_min = ScalarQuantity(value=t_min[0], units=str(t_min[1])) t_max = ScalarQuantity(value=t_max[0], units=str(t_max[1])) temperature = np.linspace(t_min.value_si, t_max.value_si, t_count) pressure = 1e7 # Pa (=100 bar) pp = None if path is not None: path = os.path.join(path, str('rate_plots.pdf')) if os.path.exists(path): os.remove(path) pp = PdfPages(path) for rxn in rxn_list: if rxn.kinetics is not None: reaction_order = len(rxn.reactants) units = '' conversion_factor = {1: 1, 2: 1e6, 3: 1e12} if reaction_order == 1: units = r' (s$^-1$)' elif reaction_order == 2: units = r' (cm$^3$/(mol s))' elif reaction_order == 3: units = r' (cm$^6$/(mol$^2$ s))' arc_k = list() for t in temperature: arc_k.append(rxn.kinetics.getRateCoefficient(t, pressure) * conversion_factor[reaction_order]) rmg_rxns = list() for rmg_rxn in rxn.rmg_reactions: rmg_rxn_dict = dict() rmg_rxn_dict['rmg_rxn'] = rmg_rxn rmg_rxn_dict['t_min'] = rmg_rxn.kinetics.Tmin if rmg_rxn.kinetics.Tmin is not None else t_min rmg_rxn_dict['t_max'] = rmg_rxn.kinetics.Tmax if rmg_rxn.kinetics.Tmax is not None else t_max k = list() temp = np.linspace(rmg_rxn_dict['t_min'].value_si, rmg_rxn_dict['t_max'].value_si, t_count) for t in temp: k.append(rmg_rxn.kinetics.getRateCoefficient(t, pressure) * conversion_factor[reaction_order]) rmg_rxn_dict['k'] = k rmg_rxn_dict['T'] = temp if rmg_rxn.kinetics.isPressureDependent(): rmg_rxn.comment += str(' (at {0} bar)'.format(int(pressure / 1e5))) rmg_rxn_dict['label'] = rmg_rxn.comment rmg_rxns.append(rmg_rxn_dict) _draw_kinetics_plots(rxn.label, arc_k, temperature, rmg_rxns, units, pp) pp.close()
def calculate_target_enthalpy(self, n_reactions_max=20, milp_software=None): """ Perform a multiple error canceling reactions search and calculate hf298 for the target species by taking the median hf298 value from among the error canceling reactions found Args: n_reactions_max (int, optional): The maximum number of found reactions that will returned, after which no further searching will occur even if there are possible subsets left in the queue. milp_software (list, optional): Solvers to try in order. Defaults to ['lpsolve'] or if pyomo is available defaults to ['lpsolve', 'pyomo']. lpsolve is usually faster. Returns: tuple(ScalarQuantity, list) - Standard heat of formation at 298 K calculated for the target species - reaction list containing all error canceling reactions found """ reaction_list = self.multiple_error_canceling_reaction_search( n_reactions_max, milp_software) h298_list = np.zeros(len(reaction_list)) for i, rxn in enumerate(reaction_list): h298_list[i] = rxn.calculate_target_thermo().value_si return ScalarQuantity(np.median(h298_list), 'J/mol'), reaction_list
def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, efficiencies, *args): rmg_efficiencies = { e.species.to_rmg(): e.efficiency for e in self.efficiency_set.all() } return kinetics.Lindemann( arrheniusHigh=self.high_arrhenius.to_rmg(), arrheniusLow=self.low_arrhenius.to_rmg(), Tmin=ScalarQuantity(min_temp, "K"), Tmax=ScalarQuantity(max_temp, "K"), Pmin=ScalarQuantity(min_pressure, "Pa"), Pmax=ScalarQuantity(max_pressure, "Pa"), efficiencies=rmg_efficiencies, )
def load_yaml(self, path, species, 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)) 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)) try: class_name = data['class'] except KeyError: raise KeyError("Can only make objects if the `class` attribute in the dictionary is known") if class_name != 'ArkaneSpecies': raise KeyError("Expected a ArkaneSpecies object, but got {0}".format(class_name)) del data['class'] class_dict = {'ScalarQuantity': ScalarQuantity, 'ArrayQuantity': ArrayQuantity, 'Conformer': Conformer, 'LinearRotor': LinearRotor, 'NonlinearRotor': NonlinearRotor, 'KRotor': KRotor, 'SphericalTopRotor': SphericalTopRotor, 'HinderedRotor': HinderedRotor, 'FreeRotor': FreeRotor, 'IdealGasTranslation': IdealGasTranslation, 'HarmonicOscillator': HarmonicOscillator, 'TransportData': TransportData, 'SingleExponentialDown': SingleExponentialDown, '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 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 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 ' 'pressure-dependent calculations are requested. Check file {0}'.format(path)) logging.debug("Parsed all YAML objects")
def calculate_target_thermo(self): """ Estimate the high level thermochemistry for the target species using the error canceling scheme Returns: rmgpy.quantity.ScalarQuantity: Hf298 in 'J/mol' estimated for the target species """ low_level_h_rxn = sum(spec[0].low_level_hf298.value_si*spec[1] for spec in self.species.items()) - \ self.target.low_level_hf298.value_si target_thermo = sum(spec[0].high_level_hf298.value_si*spec[1] for spec in self.species.items()) - \ low_level_h_rxn return ScalarQuantity(target_thermo, 'J/mol')
def _get_petersson_correction( self, bonds: Dict[str, int] = None, datapoint: BACDatapoint = None) -> ScalarQuantity: """ Given the model_chemistry and a dictionary of bonds, return the total BAC. Args: bonds: Dictionary of bonds with the following format: bonds = { 'C-H': C-H_bond_count, 'C-C': C-C_bond_count, 'C=C': C=C_bond_count, ... } datapoint: BACDatapoint instead of bonds. Returns: Petersson-type bond additivity correction. """ if datapoint is not None: if bonds is None: bonds = datapoint.bonds else: logging.warning( f'Species {datapoint.spc.label} will not be used because `bonds` was specified' ) # Sum up corrections for all bonds bac = 0.0 for symbol, count in bonds.items(): if symbol in self.bacs: bac += count * self.bacs[symbol] else: symbol_flipped = ''.join( re.findall('[a-zA-Z]+|[^a-zA-Z]+', symbol)[::-1]) # Check reversed symbol if symbol_flipped in self.bacs: bac += count * self.bacs[symbol_flipped] else: logging.warning( f'Bond correction not applied for unknown bond type {symbol}.' ) return ScalarQuantity(bac, 'kcal/mol')
def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args): """ Return an rmgpy.kinetics.Arrhenius object for this rate expression. """ if self.a_delta: A = ScalarQuantity(self.a, self.a_units, self.a_delta) else: A = ScalarQuantity(self.a, self.a_units) if self.e_delta: Ea = ScalarQuantity(self.e, self.e_units, self.e_delta) else: Ea = ScalarQuantity(self.e, self.e_units) return kinetics.Arrhenius( A=A, n=self.n, Ea=Ea, Tmin=ScalarQuantity(min_temp, "K"), Tmax=ScalarQuantity(max_temp, "K"), Pmin=ScalarQuantity(min_pressure, "Pa"), Pmax=ScalarQuantity(max_pressure, "Pa"), )
class ArkaneSpecies(RMGObject): """ A class for archiving an Arkane species including its statmech data into .yml files """ def __init__(self, species=None, conformer=None, author='', level_of_theory='', model_chemistry='', 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, 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 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: self.label = species.label else: self.label = label self.author = author self.level_of_theory = level_of_theory self.model_chemistry = model_chemistry self.frequency_scale_factor = frequency_scale_factor self.use_hindered_rotors = use_hindered_rotors self.use_bond_corrections = use_bond_corrections self.atom_energies = atom_energies self.xyz = xyz self.molecular_weight = molecular_weight self.symmetry_number = symmetry_number 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__ self.datetime = datetime if datetime is not None else time.strftime( "%Y-%m-%d %H:%M") def __repr__(self): """ Return a string representation that can be used to reconstruct the object """ result = '{0!r}'.format(self.__class__.__name__) result += '{' for key, value in self.as_dict().iteritems(): if key != 'class': result += '{0!r}: {1!r}'.format(str(key), str(value)) result += '}' return result def update_species_attributes(self, species=None): """ 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 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: inchi = toInChI(species.molecule[0], backend='try-all', aug_level=0) except ValueError: inchi = '' try: inchi_key = toInChIKey(species.molecule[0], backend='try-all', aug_level=0) except ValueError: inchi_key = '' self.inchi = inchi self.inchi_key = inchi_key if species.conformer is not None: self.conformer = species.conformer self.xyz = self.update_xyz_string() self.molecular_weight = species.molecularWeight if species.symmetryNumber != -1: self.symmetry_number = species.symmetryNumber if species.transportData is not None: self.transport_data = species.transportData # called `collisionModel` in Arkane if species.energyTransferModel is not 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 } def update_xyz_string(self): if self.conformer is not None and self.conformer.number is not None: # generate the xyz-format string from the Conformer coordinates xyz_string = '{0}\n{1}'.format(len(self.conformer.number.value_si), self.label) for i, coorlist in enumerate(self.conformer.coordinates.value_si): for element in elementList: if element.number == int( self.conformer.number.value_si[i]): element_symbol = element.symbol break else: raise ValueError( 'Could not find element symbol corresponding to atom number {0}' .format(self.conformer.number.value_si[i])) xyz_string += '\n{0} {1} {2} {3}'.format( element_symbol, coorlist[0], coorlist[1], coorlist[2]) else: xyz_string = '' return xyz_string 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), 'species', '')): os.mkdir(os.path.join(os.path.abspath(path), 'species', '')) valid_chars = "-_.()<=>+ %s%s" % (string.ascii_letters, string.digits) 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) with open(full_path, 'w') as f: 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): """ 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)) 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)) try: class_name = data['class'] except KeyError: raise KeyError( "Can only make objects if the `class` attribute in the dictionary is known" ) if class_name != 'ArkaneSpecies': raise KeyError( "Expected a ArkaneSpecies object, but got {0}".format( class_name)) del data['class'] class_dict = { 'ScalarQuantity': ScalarQuantity, 'ArrayQuantity': ArrayQuantity, 'Conformer': Conformer, 'LinearRotor': LinearRotor, 'NonlinearRotor': NonlinearRotor, 'KRotor': KRotor, 'SphericalTopRotor': SphericalTopRotor, 'HinderedRotor': HinderedRotor, 'FreeRotor': FreeRotor, 'IdealGasTranslation': IdealGasTranslation, 'HarmonicOscillator': HarmonicOscillator, 'TransportData': TransportData, 'SingleExponentialDown': SingleExponentialDown, '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 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 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 ' 'pressure-dependent calculations are requested. Check file {0}' .format(path)) logging.debug("Parsed all YAML objects")
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 job_list includes a pressureDependentJob. """ 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: content = f.read() content = replace_yaml_syntax(content, label) data = yaml.safe_load(stream=content) 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: raise KeyError( "Can only make objects if the `class` attribute in the dictionary is known" ) if class_name != 'ArkaneSpecies': raise KeyError( "Expected a ArkaneSpecies object, but got {0}".format( class_name)) del data['class'] 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().from_adjacency_list( 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=ARKANE_CLASS_DICT) if freq_data is not None: self.imaginary_frequency = ScalarQuantity() self.imaginary_frequency.make_object(data=freq_data, class_dict=ARKANE_CLASS_DICT) 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 ' 'pressure-dependent calculations are requested. Check file {0}' .format(path)) logging.debug("Parsed all YAML objects")
class ArkaneSpecies(RMGObject): """ A class for archiving an Arkane species including its statmech data into .yml files """ def __init__(self, species=None, conformer=None, author='', level_of_theory='', model_chemistry='', 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, reactants=None, products=None, reaction_label=None, is_ts=None, charge=None, formula=None, multiplicity=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 super(ArkaneSpecies, self).__init__() if species is None and conformer is None: # 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: self.label = species.label else: self.label = label self.author = author self.level_of_theory = level_of_theory self.model_chemistry = model_chemistry self.frequency_scale_factor = frequency_scale_factor self.use_hindered_rotors = use_hindered_rotors self.use_bond_corrections = use_bond_corrections self.atom_energies = atom_energies self.xyz = xyz self.molecular_weight = molecular_weight self.symmetry_number = symmetry_number self.charge = charge self.multiplicity = multiplicity 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 self.formula = formula 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__ self.datetime = datetime if datetime is not None else time.strftime( "%Y-%m-%d %H:%M") def __repr__(self): """ Return a string representation that can be used to reconstruct the object """ result = '{0!r}'.format(self.__class__.__name__) result += '{' for key, value in self.as_dict().items(): if key != 'class': result += '{0!r}: {1!r}'.format(str(key), str(value)) result += '}' return result def update_species_attributes(self, species=None): """ 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') # Don't overwrite the label if it already exists self.label = self.label or species.label 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].to_smiles() self.adjacency_list = species.molecule[0].to_adjacency_list() self.charge = species.molecule[0].get_net_charge() self.multiplicity = species.molecule[0].multiplicity self.formula = species.molecule[0].get_formula() try: inchi = to_inchi(species.molecule[0], backend='try-all', aug_level=0) except ValueError: inchi = '' try: inchi_key = to_inchi_key(species.molecule[0], backend='try-all', aug_level=0) except ValueError: inchi_key = '' self.inchi = inchi self.inchi_key = inchi_key if species.conformer is not None: self.conformer = species.conformer self.xyz = self.update_xyz_string() self.molecular_weight = species.molecular_weight if species.symmetry_number != -1: self.symmetry_number = species.symmetry_number if species.transport_data is not None: self.transport_data = species.transport_data # called `collisionModel` in Arkane if species.energy_transfer_model is not None: self.energy_transfer_model = species.energy_transfer_model if species.thermo is not None: self.thermo = species.thermo.as_dict() data = species.get_thermo_data() h298 = data.get_enthalpy(298) / 4184. s298 = data.get_entropy(298) / 4.184 temperatures = np.array( [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]) cp = [] for t in temperatures: cp.append(data.get_heat_capacity(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): """ Generate an xyz string built from self.conformer, and standardize the result Returns: str: 3D coordinates in an XYZ format. """ xyz_list = list() if self.conformer is not None and self.conformer.number is not None: # generate the xyz-format string from self.conformer.coordinates and self.conformer.number xyz_list.append(str(len(self.conformer.number.value_si))) xyz_list.append(self.label) for number, coordinate in zip(self.conformer.number.value_si, self.conformer.coordinates.value_si): element_symbol = get_element(int(number)).symbol row = '{0:4}'.format(element_symbol) row += '{0:14.8f}{1:14.8f}{2:14.8f}'.format( *(coordinate * 1e10).tolist()) # convert m to Angstrom xyz_list.append(row) return '\n'.join(xyz_list) 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), 'species', '')): os.mkdir(os.path.join(os.path.abspath(path), 'species', '')) valid_chars = "-_.()<=>+ %s%s" % (string.ascii_letters, string.digits) 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) with open(full_path, 'w') as f: 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, 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 job_list includes a pressureDependentJob. """ 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: content = f.read() content = replace_yaml_syntax(content, label) data = yaml.safe_load(stream=content) 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: raise KeyError( "Can only make objects if the `class` attribute in the dictionary is known" ) if class_name != 'ArkaneSpecies': raise KeyError( "Expected a ArkaneSpecies object, but got {0}".format( class_name)) del data['class'] 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().from_adjacency_list( 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=ARKANE_CLASS_DICT) if freq_data is not None: self.imaginary_frequency = ScalarQuantity() self.imaginary_frequency.make_object(data=freq_data, class_dict=ARKANE_CLASS_DICT) 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 ' 'pressure-dependent calculations are requested. Check file {0}' .format(path)) logging.debug("Parsed all YAML objects")
class ArkaneSpecies(RMGObject): """ A class for archiving an Arkane species including its statmech data into .yml files """ def __init__(self, species=None, conformer=None, author='', level_of_theory='', model_chemistry='', 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, 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 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: self.label = species.label else: self.label = label self.author = author self.level_of_theory = level_of_theory self.model_chemistry = model_chemistry self.frequency_scale_factor = frequency_scale_factor self.use_hindered_rotors = use_hindered_rotors self.use_bond_corrections = use_bond_corrections self.atom_energies = atom_energies self.xyz = xyz self.molecular_weight = molecular_weight self.symmetry_number = symmetry_number 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__ self.datetime = datetime if datetime is not None else time.strftime("%Y-%m-%d %H:%M") def __repr__(self): """ Return a string representation that can be used to reconstruct the object """ result = '{0!r}'.format(self.__class__.__name__) result += '{' for key, value in self.as_dict().iteritems(): if key != 'class': result += '{0!r}: {1!r}'.format(str(key), str(value)) result += '}' return result def update_species_attributes(self, species=None): """ 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 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: inchi = toInChI(species.molecule[0], backend='try-all', aug_level=0) except ValueError: inchi = '' try: inchi_key = toInChIKey(species.molecule[0], backend='try-all', aug_level=0) except ValueError: inchi_key = '' self.inchi = inchi self.inchi_key = inchi_key if species.conformer is not None: self.conformer = species.conformer self.xyz = self.update_xyz_string() self.molecular_weight = species.molecularWeight if species.symmetryNumber != -1: self.symmetry_number = species.symmetryNumber if species.transportData is not None: self.transport_data = species.transportData # called `collisionModel` in Arkane if species.energyTransferModel is not 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} def update_xyz_string(self): if self.conformer is not None and self.conformer.number is not None: # generate the xyz-format string from the Conformer coordinates xyz_string = '{0}\n{1}'.format(len(self.conformer.number.value_si), self.label) for i, coorlist in enumerate(self.conformer.coordinates.value_si): for element in elementList: if element.number == int(self.conformer.number.value_si[i]): element_symbol = element.symbol break else: raise ValueError('Could not find element symbol corresponding to atom number {0}'.format( self.conformer.number.value_si[i])) xyz_string += '\n{0} {1} {2} {3}'.format(element_symbol, coorlist[0], coorlist[1], coorlist[2]) else: xyz_string = '' return xyz_string 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), 'species', '')): os.mkdir(os.path.join(os.path.abspath(path), 'species', '')) valid_chars = "-_.()<=>+ %s%s" % (string.ascii_letters, string.digits) 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) with open(full_path, 'w') as f: 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): """ 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)) 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)) try: class_name = data['class'] except KeyError: raise KeyError("Can only make objects if the `class` attribute in the dictionary is known") if class_name != 'ArkaneSpecies': raise KeyError("Expected a ArkaneSpecies object, but got {0}".format(class_name)) del data['class'] class_dict = {'ScalarQuantity': ScalarQuantity, 'ArrayQuantity': ArrayQuantity, 'Conformer': Conformer, 'LinearRotor': LinearRotor, 'NonlinearRotor': NonlinearRotor, 'KRotor': KRotor, 'SphericalTopRotor': SphericalTopRotor, 'HinderedRotor': HinderedRotor, 'FreeRotor': FreeRotor, 'IdealGasTranslation': IdealGasTranslation, 'HarmonicOscillator': HarmonicOscillator, 'TransportData': TransportData, 'SingleExponentialDown': SingleExponentialDown, '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 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 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 ' 'pressure-dependent calculations are requested. Check file {0}'.format(path)) logging.debug("Parsed all YAML objects")
def setUp(self): self.np_array = np.array([[1, 2], [3, 4]]) self.np_dict = {'class': 'np_array', 'object': [[1, 2], [3, 4]]} self.array_quantity = ArrayQuantity(value=self.np_array, units='kJ/mol') self.array_dict = { 'class': 'ArrayQuantity', 'value': self.np_dict, 'units': 'kJ/mol' } self.scalar_quantity = ScalarQuantity(value=500.0, units='K') self.scalar_dict = { 'class': 'ScalarQuantity', 'value': 500.0, 'units': 'K' } self.highly_nested_object = PseudoRMGObject( a=PseudoRMGObject(a=PseudoRMGObject( b=self.np_array, c=PseudoRMGObject(c=self.array_quantity, d=PseudoRMGObject(a=self.scalar_quantity, b=PseudoRMGObject())))), b=6) self.highly_nest_dictionary = { 'class': 'PseudoRMGObject', 'a': { 'class': 'PseudoRMGObject', 'a': { 'class': 'PseudoRMGObject', 'b': self.np_dict, 'c': { 'class': 'PseudoRMGObject', 'c': self.array_dict, 'd': { 'class': 'PseudoRMGObject', 'a': self.scalar_dict, 'b': { 'class': 'PseudoRMGObject' } } } } }, 'b': 6 } self.list_of_objects = [ 1, 2.0, 'abc', self.np_array, self.array_quantity, self.scalar_quantity, self.highly_nested_object ] self.list_dict = [ 1, 2.0, 'abc', self.np_dict, self.array_dict, self.scalar_dict, self.highly_nest_dictionary ] self.dictionary_of_objects = { 'test_int': 1, 'test_float': 2.0, 'test_np': self.np_array, 'test_array': self.array_quantity, 'test_scalar': self.scalar_quantity, 'test_nested': self.highly_nested_object } self.objects_dict = { 'test_int': 1, 'test_float': 2.0, 'test_np': self.np_dict, 'test_array': self.array_dict, 'test_scalar': self.scalar_dict, 'test_nested': self.highly_nest_dictionary } self.input_dict = { 'class': 'PseudoRMGObject', 'a': { 'class': 'PseudoRMGObject', 'b': self.np_dict } } self.final_obj_dict = {'a': PseudoRMGObject(b=self.np_array)} self.class_dictionary = { 'np_array': np.array, 'ScalarQuantity': ScalarQuantity, 'ArrayQuantity': ArrayQuantity, 'PseudoRMGObject': PseudoRMGObject }
def process_reactions(database, libraries, families, compare_kinetics=True, show_all=False, filter_aromatic=True): """ Main function to recreate library reactions from families and display the results. """ master_dict = {} multiple_dict = {} for library_name in libraries: library = database.kinetics.libraries[library_name] reaction_dict = {} for index, entry in library.entries.items(): lib_rxn = entry.item lib_rxn.kinetics = entry.data lib_rxn.index = index # Let's make RMG try to generate this reaction from the families! fam_rxn_list = database.kinetics.generate_reactions_from_families( reactants=lib_rxn.reactants, products=lib_rxn.products, only_families=None if families == 'all' else families, resonance=True, ) # Filter by aromatic resonance structures if requested if filter_aromatic and len(fam_rxn_list) > 1: selected_rxns = [] max_num_aromatic_reactants = 0 for fam_rxn in fam_rxn_list: num_aromatic_reactants = 0 reactants = fam_rxn.reactants if fam_rxn.is_forward else fam_rxn.products for r in reactants: num_aromatic_reactants += r.molecule[0].is_aromatic() if num_aromatic_reactants > max_num_aromatic_reactants: max_num_aromatic_reactants = num_aromatic_reactants selected_rxns = [fam_rxn] elif num_aromatic_reactants == max_num_aromatic_reactants: selected_rxns.append(fam_rxn) else: continue if selected_rxns: fam_rxn_list = selected_rxns if len(fam_rxn_list) == 1: fam_rxn = fam_rxn_list[0] forward = fam_rxn.is_forward # Find the labeled atoms using family and reactants & products from fam_rxn database.kinetics.families[ fam_rxn.family].add_atom_labels_for_reaction(fam_rxn) # Replace lib_rxn spcs with fam_rxn spcs to transfer atom labels if forward: lib_rxn.reactants = fam_rxn.reactants lib_rxn.products = fam_rxn.products lib_rxn._degeneracy = fam_rxn.degeneracy else: lib_rxn.reactants = fam_rxn.products lib_rxn.products = fam_rxn.reactants if len(lib_rxn.reactants) == 1: units = 's^-1' elif len(lib_rxn.reactants) == 2: units = 'cm^3/(mol*s)' elif len(lib_rxn.reactants) == 3: units = 'cm^6/(mol^2*s)' A = lib_rxn.kinetics.A lib_rxn.kinetics.A = ScalarQuantity( value=A.value_si * A.get_conversion_factor_from_si_to_cm_mol_s(), units=units, uncertainty_type=A.uncertainty_type, uncertainty=A.uncertainty_si * A.get_conversion_factor_from_si_to_cm_mol_s()) if fam_rxn.family in reaction_dict: reaction_dict[fam_rxn.family].append(lib_rxn) else: reaction_dict[fam_rxn.family] = [lib_rxn] template = database.kinetics.families[ fam_rxn.family].retrieve_template(fam_rxn.template) if compare_kinetics: # Check what the current kinetics for this template are new_kinetics = lib_rxn.kinetics old_kinetics = database.kinetics.families[ fam_rxn.family].get_kinetics_for_template( template, degeneracy=fam_rxn.degeneracy)[0] # Evaluate kinetics tlistinv = np.linspace(1000 / 1500, 1000 / 300, num=10) tlist = 1000 * np.reciprocal(tlistinv) newklist = np.log10( np.array([ new_kinetics.get_rate_coefficient(t) for t in tlist ])) oldklist = np.log10( np.array([ old_kinetics.get_rate_coefficient(t) for t in tlist ])) # Create plot plt.cla() plt.plot(tlistinv, newklist, label='New') plt.plot(tlistinv, oldklist, label='Current') plt.legend() plt.xlabel('1000/T') plt.ylabel('log(k)') fig = BytesIO() plt.savefig(fig) fig.seek(0) figdata = b64encode(fig.getvalue()).decode() fig.close() # Format output using html html = generate_header_html(1, fam_rxn, lib_rxn, library_name, families) html += generate_template_html(fam_rxn, template) if compare_kinetics: if not forward: html += [ '<tr><th colspan="{0}" style="color:red;text-align:center">' 'Note: Training reaction written in opposite direction from reaction family.' '</th></tr>'.format(full) ] html += ['<tr>'] html += [ '<td colspan="{0}"><strong>New Kinetics:</strong><br>{1}<br><br>' '<strong>Current Kinetics</strong><br>{2}</td>'.format( half, new_kinetics, old_kinetics) ] html += [ '<td colspan="{0}"><img src="data:image/png;base64,{1}"></td>' .format(half, figdata) ] html += ['</tr>'] html += ['</table>'] display(HTML(''.join(html))) elif len(fam_rxn_list) == 0: if show_all: html = generate_header_html(0, None, lib_rxn, library_name, families) html += ['</table>'] display(HTML(''.join(html))) else: continue else: # Save results to allow further processing later if library_name in multiple_dict: multiple_dict[library_name].append((lib_rxn, fam_rxn_list)) else: multiple_dict[library_name] = [(lib_rxn, fam_rxn_list)] if compare_kinetics: old_kinetics = [] for i, rxn in enumerate(fam_rxn_list): forward = rxn.is_forward template = database.kinetics.families[ rxn.family].retrieve_template(rxn.template) if compare_kinetics: old_kinetics.append(database.kinetics.families[ rxn.family].get_kinetics_for_template( template, degeneracy=rxn.degeneracy)[0]) if i == 0: html = generate_header_html(2, rxn, lib_rxn, library_name, families) html += ['<tr>'] html += [ '<th colspan="{0}">Match #{1} - For the following resonance form of the reaction:</th>' .format(full, i + 1) ] html += ['</tr><tr>'] html += [ '<td colspan="{0}"><img src="data:image/png;base64,{1}"></td>' .format(full, b64encode(rxn._repr_png_()).decode()) ] html += ['</tr>'] html += generate_template_html(rxn, template) if compare_kinetics: new_kinetics = lib_rxn.kinetics # Evaluate kinetics tlistinv = np.linspace(1000 / 1500, 1000 / 300, num=10) tlist = 1000 * np.reciprocal(tlistinv) newklist = np.log10( np.array([ new_kinetics.get_rate_coefficient(t) for t in tlist ])) oldklist = [] for kinetics in old_kinetics: oldklist.append( np.log10( np.array([ kinetics.get_rate_coefficient(t) for t in tlist ]))) # Create plot plt.cla() plt.plot(tlistinv, newklist, label='New') for i, k in enumerate(oldklist): plt.plot(tlistinv, k, label='Match #{0}'.format(i + 1)) plt.legend() plt.xlabel('1000/T') plt.ylabel('log(k)') fig = BytesIO() plt.savefig(fig) fig.seek(0) figdata = b64encode(fig.getvalue()).decode() fig.close() if not forward: html += [ '<tr><th colspan="{0}" style="color:red;text-align:center">' 'Note: Training reaction written in opposite direction from reaction family.' '</tr></tr>'.format(full) ] html += ['<tr><td colspan="{0}">'.format(half)] html += [ '<strong>New Kinetics:</strong><br>{0}'.format( new_kinetics) ] for i, kinetics in enumerate(old_kinetics): html += [ '<br><br><strong>Match #{0} Kinetics:</strong><br>{1}' .format(i + 1, kinetics) ] html += [ '</td><td colspan="{0}"><img src="data:image/png;base64,{1}"></td>' .format(half, figdata) ] html += ['</tr>'] html += ['</table>'] display(HTML(''.join(html))) # Save results for this library if reaction_dict: master_dict[library_name] = reaction_dict return master_dict, multiple_dict
def _get_melius_correction(self, coords: np.ndarray = None, nums: Iterable[int] = None, datapoint: BACDatapoint = None, multiplicity: int = None, params: Dict[str, Union[float, Dict[str, float]]] = None) -> ScalarQuantity: """ Given the level of theory, molecular coordinates, atomic numbers, and dictionaries of BAC parameters, return the total BAC. Notes: A molecular correction term other than 0 destroys the size consistency of the quantum chemistry method. This correction also requires the multiplicity of the molecule. The negative of the total correction described in Anantharaman and Melius (JPCA 2005) is returned so that it can be added to the energy. Args: coords: Numpy array of Cartesian atomic coordinates. nums: Sequence of atomic numbers. datapoint: BACDatapoint instead of molecule. multiplicity: Multiplicity of the molecule (not necessary if using datapoint). params: Optionally provide parameters other than those stored in self. Returns: Melius-type bond additivity correction. """ if params is None: params = self.bacs atom_corr = params['atom_corr'] bond_corr_length = params['bond_corr_length'] bond_corr_neighbor = params['bond_corr_neighbor'] mol_corr = params.get('mol_corr', 0.0) # Get single-bonded RMG molecule mol = None if datapoint is not None: if nums is None or coords is None: mol = datapoint.to_mol(from_geo=True) multiplicity = datapoint.spc.multiplicity # Use species multiplicity instead else: logging.warning( f'Species {datapoint.spc.label} will not be used because `nums` and `coords` were specified' ) if mol is None: mol = geo_to_mol(coords, nums=nums) # Molecular correction if mol_corr != 0 and multiplicity is None: raise BondAdditivityCorrectionError(f'Missing multiplicity for {mol}') bac_mol = mol_corr * self._get_mol_coeff(mol, multiplicity=multiplicity) # Atomic correction bac_atom = sum(count * atom_corr[symbol] for symbol, count in self._get_atom_counts(mol).items()) # Bond correction bac_length = sum( coeff * (bond_corr_length[symbol[0]] * bond_corr_length[symbol[1]]) ** 0.5 if isinstance(symbol, tuple) else coeff * bond_corr_length[symbol] for symbol, coeff in self._get_length_coeffs(mol).items() ) bac_neighbor = sum(count * bond_corr_neighbor[symbol] for symbol, count in self._get_neighbor_coeffs(mol).items()) bac_bond = bac_length + bac_neighbor # Note the minus sign return ScalarQuantity(-(bac_mol + bac_atom + bac_bond), 'kcal/mol')
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. """ 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) 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: raise KeyError( "Can only make objects if the `class` attribute in the dictionary is known" ) if class_name != 'ArkaneSpecies': raise KeyError( "Expected a ArkaneSpecies object, but got {0}".format( class_name)) del data['class'] class_dict = { 'ScalarQuantity': ScalarQuantity, 'ArrayQuantity': ArrayQuantity, 'Conformer': Conformer, 'LinearRotor': LinearRotor, 'NonlinearRotor': NonlinearRotor, 'KRotor': KRotor, 'SphericalTopRotor': SphericalTopRotor, 'HinderedRotor': HinderedRotor, 'FreeRotor': FreeRotor, 'IdealGasTranslation': IdealGasTranslation, 'HarmonicOscillator': HarmonicOscillator, 'TransportData': TransportData, '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=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)) 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 ' 'pressure-dependent calculations are requested. Check file {0}' .format(path)) logging.debug("Parsed all YAML objects")