#!/usr/bin/env python
# coding: utf-8
# # AIChE 2019 pMuTT Workshop
#
# Instructions and materials for the Computational Catalysis workshop can be found on webpage.
#
# # Table of Contents
#
# | **1\. [Introduction](#section_1)**
#
# |-- **1.1. [Some of pMuTT's Capabilities](#section_1_1)**
#
# | **2\. [Useful Links](#section_2)**
#
# | **3\. [Creating statistical mechanical objects using StatMech](#section_3)**
#
# |-- **3.1. [Supported StatMech models](#section_3_1)**
#
# |--|-- **3.1.1. [Translations](#section_3_1_1)**
#
# |--|-- **3.1.2. [Vibrations](#section_3_1_2)**
#
# |--|-- **3.1.3. [Rotations](#section_3_1_3)**
#
# |--|-- **3.1.4. [Electronic](#section_3_1_4)**
#
# |--|-- **3.1.5. [Miscellaneous](#section_3_1_5)**
#
# |-- **3.2. [Initializing StatMech modes individually](#section_3_2)**
#
# |-- **3.3. [Initializing StatMech modes using presets](#section_3_3)**
#
# | **4\. [Creating empirical objects](#section_4)**
#
# |-- **4.1. [Inputting a NASA polynomial directly](#section_4_1)**
#
# |-- **4.2. [Fitting an empirical object to a StatMech object](#section_4_2)**
#
# | **5\. [Input/Output](#section_5)**
#
# |-- **5.1. [Input via Excel](#section_5_1)**
#
# | **6\. [Reactions](#section_6)**
#
# |-- **6.1. [Initializing Reaction objects using from_string](#section_6_1)**
#
# # 1. Introduction
#
#
#
# - 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
#
#
#
# ## 1.1 Some of pMuTT's Capabilities
# ### Reaction Coordinate Diagrams
#
# See the thermodynamic and kinetic feasibility of reaction mechanisms.
#
#
#
# ### Ab-Initio Phase Diagrams
#
# Predict the most stable configuration with respect to temperature and pressure.
#
# **Configurations**
#
# Typically we would consider more configurations than this.
#
# **1D Phase Diagram**
#
#
# **2D Phase Diagram**
#
#
# # 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. Creating statistical mechanical objects using StatMech
#
# Molecules show translational, vibrational, rotational, electronic, and nuclear modes.
#
#
#
# ## 3.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.
#
# ### 3.1.1. Translations
# - [``FreeTrans``](https://vlachosgroup.github.io/pMuTT/statmech.html#freetrans) - Translations assuming no intermolecular interactions. Can be adjusted for 1, 2, or 3 degrees of translation.
#
# ### 3.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.
#
# ### 3.1.3. Rotations
# - [``RigidRotor``](https://vlachosgroup.github.io/pMuTT/statmech.html#rigidrotor) - Molecule can be rotated with no change in bond properties
#
# ### 3.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
#
# ### 3.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.
#
#
#
# ## 3.2. Initializing StatMech modes individually
# First, we will create an ASE Atoms object of H2. This will make it easier to initialize translations and rotations.
# In[1]:
from ase.build import molecule
from ase.visualize import view
H2_atoms = molecule('H2')
view(H2_atoms)
# Now we will initialize each mode separately
# In[2]:
from pmutt.statmech import StatMech, trans, vib, rot, elec
'''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 per mole basis'''
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))
# If you specify the composition of your species, you can calculate per mass quantities too.
# In[3]:
'''Input composition'''
H2_statmech.elements = {'H': 2}
'''Calculate thermodynamic properties per mass basis'''
H_statmech = H2_statmech.get_H(T=298., units='kJ/g')
S_statmech = H2_statmech.get_S(T=298., units='J/g/K')
print('H_H2(T=298 K) = {:.1f} kJ/g'.format(H_statmech))
print('S_H2(T=298 K) = {:.2f} J/g/K'.format(S_statmech))
#
# ## 3.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[4]:
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))
#
# # 4. Creating empirical objects
# Currently, pMuTT supports
#
# - [NASA polynomials](https://vlachosgroup.github.io/pMuTT/empirical.html#nasa)
# - [NASA9 polynomials](https://vlachosgroup.github.io/pMuTT/empirical.html#nasa9)
# - [Shomate polynomials](https://vlachosgroup.github.io/pMuTT/empirical.html#shomate).
#
# They can be initialized in three ways:
# - inputting the polynomials directly
# - from another model (e.g. ``StatMech``, ``Shomate``) using (``from_model``)
# - from heat capacity, enthalpy and entropy data using (``from_data``)
#
#
#
# ## 4.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[5]:
import numpy as np
from matplotlib import pyplot as plt
from pmutt import plot_1D
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'})
# Modifying figure
ax2[0].set_ylabel('H (kcal/mol)')
ax2[1].set_ylabel('S (cal/mol/K)')
ax2[2].set_ylabel('G (kcal/mol)')
ax2[2].set_xlabel('T (K)')
f2.set_size_inches(5, 5)
f2.set_dpi(200)
plt.show()
#
# ## 4.2. Fitting an empirical object to a StatMech object
# Empirical objects can be made directly any species objects using the ``from_model`` method.
# In[6]:
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(150)
plt.show()
# In[7]:
from pmutt.empirical.shomate import Shomate
H2_shomate = Shomate.from_model(model=H2_nasa)
# Compare the statistical mechanical model to the empirical model
f3, ax3 = H2_shomate.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(150)
plt.show()
# The ``Shomate`` is a simpler polynomial than the ``Nasa`` polynomial so it does not capture the features as well for the large T range. It is always a good idea to check your fit.
#
# # 5. Input/Output
# pMuTT has more IO functionality than below. See this page for [supported IO functions](https://vlachosgroup.github.io/pMuTT/io.html).
#
# ## 5.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.
# First, we ensure that the current working directory is the same as the notebook so we can access the spreadsheet.
# In[8]:
import os
from pathlib import Path
# 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_path = os.path.dirname(__file__)
except NameError:
notebook_path = Path().resolve()
os.chdir(notebook_path)
# Now we can read from the spreadsheet.
# In[9]:
from pprint import pprint
from pmutt.io.excel import read_excel
ab_initio_data = read_excel(io='./input/NH3_Input_Data.xlsx', sheet_name='species')
pprint(ab_initio_data)
# After the data is read, we can fit the ``Nasa`` objects from the statistical mechanical data.
# In[10]:
from pmutt.empirical.nasa import Nasa
# 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)
# Just to ensure the species were read correctly, we can try printing out thermodynamic values.
# In[11]:
import pandas as pd
thermo_data = {'Name': [],
'H (kcal/mol)': [],
'S (cal/mol/K)': [],
'G (kcal/mol)': []}
'''Calculate properties at 298 K'''
T = 298. # K
for single_nasa_species in nasa_species:
thermo_data['Name'].append(single_nasa_species.name)
thermo_data['H (kcal/mol)'].append(single_nasa_species.get_H(units='kcal/mol', T=T))
thermo_data['S (cal/mol/K)'].append(single_nasa_species.get_S(units='cal/mol/K', T=T))
thermo_data['G (kcal/mol)'].append(single_nasa_species.get_G(units='kcal/mol', T=T))
'''Create Pandas Dataframe for easy printing'''
columns = ['Name', 'H (kcal/mol)', 'S (cal/mol/K)', 'G (kcal/mol)']
thermo_data = pd.DataFrame(thermo_data, columns=columns)
print(thermo_data)
#
# # 6. Reactions
#
# ``Reaction`` objects can be created by putting together ``Nasa``, ``Nasa9``, ``Shomate`` and ``StatMech`` objects.
#
#
#
#
#
#
# ## 6.1. Initializing Reaction objects using from_string
#
# 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[12]:
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 ammonia reaction
rxn = Reaction.from_string('1.5H2 + 0.5N2 = NH3', species)
# Now we can calculate thermodynamic properties of the reaction.
# In[13]:
'''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))
'''Reverse change in entropy'''
S_rxn_rev = rxn.get_delta_S(units='cal/mol/K', T=300., rev=True)
print('Delta S_rev(T = 300 K) = {:.1f} cal/mol/K'.format(S_rxn_rev))
'''Gibbs energy of reactants'''
G_react = rxn.get_G_state(units='kcal/mol', T=300., state='reactants')
print('G_reactants(T = 300 K) = {:.1f} kcal/mol'.format(G_react))