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 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 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 loadConfiguration(energyLog, geomLog, statesLog, extSymmetry, spinMultiplicity, freqScaleFactor, linear, rotors, atoms, bonds, E0=None, TS=False): logging.debug(' Reading optimized geometry...') log = GaussianLog(geomLog) geom = log.loadGeometry() logging.debug(' Reading energy...') if E0 is None: if energyLog is not None: log = GaussianLog(energyLog) E0 = log.loadEnergy() else: E0 *= 4.35974394e-18 * constants.Na # Hartree/particle to J/mol E0 = applyEnergyCorrections(E0, modelChemistry, atoms, bonds) logging.debug(' E0 (0 K) = %g kcal/mol' % (E0 / 4184)) logging.debug(' Reading molecular degrees of freedom...') log = GaussianLog(statesLog) states = log.loadStates(symmetry=extSymmetry) states.spinMultiplicity = spinMultiplicity F = log.loadForceConstantMatrix() if F is not None and len(geom.mass) > 1 and len(rotors) > 0: logging.debug(' Fitting %i hindered rotors...' % len(rotors)) for scanLog, pivots, top, symmetry in rotors: log = GaussianLog(scanLog) Vlist, angle = log.loadScanEnergies() inertia = geom.getInternalReducedMomentOfInertia(pivots, top) barr, symm = log.fitCosinePotential() cosineRotor = HinderedRotor(inertia=(inertia*constants.Na*1e23,"amu*angstrom^2"), symmetry=symm, barrier=(barr/4184.,"kcal/mol")) fourier = log.fitFourierSeriesPotential() fourierRotor = HinderedRotor(inertia=(inertia*constants.Na*1e23,"amu*angstrom^2"), symmetry=symmetry, fourier=(fourier,"J/mol")) Vlist_cosine = cosineRotor.getPotential(angle) Vlist_fourier = fourierRotor.getPotential(angle) rms_cosine = numpy.sqrt(numpy.sum((Vlist_cosine - Vlist) * (Vlist_cosine - Vlist)) / (len(Vlist) - 1)) / 4184. rms_fourier = numpy.sqrt(numpy.sum((Vlist_fourier - Vlist) * (Vlist_fourier - Vlist))/ (len(Vlist) - 1)) / 4184. print rms_cosine, rms_fourier, symm, symmetry # Keep the rotor with the most accurate potential rotor = cosineRotor if rms_cosine < rms_fourier else fourierRotor # However, keep the cosine rotor if it is accurate enough, the # fourier rotor is not significantly more accurate, and the cosine # rotor has the correct symmetry if rms_cosine < 0.05 and rms_cosine / rms_fourier > 0.25 and rms_cosine / rms_fourier < 4.0 and symmetry == symm: rotor = cosineRotor states.modes.append(rotor) import pylab phi = numpy.arange(0, 6.3, 0.02, numpy.float64) fig = pylab.figure() pylab.plot(angle, Vlist / 4184, 'ok') linespec = '-r' if rotor is cosineRotor else '--r' pylab.plot(phi, cosineRotor.getPotential(phi) / 4184, linespec) linespec = '-b' if rotor is fourierRotor else '--b' pylab.plot(phi, fourierRotor.getPotential(phi) / 4184, linespec) pylab.legend(['scan', 'cosine', 'fourier'], loc=1) pylab.xlim(0, 2*math.pi) axes = fig.get_axes()[0] axes.set_xticks([float(j*math.pi/4) for j in range(0,9)]) axes.set_xticks([float(j*math.pi/8) for j in range(0,17)], minor=True) axes.set_xticklabels(['$0$', '$\pi/4$', '$\pi/2$', '$3\pi/4$', '$\pi$', '$5\pi/4$', '$3\pi/2$', '$7\pi/4$', '$2\pi$']) pylab.show() logging.debug(' Determining frequencies from reduced force constant matrix...') frequencies = list(projectRotors(geom, F, rotors, linear, TS)) elif len(states.modes) > 2: frequencies = states.modes[2].frequencies.values rotors = [] else: frequencies = [] rotors = [] for mode in states.modes: if isinstance(mode, HarmonicOscillator): mode.frequencies.values = numpy.array(frequencies, numpy.float) * freqScaleFactor return E0, geom, states