def get_enthalpy_of_formation(self, freq_scale_factor=1.0, apply_bond_corrections=True): """ Calculate the enthalpy of formation at 298.15 K. Apply bond energy corrections if desired and if model chemistry is compatible. """ temperature = 298.15 mol = pybel.readstring('smi', self.smiles) # Use OBMol to extract bond types # Use RMG molecule to determine if it's linear # Assume it's not linear if we can't parse SMILES with RMG rmg_mol = None try: rmg_mol = Molecule().fromSMILES(self.smiles) except AtomTypeError: try: rmg_mol = Molecule().fromSMILES(self.smiles2) except AtomTypeError: try: rmg_mol = Molecule().fromInChI(self.inchi) except AtomTypeError: warnings.warn( 'Could not determine linearity from RMG molecule in {}' .format(self.file_name)) if rmg_mol is not None: is_linear = rmg_mol.isLinear() else: is_linear = False # Translation translation = IdealGasTranslation() # Rotation if is_linear: rotation = LinearRotor() else: rotation = NonlinearRotor( rotationalConstant=(self.rotational_consts, 'GHz')) # Vibration freqs = [f * freq_scale_factor for f in self.freqs] # Apply scale factor vibration = HarmonicOscillator(frequencies=(freqs, 'cm^-1')) # Group modes modes = [translation, rotation, vibration] conformer = Conformer(modes=modes) # Energy e0 = self.e0 * constants.E_h * constants.Na zpe = self.zpe * constants.E_h * constants.Na * freq_scale_factor # Bring energy to gas phase reference state at 298.15K atom_energies = energy_data.atom_energies[self.model_chemistry] for element in self.elements: e0 -= atom_energies[element] * constants.E_h * constants.Na e0 += self.enthalpy_corrections[element] * 4184.0 if apply_bond_corrections: bond_energies = energy_data.bond_energy_corrections[ self.model_chemistry] for bond in pybel.ob.OBMolBondIter(mol.OBMol): bond_symbol_split = [ self.atomic_num_dict[bond.GetBeginAtom().GetAtomicNum()], self.bond_symbols[bond.GetBondOrder()], self.atomic_num_dict[bond.GetEndAtom().GetAtomicNum()] ] try: bond_energy = bond_energies[''.join(bond_symbol_split)] except KeyError: bond_energy = bond_energies[''.join( bond_symbol_split[::-1])] # Try reverse order e0 += bond_energy * 4184.0 conformer.E0 = (e0 + zpe, 'J/mol') self.hf298 = conformer.getEnthalpy(temperature) + conformer.E0.value_si return self.hf298
def get_thermo(optfreq_log, optfreq_level, energy_level, energy_log=None, mol=None, bacs=None, soc=False, infer_symmetry=False, infer_chirality=False, unique_id='0', scr_dir='SCRATCH'): q = QChem(logfile=optfreq_log) symbols, coords = q.get_geometry() inertia = q.get_moments_of_inertia() freqs = q.get_frequencies() zpe = q.get_zpe() if energy_log is None: e0 = q.get_energy() multiplicity = q.get_multiplicity() else: m = Molpro(logfile=energy_log) e0 = m.get_energy() multiplicity = m.get_multiplicity() # Infer connections only if not given explicitly if mol is None: mol = geo_to_rmg_mol((symbols, coords)) # Does not contain bond orders # Try to infer point group to calculate symmetry number and chirality symmetry = optical_isomers = 1 point_group = None if infer_symmetry or infer_chirality: qmdata = QMData( groundStateDegeneracy=multiplicity, # Only needed to check if valid QMData numberOfAtoms=len(symbols), atomicNumbers=[atomic_symbol_dict[sym] for sym in symbols], atomCoords=(coords, 'angstrom'), energy=(e0 * 627.5095, 'kcal/mol') # Only needed to avoid error ) settings = type("", (), dict(symmetryPath='symmetry', scratchDirectory=scr_dir))() # Creates anonymous class pgc = PointGroupCalculator(settings, unique_id, qmdata) point_group = pgc.calculate() if point_group is not None: if infer_symmetry: symmetry = point_group.symmetryNumber if infer_chirality and point_group.chiral: optical_isomers = 2 # Translational mode mass = mol.getMolecularWeight() translation = IdealGasTranslation(mass=(mass, 'kg/mol')) # Rotational mode if isinstance(inertia, list): # Nonlinear rotation = NonlinearRotor(inertia=(inertia, 'amu*angstrom^2'), symmetry=symmetry) else: rotation = LinearRotor(inertia=(inertia, 'amu*angstrom^2'), symmetry=symmetry) # Vibrational mode freq_scale_factor = freq_scale_factors.get(optfreq_level, 1.0) freqs = [f * freq_scale_factor for f in freqs] vibration = HarmonicOscillator(frequencies=(freqs, 'cm^-1')) # Bring energy to gas phase reference state e0 *= constants.E_h * constants.Na zpe *= constants.E_h * constants.Na * freq_scale_factor for sym in symbols: if soc: e0 -= (atom_energies[energy_level][sym] - atom_socs[sym]) * constants.E_h * constants.Na else: e0 -= atom_energies[energy_level][sym] * constants.E_h * constants.Na e0 += (h0expt[sym] - h298corr[sym]) * 4184.0 if bacs is not None: e0 -= get_bac_correction(mol, **bacs) * 4184.0 # Group modes into Conformer object modes = [translation, rotation, vibration] conformer = Conformer(modes=modes, spinMultiplicity=multiplicity, opticalIsomers=optical_isomers) # Calculate heat of formation, entropy of formation, and heat capacities conformer.E0 = (e0 + zpe, 'J/mol') hf298 = conformer.getEnthalpy(298.0) + conformer.E0.value_si s298 = conformer.getEntropy(298.0) Tlist = [300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0] cp = np.zeros(len(Tlist)) for i, T in enumerate(Tlist): cp[i] = conformer.getHeatCapacity(T) # Return in kcal/mol and cal/mol/K return hf298/4184.0, s298/4.184, cp/4.184