Ejemplo n.º 1
0
    def __init__(self,
                 reaction,
                 Tmin=None,
                 Tmax=None,
                 Tlist=None,
                 Tcount=0,
                 sensitivity_conditions=None):
        self.usedTST = False
        self.Tmin = Tmin if Tmin is not None else (298, 'K')
        self.Tmax = Tmax if Tmax is not None else (2500, 'K')
        self.Tcount = Tcount if Tcount > 3 else 50

        if Tlist is not None:
            self.Tlist = Tlist
            self.Tmin = (min(self.Tlist.value_si), 'K')
            self.Tmax = (max(self.Tlist.value_si), 'K')
            self.Tcount = len(self.Tlist.value_si)
        else:
            self.Tlist = (1 / np.linspace(1 / self.Tmax.value_si,
                                          1 / self.Tmin.value_si, self.Tcount),
                          'K')

        self.reaction = reaction
        self.k_units = None

        if sensitivity_conditions is not None:
            self.sensitivity_conditions = [
                quantity.Quantity(condition)
                for condition in sensitivity_conditions
            ]
        else:
            self.sensitivity_conditions = None

        self.arkane_species = ArkaneSpecies(
            species=self.reaction.transition_state)
Ejemplo n.º 2
0
    def test_loading_different_versions_of_yaml(self):
        """Test loading a YAML file generated by RMG v 2.4.1 and by a more recent version"""
        arkane_spc_v_241 = ArkaneSpecies.__new__(ArkaneSpecies)
        arkane_spc_v_241.load_yaml(
            path=os.path.join(self.data_path, 'vinoxy_v_2.4.1.yml'))
        self.assertIsInstance(arkane_spc_v_241,
                              ArkaneSpecies)  # checks make_object
        self.assertEqual(arkane_spc_v_241.conformer.spin_multiplicity, 2)

        arkane_current = ArkaneSpecies.__new__(ArkaneSpecies)
        arkane_current.load_yaml(
            path=os.path.join(self.data_path, 'vinoxy_current.yml'))
        self.assertIsInstance(arkane_current,
                              ArkaneSpecies)  # checks make_object
        self.assertEqual(arkane_current.conformer.spin_multiplicity, 2)
Ejemplo n.º 3
0
    def test_load_existing_yaml(self):
        """
        Test that existing Arkane YAML files can still be loaded
        """
        # Load in YAML file
        arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies)
        arkane_spc.load_yaml(path=os.path.join(self.load_path, 'C2H6.yml'))

        self.assertIsInstance(arkane_spc, ArkaneSpecies)  # checks make_object
        self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity)
        self.assertIsInstance(arkane_spc.thermo, NASA)
        self.assertNotEqual(arkane_spc.author, '')
        self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3')
        self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N')
        self.assertEqual(arkane_spc.smiles, 'CC')
        self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list)
        self.assertEqual(arkane_spc.label, 'C2H6')
        self.assertEqual(arkane_spc.frequency_scale_factor,
                         0.99)  # checks float conversion
        self.assertFalse(arkane_spc.use_bond_corrections)
        self.assertAlmostEqual(
            arkane_spc.conformer.modes[2].frequencies.value_si[0], 818.91718,
            4)  # HarmonicOsc.
        self.assertIsInstance(arkane_spc.energy_transfer_model,
                              SingleExponentialDown)
        self.assertFalse(arkane_spc.is_ts)
        self.assertTrue(arkane_spc.use_hindered_rotors)
        self.assertTrue('C 7.54e-14 1.193e-13 5.52e-14' in arkane_spc.xyz)
        self.assertIsInstance(arkane_spc.chemkin_thermo_string, str)
Ejemplo n.º 4
0
    def __init__(self, reaction,
                 Tmin=None,
                 Tmax=None,
                 Tlist=None,
                 Tcount=0,
                 sensitivity_conditions=None):
        self.usedTST = False
        if Tmin is not None:
            self.Tmin = quantity.Quantity(Tmin)
        else:
            self.Tmin = None

        if Tmax is not None:
            self.Tmax = quantity.Quantity(Tmax)
        else:
            self.Tmax = None

        self.Tcount = Tcount

        if Tlist is not None:
            self.Tlist = quantity.Quantity(Tlist)
            self.Tmin = quantity.Quantity(numpy.min(self.Tlist.value_si), "K")
            self.Tmax = quantity.Quantity(numpy.max(self.Tlist.value_si), "K")
            self.Tcount = len(self.Tlist.value_si)
        else:
            if Tmin and Tmax is not None:

                if self.Tcount <= 3.:
                    self.Tcount = 50

                stepsize = (self.Tmax.value_si - self.Tmin.value_si) / self.Tcount

                self.Tlist = quantity.Quantity(numpy.arange(self.Tmin.value_si,
                                                            self.Tmax.value_si + stepsize, stepsize), 'K')
            else:
                self.Tlist = None

        self.reaction = reaction
        self.kunits = None

        if sensitivity_conditions is not None:
            self.sensitivity_conditions = [quantity.Quantity(condition) for condition in sensitivity_conditions]
        else:
            self.sensitivity_conditions = None

        self.arkane_species = ArkaneSpecies(species=self.reaction.transitionState)
Ejemplo n.º 5
0
    def test_create_and_load_yaml(self):
        """
        Test properly loading the ArkaneSpecies object and respective sub-objects
        """
        # Create YAML file by running Arkane
        job_list = self.arkane.load_input_file(self.dump_input_path)
        for job in job_list:
            job.execute(output_directory=self.dump_path)

        # Load in newly created YAML file
        arkane_spc_old = job_list[0].arkane_species
        arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies)
        arkane_spc.load_yaml(
            path=os.path.join(self.dump_path, 'species', arkane_spc_old.label +
                              '.yml'))

        self.assertIsInstance(arkane_spc, ArkaneSpecies)  # checks make_object
        self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity)
        self.assertIsInstance(arkane_spc.thermo, NASA)
        self.assertNotEqual(arkane_spc.author, '')
        self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3')
        self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N')
        self.assertEqual(arkane_spc.smiles, 'CC')
        self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list)
        self.assertEqual(arkane_spc.label, 'C2H6')
        self.assertEqual(arkane_spc.frequency_scale_factor,
                         0.99 * 1.014)  # checks float conversion
        self.assertFalse(arkane_spc.use_bond_corrections)
        self.assertAlmostEqual(
            arkane_spc.conformer.modes[2].frequencies.value_si[0], 830.38202,
            4)  # HarmonicOsc.
        self.assertIsInstance(arkane_spc.energy_transfer_model,
                              SingleExponentialDown)
        self.assertFalse(arkane_spc.is_ts)
        self.assertEqual(arkane_spc.level_of_theory, 'cbs-qb3')
        self.assertIsInstance(arkane_spc.thermo_data, ThermoData)
        self.assertTrue(arkane_spc.use_hindered_rotors)
        self.assertIsInstance(arkane_spc.chemkin_thermo_string, str)
        expected_xyz = """8
C2H6
C       0.00075400    0.00119300    0.00055200
H       0.00074000    0.00117100    1.09413800
H       1.04376600    0.00117100   -0.32820200
H      -0.44760300    0.94289500   -0.32825300
C      -0.76014200   -1.20389600   -0.55748300
H      -0.76012800   -1.20387400   -1.65106900
H      -0.31178500   -2.14559800   -0.22867800
H      -1.80315400   -1.20387400   -0.22872900"""
        self.assertEqual(arkane_spc.xyz, expected_xyz)
Ejemplo n.º 6
0
    def __init__(self, reaction,  
                 Tmin=None, 
                 Tmax=None,
                 Tlist=None,
                 Tcount=0,
                 sensitivity_conditions=None):
        self.usedTST = False
        if Tmin is not None:
            self.Tmin = quantity.Quantity(Tmin)
        else:
            self.Tmin = None
            
        if Tmax is not None:
            self.Tmax = quantity.Quantity(Tmax)
        else:
            self.Tmax = None
        
        self.Tcount = Tcount
        
        if Tlist is not None:
            self.Tlist = quantity.Quantity(Tlist)
            self.Tmin = quantity.Quantity(numpy.min(self.Tlist.value_si), "K")
            self.Tmax = quantity.Quantity(numpy.max(self.Tlist.value_si), "K")
            self.Tcount = len(self.Tlist.value_si)
        else:
            if Tmin and Tmax is not None:
                
                if self.Tcount <= 3.:
                    self.Tcount = 50
                
                stepsize = (self.Tmax.value_si-self.Tmin.value_si) / self.Tcount
                
                self.Tlist = quantity.Quantity(numpy.arange(self.Tmin.value_si, self.Tmax.value_si+stepsize, stepsize),"K")
            else:
                self.Tlist = None
        
        self.reaction = reaction
        self.kunits = None

        if sensitivity_conditions is not None:
            self.sensitivity_conditions = [quantity.Quantity(condition) for condition in sensitivity_conditions]
        else:
            self.sensitivity_conditions = None

        self.arkane_species = ArkaneSpecies(species=self.reaction.transitionState)
Ejemplo n.º 7
0
    def test_create_and_load_yaml(self):
        """
        Test properly loading the ArkaneSpecies object and respective sub-objects
        """
        # Create YAML file by running Arkane
        jobList = self.arkane.loadInputFile(self.dump_input_path)
        for job in jobList:
            job.execute(output_directory=self.dump_path)

        # Load in newly created YAML file
        arkane_spc_old = jobList[0].arkane_species
        arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies)
        arkane_spc.load_yaml(
            path=os.path.join(self.dump_path, 'species', arkane_spc_old.label +
                              '.yml'))

        self.assertIsInstance(arkane_spc, ArkaneSpecies)  # checks make_object
        self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity)
        self.assertIsInstance(arkane_spc.thermo, NASA)
        self.assertNotEqual(arkane_spc.author, '')
        self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3')
        self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N')
        self.assertEqual(arkane_spc.smiles, 'CC')
        self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list)
        self.assertEqual(arkane_spc.label, 'C2H6')
        self.assertEqual(arkane_spc.frequency_scale_factor,
                         0.99 * 1.014)  # checks float conversion
        self.assertFalse(arkane_spc.use_bond_corrections)
        self.assertAlmostEqual(
            arkane_spc.conformer.modes[2].frequencies.value_si[0], 830.38202,
            4)  # HarmonicOsc.
        self.assertIsInstance(arkane_spc.energy_transfer_model,
                              SingleExponentialDown)
        self.assertFalse(arkane_spc.is_ts)
        self.assertEqual(arkane_spc.level_of_theory, 'cbs-qb3')
        self.assertIsInstance(arkane_spc.thermo_data, ThermoData)
        self.assertTrue(arkane_spc.use_hindered_rotors)
        self.assertTrue('C 7.54e-14 1.193e-13 5.52e-14' in arkane_spc.xyz)
        self.assertIsInstance(arkane_spc.chemkin_thermo_string, str)
Ejemplo n.º 8
0
class KineticsJob(object):
    """
    A representation of an Arkane kinetics job. This job is used to compute
    and save the high-pressure-limit kinetics information for a single reaction.

    `usedTST` - a boolean representing if TST was used to calculate the kinetics
                if kinetics is already given in the input, then it is False.
    """
    
    def __init__(self, reaction,  
                 Tmin=None, 
                 Tmax=None,
                 Tlist=None,
                 Tcount=0,
                 sensitivity_conditions=None):
        self.usedTST = False
        if Tmin is not None:
            self.Tmin = quantity.Quantity(Tmin)
        else:
            self.Tmin = None
            
        if Tmax is not None:
            self.Tmax = quantity.Quantity(Tmax)
        else:
            self.Tmax = None
        
        self.Tcount = Tcount
        
        if Tlist is not None:
            self.Tlist = quantity.Quantity(Tlist)
            self.Tmin = quantity.Quantity(numpy.min(self.Tlist.value_si), "K")
            self.Tmax = quantity.Quantity(numpy.max(self.Tlist.value_si), "K")
            self.Tcount = len(self.Tlist.value_si)
        else:
            if Tmin and Tmax is not None:
                
                if self.Tcount <= 3.:
                    self.Tcount = 50
                
                stepsize = (self.Tmax.value_si-self.Tmin.value_si) / self.Tcount
                
                self.Tlist = quantity.Quantity(numpy.arange(self.Tmin.value_si, self.Tmax.value_si+stepsize, stepsize),"K")
            else:
                self.Tlist = None
        
        self.reaction = reaction
        self.kunits = None

        if sensitivity_conditions is not None:
            self.sensitivity_conditions = [quantity.Quantity(condition) for condition in sensitivity_conditions]
        else:
            self.sensitivity_conditions = None

        self.arkane_species = ArkaneSpecies(species=self.reaction.transitionState)
    
    @property
    def Tmin(self):
        """The minimum temperature at which the computed k(T) values are valid, or ``None`` if not defined."""
        return self._Tmin
    @Tmin.setter
    def Tmin(self, value):
        self._Tmin = quantity.Temperature(value)
    
    @property
    def Tmax(self):
        """The maximum temperature at which the computed k(T) values are valid, or ``None`` if not defined."""
        return self._Tmax
    @Tmax.setter
    def Tmax(self, value):
        self._Tmax = quantity.Temperature(value)
    
    @property
    def Tlist(self):
        """The temperatures at which the k(T) values are computed."""
        return self._Tlist
    @Tlist.setter
    def Tlist(self, value):
        self._Tlist = quantity.Temperature(value)
        
    def execute(self, outputFile=None, plot=True):
        """
        Execute the kinetics job, saving the results to the given `outputFile` on disk.
        """
        if self.Tlist is not None:
            self.generateKinetics(self.Tlist.value_si)
        else:
            self.generateKinetics()
        if outputFile is not None:
            self.save(outputFile)
            if plot:
                self.plot(os.path.dirname(outputFile))
            self.draw(os.path.dirname(outputFile))
            if self.sensitivity_conditions is not None:
                logging.info('\n\nRunning sensitivity analysis...')
                sa(self, os.path.dirname(outputFile))
        logging.debug('Finished kinetics job for reaction {0}.'.format(self.reaction))
        logging.debug(repr(self.reaction))
    
    def generateKinetics(self,Tlist=None):
        """
        Generate the kinetics data for the reaction and fit it to a modified Arrhenius model.
        """

        if isinstance(self.reaction.kinetics, Arrhenius):
            return None
        self.usedTST=True
        kineticsClass = 'Arrhenius'
        
        tunneling = self.reaction.transitionState.tunneling
        if isinstance(tunneling, Wigner) and tunneling.frequency is None:
            tunneling.frequency = (self.reaction.transitionState.frequency.value_si,"cm^-1")
        elif isinstance(tunneling, Eckart) and tunneling.frequency is None:
            tunneling.frequency = (self.reaction.transitionState.frequency.value_si,"cm^-1")
            tunneling.E0_reac = (sum([reactant.conformer.E0.value_si for reactant in self.reaction.reactants])*0.001,"kJ/mol")
            tunneling.E0_TS = (self.reaction.transitionState.conformer.E0.value_si*0.001,"kJ/mol")
            tunneling.E0_prod = (sum([product.conformer.E0.value_si for product in self.reaction.products])*0.001,"kJ/mol")
        elif tunneling is not None:
            if tunneling.frequency is not None:
                # Frequency was given by the user
                pass
            else:
                raise ValueError('Unknown tunneling model {0!r} for reaction {1}.'.format(tunneling, self.reaction))
        logging.debug('Generating {0} kinetics model for {1}...'.format(kineticsClass, self.reaction))
        if Tlist is None:
            Tlist = 1000.0/numpy.arange(0.4, 3.35, 0.05)
        klist = numpy.zeros_like(Tlist)
        for i in range(Tlist.shape[0]):
            klist[i] = self.reaction.calculateTSTRateCoefficient(Tlist[i])

        order = len(self.reaction.reactants)
        klist *= 1e6 ** (order-1)
        self.kunits = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order]
        self.Kequnits = {2:'mol^2/cm^6', 1:'mol/cm^3', 0:'       ', -1:'cm^3/mol', -2:'cm^6/mol^2'}[len(self.reaction.products)-len(self.reaction.reactants)]
        self.krunits = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[len(self.reaction.products)]
        self.reaction.kinetics = Arrhenius().fitToData(Tlist, klist, kunits=self.kunits)
        self.reaction.elementary_high_p = True
        
    def save(self, outputFile):
        """
        Save the results of the kinetics job to the file located
        at `path` on disk.
        """
        reaction = self.reaction
        
        ks = []
        k0s = []
        k0revs = []
        krevs = []
        
        logging.info('Saving kinetics for {0}...'.format(reaction))
        
        order = len(self.reaction.reactants)
        factor = 1e6 ** (order-1)
        
        f = open(outputFile, 'a')

        if self.usedTST:
            # If TST is not used, eg. it was given in 'reaction', then this will throw an error.
            f.write('#   ======= =========== =========== =========== ===============\n')
            f.write('#   Temp.   k (TST)     Tunneling   k (TST+T)   Units\n')
            f.write('#   ======= =========== =========== =========== ===============\n')
            
            if self.Tlist is None:
                Tlist = numpy.array([300,400,500,600,800,1000,1500,2000])
            else:
                Tlist =self.Tlist.value_si

            for T in Tlist:  
                tunneling = reaction.transitionState.tunneling
                reaction.transitionState.tunneling = None
                try:
                    k0 = reaction.calculateTSTRateCoefficient(T) * factor
                except SpeciesError:
                    k0 = 0
                reaction.transitionState.tunneling = tunneling
                try:
                    k = reaction.calculateTSTRateCoefficient(T) * factor
                    kappa = k / k0
                except (SpeciesError,ZeroDivisionError):
                    k = reaction.getRateCoefficient(T)
                    kappa = 0
                    logging.info("The species in reaction {} do not have adequate information for TST, using default kinetics values.".format(reaction))
                tunneling = reaction.transitionState.tunneling
                ks.append(k)
                k0s.append(k0)

                f.write('#    {0:4g} K {1:11.3e} {2:11g} {3:11.3e} {4}\n'.format(T, k0, kappa, k, self.kunits))
            f.write('#   ======= =========== =========== =========== ===============\n')
            f.write('\n\n')
            
            f.write('#   ======= ============ =========== ============ ============= =========\n')
            f.write('#   Temp.    Kc (eq)        Units     krev (TST)   krev (TST+T)   Units\n')
            f.write('#   ======= ============ =========== ============ ============= =========\n')

            # Initialize Object for Converting Units
            if self.Kequnits != '       ':
                keq_unit_converter = quantity.Units(self.Kequnits).getConversionFactorFromSI()
            else:
                keq_unit_converter = 1

            for n,T in enumerate(Tlist):
                k = ks[n]
                k0 = k0s[n]
                Keq = keq_unit_converter * reaction.getEquilibriumConstant(T)  # getEquilibriumConstant returns SI units
                k0rev = k0/Keq
                krev =  k/Keq
                k0revs.append(k0rev)
                krevs.append(krev)
                f.write('#    {0:4g} K {1:11.3e}   {2}  {3:11.3e}   {4:11.3e}      {5}\n'.format(T, Keq, self.Kequnits, k0rev, krev, self.krunits))

            f.write('#   ======= ============ =========== ============ ============= =========\n')
            f.write('\n\n')

            kinetics0rev = Arrhenius().fitToData(Tlist, numpy.array(k0revs), kunits=self.krunits)
            kineticsrev = Arrhenius().fitToData(Tlist, numpy.array(krevs), kunits=self.krunits)
            
            f.write('# krev (TST) = {0} \n'.format(kinetics0rev))
            f.write('# krev (TST+T) = {0} \n\n'.format(kineticsrev))

        # Reaction path degeneracy is INCLUDED in the kinetics itself!
        string = 'kinetics(label={0!r}, kinetics={1!r})'.format(reaction.label, reaction.kinetics)
        f.write('{0}\n\n'.format(prettify(string)))
        
        f.close()
        
        # Also save the result to chem.inp
        f = open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a')
        
        reaction = self.reaction
        kinetics = reaction.kinetics

        string = ''
        if reaction.kinetics.comment:
            for line in reaction.kinetics.comment.split("\n"):
                string += "! {0}\n".format(line)
        string += '{0!s:51} {1:9.3e} {2:9.3f} {3:9.3f}\n'.format(
            reaction,
            kinetics.A.value_si * factor,
            kinetics.n.value_si,
            kinetics.Ea.value_si / 4184.,
        )
                
        f.write('{0}\n'.format(string))
            
        f.close()

        # We're saving a YAML file for TSs iff structures of the respective reactant/s and product/s are known
        if all ([spc.molecule is not None and len(spc.molecule)
                 for spc in self.reaction.reactants + self.reaction.products]):
            self.arkane_species.update_species_attributes(self.reaction.transitionState)
            self.arkane_species.reaction_label = reaction.label
            self.arkane_species.reactants = [{'label': spc.label, 'adjacency_list': spc.molecule[0].toAdjacencyList()}
                                             for spc in self.reaction.reactants]
            self.arkane_species.products = [{'label': spc.label, 'adjacency_list': spc.molecule[0].toAdjacencyList()}
                                             for spc in self.reaction.products]
            self.arkane_species.save_yaml(path=os.path.dirname(outputFile))

    def plot(self, outputDirectory):
        """
        Plot both the raw kinetics data and the Arrhenius fit versus 
        temperature. The plot is saved to the file ``kinetics.pdf`` in the
        output directory. The plot is not generated if ``matplotlib`` is not
        installed.
        """
        # Skip this step if matplotlib is not installed
        try:
            import matplotlib.pyplot as plt
        except ImportError:
            return
        if self.Tlist is not None:
            t_list = [t for t in self.Tlist.value_si]
        else:
            t_list = 1000.0/numpy.arange(0.4, 3.35, 0.05)
        klist = numpy.zeros_like(t_list)
        klist2 = numpy.zeros_like(t_list)
        for i in xrange(len(t_list)):
            klist[i] = self.reaction.calculateTSTRateCoefficient(t_list[i])
            klist2[i] = self.reaction.kinetics.getRateCoefficient(t_list[i])

        order = len(self.reaction.reactants)
        klist *= 1e6 ** (order-1)
        klist2 *= 1e6 ** (order-1)
        t_list = [1000.0 / t for t in t_list]
        plt.semilogy(t_list, klist, 'ob', label='TST calculation')
        plt.semilogy(t_list, klist2, '-k', label='Fitted rate')
        plt.legend()
        reaction_str = '{0} {1} {2}'.format(
            ' + '.join([reactant.label for reactant in self.reaction.reactants]),
            '<=>', ' + '.join([product.label for product in self.reaction.products]))
        plt.title(reaction_str)
        plt.xlabel('1000 / Temperature (1000/K)')
        plt.ylabel('Rate coefficient ({0})'.format(self.kunits))

        plot_path = os.path.join(outputDirectory, 'plots')

        if not os.path.exists(plot_path):
            os.mkdir(plot_path)
        valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits)
        filename = ''.join(c for c in reaction_str if c in valid_chars) + '.pdf'
        plt.savefig(os.path.join(plot_path, filename))
        plt.close()

    def draw(self, outputDirectory, format='pdf'):
        """
        Generate a PDF drawing of the reaction.
        This requires that Cairo and its Python wrapper be available; if not,
        the drawing is not generated.

        You may also generate different formats of drawings, by changing format to
        one of the following: `pdf`, `svg`, `png`.
        """

        drawing_path = os.path.join(outputDirectory, 'paths')

        if not os.path.exists(drawing_path):
            os.mkdir(drawing_path)
        valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits)
        reaction_str = '{0} {1} {2}'.format(
            ' + '.join([reactant.label for reactant in self.reaction.reactants]),
            '<=>', ' + '.join([product.label for product in self.reaction.products]))
        filename = ''.join(c for c in reaction_str if c in valid_chars) + '.pdf'
        path = os.path.join(drawing_path, filename)

        KineticsDrawer().draw(self.reaction, format=format, path=path)
Ejemplo n.º 9
0
 def __init__(self, species, thermo_class):
     self.species = species
     self.thermo_class = thermo_class
     self.arkane_species = ArkaneSpecies(species=species)
Ejemplo n.º 10
0
class ThermoJob(object):
    """
    A representation of an Arkane thermodynamics job. This job is used to
    compute and save the thermodynamics information for a single species.
    """
    def __init__(self, species, thermo_class):
        self.species = species
        self.thermo_class = thermo_class
        self.arkane_species = ArkaneSpecies(species=species)

    def execute(self, output_directory=None, plot=False):
        """
        Execute the thermodynamics job, saving the results within
        the `output_directory`.

        If `plot` is true, then plots of the raw and fitted values for heat
        capacity, entropy, enthalpy, gibbs free energy, and hindered rotors
        will be saved.
        """
        self.generate_thermo()
        if output_directory is not None:
            try:
                self.write_output(output_directory)
            except Exception as e:
                logging.warning("Could not write output file due to error: "
                                "{0} for species {1}".format(
                                    e, self.species.label))
            try:
                self.arkane_species.chemkin_thermo_string = self.write_chemkin(
                    output_directory)
            except Exception as e:
                logging.warning("Could not write chemkin output due to error: "
                                "{0} for species {1}".format(
                                    e, self.species.label))
            if self.species.molecule is None or len(
                    self.species.molecule) == 0:
                logging.debug(
                    "Not generating a YAML file for species {0}, since its structure wasn't"
                    " specified".format(self.species.label))
            else:
                # We're saving a YAML file for species iff Thermo is called and they're structure is known
                self.arkane_species.update_species_attributes(self.species)
                self.arkane_species.save_yaml(path=output_directory)
            if plot:
                try:
                    self.plot(output_directory)
                except Exception as e:
                    logging.warning("Could not create plots due to error: "
                                    "{0} for species {1}".format(
                                        e, self.species.label))

    def generate_thermo(self):
        """
        Generate the thermodynamic data for the species and fit it to the
        desired heat capacity model (as specified in the `thermo_class`
        attribute).
        """
        if self.thermo_class.lower() not in ['wilhoit', 'nasa']:
            raise InputError('Unknown thermodynamic model "{0}".'.format(
                self.thermo_class))

        species = self.species

        logging.debug('Generating {0} thermo model for {1}...'.format(
            self.thermo_class, species))

        if species.thermo is not None:
            logging.info(
                "Thermo already generated for species {}. Skipping thermo generation."
                .format(species))
            return None

        Tlist = np.arange(10.0, 3001.0, 10.0, np.float64)
        Cplist = np.zeros_like(Tlist)
        H298 = 0.0
        S298 = 0.0
        conformer = self.species.conformer
        for i in range(Tlist.shape[0]):
            Cplist[i] += conformer.get_heat_capacity(Tlist[i])
        H298 += conformer.get_enthalpy(298.) + conformer.E0.value_si
        S298 += conformer.get_entropy(298.)

        if not any([
                isinstance(mode, (LinearRotor, NonlinearRotor))
                for mode in conformer.modes
        ]):
            # Monatomic species
            n_freq = 0
            n_rotors = 0
            Cp0 = 2.5 * constants.R
            CpInf = 2.5 * constants.R
        else:
            # Polyatomic species
            linear = True if isinstance(conformer.modes[1],
                                        LinearRotor) else False
            n_freq = len(conformer.modes[2].frequencies.value)
            n_rotors = len(conformer.modes[3:])
            Cp0 = (3.5 if linear else 4.0) * constants.R
            CpInf = Cp0 + (n_freq + 0.5 * n_rotors) * constants.R

        wilhoit = Wilhoit()
        if n_freq == 0 and n_rotors == 0:
            wilhoit.Cp0 = (Cplist[0], "J/(mol*K)")
            wilhoit.CpInf = (Cplist[0], "J/(mol*K)")
            wilhoit.B = (500., "K")
            wilhoit.H0 = (0.0, "J/mol")
            wilhoit.S0 = (0.0, "J/(mol*K)")
            wilhoit.H0 = (H298 - wilhoit.get_enthalpy(298.15), "J/mol")
            wilhoit.S0 = (S298 - wilhoit.get_entropy(298.15), "J/(mol*K)")
        else:
            wilhoit.fit_to_data(Tlist,
                                Cplist,
                                Cp0,
                                CpInf,
                                H298,
                                S298,
                                B0=500.0)

        if self.thermo_class.lower() == 'nasa':
            species.thermo = wilhoit.to_nasa(Tmin=10.0,
                                             Tmax=3000.0,
                                             Tint=500.0)
        else:
            species.thermo = wilhoit

    def write_output(self, output_directory):
        """
        Save the results of the thermodynamics job to the `output.py` file located
        in `output_directory`.
        """
        species = self.species
        output_file = os.path.join(output_directory, 'output.py')
        logging.info('Saving thermo for {0}...'.format(species.label))

        with open(output_file, 'a') as f:
            f.write('# Thermodynamics for {0}:\n'.format(species.label))
            H298 = species.get_thermo_data().get_enthalpy(298) / 4184.
            S298 = species.get_thermo_data().get_entropy(298) / 4.184
            f.write(
                '#   Enthalpy of formation (298 K)   = {0:9.3f} kcal/mol\n'.
                format(H298))
            f.write(
                '#   Entropy of formation (298 K)    = {0:9.3f} cal/(mol*K)\n'.
                format(S298))
            f.write(
                '#    =========== =========== =========== =========== ===========\n'
            )
            f.write(
                '#    Temperature Heat cap.   Enthalpy    Entropy     Free energy\n'
            )
            f.write(
                '#    (K)         (cal/mol*K) (kcal/mol)  (cal/mol*K) (kcal/mol)\n'
            )
            f.write(
                '#    =========== =========== =========== =========== ===========\n'
            )
            for T in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]:
                try:
                    Cp = species.get_thermo_data().get_heat_capacity(T) / 4.184
                    H = species.get_thermo_data().get_enthalpy(T) / 4184.
                    S = species.get_thermo_data().get_entropy(T) / 4.184
                    G = species.get_thermo_data().get_free_energy(T) / 4184.
                    f.write(
                        '#    {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n'
                        .format(T, Cp, H, S, G))
                except ValueError:
                    logging.debug(
                        "Valid thermo for {0} is outside range for temperature {1}"
                        .format(species, T))
            f.write(
                '#    =========== =========== =========== =========== ===========\n'
            )

            thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format(
                species.label, species.get_thermo_data())
            f.write('{0}\n\n'.format(prettify(thermo_string)))

    def write_chemkin(self, output_directory):
        """
        Appends the thermo block to `chem.inp` and species name to
        `species_dictionary.txt` within the `outut_directory` specified
        """
        species = self.species
        with open(os.path.join(output_directory, 'chem.inp'), 'a') as f:
            if isinstance(species, Species):
                if species.molecule and isinstance(species.molecule[0],
                                                   Molecule):
                    element_counts = get_element_count(species.molecule[0])
                else:
                    try:
                        element_counts = species.props['element_counts']
                    except KeyError:
                        element_counts = self.element_count_from_conformer()
            else:
                element_counts = {'C': 0, 'H': 0}
            chemkin_thermo_string = write_thermo_entry(
                species, element_counts=element_counts, verbose=True)
            f.write('{0}\n'.format(chemkin_thermo_string))

        # write species dictionary
        if isinstance(species, Species):
            if species.molecule and isinstance(species.molecule[0], Molecule):
                spec_dict_path = os.path.join(output_directory,
                                              'species_dictionary.txt')
                is_species_in_dict = False
                if os.path.isfile(spec_dict_path):
                    with open(spec_dict_path, 'r') as f:
                        # check whether the species dictionary contains this species, in which case do not re-append
                        for line in f.readlines():
                            if species.label == line.strip():
                                is_species_in_dict = True
                                break
                if not is_species_in_dict:
                    with open(spec_dict_path, 'a') as f:
                        f.write(species.molecule[0].to_adjacency_list(
                            remove_h=False, label=species.label))
                        f.write('\n')
        return chemkin_thermo_string

    def element_count_from_conformer(self):
        """
        Get an element count in a dictionary form (e.g.,  {'C': 3, 'H': 8}) from the species.conformer attribute.

        Returns:
            dict: Element count, keys are element symbols,
                  values are number of occurrences of the element in the molecule.
        """
        element_counts = dict()
        for number in self.species.conformer.number.value_si:
            symbol = symbol_by_number[number]
            if symbol in element_counts:
                element_counts[symbol] += 1
            else:
                element_counts[symbol] = 1
        return element_counts

    def plot(self, output_directory):
        """
        Plot the heat capacity, enthapy, entropy, and Gibbs free energy of the
        fitted thermodynamics model, along with the same values from the
        statistical mechanics model that the thermodynamics model was fitted 
        to. The plot is saved to the file ``thermo.pdf`` in the output
        directory. The plot is not generated if ``matplotlib`` is not installed.
        """
        # Skip this step if matplotlib is not installed
        try:
            import matplotlib.pyplot as plt
        except ImportError:
            return

        Tlist = np.arange(10.0, 2501.0, 10.0)
        Cplist = np.zeros_like(Tlist)
        Cplist1 = np.zeros_like(Tlist)
        Hlist = np.zeros_like(Tlist)
        Hlist1 = np.zeros_like(Tlist)
        Slist = np.zeros_like(Tlist)
        Slist1 = np.zeros_like(Tlist)
        Glist = np.zeros_like(Tlist)
        Glist1 = np.zeros_like(Tlist)

        conformer = self.species.conformer
        thermo = self.species.get_thermo_data()
        for i in range(Tlist.shape[0]):
            try:
                Cplist[i] = conformer.get_heat_capacity(Tlist[i])
                Slist[i] = conformer.get_entropy(Tlist[i])
                Hlist[i] = (conformer.get_enthalpy(Tlist[i]) +
                            conformer.E0.value_si) * 0.001
                Glist[i] = Hlist[i] - Tlist[i] * Slist[i] * 0.001
                Cplist1[i] = thermo.get_heat_capacity(Tlist[i])
                Slist1[i] = thermo.get_entropy(Tlist[i])
                Hlist1[i] = thermo.get_enthalpy(Tlist[i]) * 0.001
                Glist1[i] = thermo.get_free_energy(Tlist[i]) * 0.001
            except (ValueError, AttributeError):
                continue

        fig = plt.figure(figsize=(10, 8))
        fig.suptitle('{0}'.format(self.species.label))
        plt.subplot(2, 2, 1)
        plt.plot(Tlist, Cplist / 4.184, '-r', Tlist, Cplist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Heat capacity (cal/mol*K)')
        plt.legend(['statmech', 'fitted'], loc=4)

        plt.subplot(2, 2, 2)
        plt.plot(Tlist, Slist / 4.184, '-r', Tlist, Slist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Entropy (cal/mol*K)')

        plt.subplot(2, 2, 3)
        plt.plot(Tlist, Hlist / 4.184, '-r', Tlist, Hlist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Enthalpy (kcal/mol)')

        plt.subplot(2, 2, 4)
        plt.plot(Tlist, Glist / 4.184, '-r', Tlist, Glist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Gibbs free energy (kcal/mol)')

        fig.subplots_adjust(left=0.10,
                            bottom=0.08,
                            right=0.95,
                            top=0.95,
                            wspace=0.35,
                            hspace=0.20)

        plot_path = os.path.join(output_directory, 'plots')

        if not os.path.exists(plot_path):
            os.mkdir(plot_path)
        valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits)
        filename = ''.join(
            c for c in self.species.label if c in valid_chars) + '.pdf'
        plt.savefig(os.path.join(plot_path, filename))
        plt.close()
Ejemplo n.º 11
0
class KineticsJob(object):
    """
    A representation of an Arkane kinetics job. This job is used to compute
    and save the high-pressure-limit kinetics information for a single reaction.

    `usedTST` - a boolean representing if TST was used to calculate the kinetics
                if kinetics is already given in the input, then it is False.
    `three_params` - a boolean representing if the modified three-parameter Arrhenius equation is used to calculate
                     high pressure kinetic rate coefficients. If it is False, the classical two-parameter Arrhenius
                     equation is used.
    """
    def __init__(self,
                 reaction,
                 Tmin=None,
                 Tmax=None,
                 Tlist=None,
                 Tcount=0,
                 sensitivity_conditions=None,
                 three_params=True):
        self.usedTST = False
        self.Tmin = Tmin if Tmin is not None else (298, 'K')
        self.Tmax = Tmax if Tmax is not None else (2500, 'K')
        self.Tcount = Tcount if Tcount > 3 else 50
        self.three_params = three_params

        if Tlist is not None:
            self.Tlist = Tlist
            self.Tmin = (min(self.Tlist.value_si), 'K')
            self.Tmax = (max(self.Tlist.value_si), 'K')
            self.Tcount = len(self.Tlist.value_si)
        else:
            self.Tlist = (1 / np.linspace(1 / self.Tmax.value_si,
                                          1 / self.Tmin.value_si, self.Tcount),
                          'K')

        self.reaction = reaction
        self.k_units = None

        if sensitivity_conditions is not None:
            self.sensitivity_conditions = [
                quantity.Quantity(condition)
                for condition in sensitivity_conditions
            ]
        else:
            self.sensitivity_conditions = None

        self.arkane_species = ArkaneSpecies(
            species=self.reaction.transition_state)

    @property
    def Tmin(self):
        """The minimum temperature at which the computed k(T) values are valid, or ``None`` if not defined."""
        return self._Tmin

    @Tmin.setter
    def Tmin(self, value):
        self._Tmin = quantity.Temperature(value)

    @property
    def Tmax(self):
        """The maximum temperature at which the computed k(T) values are valid, or ``None`` if not defined."""
        return self._Tmax

    @Tmax.setter
    def Tmax(self, value):
        self._Tmax = quantity.Temperature(value)

    @property
    def Tlist(self):
        """The temperatures at which the k(T) values are computed."""
        return self._Tlist

    @Tlist.setter
    def Tlist(self, value):
        self._Tlist = quantity.Temperature(value)

    def execute(self, output_directory=None, plot=True):
        """
        Execute the kinetics job, saving the results within
        the `output_directory`.

        If `plot` is True, then plots of the raw and fitted values for the kinetics
        will be saved.
        """
        self.generate_kinetics()
        if output_directory is not None:
            try:
                self.write_output(output_directory)
            except Exception as e:
                logging.warning(
                    "Could not write kinetics output file due to error: "
                    "{0} in reaction {1}".format(e, self.reaction.label))
            try:
                self.write_chemkin(output_directory)
            except Exception as e:
                logging.warning(
                    "Could not write kinetics chemkin output due to error: "
                    "{0} in reaction {1}".format(e, self.reaction.label))
            if plot:
                try:
                    self.plot(output_directory)
                except Exception as e:
                    logging.warning("Could not plot kinetics due to error: "
                                    "{0} in reaction {1}".format(
                                        e, self.reaction.label))
                try:
                    self.draw(output_directory)
                except Exception as e:
                    logging.warning(
                        "Could not draw reaction {1} due to error: {0}".format(
                            e, self.reaction.label))
            if self.sensitivity_conditions is not None:
                logging.info('\n\nRunning sensitivity analysis...')
                SensAnalysis(self, output_directory)
        logging.debug('Finished kinetics job for reaction {0}.'.format(
            self.reaction))
        logging.debug(repr(self.reaction))

    def generate_kinetics(self):
        """
        Generate the kinetics data for the reaction and fit it to a modified Arrhenius model.
        """

        if isinstance(self.reaction.kinetics, Arrhenius):
            return None
        self.usedTST = True
        kinetics_class = 'Arrhenius'

        tunneling = self.reaction.transition_state.tunneling
        if isinstance(tunneling, Wigner) and tunneling.frequency is None:
            tunneling.frequency = (
                self.reaction.transition_state.frequency.value_si, "cm^-1")
        elif isinstance(tunneling, Eckart) and tunneling.frequency is None:
            tunneling.frequency = (
                self.reaction.transition_state.frequency.value_si, "cm^-1")
            tunneling.E0_reac = (sum([
                reactant.conformer.E0.value_si
                for reactant in self.reaction.reactants
            ]) * 0.001, "kJ/mol")
            tunneling.E0_TS = (
                self.reaction.transition_state.conformer.E0.value_si * 0.001,
                "kJ/mol")
            tunneling.E0_prod = (sum([
                product.conformer.E0.value_si
                for product in self.reaction.products
            ]) * 0.001, "kJ/mol")
        elif tunneling is not None:
            if tunneling.frequency is not None:
                # Frequency was given by the user
                pass
            else:
                raise ValueError(
                    'Unknown tunneling model {0!r} for reaction {1}.'.format(
                        tunneling, self.reaction))
        logging.debug('Generating {0} kinetics model for {1}...'.format(
            kinetics_class, self.reaction))
        klist = np.zeros_like(self.Tlist.value_si)
        for i, t in enumerate(self.Tlist.value_si):
            klist[i] = self.reaction.calculate_tst_rate_coefficient(t)
        order = len(self.reaction.reactants)
        klist *= 1e6**(order - 1)
        self.k_units = {
            1: 's^-1',
            2: 'cm^3/(mol*s)',
            3: 'cm^6/(mol^2*s)'
        }[order]
        self.K_eq_units = {
            2: 'mol^2/cm^6',
            1: 'mol/cm^3',
            0: '       ',
            -1: 'cm^3/mol',
            -2: 'cm^6/mol^2'
        }[len(self.reaction.products) - len(self.reaction.reactants)]
        self.k_r_units = {
            1: 's^-1',
            2: 'cm^3/(mol*s)',
            3: 'cm^6/(mol^2*s)'
        }[len(self.reaction.products)]
        self.reaction.kinetics = Arrhenius().fit_to_data(
            self.Tlist.value_si,
            klist,
            kunits=self.k_units,
            three_params=self.three_params)
        self.reaction.elementary_high_p = True

    def write_output(self, output_directory):
        """
        Save the results of the kinetics job to the `output.py` file located
        in `output_directory`.
        """
        reaction = self.reaction

        ks, k0s, k0_revs, k_revs = [], [], [], []

        logging.info('Saving kinetics for {0}...'.format(reaction))

        order = len(self.reaction.reactants)

        factor = 1e6**(order - 1)

        f = open(os.path.join(output_directory, 'output.py'), 'a')

        if self.usedTST:
            # If TST is not used, eg. it was given in 'reaction', then this will throw an error.
            f.write(
                '#   ======= =========== =========== =========== ===============\n'
            )
            f.write('#   Temp.   k (TST)     Tunneling   k (TST+T)   Units\n')
            f.write(
                '#   ======= =========== =========== =========== ===============\n'
            )

            if self.Tlist is None:
                t_list = np.array([300, 400, 500, 600, 800, 1000, 1500, 2000])
            else:
                t_list = self.Tlist.value_si

            for T in t_list:
                tunneling = reaction.transition_state.tunneling
                reaction.transition_state.tunneling = None
                try:
                    k0 = reaction.calculate_tst_rate_coefficient(T) * factor
                except SpeciesError:
                    k0 = 0
                reaction.transition_state.tunneling = tunneling
                try:
                    k = reaction.calculate_tst_rate_coefficient(T) * factor
                    kappa = k / k0
                except (SpeciesError, ZeroDivisionError):
                    k = reaction.get_rate_coefficient(T)
                    kappa = 0
                    logging.info(
                        "The species in reaction {0} do not have adequate information for TST, "
                        "using default kinetics values.".format(reaction))
                tunneling = reaction.transition_state.tunneling
                ks.append(k)
                k0s.append(k0)

                f.write(
                    '#    {0:4g} K {1:11.3e} {2:11g} {3:11.3e} {4}\n'.format(
                        T, k0, kappa, k, self.k_units))
            f.write(
                '#   ======= =========== =========== =========== ===============\n'
            )
            f.write('\n\n')

            f.write(
                '#   ======= ============ =========== ============ ============= =========\n'
            )
            f.write(
                '#   Temp.    Kc (eq)        Units     k_rev (TST) k_rev (TST+T)   Units\n'
            )
            f.write(
                '#   ======= ============ =========== ============ ============= =========\n'
            )

            # Initialize Object for Converting Units
            if self.K_eq_units != '       ':
                keq_unit_converter = quantity.Units(
                    self.K_eq_units).get_conversion_factor_from_si()
            else:
                keq_unit_converter = 1

            for n, T in enumerate(t_list):
                k = ks[n]
                k0 = k0s[n]
                K_eq = keq_unit_converter * reaction.get_equilibrium_constant(
                    T)  # returns SI units
                k0_rev = k0 / K_eq
                k_rev = k / K_eq
                k0_revs.append(k0_rev)
                k_revs.append(k_rev)
                f.write(
                    '#    {0:4g} K {1:11.3e}   {2}  {3:11.3e}   {4:11.3e}      {5}\n'
                    .format(T, K_eq, self.K_eq_units, k0_rev, k_rev,
                            self.k_r_units))

            f.write(
                '#   ======= ============ =========== ============ ============= =========\n'
            )
            f.write('\n\n')

            kinetics_0_rev = Arrhenius().fit_to_data(
                t_list,
                np.array(k0_revs),
                kunits=self.k_r_units,
                three_params=self.three_params)
            kinetics_rev = Arrhenius().fit_to_data(
                t_list,
                np.array(k_revs),
                kunits=self.k_r_units,
                three_params=self.three_params)

            f.write('# k_rev (TST) = {0} \n'.format(kinetics_0_rev))
            f.write('# k_rev (TST+T) = {0} \n\n'.format(kinetics_rev))

        if self.three_params:
            f.write(
                '# kinetics fitted using the modified three-parameter Arrhenius equation '
                'k = A * (T/T0)^n * exp(-Ea/RT) \n')
        else:
            f.write(
                '# kinetics fitted using the two-parameter Arrhenius equation k = A * exp(-Ea/RT) \n'
            )

        # Reaction path degeneracy is INCLUDED in the kinetics itself!
        rxn_str = 'kinetics(label={0!r}, kinetics={1!r})'.format(
            reaction.label, reaction.kinetics)
        f.write('{0}\n\n'.format(prettify(rxn_str)))

        f.close()

    def write_chemkin(self, output_directory):
        """
        Appends the kinetics rates to `chem.inp` in `outut_directory`
        """

        # obtain a unit conversion factor
        order = len(self.reaction.reactants)
        factor = 1e6**(order - 1)

        reaction = self.reaction
        kinetics = reaction.kinetics
        rxn_str = ''
        if reaction.kinetics.comment:
            for line in reaction.kinetics.comment.split("\n"):
                rxn_str += "! {0}\n".format(line)
        rxn_str += '{0!s:51} {1:9.3e} {2:9.3f} {3:9.3f}\n'.format(
            reaction,
            kinetics.A.value_si * factor,
            kinetics.n.value_si,
            kinetics.Ea.value_si / 4184.,
        )

        with open(os.path.join(output_directory, 'chem.inp'), 'a') as f:
            f.write('{0}\n'.format(rxn_str))

    def save_yaml(self, output_directory):
        """
        Save a YAML file for TSs if structures of the respective reactant/s and product/s are known
        """
        if all([
                spc.molecule is not None and len(spc.molecule)
                for spc in self.reaction.reactants + self.reaction.products
        ]):
            self.arkane_species.update_species_attributes(
                self.reaction.transition_state)
            self.arkane_species.reaction_label = self.reaction.label
            self.arkane_species.reactants = [{
                'label':
                spc.label,
                'adjacency_list':
                spc.molecule[0].to_adjacency_list()
            } for spc in self.reaction.reactants]
            self.arkane_species.products = [{
                'label':
                spc.label,
                'adjacency_list':
                spc.molecule[0].to_adjacency_list()
            } for spc in self.reaction.products]
            self.arkane_species.save_yaml(path=output_directory)

    def plot(self, output_directory):
        """
        Plot both the raw kinetics data and the Arrhenius fit versus 
        temperature. The plot is saved to the file ``kinetics.pdf`` in the
        output directory. The plot is not generated if ``matplotlib`` is not
        installed.
        """
        import matplotlib.pyplot as plt

        f, ax = plt.subplots()
        if self.Tlist is not None:
            t_list = [t for t in self.Tlist.value_si]
        else:
            t_list = 1000.0 / np.arange(0.4, 3.35, 0.05)
        klist = np.zeros_like(t_list)
        klist2 = np.zeros_like(t_list)
        for i in range(len(t_list)):
            klist[i] = self.reaction.calculate_tst_rate_coefficient(t_list[i])
            klist2[i] = self.reaction.kinetics.get_rate_coefficient(t_list[i])

        order = len(self.reaction.reactants)
        klist *= 1e6**(order - 1)
        klist2 *= 1e6**(order - 1)
        t_list = [1000.0 / t for t in t_list]
        plt.semilogy(t_list, klist, 'ob', label='TST calculation')
        plt.semilogy(t_list, klist2, '-k', label='Fitted rate')
        plt.legend()
        reaction_str = '{0} {1} {2}'.format(
            ' + '.join([
                reactant.label for reactant in self.reaction.reactants
            ]), '<=>',
            ' + '.join([product.label for product in self.reaction.products]))
        plt.title(reaction_str)
        plt.xlabel('1000 / Temperature (K^-1)')
        plt.ylabel('Rate coefficient ({0})'.format(self.k_units))

        plot_path = os.path.join(output_directory, 'plots')

        if not os.path.exists(plot_path):
            os.mkdir(plot_path)
        valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits)
        filename = ''.join(c
                           for c in reaction_str if c in valid_chars) + '.pdf'
        plt.savefig(os.path.join(plot_path, filename))
        plt.close()

    def draw(self, output_directory, file_format='pdf'):
        """
        Generate a PDF drawing of the reaction.
        This requires that Cairo and its Python wrapper be available; if not,
        the drawing is not generated.

        You may also generate different formats of drawings, by changing format to
        one of the following: `pdf`, `svg`, `png`.
        """

        drawing_path = os.path.join(output_directory, 'paths')

        if not os.path.exists(drawing_path):
            os.mkdir(drawing_path)
        valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits)
        reaction_str = '{0} {1} {2}'.format(
            ' + '.join([
                reactant.label for reactant in self.reaction.reactants
            ]), '<=>',
            ' + '.join([product.label for product in self.reaction.products]))
        filename = ''.join(c
                           for c in reaction_str if c in valid_chars) + '.pdf'
        path = os.path.join(drawing_path, filename)

        KineticsDrawer().draw(self.reaction,
                              file_format=file_format,
                              path=path)
Ejemplo n.º 12
0
class ThermoJob(object):
    """
    A representation of an Arkane thermodynamics job. This job is used to
    compute and save the thermodynamics information for a single species.
    """
    def __init__(self, species, thermoClass):
        self.species = species
        self.thermoClass = thermoClass
        self.arkane_species = ArkaneSpecies(species=species)

    def execute(self, outputFile=None, plot=False):
        """
        Execute the thermodynamics job, saving the results to the
        given `outputFile` on disk.
        """
        self.generateThermo()
        if outputFile is not None:
            self.arkane_species.chemkin_thermo_string = self.save(outputFile)
            if self.species.molecule is None or len(
                    self.species.molecule) == 0:
                logging.debug(
                    "Not generating database YAML file for species {0}, since its structure wasn't"
                    " specified".format(self.species.label))
            else:
                self.arkane_species.update_species_attributes(self.species)
                self.arkane_species.save_yaml(path=os.path.dirname(outputFile))
            if plot:
                self.plot(os.path.dirname(outputFile))

    def generateThermo(self):
        """
        Generate the thermodynamic data for the species and fit it to the
        desired heat capacity model (as specified in the `thermoClass` 
        attribute).
        """
        if self.thermoClass.lower() not in ['wilhoit', 'nasa']:
            raise Exception('Unknown thermodynamic model "{0}".'.format(
                self.thermoClass))

        species = self.species

        logging.debug('Generating {0} thermo model for {1}...'.format(
            self.thermoClass, species))

        if species.thermo is not None:
            logging.info(
                "Thermo already generated for species {}. Skipping thermo generation."
                .format(species))
            return None

        Tlist = np.arange(10.0, 3001.0, 10.0, np.float64)
        Cplist = np.zeros_like(Tlist)
        H298 = 0.0
        S298 = 0.0
        conformer = self.species.conformer
        for i in range(Tlist.shape[0]):
            Cplist[i] += conformer.getHeatCapacity(Tlist[i])
        H298 += conformer.getEnthalpy(298.) + conformer.E0.value_si
        S298 += conformer.getEntropy(298.)

        if not any([
                isinstance(mode, (LinearRotor, NonlinearRotor))
                for mode in conformer.modes
        ]):
            # Monatomic species
            linear = False
            Nfreq = 0
            Nrotors = 0
            Cp0 = 2.5 * constants.R
            CpInf = 2.5 * constants.R
        else:
            # Polyatomic species
            linear = True if isinstance(conformer.modes[1],
                                        LinearRotor) else False
            Nfreq = len(conformer.modes[2].frequencies.value)
            Nrotors = len(conformer.modes[3:])
            Cp0 = (3.5 if linear else 4.0) * constants.R
            CpInf = Cp0 + (Nfreq + 0.5 * Nrotors) * constants.R

        wilhoit = Wilhoit()
        if Nfreq == 0 and Nrotors == 0:
            wilhoit.Cp0 = (Cplist[0], "J/(mol*K)")
            wilhoit.CpInf = (Cplist[0], "J/(mol*K)")
            wilhoit.B = (500., "K")
            wilhoit.H0 = (0.0, "J/mol")
            wilhoit.S0 = (0.0, "J/(mol*K)")
            wilhoit.H0 = (H298 - wilhoit.getEnthalpy(298.15), "J/mol")
            wilhoit.S0 = (S298 - wilhoit.getEntropy(298.15), "J/(mol*K)")
        else:
            wilhoit.fitToData(Tlist, Cplist, Cp0, CpInf, H298, S298, B0=500.0)

        if self.thermoClass.lower() == 'nasa':
            species.thermo = wilhoit.toNASA(Tmin=10.0, Tmax=3000.0, Tint=500.0)
        else:
            species.thermo = wilhoit

    def save(self, outputFile):
        """
        Save the results of the thermodynamics job to the file located
        at `path` on disk.
        """
        species = self.species
        logging.info('Saving thermo for {0}...'.format(species.label))

        f = open(outputFile, 'a')

        f.write('# Thermodynamics for {0}:\n'.format(species.label))
        H298 = species.getThermoData().getEnthalpy(298) / 4184.
        S298 = species.getThermoData().getEntropy(298) / 4.184
        f.write(
            '#   Enthalpy of formation (298 K)   = {0:9.3f} kcal/mol\n'.format(
                H298))
        f.write('#   Entropy of formation (298 K)    = {0:9.3f} cal/(mol*K)\n'.
                format(S298))
        f.write(
            '#    =========== =========== =========== =========== ===========\n'
        )
        f.write(
            '#    Temperature Heat cap.   Enthalpy    Entropy     Free energy\n'
        )
        f.write(
            '#    (K)         (cal/mol*K) (kcal/mol)  (cal/mol*K) (kcal/mol)\n'
        )
        f.write(
            '#    =========== =========== =========== =========== ===========\n'
        )
        for T in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]:
            try:
                Cp = species.getThermoData().getHeatCapacity(T) / 4.184
                H = species.getThermoData().getEnthalpy(T) / 4184.
                S = species.getThermoData().getEntropy(T) / 4.184
                G = species.getThermoData().getFreeEnergy(T) / 4184.
                f.write(
                    '#    {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n'.
                    format(T, Cp, H, S, G))
            except ValueError:
                logging.debug(
                    "Valid thermo for {0} is outside range for temperature {1}"
                    .format(species, T))
        f.write(
            '#    =========== =========== =========== =========== ===========\n'
        )

        thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format(
            species.label, species.getThermoData())
        f.write('{0}\n\n'.format(prettify(thermo_string)))

        f.close()
        # write chemkin file
        f = open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a')
        if isinstance(species, Species):
            if species.molecule and isinstance(species.molecule[0], Molecule):
                elementCounts = retrieveElementCount(species.molecule[0])
            else:
                try:
                    elementCounts = species.props['elementCounts']
                except KeyError:
                    elementCounts = {'C': 0, 'H': 0}
        else:
            elementCounts = {'C': 0, 'H': 0}
        chemkin_thermo_string = writeThermoEntry(species,
                                                 elementCounts=elementCounts,
                                                 verbose=True)
        f.write('{0}\n'.format(chemkin_thermo_string))
        f.close()

        # write species dictionary
        if isinstance(species, Species):
            if species.molecule and isinstance(species.molecule[0], Molecule):
                with open(
                        os.path.join(os.path.dirname(outputFile),
                                     'species_dictionary.txt'), 'a') as f:
                    f.write(species.molecule[0].toAdjacencyList(
                        removeH=False, label=species.label))
                    f.write('\n')
        return chemkin_thermo_string

    def plot(self, outputDirectory):
        """
        Plot the heat capacity, enthapy, entropy, and Gibbs free energy of the
        fitted thermodynamics model, along with the same values from the
        statistical mechanics model that the thermodynamics model was fitted 
        to. The plot is saved to the file ``thermo.pdf`` in the output
        directory. The plot is not generated if ``matplotlib`` is not installed.
        """
        # Skip this step if matplotlib is not installed
        try:
            import matplotlib.pyplot as plt
        except ImportError:
            return

        Tlist = np.arange(10.0, 2501.0, 10.0)
        Cplist = np.zeros_like(Tlist)
        Cplist1 = np.zeros_like(Tlist)
        Hlist = np.zeros_like(Tlist)
        Hlist1 = np.zeros_like(Tlist)
        Slist = np.zeros_like(Tlist)
        Slist1 = np.zeros_like(Tlist)
        Glist = np.zeros_like(Tlist)
        Glist1 = np.zeros_like(Tlist)

        conformer = self.species.conformer
        thermo = self.species.getThermoData()
        for i in range(Tlist.shape[0]):
            try:
                Cplist[i] = conformer.getHeatCapacity(Tlist[i])
                Slist[i] = conformer.getEntropy(Tlist[i])
                Hlist[i] = (conformer.getEnthalpy(Tlist[i]) +
                            conformer.E0.value_si) * 0.001
                Glist[i] = Hlist[i] - Tlist[i] * Slist[i] * 0.001
                Cplist1[i] = thermo.getHeatCapacity(Tlist[i])
                Slist1[i] = thermo.getEntropy(Tlist[i])
                Hlist1[i] = thermo.getEnthalpy(Tlist[i]) * 0.001
                Glist1[i] = thermo.getFreeEnergy(Tlist[i]) * 0.001
            except (ValueError, AttributeError):
                continue

        fig = plt.figure(figsize=(10, 8))
        fig.suptitle('{0}'.format(self.species.label))
        plt.subplot(2, 2, 1)
        plt.plot(Tlist, Cplist / 4.184, '-r', Tlist, Cplist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Heat capacity (cal/mol*K)')
        plt.legend(['statmech', 'fitted'], loc=4)

        plt.subplot(2, 2, 2)
        plt.plot(Tlist, Slist / 4.184, '-r', Tlist, Slist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Entropy (cal/mol*K)')

        plt.subplot(2, 2, 3)
        plt.plot(Tlist, Hlist / 4.184, '-r', Tlist, Hlist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Enthalpy (kcal/mol)')

        plt.subplot(2, 2, 4)
        plt.plot(Tlist, Glist / 4.184, '-r', Tlist, Glist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Gibbs free energy (kcal/mol)')

        fig.subplots_adjust(left=0.10,
                            bottom=0.08,
                            right=0.95,
                            top=0.95,
                            wspace=0.35,
                            hspace=0.20)

        plot_path = os.path.join(outputDirectory, 'plots')

        if not os.path.exists(plot_path):
            os.mkdir(plot_path)
        valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits)
        filename = ''.join(
            c for c in self.species.label if c in valid_chars) + '.pdf'
        plt.savefig(os.path.join(plot_path, filename))
        plt.close()
Ejemplo n.º 13
0
 def __init__(self, species, thermoClass):
     self.species = species
     self.thermoClass = thermoClass
     self.arkane_species = ArkaneSpecies(species=species)
Ejemplo n.º 14
0
class ThermoJob(object):
    """
    A representation of an Arkane thermodynamics job. This job is used to
    compute and save the thermodynamics information for a single species.
    """
    
    def __init__(self, species, thermoClass):
        self.species = species
        self.thermoClass = thermoClass
        self.arkane_species = ArkaneSpecies(species=species)
    
    def execute(self, outputFile=None, plot=False):
        """
        Execute the thermodynamics job, saving the results to the
        given `outputFile` on disk.
        """
        self.generateThermo()
        if outputFile is not None:
            self.arkane_species.chemkin_thermo_string = self.save(outputFile)
            if self.species.molecule is None or len(self.species.molecule) == 0:
                logging.debug("Not generating a YAML file for species {0}, since its structure wasn't"
                              " specified".format(self.species.label))
            else:
                # We're saving a YAML file for species iff Thermo is called and they're structure is known
                self.arkane_species.update_species_attributes(self.species)
                self.arkane_species.save_yaml(path=os.path.dirname(outputFile))
            if plot:
                self.plot(os.path.dirname(outputFile))
    
    def generateThermo(self):
        """
        Generate the thermodynamic data for the species and fit it to the
        desired heat capacity model (as specified in the `thermoClass` 
        attribute).
        """
        if self.thermoClass.lower() not in ['wilhoit', 'nasa']:
            raise Exception('Unknown thermodynamic model "{0}".'.format(self.thermoClass))
    
        species = self.species
    
        logging.debug('Generating {0} thermo model for {1}...'.format(self.thermoClass, species))
        
        if species.thermo is not None:
            logging.info("Thermo already generated for species {}. Skipping thermo generation.".format(species))
            return None
        
        Tlist = np.arange(10.0, 3001.0, 10.0, np.float64)
        Cplist = np.zeros_like(Tlist)
        H298 = 0.0
        S298 = 0.0
        conformer = self.species.conformer
        for i in range(Tlist.shape[0]):
            Cplist[i] += conformer.getHeatCapacity(Tlist[i])
        H298 += conformer.getEnthalpy(298.) + conformer.E0.value_si
        S298 += conformer.getEntropy(298.)
        
        if not any([isinstance(mode, (LinearRotor, NonlinearRotor)) for mode in conformer.modes]):
            # Monatomic species
            linear = False
            Nfreq = 0
            Nrotors = 0
            Cp0 = 2.5 * constants.R
            CpInf = 2.5 * constants.R
        else:
            # Polyatomic species
            linear = True if isinstance(conformer.modes[1], LinearRotor) else False
            Nfreq = len(conformer.modes[2].frequencies.value)
            Nrotors = len(conformer.modes[3:])
            Cp0 = (3.5 if linear else 4.0) * constants.R
            CpInf = Cp0 + (Nfreq + 0.5 * Nrotors) * constants.R
    
        wilhoit = Wilhoit()
        if Nfreq == 0 and Nrotors == 0:
            wilhoit.Cp0 = (Cplist[0],"J/(mol*K)") 
            wilhoit.CpInf = (Cplist[0],"J/(mol*K)")
            wilhoit.B = (500.,"K") 
            wilhoit.H0 = (0.0,"J/mol")
            wilhoit.S0 = (0.0,"J/(mol*K)") 
            wilhoit.H0 =  (H298 -wilhoit.getEnthalpy(298.15), "J/mol") 
            wilhoit.S0 = (S298 - wilhoit.getEntropy(298.15),"J/(mol*K)")
        else:
            wilhoit.fitToData(Tlist, Cplist, Cp0, CpInf, H298, S298, B0=500.0)
        
        if self.thermoClass.lower() == 'nasa':
            species.thermo = wilhoit.toNASA(Tmin=10.0, Tmax=3000.0, Tint=500.0)
        else:
            species.thermo = wilhoit

    def save(self, outputFile):
        """
        Save the results of the thermodynamics job to the file located
        at `path` on disk.
        """
        species = self.species
        logging.info('Saving thermo for {0}...'.format(species.label))
        
        f = open(outputFile, 'a')
    
        f.write('# Thermodynamics for {0}:\n'.format(species.label))
        H298 = species.getThermoData().getEnthalpy(298) / 4184.
        S298 = species.getThermoData().getEntropy(298) / 4.184
        f.write('#   Enthalpy of formation (298 K)   = {0:9.3f} kcal/mol\n'.format(H298))
        f.write('#   Entropy of formation (298 K)    = {0:9.3f} cal/(mol*K)\n'.format(S298))
        f.write('#    =========== =========== =========== =========== ===========\n')
        f.write('#    Temperature Heat cap.   Enthalpy    Entropy     Free energy\n')
        f.write('#    (K)         (cal/mol*K) (kcal/mol)  (cal/mol*K) (kcal/mol)\n')
        f.write('#    =========== =========== =========== =========== ===========\n')
        for T in [300,400,500,600,800,1000,1500,2000,2400]:
            try:
                Cp = species.getThermoData().getHeatCapacity(T) / 4.184
                H = species.getThermoData().getEnthalpy(T) / 4184.
                S = species.getThermoData().getEntropy(T) / 4.184
                G = species.getThermoData().getFreeEnergy(T) / 4184.
                f.write('#    {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n'.format(T, Cp, H, S, G))
            except ValueError:
                logging.debug("Valid thermo for {0} is outside range for temperature {1}".format(species,T))
        f.write('#    =========== =========== =========== =========== ===========\n')

        thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format(species.label, species.getThermoData())
        f.write('{0}\n\n'.format(prettify(thermo_string)))
        
        f.close()
        # write chemkin file
        f = open(os.path.join(os.path.dirname(outputFile), 'chem.inp'), 'a')
        if isinstance(species, Species):
            if species.molecule and isinstance(species.molecule[0], Molecule):
                elementCounts = retrieveElementCount(species.molecule[0])
            else:
                try:
                    elementCounts = species.props['elementCounts']
                except KeyError:
                    elementCounts = {'C': 0, 'H': 0}
        else:
            elementCounts = {'C': 0, 'H': 0}
        chemkin_thermo_string = writeThermoEntry(species, elementCounts=elementCounts, verbose=True)
        f.write('{0}\n'.format(chemkin_thermo_string))
        f.close()

        # write species dictionary
        if isinstance(species, Species):
            if species.molecule and isinstance(species.molecule[0], Molecule):
                with open(os.path.join(os.path.dirname(outputFile), 'species_dictionary.txt'), 'a') as f:
                    f.write(species.molecule[0].toAdjacencyList(removeH=False, label=species.label))
                    f.write('\n')
        return chemkin_thermo_string

    def plot(self, outputDirectory):
        """
        Plot the heat capacity, enthapy, entropy, and Gibbs free energy of the
        fitted thermodynamics model, along with the same values from the
        statistical mechanics model that the thermodynamics model was fitted 
        to. The plot is saved to the file ``thermo.pdf`` in the output
        directory. The plot is not generated if ``matplotlib`` is not installed.
        """
        # Skip this step if matplotlib is not installed
        try:
            import matplotlib.pyplot as plt
        except ImportError:
            return
        
        Tlist = np.arange(10.0, 2501.0, 10.0)
        Cplist = np.zeros_like(Tlist)
        Cplist1 = np.zeros_like(Tlist)
        Hlist = np.zeros_like(Tlist)
        Hlist1 = np.zeros_like(Tlist)
        Slist = np.zeros_like(Tlist)
        Slist1 = np.zeros_like(Tlist)
        Glist = np.zeros_like(Tlist)
        Glist1 = np.zeros_like(Tlist)
        
        conformer = self.species.conformer
        thermo = self.species.getThermoData()
        for i in range(Tlist.shape[0]):
            try:
                Cplist[i] = conformer.getHeatCapacity(Tlist[i])
                Slist[i] = conformer.getEntropy(Tlist[i])
                Hlist[i] = (conformer.getEnthalpy(Tlist[i]) + conformer.E0.value_si) * 0.001
                Glist[i] = Hlist[i] - Tlist[i] * Slist[i] * 0.001
                Cplist1[i] = thermo.getHeatCapacity(Tlist[i])
                Slist1[i] = thermo.getEntropy(Tlist[i])
                Hlist1[i] = thermo.getEnthalpy(Tlist[i]) * 0.001
                Glist1[i] = thermo.getFreeEnergy(Tlist[i]) * 0.001
            except (ValueError,AttributeError):
                continue

        fig = plt.figure(figsize=(10,8))
        fig.suptitle('{0}'.format(self.species.label))
        plt.subplot(2,2,1)
        plt.plot(Tlist, Cplist / 4.184, '-r', Tlist, Cplist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Heat capacity (cal/mol*K)')
        plt.legend(['statmech', 'fitted'], loc=4)

        plt.subplot(2,2,2)
        plt.plot(Tlist, Slist / 4.184, '-r', Tlist, Slist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Entropy (cal/mol*K)')

        plt.subplot(2,2,3)
        plt.plot(Tlist, Hlist / 4.184, '-r', Tlist, Hlist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Enthalpy (kcal/mol)')

        plt.subplot(2,2,4)
        plt.plot(Tlist, Glist / 4.184, '-r', Tlist, Glist1 / 4.184, '-b')
        plt.xlabel('Temperature (K)')
        plt.ylabel('Gibbs free energy (kcal/mol)')

        fig.subplots_adjust(left=0.10, bottom=0.08, right=0.95, top=0.95, wspace=0.35, hspace=0.20)

        plot_path = os.path.join(outputDirectory, 'plots')

        if not os.path.exists(plot_path):
            os.mkdir(plot_path)
        valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits)
        filename = ''.join(c for c in self.species.label if c in valid_chars) + '.pdf'
        plt.savefig(os.path.join(plot_path, filename))
        plt.close()