Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

UMA Catalysis Tutorial

Author: Zack Ulissi (Meta, CMU), with help from AI coding agents / LLMs

Original paper: Bjarne Kreitz et al. JPCC (2021)

Overview

This tutorial demonstrates how to use the Universal Model for Atoms (UMA) machine learning potential to perform comprehensive catalyst surface analysis. We replicate key computational workflows from “Microkinetic Modeling of CO₂ Desorption from Supported Multifaceted Ni Catalysts” by Bjarne Kreitz (now faculty at Georgia Tech!), showing how ML potentials can accelerate computational catalysis research.

Installation and Setup

This tutorial uses a number of helpful open source packages:

Huggingface setups

You need to get a HuggingFace account and request access to the UMA models.

You need a Huggingface account, request access to https://huggingface.co/facebook/UMA, and to create a Huggingface token at https://huggingface.co/settings/tokens/ with these permission:

Permissions: Read access to contents of all public gated repos you can access

Then, add the token as an environment variable using huggingface-cli login:

or you can set the token via HF_TOKEN variable:

FAIR Chemistry (UMA) installation

It may be enough to use pip install fairchem-core. This gets you the latest version on PyPi (https://pypi.org/project/fairchem-core/)

Here we install some sub-packages. This can take 2-5 minutes to run.

fairchem-applications-cattsunami         1.1.2.dev403+g3801dac0c
fairchem-core                            2.23.1.dev4+g3801dac0c
fairchem-data-oc                         1.0.3.dev403+g3801dac0c
fairchem-data-omat                       0.2.1.dev308+g3801dac0c
Warp 1.18.0 initialized:
   CUDA Toolkit 13.4, Driver 13.1
   Devices:
     "cpu"      : "x86_64"
     "cuda:0"   : "Tesla T4" (16 GiB, sm_75, mempool enabled)
   Kernel cache:
     /home/runner/.cache/warp/1.18.0
'2.23.1.dev4+g3801dac0c'

Package imports

First, let’s import all necessary libraries and initialize the UMA-S-1P2P1 predictor:


Loading UMA-S-1P2P1 model...
WARNING:root:device was not explicitly set, using device='cuda'.
✓ Model loaded successfully!

It is somewhat time consuming to run this. We’re going to use a small number of bulks for the testing of this documentation, but otherwise run all of the results for the actual documentation.


Part 1: Bulk Crystal Optimization

Introduction

Before studying surfaces, we need to determine the equilibrium lattice constant of bulk Ni. This is crucial because surface energies and adsorbate binding depend strongly on the underlying lattice parameter.

Theory

For FCC metals like Ni, the lattice constant a defines the unit cell size. The experimental value for Ni is a = 3.524 Å at room temperature. We’ll optimize both atomic positions and the cell volume to find the ML potential’s equilibrium structure.

Initial lattice constant: 3.52 Å
Number of atoms: 4
/tmp/ipykernel_10136/3959847705.py:15: DeprecationWarning: Use FrechetCellFilter for better convergence w.r.t. cell variables.
  ecf = ExpCellFilter(ni_bulk)
WARNING:root:Model is being compiled this might take a while for the first time
W1007 03:11:09.468000 10136 site-packages/torch/_logging/_internal.py:1345] [0/0] Profiler record function <class 'torch.autograd.profiler.record_function'> will be ignored

==================================================
Experimental lattice constant: 3.52 Å
Optimized lattice constant:    3.51 Å
Relative error:                0.44%
==================================================

Part 2: Surface Energy Calculations

Introduction

Surface energy (γ) quantifies the thermodynamic cost of creating a surface. It determines surface stability, morphology, and catalytic activity. We’ll calculate γ for four low-index Ni facets: (111), (100), (110), and (211).

Theory

The surface energy is defined as:

γ=Eslab−N⋅Ebulk2A\gamma = \frac{E_{\text{slab}} - N \cdot E_{\text{bulk}}}{2A}

where:

Challenge: Direct calculation suffers from quantum size effects, and if you were doing DFT calculations small numerical errors in the simulation or from the K-point grid sampling can lead to small (but significant) errors in the bulk lattice energy.

Solution: It is fairly common when calculating surface energies to use the bulk energy from a bulk relaxation in the above equation. However, because DFT often has some small numerical noise in the predictions from k-point convergence, this might lead to the wrong surface energy. Instead, two more careful schemes are either:

  1. Calculate the energy of a bulk structure oriented to each slab to maximize cancellation of small numerical errors or

  2. Calculate the energy of multiple slabs at multiple thicknesses and extrapolate to zero thickness. The intercept will be the surface energy, and the slope will be a fitted bulk energy. A benefit of this approach is that it also forces us to check that we have a sufficiently thick slab for a well defined surface energy; if the fit is non-linear we need thicker slabs.

We’ll use the linear extrapolation method here as it’s more likely to work in future DFT studies if you use this code!

Step 1: Setup and Bulk Energy Reference

First, we’ll set up the calculation parameters and get the bulk energy reference:

Bulk energy reference:
  Total energy: -21.97 eV
  Number of atoms: 4
  Energy per atom: -5.492344 eV/atom

Step 2: Generate and Relax Slabs

Now we’ll loop through each facet, generating slabs at three different thicknesses:


============================================================
Calculating Ni(111) surface energy
============================================================

  Thickness: 4 layers
    Atoms: 5
    Energy: -26.15 eV

  Thickness: 6 layers
    Atoms: 7
    Energy: -37.14 eV

  Thickness: 8 layers
    Atoms: 9
    Energy: -48.12 eV

  Linear fit:
    Slope:     -5.492550 eV/atom (cf. bulk -5.492344)
    Intercept: 1.31 eV

  Surface energy:
    γ = 0.123009 eV/Ų = 1.97 J/m²

============================================================
Calculating Ni(100) surface energy
============================================================

  Thickness: 4 layers
    Atoms: 8
    Energy: -42.17 eV

  Thickness: 6 layers
    Atoms: 12
    Energy: -64.14 eV

  Thickness: 8 layers
    Atoms: 16
    Energy: -86.11 eV

  Linear fit:
    Slope:     -5.492716 eV/atom (cf. bulk -5.492344)
    Intercept: 1.77 eV

  Surface energy:
    γ = 0.144095 eV/Ų = 2.31 J/m²

============================================================
Calculating Ni(110) surface energy
============================================================

  Thickness: 4 layers
    Atoms: 10
    Energy: -52.38 eV

  Thickness: 6 layers
    Atoms: 14
    Energy: -74.35 eV

  Thickness: 8 layers
    Atoms: 18
    Energy: -96.32 eV

  Linear fit:
    Slope:     -5.492098 eV/atom (cf. bulk -5.492344)
    Intercept: 2.54 eV

  Surface energy:
    γ = 0.146040 eV/Ų = 2.34 J/m²

============================================================
Calculating Ni(211) surface energy
============================================================

  Thickness: 4 layers
    Atoms: 12
    Energy: -61.62 eV

  Thickness: 6 layers
    Atoms: 16
    Energy: -83.56 eV

  Thickness: 8 layers
    Atoms: 20
    Energy: -105.53 eV

  Linear fit:
    Slope:     -5.488851 eV/atom (cf. bulk -5.492344)
    Intercept: 4.26 eV

  Surface energy:
    γ = 0.141138 eV/Ų = 2.26 J/m²

Step 3: Visualize Linear Fits

Let’s visualize the linear extrapolation for all four facets:

<Figure size 1200x1000 with 4 Axes>

Step 4: Compare with Literature

Finally, let’s compare our calculated surface energies with DFT literature values:


======================================================================
Comparison with DFT Literature (Tran et al., 2016)
======================================================================
Ni(111)        1.97 J/m²  (Lit: 1.92, Δ=2.6%)
Ni(100)        2.31 J/m²  (Lit: 2.21, Δ=4.5%)
Ni(110)        2.34 J/m²  (Lit: 2.29, Δ=2.2%)
Ni(211)        2.26 J/m²  (Lit: 2.24, Δ=1.0%)

Explore on Your Own

  1. Thickness convergence: Add 10 and 12 layer calculations. Is the linear fit still valid?

  2. Constraint effects: Fix the bottom 2 layers during relaxation. How does this affect γ?

  3. Vacuum size: Vary min_vacuum_size from 8 to 15 Å. When does γ converge?

  4. High-index facets: Try (311) or (331) surfaces. Are they more or less stable?

  5. Alternative fitting: Use polynomial (degree 2) instead of linear fit. Does the intercept change?


Part 3: Wulff Construction

Introduction

The Wulff construction predicts the equilibrium shape of a crystalline particle by minimizing total surface energy. This determines the morphology of supported catalyst nanoparticles.

Theory

The Wulff theorem states that at equilibrium, the distance from the particle center to a facet is proportional to its surface energy:

hiγi=constant\frac{h_i}{\gamma_i} = \text{constant}

Facets with lower surface energy have larger areas in the equilibrium shape.

Step 1: Prepare Surface Energies

We’ll use the surface energies calculated in Part 2 to construct the Wulff shape:


Constructing Wulff Shape
==================================================
Using 4 facets:
  (1, 1, 1): 1.97 J/m²
  (1, 0, 0): 2.31 J/m²
  (1, 1, 0): 2.34 J/m²
  (2, 1, 1): 2.26 J/m²

Step 2: Generate Wulff Construction

Now we create the Wulff shape and analyze its properties:


Wulff Shape Properties:
  Volume:          47.07 ų
  Surface area:    67.54 Ų
  Effective radius: 2.24 Å
  Weighted γ:      2.09 J/m²

Facet Area Fractions:
  (1, 1, 1): 62.7%
  (2, 1, 1): 16.7%
  (1, 0, 0): 15.1%
  (1, 1, 0): 5.5%

Step 3: Visualize and Compare

Let’s visualize the Wulff shape and compare with literature:

<Figure size 800x800 with 1 Axes>

Comparison with Paper (Table 2):
  (1, 1, 1):   62.7% (Paper: 69.2%)
  (1, 0, 0):   15.1% (Paper: 21.1%)
  (1, 1, 0):    5.5% (Paper: 5.3%)
  (2, 1, 1):   16.7% (Paper: 4.4%)

Explore on Your Own

  1. Particle size effects: How would including edge/corner energies modify the shape?

  2. Anisotropic strain: Apply 2% compressive strain to the lattice. How does the shape change?

  3. Temperature effects: Surface energies decrease with T. Estimate γ(T) and recompute Wulff shape.

  4. Alloy nanoparticles: Replace some Ni with Cu or Au. How would segregation affect the shape?

  5. Support effects: Some facets interact more strongly with supports. Model this by reducing their γ.


Part 4: H Adsorption Energy with ZPE Correction

Introduction

Hydrogen adsorption is a fundamental step in many catalytic reactions (hydrogenation, dehydrogenation, etc.). We’ll calculate the binding energy with vibrational zero-point energy (ZPE) corrections.

Theory

The adsorption energy is:

Eads=E(slab+H)−E(slab)−12E(H2)E_{\text{ads}} = E(\text{slab+H}) - E(\text{slab}) - \frac{1}{2}E(\text{H}_2)

ZPE correction accounts for quantum vibrational effects:

EadsZPE=Eads+ZPE(H∗)−12ZPE(H2)E_{\text{ads}}^{\text{ZPE}} = E_{\text{ads}} + \text{ZPE}(\text{H}^*) - \frac{1}{2}\text{ZPE}(\text{H}_2)

The ZPE correction is calculated by analyzing the vibrational modes of the molecule/adsorbate.

Step 1: Setup and Relax Clean Slab

First, we create the Ni(111) surface and relax it:

   Created 96 atom slab
   Calculators initialized (ML + D3)

Step 2: Relax Clean Slab

Relax the bare Ni(111) surface as our reference:

WARNING:root:The UMA fast path (merge_mole + compile) is only available for fixed composition, task, charge, and spin. This is optimized for MD applications. Falling back to a less optimized version for subsequent evaluations. Reason: 'Dataset differs: ['omat'] vs ['oc20']'.
Use inference_settings='batch' for heterogeneous batched evaluations.

1. Relaxing clean Ni(111) slab...
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/torch_dftd/torch_dftd3_calculator.py:98: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at /__w/pytorch/pytorch/torch/csrc/utils/tensor_new.cpp:252.)
  cell: Optional[Tensor] = torch.tensor(
   E(clean): -487.48 eV (ML: -450.74, D3: -36.74)
   ✓ Clean slab relaxed and saved

Step 3: Generate H Adsorption Sites

Use heuristic placement to generate multiple candidate H adsorption sites:


2. Generating 5 H adsorption sites...
   Generated 5 initial configurations
   These include fcc, hcp, bridge, and top sites
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)

Step 4: Relax All H Configurations

Relax each configuration and identify the most stable site:


3. Relaxing all H adsorption configurations...
   Config 1: -491.56 eV (ML: -454.74, D3: -36.82)
   Config 2: -491.56 eV (ML: -454.74, D3: -36.82)
   Config 3: -491.54 eV (ML: -454.72, D3: -36.82)
   Config 4: -491.56 eV (ML: -454.74, D3: -36.82)
   Config 5: -491.54 eV (ML: -454.72, D3: -36.82)

   ✓ Best site: Config 4, E = -491.56 eV
   Energy spread: 0.02 eV
   This spread indicates the importance of testing multiple sites!

Step 5: Calculate H₂ Reference Energy

We need the H₂ molecule energy as a reference:


4. Calculating H₂ reference energy...
   E(H₂): -6.96 eV (ML: -6.96, D3: -0.00)

Step 6: Compute Adsorption Energy

Calculate the adsorption energy using the formula: E_ads = E(slab+H) - E(slab) - 0.5×E(H₂)


4. Computing Adsorption Energy:
   E_ads = E(slab+H) - E(slab) - 0.5×E(H₂)

   Without D3: -0.52 eV
   With D3:    -0.60 eV
   D3 effect:  -0.08 eV

   → D3 corrections are negligible for H* (small, covalent bonding)

Step 7: Zero-Point Energy (ZPE) Corrections

Calculate vibrational frequencies to get ZPE corrections:


6. Computing ZPE corrections...
   This accounts for quantum vibrational effects
   ZPE(H*):  0.18+0.00j eV
   ZPE(H₂):  0.41+0.00j eV
   E_ads(ZPE): -0.62-0.00j eV

   Creating animations of vibrational modes...
<IPython.core.display.Image object>
0
<Figure size 640x480 with 1 Axes>

Step 8: Visualize and Compare Results

Visualize the best configuration and compare with literature:


7. Visualizing best H* configuration...
Loading...

============================================================
Comparison with Literature:
============================================================
Table 4 (DFT): -0.60 eV (Ni(111), ref H₂)
This work:     -0.62-0.00j eV
Difference:    0.02 eV

Explore on Your Own

  1. Site preference: Identify which site (fcc, hcp, bridge, top) the H prefers. Visualize with view(atoms, viewer='x3d').

  2. Coverage effects: Place 2 H atoms on the slab. How does binding change with separation?

  3. Different facets: Compare H adsorption on (100) and (110) surfaces. Which is strongest?

  4. Subsurface H: Place H below the surface layer. Is it stable?

  5. ZPE uncertainty: How sensitive is E_ads to the vibrational delta parameter (try 0.01, 0.03 Å)?


Part 5: Coverage-Dependent H Adsorption

Introduction

At higher coverages, adsorbate-adsorbate interactions become significant. We’ll study how H binding energy changes from dilute (1 atom) to saturated (full monolayer) coverage.

Theory

The differential adsorption energy at coverage θ is:

Eads(θ)=E(nH∗)−E(∗)−n⋅12E(H2)nE_{\text{ads}}(\theta) = \frac{E(n\text{H}^*) - E(*) - n \cdot \frac{1}{2}E(\text{H}_2)}{n}

For many systems, this varies linearly:

Eads(θ)=Eads(0)+βθE_{\text{ads}}(\theta) = E_{\text{ads}}(0) + \beta \theta

where β quantifies lateral interactions (repulsive if β > 0).

Step 1: Setup Slab and Calculators

Create a larger Ni(111) slab to accommodate multiple adsorbates:

   Created 96 atom slab
   ✓ Calculators initialized

Step 2: Calculate Reference Energies

Get reference energies for clean surface and H₂:


1. Relaxing clean slab...
   E(clean): -487.48 eV

2. Calculating H₂ reference...
   E(H₂): -6.96 eV

Step 3: Set Up Coverage Study

Define the coverages we’ll test (from dilute to nearly 1 ML):


3. Surface sites: 16 (4×4 Ni(111))

   Will test coverages: ['0.06 ML', '0.25 ML', '0.50 ML', '0.75 ML', '1.00 ML']
   This spans from dilute to nearly full monolayer

Step 4: Generate and Relax Configurations at Each Coverage

For each coverage, generate multiple configurations and find the lowest energy:


3. Coverage: 1 H (0.06 ML)
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)
   Generated 5 configurations
     Config 1: -491.54 eV
     Config 2: -491.54 eV
     Config 3: -491.56 eV
     Config 4: -491.54 eV
     Config 5: -491.54 eV
   → E_ads/H: -0.60 eV
   Visualizing configuration with 1 H atoms...

3. Coverage: 4 H (0.25 ML)
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)
   Generated 5 configurations
     Config 1: -503.74 eV
     Config 2: -503.59 eV
     Config 3: -503.77 eV
     Config 4: -503.77 eV
     Config 5: -503.63 eV
   → E_ads/H: -0.59 eV
   Visualizing configuration with 4 H atoms...

3. Coverage: 8 H (0.50 ML)
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)
   Generated 5 configurations
     Config 1: -519.42 eV
     Config 2: -519.72 eV
     Config 3: -519.90 eV
     Config 4: -519.57 eV
     Config 5: -519.71 eV
   → E_ads/H: -0.57 eV
   Visualizing configuration with 8 H atoms...

3. Coverage: 12 H (0.75 ML)
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)
   Generated 5 configurations
     Config 1: -535.18 eV
     Config 2: -533.66 eV
     Config 3: -534.72 eV
     Config 4: -535.20 eV
     Config 5: -535.60 eV
   → E_ads/H: -0.53 eV
   Visualizing configuration with 12 H atoms...

3. Coverage: 16 H (1.00 ML)
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)
   Generated 5 configurations
     Config 1: -549.08 eV
     Config 2: -551.93 eV
     Config 3: -551.93 eV
     Config 4: -549.65 eV
     Config 5: -550.32 eV
   → E_ads/H: -0.55 eV
   Visualizing configuration with 16 H atoms...

✓ Completed coverage study: 5 data points

Step 5: Perform Linear Fit

Fit E_ads vs coverage to extract the slope (lateral interaction strength):


4. Performing linear fit to coverage dependence...

============================================================
Linear Fit: E_ads = -0.57 + 0.03θ (eV)
Slope: 3.0 kJ/mol per ML
Paper: 8.7 kJ/mol per ML
============================================================

Step 6: Visualize Coverage Dependence

Create a plot showing how adsorption energy changes with coverage:


5. Plotting coverage dependence...
<Figure size 800x600 with 1 Axes>

✓ Coverage dependence analysis complete!

Explore on Your Own

  1. Non-linear behavior: Use polynomial (degree 2) fit. Is there curvature at high coverage?

  2. Temperature effects: Estimate configurational entropy at each coverage. How does this affect free energy?

  3. Pattern formation: Visualize the lowest-energy configuration at 0.5 ML. Are H atoms ordered?

  4. Other adsorbates: Repeat for O or N. How do lateral interactions compare?

  5. Phase diagrams: At what coverage do you expect phase separation (islands vs uniform)?


Part 6: CO Formation/Dissociation Thermochemistry and Barrier

Introduction

CO dissociation (CO* → C* + O*) is the rate-limiting step in many catalytic processes (Fischer-Tropsch, CO oxidation, etc.). We’ll calculate the reaction energy for C* + O* → CO* and the activation barriers in both directions using the nudged elastic band (NEB) method.

Theory

Step 1: Setup Slab and Calculators

Initialize the Ni(111) surface and calculators:

   Created 96 atom slab
   ✓ Calculators initialized

Step 2: Generate and Relax Final State (CO*)

Find the most stable CO adsorption configuration (this is the product of C+O recombination):


1. Final State: CO* on Ni(111)
   Generating CO adsorption configurations...
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)
   Generated 5 configurations
     Config 1: E_total = -504.00 eV (RPBE: -467.01, D3: -36.99)
     Config 2: E_total = -503.99 eV (RPBE: -467.01, D3: -36.99)
     Config 3: E_total = -503.62 eV (RPBE: -466.61, D3: -37.01)
     Config 4: E_total = -504.00 eV (RPBE: -467.01, D3: -36.99)
     Config 5: E_total = -504.00 eV (RPBE: -467.01, D3: -36.99)

   → Best CO* (Config 5):
      RPBE:  -467.01 eV
      D3:    -36.99 eV
      Total: -504.00 eV
   ✓ Best CO* structure saved

   Visualizing best CO* structure...
Loading...

Step 3: Generate and Relax Initial State (C* + O*)

Find the most stable configuration for dissociated C and O (reactants):


2. Initial State: C* + O* on Ni(111)
   Generating C+O configurations...
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)
   Generated 5 configurations
     Config 1: E_total = -502.88 eV (RPBE: -465.82, D3: -37.06, C-O dist: 4.33 Å)
     Config 2: E_total = -502.92 eV (RPBE: -465.85, D3: -37.07, C-O dist: 3.82 Å)
     Config 3: E_total = -502.92 eV (RPBE: -465.85, D3: -37.07, C-O dist: 3.83 Å)
     Config 4: E_total = -502.94 eV (RPBE: -465.87, D3: -37.07, C-O dist: 5.17 Å)
     Config 5: ⚠ REJECTED - C and O formed CO molecule (d = 1.20 Å)

   → Best C*+O* (Config 4):
      RPBE:  -465.87 eV
      D3:    -37.07 eV
      Total: -502.94 eV
   ✓ Best C*+O* structure saved

   Visualizing best C*+O* structure...
Loading...

Step 3b: Calculate C* and O* Energies Separately

Another strategy to calculate the initial energies for *C and *O at very low coverage (without interactions between the two reactants) is to do two separate relaxations.


   Clean slab: E_total = -487.48 eV (RPBE: -450.74, D3: -36.74)

2b. Separate C* and O* Energies:
    Calculating energies in separate unit cells to avoid interactions

   Generating C* configurations...
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/fairchem/data/oc/core/adsorbate.py:79: UserWarning: Loading data from a pickle file. Pickle files can execute arbitrary code and should only be loaded from trusted sources. Consider migrating to a safer format such as Parquet, CSV, or JSON.
  adsorbate_db = safe_pickle_load(fp)
     Config 1: E_total = -495.71 eV (RPBE: -458.77, D3: -36.94)
     Config 2: E_total = -494.17 eV (RPBE: -457.28, D3: -36.89)
     Config 3: E_total = -495.70 eV (RPBE: -458.76, D3: -36.94)
     Config 4: E_total = -495.75 eV (RPBE: -458.81, D3: -36.95)
     Config 5: E_total = -495.75 eV (RPBE: -458.80, D3: -36.95)

   → Best C* (Config 5):
      RPBE:  -458.80 eV
      D3:    -36.95 eV
      Total: -495.75 eV

   Visualizing best C* structure...

   Generating O* configurations...
     Config 1: E_total = -494.58 eV (RPBE: -457.74, D3: -36.85)
     Config 2: E_total = -494.58 eV (RPBE: -457.74, D3: -36.84)
     Config 3: E_total = -494.58 eV (RPBE: -457.74, D3: -36.85)
     Config 4: E_total = -494.69 eV (RPBE: -457.84, D3: -36.85)
     Config 5: E_total = -494.69 eV (RPBE: -457.84, D3: -36.85)

   → Best O* (Config 5):
      RPBE:  -457.84 eV
      D3:    -36.85 eV
      Total: -494.69 eV

   Visualizing best O* structure...

   Combined C* + O* (separate calculations):
      RPBE:  -916.65 eV
      D3:    -73.80 eV
      Total: -990.45 eV

   Comparison:
      C*+O* (same cell):  -15.46 eV
      C* + O* (separate): -15.49 eV
      Difference:         0.03 eV
   ✓ Separate C* and O* energies calculated

Step 4: Calculate Reaction Energy with ZPE

Compute the thermochemistry for C* + O* → CO* with ZPE corrections:


3. Reaction Energy (C* + O* → CO*):
   ============================================================

   Electronic Energies:
   Initial (C*+O*): RPBE = -465.87 eV, D3 = -37.07 eV, Total = -502.94 eV
   Final (CO*):     RPBE = -467.01 eV, D3 = -36.99 eV, Total = -504.00 eV

   Reaction Energies (without ZPE):
   ΔE(RPBE only):     -1.14 eV = -110.1 kJ/mol
   ΔE(D3 contrib):    0.08 eV = 8.1 kJ/mol
   ΔE(RPBE+D3):       -1.06 eV = -102.1 kJ/mol

   Computing ZPE for CO*...
   ZPE(CO*): 0.18+0.00j eV (182.8+0.0j meV)

   Computing ZPE for C* and O*...
   ZPE(C*+O*): 0.18+0.00j eV (179.1+0.0j meV)

   Reaction Energy (with ZPE):
   ΔE(electronic):    -1.06 eV = -102.1 kJ/mol
   ΔZPE:              0.00+0.00j eV = 0.4+0.0j kJ/mol (3.7+0.0j meV)
   ΔE(total):         -1.05+0.00j eV = -101.7+0.0j kJ/mol

   Summary:
   Without D3, without ZPE: -1.14 eV = -110.1 kJ/mol
   With D3, without ZPE:    -1.06 eV = -102.1 kJ/mol
   With D3, with ZPE:       -1.05+0.00j eV = -101.7+0.0j kJ/mol

   ============================================================

   Comparison with Paper (Table 5):
   Paper (DFT-D3): -142.7 kJ/mol = -1.48 eV
   This work:      -101.7+0.0j kJ/mol = -1.05+0.00j eV
   Difference:     0.43 eV

   ✓ Reaction is exothermic (C+O recombination favorable)

Step 5: Calculate CO Adsorption Energy (Bonus)

Calculate how strongly CO binds to the surface:


4. CO Adsorption Energy ( CO(g) + * → CO*):
   This helps us understand CO binding strength
   CO(g):       E_total = -14.41 eV (RPBE: -14.40, D3: -0.01)
   ZPE(CO(g)):  0.13+0.00j eV
   ZPE(CO*):    0.18+0.00j eV (from Step 4 calculation)

   Electronic Energy Breakdown:
   ΔE(RPBE only) = -1.87 eV
   ΔE(D3 contrib) = -0.24 eV
   ΔE(RPBE+D3) = -2.10 eV

   ZPE Contribution:
   ΔZPE = 0.05-0.00j eV

   Total Adsorption Energy:
   ΔE(total) = -2.05-0.00j eV = -197.9-0.0j kJ/mol

   Summary:
   E_ads(CO) without ZPE = 2.10 eV = 202.9 kJ/mol
   E_ads(CO) with ZPE    = 2.05+0.00j eV = 197.9+0.0j kJ/mol
   → CO binds 2.05 eV stronger than H (3.4x)

Step 6: Find guesses for nearby initial and final states for the reaction

Now that we have an estimate on the reaction energy from the best possible initial and final states, we want to find a transition state (barrier) for this reaction. There are MANY possible ways that we could do this. In this case, we’ll start with the *CO final state and then try and find a nearby local minimal of *C and *O, by fixing the C-O bond distance and finding a nearby local minima. Note that this approach required some insight into what the transition state might look like, and could be considerably more complicated for a reaction that did not involve breaking a single bond.


Finding Transition State Initial and Final States
   Creating initial guess with stretched C-O bond...
   Starting from CO* and stretching the C-O bond...
       Step     Time          Energy          fmax
LBFGS:    0 03:29:24     -466.697795        0.911731
LBFGS:    1 03:29:24     -461.347122        4.195111
LBFGS:    2 03:29:25     -461.409632        3.845151
LBFGS:    3 03:29:25     -461.718375        1.005805
LBFGS:    4 03:29:25     -461.803818        0.979189
LBFGS:    5 03:29:26     -461.822558        1.092793
LBFGS:    6 03:29:26     -461.804546        0.916163
LBFGS:    7 03:29:26     -461.888136        0.393819
LBFGS:    8 03:29:26     -461.894432        0.390212
LBFGS:    9 03:29:27     -461.916659        0.468708
LBFGS:   10 03:29:27     -461.941000        0.657743
LBFGS:   11 03:29:27     -461.994402        1.089027
LBFGS:   12 03:29:27     -462.062150        1.843586
LBFGS:   13 03:29:28     -462.123162        2.153341
LBFGS:   14 03:29:28     -462.146666        2.183362
LBFGS:   15 03:29:28     -462.185014        2.137024
LBFGS:   16 03:29:29     -462.343869        2.026956
LBFGS:   17 03:29:29     -462.622800        1.021596
LBFGS:   18 03:29:29     -462.661460        1.372795
LBFGS:   19 03:29:30     -462.737689        0.696784
LBFGS:   20 03:29:30     -462.761702        0.649296
LBFGS:   21 03:29:30     -462.823343        0.595303
LBFGS:   22 03:29:30     -462.852064        0.540117
LBFGS:   23 03:29:31     -462.871827        0.462896
LBFGS:   24 03:29:31     -462.876222        0.315162
LBFGS:   25 03:29:31     -462.897611        0.287617
LBFGS:   26 03:29:32     -462.916962        0.412021
LBFGS:   27 03:29:32     -462.945997        0.355988
LBFGS:   28 03:29:32     -462.947379        0.410820
LBFGS:   29 03:29:32     -462.973306        0.428221
LBFGS:   30 03:29:33     -462.987486        0.406741
LBFGS:   31 03:29:33     -463.008802        0.359683
LBFGS:   32 03:29:33     -463.077661        0.379701
LBFGS:   33 03:29:34     -463.087847        0.696348
LBFGS:   34 03:29:34     -463.109461        0.503353
LBFGS:   35 03:29:34     -463.130899        0.310970
LBFGS:   36 03:29:34     -463.135973        0.286679
       Step     Time          Energy          fmax
LBFGS:    0 03:29:35     -463.132099        1.326792
LBFGS:    1 03:29:35     -463.174762        1.427305
LBFGS:    2 03:29:35     -463.698245        4.493892
LBFGS:    3 03:29:36     -463.172526        1.426697
LBFGS:    4 03:29:36     -462.554555        3.221327
LBFGS:    5 03:29:36     -463.157462        1.368649
LBFGS:    6 03:29:37     -463.521670        5.322042
LBFGS:    7 03:29:37     -463.552627        9.882803
LBFGS:    8 03:29:37     -463.548673        7.013573
LBFGS:    9 03:29:37     -463.521227        8.255435
LBFGS:   10 03:29:38     -463.563171        6.614626
LBFGS:   11 03:29:38     -463.579224        6.275954
LBFGS:   12 03:29:38     -463.697500        5.009525
LBFGS:   13 03:29:39     -464.025790        4.404651
LBFGS:   14 03:29:39     -464.845521        5.105271
LBFGS:   15 03:29:39     -465.536810        3.850613
LBFGS:   16 03:29:39     -465.716033        2.501393
LBFGS:   17 03:29:40     -465.844673        1.544333
LBFGS:   18 03:29:40     -465.936607        1.568834
LBFGS:   19 03:29:40     -466.090333        1.208403
LBFGS:   20 03:29:41     -466.145345        1.146401
LBFGS:   21 03:29:41     -466.222264        1.325053
LBFGS:   22 03:29:41     -466.273948        0.935494
LBFGS:   23 03:29:41     -466.297090        0.942901
LBFGS:   24 03:29:42     -466.346855        0.985637
LBFGS:   25 03:29:42     -466.395692        1.068503
LBFGS:   26 03:29:42     -466.432780        1.007919
LBFGS:   27 03:29:43     -466.476059        0.932413
LBFGS:   28 03:29:43     -466.536987        1.064520
LBFGS:   29 03:29:43     -466.563992        2.212408
LBFGS:   30 03:29:43     -466.643199        0.723295
LBFGS:   31 03:29:44     -466.698550        0.743891
LBFGS:   32 03:29:44     -466.756976        0.847641
LBFGS:   33 03:29:44     -466.780567        0.514354
LBFGS:   34 03:29:45     -466.839969        0.448960
LBFGS:   35 03:29:45     -466.875875        0.500736
LBFGS:   36 03:29:45     -466.893182        0.619307
LBFGS:   37 03:29:45     -466.911551        0.368145
LBFGS:   38 03:29:46     -466.927022        0.397692
LBFGS:   39 03:29:46     -466.941120        0.467548
LBFGS:   40 03:29:46     -466.953191        0.481780
LBFGS:   41 03:29:47     -466.965303        0.425475
LBFGS:   42 03:29:47     -466.975811        0.248842
LBFGS:   43 03:29:47     -466.982079        0.147937
LBFGS:   44 03:29:47     -466.986094        0.166003
LBFGS:   45 03:29:48     -466.990446        0.201139
LBFGS:   46 03:29:48     -466.994942        0.194765
LBFGS:   47 03:29:48     -466.998440        0.116046
LBFGS:   48 03:29:49     -467.000520        0.108542
LBFGS:   49 03:29:49     -467.001808        0.103583
LBFGS:   50 03:29:49     -467.003169        0.081399
LBFGS:   51 03:29:50     -467.004811        0.120919
LBFGS:   52 03:29:50     -467.006007        0.075829
LBFGS:   53 03:29:50     -467.006688        0.073748
LBFGS:   54 03:29:50     -467.007337        0.072122
LBFGS:   55 03:29:51     -467.008162        0.071938
LBFGS:   56 03:29:51     -467.008963        0.077905
LBFGS:   57 03:29:51     -467.009474        0.056302
LBFGS:   58 03:29:52     -467.009761        0.055220
LBFGS:   59 03:29:52     -467.010024        0.040818
LBFGS:   60 03:29:52     -467.010356        0.046086
LBFGS:   61 03:29:52     -467.010595        0.044210
LBFGS:   62 03:29:53     -467.010701        0.028210
LBFGS:   63 03:29:53     -467.010755        0.024697
LBFGS:   64 03:29:53     -467.010822        0.033717
LBFGS:   65 03:29:53     -467.010899        0.037665
LBFGS:   66 03:29:54     -467.010955        0.031273
LBFGS:   67 03:29:54     -467.010985        0.020416
LBFGS:   68 03:29:54     -467.011005        0.014607
LBFGS:   69 03:29:55     -467.011027        0.014688
LBFGS:   70 03:29:55     -467.011054        0.014552
LBFGS:   71 03:29:55     -467.011074        0.012774
LBFGS:   72 03:29:56     -467.011086        0.010469
LBFGS:   73 03:29:56     -467.011094        0.011424
LBFGS:   74 03:29:56     -467.011109        0.015105
LBFGS:   75 03:29:56     -467.011121        0.012347
LBFGS:   76 03:29:57     -467.011130        0.005711
np.True_

Step 7: Run NEB to Find Activation Barrier

Use the nudged elastic band method to find the minimum energy path:


7. NEB Barrier Calculation (C* + O* → CO*)
   Setting up 7-image NEB chain with TS guess in middle...
   Reaction: C* + O* (initial) → TS → CO* (final)

   Interpolating images...
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/ase/mep/neb.py:329: UserWarning: The default method has changed from 'aseneb' to 'improvedtangent'. The 'aseneb' method is an unpublished, custom implementation that is not recommended as it frequently results in very poor bands. Please explicitly set method='improvedtangent' to silence this warning, or set method='aseneb' if you strictly require the old behavior (results may vary). See: https://gitlab.com/ase/ase/-/merge_requests/3952
  warnings.warn(
   Optimizing NEB path (this may take a while)...

   ✓ NEB converged!

   Forward barrier (C*+O* → CO*): 0.00 eV = 0.2 kJ/mol
   Reverse barrier (CO* → C*+O*): 0.00 eV = 0.0 kJ/mol

   Paper (Table 5): 153 kJ/mol = 1.59 eV 
   Difference: 1.59 eV

Step 8: Visualize NEB Path and Key Structures

Create plots showing the reaction pathway:


   Creating NEB visualization...
/home/runner/work/_tool/Python/3.12.15/x64/lib/python3.12/site-packages/matplotlib/cbook.py:1408: ComplexWarning: Casting complex values to real discards the imaginary part
  return np.asanyarray(x, float)
<Figure size 1000x600 with 1 Axes>

   Creating NEB path animation...
   → Saved as neb_path.gif

   Visualizing initial state (C* + O*)...

   Visualizing transition state...

   Visualizing final state (CO*)...

✓ NEB analysis complete!
<Figure size 640x480 with 1 Axes>

Explore on Your Own

  1. Image convergence: Run with 7 or 9 images. Does the barrier change?

  2. Spring constant: Modify the NEB spring constant. How does this affect convergence?

  3. Alternative paths: Try different initial CO/final C+O configurations. Are there multiple pathways?

  4. Reverse barrier: Calculate E_a(reverse) = E_a(forward) - ΔE. Check Brønsted-Evans-Polanyi relationship.

  5. Diffusion barriers: Compute NEB for C or O diffusion on the surface. How do they compare?


Summary and Best Practices

Key Takeaways

  1. ML Potentials: uma-s-1p2p1 provides ~1000× speedup over DFT with reasonable accuracy

  2. Bulk optimization: Always use the ML-optimized lattice constant for consistency

  3. Surface energies: Linear extrapolation eliminates finite-size effects

  4. Adsorption: Test multiple sites; lowest energy may not be intuitive

  5. Coverage: Lateral interactions become significant above ~0.3 ML

  6. Barriers: NEB requires careful setup but yields full reaction pathway

  1. Optimize Bulk - Determine equilibrium lattice constant

  2. Calculate Surface Energies - Identify stable facets

  3. Wulff Construction - Predict nanoparticle morphology

  4. Low-Coverage Adsorption - Find binding sites and energies

  5. Coverage Study (if coverage-dependent effects are important) - Determine lateral interactions

  6. Reaction Barriers - Calculate activation energies using NEB

  7. Microkinetic Modeling - Predict overall catalytic performance

Accuracy Considerations

PropertyTypical ErrorWhen Critical
Lattice constants1-2%Strain effects, alloys
Surface energies10-20%Nanoparticle shapes
Adsorption energies0.1-0.3 eVThermochemistry
Barriers0.2-0.5 eVKinetics, selectivity

Rule of thumb: Use ML for screening → DFT for validation → Experiment for verification

Further Reading


Appendix: Troubleshooting

Common Issues

Problem: Convergence failures

Problem: NEB fails to find transition state

Problem: Unexpected adsorption energies

Problem: Out of memory

Performance Tips

  1. Use batching: Relax multiple configurations in parallel

  2. Start with DEBUG_MAX_STEPS=50: Get quick results, refine later

  3. Cache bulk energies: Don’t recalculate reference systems

  4. Trajectory analysis: Monitor optimization progress with ASE GUI


Caveats and Pitfalls

1. Task Selection: OMAT vs OC20

Critical choice: Which task_name to use?

Impact: Using wrong task can lead to 0.1-0.3 eV errors in adsorption energies!

2. D3 Dispersion Corrections

Multiple decisions required:

  1. Whether to use D3 at all?

    • Small adsorbates (H, O, N): D3 effect ~0.01-0.05 eV (often negligible)

    • Large molecules (CO, CO₂, aromatics): D3 effect ~0.1-0.3 eV (important!)

    • Physisorption: D3 critical (can change binding from repulsive to attractive)

    • RPBE was originally fit for chemisorption energies without D3 corrections, so adding D3 corrections may actually cause small adsorbates to overbind. However, it probably would be important for larger molecules. It’s relatively uncommon to see RPBE+D3 as a choice in the catalysis literature (compared to PBE+D3, or RPBE, or BEEF-vdW).

  2. Which DFT functional for D3?

    • This tutorial uses method="PBE" consistently for the D3 correction. This is often implied when papers say they use a D3 correction, but the results can be different if use the RPBE parameterizations.

    • Original paper used PBE for bulk/surfaces, RPBE for adsorption. It’s not specified what D3 parameterization they used, but it’s likely PBE.

  3. When to apply D3?

    • End-point correction (used here): Fast, run ML optimization then add D3 energy

    • During optimization: Slower but more accurate geometries

    • Impact: Usually <0.05 eV difference, but can be larger for weak interactions

3. Coverage Dependence Challenges

Non-linearity at high coverage:

Low coverage limit:

4. Periodic Boundary Conditions

UMa requires PBC=True in all directions!

atoms.set_pbc([True, True, True])  # Always required

5. Gas Phase Reference Energies

Tricky cases:

Best practice: Always use stable molecules as references (H₂, not H; H₂O, not OH)

6. Spin Polarization

Key limitation: OC20/UMa does not include spin!

7. Constraint Philosophy

Clean slabs (Part 2): No constraints (both surfaces relax)

Adsorbate slabs (Part 4-6): Bottom layers fixed

Fairchem helper functions: Automatically apply sensible constraints

8. Complex Surface Structures

This tutorial uses low-index facets (111, 100, 110, 211)

Real catalysts have:

9. Slab Thickness and Vacuum

Convergence tests critical but expensive:

10. NEB Convergence

Most computationally expensive part:

Tricks:

  1. Use dimer method to find better TS guess (as shown in Part 6)

  2. Start with coarse convergence (fmax=0.2), refine later

  3. Visualize the path - does it make chemical sense?

  4. Try different spring constants (0.1-1.0 eV/Å)

11. Lattice Constant Source

Consistency is key:

12. Adsorbate Placement

Multiple local minima:

For complex adsorbates:


References
  1. Kreitz, B., & others. (2021). Microkinetic Modeling of CO2 Desorption from Supported Multifaceted Ni Catalysts. 10.1021/acs.jpcc.0c09985
  2. Chanussot, L., Das, A., Goyal, S., Lavril, T., Shuaibi, M., Riviere, M., Tran, K., Heras-Domingo, J., Ho, C., Hu, W., Palizhati, A., Sriram, A., Wood, B., Yoon, J., Parikh, D., Zitnick, C. L., & Ulissi, Z. (2021). Open Catalyst 2020 (OC20) Dataset and Community Challenges. ACS Catalysis, 11(10), 6059–6072. 10.1021/acscatal.0c04525