def fitStatmechPseudo(Tlist, Cvlist, Nvib, Nrot, molecule=None): """ Fit `Nvib` harmonic oscillator and `Nrot` hindered internal rotor modes to the provided dimensionless heat capacities `Cvlist` at temperatures `Tlist` in K. This method assumes that there are relatively few heat capacity points provided, so the vibrations must be combined into one real vibration and two "pseudo-vibrations" and the hindered rotors must be combined into a single "pseudo-rotor". """ # Construct the lower and upper bounds for each variable bounds = [] # x[0] corresponds to the first harmonic oscillator (real) frequency bounds.append((hoFreqLowerBound, hoFreqUpperBound)) # x[1] corresponds to the degeneracy of the second harmonic oscillator bounds.append((1.0, float(Nvib - 2))) # x[2] corresponds to the second harmonic oscillator pseudo-frequency bounds.append((hoFreqLowerBound, hoFreqUpperBound)) # x[3] corresponds to the third harmonic oscillator pseudo-frequency bounds.append((hoFreqLowerBound, hoFreqUpperBound)) # x[4] corresponds to the hindered rotor pseudo-frequency bounds.append((hrFreqLowerBound, hrFreqUpperBound)) # x[5] corresponds to the hindered rotor pseudo-barrier bounds.append((hrBarrLowerBound, hrBarrUpperBound)) # Construct the initial guess x0 = numpy.zeros(6, numpy.float64) # Initial guess x0[0] = 300.0 x0[1] = float(math.floor((Nvib - 1) / 2.0)) x0[2] = 800.0 x0[3] = 1600.0 x0[4] = 100.0 x0[5] = 300.0 # Execute the optimization fit = PseudoFit(Tlist, Cvlist, Nvib, Nrot) fit.initialize(Neq=len(Tlist), Nvars=len(x0), Ncons=0, bounds=bounds, maxIter=maxIter) x, igo = fit.solve(x0) # Check that the results of the optimization are valid if not numpy.isfinite(x).all(): raise StatmechFitError('Returned solution vector is nonsensical: x = {0}.'.format(x)) if igo == 8: logging.warning('Maximum number of iterations reached when fitting spectral data for {0}.'.format(molecule.toSMILES())) # Postprocess optimization results Nvib2 = int(round(x[1])) Nvib3 = Nvib - Nvib2 - 1 if Nvib2 < 0 or Nvib2 > Nvib-1 or Nvib3 < 0 or Nvib3 > Nvib-1: raise StatmechFitError('Invalid degeneracies {0} and {1} fitted for pseudo-frequencies.'.format(Nvib2, Nvib3)) vib = [x[0]] for i in range(Nvib2): vib.append(x[2]) for i in range(Nvib3): vib.append(x[3]) hind = [] for i in range(Nrot): hind.append((x[4], x[5])) return vib, hind
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 fit_statmech_direct(Tlist, Cvlist, n_vib, n_rot, molecule=None): """ Fit `n_vib` harmonic oscillator and `n_rot` hindered internal rotor modes to the provided dimensionless heat capacities `Cvlist` at temperatures `Tlist` in K. This method assumes that there are enough heat capacity points provided that the vibrational frequencies and hindered rotation frequency- barrier pairs can be fit directly. """ # Construct the lower and upper bounds for each variable bounds = [] # Bounds for harmonic oscillator frequencies for i in range(n_vib): bounds.append((ho_freq_lower_bound, ho_freq_upper_bound)) # Bounds for hindered rotor frequencies and barrier heights for i in range(n_rot): bounds.append((hr_freq_lower_bound, hr_freq_upper_bound)) bounds.append((hr_barr_lower_bound, hr_barr_upper_bound)) # Construct the initial guess # Initial guesses within each mode type must be distinct or else the # optimization will fail x0 = np.zeros(n_vib + 2 * n_rot, np.float64) # Initial guess for harmonic oscillator frequencies if n_vib > 0: x0[0] = 200.0 x0[1:n_vib] = np.linspace(800.0, 1600.0, n_vib - 1) # Initial guess for hindered rotor frequencies and barrier heights if n_rot > 0: x0[n_vib] = 100.0 x0[n_vib + 1] = 100.0 for i in range(1, n_rot): x0[n_vib + 2 * i] = x0[n_vib + 2 * i - 2] + 20.0 x0[n_vib + 2 * i + 1] = x0[n_vib + 2 * i - 1] + 100.0 # Execute the optimization fit = DirectFit(Tlist, Cvlist, n_vib, n_rot) fit.initialize(Neq=len(Tlist), Nvars=len(x0), Ncons=0, bounds=bounds, maxIter=max_iter) x, igo = fit.solve(x0) # Check that the results of the optimization are valid if not np.isfinite(x).all(): raise StatmechFitError('Returned solution vector is nonsensical: x = {0}.'.format(x)) if igo == 8: logging.warning('Maximum number of iterations reached when fitting spectral data for ' '{0}.'.format(molecule.to_smiles())) elif igo > 8: logging.warning('A solver error occured when fitting spectral data for {0}.'.format(molecule.to_smiles())) logging.debug('Fitting remaining heat capacity to {0} vibrations and {1} rotations'.format(n_vib, n_rot)) logging.debug('The residuals for heat capacity values is {}'.format(fit.evaluate(x)[0])) # Postprocess optimization results vib = list(x[0:n_vib]) hind = [] for i in range(n_rot): hind.append((x[n_vib + 2 * i], x[n_vib + 2 * i + 1])) return vib, hind
def fitStatmechDirect(Tlist, Cvlist, Nvib, Nrot, molecule=None): """ Fit `Nvib` harmonic oscillator and `Nrot` hindered internal rotor modes to the provided dimensionless heat capacities `Cvlist` at temperatures `Tlist` in K. This method assumes that there are enough heat capacity points provided that the vibrational frequencies and hindered rotation frequency- barrier pairs can be fit directly. """ # Construct the lower and upper bounds for each variable bounds = [] # Bounds for harmonic oscillator frequencies for i in range(Nvib): bounds.append((hoFreqLowerBound, hoFreqUpperBound)) # Bounds for hindered rotor frequencies and barrier heights for i in range(Nrot): bounds.append((hrFreqLowerBound, hrFreqUpperBound)) bounds.append((hrBarrLowerBound, hrBarrUpperBound)) # Construct the initial guess # Initial guesses within each mode type must be distinct or else the # optimization will fail x0 = numpy.zeros(Nvib + 2*Nrot, numpy.float64) # Initial guess for harmonic oscillator frequencies if Nvib > 0: x0[0] = 200.0 x0[1:Nvib] = numpy.linspace(800.0, 1600.0, Nvib-1) # Initial guess for hindered rotor frequencies and barrier heights if Nrot > 0: x0[Nvib] = 100.0 x0[Nvib+1] = 100.0 for i in range(1, Nrot): x0[Nvib+2*i] = x0[Nvib+2*i-2] + 20.0 x0[Nvib+2*i+1] = x0[Nvib+2*i-1] + 100.0 # Execute the optimization fit = DirectFit(Tlist, Cvlist, Nvib, Nrot) fit.initialize(Neq=len(Tlist), Nvars=len(x0), Ncons=0, bounds=bounds, maxIter=maxIter) x, igo = fit.solve(x0) # Check that the results of the optimization are valid if not numpy.isfinite(x).all(): raise StatmechFitError('Returned solution vector is nonsensical: x = {0}.'.format(x)) if igo == 8: logging.warning('Maximum number of iterations reached when fitting spectral data for {0}.'.format(molecule.toSMILES())) # Postprocess optimization results vib = list(x[0:Nvib]) hind = [] for i in range(Nrot): hind.append((x[Nvib+2*i], x[Nvib+2*i+1])) return vib, hind
def fit_statmech_pseudo(Tlist, Cvlist, n_vib, n_rot, molecule=None): """ Fit `n_vib` harmonic oscillator and `n_rot` hindered internal rotor modes to the provided dimensionless heat capacities `Cvlist` at temperatures `Tlist` in K. This method assumes that there are relatively few heat capacity points provided, so the vibrations must be combined into one real vibration and two "pseudo-vibrations" and the hindered rotors must be combined into a single "pseudo-rotor". """ # Construct the lower and upper bounds for each variable bounds = [] # x[0] corresponds to the first harmonic oscillator (real) frequency bounds.append((ho_freq_lower_bound, ho_freq_upper_bound)) # x[1] corresponds to the degeneracy of the second harmonic oscillator bounds.append((1.0, float(n_vib - 2))) # x[2] corresponds to the second harmonic oscillator pseudo-frequency bounds.append((ho_freq_lower_bound, ho_freq_upper_bound)) # x[3] corresponds to the third harmonic oscillator pseudo-frequency bounds.append((ho_freq_lower_bound, ho_freq_upper_bound)) # x[4] corresponds to the hindered rotor pseudo-frequency bounds.append((hr_freq_lower_bound, hr_freq_upper_bound)) # x[5] corresponds to the hindered rotor pseudo-barrier bounds.append((hr_barr_lower_bound, hr_barr_upper_bound)) # Construct the initial guess x0 = np.zeros(6, np.float64) # Initial guess x0[0] = 300.0 x0[1] = float(math.floor((n_vib - 1) / 2.0)) x0[2] = 800.0 x0[3] = 1600.0 x0[4] = 100.0 x0[5] = 300.0 # Execute the optimization fit = PseudoFit(Tlist, Cvlist, n_vib, n_rot) fit.initialize(Neq=len(Tlist), Nvars=len(x0), Ncons=0, bounds=bounds, maxIter=max_iter) x, igo = fit.solve(x0) # Check that the results of the optimization are valid if not np.isfinite(x).all(): raise StatmechFitError( 'Returned solution vector is nonsensical: x = {0}.'.format(x)) if igo == 8: logging.warning( 'Maximum number of iterations reached when fitting spectral data for ' '{0}.'.format(molecule.to_smiles())) if igo > 8: logging.warning( 'A solver error occured when fitting spectral data for {0}.'. format(molecule.to_smiles())) logging.debug( 'Fitting remaining heat capacity to {0} vibrations and {1} rotations'. format(n_vib, n_rot)) logging.debug('The residuals for heat capacity values is {}'.format( fit.evaluate(x)[0])) # Postprocess optimization results n_vib2 = int(round(x[1])) n_vib_3 = n_vib - n_vib2 - 1 if n_vib2 < 0 or n_vib2 > n_vib - 1 or n_vib_3 < 0 or n_vib_3 > n_vib - 1: raise StatmechFitError('Invalid degeneracies {0} and {1} fitted for ' 'pseudo-frequencies.'.format(n_vib2, n_vib_3)) vib = [x[0]] for i in range(n_vib2): vib.append(x[2]) for i in range(n_vib_3): vib.append(x[3]) hind = [] for i in range(n_rot): hind.append((x[4], x[5])) return vib, hind