Ejemplo n.º 1
0
    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"),
        )
Ejemplo n.º 2
0
 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,
     )
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    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}
            """)
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
    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)
Ejemplo n.º 13
0
    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
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
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