#!/usr/bin/env python # coding: utf-8 # # NAM 2019 pMuTT Workshop # # Instructions and materials for the "Theory, Applications, and Tools for Kinetic Modeling" workshop can be found on [our documentation page](https://vlachosgroup.github.io/pMuTT/nam_2019.html). # # # Table of Contents # # | **1\. [Virtual Kinetic Laboratory Ecosystem](#section_1)** # # | **2\. [Useful Links](#section_2)** # # | **3\. [Constants](#section_3)** # # |-- **3.1. [Access common constants in appropriate units](#section_3_1)** # # |-- **3.2. [Convert between units](#section_3_2)** # # |-- **3.3. [Convert between equivalent quantities](#section_3_3)** # # | **4\. [Exercise 1](#section_4)** # # | **5\. [Creating statistical mechanical objects using StatMech](#section_5)** # # |-- **5.1. [Supported StatMech models](#section_5_1)** # # |--|-- **5.1.1 [Translations](#section_5_1_1)** # # |--|-- **5.1.2. [Vibrations](#section_5_1_2)** # # |--|-- **5.1.3. [Rotations](#section_5_1_3)** # # |--|-- **5.1.4. [Electronic](#section_5_1_4)** # # |--|-- **5.1.5. [Miscellaneous](#section_5_1_5)** # # |-- **5.2. [Initializing StatMech modes individually](#section_5_2)** # # |-- **5.3. [Initializing StatMech modes using presets](#section_5_3)** # # | **6\. [Plot Thermodynamic Quantities](#section_6)** # # | **7\. [Exercise 2](#section_7)** # # | **8\. [Creating empirical objects](#section_8)** # # |-- **8.1. [Inputting a NASA polynomial directly](#section_8_1)** # # |-- **8.2. [Fitting an empirical object to a StatMech object](#section_8_2)** # # | **9\. [Input/Output](#section_9)** # # |-- **9.1. [Input via Excel](#section_9_1)** # # |-- **9.2. [Output via Thermdat](#section_9_2)** # # | **10\. [Reactions](#section_10)** # # | **11\. [Exercise 3](#section_11)** # # | **12\. [Solutions](#section_12)** # # |-- **12.1. [Solution 1](#section_12_1)** # # |-- **12.2. [Solution 2](#section_12_2)** # # |-- **12.3. [Solution 3](#section_12_3)** # # # 1. Virtual Kinetic Laboratory Ecosystem # # # # # # # - Estimates thermochemical and kinetic parameters using statistical mechanics, transition state theory # - Writes input files for kinetic models and eases thermodynamic analysis # - Implemented in Python # - Easy to learn # - Heavily used in scientific community # - Object-oriented approach is a natural analogy to chemical phenomenon # - Library approach allows users to define the starting point and end point # # # # # # 2. Useful Links # # - [Documentation](https://vlachosgroup.github.io/pMuTT/): find the most updated documentation # - [Issues](https://github.com/VlachosGroup/pmutt/issues): report bugs, request features, receive help # - [Examples](https://vlachosgroup.github.io/pMuTT/examples.html): see examples # # # 3. Constants # # The [constants module](https://vlachosgroup.github.io/pMuTT/constants.html) has a wide variety of functions for constants and unit conversion. # # ## 3.1. Access common constants in appropriate units # Below, we access Planck's constant in J s. # In[1]: from pmutt import constants as c h1 = c.h('eV s', bar=True) print('h = {} eV s'.format(h1)) # # ## 3.2. Convert between units # Below, we convert 12 atm of pressure to psi. # In[2]: from pmutt import constants as c P_atm = 12. # atm P_psi = c.convert_unit(num=P_atm, initial='atm', final='psi') print('{} atm = {} psi'.format(P_atm, P_psi)) # # ## 3.3. Convert between equivalent quantities # Below, we convert 1000 wavenumbers (cm-1) to frequency. # In[3]: from pmutt import constants as c wave_num = 1000. # cm-1 freq = c.wavenumber_to_freq(wave_num) # Hz print('{} cm-1 = {} Hz'.format(wave_num, freq)) # # # 4. Exercise 1 # # Using `pmutt.constants`, calculate the dimensionless enthalpy (H/RT) using the following information: # - H = 0.5 eV # - T = 77 F # In[4]: # Fill in your answer for Exercise 1 here # # # 5. Creating statistical mechanical objects using StatMech # # Molecules show translational, vibrational, rotational, electronic, and nuclear modes. # # # # ## 5.1. Supported StatMech modes # # # # The StatMech object allows us to specify translational, vibrational, rotational, electronic and nuclear modes independently, which gives flexibility in what behavior you would like. Below are the available modes. # # ### 5.1.1. Translations # - [``FreeTrans``](https://vlachosgroup.github.io/pMuTT/statmech.html#freetrans) - Translations assuming no intermolecular interactions # # ### 5.1.2. Vibrations # - [``HarmonicVib``](https://vlachosgroup.github.io/pMuTT/statmech.html#harmonicvib) - Harmonic vibrations # - [``QRRHOVib``](https://vlachosgroup.github.io/pMuTT/statmech.html#harmonicvib) - Quasi rigid rotor harmonic oscillator. Low frequency modes are treated as rigid rotations. # - [``EinsteinVib``](https://vlachosgroup.github.io/pMuTT/statmech.html#einsteinvib) - Each atom in the crystal vibrates as independent 3D harmonic oscillators # - [``DebyeVib``](https://vlachosgroup.github.io/pMuTT/statmech.html#debyevib) - Improves upon ``EinsteinVib`` by considering simultaneous vibrations. Improves accuracy at lower temperatures. # # ### 5.1.3. Rotations # - [``RigidRotor``](https://vlachosgroup.github.io/pMuTT/statmech.html#rigidrotor) - Molecule can be rotated with no change in bond properties # # ### 5.1.4. Electronic # - [``GroundStateElec``](https://vlachosgroup.github.io/pMuTT/statmech.html#groundstateelec) - Electronic ground state of the system # - [``LSR``](https://vlachosgroup.github.io/pMuTT/statmech.html#linear-scaling-relationships-lsrs) - Linear Scaling Relationship to estimate binding energies using reference adsorbate # # ### 5.1.5. Miscellaneous # - [``EmptyMode``](https://vlachosgroup.github.io/pMuTT/statmech.html#empty-mode) - Default mode if not specified. Does not contribute to any properties # - [``ConstantMode``](https://vlachosgroup.github.io/pMuTT/statmech.html#constant-mode) - Specify arbitrary values to thermodynamic quantities # # Using a ``StatMech`` mode gives you access to all the common thermodynamic properties. # # # # For this example, we will use a hydrogen molecule as an ideal gas: # - translations with no interaction between molecules # - harmonic vibrations # - rigid rotor rotations # - ground state electronic structure # - no contribution from nuclear modes. # # # # ## 5.2. Initializing StatMech modes individually # In[5]: from ase.build import molecule from pmutt.statmech import StatMech, trans, vib, rot, elec H2_atoms = molecule('H2') '''Translational''' H2_trans = trans.FreeTrans(n_degrees=3, atoms=H2_atoms) '''Vibrational''' H2_vib = vib.HarmonicVib(vib_wavenumbers=[4342.]) # vib_wavenumbers in cm-1 '''Rotational''' H2_rot = rot.RigidRotor(symmetrynumber=2, atoms=H2_atoms) '''Electronic''' H2_elec = elec.GroundStateElec(potentialenergy=-6.77,spin=0) # potentialenergy in eV '''StatMech Initialization''' H2_statmech = StatMech(name='H2', trans_model=H2_trans, vib_model=H2_vib, rot_model=H2_rot, elec_model=H2_elec) '''Calculate thermodynamic properties''' H_statmech = H2_statmech.get_H(T=298., units='kJ/mol') S_statmech = H2_statmech.get_S(T=298., units='J/mol/K') print('H_H2(T=298 K) = {:.1f} kJ/mol'.format(H_statmech)) print('S_H2(T=298 K) = {:.2f} J/mol/K'.format(S_statmech)) # # ## 5.3. Initializing StatMech modes using presets # # Commonly used models can be accessed via [``presets``](https://vlachosgroup.github.io/pMuTT/statmech.html#presets). The currently supported models are: # # - [``idealgas``](https://vlachosgroup.github.io/pMuTT/statmech.html#ideal-gas-idealgas) - Ideal gases # - [``harmonic``](https://vlachosgroup.github.io/pMuTT/statmech.html#harmonic-approximation-harmonic) - Typical for surface species # - [``electronic``](https://vlachosgroup.github.io/pMuTT/statmech.html#electronic-electronic) - Only has electronic modes # - [``placeholder``](https://vlachosgroup.github.io/pMuTT/statmech.html#placeholder-placeholder) - No contribution to any property # - [``constant``](https://vlachosgroup.github.io/pMuTT/statmech.html#constant-constant) - Use arbitrary constants to thermodynamic properties # # In[6]: from ase.build import molecule from pmutt.statmech import StatMech, presets H2_statmech = StatMech(atoms=molecule('H2'), vib_wavenumbers=[4342.], # cm-1 symmetrynumber=2, potentialenergy=-6.77, # eV spin=0., **presets['idealgas']) '''Calculate thermodynamic properties''' H_statmech = H2_statmech.get_H(T=298., units='kJ/mol') S_statmech = H2_statmech.get_S(T=298., units='J/mol/K') print('H_H2(T=298 K) = {:.1f} kJ/mol'.format(H_statmech)) print('S_H2(T=298 K) = {:.2f} J/mol/K'.format(S_statmech)) # # # 6. Plot Thermodynamic Quantities # Use [`pmutt.plot_1D`](https://vlachosgroup.github.io/pMuTT/visual.html#plot-1d) and [`pmutt.plot_2D`](https://vlachosgroup.github.io/pMuTT/visual.html#plot-2d) to plot any function with respect to 1 or 2 variables. # In[7]: import numpy as np from matplotlib import pyplot as plt from pmutt import plot_1D, plot_2D T = np.linspace(300., 500.) f1, ax1 = plot_1D(H2_statmech, x_name='T', x_values=T, methods=('get_H', 'get_S', 'get_G'), get_H_kwargs={'units': 'kcal/mol'}, get_S_kwargs={'units': 'cal/mol/K'}, get_G_kwargs={'units': 'kcal/mol'}) f1.set_size_inches(6, 6) f1.set_dpi(200) plt.show() # # # 7. Exercise 2 # # 1. Create a ``StatMech`` object for ideal gas-phase H2O. The necessary inputs are given below. # # | Parameter | Value | # |--------------------------------|------------------------------| # | atoms | `molecule('H2O')` | # | Potential Energy (eV) | -6.7598 | # | Symmetry number | 2 | # | Spin | 0 | # | Vibrational Wavenumbers (cm-1) | 3825.434, 3710.264, 1582.432 | # # 2. Calculate the Gibbs energy in eV for H2O at T = 500 K and P = 2 bar. # # 3. Create a ``StatMech`` object for a Cu crystal using the ``DebyeVib`` model for the vibration mode and ``GroundStateElec`` model for electronic mode. The necessary inputs are given below. # # | Parameter | Value | # |------------------------|------------| # | Debye Temperature (K) | 310 | # | Interaction energy (eV)| 0 | # | Potential energy (eV) | -14.922356 | # # 4. Plot the H (in eV) and S (in eV/K) for Cu between T = 300 - 700 K. # In[8]: # Fill in your answer for Exercise 2 here # 1. # 2. # 3. # 4. # # # 8. Creating empirical objects # Currently, pMuTT supports [NASA polynomials](https://vlachosgroup.github.io/pMuTT/empirical.html#nasa) and [Shomate polynomials](https://vlachosgroup.github.io/pMuTT/empirical.html#shomate). They can be initialized in three ways: # - passing in the polynomials directly # - from a model (e.g. ``StatMech``, ``Shomate``) (``from_model``) # - from heat capacity, enthalpy and entropy data (``from_data``) # # # # ## 8.1. Inputting a NASA polynomial directly # # The H2 NASA polynomial from the [Burcat database](http://combustion.berkeley.edu/gri_mech/version30/files30/thermo30.dat) is represented as: # # ``` # H2 TPIS78H 2 G 200.000 3500.000 1000.000 1 # 3.33727920E+00-4.94024731E-05 4.99456778E-07-1.79566394E-10 2.00255376E-14 2 # -9.50158922E+02-3.20502331E+00 2.34433112E+00 7.98052075E-03-1.94781510E-05 3 # 2.01572094E-08-7.37611761E-12-9.17935173E+02 6.83010238E-01 4 # ``` # # This can be translated to pMuTT syntax using: # In[9]: from pmutt.empirical.nasa import Nasa # Initialize NASA polynomial H2_nasa = Nasa(name='H2', elements={'H': 2}, phase='G', T_low=200., T_mid=1000., T_high=3500., a_low=[2.34433112E+00, 7.98052075E-03, -1.94781510E-05, 2.01572094E-08, -7.37611761E-12, -9.17935173E+02, 6.83010238E-01], a_high=[3.33727920E+00, -4.94024731E-05, 4.99456778E-07, -1.79566394E-10, 2.00255376E-14, -9.50158922E+02, -3.20502331E+00]) # Calculate thermodynamic quantities using the same syntax as StatMech H_H2 = H2_nasa.get_H(units='kcal/mol', T=298.) print('H_H2(T=298 K) = {} kcal/mol'.format(H_H2)) # Show thermodynamic quantities vs. T T = np.linspace(200., 3500.) f2, ax2 = plot_1D(H2_nasa, x_name='T', x_values=T, methods=('get_H', 'get_S', 'get_G'), get_H_kwargs={'units': 'kcal/mol'}, get_S_kwargs={'units': 'cal/mol/K'}, get_G_kwargs={'units': 'kcal/mol'}) f2.set_size_inches(6, 6) f2.set_dpi(200) plt.show() # # ## 8.2. Fitting an empirical object to a StatMech object # Empirical objects can be made directly using ``StatMech`` objects and the ``from_model`` method. # In[10]: H2_nasa = Nasa.from_model(name='H2', T_low=200., T_high=3500., model=H2_statmech) # Compare the statistical mechanical model to the empirical model f3, ax3 = H2_nasa.plot_statmech_and_empirical(Cp_units='J/mol/K', H_units='kJ/mol', S_units='J/mol/K', G_units='kJ/mol') f3.set_size_inches(6, 8) f3.set_dpi(200) plt.show() # # # 9. Input/Output # pMuTT has more IO functionality than below. See this page for [supported IO functions](https://vlachosgroup.github.io/pMuTT/io.html). # # ## 9.1. Input via Excel # # Encoding each object in Python can be tedious. You can read several species from Excel spreadsheets using [``pmutt.io.excel.read_excel``](https://vlachosgroup.github.io/pmutt/io.html?highlight=read_excel#pmutt.io.excel.read_excel). Note that this function returns a list of dictionaries. This output allows you to initialize whichever object you want using kwargs syntax. There are also [special rules that depend on the header name](https://vlachosgroup.github.io/pMuTT/io.html#special-rules). # # Below, we show an example importing species data from a spreadsheet and creating a series of NASA polynomials. # In[11]: import os from pprint import pprint from pathlib import Path from pmutt.io.excel import read_excel from pmutt.empirical.nasa import Nasa # Find the location of Jupyter notebook # Note that normally Python scripts have a __file__ variable but Jupyter notebook doesn't. # Using pathlib can overcome this limiation try: notebook_folder = os.path.dirname(__file__) except NameError: notebook_folder = Path().resolve() os.chdir(notebook_folder) # Read the data from Excel ab_initio_data = read_excel(io='./input/NH3_Input_Data.xlsx', sheet_name='species') pprint(ab_initio_data) # In[12]: # Create NASA polynomials using **kwargs syntax nasa_species = [] for species_data in ab_initio_data: single_nasa_species = Nasa.from_model(T_low=100., T_high=1500., **species_data) nasa_species.append(single_nasa_species) # Print out a table using enthalpy, entropy, Gibbs energy at 298 K for each species print('Name Enthalpy (kcal/mol) Entropy (cal/mol/K) Gibbs energy (kcal/mol)') print('--------------------------------------------------------------------------------') for single_nasa_species in nasa_species: name = single_nasa_species.name H = single_nasa_species.get_H(units='kcal/mol', T=298.) S = single_nasa_species.get_S(units='cal/mol/K', T=298.) G = single_nasa_species.get_G(units='kcal/mol', T=298.) print('{:12} {:10.1f} {:15.1f} {:15.1f}'.format(name, H, S, G)) # # ## 9.2. Output via Thermdat # The thermdat format uses NASA polynomials to represent several species. It has a very particular format so doing it manually is error-prone. You can write a list of ``Nasa`` objects to thermdat format using [``pmutt.io.thermdat.write_thermdat``](https://vlachosgroup.github.io/pmutt/io.html#pmutt.io.thermdat.write_thermdat). # # Below, we write a thermdat file using the species imported from the spreadsheet. # In[13]: from pmutt.io.thermdat import write_thermdat write_thermdat(filename='./output/thermdat', nasa_species=nasa_species) # Similarly, a list of ``Nasa`` objects can be read from a thermdat using [``pmutt.io.thermdat.read_thermdat``](https://vlachosgroup.github.io/pMuTT/io.html#pmutt.io.thermdat.read_thermdat). # In[14]: from pmutt.io.thermdat import read_thermdat nasa_species = read_thermdat('./output/thermdat') # # # 10. Reactions # # # # ``Reaction`` objects can be created by putting together ``Nasa``, ``Shomate`` and ``StatMech`` objects. # # # # # The ``from_string`` method is the easiest way to create a ``Reaction`` object. It requires the relevant species to be in a dictionary and a string to describe the reaction. # # # # We will demonstrate its use for the formation of NH3. # In[15]: from pmutt.empirical.nasa import Nasa from pmutt.empirical.shomate import Shomate from pmutt.reaction import Reaction # Create species. Note that you can mix different types of species species = { 'H2': StatMech(name='H2', atoms=molecule('H2'), vib_wavenumbers=[4342.], # cm-1 symmetrynumber=2, potentialenergy=-6.77, # eV spin=0., **presets['idealgas']), 'N2': Nasa(name='N2', T_low=300., T_mid=643., T_high=1000., a_low=[3.3956319945669633, 0.001115707689025668, -4.301993779374381e-06, 6.8071424019295535e-09, -3.2903312791047058e-12, -191001.55648623788, 3.556111439828502], a_high=[4.050329990684662, -0.0029677854067980108, 5.323485005316287e-06, -3.3518122405333548e-09, 7.58446718337381e-13, -191086.2004520406, 0.6858235504924011]), 'NH3': Shomate(name='NH3', T_low=300., T_high=1000., a=[18.792357134351683, 44.82725349479501, -10.05898449447048, 0.3711633831565547, 0.2969942466370908, -1791.225746924463, 203.9035662274934, 1784.714638346206]), } # Define the formation of water reaction rxn = Reaction.from_string('1.5H2 + 0.5N2 = NH3', species) # Calculate forward change in enthalpy H_rxn_fwd = rxn.get_delta_H(units='kcal/mol', T=300.) print('Delta H_fwd(T = 300 K) = {:.1f} kcal/mol'.format(H_rxn_fwd)) # Calculate reverse change in enthalpy H_rxn_rev = rxn.get_delta_H(units='kcal/mol', T=300., rev=True) print('Delta H_rev(T = 300 K) = {:.1f} kcal/mol'.format(H_rxn_rev)) # Calculate enthalpy of reactants H_react = rxn.get_H_state(units='kcal/mol', T=300., state='reactants') print('H_reactants(T = 300 K) = {:.1f} kcal/mol'.format(H_react)) # # ## 11. Exercise 3 # # 1. Use [``pmutt.io.thermdat.read_thermdat``](https://vlachosgroup.github.io/pMuTT/io.html#pmutt.io.thermdat.read_thermdat) to read the thermdat from './output/thermdat'. # # 2. Convert the list of ``Nasa`` to a dictionary of ``Nasa`` using ``pmutt.pmutt_list_to_dict``. The syntax to use the function is shown below. # # ``` # from pmutt import pmutt_list_to_dict # # species_dict = pmutt_list_to_dict(species_list) # ``` # # 3. Create a ``Reaction`` from the string: ``NH3(S) + RU(S) = TS1_NH3(S) = NH2(S) + H(S)`` and the dictionary from step 2. # # 4. Calculate the forward reaction enthalpy in kcal/mol at 298 K using [``Reaction.get_delta_H``](https://vlachosgroup.github.io/pMuTT/reactions.html#pmutt.reaction.Reaction.get_E_act). # # 5. Calculate the forward activation energy in kcal/mol at 298 K using [``Reaction.get_E_act``](https://vlachosgroup.github.io/pMuTT/reactions.html#pmutt.reaction.Reaction.get_E_act). # In[16]: # Fill in your answer for Exercise 3 # 1. # 2. # 3. # 4. # 5. # # # 12. Solutions # # ## 12.1. Solution to Exercise 1 # [Link to Exercise 1](#section_4) # In[17]: from pmutt import constants as c # Define information given H = 0.5 # eV T = 77 # F # Calculate H/RT HoRT = H/c.R('eV/K')/c.convert_unit(77, initial='F', final='K') print('H/RT = {}'.format(HoRT)) # # ## 12.2. Solution to Exercise 2 # [Link to Exercise 2](#section_7) # In[18]: from ase.build import molecule from pmutt import plot_1D from pmutt.statmech import StatMech, presets from pmutt.statmech.vib import DebyeVib from pmutt.statmech.elec import GroundStateElec # 1. Create H2O molecule H2O_statmech = StatMech(atoms=molecule('H2O'), potentialenergy=-6.7598, symmetrynumber=2, spin=0, vib_wavenumbers=[3825.434, 3710.264, 1582.432], **presets['idealgas']) # 2. Calculate Gibbs energy of H2O at T = 500 K and P = 2 bar T = 500. # K P = 2. # bar G_H2O = H2O_statmech.get_G(units='eV', T=T, P=P) print('G_H2O(T = 500 K, P = 2 bar) = {} eV'.format(G_H2O)) # 3. Create Cu crystal Cu_vib = DebyeVib(debye_temperature=310., interaction_energy=0.) Cu_elec = GroundStateElec(potentialenergy=-14.922356) Cu_statmech = StatMech(vib_model=Cu_vib, elec_model=Cu_elec) # 4. Plot the 1D profile for H and S between 300 K and 700 K T = np.linspace(300., 700.) # K f2, ax2 = plot_1D(Cu_statmech, x_name='T', x_values=T, methods=('get_H', 'get_S'), get_H_kwargs={'units': 'eV'}, get_S_kwargs={'units': 'eV/K'}) f2.set_size_inches(6, 6) f2.set_dpi(200) plt.show() # # ## 12.3. Solution to Exercise 3 # [Link to Exercise 3](#section_11) # In[19]: from pmutt.io.thermdat import read_thermdat from pmutt import pmutt_list_to_dict from pmutt.reaction import Reaction # 1. Read the thermdat file species_list = read_thermdat('./output/thermdat') # 2. Convert the list of Nasa to a dictionary of Nasa species_dict = pmutt_list_to_dict(species_list) # 3. Create a Reaction from the specified string rxn = Reaction.from_string('NH3(S) + RU(S) = TS1_NH3(S) = NH2(S) + H(S)', species_dict) # 4. Calculate the reaction enthalpy H_rxn = rxn.get_delta_H(units='kcal/mol', T=298.) print('H_rxn = {:.1f} kcal/mol'.format(H_rxn)) # 5. Calculate the forward activation energy Ea = rxn.get_E_act(units='kcal/mol', T=298.) print('Ea_fwd = {:.1f} kcal/mol'.format(Ea))