Modeling dry reforming of methane using RMG-Cat

A notebook to accompany the manuscript:
"Automatic generation of microkinetic mechanisms for heterogeneous catalysis" by
C. Franklin Goldsmith, School of Engineering, Brown University, franklin_goldsmith@brown.edu, and
Richard H. West, Department of Chemical Engineering, Northeastern University, r.west@northeastern.edu

To demonstrate the capabilities of automatic mechanism generation for heterogeneous catalysis we apply our mechanism generator software RMG-Cat to the problem of methane dry reforming on nickel. Our first goal is to see if the code can produce a microkinetic mechanism that is consistent with a mechanism that was developed “by hand” by experts – in this case, the mechanism developed by Olaf Deutschmann and coworkers (Delgado, K.; Maier, L.; Tischer, S.; Zellner, A.; Stotz, H.; Deutschmann, O. Catalysts 2015, 5, 871–904.)

First, we print what git commit we were on when we ran this notebook, for both the source code (RMG-Py) and the database.

In [1]:
%%bash
cd $RMGpy
pwd
git log -n1 --pretty=oneline
cd ../RMG-database
pwd
git log -n1 --pretty=oneline
/Users/rwest/Downloads/temp-cat/RMG-Py
d8a19ee370bede03236234102ac407f2bada19ea Tweak the version info, header, and readme, for Cat branch
/Users/rwest/Downloads/temp-cat/RMG-database
2d575f2d7b09c923b9a4f0b06b03f49d33568941 I made a few small modifications to make it consistent with the new training data.

Model generation: Deutschmann only

We start with just the full seed mechanism, no extra reactions generated by RMG's reaction families. First we print the input file we'll use to generate the model.

In [2]:
%cat ch4_co2_seed/input.py
# Data sources
database(
    thermoLibraries=['surfaceThermo', 'primaryThermoLibrary', 'thermo_DFT_CCSDTF12_BAC','DFT_QCI_thermo'],
    reactionLibraries = [],
    seedMechanisms = ['Deutschmann_Ni_full'], # Full Deutschmann as seed mechanism.
    kineticsDepositories = ['training'],
    kineticsFamilies = 'none', # Turn off all reaction generation.
    kineticsEstimator = 'rate rules',
)

# List of species
species(
    label='X',
    reactive=True,
    structure=adjacencyList("1 X u0"),
)

species(
    label='CH4',
    reactive=True,
    structure=SMILES("[CH4]"),
)
species(
   label='O2',
   reactive=True,
   structure=adjacencyList(
       """
1 O u1 p2 c0 {2,S}
2 O u1 p2 c0 {1,S}
"""),
)

species(
    label='N2',
    reactive=False,
    structure=SMILES("N#N"),
)

species(
    label='CO2',
    reactive=True,
    structure=SMILES("O=C=O"),
)

species(
    label='H2O',
    reactive=True,
    structure=SMILES("O"),
)

species(
    label='H2',
    reactive=True,
    structure=SMILES("[H][H]"),
)

species(
    label='CO',
    reactive=True,
    structure=SMILES("[C-]#[O+]"),
)

species(
    label='C2H6',
    reactive=True,
    structure=SMILES("CC"),
)

species(
    label='CH2O',
    reactive=True,
    structure=SMILES("C=O"),
)

species(
    label='CH3',
    reactive=True,
    structure=SMILES("[CH3]"),
)

species(
    label='C3H8',
    reactive=True,
    structure=SMILES("CCC"),
)

species(
    label='H',
    reactive=True,
    structure=SMILES("[H]"),
)

species(
    label='C2H5',
    reactive=True,
    structure=SMILES("C[CH2]"),
)

species(
    label='CH3OH',
    reactive=True,
    structure=SMILES("CO"),
)

species(
    label='HCO',
    reactive=True,
    structure=SMILES("[CH]=O"),
)

species(
    label='CH3CHO',
    reactive=True,
    structure=SMILES("CC=O"),
)

species(
    label='OH',
    reactive=True,
    structure=SMILES("[OH]"),
)

species(
    label='C2H4',

    structure=SMILES("C=C"),
)

#----------
# Reaction systems
surfaceReactor(
    temperature=(1000,'K'),
    initialPressure=(1.0, 'bar'),
    initialGasMoleFractions={
        "CH4": 0.1,
        "CO2": 0.1,
        "N2": 0.8,
    },
    initialSurfaceCoverages={
        "X": 1.0,
    },
    surfaceVolumeRatio=(1.e5, 'm^-1'),
    surfaceSiteDensity=(2.9e-9, 'mol/cm^2'),
#    terminationConversion = { "CH4":0.9,},
    terminationTime=(1.0, 's'),
)

simulator(
    atol=1e-18,
    rtol=1e-12,
)

model(
    toleranceKeepInEdge=0.0,
    toleranceMoveToCore=1e-4,
    toleranceInterruptSimulation=0.1,
    maximumEdgeSpecies=100000
)

options(
    units='si',
    saveRestartPeriod=None,
    generateOutputHTML=True,
    generatePlots=True,
    saveEdgeSpecies=True,
    saveSimulationProfiles=True,
)

Then we try running it

In [3]:
%%bash
python $RMGpy/rmg.py ch4_co2_seed/input.py > /dev/null
tail -n12 ch4_co2_seed/RMG.log
Updating RMG execution statistics...
    Execution time (DD:HH:MM:SS): 00:00:00:07
    Memory used: memory usage was unable to be logged
Generating plots of execution statistics...


MODEL GENERATION COMPLETED

The final model core has 32 species and 23 reactions
The final model edge has 0 species and 0 reactions

RMG execution terminated at Thu Feb  9 16:56:49 2017

There are 23 reactions are as expected: just those in the Deutschmann_Ni_full seed mechanism. It reports there are more species than you might expect because all the gas phase species mentioned in the input file are included, even though many of them always have zero concentration. We find it helpful to leave them there so that, when they do show up later, they will be given our preferred names (RMG-generated names are not currently human-optimized).

Data processing

Next we will import some libraries and set things up to start importing and analyzing the simulation results.

In [4]:
%matplotlib inline
from matplotlib import pyplot as plt
import matplotlib

# The default output Type 3 (Type3) fonts can't be edited in Adobe Illustrator
# but Type 42 (TrueType) fonts can be, making it easier to move labels around
# slightly to improve layout before publication.
matplotlib.rcParams['pdf.fonttype'] = 42 

# Seaborn helps make matplotlib graphics nicer
import seaborn as sns
sns.set_style("ticks")
sns.set_context("paper", font_scale=1.5, rc={"lines.linewidth": 2.0})

import os
import re
import pandas as pd
import numpy as np
In [5]:
def get_last_csv_file(job_directory):
    """
    Find the CSV file from the largest model in the provided job directory.
    
    For CSV files named `simulation_1_13.csv` you want 13 to be the highest number.
    """
    solver_directory = os.path.join(job_directory,'solver')
    csv_files = sorted([f for f in os.listdir(solver_directory) if f.endswith('.csv') ],
                       key=lambda f: int(f[:-4].split('_')[2]))
    return os.path.join(solver_directory, csv_files[-1])
    
job_directory = 'ch4_co2_seed'
get_last_csv_file(job_directory)
get_last_csv_file('ch4_co2_families/')
Out[5]:
'ch4_co2_families/solver/simulation_1_49.csv'

We will use Pandas to import the csv file

In [6]:
last_csv_file = get_last_csv_file(job_directory)
data = pd.read_csv(last_csv_file)
data
Out[6]:
Time (s) Volume (m^3) HX(19) OX(20) OCX(21) CH3X(22) CH2X(23) CHX(24) CX(25) HOX(26) ... CH2O(9) CH3(10) C3H8(11) H(12) C2H5(13) CH3OH(14) HCO(15) CH3CHO(16) OH(17) C2H4(18)
0 1.072460e-19 0.083145 1.999990e-18 6.263650e-21 6.263650e-21 1.999990e-18 1.353993e-26 1.199091e-31 2.904726e-41 1.263652e-36 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 3.002887e-19 0.083145 5.599973e-18 1.753822e-20 1.753822e-20 5.599972e-18 8.178049e-26 1.423550e-30 1.754460e-40 7.632473e-36 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
2 6.863741e-19 0.083145 1.279994e-17 4.008736e-20 4.008736e-20 1.279994e-17 3.937307e-25 1.397626e-29 8.447080e-40 3.674725e-35 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
3 1.458545e-18 0.083145 2.719987e-17 8.518563e-20 8.518563e-20 2.719986e-17 1.719463e-24 1.236142e-28 3.689260e-39 1.604864e-34 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
4 3.002887e-18 0.083145 5.599973e-17 1.753822e-19 1.753822e-19 5.599972e-17 7.177896e-24 1.038981e-27 1.540527e-38 6.700119e-34 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
5 6.091570e-18 0.083145 1.135995e-16 3.557753e-19 3.557753e-19 1.135994e-16 2.931977e-23 8.517038e-27 6.299028e-38 2.737322e-33 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6 1.226894e-17 0.083145 2.287990e-16 7.165615e-19 7.165615e-19 2.287988e-16 1.184806e-22 6.895441e-26 2.555028e-37 1.106554e-32 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
7 2.462367e-17 0.083145 4.591982e-16 1.438134e-18 1.438134e-18 4.591973e-16 4.761278e-22 5.547032e-25 1.041619e-36 4.450082e-32 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
8 4.933314e-17 0.083145 9.199974e-16 2.881279e-18 2.881279e-18 9.199936e-16 1.907262e-21 4.446306e-24 4.405827e-36 1.785226e-31 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
9 9.875208e-17 0.083145 1.841599e-15 5.767568e-18 5.767568e-18 1.841583e-15 7.621244e-21 3.554728e-23 2.129840e-35 7.154574e-31 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
10 1.975900e-16 0.083145 3.684813e-15 1.154015e-17 1.154015e-17 3.684751e-15 3.036365e-20 2.833648e-22 1.434421e-34 2.867183e-30 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
11 3.952657e-16 0.083145 7.371289e-15 2.308531e-17 2.308530e-17 7.371041e-15 1.203787e-19 2.248344e-21 1.496387e-33 1.150038e-29 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
12 7.906172e-16 0.083145 1.474444e-14 4.617562e-17 4.617562e-17 1.474344e-14 4.728945e-19 1.768675e-20 2.046727e-32 4.623217e-29 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
13 1.581320e-15 0.083145 2.949155e-14 9.235626e-17 9.235623e-17 2.948749e-14 1.825546e-18 1.368825e-19 3.050480e-31 1.867272e-28 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
14 2.372023e-15 0.083145 4.423954e-14 1.385369e-16 1.385368e-16 4.423081e-14 3.786090e-18 3.840885e-19 1.099450e-30 3.994215e-28 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
15 3.953429e-15 0.083145 7.373924e-14 2.308982e-16 2.308980e-16 7.371451e-14 9.857632e-18 1.671362e-18 7.987695e-30 1.122923e-27 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
16 5.370983e-15 0.083145 1.001856e-13 3.136898e-16 3.136895e-16 1.001410e-13 1.684402e-17 3.639050e-18 2.135747e-29 2.016867e-27 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
17 6.788538e-15 0.083145 1.266355e-13 3.964813e-16 3.964809e-16 1.265650e-13 2.527610e-17 6.662235e-18 4.678741e-29 3.179193e-27 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
18 8.206092e-15 0.083145 1.530889e-13 4.792729e-16 4.792723e-16 1.529866e-13 3.493222e-17 1.081823e-17 8.773648e-29 4.600928e-27 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
19 1.104120e-14 0.083145 2.060041e-13 6.448561e-16 6.448549e-16 2.058245e-13 5.667971e-17 2.206442e-17 2.183921e-28 8.054109e-27 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
20 1.387631e-14 0.083145 2.589331e-13 8.104393e-16 8.104375e-16 2.586537e-13 8.197100e-17 3.848027e-17 4.473652e-28 1.252111e-26 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
21 1.671142e-14 0.083145 3.118776e-13 9.760224e-16 9.760199e-16 3.114736e-13 1.103744e-16 6.108493e-17 8.192120e-28 1.811512e-26 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
22 1.954653e-14 0.083145 3.648384e-13 1.141606e-15 1.141602e-15 3.642840e-13 1.413024e-16 9.060970e-17 1.383429e-27 2.490440e-26 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
23 2.238163e-14 0.083145 4.178162e-13 1.307189e-15 1.307184e-15 4.170850e-13 1.742803e-16 1.275435e-16 2.187799e-27 3.293784e-26 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
24 2.805185e-14 0.083145 5.238239e-13 1.638355e-15 1.638348e-15 5.226586e-13 2.447105e-16 2.252826e-16 4.761699e-27 5.295574e-26 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
25 3.372207e-14 0.083145 6.299032e-13 1.969521e-15 1.969512e-15 6.281944e-13 3.190704e-16 3.568860e-16 9.021091e-27 7.858511e-26 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
26 3.939228e-14 0.083145 7.360558e-13 2.300688e-15 2.300674e-15 7.336925e-13 3.957568e-16 5.239373e-16 1.547737e-26 1.102371e-25 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
27 4.506250e-14 0.083145 8.422824e-13 2.631854e-15 2.631837e-15 8.391527e-13 4.738538e-16 7.273348e-16 2.466095e-26 1.483167e-25 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
28 5.073272e-14 0.083145 9.485837e-13 2.963020e-15 2.962998e-15 9.445753e-13 5.528363e-16 9.675885e-16 3.711278e-26 1.932238e-25 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
29 5.640294e-14 0.083145 1.054960e-12 3.294187e-15 3.294159e-15 1.049960e-12 6.323652e-16 1.245024e-15 5.337898e-26 2.453542e-25 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
6625 9.587599e-01 0.083145 1.160163e-02 5.537935e-06 6.100101e-03 2.882105e-09 6.932365e-09 1.207607e-05 6.260070e-07 1.002195e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6626 9.605623e-01 0.083145 1.160194e-02 5.536540e-06 6.100371e-03 2.880673e-09 6.928720e-09 1.206937e-05 6.261881e-07 1.001972e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6627 9.623648e-01 0.083145 1.160224e-02 5.535159e-06 6.100639e-03 2.879255e-09 6.925112e-09 1.206274e-05 6.263676e-07 1.001751e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6628 9.641673e-01 0.083145 1.160254e-02 5.533792e-06 6.100903e-03 2.877850e-09 6.921538e-09 1.205617e-05 6.265454e-07 1.001532e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6629 9.657895e-01 0.083145 1.160281e-02 5.532572e-06 6.101140e-03 2.876598e-09 6.918352e-09 1.205032e-05 6.267041e-07 1.001336e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6630 9.674117e-01 0.083145 1.160307e-02 5.531363e-06 6.101374e-03 2.875357e-09 6.915194e-09 1.204451e-05 6.268614e-07 1.001142e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6631 9.690339e-01 0.083145 1.160334e-02 5.530165e-06 6.101606e-03 2.874126e-09 6.912064e-09 1.203876e-05 6.270174e-07 1.000951e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6632 9.706561e-01 0.083145 1.160360e-02 5.528977e-06 6.101836e-03 2.872906e-09 6.908961e-09 1.203306e-05 6.271721e-07 1.000760e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6633 9.722783e-01 0.083145 1.160386e-02 5.527800e-06 6.102064e-03 2.871697e-09 6.905886e-09 1.202741e-05 6.273256e-07 1.000572e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6634 9.737383e-01 0.083145 1.160409e-02 5.526749e-06 6.102268e-03 2.870618e-09 6.903141e-09 1.202237e-05 6.274626e-07 1.000403e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6635 9.751983e-01 0.083145 1.160432e-02 5.525707e-06 6.102470e-03 2.869548e-09 6.900418e-09 1.201737e-05 6.275985e-07 1.000236e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6636 9.766583e-01 0.083145 1.160454e-02 5.524672e-06 6.102670e-03 2.868486e-09 6.897717e-09 1.201241e-05 6.277335e-07 1.000070e-08 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6637 9.781183e-01 0.083145 1.160477e-02 5.523646e-06 6.102869e-03 2.867432e-09 6.895037e-09 1.200748e-05 6.278674e-07 9.999058e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6638 9.795783e-01 0.083145 1.160499e-02 5.522628e-06 6.103066e-03 2.866387e-09 6.892378e-09 1.200260e-05 6.280003e-07 9.997426e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6639 9.810383e-01 0.083145 1.160521e-02 5.521618e-06 6.103262e-03 2.865349e-09 6.889741e-09 1.199776e-05 6.281323e-07 9.995807e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6640 9.824983e-01 0.083145 1.160543e-02 5.520616e-06 6.103456e-03 2.864320e-09 6.887124e-09 1.199295e-05 6.282632e-07 9.994200e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6641 9.838123e-01 0.083145 1.160563e-02 5.519721e-06 6.103629e-03 2.863401e-09 6.884787e-09 1.198866e-05 6.283802e-07 9.992765e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6642 9.851263e-01 0.083145 1.160582e-02 5.518832e-06 6.103801e-03 2.862489e-09 6.882467e-09 1.198440e-05 6.284964e-07 9.991340e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6643 9.864403e-01 0.083145 1.160602e-02 5.517949e-06 6.103972e-03 2.861583e-09 6.880163e-09 1.198017e-05 6.286119e-07 9.989926e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6644 9.876229e-01 0.083145 1.160619e-02 5.517161e-06 6.104125e-03 2.860773e-09 6.878103e-09 1.197638e-05 6.287151e-07 9.988661e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6645 9.888055e-01 0.083145 1.160636e-02 5.516377e-06 6.104277e-03 2.859968e-09 6.876057e-09 1.197263e-05 6.288177e-07 9.987404e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6646 9.899881e-01 0.083145 1.160653e-02 5.515598e-06 6.104428e-03 2.859168e-09 6.874024e-09 1.196889e-05 6.289196e-07 9.986155e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6647 9.911707e-01 0.083145 1.160670e-02 5.514824e-06 6.104578e-03 2.858374e-09 6.872004e-09 1.196518e-05 6.290209e-07 9.984914e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6648 9.923533e-01 0.083145 1.160687e-02 5.514055e-06 6.104727e-03 2.857584e-09 6.869997e-09 1.196150e-05 6.291217e-07 9.983681e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6649 9.935359e-01 0.083145 1.160704e-02 5.513291e-06 6.104875e-03 2.856800e-09 6.868003e-09 1.195784e-05 6.292218e-07 9.982456e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6650 9.947185e-01 0.083145 1.160721e-02 5.512532e-06 6.105022e-03 2.856020e-09 6.866022e-09 1.195420e-05 6.293213e-07 9.981239e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6651 9.970837e-01 0.083145 1.160754e-02 5.511028e-06 6.105314e-03 2.854476e-09 6.862097e-09 1.194699e-05 6.295184e-07 9.978827e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6652 9.994489e-01 0.083145 1.160786e-02 5.509543e-06 6.105601e-03 2.852952e-09 6.858223e-09 1.193988e-05 6.297132e-07 9.976446e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6653 1.000000e+00 0.083145 1.160794e-02 5.509200e-06 6.105668e-03 2.852600e-09 6.857327e-09 1.193824e-05 6.297582e-07 9.975895e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
6654 1.001814e+00 0.083145 1.160819e-02 5.508078e-06 6.105885e-03 2.851448e-09 6.854398e-09 1.193286e-05 6.299056e-07 9.974095e-09 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

6655 rows × 34 columns

In [7]:
def get_pandas_data(job_directory):
    """
    Get the last CSV file from the provided job directory,
    import it into a Pandas data frame, and tidy it up a bit.
    """
    last_csv_file = get_last_csv_file(job_directory)
    data = pd.read_csv(last_csv_file)
    
    # Make the Time into an index and remove it as a column
    data.index = data['Time (s)']
    del data['Time (s)']
    # Remove inerts that RMG added automatically but we're not using
    for i in 'Ar He Ne'.split():
        del data[i]
    # Remove the Volume column
    del data['Volume (m^3)']
    # Set any amounts that are slightly negative (within the ODE solver's ATOL tolerance), equal to zero
    # to allow 'area' plots without warnings or errors.
    # Anything more negative than -1e-16 probably indicates a bug in RMG and should not be hidden here ;-)
    data[(data<0) & (data>-1e-16)] = 0
    return data
In [8]:
def rename_columns(data):
    """
    Removes the number (##) from the end of the column names, in place,
    unless doing so would make the names collide.
    Also renames a few species so the plot labels match the names in the manuscript.
    """
    import re
    old = data.columns
    new = [re.sub('\(\d+\)$','',n) for n in old]
    # don't translate names that would no longer be unique
    mapping = {k:v for k,v in zip(old,new) if new.count(v)==1}
    data.rename(columns=mapping, inplace=True)
    
    # Now change a few species that are named differently in the manuscript
    # than in the thermodynamics database used to build the model,
    # so that the plot labels match the manuscript.
    mapping = {'OCX': 'COX', 'C2H3X':'CH3CX', 'C2H3OX':'CH3CXO'}
    data.rename(columns=mapping, inplace=True)
In [9]:
data1 = get_pandas_data('ch4_co2_seed')
rename_columns(data1)
data1.columns
Out[9]:
Index([u'HX', u'OX', u'COX', u'CH3X', u'CH2X', u'CHX', u'CX', u'HOX', u'HOCXO',
       u'CXHO', u'N2', u'X', u'CH4', u'O2', u'CO2', u'H2O', u'H2', u'CO',
       u'C2H6', u'CH2O', u'CH3', u'C3H8', u'H', u'C2H5', u'CH3OH', u'HCO',
       u'CH3CHO', u'OH', u'C2H4'],
      dtype='object')
In [10]:
# Test it with some plots
data1[['CH4', 'CO2']].plot.line()
data1[['CO', 'H2']].plot.line()
data1[['H2O']].plot.line()
Out[10]:
<matplotlib.axes._subplots.AxesSubplot at 0x11f193090>
In [11]:
species_names = data1.columns
# just the gas phase species that aren't always zero:
gas_phase = [n for n in species_names if 'X' not in n and (data1[n]>0).any()]
surface_phase = [n for n in species_names if 'X' in n]
surface_phase.remove('X')
data1[gas_phase].plot.line()
data1[surface_phase].plot.line()
Out[11]:
<matplotlib.axes._subplots.AxesSubplot at 0x11f219b90>

Model generation: with RMG-Cat reaction families

Now lets look at the version that generates a mechanism by applying reaction families. First, inspect how the input file differs from the one above.

In [12]:
%%bash
diff -U 3 ch4_co2_seed/input.py ch4_co2_families/input.py
--- ch4_co2_seed/input.py	2017-02-08 22:23:42.000000000 -0500
+++ ch4_co2_families/input.py	2017-02-08 22:23:42.000000000 -0500
@@ -1,10 +1,10 @@
 # Data sources
 database(
     thermoLibraries=['surfaceThermo', 'primaryThermoLibrary', 'thermo_DFT_CCSDTF12_BAC','DFT_QCI_thermo'],
-    reactionLibraries = [],
-    seedMechanisms = ['Deutschmann_Ni_full'], # Full Deutschmann as seed mechanism.
+    reactionLibraries = [('Deutschmann_Ni', False)],
+    seedMechanisms = [],
     kineticsDepositories = ['training'],
-    kineticsFamilies = 'none', # Turn off all reaction generation.
+    kineticsFamilies = 'default',
     kineticsEstimator = 'rate rules',
 )
 

The only changes are

  1. remove the 'Deutschmann_Ni_full' seed mechanism
  2. add the 'Deutschmann_Ni' reaction library (which contains only those reactions that don't belong to a family)
  3. turn on the 'default' set of reaction families (as specified in the database).

Now we run this new input file. This will take a while longer than the run above, but should complete in a few minutes.

In [13]:
%%bash
python $RMGpy/rmg.py ch4_co2_families/input.py > /dev/null
tail -n12 ch4_co2_families/RMG.log
Updating RMG execution statistics...
    Execution time (DD:HH:MM:SS): 00:00:01:54
    Memory used: memory usage was unable to be logged
Generating plots of execution statistics...


MODEL GENERATION COMPLETED

The final model core has 49 species and 98 reactions
The final model edge has 249 species and 418 reactions

RMG execution terminated at Thu Feb  9 16:58:52 2017

Data processing

First, we make a few plots of the new model. Mostly just to show how it can be done, and to see what the results look like. These aren't very pretty as they're not going in the manuscript.

In [14]:
data2 = get_pandas_data('ch4_co2_families')
rename_columns(data2)
data2.columns
Out[14]:
Index([u'N2', u'X', u'CH4', u'O2', u'CO2', u'H2O', u'H2', u'CO', u'C2H6',
       u'CH2O', u'CH3', u'C3H8', u'H', u'C2H5', u'CH3OH', u'HCO', u'CH3CHO',
       u'OH', u'C2H4', u'HX', u'CH3X', u'OX', u'COX', u'CH2X', u'CHX', u'CX',
       u'HOX', u'HOCXO', u'CXHO', u'C2H5X', u'C2H4X', u'CH3CX', u'C3H7X(30)',
       u'C3H7X(29)', u'CH3CXO', u'SX(107)', u'C2HO2X', u'CHO2X', u'C2HOX',
       u'CH2O2', u'SX(106)', u'SX(152)', u'CH3OX(31)', u'SX(136)',
       u'CH3OX(32)', u'C2H4OX'],
      dtype='object')
In [15]:
get_last_csv_file('ch4_co2_families')
Out[15]:
'ch4_co2_families/solver/simulation_1_49.csv'
In [16]:
data2[['CH4', 'CO2']].plot.line()
data2[['CO', 'H2']].plot.line()
data2[['H2O']].plot.line()
Out[16]:
<matplotlib.axes._subplots.AxesSubplot at 0x11bda2e90>
In [17]:
print "All species"
data2.plot.area()
plt.show()
All species
In [18]:
print "Significant species (those that exceed 0.001 mol at some point)"
significant = [n for n in data2.columns if(data2[n]>0.001).any()]
with sns.color_palette("hls", len(significant)):
    data2[significant].plot.area(legend='reverse')
Significant species (those that exceed 0.001 mol at some point)
In [19]:
surface = [n for n in data2.columns if 'X' in n and n!='X' and (data2[n]>1e-6).any()]
print "The {} surface species that exceed 1e-6 mol at some point".format(len(surface))
with sns.color_palette('Set3',len(surface)):
    data2[surface].plot.area(legend='reverse')
The 8 surface species that exceed 1e-6 mol at some point
In [20]:
species_names = data2.columns
gas_phase = [n for n in species_names if 'X' not in n and (data2[n]>0).any()]
surface_phase = [n for n in species_names if 'X' in n]
surface_phase.remove('X')
print "Total moles of gas"
data2[gas_phase].sum(axis=1).plot()
plt.show()
print "All gas phase species"
data2[gas_phase].plot.line()
plt.show()
print "All surface species"
data2[surface_phase].plot.line()
plt.show()
Total moles of gas
All gas phase species
All surface species
In [21]:
print "A comparison of the two reactants across the two models"
ax = plt.subplot()
data1[['CH4', 'CO2']].plot.line(ax=ax) # seed
data2[['CH4', 'CO2']].plot.line(ax=ax, style=':') # families
plt.show()
A comparison of the two reactants across the two models

Model comparison

Now we will start making some prettier plots for the manuscript, comparing the two models

In [22]:
plt.rcParams.update({'mathtext.default':  'regular' }) # make the LaTeX subscripts (eg. CH4) use regular font
sns.set_context("paper", font_scale=1, rc={"lines.linewidth": 1.5}) # Tweak the font size and default line widths

def comparison_plot(subplot_axis=None, species='CH4'):
    label = re.sub('(\d)',r'$_\1$',species)
    ax1 = subplot_axis or plt.subplot()
    
    plt.locator_params(nbins=4) # fewer tick marks
    
    ax1.plot(0, data1[species].iloc[0], 'ko')
    ax1.plot(data1.index, data1[species],'k--', linewidth=2.5)
    ax1.plot(data2.index, data2[species],'r-')
    plt.xlim(0,1)
    ax1.set_ylabel('moles')
    ax1.set_xlabel('time (s)')
    
    # Legend
    dummy = matplotlib.patches.Patch(alpha=0)
    plt.legend([dummy],[label], loc='best', fontsize=12)
    
    plt.tight_layout()

fig = plt.figure(figsize=(2.5,2.5))
ax1 = comparison_plot()
In [23]:
fig = plt.figure(figsize=(7,4))
for n, species in enumerate('CH4 CO H2O CO2 H2'.split()):
    ax = plt.subplot(2,3,n+1)
    comparison_plot(ax, species)

ax = plt.subplot(2,3,6)
ax.axis('off')
red = matplotlib.lines.Line2D([], [], color='r', label='RMG-Cat')
dash = matplotlib.lines.Line2D([], [], color='k', linestyle='--', linewidth=2.5, label='Delgado et al.')

plt.legend(handles=[red, dash], loc='best',fontsize=12)
plt.tight_layout()
plt.savefig('Multi-panel gas comparison.pdf', bbox_inches='tight')
In [24]:
def extras_plot(subplot_axis=None, species_list=['C2H6','CH2O'], label_positions=None):
    """
    Plot the requested species on one plot,
    with just the 'data2' (RMG-Cat) values.
    Useful for species not in the Delgado model.
    """
    ax1 = subplot_axis or plt.subplot()
    plt.locator_params(nbins=4) # fewer tick marks
    plt.ticklabel_format(style='sci', axis='y', scilimits=(3,3))
    
    for i,species in enumerate(species_list):
        label = re.sub('(\d)',r'$_\1$',species)
        ax1.semilogy(data2.index, data2[species],'-', label=label)
        plt.ylim(ymin=1e-9)
        plt.xlim(0,1)
        ax1.set_ylabel('moles')
        ax1.set_xlabel('time (s)')
        # Manually constructed label
        x = label_positions[species] if label_positions and species in label_positions else 0.5
        y = data2[x:][species].iloc[0]
        ax1.text(x,y,label,
                 verticalalignment='bottom',
                 color=sns.color_palette()[i],
                 fontsize=10)
        plt.tight_layout()

# test it
fig = plt.figure(figsize=(4,4))
with sns.color_palette("Dark2", 3):
    extras_plot(species_list='C2H6 CH2O C3H8'.split())
In [25]:
# Everything that exceeds some lower threshold
gas_phase = [n for n in species_names if 'X' not in n and (data2[n]>1.1e-9).any()]
# Remove things already in the comparison plots
gas_phase = [n for n in gas_phase if n not in 'CH4 CO H2O CO2 H2 N2'.split()]
print "Extra gas phase species of note are {}.".format(", ".join(gas_phase))
fig = plt.figure(figsize=(3.25,3.25))
with sns.color_palette("Dark2", len(gas_phase)):
    extras_plot(species_list=gas_phase, 
               label_positions={'C2H5':0.15, 'C3H8':0.1, 'CH3OH':0.7, 'H':0.3})
plt.savefig('Gas extra species (many).pdf',bbox_inches='tight')
Extra gas phase species of note are C2H6, CH2O, CH3, C3H8.
In [26]:
def multi_comparison_plot(subplot_axis=None, species_list=['X', 'HX'], label_positions=None):
    """
    Plot many surface species AND their comparison (if there is one) from the Delgado model
    on a semilog plot. 
    """
    ax1 = subplot_axis or plt.subplot()
    plt.locator_params(nbins=4) # fewer tick marks
    for i,species in enumerate(species_list):
        assert 'X' in species, "For surface species only (y axis is normalized for fractional coverage)."
        label = re.sub('(\d)',r'$_\1$',species)
        label = label.replace('X','*')
        if label=='*': label = 'vacant'
        if species in data1.columns:
            sites = data1['X'].iloc[0]
            ax1.semilogy(data1.index, data1[species]/sites,'k--', linewidth=2.5)
        sites = data2['X'].iloc[0]
        ax1.semilogy(data2.index, data2[species]/sites,'-')
        plt.xlim(0,1)
        plt.ylim(ymin=1e-6)
        ax1.set_ylabel('site fraction')
        ax1.set_xlabel('time (s)')
        ## Manually constructed label
        x = label_positions[species] if label_positions and species in label_positions else 0.5
        y = data2[x:][species].iloc[0] / sites
        ax1.text(x,y,label, 
                 fontsize=10,
                 verticalalignment='bottom',
                 color=sns.color_palette()[i])

# test it
multi_comparison_plot()
In [27]:
species_names = data2.columns
gas_phase = [n for n in species_names if 'X' not in n and (data2[n]>0).any()]

sites = data2['X'].iloc[0] # initial number of suface sites
# get only adsorbates that exceed a site fraction of 1e-6
surface_phase = [n for n in species_names if 'X' in n and (data2[n]>sites*1e-6).any()]
surface_phase
Out[27]:
['X',
 'HX',
 'OX',
 'COX',
 'CH2X',
 'CHX',
 'CX',
 'CH3CX',
 'CH3CXO',
 'CHO2X',
 'C2HOX']
In [28]:
label_positions = {'HX':0.4, 'COX':0.6, 'CHX':0.7, 'OX':0.4, 
                   'CHOX':0.6, 'CX':0.3, 'CH2X':0.05,
                   'C2HOX':0.8, 'CHO2X':0.7}
fig = plt.figure(figsize=(5,5))
with sns.color_palette("Dark2", len(surface_phase)):
    ax1 = multi_comparison_plot(species_list=surface_phase, label_positions=label_positions)
plt.tight_layout()
plt.savefig('Surface comparison semilog.pdf', bbox_inches='tight')
In [29]:
def multi_comparison_loglogplot(subplot_axis=None, species_list=['X', 'HX'], label_positions=None):
    """
    Plot many things AND their comparison (if there is one) from the Delgado model
    on a log-log plot
    """
    ax1 = subplot_axis or plt.subplot()
    for i,species in enumerate(species_list):
        label = re.sub('(\d)',r'$_\1$',species)
        label = label.replace('X','*')
        if label=='*': label='vacant'
        if species in data1.columns:
            sites = data1['X'].iloc[0]
            ax1.loglog(data1.index, data1[species]/sites,'k--', linewidth=2.5)
        sites = data2['X'].iloc[0]
        ax1.loglog(data2.index, data2[species]/sites,'-')
        plt.xlim(1e-6,1)
        plt.ylim(ymin=1e-6)
        ax1.set_ylabel('site fraction')
        ax1.set_xlabel('time (s)')
        ## Manually constructed label
        x = label_positions[species] if label_positions and species in label_positions else 3.0
        x = 10**(-1*x)
        y = data2[x:][species].iloc[0] / sites
        if x==1: x=1.3 # move them right just a little
        ax1.text(x,y,label, fontsize=10,
                 verticalalignment='center' if x==1.3 else 'bottom',
                 color=sns.color_palette()[i])  

label_positions = {'HX':0.5, 'COX':0, 'CX':2, 'C2HOX':0,
                  'CH3CXO':0, 'CH3CX':0, 'OX':5, 'CHO2X':0,
                  'CH2X':2.5}
fig = plt.figure(figsize=(5,4))
with sns.color_palette("Dark2", len(surface_phase)):
    ax1 = multi_comparison_loglogplot(species_list=surface_phase, label_positions=label_positions)
plt.tight_layout()
plt.savefig('Surface comparison loglog.pdf',bbox_inches='tight')

Effect of tolerance

Now we investigate the effect of gradually decreasing (tightening) the tolerance

In [30]:
# First, make a series of input files in separate directories

base_directory = 'ch4_co2_tolerances'

with open(os.path.join(base_directory, 'input.template.py')) as infile:
    input_file = infile.read()

def directory(i):
    return os.path.join(base_directory, "ch4_co2_tolerance_m{}".format(i))

for i in range(9):
    tolerance = 0.1**i
    tolerance_string = 'toleranceMoveToCore={:.1e},'.format(tolerance)
    print tolerance_string
    input_file = re.sub('toleranceMoveToCore\s*=\s*(.*?),', tolerance_string, input_file)
    os.path.exists(directory(i)) or  os.makedirs(directory(i))
    with open(os.path.join(directory(i), 'input.py'), 'w') as outfile:
        outfile.write(input_file)
    print "Saved to {}/input.py".format(directory(i))
toleranceMoveToCore=1.0e+00,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m0/input.py
toleranceMoveToCore=1.0e-01,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m1/input.py
toleranceMoveToCore=1.0e-02,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m2/input.py
toleranceMoveToCore=1.0e-03,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m3/input.py
toleranceMoveToCore=1.0e-04,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m4/input.py
toleranceMoveToCore=1.0e-05,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m5/input.py
toleranceMoveToCore=1.0e-06,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m6/input.py
toleranceMoveToCore=1.0e-07,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m7/input.py
toleranceMoveToCore=1.0e-08,
Saved to ch4_co2_tolerances/ch4_co2_tolerance_m8/input.py
In [31]:
# Now run all the jobs
# Don't execute this cell unless you have a while to wait.
import subprocess
import sys
for i in range(9):
    print "Attempting to run job {} in directory {}".format(i, directory(i))
    try:
        retcode = subprocess.call("python $RMGpy/rmg.py {}/input.py".format(directory(i)), shell=True)
        if retcode < 0:
            print >>sys.stderr, "Process was terminated by signal", -retcode
        elif retcode > 0:
            print >>sys.stderr, "Process returned", retcode
        else:
            print "Success"
    except OSError as e:
        print >>sys.stderr, "Execution failed:", e
Attempting to run job 0 in directory ch4_co2_tolerances/ch4_co2_tolerance_m0
Success
Attempting to run job 1 in directory ch4_co2_tolerances/ch4_co2_tolerance_m1
Success
Attempting to run job 2 in directory ch4_co2_tolerances/ch4_co2_tolerance_m2
Success
Attempting to run job 3 in directory ch4_co2_tolerances/ch4_co2_tolerance_m3
Success
Attempting to run job 4 in directory ch4_co2_tolerances/ch4_co2_tolerance_m4
Success
Attempting to run job 5 in directory ch4_co2_tolerances/ch4_co2_tolerance_m5
Success
Attempting to run job 6 in directory ch4_co2_tolerances/ch4_co2_tolerance_m6
Success
Attempting to run job 7 in directory ch4_co2_tolerances/ch4_co2_tolerance_m7
Success
Attempting to run job 8 in directory ch4_co2_tolerances/ch4_co2_tolerance_m8
Success
In [32]:
# Now read the ends of the log files and extract the mechanism sizes.
epsilon = []
core_species = []
core_rxns = []
edge_species = []
edge_rxns = []

for i in range(9):
    dirname = directory(i)
    print "reading from ", directory(i)
    last_lines = subprocess.check_output(['tail', '-n','6', os.path.join(directory(i),'RMG.log')])

    match = re.search('The final model core has (\d+) species and (\d+) reactions', last_lines)
    if match is None: 
        print "Trouble with {}/RMG.log:\n{}".format(directory(i),last_lines)
    core_species.append(int(match.group(1)))
    core_rxns.append(int(match.group(2)))
    match = re.search('The final model edge has (\d+) species and (\d+) reactions', last_lines)
    if match is None: 
        print "Trouble with {}/RMG.log:\n{}".format(directory(i),last_lines)
    edge_species.append(int(match.group(1)))
    edge_rxns.append(int(match.group(2)))
    epsilon.append( 0.1**i )

#remove the four inerts, N2, Ar, Ne, and He that RMG automatically adds
core_species = np.array(core_species) - 4
edge_species = np.array(edge_species) - 4
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m0
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m1
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m2
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m3
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m4
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m5
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m6
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m7
reading from  ch4_co2_tolerances/ch4_co2_tolerance_m8
In [33]:
# Now count the reactions by type
Deutschmann = []
abstraction = []
adsorption = []
dissociation = []
for i in range(9):
    ckfilepath = os.path.join(directory(i), 'chemkin', 'chem_annotated.inp')
    re_library = re.compile('! Library reaction: (.*)')
    re_family = re.compile('! Template reaction: (.*)')
    from collections import Counter
    counts = Counter()
    with open(ckfilepath) as ckfile:
        for line in ckfile:
            match = re_family.match(line) or re_library.match(line)
            if match is None:
                continue
            source = match.group(1)
            counts[source] += 1
    counts

    Deutschmann.append(counts['Deutschmann_Ni'])
    abstraction.append(counts['Surface_Abstraction'])
    adsorption.append(counts['Surface_Adsorption_Dissociative'] + counts['Surface_Adsorption_Single'])
    dissociation.append(counts['Surface_Dissociation'])
print 'Deutschmann',Deutschmann
print 'abstraction',abstraction
print 'adsorption',adsorption
print 'dissociation',dissociation

Deutschmann = np.array(Deutschmann)
abstraction = np.array(abstraction)
adsorption = np.array(adsorption)
dissociation = np.array(dissociation)
total = Deutschmann + abstraction + adsorption + dissociation
assert (total == np.array(core_rxns)).all(), "Sum of counters doesn't equal core_rxns from above"
Deutschmann [11, 11, 11, 13, 13, 13, 13, 13, 13]
abstraction [10, 10, 22, 25, 29, 61, 61, 103, 269]
adsorption [2, 3, 3, 5, 8, 10, 14, 28, 35]
dissociation [7, 7, 11, 12, 14, 22, 22, 35, 60]
In [34]:
# Now plot the figure
fig = plt.figure()
fig.set_size_inches(7,3.5) #set size, in inches
gs = matplotlib.gridspec.GridSpec(1, 3)
ax0 = plt.subplot(gs[0])
ax1 = plt.subplot(gs[1])
ax2 = plt.subplot(gs[2])

ax0.loglog(epsilon, edge_species, 'r^-', label='edge')
ax0.loglog(epsilon, core_species, 'bo-', label='core')

ax1.loglog(epsilon, edge_rxns, 'r^-', label='edge')
ax1.loglog(epsilon, core_rxns, 'bo-', label='core')

#ax2.semilogx(epsilon, Deutschmann, 'k:', label='Deutschmann')
#ax2.semilogx(epsilon, abstraction, 'b-', label='abstraction')
#ax2.semilogx(epsilon, dissociation, 'g--', label='dissociation')
#ax2.semilogx(epsilon, adsorption, 'r-.', label='adsorption')

stacks = ax2.stackplot(epsilon, Deutschmann, adsorption, dissociation, abstraction,
             labels='Deutcschmann Adsorption Dissociation  Abstraction'.split(),
             colors=sns.color_palette("Set2",4)
             )
hatches = ['', '///', '|||', '---']
for i, patch in enumerate(stacks):
    patch.set_hatch(hatches[i])
ax2.set_xscale('log')


#ax0.set_ylim([20,1000])
#ax1.set_ylim([30,2000])
#ax2.set_ylim([0,300])

ax0.set_xlabel('error tolerance, $\epsilon$', fontsize=11)
ax1.set_xlabel('error tolerance, $\epsilon$', fontsize=11)
ax2.set_xlabel('error tolerance, $\epsilon$', fontsize=11)

#ax0.set_ylabel('species')
#ax1.set_ylabel('reactions')
#ax2.set_ylabel('reactions in core')

ax0.set_title('no. of species', fontsize=11)
ax1.set_title('no. of reactions', fontsize=11)
ax2.set_title('no. of core reactions', fontsize=11)

ax0.legend(loc='upper left', bbox_to_anchor=[0.0, 0.9], fontsize=10, frameon=False)
ax1.legend(loc='upper left', bbox_to_anchor=[0.0, 0.9], fontsize=10, frameon=False)
# For the stacked plot we want to plot the legend in reverse order 
# so the bottom patch is on the bottom of the legend
handles, labels = ax2.get_legend_handles_labels()
ax2.legend(handles[::-1], labels[::-1],
          loc='upper left', bbox_to_anchor=[0.0, 0.9], fontsize=10, frameon=False)


ax0.set_xticks([1.0E-8, 1.0E-4,  1.0])
ax1.set_xticks([1.0E-8, 1.0E-4,  1.0])
ax2.set_xticks([1.0E-8, 1.0E-4,  1.0])
ax0.tick_params(labelsize=10)
ax1.tick_params(labelsize=10)
ax2.tick_params(labelsize=10)

ax0.invert_xaxis()
ax1.invert_xaxis()
ax2.invert_xaxis()

ax0.text(0.1, 0.9, '(a)', fontsize=10,
verticalalignment='bottom', horizontalalignment='left',
transform=ax0.transAxes)
ax1.text(0.1, 0.9, '(b)', fontsize=10,
verticalalignment='bottom', horizontalalignment='left',
transform=ax1.transAxes)
ax2.text(0.1, 0.9, '(c)', fontsize=10,
verticalalignment='bottom', horizontalalignment='left',
transform=ax2.transAxes)

fig.tight_layout()
fig.savefig('mechanism_size.pdf', bbox_inches='tight')
In [ ]: