def setUp(self): """ A function run before each unit test in this class. """ self.ethylene = Conformer( E0=(0.0, "kJ/mol"), modes=[ IdealGasTranslation(mass=(28.03, "amu")), NonlinearRotor(inertia=([3.41526, 16.6498, 20.065], "amu*angstrom^2"), symmetry=4), HarmonicOscillator(frequencies=([828.397, 970.652, 977.223, 1052.93, 1233.55, 1367.56, 1465.09, 1672.25, 3098.46, 3111.7, 3165.79, 3193.54], "cm^-1")), ], spin_multiplicity=1, optical_isomers=1, ) self.oxygen = Conformer( E0=(0.0, "kJ/mol"), modes=[ IdealGasTranslation(mass=(31.99, "amu")), LinearRotor(inertia=(11.6056, "amu*angstrom^2"), symmetry=2), HarmonicOscillator(frequencies=([1621.54], "cm^-1")), ], spin_multiplicity=3, optical_isomers=1, ) # The following data is for ethane at the CBS-QB3 level self.coordinates = np.array([ [0.0000, 0.0000, 0.0000], [-0.0000, -0.0000, 1.0936], [1.0430, -0.0000, -0.3288], [-0.4484, 0.9417, -0.3288], [-0.7609, -1.2051, -0.5580], [-0.7609, -1.2051, -1.6516], [-0.3125, -2.1468, -0.2292], [-1.8039, -1.2051, -0.2293], ]) self.number = np.array([6, 1, 1, 1, 6, 1, 1, 1]) self.mass = np.array([12, 1.007825, 1.007825, 1.007825, 12, 1.007825, 1.007825, 1.007825]) self.E0 = -93.5097 self.conformer = Conformer( E0=(self.E0, "kJ/mol"), modes=[ IdealGasTranslation(mass=(30.0469, "amu")), NonlinearRotor(inertia=([6.27071, 25.3832, 25.3833], "amu*angstrom^2"), symmetry=6), HarmonicOscillator(frequencies=([818.917, 819.479, 987.099, 1206.76, 1207.05, 1396, 1411.35, 1489.73, 1489.95, 1492.49, 1492.66, 2995.36, 2996.06, 3040.77, 3041, 3065.86, 3066.02], "cm^-1")), HinderedRotor(inertia=(1.56768, "amu*angstrom^2"), symmetry=3, barrier=(2.69401, "kcal/mol"), quantum=False, semiclassical=False), ], spin_multiplicity=1, optical_isomers=1, coordinates=(self.coordinates, "angstrom"), number=self.number, mass=(self.mass, "amu"), )
def setUp(self): """ A method that is run before each unit test in this class. """ self.species = Species( index=1, label='C2H4', thermo=ThermoData( Tdata=([300.0,400.0,500.0,600.0,800.0,1000.0,1500.0],'K'), Cpdata=([3.0,4.0,5.0,6.0,8.0,10.0,15.0],'cal/(mol*K)'), H298=(-20.0,'kcal/mol'), S298=(50.0,'cal/(mol*K)'), Tmin=(300.0,'K'), Tmax=(2000.0,'K'), ), conformer=Conformer( E0=(0.0,'kJ/mol'), modes=[ IdealGasTranslation(mass=(28.03,'amu')), NonlinearRotor(inertia=([5.6952e-47, 2.7758e-46, 3.3454e-46],'kg*m^2'), symmetry=1), HarmonicOscillator(frequencies=([834.50, 973.31, 975.37, 1067.1, 1238.5, 1379.5, 1472.3, 1691.3, 3121.6, 3136.7, 3192.5, 3221.0],'cm^-1')), ], spinMultiplicity=1, opticalIsomers=1, ), molecule=[Molecule().fromSMILES('C=C')], transportData=TransportData(sigma=(1, 'angstrom'), epsilon=(100, 'K')), molecularWeight=(28.03,'amu'), reactive=True, )
def fitStatmechToHeatCapacity(Tlist, Cvlist, Nvib, Nrot, molecule=None): """ For a given set of dimensionless heat capacity data `Cvlist` corresponding to temperature list `Tlist` in K, fit `Nvib` harmonic oscillator and `Nrot` hindered internal rotor modes. External and other previously-known modes should have already been removed from `Cvlist` prior to calling this function. You must provide at least 7 values for `Cvlist`. This function returns a list containing the fitted vibrational frequencies in a :class:`HarmonicOscillator` object and the fitted 1D hindered rotors in :class:`HinderedRotor` objects. """ # You must specify at least 7 heat capacity points to use in the fitting; # you can specify as many as you like above that minimum if len(Tlist) < 7: raise StatmechFitError('You must specify at least 7 heat capacity points to fitStatmechToHeatCapacity().') if len(Tlist) != len(Cvlist): raise StatmechFitError('The number of heat capacity points ({0:d}) does not match the number of temperatures provided ({1:d}).'.format(len(Cvlist), len(Tlist))) # The number of optimization variables available is constrained to be less # than the number of heat capacity points # This is also capped to a (somewhat arbitrarily chosen) maximum of 16 maxVariables = len(Tlist) - 1 if maxVariables > 16: maxVariables = 16 # The type of variables fitted depends on the values of Nvib and Nrot and # the number of heat capacity points provided # For low values of Nvib and Nrot, we can fit the individual # parameters directly # For high values of Nvib and/or Nrot we are limited by the number of # temperatures we are fitting at, and so we can only fit # pseudo-oscillators and/or pseudo-rotors vib = []; hind = [] if Nvib <= 0 and Nrot <= 0: pass elif Nvib + 2 * Nrot <= maxVariables: vib, hind = fitStatmechDirect(Tlist, Cvlist, Nvib, Nrot, molecule) elif Nvib + 2 <= maxVariables: vib, hind = fitStatmechPseudoRotors(Tlist, Cvlist, Nvib, Nrot, molecule) else: vib, hind = fitStatmechPseudo(Tlist, Cvlist, Nvib, Nrot, molecule) modes = [] if Nvib > 0: vib.sort() ho = HarmonicOscillator(frequencies=(vib[:],"cm^-1")) modes.append(ho) for i in range(Nrot): freq = hind[i][0] barr = hind[i][1] inertia = (barr*constants.c*100.0*constants.h) / (8 * math.pi * math.pi * (freq*constants.c*100.0)**2) barrier = barr*constants.c*100.0*constants.h*constants.Na hr = HinderedRotor(inertia=(inertia*constants.Na*1e23,"amu*angstrom^2"), barrier=(barrier/1000.,"kJ/mol"), symmetry=1, semiclassical=False, quantum=False) modes.append(hr) # Return the fitted modes return modes
def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): """ Load the molecular degree of freedom data from an output file created as the result of a TeraChem "Freq" calculation. As TeraChem's guess of the external symmetry number might not always correct, you can use the `symmetry` parameter to substitute your own value; if not provided, the value in the TeraChem output file will be adopted. """ modes, unscaled_freqs = list(), list() converged = False if optical_isomers is None: _optical_isomers = self.get_symmetry_properties()[0] if optical_isomers is None: optical_isomers = _optical_isomers with open(self.path, 'r') as f: line = f.readline() while line != '': # Read spin multiplicity if not explicitly given if 'Spin multiplicity' in line and spin_multiplicity == 0 and len( line.split()) == 3: spin_multiplicity = int(float(line.split()[-1])) logging.debug( f'Conformer {label} is assigned a spin multiplicity of {spin_multiplicity}' ) # Read vibrational modes elif 'Mode Eigenvalue(AU) Frequency(cm-1)' in line: line = f.readline() while line != '\n': # example: # 'Mode Eigenvalue(AU) Frequency(cm-1) Intensity(km/mol) Vib.Temp(K) ZPE(AU) ...' # ' 1 0.0331810528 170.5666870932 52.2294230772 245.3982965841 0.0003885795 ...' if 'i' not in line.split()[2]: # only consider non-imaginary frequencies in this function unscaled_freqs.append(float(line.split()[2])) line = f.readline() if 'Vibrational Frequencies/Thermochemical Analysis' in line: converged = True line = f.readline() if not len(unscaled_freqs): raise LogError( f'Could not read frequencies from TeraChem log file {self.path}' ) if not converged: raise LogError(f'TeraChem job {self.path} did not converge.') modes.append( HarmonicOscillator(frequencies=(unscaled_freqs, "cm^-1"))) return Conformer(E0=(0.0, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), unscaled_freqs
def setUp(self): """ A method that is run before each unit test in this class. """ self.species = Species( index=1, label='C2H4', thermo=ThermoData( Tdata=([300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0], 'K'), Cpdata=([3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 15.0], 'cal/(mol*K)'), H298=(-20.0, 'kcal/mol'), S298=(50.0, 'cal/(mol*K)'), Tmin=(300.0, 'K'), Tmax=(2000.0, 'K'), ), conformer=Conformer( E0=(0.0, 'kJ/mol'), modes=[ IdealGasTranslation(mass=(28.03, 'amu')), NonlinearRotor( inertia=([5.6952e-47, 2.7758e-46, 3.3454e-46], 'kg*m^2'), symmetry=1), HarmonicOscillator(frequencies=([ 834.50, 973.31, 975.37, 1067.1, 1238.5, 1379.5, 1472.3, 1691.3, 3121.6, 3136.7, 3192.5, 3221.0 ], 'cm^-1')), ], spin_multiplicity=1, optical_isomers=1, ), molecule=[Molecule().from_smiles('C=C')], transport_data=TransportData(sigma=(1, 'angstrom'), epsilon=(100, 'K')), molecular_weight=(28.03, 'amu'), reactive=True, ) self.species2 = Species().from_adjacency_list(""" 1 C u0 p0 c0 {2,D} {6,S} {7,S} 2 C u0 p0 c0 {1,D} {3,S} {8,S} 3 C u0 p0 c0 {2,S} {4,D} {9,S} 4 C u0 p0 c0 {3,D} {5,S} {10,S} 5 C u0 p0 c0 {4,S} {6,D} {11,S} 6 C u0 p0 c0 {1,S} {5,D} {12,S} 7 H u0 p0 c0 {1,S} 8 H u0 p0 c0 {2,S} 9 H u0 p0 c0 {3,S} 10 H u0 p0 c0 {4,S} 11 H u0 p0 c0 {5,S} 12 H u0 p0 c0 {6,S} """)
def getStatmechData(self, molecule, thermoModel): """ Use the previously-loaded frequency database to generate a set of characteristic group frequencies corresponding to the speficied `molecule`. The provided thermo data in `thermoModel` is used to fit some frequencies and all hindered rotors to heat capacity data. """ conformer = Conformer() # Compute spin multiplicity # For closed-shell molecule the spin multiplicity is 1 # For monoradicals the spin multiplicity is 2 # For higher-order radicals the highest allowed spin multiplicity is assumed conformer.spinMultiplicity = molecule.getRadicalCount() + 1 # No need to determine rotational and vibrational modes for single atoms if len(molecule.atoms) < 2: return (conformer, None, None) linear = molecule.isLinear() numRotors = molecule.countInternalRotors() numVibrations = 3 * len(molecule.atoms) - (5 if linear else 6) - numRotors # Get characteristic frequency groups and the associated frequencies groupCount = self.getFrequencyGroups(molecule) frequencies = [] for entry, count in groupCount.iteritems(): if count != 0 and entry.data is not None: frequencies.extend(entry.data.generateFrequencies(count)) # Check that we have the right number of degrees of freedom specified if len(frequencies) > numVibrations: # We have too many vibrational modes difference = len(frequencies) - numVibrations # First try to remove hindered rotor modes until the proper number of modes remain if numRotors > difference: numRotors -= difference numVibrations = len(frequencies) logging.warning( 'For {0}, more characteristic frequencies were generated than vibrational modes allowed. Removed {1:d} internal rotors to compensate.' .format(molecule, difference)) # If that won't work, turn off functional groups until the problem is underspecified again else: groupsRemoved = 0 freqsRemoved = 0 freqCount = len(frequencies) while freqCount > numVibrations: minDegeneracy, minEntry = min([(entry.data.symmetry, entry) for entry in groupCount if groupCount[entry] > 0]) if groupCount[minEntry] > 1: groupCount[minEntry] -= 1 else: del groupCount[minEntry] groupsRemoved += 1 freqsRemoved += minDegeneracy freqCount -= minDegeneracy # Log warning logging.warning( 'For {0}, more characteristic frequencies were generated than vibrational modes allowed. Removed {1:d} groups ({2:d} frequencies) to compensate.' .format(molecule, groupsRemoved, freqsRemoved)) # Regenerate characteristic frequencies frequencies = [] for entry, count in groupCount.iteritems(): if count != 0: frequencies.extend( entry.data.generateFrequencies(count)) # Subtract out contributions to heat capacity from the group frequencies Tlist = numpy.arange(300.0, 1501.0, 100.0, numpy.float64) Cv = numpy.array( [thermoModel.getHeatCapacity(T) / constants.R for T in Tlist], numpy.float64) ho = HarmonicOscillator(frequencies=(frequencies, "cm^-1")) for i in range(Tlist.shape[0]): Cv[i] -= ho.getHeatCapacity(Tlist[i]) / constants.R # Subtract out translational modes Cv -= 1.5 # Subtract out external rotational modes Cv -= (1.5 if not linear else 1.0) # Subtract out PV term (Cp -> Cv) Cv -= 1.0 # Fit remaining frequencies and hindered rotors to the heat capacity data from statmechfit import fitStatmechToHeatCapacity modes = fitStatmechToHeatCapacity(Tlist, Cv, numVibrations - len(frequencies), numRotors, molecule) for mode in modes: if isinstance(mode, HarmonicOscillator): uncertainties = [0 for f in frequencies ] # probably shouldn't be zero frequencies.extend(mode.frequencies.value_si) uncertainties.extend(mode.frequencies.uncertainty) mode.frequencies.value_si = numpy.array( frequencies, numpy.float) mode.frequencies.uncertainty = numpy.array( uncertainties, numpy.float) break else: modes.insert( 0, HarmonicOscillator(frequencies=(frequencies, "cm^-1"))) conformer.modes = modes return (conformer, None, None)
def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): """ Load the molecular degree of freedom data from a log file created as the result of a Gaussian "Freq" quantum chemistry calculation. As Gaussian's guess of the external symmetry number is not always correct, you can use the `symmetry` parameter to substitute your own value; if not provided, the value in the Gaussian log file will be adopted. In a log file with multiple Thermochemistry sections, only the last one will be kept. """ modes = [] unscaled_frequencies = [] e0 = 0.0 if optical_isomers is None or symmetry is None: _optical_isomers, _symmetry, _ = self.get_symmetry_properties() if optical_isomers is None: optical_isomers = _optical_isomers if symmetry is None: symmetry = _symmetry with open(self.path, 'r') as f: line = f.readline() while line != '': # Read the spin multiplicity if not explicitly given if spin_multiplicity == 0 and 'Multiplicity =' in line: spin_multiplicity = int(line.split()[-1]) logging.debug('Conformer {0} is assigned a spin multiplicity of {1}' .format(label, spin_multiplicity)) # The data we want is in the Thermochemistry section of the output if '- Thermochemistry -' in line: modes = [] in_partition_functions = False line = f.readline() while line != '': # This marks the end of the thermochemistry section if '-------------------------------------------------------------------' in line: break # Read molecular mass for external translational modes elif 'Molecular mass:' in line: mass = float(line.split()[2]) translation = IdealGasTranslation(mass=(mass, "amu")) modes.append(translation) # Read moments of inertia for external rotational modes elif 'Rotational constants (GHZ):' in line: inertia = [float(d) for d in line.split()[-3:]] for i in range(3): inertia[i] = constants.h / (8 * constants.pi * constants.pi * inertia[i] * 1e9) \ * constants.Na * 1e23 rotation = NonlinearRotor(inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) elif 'Rotational constant (GHZ):' in line: inertia = [float(line.split()[3])] inertia[0] = constants.h / (8 * constants.pi * constants.pi * inertia[0] * 1e9) \ * constants.Na * 1e23 rotation = LinearRotor(inertia=(inertia[0], "amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) # Read vibrational modes elif 'Vibrational temperatures:' in line: frequencies = [] frequencies.extend([float(d) for d in line.split()[2:]]) line = f.readline() frequencies.extend([float(d) for d in line.split()[1:]]) line = f.readline() while line.strip() != '': frequencies.extend([float(d) for d in line.split()]) line = f.readline() # Convert from K to cm^-1 if len(frequencies) > 0: frequencies = [freq * 0.695039 for freq in frequencies] # kB = 0.695039 cm^-1/K unscaled_frequencies = frequencies vibration = HarmonicOscillator(frequencies=(frequencies, "cm^-1")) modes.append(vibration) # Read ground-state energy elif 'Sum of electronic and zero-point Energies=' in line: e0 = float(line.split()[6]) * 4.35974394e-18 * constants.Na # Read spin multiplicity if above method was unsuccessful elif 'Electronic' in line and in_partition_functions and spin_multiplicity == 0: spin_multiplicity = int(float(line.split()[1].replace('D', 'E'))) elif 'Log10(Q)' in line: in_partition_functions = True # Read the next line in the file line = f.readline() if 'Error termination' in line: raise LogError(f'The Gaussian job in {self.path} did not converge.') # Read the next line in the file line = f.readline() return Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), unscaled_frequencies
def loadFAMEInput(path, moleculeDict=None): """ Load the contents of a FAME input file into the MEASURE object. FAME is an early version of MEASURE written in Fortran and used by RMG-Java. This script enables importing FAME input files into MEASURE so we can use the additional functionality that MEASURE provides. Note that it is mostly designed to load the FAME input files generated automatically by RMG-Java, and may not load hand-crafted FAME input files. If you specify a `moleculeDict`, then this script will use it to associate the species with their structures. """ def readMeaningfulLine(f): line = f.readline() while line != '': line = line.strip() if len(line) > 0 and line[0] != '#': return line else: line = f.readline() return '' moleculeDict = moleculeDict or {} logging.info('Loading file "{0}"...'.format(path)) f = open(path) job = PressureDependenceJob(network=None) # Read method method = readMeaningfulLine(f).lower() if method == 'modifiedstrongcollision': job.method = 'modified strong collision' elif method == 'reservoirstate': job.method = 'reservoir state' # Read temperatures Tcount, Tunits, Tmin, Tmax = readMeaningfulLine(f).split() job.Tmin = Quantity(float(Tmin), Tunits) job.Tmax = Quantity(float(Tmax), Tunits) job.Tcount = int(Tcount) Tlist = [] for i in range(int(Tcount)): Tlist.append(float(readMeaningfulLine(f))) job.Tlist = Quantity(Tlist, Tunits) # Read pressures Pcount, Punits, Pmin, Pmax = readMeaningfulLine(f).split() job.Pmin = Quantity(float(Pmin), Punits) job.Pmax = Quantity(float(Pmax), Punits) job.Pcount = int(Pcount) Plist = [] for i in range(int(Pcount)): Plist.append(float(readMeaningfulLine(f))) job.Plist = Quantity(Plist, Punits) # Read interpolation model model = readMeaningfulLine(f).split() if model[0].lower() == 'chebyshev': job.interpolationModel = ('chebyshev', int(model[1]), int(model[2])) elif model[0].lower() == 'pdeparrhenius': job.interpolationModel = ('pdeparrhenius',) # Read grain size or number of grains job.minimumGrainCount = 0 job.maximumGrainSize = None for i in range(2): data = readMeaningfulLine(f).split() if data[0].lower() == 'numgrains': job.minimumGrainCount = int(data[1]) elif data[0].lower() == 'grainsize': job.maximumGrainSize = (float(data[2]), data[1]) # A FAME file is almost certainly created during an RMG job, so use RMG mode job.rmgmode = True # Create the Network job.network = Network() # Read collision model data = readMeaningfulLine(f) assert data.lower() == 'singleexpdown' alpha0units, alpha0 = readMeaningfulLine(f).split() T0units, T0 = readMeaningfulLine(f).split() n = readMeaningfulLine(f) energyTransferModel = SingleExponentialDown( alpha0 = Quantity(float(alpha0), alpha0units), T0 = Quantity(float(T0), T0units), n = float(n), ) speciesDict = {} # Read bath gas parameters bathGas = Species(label='bath_gas', energyTransferModel=energyTransferModel) molWtunits, molWt = readMeaningfulLine(f).split() if molWtunits == 'u': molWtunits = 'amu' bathGas.molecularWeight = Quantity(float(molWt), molWtunits) sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split() epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split() assert epsilonLJunits == 'J' bathGas.transportData = TransportData( sigma = Quantity(float(sigmaLJ), sigmaLJunits), epsilon = Quantity(float(epsilonLJ) / constants.kB, 'K'), ) job.network.bathGas = {bathGas: 1.0} # Read species data Nspec = int(readMeaningfulLine(f)) for i in range(Nspec): species = Species() species.conformer = Conformer() species.energyTransferModel = energyTransferModel # Read species label species.label = readMeaningfulLine(f) speciesDict[species.label] = species if species.label in moleculeDict: species.molecule = [moleculeDict[species.label]] # Read species E0 E0units, E0 = readMeaningfulLine(f).split() species.conformer.E0 = Quantity(float(E0), E0units) species.conformer.E0.units = 'kJ/mol' # Read species thermo data H298units, H298 = readMeaningfulLine(f).split() S298units, S298 = readMeaningfulLine(f).split() Cpcount, Cpunits = readMeaningfulLine(f).split() Cpdata = [] for i in range(int(Cpcount)): Cpdata.append(float(readMeaningfulLine(f))) if S298units == 'J/mol*K': S298units = 'J/(mol*K)' if Cpunits == 'J/mol*K': Cpunits = 'J/(mol*K)' species.thermo = ThermoData( H298 = Quantity(float(H298), H298units), S298 = Quantity(float(S298), S298units), Tdata = Quantity([300,400,500,600,800,1000,1500], "K"), Cpdata = Quantity(Cpdata, Cpunits), Cp0 = (Cpdata[0], Cpunits), CpInf = (Cpdata[-1], Cpunits), ) # Read species collision parameters molWtunits, molWt = readMeaningfulLine(f).split() if molWtunits == 'u': molWtunits = 'amu' species.molecularWeight = Quantity(float(molWt), molWtunits) sigmaLJunits, sigmaLJ = readMeaningfulLine(f).split() epsilonLJunits, epsilonLJ = readMeaningfulLine(f).split() assert epsilonLJunits == 'J' species.transportData = TransportData( sigma = Quantity(float(sigmaLJ), sigmaLJunits), epsilon = Quantity(float(epsilonLJ) / constants.kB, 'K'), ) # Read species vibrational frequencies freqCount, freqUnits = readMeaningfulLine(f).split() frequencies = [] for j in range(int(freqCount)): frequencies.append(float(readMeaningfulLine(f))) species.conformer.modes.append(HarmonicOscillator( frequencies = Quantity(frequencies, freqUnits), )) # Read species external rotors rotCount, rotUnits = readMeaningfulLine(f).split() if int(rotCount) > 0: raise NotImplementedError('Cannot handle external rotational modes in FAME input.') # Read species internal rotors freqCount, freqUnits = readMeaningfulLine(f).split() frequencies = [] for j in range(int(freqCount)): frequencies.append(float(readMeaningfulLine(f))) barrCount, barrUnits = readMeaningfulLine(f).split() barriers = [] for j in range(int(barrCount)): barriers.append(float(readMeaningfulLine(f))) if barrUnits == 'cm^-1': barrUnits = 'J/mol' barriers = [barr * constants.h * constants.c * constants.Na * 100. for barr in barriers] elif barrUnits in ['Hz', 's^-1']: barrUnits = 'J/mol' barriers = [barr * constants.h * constants.Na for barr in barriers] elif barrUnits != 'J/mol': raise Exception('Unexpected units "{0}" for hindered rotor barrier height.'.format(barrUnits)) inertia = [V0 / 2.0 / (nu * constants.c * 100.)**2 / constants.Na for nu, V0 in zip(frequencies, barriers)] for I, V0 in zip(inertia, barriers): species.conformer.modes.append(HinderedRotor( inertia = Quantity(I,"kg*m^2"), barrier = Quantity(V0,barrUnits), symmetry = 1, semiclassical = False, )) # Read overall symmetry number species.conformer.spinMultiplicity = int(readMeaningfulLine(f)) # Read isomer, reactant channel, and product channel data Nisom = int(readMeaningfulLine(f)) Nreac = int(readMeaningfulLine(f)) Nprod = int(readMeaningfulLine(f)) for i in range(Nisom): data = readMeaningfulLine(f).split() assert data[0] == '1' job.network.isomers.append(speciesDict[data[1]]) for i in range(Nreac): data = readMeaningfulLine(f).split() assert data[0] == '2' job.network.reactants.append([speciesDict[data[1]], speciesDict[data[2]]]) for i in range(Nprod): data = readMeaningfulLine(f).split() if data[0] == '1': job.network.products.append([speciesDict[data[1]]]) elif data[0] == '2': job.network.products.append([speciesDict[data[1]], speciesDict[data[2]]]) # Read path reactions Nrxn = int(readMeaningfulLine(f)) for i in range(Nrxn): # Read and ignore reaction equation equation = readMeaningfulLine(f) reaction = Reaction(transitionState=TransitionState(), reversible=True) job.network.pathReactions.append(reaction) reaction.transitionState.conformer = Conformer() # Read reactant and product indices data = readMeaningfulLine(f).split() reac = int(data[0]) - 1 prod = int(data[1]) - 1 if reac < Nisom: reaction.reactants = [job.network.isomers[reac]] elif reac < Nisom+Nreac: reaction.reactants = job.network.reactants[reac-Nisom] else: reaction.reactants = job.network.products[reac-Nisom-Nreac] if prod < Nisom: reaction.products = [job.network.isomers[prod]] elif prod < Nisom+Nreac: reaction.products = job.network.reactants[prod-Nisom] else: reaction.products = job.network.products[prod-Nisom-Nreac] # Read reaction E0 E0units, E0 = readMeaningfulLine(f).split() reaction.transitionState.conformer.E0 = Quantity(float(E0), E0units) reaction.transitionState.conformer.E0.units = 'kJ/mol' # Read high-pressure limit kinetics data = readMeaningfulLine(f) assert data.lower() == 'arrhenius' Aunits, A = readMeaningfulLine(f).split() if '/' in Aunits: index = Aunits.find('/') Aunits = '{0}/({1})'.format(Aunits[0:index], Aunits[index+1:]) Eaunits, Ea = readMeaningfulLine(f).split() n = readMeaningfulLine(f) reaction.kinetics = Arrhenius( A = Quantity(float(A), Aunits), Ea = Quantity(float(Ea), Eaunits), n = Quantity(float(n)), ) reaction.kinetics.Ea.units = 'kJ/mol' f.close() job.network.isomers = [Configuration(isomer) for isomer in job.network.isomers] job.network.reactants = [Configuration(*reactants) for reactants in job.network.reactants] job.network.products = [Configuration(*products) for products in job.network.products] return job
def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): """ Load the molecular degree of freedom data from a log file created as the result of a MolPro "Freq" quantum chemistry calculation with the thermo printed. """ modes = [] unscaled_frequencies = [] e0 = 0.0 if optical_isomers is None or symmetry is None: _optical_isomers, _symmetry, _ = self.get_symmetry_properties() if optical_isomers is None: optical_isomers = _optical_isomers if symmetry is None: symmetry = _symmetry with open(self.path, 'r') as f: line = f.readline() while line != '': # Read the spin multiplicity if not explicitly given if spin_multiplicity == 0 and 'spin' in line: splits = line.replace('=', ' ').replace(',', ' ').split(' ') for i, s in enumerate(splits): if 'spin' in s: spin_multiplicity = int(splits[i + 1]) + 1 logging.debug( 'Conformer {0} is assigned a spin multiplicity of {1}' .format(label, spin_multiplicity)) break if spin_multiplicity == 0 and 'SPIN SYMMETRY' in line: spin_symmetry = line.split()[-1] if spin_symmetry == 'Singlet': spin_multiplicity = 1 elif spin_symmetry == 'Doublet': spin_multiplicity = 2 elif spin_symmetry == 'Triplet': spin_multiplicity = 3 elif spin_symmetry == 'Quartet': spin_multiplicity = 4 elif spin_symmetry == 'Quintet': spin_multiplicity = 5 elif spin_symmetry == 'Sextet': spin_multiplicity = 6 if spin_multiplicity: logging.debug( 'Conformer {0} is assigned a spin multiplicity of {1}' .format(label, spin_multiplicity)) break # The data we want is in the Thermochemistry section of the output if 'THERMODYNAMICAL' in line: modes = [] line = f.readline() while line != '': # This marks the end of the thermochemistry section if '*************************************************' in line: break # Read molecular mass for external translational modes elif 'Molecular Mass:' in line: mass = float(line.split()[2]) translation = IdealGasTranslation(mass=(mass, "amu")) modes.append(translation) # Read moments of inertia for external rotational modes elif 'Rotational Constants' in line and line.split( )[-1] == '[GHz]': inertia = [float(d) for d in line.split()[-4:-1]] for i in range(3): inertia[i] = constants.h / (8 * constants.pi * constants.pi * inertia[i] * 1e9) \ * constants.Na * 1e23 rotation = NonlinearRotor( inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) elif 'Rotational Constant' in line and line.split( )[3] == '[GHz]': inertia = float(line.split()[2]) inertia = constants.h / (8 * constants.pi * constants.pi * inertia * 1e9) \ * constants.Na * 1e23 rotation = LinearRotor(inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) # Read vibrational modes elif 'Vibrational Temperatures' in line: frequencies = [] frequencies.extend( [float(d) for d in line.split()[3:]]) line = f.readline() while line.strip() != '': frequencies.extend( [float(d) for d in line.split()]) line = f.readline() # Convert from K to cm^-1 if len(frequencies) > 0: frequencies = [ freq * 0.695039 for freq in frequencies ] # kB = 0.695039 cm^-1/K unscaled_frequencies = frequencies vibration = HarmonicOscillator( frequencies=(frequencies, "cm^-1")) modes.append(vibration) # Read the next line in the file line = f.readline() # Read the next line in the file line = f.readline() return Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), unscaled_frequencies
def loadConformer(self, symmetry=None, spinMultiplicity=0, opticalIsomers=None, label=''): """ Load the molecular degree of freedom data from an output file created as the result of a QChem "Freq" calculation. As QChem's guess of the external symmetry number is not always correct, you can use the `symmetry` parameter to substitute your own value; if not provided, the value in the QChem output file will be adopted. """ modes = []; freq = []; mmass = []; rot = []; inertia = [] unscaled_frequencies = [] E0 = 0.0 if opticalIsomers is None or symmetry is None: _opticalIsomers, _symmetry = self.get_optical_isomers_and_symmetry_number() if opticalIsomers is None: opticalIsomers = _opticalIsomers if symmetry is None: symmetry = _symmetry f = open(self.path, 'r') line = f.readline() while line != '': # Read spin multiplicity if not explicitly given if '$molecule' in line and spinMultiplicity == 0: line = f.readline() if len(line.split()) == 2: spinMultiplicity = int(float(line.split()[1])) logging.debug('Conformer {0} is assigned a spin multiplicity of {1}'.format(label,spinMultiplicity)) # The rest of the data we want is in the Thermochemistry section of the output elif 'VIBRATIONAL ANALYSIS' in line: modes = [] line = f.readline() while line != '': # This marks the end of the thermochemistry section if 'Thank you very much for using Q-Chem.' in line: break # Read vibrational modes elif 'VIBRATIONAL FREQUENCIES (CM**-1)' in line: frequencies = [] while 'STANDARD THERMODYNAMIC QUANTITIES AT' not in line: if ' Frequency:' in line: if len(line.split()) == 4: frequencies.extend([float(d) for d in line.split()[-3:]]) elif len(line.split()) == 3: frequencies.extend([float(d) for d in line.split()[-2:]]) elif len(line.split()) == 2: frequencies.extend([float(d) for d in line.split()[-1:]]) line = f.readline() line = f.readline() # If there is an imaginary frequency, remove it if frequencies[0] < 0.0: frequencies = frequencies[1:] unscaled_frequencies = frequencies vibration = HarmonicOscillator(frequencies=(frequencies,"cm^-1")) # modes.append(vibration) freq.append(vibration) # Read molecular mass for external translational modes elif 'Molecular Mass:' in line: mass = float(line.split()[2]) translation = IdealGasTranslation(mass=(mass,"amu")) # modes.append(translation) mmass.append(translation) # Read moments of inertia for external rotational modes, given in atomic units elif 'Eigenvalues --' in line: inertia = [float(d) for d in line.split()[-3:]] # Read the next line in the file line = f.readline() # Read the next line in the file line = f.readline() if len(inertia): if inertia[0] == 0.0: # If the first eigenvalue is 0, the rotor is linear inertia.remove(0.0) logging.debug('inertia is {}'.format(str(inertia))) for i in range(2): inertia[i] *= (constants.a0 / 1e-10) ** 2 inertia = numpy.sqrt(inertia[0] * inertia[1]) rotation = LinearRotor(inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) rot.append(rotation) else: for i in range(3): inertia[i] *= (constants.a0 / 1e-10) ** 2 rotation = NonlinearRotor(inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) # modes.append(rotation) rot.append(rotation) inertia = [] # Close file when finished f.close() modes = mmass + rot + freq return Conformer(E0=(E0*0.001,"kJ/mol"), modes=modes, spinMultiplicity=spinMultiplicity, opticalIsomers=opticalIsomers), unscaled_frequencies
def getStatmechData(self, molecule, thermoModel): """ Use the previously-loaded frequency database to generate a set of characteristic group frequencies corresponding to the speficied `molecule`. The provided thermo data in `thermoModel` is used to fit some frequencies and all hindered rotors to heat capacity data. """ conformer = Conformer() # Compute spin multiplicity # For closed-shell molecule the spin multiplicity is 1 # For monoradicals the spin multiplicity is 2 # For higher-order radicals the highest allowed spin multiplicity is assumed conformer.spinMultiplicity = molecule.getRadicalCount() + 1 # No need to determine rotational and vibrational modes for single atoms if len(molecule.atoms) < 2: return (conformer, None, None) linear = molecule.isLinear() numRotors = molecule.countInternalRotors() numVibrations = 3 * len(molecule.atoms) - (5 if linear else 6) - numRotors # Get characteristic frequency groups and the associated frequencies groupCount = self.getFrequencyGroups(molecule) frequencies = [] for entry, count in groupCount.iteritems(): if count != 0 and entry.data is not None: frequencies.extend(entry.data.generateFrequencies(count)) # Check that we have the right number of degrees of freedom specified if len(frequencies) > numVibrations: # We have too many vibrational modes difference = len(frequencies) - numVibrations # First try to remove hindered rotor modes until the proper number of modes remain if numRotors > difference: numRotors -= difference numVibrations = len(frequencies) logging.warning('For {0}, more characteristic frequencies were generated than vibrational modes allowed. Removed {1:d} internal rotors to compensate.'.format(molecule, difference)) # If that won't work, turn off functional groups until the problem is underspecified again else: groupsRemoved = 0 freqsRemoved = 0 freqCount = len(frequencies) while freqCount > numVibrations: minDegeneracy, minEntry = min([(entry.data.symmetry, entry) for entry in groupCount if groupCount[entry] > 0]) if groupCount[minEntry] > 1: groupCount[minEntry] -= 1 else: del groupCount[minEntry] groupsRemoved += 1 freqsRemoved += minDegeneracy freqCount -= minDegeneracy # Log warning logging.warning('For {0}, more characteristic frequencies were generated than vibrational modes allowed. Removed {1:d} groups ({2:d} frequencies) to compensate.'.format(molecule, groupsRemoved, freqsRemoved)) # Regenerate characteristic frequencies frequencies = [] for entry, count in groupCount.iteritems(): if count != 0: frequencies.extend(entry.data.generateFrequencies(count)) # Subtract out contributions to heat capacity from the group frequencies Tlist = numpy.arange(300.0, 1501.0, 100.0, numpy.float64) Cv = numpy.array([thermoModel.getHeatCapacity(T) / constants.R for T in Tlist], numpy.float64) ho = HarmonicOscillator(frequencies=(frequencies,"cm^-1")) for i in range(Tlist.shape[0]): Cv[i] -= ho.getHeatCapacity(Tlist[i]) / constants.R # Subtract out translational modes Cv -= 1.5 # Subtract out external rotational modes Cv -= (1.5 if not linear else 1.0) # Subtract out PV term (Cp -> Cv) Cv -= 1.0 # Fit remaining frequencies and hindered rotors to the heat capacity data from statmechfit import fitStatmechToHeatCapacity modes = fitStatmechToHeatCapacity(Tlist, Cv, numVibrations - len(frequencies), numRotors, molecule) for mode in modes: if isinstance(mode, HarmonicOscillator): uncertainties = [0 for f in frequencies] # probably shouldn't be zero frequencies.extend(mode.frequencies.value_si) uncertainties.extend(mode.frequencies.uncertainty) mode.frequencies.value_si = numpy.array(frequencies, numpy.float) mode.frequencies.uncertainty = numpy.array(uncertainties, numpy.float) break else: modes.insert(0, HarmonicOscillator(frequencies=(frequencies,"cm^-1"))) conformer.modes = modes return (conformer, None, None)
def loadConformer(self, symmetry=None, spinMultiplicity=None, opticalIsomers=1): """ Load the molecular degree of freedom data from a log file created as the result of a Gaussian "Freq" quantum chemistry calculation. As Gaussian's guess of the external symmetry number is not always correct, you can use the `symmetry` parameter to substitute your own value; if not provided, the value in the Gaussian log file will be adopted. In a log file with multiple Thermochemistry sections, only the last one will be kept. """ modes = [] E0 = 0.0 f = open(self.path, 'r') line = f.readline() while line != '': # The data we want is in the Thermochemistry section of the output if '- Thermochemistry -' in line: modes = [] inPartitionFunctions = False line = f.readline() while line != '': # This marks the end of the thermochemistry section if '-------------------------------------------------------------------' in line: break # Read molecular mass for external translational modes elif 'Molecular mass:' in line: mass = float(line.split()[2]) translation = IdealGasTranslation(mass=(mass,"amu")) modes.append(translation) # Read Gaussian's estimate of the external symmetry number elif 'Rotational symmetry number' in line and symmetry is None: symmetry = int(float(line.split()[3])) # Read moments of inertia for external rotational modes elif 'Rotational constants (GHZ):' in line: inertia = [float(d) for d in line.split()[-3:]] for i in range(3): inertia[i] = constants.h / (8 * constants.pi * constants.pi * inertia[i] * 1e9) *constants.Na*1e23 rotation = NonlinearRotor(inertia=(inertia,"amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) elif 'Rotational constant (GHZ):' in line: inertia = [float(line.split()[3])] inertia[0] = constants.h / (8 * constants.pi * constants.pi * inertia[0] * 1e9) *constants.Na*1e23 rotation = LinearRotor(inertia=(inertia[0],"amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) # Read vibrational modes elif 'Vibrational temperatures:' in line: frequencies = [] frequencies.extend([float(d) for d in line.split()[2:]]) line = f.readline() frequencies.extend([float(d) for d in line.split()[1:]]) line = f.readline() while line.strip() != '': frequencies.extend([float(d) for d in line.split()]) line = f.readline() # Convert from K to cm^-1 if len(frequencies) > 0: frequencies = [freq * 0.695039 for freq in frequencies] # kB = 0.695039 cm^-1/K vibration = HarmonicOscillator(frequencies=(frequencies,"cm^-1")) modes.append(vibration) # Read ground-state energy elif 'Sum of electronic and zero-point Energies=' in line: E0 = float(line.split()[6]) * 4.35974394e-18 * constants.Na # Read spin multiplicity if not explicitly given elif 'Electronic' in line and inPartitionFunctions and spinMultiplicity is None: spinMultiplicity = int(float(line.split()[1].replace('D', 'E'))) elif 'Log10(Q)' in line: inPartitionFunctions = True # Read the next line in the file line = f.readline() # Read the next line in the file line = f.readline() # Close file when finished f.close() return Conformer(E0=(E0*0.001,"kJ/mol"), modes=modes, spinMultiplicity=spinMultiplicity, opticalIsomers=opticalIsomers)
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 loadConformer(self, symmetry=None, spinMultiplicity=None, opticalIsomers=1): """ Load the molecular degree of freedom data from a log file created as the result of a Qchem "Freq" calculation. As Qchem's guess of the external symmetry number is not always correct, you can use the `symmetry` parameter to substitute your own value; if not provided, the value in the Qchem output file will be adopted. """ modes = [] freq = [] mmass = [] rot = [] E0 = 0.0 f = open(self.path, 'r') line = f.readline() while line != '': # The data we want is in the Thermochemistry section of the output if 'VIBRATIONAL ANALYSIS' in line: modes = [] inPartitionFunctions = False line = f.readline() while line != '': # This marks the end of the thermochemistry section if 'Thank you very much for using Q-Chem.' in line: break # Read vibrational modes elif 'VIBRATIONAL FREQUENCIES (CM**-1)' in line: frequencies = [] while 'STANDARD THERMODYNAMIC QUANTITIES AT' not in line: if ' Frequency:' in line: frequencies.extend( [float(d) for d in line.split()[-3:]]) line = f.readline() line = f.readline() # If there is an imaginary frequency, remove it if frequencies[0] < 0.0: frequencies = frequencies[1:] vibration = HarmonicOscillator( frequencies=(frequencies, "cm^-1")) #modes.append(vibration) freq.append(vibration) # Read molecular mass for external translational modes elif 'Molecular Mass:' in line: mass = float(line.split()[2]) translation = IdealGasTranslation(mass=(mass, "amu")) #modes.append(translation) mmass.append(translation) # Read moments of inertia for external rotational modes, given in atomic units elif 'Eigenvalues --' in line: inertia = [float(d) for d in line.split()[-3:]] # If the first eigenvalue is 0, the rotor is linear if inertia[0] == 0.0: inertia.remove(0.0) for i in range(2): inertia[i] *= (constants.a0 / 1e-10)**2 rotation = LinearRotor( inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) #modes.append(rotation) rot.append(rotation) else: for i in range(3): inertia[i] *= (constants.a0 / 1e-10)**2 rotation = NonlinearRotor( inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) #modes.append(rotation) rot.append(rotation) # Read Qchem's estimate of the external rotational symmetry number, which may very well be incorrect elif 'Rotational Symmetry Number is' in line and symmetry is None: symmetry = int(float(line.split()[4])) elif 'Final energy is' in line: E0 = float( line.split()[3]) * constants.E_h * constants.Na print 'energy is' + str(E0) # Read ZPE and add to ground-state energy # NEED TO MULTIPLY ZPE BY scaling factor! elif 'Zero point vibrational energy:' in line: ZPE = float(line.split()[4]) * 4184 E0 = E0 + ZPE # Read spin multiplicity if not explicitly given # elif 'Electronic' in line and inPartitionFunctions and spinMultiplicity is None: # spinMultiplicity = int(float(line.split()[1].replace('D', 'E'))) # elif 'Log10(Q)' in line: # inPartitionFunctions = True # Read the next line in the file line = f.readline() # Read the next line in the file line = f.readline() # Close file when finished f.close() modes = mmass + rot + freq #modes.append(mmass), modes.append(rot), modes.append(freq) return Conformer(E0=(E0 * 0.001, "kJ/mol"), modes=modes, spinMultiplicity=spinMultiplicity, opticalIsomers=opticalIsomers)
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