Example #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")),
         ],
         spinMultiplicity = 1,
         opticalIsomers = 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")),
         ],
         spinMultiplicity = 3,
         opticalIsomers = 1,
     )
     
     # The following data is for ethane at the CBS-QB3 level
     self.coordinates = numpy.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 = numpy.array([6, 1, 1, 1, 6, 1, 1, 1])
     self.mass = numpy.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),
         ],
         spinMultiplicity = 1,
         opticalIsomers = 1,
         coordinates = (self.coordinates,"angstrom"),
         number = self.number,
         mass = (self.mass,"amu"),
     )
Example #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,
     )
Example #3
0
class TestConformer(unittest.TestCase):
    """
    Contains unit tests of the :class:`Conformer` class.
    """
    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 test_get_partition_function_ethylene(self):
        """
        Test the StatMech.get_partition_function() method for ethylene.
        """
        t_list = np.array([300, 500, 1000, 1500, 2000])
        q_exp_list = np.array(
            [4.05311e+09, 4.19728e+10, 2.82309e+12, 7.51135e+13, 1.16538e+15])
        for temperature, q_exp in zip(t_list, q_exp_list):
            q_act = self.ethylene.get_partition_function(temperature)
            self.assertAlmostEqual(q_exp, q_act, delta=1e-4 * q_exp)

    def test_get_heat_capacity_ethylene(self):
        """
        Test the StatMech.get_heat_capacity() method for ethylene.
        """
        t_list = np.array([300, 500, 1000, 1500, 2000])
        cv_exp_list = np.array([5.11186, 7.40447, 11.1659, 13.1221, 14.1617
                                ]) * constants.R
        for temperature, cv_exp in zip(t_list, cv_exp_list):
            cv_act = self.ethylene.get_heat_capacity(temperature)
            self.assertAlmostEqual(cv_exp, cv_act, 3)

    def test_get_enthalpy_ethylene(self):
        """
        Test the StatMech.get_enthalpy() method for ethylene.
        """
        t_list = np.array([300, 500, 1000, 1500, 2000])
        h_exp_list = np.array([4.23129, 5.04826, 7.27337, 8.93167, 10.1223
                               ]) * constants.R * t_list
        for temperature, h_exp in zip(t_list, h_exp_list):
            h_act = self.ethylene.get_enthalpy(temperature)
            self.assertAlmostEqual(h_exp, h_act, delta=1e-4 * h_exp)

    def test_get_entropy_ethylene(self):
        """
        Test the StatMech.get_entropy() method for ethylene.
        """
        t_list = np.array([300, 500, 1000, 1500, 2000])
        s_exp_list = np.array([26.3540, 29.5085, 35.9422, 40.8817, 44.8142
                               ]) * constants.R
        for temperature, s_exp in zip(t_list, s_exp_list):
            s_act = self.ethylene.get_entropy(temperature)
            self.assertAlmostEqual(s_exp, s_act, 3)

    def test_get_sum_of_states_ethylene(self):
        """
        Test the StatMech.get_sum_of_states() method for ethylene.
        """
        e_list = np.arange(0, 5000 * 11.96, 2 * 11.96)
        sum_states = self.ethylene.get_sum_of_states(e_list)
        dens_states = self.ethylene.get_density_of_states(e_list)
        for n in range(10, len(e_list)):
            self.assertTrue(
                0.8 < np.sum(dens_states[0:n + 1]) / sum_states[n] < 1.25,
                '{0} != {1}'.format(np.sum(dens_states[0:n + 1]),
                                    sum_states[n]))

    def test_get_density_of_states_ethylene(self):
        """
        Test the StatMech.get_density_of_states() method for ethylene.
        """
        e_list = np.arange(0, 5000 * 11.96, 2 * 11.96)
        dens_states = self.ethylene.get_density_of_states(e_list)
        temperature = 100
        q_act = np.sum(dens_states *
                       np.exp(-e_list / constants.R / temperature))
        q_exp = self.ethylene.get_partition_function(temperature)
        self.assertAlmostEqual(q_exp, q_act, delta=1e-1 * q_exp)

    def test_get_partition_function_oxygen(self):
        """
        Test the StatMech.get_partition_function() method for oxygen.
        """
        t_list = np.array([300, 500, 1000, 1500, 2000])
        q_exp_list = np.array(
            [1.55584e+09, 9.38339e+09, 1.16459e+11, 5.51016e+11, 1.72794e+12])
        for temperature, q_exp in zip(t_list, q_exp_list):
            q_act = self.oxygen.get_partition_function(temperature)
            self.assertAlmostEqual(q_exp, q_act, delta=1e-4 * q_exp)

    def test_get_heat_capacity_oxygen(self):
        """
        Test the StatMech.get_heat_capacity() method for oxygen.
        """
        t_list = np.array([300, 500, 1000, 1500, 2000])
        cv_exp_list = np.array([3.52538, 3.70877, 4.14751, 4.32063, 4.39392
                                ]) * constants.R
        for temperature, Cv_exp in zip(t_list, cv_exp_list):
            cv_act = self.oxygen.get_heat_capacity(temperature)
            self.assertAlmostEqual(Cv_exp, cv_act, 3)

    def test_get_enthalpy_oxygen(self):
        """
        Test the StatMech.get_enthalpy() method for oxygen.
        """
        t_list = np.array([300, 500, 1000, 1500, 2000])
        h_exp_list = np.array([3.50326, 3.54432, 3.75062, 3.91623, 4.02765
                               ]) * constants.R * t_list
        for temperature, h_exp in zip(t_list, h_exp_list):
            h_act = self.oxygen.get_enthalpy(temperature)
            self.assertAlmostEqual(h_exp, h_act, delta=1e-4 * h_exp)

    def test_get_entropy_oxygen(self):
        """
        Test the StatMech.get_entropy() method for oxygen.
        """
        t_list = np.array([300, 500, 1000, 1500, 2000])
        s_exp_list = np.array([24.6685, 26.5065, 29.2314, 30.9513, 32.2056
                               ]) * constants.R
        for temperature, s_exp in zip(t_list, s_exp_list):
            s_act = self.oxygen.get_entropy(temperature)
            self.assertAlmostEqual(s_exp, s_act, 3)

    def test_get_sum_of_states_oxygen(self):
        """
        Test the StatMech.get_sum_of_states() method for oxygen.
        """
        e_list = np.arange(0, 5000 * 11.96, 2 * 11.96)
        sum_states = self.oxygen.get_sum_of_states(e_list)
        dens_states = self.oxygen.get_density_of_states(e_list)
        for n in range(10, len(e_list)):
            self.assertTrue(
                0.8 < np.sum(dens_states[0:n + 1]) / sum_states[n] < 1.25,
                '{0} != {1}'.format(np.sum(dens_states[0:n + 1]),
                                    sum_states[n]))

    def test_get_density_of_states_oxygen(self):
        """
        Test the StatMech.get_density_of_states() method for oxygen.
        """
        e_list = np.arange(0, 5000 * 11.96, 2 * 11.96)
        dens_states = self.oxygen.get_density_of_states(e_list)
        temperature = 100
        q_act = np.sum(dens_states *
                       np.exp(-e_list / constants.R / temperature))
        q_exp = self.oxygen.get_partition_function(temperature)
        self.assertAlmostEqual(q_exp, q_act, delta=1e-1 * q_exp)

    def test_get_total_mass(self):
        """
        Test the Conformer.get_total_mass() method.
        """
        self.assertAlmostEqual(
            self.conformer.get_total_mass() * constants.Na * 1000.,
            np.sum(self.mass), 6)

    def test_get_center_of_mass(self):
        """
        Test the Conformer.get_center_of_mass() method.
        """
        cm = self.conformer.get_center_of_mass()
        self.assertAlmostEqual(cm[0] * 1e10, -0.38045, 4)
        self.assertAlmostEqual(cm[1] * 1e10, -0.60255, 4)
        self.assertAlmostEqual(cm[2] * 1e10, -0.27900, 4)

    def test_get_moment_of_inertia_tensor(self):
        """
        Test the Conformer.get_moment_of_inertia_tensor() method.
        """
        inertia = self.conformer.get_moment_of_inertia_tensor()
        self.assertAlmostEqual(inertia[0, 0] * constants.Na * 1e23, 20.65968,
                               4)
        self.assertAlmostEqual(inertia[0, 1] * constants.Na * 1e23, -7.48115,
                               4)
        self.assertAlmostEqual(inertia[0, 2] * constants.Na * 1e23, -3.46416,
                               4)
        self.assertAlmostEqual(inertia[1, 0] * constants.Na * 1e23, -7.48115,
                               4)
        self.assertAlmostEqual(inertia[1, 1] * constants.Na * 1e23, 13.53472,
                               4)
        self.assertAlmostEqual(inertia[1, 2] * constants.Na * 1e23, -5.48630,
                               4)
        self.assertAlmostEqual(inertia[2, 0] * constants.Na * 1e23, -3.46416,
                               4)
        self.assertAlmostEqual(inertia[2, 1] * constants.Na * 1e23, -5.48630,
                               4)
        self.assertAlmostEqual(inertia[2, 2] * constants.Na * 1e23, 22.84296,
                               4)

    def test_get_principal_moments_of_inertia(self):
        """
        Test the Conformer.get_principal_moments_of_inertia() method.
        """
        inertia, axes = self.conformer.get_principal_moments_of_inertia()
        self.assertAlmostEqual(inertia[0] * constants.Na * 1e23, 6.27074, 4)
        self.assertAlmostEqual(inertia[1] * constants.Na * 1e23, 25.38321, 3)
        self.assertAlmostEqual(inertia[2] * constants.Na * 1e23, 25.38341, 3)
        # For some reason the axes seem to jump around (positioning and signs change)
        # but the absolute values should be the same as we expect
        expected = sorted([
            0.497140, 0.610114, 0.616938, 0.787360, 0.018454, 0.616218,
            0.364578, 0.792099, 0.489554
        ])
        result = sorted(abs(axes).flat)
        for i, j in zip(expected, result):
            self.assertAlmostEqual(i, j, 4)
        return

    def test_get_internal_reduced_moment_of_inertia(self):
        """
        Test the Conformer.get_internal_reduced_moment_of_inertia() method.
        """
        inertia = self.conformer.get_internal_reduced_moment_of_inertia(
            pivots=[1, 5], top1=[1, 2, 3, 4])
        self.assertAlmostEqual(inertia * constants.Na * 1e23, 1.56768, 4)

    def test_get_number_degrees_of_freedom(self):
        """
        Test the Conformer.get_number_degrees_of_freedom() method.
        """
        # this is for ethane:
        number_degrees_of_freedom = self.conformer.get_number_degrees_of_freedom(
        )
        self.assertEqual(number_degrees_of_freedom, 24)

        # this is for ethylene:
        # It doesn't check against 3 * n_atoms, because n_atoms is not declared.
        number_degrees_of_freedom = self.ethylene.get_number_degrees_of_freedom(
        )
        self.assertEqual(number_degrees_of_freedom, 18)

        # this is for CO
        # It doesn't check against 3 * n_atoms, because n_atoms is not declared.
        number_degrees_of_freedom = self.oxygen.get_number_degrees_of_freedom()
        self.assertEqual(number_degrees_of_freedom, 6)
Example #4
0
def process_thermo_data(spc, thermo0, thermo_class=NASA, solvent_name=''):
    """
    Converts via Wilhoit into required `thermo_class` and sets `E0`.
    
    Resulting thermo is returned.
    """
    thermo = None

    # Always convert to Wilhoit so we can compute E0
    if isinstance(thermo0, Wilhoit):
        wilhoit = thermo0
    elif isinstance(thermo0, ThermoData):
        wilhoit = thermo0.to_wilhoit(B=1000.)
    else:
        wilhoit = thermo0.to_wilhoit()

    # Add on solvation correction
    solvation_database = get_db('solvation')
    if not solvent_name or solvation_database is None:
        logging.debug(
            'Solvent database or solvent_name not found. Solvent effect was not utilized'
        )
        solvent_data = None
    else:
        solvent_data = solvation_database.get_solvent_data(solvent_name)
    if solvent_data and not "Liquid thermo library" in thermo0.comment:
        solvation_database = get_db('solvation')
        solute_data = solvation_database.get_solute_data(spc)
        solvation_correction = solvation_database.get_solvation_correction(
            solute_data, solvent_data)
        # correction is added to the entropy and enthalpy
        wilhoit.S0.value_si = (wilhoit.S0.value_si +
                               solvation_correction.entropy)
        wilhoit.H0.value_si = (wilhoit.H0.value_si +
                               solvation_correction.enthalpy)

    # Compute E0 by extrapolation to 0 K
    if spc.conformer is None:
        spc.conformer = Conformer()
    spc.conformer.E0 = wilhoit.E0

    # Convert to desired thermo class
    if thermo_class is Wilhoit:
        thermo = wilhoit
    elif thermo_class is NASA:
        if solvent_data:
            # If liquid phase simulation keep the nasa polynomial if it comes from a liquid phase thermoLibrary.
            # Otherwise convert wilhoit to NASA
            if "Liquid thermo library" in thermo0.comment and isinstance(
                    thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.to_nasa(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
        else:
            # gas phase with species matching thermo library keep the NASA from library or convert if group additivity
            if "Thermo library" in thermo0.comment and isinstance(
                    thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.to_nasa(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
    else:
        raise Exception(
            'thermo_class neither NASA nor Wilhoit.  Cannot process thermo data.'
        )

    return thermo
Example #5
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
Example #6
0
def process_thermo_data(spc, thermo0, thermo_class=NASA, solvent_name=''):
    """
    Converts via Wilhoit into required `thermo_class` and sets `E0`.
    
    Resulting thermo is returned.
    """
    thermo = None

    # Always convert to Wilhoit so we can compute E0
    if isinstance(thermo0, Wilhoit):
        wilhoit = thermo0
    elif isinstance(thermo0, ThermoData):
        wilhoit = thermo0.to_wilhoit(B=1000.)
    else:
        wilhoit = thermo0.to_wilhoit()

    # Add on solvation correction
    solvation_database = get_db('solvation')
    if not solvent_name or solvation_database is None:
        logging.debug('Solvent database or solvent_name not found. Solvent effect was not utilized')
        solvent_data = None
    else:
        solvent_data = solvation_database.get_solvent_data(solvent_name)
    if solvent_data and not "Liquid thermo library" in thermo0.comment:
        solvation_database = get_db('solvation')
        solute_data = solvation_database.get_solute_data(spc)
        solvation_correction = solvation_database.get_solvation_correction(solute_data, solvent_data)
        # correction is added to the entropy and enthalpy
        wilhoit.S0.value_si = (wilhoit.S0.value_si + solvation_correction.entropy)
        wilhoit.H0.value_si = (wilhoit.H0.value_si + solvation_correction.enthalpy)

    # Compute E0 by extrapolation to 0 K
    if spc.conformer is None:
        spc.conformer = Conformer()
    spc.conformer.E0 = wilhoit.E0

    # Convert to desired thermo class
    if thermo_class is Wilhoit:
        thermo = wilhoit
    elif thermo_class is NASA:
        if solvent_data:
            # If liquid phase simulation keep the nasa polynomial if it comes from a liquid phase thermoLibrary.
            # Otherwise convert wilhoit to NASA
            if "Liquid thermo library" in thermo0.comment and isinstance(thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.to_nasa(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
        else:
            # gas phase with species matching thermo library keep the NASA from library or convert if group additivity
            if "Thermo library" in thermo0.comment and isinstance(thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.to_nasa(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
    else:
        raise Exception('thermo_class neither NASA nor Wilhoit.  Cannot process thermo data.')

    if thermo.__class__ != thermo0.__class__:
        # Compute RMS error of overall transformation
        Tlist = np.array([300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0], np.float64)
        err = 0.0
        for T in Tlist:
            err += (thermo.get_heat_capacity(T) - thermo0.get_heat_capacity(T)) ** 2
        err = math.sqrt(err / len(Tlist)) / constants.R
        # logging.log(logging.WARNING if err > 0.2 else 0, 'Average RMS error in heat capacity fit to {0} = {1:g}*R'.format(spc, err))

    return thermo
Example #7
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)
Example #8
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
Example #9
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)
Example #10
0
    def update(self, reaction_model, pdep_settings):
        """
        Regenerate the :math:`k(T,P)` values for this partial network if the
        network is marked as invalid.
        """
        from rmgpy.kinetics import Arrhenius, KineticsData, MultiArrhenius

        # Get the parameters for the pressure dependence calculation
        job = pdep_settings
        job.network = self
        output_directory = pdep_settings.output_file

        Tmin = job.Tmin.value_si
        Tmax = job.Tmax.value_si
        Pmin = job.Pmin.value_si
        Pmax = job.Pmax.value_si
        Tlist = job.Tlist.value_si
        Plist = job.Plist.value_si
        maximum_grain_size = job.maximum_grain_size.value_si if job.maximum_grain_size is not None else 0.0
        minimum_grain_count = job.minimum_grain_count
        method = job.method
        interpolation_model = job.interpolation_model
        active_j_rotor = job.active_j_rotor
        active_k_rotor = job.active_k_rotor
        rmgmode = job.rmgmode

        # Figure out which configurations are isomers, reactant channels, and product channels
        self.update_configurations(reaction_model)

        # Make sure we have high-P kinetics for all path reactions
        for rxn in self.path_reactions:
            if rxn.kinetics is None and rxn.reverse.kinetics is None:
                raise PressureDependenceError(
                    'Path reaction {0} with no high-pressure-limit kinetics encountered in '
                    'PDepNetwork #{1:d}.'.format(rxn, self.index))
            elif rxn.kinetics is not None and rxn.kinetics.is_pressure_dependent(
            ) and rxn.network_kinetics is None:
                raise PressureDependenceError(
                    'Pressure-dependent kinetics encountered for path reaction {0} in '
                    'PDepNetwork #{1:d}.'.format(rxn, self.index))

        # Do nothing if the network is already valid
        if self.valid:
            return
        # Do nothing if there are no explored wells
        if len(self.explored) == 0 and len(self.source) > 1:
            return
        # Log the network being updated
        logging.info("Updating {0!s}".format(self))

        # Generate states data for unimolecular isomers and reactants if necessary
        for isomer in self.isomers:
            spec = isomer.species[0]
            if not spec.has_statmech():
                spec.generate_statmech()
        for reactants in self.reactants:
            for spec in reactants.species:
                if not spec.has_statmech():
                    spec.generate_statmech()
        # Also generate states data for any path reaction reactants, so we can
        # always apply the ILT method in the direction the kinetics are known
        for reaction in self.path_reactions:
            for spec in reaction.reactants:
                if not spec.has_statmech():
                    spec.generate_statmech()
        # While we don't need the frequencies for product channels, we do need
        # the E0, so create a conformer object with the E0 for the product
        # channel species if necessary
        for products in self.products:
            for spec in products.species:
                if spec.conformer is None:
                    spec.conformer = Conformer(E0=spec.get_thermo_data().E0)

        # Determine transition state energies on potential energy surface
        # In the absence of any better information, we simply set it to
        # be the reactant ground-state energy + the activation energy
        # Note that we need Arrhenius kinetics in order to do this
        for rxn in self.path_reactions:
            if rxn.kinetics is None:
                raise Exception(
                    'Path reaction "{0}" in PDepNetwork #{1:d} has no kinetics!'
                    .format(rxn, self.index))
            elif isinstance(rxn.kinetics, KineticsData):
                if len(rxn.reactants) == 1:
                    kunits = 's^-1'
                elif len(rxn.reactants) == 2:
                    kunits = 'm^3/(mol*s)'
                elif len(rxn.reactants) == 3:
                    kunits = 'm^6/(mol^2*s)'
                else:
                    kunits = ''
                rxn.kinetics = Arrhenius().fit_to_data(
                    Tlist=rxn.kinetics.Tdata.value_si,
                    klist=rxn.kinetics.kdata.value_si,
                    kunits=kunits)
            elif isinstance(rxn.kinetics, MultiArrhenius):
                logging.info(
                    'Converting multiple kinetics to a single Arrhenius expression for reaction {rxn}'
                    .format(rxn=rxn))
                rxn.kinetics = rxn.kinetics.to_arrhenius(Tmin=Tmin, Tmax=Tmax)
            elif not isinstance(rxn.kinetics,
                                Arrhenius) and rxn.network_kinetics is None:
                raise Exception(
                    'Path reaction "{0}" in PDepNetwork #{1:d} has invalid kinetics '
                    'type "{2!s}".'.format(rxn, self.index,
                                           rxn.kinetics.__class__))
            rxn.fix_barrier_height(force_positive=True)
            if rxn.network_kinetics is None:
                E0 = sum(
                    [spec.conformer.E0.value_si
                     for spec in rxn.reactants]) + rxn.kinetics.Ea.value_si
            else:
                E0 = sum([
                    spec.conformer.E0.value_si for spec in rxn.reactants
                ]) + rxn.network_kinetics.Ea.value_si
            rxn.transition_state = rmgpy.species.TransitionState(
                conformer=Conformer(E0=(E0 * 0.001, "kJ/mol")))

        # Set collision model
        bath_gas = [
            spec for spec in reaction_model.core.species if not spec.reactive
        ]
        assert len(
            bath_gas) > 0, 'No unreactive species to identify as bath gas'

        self.bath_gas = {}
        for spec in bath_gas:
            # is this really the only/best way to weight them?
            self.bath_gas[spec] = 1.0 / len(bath_gas)

        # Save input file
        if not self.label:
            self.label = str(self.index)

        if output_directory:
            job.save_input_file(
                os.path.join(
                    output_directory, 'pdep',
                    'network{0:d}_{1:d}.py'.format(self.index,
                                                   len(self.isomers))))

        self.log_summary(level=logging.INFO)

        # Calculate the rate coefficients
        self.initialize(Tmin, Tmax, Pmin, Pmax, maximum_grain_size,
                        minimum_grain_count, active_j_rotor, active_k_rotor,
                        rmgmode)
        K = self.calculate_rate_coefficients(Tlist, Plist, method)

        # Generate PDepReaction objects
        configurations = []
        configurations.extend([isom.species[:] for isom in self.isomers])
        configurations.extend(
            [reactant.species[:] for reactant in self.reactants])
        configurations.extend(
            [product.species[:] for product in self.products])
        j = configurations.index(self.source)

        for i in range(K.shape[2]):
            if i != j:
                # Find the path reaction
                net_reaction = None
                for r in self.net_reactions:
                    if r.has_template(configurations[j], configurations[i]):
                        net_reaction = r
                # If net reaction does not already exist, make a new one
                if net_reaction is None:
                    net_reaction = PDepReaction(reactants=configurations[j],
                                                products=configurations[i],
                                                network=self,
                                                kinetics=None)
                    net_reaction = reaction_model.make_new_pdep_reaction(
                        net_reaction)
                    self.net_reactions.append(net_reaction)

                    # Place the net reaction in the core or edge if necessary
                    # Note that leak reactions are not placed in the edge
                    if all([s in reaction_model.core.species for s in net_reaction.reactants]) \
                            and all([s in reaction_model.core.species for s in net_reaction.products]):
                        # Check whether netReaction already exists in the core as a LibraryReaction
                        for rxn in reaction_model.core.reactions:
                            if isinstance(rxn, LibraryReaction) \
                                    and rxn.is_isomorphic(net_reaction, either_direction=True) \
                                    and not rxn.allow_pdep_route and not rxn.elementary_high_p:
                                logging.info(
                                    'Network reaction {0} matched an existing core reaction {1}'
                                    ' from the {2} library, and was not added to the model'
                                    .format(str(net_reaction), str(rxn),
                                            rxn.library))
                                break
                        else:
                            reaction_model.add_reaction_to_core(net_reaction)
                    else:
                        # Check whether netReaction already exists in the edge as a LibraryReaction
                        for rxn in reaction_model.edge.reactions:
                            if isinstance(rxn, LibraryReaction) \
                                    and rxn.is_isomorphic(net_reaction, either_direction=True) \
                                    and not rxn.allow_pdep_route and not rxn.elementary_high_p:
                                logging.info(
                                    'Network reaction {0} matched an existing edge reaction {1}'
                                    ' from the {2} library, and was not added to the model'
                                    .format(str(net_reaction), str(rxn),
                                            rxn.library))
                                break
                        else:
                            reaction_model.add_reaction_to_edge(net_reaction)

                # Set/update the net reaction kinetics using interpolation model
                kdata = K[:, :, i, j].copy()
                order = len(net_reaction.reactants)
                kdata *= 1e6**(order - 1)
                kunits = {
                    1: 's^-1',
                    2: 'cm^3/(mol*s)',
                    3: 'cm^6/(mol^2*s)'
                }[order]
                net_reaction.kinetics = job.fit_interpolation_model(
                    Tlist, Plist, kdata, kunits)

                # Check: For each net reaction that has a path reaction, make
                # sure the k(T,P) values for the net reaction do not exceed
                # the k(T) values of the path reaction
                # Only check the k(T,P) value at the highest P and lowest T,
                # as this is the one most likely to be in the high-pressure
                # limit
                t = 0
                p = len(Plist) - 1
                for pathReaction in self.path_reactions:
                    if pathReaction.is_isomerization():
                        # Don't check isomerization reactions, since their
                        # k(T,P) values potentially contain both direct and
                        # well-skipping contributions, and therefore could be
                        # significantly larger than the direct k(T) value
                        # (This can also happen for association/dissociation
                        # reactions, but the effect is generally not too large)
                        continue
                    if pathReaction.reactants == net_reaction.reactants and pathReaction.products == net_reaction.products:
                        if pathReaction.network_kinetics is not None:
                            kinf = pathReaction.network_kinetics.get_rate_coefficient(
                                Tlist[t])
                        else:
                            kinf = pathReaction.kinetics.get_rate_coefficient(
                                Tlist[t])
                        if K[t, p, i,
                             j] > 2 * kinf:  # To allow for a small discretization error
                            logging.warning(
                                'k(T,P) for net reaction {0} exceeds high-P k(T) by {1:g} at {2:g} K, '
                                '{3:g} bar'.format(net_reaction,
                                                   K[t, p, i, j] / kinf,
                                                   Tlist[t], Plist[p] / 1e5))
                            logging.info(
                                '    k(T,P) = {0:9.2e}    k(T) = {1:9.2e}'.
                                format(K[t, p, i, j], kinf))
                        break
                    elif pathReaction.products == net_reaction.reactants and pathReaction.reactants == net_reaction.products:
                        if pathReaction.network_kinetics is not None:
                            kinf = pathReaction.network_kinetics.get_rate_coefficient(
                                Tlist[t]
                            ) / pathReaction.get_equilibrium_constant(Tlist[t])
                        else:
                            kinf = pathReaction.kinetics.get_rate_coefficient(
                                Tlist[t]
                            ) / pathReaction.get_equilibrium_constant(Tlist[t])
                        if K[t, p, i,
                             j] > 2 * kinf:  # To allow for a small discretization error
                            logging.warning(
                                'k(T,P) for net reaction {0} exceeds high-P k(T) by {1:g} at {2:g} K, '
                                '{3:g} bar'.format(net_reaction,
                                                   K[t, p, i, j] / kinf,
                                                   Tlist[t], Plist[p] / 1e5))
                            logging.info(
                                '    k(T,P) = {0:9.2e}    k(T) = {1:9.2e}'.
                                format(K[t, p, i, j], kinf))
                        break

        # Delete intermediate arrays to conserve memory
        self.cleanup()

        # We're done processing this network, so mark it as valid
        self.valid = True
Example #11
0
class TestConformer(unittest.TestCase):
    """
    Contains unit tests of the :class:`Conformer` class.
    """
    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")),
            ],
            spinMultiplicity=1,
            opticalIsomers=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")),
            ],
            spinMultiplicity=3,
            opticalIsomers=1,
        )

        # The following data is for ethane at the CBS-QB3 level
        self.coordinates = numpy.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 = numpy.array([6, 1, 1, 1, 6, 1, 1, 1])
        self.mass = numpy.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),
            ],
            spinMultiplicity=1,
            opticalIsomers=1,
            coordinates=(self.coordinates, "angstrom"),
            number=self.number,
            mass=(self.mass, "amu"),
        )

    def test_getPartitionFunction_ethylene(self):
        """
        Test the StatMech.getPartitionFunction() method for ethylene.
        """
        Tlist = numpy.array([300, 500, 1000, 1500, 2000])
        Qexplist = numpy.array(
            [4.05311e+09, 4.19728e+10, 2.82309e+12, 7.51135e+13, 1.16538e+15])
        for T, Qexp in zip(Tlist, Qexplist):
            Qact = self.ethylene.getPartitionFunction(T)
            self.assertAlmostEqual(Qexp, Qact, delta=1e-4 * Qexp)

    def test_getHeatCapacity_ethylene(self):
        """
        Test the StatMech.getHeatCapacity() method for ethylene.
        """
        Tlist = numpy.array([300, 500, 1000, 1500, 2000])
        Cvexplist = numpy.array([5.11186, 7.40447, 11.1659, 13.1221, 14.1617
                                 ]) * constants.R
        for T, Cvexp in zip(Tlist, Cvexplist):
            Cvact = self.ethylene.getHeatCapacity(T)
            self.assertAlmostEqual(Cvexp, Cvact, 3)

    def test_getEnthalpy_ethylene(self):
        """
        Test the StatMech.getEnthalpy() method for ethylene.
        """
        Tlist = numpy.array([300, 500, 1000, 1500, 2000])
        Hexplist = numpy.array([4.23129, 5.04826, 7.27337, 8.93167, 10.1223
                                ]) * constants.R * Tlist
        for T, Hexp in zip(Tlist, Hexplist):
            Hact = self.ethylene.getEnthalpy(T)
            self.assertAlmostEqual(Hexp, Hact, delta=1e-4 * Hexp)

    def test_getEntropy_ethylene(self):
        """
        Test the StatMech.getEntropy() method for ethylene.
        """
        Tlist = numpy.array([300, 500, 1000, 1500, 2000])
        Sexplist = numpy.array([26.3540, 29.5085, 35.9422, 40.8817, 44.8142
                                ]) * constants.R
        for T, Sexp in zip(Tlist, Sexplist):
            Sact = self.ethylene.getEntropy(T)
            self.assertAlmostEqual(Sexp, Sact, 3)

    def test_getSumOfStates_ethylene(self):
        """
        Test the StatMech.getSumOfStates() method for ethylene.
        """
        Elist = numpy.arange(0, 5000 * 11.96, 2 * 11.96)
        sumStates = self.ethylene.getSumOfStates(Elist)
        densStates = self.ethylene.getDensityOfStates(Elist)
        for n in range(10, len(Elist)):
            self.assertTrue(
                0.8 < numpy.sum(densStates[0:n + 1]) / sumStates[n] < 1.25,
                '{0} != {1}'.format(numpy.sum(densStates[0:n + 1]),
                                    sumStates[n]))

    def test_getDensityOfStates_ethylene(self):
        """
        Test the StatMech.getDensityOfStates() method for ethylene.
        """
        Elist = numpy.arange(0, 5000 * 11.96, 2 * 11.96)
        densStates = self.ethylene.getDensityOfStates(Elist)
        T = 100
        Qact = numpy.sum(densStates * numpy.exp(-Elist / constants.R / T))
        Qexp = self.ethylene.getPartitionFunction(T)
        self.assertAlmostEqual(Qexp, Qact, delta=1e-1 * Qexp)

    def test_getPartitionFunction_oxygen(self):
        """
        Test the StatMech.getPartitionFunction() method for oxygen.
        """
        Tlist = numpy.array([300, 500, 1000, 1500, 2000])
        Qexplist = numpy.array(
            [1.55584e+09, 9.38339e+09, 1.16459e+11, 5.51016e+11, 1.72794e+12])
        for T, Qexp in zip(Tlist, Qexplist):
            Qact = self.oxygen.getPartitionFunction(T)
            self.assertAlmostEqual(Qexp, Qact, delta=1e-4 * Qexp)

    def test_getHeatCapacity_oxygen(self):
        """
        Test the StatMech.getHeatCapacity() method for oxygen.
        """
        Tlist = numpy.array([300, 500, 1000, 1500, 2000])
        Cvexplist = numpy.array([3.52538, 3.70877, 4.14751, 4.32063, 4.39392
                                 ]) * constants.R
        for T, Cvexp in zip(Tlist, Cvexplist):
            Cvact = self.oxygen.getHeatCapacity(T)
            self.assertAlmostEqual(Cvexp, Cvact, 3)

    def test_getEnthalpy_oxygen(self):
        """
        Test the StatMech.getEnthalpy() method for oxygen.
        """
        Tlist = numpy.array([300, 500, 1000, 1500, 2000])
        Hexplist = numpy.array([3.50326, 3.54432, 3.75062, 3.91623, 4.02765
                                ]) * constants.R * Tlist
        for T, Hexp in zip(Tlist, Hexplist):
            Hact = self.oxygen.getEnthalpy(T)
            self.assertAlmostEqual(Hexp, Hact, delta=1e-4 * Hexp)

    def test_getEntropy_oxygen(self):
        """
        Test the StatMech.getEntropy() method for oxygen.
        """
        Tlist = numpy.array([300, 500, 1000, 1500, 2000])
        Sexplist = numpy.array([24.6685, 26.5065, 29.2314, 30.9513, 32.2056
                                ]) * constants.R
        for T, Sexp in zip(Tlist, Sexplist):
            Sact = self.oxygen.getEntropy(T)
            self.assertAlmostEqual(Sexp, Sact, 3)

    def test_getSumOfStates_oxygen(self):
        """
        Test the StatMech.getSumOfStates() method for oxygen.
        """
        Elist = numpy.arange(0, 5000 * 11.96, 2 * 11.96)
        sumStates = self.oxygen.getSumOfStates(Elist)
        densStates = self.oxygen.getDensityOfStates(Elist)
        for n in range(10, len(Elist)):
            self.assertTrue(
                0.8 < numpy.sum(densStates[0:n + 1]) / sumStates[n] < 1.25,
                '{0} != {1}'.format(numpy.sum(densStates[0:n + 1]),
                                    sumStates[n]))

    def test_getDensityOfStates_oxygen(self):
        """
        Test the StatMech.getDensityOfStates() method for oxygen.
        """
        Elist = numpy.arange(0, 5000 * 11.96, 2 * 11.96)
        densStates = self.oxygen.getDensityOfStates(Elist)
        T = 100
        Qact = numpy.sum(densStates * numpy.exp(-Elist / constants.R / T))
        Qexp = self.oxygen.getPartitionFunction(T)
        self.assertAlmostEqual(Qexp, Qact, delta=1e-1 * Qexp)

    def test_getTotalMass(self):
        """
        Test the Conformer.getTotalMass() method.
        """
        self.assertAlmostEqual(
            self.conformer.getTotalMass() * constants.Na * 1000.,
            numpy.sum(self.mass), 6)

    def test_getCenterOfMass(self):
        """
        Test the Conformer.getCenterOfMass() method.
        """
        cm = self.conformer.getCenterOfMass()
        self.assertAlmostEqual(cm[0] * 1e10, -0.38045, 4)
        self.assertAlmostEqual(cm[1] * 1e10, -0.60255, 4)
        self.assertAlmostEqual(cm[2] * 1e10, -0.27900, 4)

    def test_getMomentOfInertiaTensor(self):
        """
        Test the Conformer.getMomentOfInertiaTensor() method.
        """
        I = self.conformer.getMomentOfInertiaTensor()
        self.assertAlmostEqual(I[0, 0] * constants.Na * 1e23, 20.65968, 4)
        self.assertAlmostEqual(I[0, 1] * constants.Na * 1e23, -7.48115, 4)
        self.assertAlmostEqual(I[0, 2] * constants.Na * 1e23, -3.46416, 4)
        self.assertAlmostEqual(I[1, 0] * constants.Na * 1e23, -7.48115, 4)
        self.assertAlmostEqual(I[1, 1] * constants.Na * 1e23, 13.53472, 4)
        self.assertAlmostEqual(I[1, 2] * constants.Na * 1e23, -5.48630, 4)
        self.assertAlmostEqual(I[2, 0] * constants.Na * 1e23, -3.46416, 4)
        self.assertAlmostEqual(I[2, 1] * constants.Na * 1e23, -5.48630, 4)
        self.assertAlmostEqual(I[2, 2] * constants.Na * 1e23, 22.84296, 4)

    def test_getPrincipalMomentsOfInertia(self):
        """
        Test the Conformer.getPrincipalMomentsOfInertia() method.
        """
        I, V = self.conformer.getPrincipalMomentsOfInertia()
        self.assertAlmostEqual(I[0] * constants.Na * 1e23, 6.27074, 4)
        self.assertAlmostEqual(I[1] * constants.Na * 1e23, 25.38321, 3)
        self.assertAlmostEqual(I[2] * constants.Na * 1e23, 25.38341, 3)
        #print V
        # For some reason the axes seem to jump around (positioning and signs change)
        # but the absolute values should be the same as we expect
        expected = sorted([
            0.497140, 0.610114, 0.616938, 0.787360, 0.018454, 0.616218,
            0.364578, 0.792099, 0.489554
        ])
        result = sorted(abs(V).flat)
        for i, j in zip(expected, result):
            self.assertAlmostEqual(i, j, 4)
        return  # now because the following often fails:
        self.assertAlmostEqual(V[0, 0], 0.497140, 4)
        self.assertAlmostEqual(V[0, 1], -0.610114, 4)
        self.assertAlmostEqual(V[0, 2], -0.616938, 4)
        self.assertAlmostEqual(V[1, 0], 0.787360, 4)
        self.assertAlmostEqual(V[1, 1], 0.018454, 4)
        self.assertAlmostEqual(V[1, 2], 0.616218, 4)
        self.assertAlmostEqual(V[2, 0], 0.364578, 4)
        self.assertAlmostEqual(V[2, 1], 0.792099, 4)
        self.assertAlmostEqual(V[2, 2], -0.489554, 4)

    def test_getInternalReducedMomentOfInertia(self):
        """
        Test the Conformer.getInternalReducedMomentOfInertia() method.
        """
        I = self.conformer.getInternalReducedMomentOfInertia(pivots=[1, 5],
                                                             top1=[1, 2, 3, 4])
        self.assertAlmostEqual(I * constants.Na * 1e23, 1.56768, 4)

    def test_getNumberDegreesOfFreedom(self):
        """
        Test the Conformer.getNumberDegreesOfFreedom() method.
        """
        #this is for ethane:
        numberDegreesOfFreedom = self.conformer.getNumberDegreesOfFreedom()
        self.assertEqual(numberDegreesOfFreedom, 24)

        #this is for ethylene:
        # It doesn't check aganist 3*Natoms, because Natoms is not declared.
        numberDegreesOfFreedom = self.ethylene.getNumberDegreesOfFreedom()
        self.assertEqual(numberDegreesOfFreedom, 18)

        #this is for CO
        # It doesn't check aganist 3*Natoms, because Natoms is not declared.
        numberDegreesOfFreedom = self.oxygen.getNumberDegreesOfFreedom()
        self.assertEqual(numberDegreesOfFreedom, 6)
    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)
Example #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
Example #14
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"),
        )
Example #15
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
Example #16
0
class TestConformer(unittest.TestCase):
    """
    Contains unit tests of the :class:`Conformer` class.
    """
    
    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")),
            ],
            spinMultiplicity = 1,
            opticalIsomers = 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")),
            ],
            spinMultiplicity = 3,
            opticalIsomers = 1,
        )
        
        # The following data is for ethane at the CBS-QB3 level
        self.coordinates = numpy.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 = numpy.array([6, 1, 1, 1, 6, 1, 1, 1])
        self.mass = numpy.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),
            ],
            spinMultiplicity = 1,
            opticalIsomers = 1,
            coordinates = (self.coordinates,"angstrom"),
            number = self.number,
            mass = (self.mass,"amu"),
        )
        
    def test_getPartitionFunction_ethylene(self):
        """
        Test the StatMech.getPartitionFunction() method for ethylene.
        """
        Tlist = numpy.array([300,500,1000,1500,2000])
        Qexplist = numpy.array([4.05311e+09, 4.19728e+10, 2.82309e+12, 7.51135e+13, 1.16538e+15])
        for T, Qexp in zip(Tlist, Qexplist):
            Qact = self.ethylene.getPartitionFunction(T)
            self.assertAlmostEqual(Qexp, Qact, delta=1e-4*Qexp)

    def test_getHeatCapacity_ethylene(self):
        """
        Test the StatMech.getHeatCapacity() method for ethylene.
        """
        Tlist = numpy.array([300,500,1000,1500,2000])
        Cvexplist = numpy.array([5.11186, 7.40447, 11.1659, 13.1221, 14.1617]) * constants.R
        for T, Cvexp in zip(Tlist, Cvexplist):
            Cvact = self.ethylene.getHeatCapacity(T)
            self.assertAlmostEqual(Cvexp, Cvact, 3)
    
    def test_getEnthalpy_ethylene(self):
        """
        Test the StatMech.getEnthalpy() method for ethylene.
        """
        Tlist = numpy.array([300,500,1000,1500,2000])
        Hexplist = numpy.array([4.23129, 5.04826, 7.27337, 8.93167, 10.1223]) * constants.R * Tlist
        for T, Hexp in zip(Tlist, Hexplist):
            Hact = self.ethylene.getEnthalpy(T)
            self.assertAlmostEqual(Hexp, Hact, delta=1e-4*Hexp)
    
    def test_getEntropy_ethylene(self):
        """
        Test the StatMech.getEntropy() method for ethylene.
        """
        Tlist = numpy.array([300,500,1000,1500,2000])
        Sexplist = numpy.array([26.3540, 29.5085, 35.9422, 40.8817, 44.8142]) * constants.R
        for T, Sexp in zip(Tlist, Sexplist):
            Sact = self.ethylene.getEntropy(T)
            self.assertAlmostEqual(Sexp, Sact, 3)
    
    def test_getSumOfStates_ethylene(self):
        """
        Test the StatMech.getSumOfStates() method for ethylene.
        """
        Elist = numpy.arange(0, 5000*11.96, 2*11.96)
        sumStates = self.ethylene.getSumOfStates(Elist)
        densStates = self.ethylene.getDensityOfStates(Elist)
        for n in range(10, len(Elist)):
            self.assertTrue(0.8 < numpy.sum(densStates[0:n+1]) / sumStates[n] < 1.25, '{0} != {1}'.format(numpy.sum(densStates[0:n+1]), sumStates[n]))
            
    def test_getDensityOfStates_ethylene(self):
        """
        Test the StatMech.getDensityOfStates() method for ethylene.
        """
        Elist = numpy.arange(0, 5000*11.96, 2*11.96)
        densStates = self.ethylene.getDensityOfStates(Elist)
        T = 100
        Qact = numpy.sum(densStates * numpy.exp(-Elist / constants.R / T))
        Qexp = self.ethylene.getPartitionFunction(T)
        self.assertAlmostEqual(Qexp, Qact, delta=1e-1*Qexp)

    def test_getPartitionFunction_oxygen(self):
        """
        Test the StatMech.getPartitionFunction() method for oxygen.
        """
        Tlist = numpy.array([300,500,1000,1500,2000])
        Qexplist = numpy.array([1.55584e+09, 9.38339e+09, 1.16459e+11, 5.51016e+11, 1.72794e+12])
        for T, Qexp in zip(Tlist, Qexplist):
            Qact = self.oxygen.getPartitionFunction(T)
            self.assertAlmostEqual(Qexp, Qact, delta=1e-4*Qexp)

    def test_getHeatCapacity_oxygen(self):
        """
        Test the StatMech.getHeatCapacity() method for oxygen.
        """
        Tlist = numpy.array([300,500,1000,1500,2000])
        Cvexplist = numpy.array([3.52538, 3.70877, 4.14751, 4.32063, 4.39392]) * constants.R
        for T, Cvexp in zip(Tlist, Cvexplist):
            Cvact = self.oxygen.getHeatCapacity(T)
            self.assertAlmostEqual(Cvexp, Cvact, 3)
    
    def test_getEnthalpy_oxygen(self):
        """
        Test the StatMech.getEnthalpy() method for oxygen.
        """
        Tlist = numpy.array([300,500,1000,1500,2000])
        Hexplist = numpy.array([3.50326, 3.54432, 3.75062, 3.91623, 4.02765]) * constants.R * Tlist
        for T, Hexp in zip(Tlist, Hexplist):
            Hact = self.oxygen.getEnthalpy(T)
            self.assertAlmostEqual(Hexp, Hact, delta=1e-4*Hexp)
    
    def test_getEntropy_oxygen(self):
        """
        Test the StatMech.getEntropy() method for oxygen.
        """
        Tlist = numpy.array([300,500,1000,1500,2000])
        Sexplist = numpy.array([24.6685, 26.5065, 29.2314, 30.9513, 32.2056]) * constants.R
        for T, Sexp in zip(Tlist, Sexplist):
            Sact = self.oxygen.getEntropy(T)
            self.assertAlmostEqual(Sexp, Sact, 3)
    
    def test_getSumOfStates_oxygen(self):
        """
        Test the StatMech.getSumOfStates() method for oxygen.
        """
        Elist = numpy.arange(0, 5000*11.96, 2*11.96)
        sumStates = self.oxygen.getSumOfStates(Elist)
        densStates = self.oxygen.getDensityOfStates(Elist)
        for n in range(10, len(Elist)):
            self.assertTrue(0.8 < numpy.sum(densStates[0:n+1]) / sumStates[n] < 1.25, '{0} != {1}'.format(numpy.sum(densStates[0:n+1]), sumStates[n]))
            
    def test_getDensityOfStates_oxygen(self):
        """
        Test the StatMech.getDensityOfStates() method for oxygen.
        """
        Elist = numpy.arange(0, 5000*11.96, 2*11.96)
        densStates = self.oxygen.getDensityOfStates(Elist)
        T = 100
        Qact = numpy.sum(densStates * numpy.exp(-Elist / constants.R / T))
        Qexp = self.oxygen.getPartitionFunction(T)
        self.assertAlmostEqual(Qexp, Qact, delta=1e-1*Qexp)

    def test_getTotalMass(self):
        """
        Test the Conformer.getTotalMass() method.
        """
        self.assertAlmostEqual(self.conformer.getTotalMass()*constants.Na*1000., numpy.sum(self.mass), 6)

    def test_getCenterOfMass(self):
        """
        Test the Conformer.getCenterOfMass() method.
        """
        cm = self.conformer.getCenterOfMass()
        self.assertAlmostEqual(cm[0]*1e10, -0.38045, 4)
        self.assertAlmostEqual(cm[1]*1e10, -0.60255, 4)
        self.assertAlmostEqual(cm[2]*1e10, -0.27900, 4)

    def test_getMomentOfInertiaTensor(self):
        """
        Test the Conformer.getMomentOfInertiaTensor() method.
        """
        I = self.conformer.getMomentOfInertiaTensor()
        self.assertAlmostEqual(I[0,0]*constants.Na*1e23, 20.65968, 4)
        self.assertAlmostEqual(I[0,1]*constants.Na*1e23, -7.48115, 4)
        self.assertAlmostEqual(I[0,2]*constants.Na*1e23, -3.46416, 4)
        self.assertAlmostEqual(I[1,0]*constants.Na*1e23, -7.48115, 4)
        self.assertAlmostEqual(I[1,1]*constants.Na*1e23, 13.53472, 4)
        self.assertAlmostEqual(I[1,2]*constants.Na*1e23, -5.48630, 4)
        self.assertAlmostEqual(I[2,0]*constants.Na*1e23, -3.46416, 4)
        self.assertAlmostEqual(I[2,1]*constants.Na*1e23, -5.48630, 4)
        self.assertAlmostEqual(I[2,2]*constants.Na*1e23, 22.84296, 4)

    def test_getPrincipalMomentsOfInertia(self):
        """
        Test the Conformer.getPrincipalMomentsOfInertia() method.
        """
        I, V = self.conformer.getPrincipalMomentsOfInertia()
        self.assertAlmostEqual(I[0]*constants.Na*1e23,  6.27074, 4)
        self.assertAlmostEqual(I[1]*constants.Na*1e23, 25.38321, 3)
        self.assertAlmostEqual(I[2]*constants.Na*1e23, 25.38341, 3)
        #print V
        # For some reason the axes seem to jump around (positioning and signs change)
        # but the absolute values should be the same as we expect
        expected = sorted([0.497140,
                           0.610114,
                           0.616938,
                           0.787360,
                           0.018454,
                           0.616218,
                           0.364578,
                           0.792099,
                           0.489554])
        result = sorted(abs(V).flat)
        for i,j in zip(expected, result):
            self.assertAlmostEqual(i, j, 4)
        return # now because the following often fails:
        self.assertAlmostEqual(V[0,0],  0.497140, 4)
        self.assertAlmostEqual(V[0,1], -0.610114, 4)
        self.assertAlmostEqual(V[0,2], -0.616938, 4)
        self.assertAlmostEqual(V[1,0],  0.787360, 4)
        self.assertAlmostEqual(V[1,1],  0.018454, 4)
        self.assertAlmostEqual(V[1,2],  0.616218, 4)
        self.assertAlmostEqual(V[2,0],  0.364578, 4)
        self.assertAlmostEqual(V[2,1],  0.792099, 4)
        self.assertAlmostEqual(V[2,2], -0.489554, 4)

    def test_getInternalReducedMomentOfInertia(self):
        """
        Test the Conformer.getInternalReducedMomentOfInertia() method.
        """
        I = self.conformer.getInternalReducedMomentOfInertia(pivots=[1,5], top1=[1,2,3,4])
        self.assertAlmostEqual(I*constants.Na*1e23, 1.56768, 4)

    def test_getNumberDegreesOfFreedom(self):
        """
        Test the Conformer.getNumberDegreesOfFreedom() method.
        """
        #this is for ethane:
        numberDegreesOfFreedom = self.conformer.getNumberDegreesOfFreedom()
        self.assertEqual(numberDegreesOfFreedom, 24)

        #this is for ethylene:
        # It doesn't check aganist 3*Natoms, because Natoms is not declared.
        numberDegreesOfFreedom = self.ethylene.getNumberDegreesOfFreedom()
        self.assertEqual(numberDegreesOfFreedom, 18)

        #this is for CO
        # It doesn't check aganist 3*Natoms, because Natoms is not declared.
        numberDegreesOfFreedom = self.oxygen.getNumberDegreesOfFreedom()
        self.assertEqual(numberDegreesOfFreedom, 6)
Example #17
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}
            """)

        self.species3 = Species().from_adjacency_list("""
            multiplicity 2
            1 O u1 p2 c0 {3,S}
            2 O u0 p2 c0 {3,D}
            3 N u0 p1 c0 {1,S} {2,D}
            """)

        self.species4 = Species().from_adjacency_list("""
            Propane     
            multiplicity 1
            1  C u0 p0 c0 {2,S} {4,S} {5,S} {6,S}
            2  C u0 p0 c0 {1,S} {3,S} {7,S} {8,S}
            3  C u0 p0 c0 {2,S} {9,S} {10,S} {11,S}
            4  H u0 p0 c0 {1,S}
            5  H u0 p0 c0 {1,S}
            6  H u0 p0 c0 {1,S}
            7  H u0 p0 c0 {2,S}
            8  H u0 p0 c0 {2,S}
            9  H u0 p0 c0 {3,S}
            10 H u0 p0 c0 {3,S}
            11 H u0 p0 c0 {3,S}
            """)
Example #18
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
Example #19
0
def processThermoData(spc, thermo0, thermoClass=NASA):
    """
    Converts via Wilhoit into required `thermoClass` and sets `E0`.
    
    Resulting thermo is returned.
    """
    # TODO moving this as a global import leads to circular imports.
    from rmgpy.rmg.model import Species

    thermo = None

    # Always convert to Wilhoit so we can compute E0
    if isinstance(thermo0, Wilhoit):
        wilhoit = thermo0
    elif isinstance(thermo0, ThermoData):
        wilhoit = thermo0.toWilhoit(B=1000.)
    else:
        wilhoit = thermo0.toWilhoit()

    # Add on solvation correction
    if Species.solventData and not "Liquid thermo library" in thermo0.comment:
        solvationdatabase = getDB('solvation')
        #logging.info("Making solvent correction for {0}".format(Species.solventName))
        soluteData = solvationdatabase.getSoluteData(spc)
        solvation_correction = solvationdatabase.getSolvationCorrection(
            soluteData, Species.solventData)
        # correction is added to the entropy and enthalpy
        wilhoit.S0.value_si = (wilhoit.S0.value_si +
                               solvation_correction.entropy)
        wilhoit.H0.value_si = (wilhoit.H0.value_si +
                               solvation_correction.enthalpy)

    # Compute E0 by extrapolation to 0 K
    if spc.conformer is None:
        spc.conformer = Conformer()
    spc.conformer.E0 = wilhoit.E0

    # Convert to desired thermo class
    if thermoClass is Wilhoit:
        thermo = wilhoit
    elif thermoClass is NASA:
        if Species.solventData:
            #if liquid phase simulation keep the nasa polynomial if it comes from a liquid phase thermoLibrary. Otherwise convert wilhoit to NASA
            if "Liquid thermo library" in thermo0.comment and isinstance(
                    thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.toNASA(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
        else:
            #gas phase with species matching thermo library keep the NASA from library or convert if group additivity
            if "Thermo library" in thermo0.comment and isinstance(
                    thermo0, NASA):
                thermo = thermo0
                if thermo.E0 is None:
                    thermo.E0 = wilhoit.E0
            else:
                thermo = wilhoit.toNASA(Tmin=100.0, Tmax=5000.0, Tint=1000.0)
    else:
        raise Exception(
            'thermoClass neither NASA nor Wilhoit.  Cannot process thermo data.'
        )

    if thermo.__class__ != thermo0.__class__:
        # Compute RMS error of overall transformation
        Tlist = numpy.array(
            [300.0, 400.0, 500.0, 600.0, 800.0, 1000.0, 1500.0], numpy.float64)
        err = 0.0
        for T in Tlist:
            err += (thermo.getHeatCapacity(T) - thermo0.getHeatCapacity(T))**2
        err = math.sqrt(err / len(Tlist)) / constants.R
        # logging.log(logging.WARNING if err > 0.2 else 0, 'Average RMS error in heat capacity fit to {0} = {1:g}*R'.format(spc, err))

    return thermo
Example #20
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)
Example #21
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