Intro to adsorption energies#
To introduce OCP we start with using it to calculate adsorption energies for a simple, atomic adsorbate where we specify the site we want to the adsorption energy for. Conceptually, you do this like you would do it with density functional theory. You create a slab model for the surface, place an adsorbate on it as an initial guess, run a relaxation to get the lowest energy geometry, and then compute the adsorption energy using reference states for the adsorbate.
You do have to be careful in the details though. Some OCP model/checkpoint combinations return a total energy like density functional theory would, but some return an “adsorption energy” directly. You have to know which one you are using. In this example, the model we use returns an “adsorption energy”.
Intro to Adsorption energies#
Adsorption energies are always a reaction energy (an adsorbed species relative to some implied combination of reactants). There are many common schemes in the catalysis literature.
For example, you may want the adsorption energy of oxygen, and you might compute that from this reaction:
1/2 O2 + slab -> slab-O
DFT has known errors with the energy of a gas-phase O2 molecule, so it’s more common to compute this energy relative to a linear combination of H2O and H2. The suggested reference scheme for consistency with OC20 is a reaction
x CO + (x + y/2 - z) H2 + (z-x) H2O + w/2 N2 + * -> CxHyOzNw*
Here, x=y=w=0, z=1, so the reaction ends up as
-H2 + H2O + * -> O*
or alternatively,
H2O + * -> O* + H2
It is possible through thermodynamic cycles to compute other reactions. If we can look up rH1 below and compute rH2
H2 + 1/2 O2 -> H2O re1 = -3.03 eV, from exp
H2O + * -> O* + H2 re2 # Get from UMA
Then, the adsorption energy for
1/2O2 + * -> O*
is just re1 + re2.
Based on https://atct.anl.gov/Thermochemical%20Data/version%201.118/species/?species_number=986, the formation energy of water is about -3.03 eV at standard state experimentally. You could also compute this using DFT, but you would probably get the wrong answer for this.
The first step is getting a checkpoint for the model we want to use. UMA is currently the state-of-the-art model and will provide total energy estimates at the RPBE level of theory if you use the “OC20” task.
Need to install fairchem-core or get UMA access or getting permissions/401 errors?
Install the necessary packages using pip, uv etc
Get access to any necessary huggingface gated models
Get and login to your Huggingface account
Request access to https://huggingface.co/facebook/UMA
Create a Huggingface token at https://huggingface.co/settings/tokens/ with the permission “Permissions: Read access to contents of all public gated repos you can access”
Add the token as an environment variable using
huggingface-cli login
or by setting the HF_TOKEN environment variable.
If you find your kernel is crashing, it probably means you have exceeded the allowed amount of memory. This checkpoint works fine in this example, but it may crash your kernel if you use it in the NRR example.
This next cell will automatically download the checkpoint from huggingface and load it.
from __future__ import annotations
from fairchem.core import FAIRChemCalculator, pretrained_mlip
predictor = pretrained_mlip.get_predict_unit("uma-s-1p1")
calc = FAIRChemCalculator(predictor, task_name="oc20")
WARNING:root:device was not explicitly set, using device='cuda'.
/home/runner/work/_tool/Python/3.12.12/x64/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:2249: UnsupportedFieldAttributeWarning: The 'repr' attribute with value False was provided to the `Field()` function, which has no effect in the context it was used. 'repr' is field-specific metadata, and can only be attached to a model field using `Annotated` metadata or by assignment. This may have happened because an `Annotated` type alias using the `type` statement was used, or if the `Field()` function was attached to a single member of a union type.
warnings.warn(
/home/runner/work/_tool/Python/3.12.12/x64/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:2249: UnsupportedFieldAttributeWarning: The 'frozen' attribute with value True was provided to the `Field()` function, which has no effect in the context it was used. 'frozen' is field-specific metadata, and can only be attached to a model field using `Annotated` metadata or by assignment. This may have happened because an `Annotated` type alias using the `type` statement was used, or if the `Field()` function was attached to a single member of a union type.
warnings.warn(
Next we can build a slab with an adsorbate on it. Here we use the ASE module to build a Pt slab. We use the experimental lattice constant that is the default. This can introduce some small errors with DFT since the lattice constant can differ by a few percent, and it is common to use DFT lattice constants. In this example, we do not constrain any layers.
from ase.build import add_adsorbate, fcc111
from ase.optimize import BFGS
# reference energies from a linear combination of H2O/N2/CO/H2!
atomic_reference_energies = {
"H": -3.477,
"N": -8.083,
"O": -7.204,
"C": -7.282,
}
re1 = -3.03
slab = fcc111("Pt", size=(2, 2, 5), vacuum=20.0)
slab.pbc = True
adslab = slab.copy()
add_adsorbate(adslab, "O", height=1.2, position="fcc")
slab.set_calculator(calc)
opt = BFGS(slab)
opt.run(fmax=0.05, steps=100)
slab_e = slab.get_potential_energy()
adslab.set_calculator(calc)
opt = BFGS(adslab)
opt.run(fmax=0.05, steps=100)
adslab_e = adslab.get_potential_energy()
# Energy for ((H2O-H2) + * -> *O) + (H2 + 1/2O2 -> H2) leads to 1/2O2 + * -> *O!
adslab_e - slab_e - atomic_reference_energies["O"] + re1
/tmp/ipykernel_3148/3752951811.py:17: FutureWarning: Please use atoms.calc = calc
slab.set_calculator(calc)
Step Time Energy fmax
BFGS: 0 17:35:21 -104.695193 0.710159
BFGS: 1 17:35:22 -104.753188 0.608163
BFGS: 2 17:35:22 -104.906056 0.369446
BFGS: 3 17:35:22 -104.938752 0.439790
BFGS: 4 17:35:22 -105.016134 0.464062
BFGS: 5 17:35:22 -105.076558 0.356164
BFGS: 6 17:35:22 -105.112617 0.189538
BFGS: 7 17:35:22 -105.126757 0.045471
Step Time Energy fmax
BFGS: 0 17:35:22 -110.048789 1.757515
BFGS: 1 17:35:22 -110.230724 0.986419
/tmp/ipykernel_3148/3752951811.py:22: FutureWarning: Please use atoms.calc = calc
adslab.set_calculator(calc)
BFGS: 2 17:35:22 -110.379374 0.731506
BFGS: 3 17:35:22 -110.430309 0.807316
BFGS: 4 17:35:23 -110.543692 0.691969
BFGS: 5 17:35:23 -110.617484 0.492714
BFGS: 6 17:35:23 -110.673468 0.666979
BFGS: 7 17:35:23 -110.721683 0.710470
BFGS: 8 17:35:23 -110.756904 0.443785
BFGS: 9 17:35:23 -110.769295 0.214520
BFGS: 10 17:35:23 -110.772451 0.091720
BFGS: 11 17:35:23 -110.772965 0.062476
BFGS: 12 17:35:23 -110.773259 0.046194
-1.4725024897882188
It is good practice to look at your geometries to make sure they are what you expect.
import matplotlib.pyplot as plt
from ase.visualize.plot import plot_atoms
fig, axs = plt.subplots(1, 2)
plot_atoms(slab, axs[0])
plot_atoms(slab, axs[1], rotation=("-90x"))
axs[0].set_axis_off()
axs[1].set_axis_off()

import matplotlib.pyplot as plt
from ase.visualize.plot import plot_atoms
fig, axs = plt.subplots(1, 2)
plot_atoms(adslab, axs[0])
plot_atoms(adslab, axs[1], rotation=("-90x"))
axs[0].set_axis_off()
axs[1].set_axis_off()

How did we do? We need a reference point. In the paper below, there is an atomic adsorption energy for O on Pt(111) of about -4.264 eV. This is for the reaction O + * -> O*. To convert this to the dissociative adsorption energy, we have to add the reaction:
1/2 O2 -> O D = 2.58 eV (expt)
to get a comparable energy of about -1.68 eV. There is about ~0.2 eV difference (we predicted -1.47 eV above, and the reference comparison is -1.68 eV) to account for. The biggest difference is likely due to the differences in exchange-correlation functional. The reference data used the PBE functional, and eSCN was trained on RPBE data. To additional places where there are differences include:
Difference in lattice constant
The reference energy used for the experiment references. These can differ by up to 0.5 eV from comparable DFT calculations.
How many layers are relaxed in the calculation
Some of these differences tend to be systematic, and you can calibrate and correct these, especially if you can augment these with your own DFT calculations.
See convergence study for some additional studies of factors that influence this number.
Exercises#
Explore the effect of the lattice constant on the adsorption energy.
Try different sites, including the bridge and top sites. Compare the energies, and inspect the resulting geometries.
Trends in adsorption energies across metals.#
Xu, Z., & Kitchin, J. R. (2014). Probing the coverage dependence of site and adsorbate configurational correlations on (111) surfaces of late transition metals. J. Phys. Chem. C, 118(44), 25597–25602. http://dx.doi.org/10.1021/jp508805h
These are atomic adsorption energies:
O + * -> O*
We have to do some work to get comparable numbers from OCP
H2 + 1/2 O2 -> H2O re1 = -3.03 eV
H2O + * -> O* + H2 re2 # Get from UMA
O -> 1/2 O2 re3 = -2.58 eV
Then, the adsorption energy for
O + * -> O*
is just re1 + re2 + re3.
Here we just look at the fcc site on Pt. First, we get the data stored in the paper.
Next we get the structures and compute their energies. Some subtle points are that we have to account for stoichiometry, and normalize the adsorption energy by the number of oxygens.
First we get a reference energy from the paper (PBE, 0.25 ML O on Pt(111)).
import json
with open("energies.json") as f:
edata = json.load(f)
with open("structures.json") as f:
sdata = json.load(f)
edata["Pt"]["O"]["fcc"]["0.25"]
-4.263842000000002
Next, we load data from the SI to get the geometry to start from.
with open("structures.json") as f:
s = json.load(f)
sfcc = s["Pt"]["O"]["fcc"]["0.25"]
Next, we construct the atomic geometry, run the geometry optimization, and compute the energy.
re3 = -2.58 # O -> 1/2 O2 re3 = -2.58 eV
from ase import Atoms
adslab = Atoms(sfcc["symbols"], positions=sfcc["pos"], cell=sfcc["cell"], pbc=True)
# Grab just the metal surface atoms
slab = adslab[adslab.arrays["numbers"] == adslab.arrays["numbers"][0]]
adsorbates = adslab[~(adslab.arrays["numbers"] == adslab.arrays["numbers"][0])]
slab.set_calculator(calc)
opt = BFGS(slab)
opt.run(fmax=0.05, steps=100)
adslab.set_calculator(calc)
opt = BFGS(adslab)
opt.run(fmax=0.05, steps=100)
re2 = (
adslab.get_potential_energy()
- slab.get_potential_energy()
- sum([atomic_reference_energies[x] for x in adsorbates.get_chemical_symbols()])
)
nO = 0
for atom in adslab:
if atom.symbol == "O":
nO += 1
re2 += re1 + re3
print(re2 / nO)
Step Time Energy fmax
BFGS: 0 17:35:26 -82.862062 1.000262
BFGS: 1 17:35:26 -82.918113 0.752931
/tmp/ipykernel_3148/647904475.py:10: FutureWarning: Please use atoms.calc = calc
slab.set_calculator(calc)
BFGS: 2 17:35:26 -83.011695 0.335858
BFGS: 3 17:35:26 -83.016496 0.303119
BFGS: 4 17:35:26 -83.024499 0.224913
BFGS: 5 17:35:26 -83.030038 0.152533
BFGS: 6 17:35:26 -83.033360 0.088582
BFGS: 7 17:35:26 -83.034468 0.065764
BFGS: 8 17:35:26 -83.035206 0.068687
BFGS: 9 17:35:26 -83.035564 0.046741
Step Time Energy fmax
BFGS: 0 17:35:27 -88.770343 0.312890
BFGS: 1 17:35:27 -88.773632 0.271658
/tmp/ipykernel_3148/647904475.py:14: FutureWarning: Please use atoms.calc = calc
adslab.set_calculator(calc)
BFGS: 2 17:35:27 -88.784463 0.104369
BFGS: 3 17:35:27 -88.786013 0.101422
BFGS: 4 17:35:27 -88.788527 0.106427
BFGS: 5 17:35:27 -88.790484 0.089422
BFGS: 6 17:35:27 -88.791961 0.096088
BFGS: 7 17:35:27 -88.792655 0.099435
BFGS: 8 17:35:27 -88.793174 0.077723
BFGS: 9 17:35:27 -88.793675 0.036077
-4.164110995037242
Site correlations#
This cell reproduces a portion of a figure in the paper. We compare oxygen adsorption energies in the fcc and hcp sites across metals and coverages. These adsorption energies are highly correlated with each other because the adsorption sites are so similar.
At higher coverages, the agreement is not as good. This is likely because the model is extrapolating and needs to be fine-tuned.
import time
from tqdm import tqdm
t0 = time.time()
data = {"fcc": [], "hcp": []}
refdata = {"fcc": [], "hcp": []}
for metal in ["Cu", "Ag", "Pd", "Pt", "Rh", "Ir"]:
print(metal)
for site in ["fcc", "hcp"]:
for adsorbate in ["O"]:
for coverage in tqdm(["0.25"]):
entry = s[metal][adsorbate][site][coverage]
adslab = Atoms(
entry["symbols"],
positions=entry["pos"],
cell=entry["cell"],
pbc=True,
)
# Grab just the metal surface atoms
adsorbates = adslab[
~(adslab.arrays["numbers"] == adslab.arrays["numbers"][0])
]
slab = adslab[adslab.arrays["numbers"] == adslab.arrays["numbers"][0]]
slab.set_calculator(calc)
opt = BFGS(slab)
opt.run(fmax=0.05, steps=100)
adslab.set_calculator(calc)
opt = BFGS(adslab)
opt.run(fmax=0.05, steps=100)
re2 = (
adslab.get_potential_energy()
- slab.get_potential_energy()
- sum(
[
atomic_reference_energies[x]
for x in adsorbates.get_chemical_symbols()
]
)
)
nO = 0
for atom in adslab:
if atom.symbol == "O":
nO += 1
re2 += re1 + re3
data[site] += [re2 / nO]
refdata[site] += [edata[metal][adsorbate][site][coverage]]
f"Elapsed time = {time.time() - t0} seconds"
Cu
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:27 -48.910948 0.646351
BFGS: 1 17:35:28 -48.934031 0.539303
/tmp/ipykernel_3148/1356342052.py:33: FutureWarning: Please use atoms.calc = calc
slab.set_calculator(calc)
BFGS: 2 17:35:28 -49.000044 0.280104
BFGS: 3 17:35:28 -49.002374 0.251321
BFGS: 4 17:35:28 -49.008810 0.158917
BFGS: 5 17:35:28 -49.013986 0.134404
BFGS: 6 17:35:28 -49.016868 0.071532
BFGS: 7 17:35:28 -49.017633 0.051270
BFGS: 8 17:35:28 -49.018011 0.047563
Step Time Energy fmax
BFGS: 0 17:35:28 -55.204221 0.339047
BFGS: 1 17:35:28 -55.206662 0.267937
/tmp/ipykernel_3148/1356342052.py:37: FutureWarning: Please use atoms.calc = calc
adslab.set_calculator(calc)
BFGS: 2 17:35:28 -55.213711 0.159974
BFGS: 3 17:35:29 -55.215873 0.166172
BFGS: 4 17:35:29 -55.219671 0.116799
BFGS: 5 17:35:29 -55.221812 0.079429
BFGS: 6 17:35:29 -55.223359 0.075348
BFGS: 7 17:35:29 -55.224364 0.090256
BFGS: 8 17:35:29 -55.225515 0.098899
BFGS: 9 17:35:29 -55.226527 0.068622
BFGS: 10 17:35:29 -55.226977 0.043008
100%|██████████| 1/1 [00:01<00:00, 1.80s/it]
100%|██████████| 1/1 [00:01<00:00, 1.80s/it]
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:29 -48.936503 0.552695
BFGS: 1 17:35:29 -48.954084 0.470533
BFGS: 2 17:35:29 -49.008577 0.210998
BFGS: 3 17:35:30 -49.009768 0.197685
BFGS: 4 17:35:30 -49.017746 0.041125
Step Time Energy fmax
BFGS: 0 17:35:30 -55.119070 0.305598
BFGS: 1 17:35:30 -55.120917 0.239019
BFGS: 2 17:35:30 -55.125707 0.129257
BFGS: 3 17:35:30 -55.127339 0.147623
BFGS: 4 17:35:30 -55.130404 0.128454
BFGS: 5 17:35:30 -55.132311 0.102941
BFGS: 6 17:35:30 -55.133382 0.051705
BFGS: 7 17:35:30 -55.133852 0.056100
BFGS: 8 17:35:30 -55.134358 0.071290
BFGS: 9 17:35:30 -55.135044 0.069407
BFGS: 10 17:35:31 -55.135575 0.039816
100%|██████████| 1/1 [00:01<00:00, 1.44s/it]
100%|██████████| 1/1 [00:01<00:00, 1.45s/it]
Ag
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:31 -33.034314 0.634820
BFGS: 1 17:35:31 -33.053469 0.551048
BFGS: 2 17:35:31 -33.120907 0.172014
BFGS: 3 17:35:31 -33.122778 0.157330
BFGS: 4 17:35:31 -33.124182 0.143378
BFGS: 5 17:35:31 -33.126944 0.112450
BFGS: 6 17:35:31 -33.130773 0.110651
BFGS: 7 17:35:31 -33.133993 0.067506
BFGS: 8 17:35:31 -33.134835 0.034878
Step Time Energy fmax
BFGS: 0 17:35:31 -38.173508 0.098100
BFGS: 1 17:35:32 -38.174434 0.094358
BFGS: 2 17:35:32 -38.184904 0.084050
BFGS: 3 17:35:32 -38.185728 0.085482
BFGS: 4 17:35:32 -38.189041 0.086049
BFGS: 5 17:35:32 -38.190956 0.068717
BFGS: 6 17:35:32 -38.192259 0.087489
BFGS: 7 17:35:32 -38.193189 0.076825
BFGS: 8 17:35:32 -38.193671 0.042485
100%|██████████| 1/1 [00:01<00:00, 1.61s/it]
100%|██████████| 1/1 [00:01<00:00, 1.61s/it]
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:32 -33.056750 0.557366
BFGS: 1 17:35:32 -33.071227 0.489412
BFGS: 2 17:35:33 -33.126397 0.123824
BFGS: 3 17:35:33 -33.127308 0.112960
BFGS: 4 17:35:33 -33.128516 0.097369
BFGS: 5 17:35:33 -33.130260 0.079538
BFGS: 6 17:35:33 -33.133158 0.076356
BFGS: 7 17:35:33 -33.134741 0.040737
Step Time Energy fmax
BFGS: 0 17:35:33 -38.102026 0.092774
BFGS: 1 17:35:33 -38.102856 0.088843
BFGS: 2 17:35:33 -38.111090 0.134778
BFGS: 3 17:35:33 -38.112011 0.120334
BFGS: 4 17:35:33 -38.115307 0.063901
BFGS: 5 17:35:33 -38.116160 0.056322
BFGS: 6 17:35:34 -38.116718 0.058542
BFGS: 7 17:35:34 -38.117591 0.073183
BFGS: 8 17:35:34 -38.118458 0.057396
BFGS: 9 17:35:34 -38.118837 0.026086
100%|██████████| 1/1 [00:01<00:00, 1.62s/it]
100%|██████████| 1/1 [00:01<00:00, 1.62s/it]
Pd
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:34 -70.165062 0.631446
BFGS: 1 17:35:34 -70.189910 0.508128
BFGS: 2 17:35:34 -70.241649 0.191934
BFGS: 3 17:35:34 -70.243347 0.182565
BFGS: 4 17:35:34 -70.248576 0.144555
BFGS: 5 17:35:34 -70.251930 0.115934
BFGS: 6 17:35:34 -70.254308 0.074837
BFGS: 7 17:35:35 -70.255205 0.059446
BFGS: 8 17:35:35 -70.255945 0.041111
Step Time Energy fmax
BFGS: 0 17:35:35 -76.130241 0.213656
BFGS: 1 17:35:35 -76.133242 0.190340
BFGS: 2 17:35:35 -76.148718 0.169649
BFGS: 3 17:35:35 -76.150812 0.152247
BFGS: 4 17:35:35 -76.154315 0.130495
BFGS: 5 17:35:35 -76.157060 0.106089
BFGS: 6 17:35:35 -76.159303 0.088131
BFGS: 7 17:35:35 -76.160431 0.080472
BFGS: 8 17:35:35 -76.161130 0.064482
BFGS: 9 17:35:35 -76.161614 0.042961
100%|██████████| 1/1 [00:01<00:00, 1.70s/it]
100%|██████████| 1/1 [00:01<00:00, 1.70s/it]
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:36 -70.197712 0.454636
BFGS: 1 17:35:36 -70.211775 0.373893
BFGS: 2 17:35:36 -70.245977 0.178178
BFGS: 3 17:35:36 -70.247162 0.164572
BFGS: 4 17:35:36 -70.253347 0.078699
BFGS: 5 17:35:36 -70.254512 0.075506
BFGS: 6 17:35:36 -70.255271 0.058126
BFGS: 7 17:35:36 -70.255844 0.036953
Step Time Energy fmax
BFGS: 0 17:35:36 -75.892387 0.169440
BFGS: 1 17:35:36 -75.895234 0.150801
BFGS: 2 17:35:37 -75.906632 0.144094
BFGS: 3 17:35:37 -75.908423 0.135676
BFGS: 4 17:35:37 -75.912426 0.129049
BFGS: 5 17:35:37 -75.915495 0.108690
BFGS: 6 17:35:37 -75.917928 0.075327
BFGS: 7 17:35:37 -75.918955 0.067259
BFGS: 8 17:35:37 -75.919478 0.044584
100%|██████████| 1/1 [00:01<00:00, 1.53s/it]
100%|██████████| 1/1 [00:01<00:00, 1.53s/it]
Pt
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:37 -82.862062 1.000262
BFGS: 1 17:35:37 -82.918113 0.752917
BFGS: 2 17:35:37 -83.011694 0.335872
BFGS: 3 17:35:37 -83.016495 0.303119
BFGS: 4 17:35:38 -83.024495 0.224971
BFGS: 5 17:35:38 -83.030035 0.152585
BFGS: 6 17:35:38 -83.033361 0.088639
BFGS: 7 17:35:38 -83.034469 0.065739
BFGS: 8 17:35:38 -83.035207 0.068692
BFGS: 9 17:35:38 -83.035565 0.046778
Step Time Energy fmax
BFGS: 0 17:35:38 -88.770343 0.313371
BFGS: 1 17:35:38 -88.773634 0.271604
BFGS: 2 17:35:38 -88.784452 0.104771
BFGS: 3 17:35:38 -88.786014 0.101389
BFGS: 4 17:35:38 -88.788495 0.106521
BFGS: 5 17:35:38 -88.790461 0.089942
BFGS: 6 17:35:39 -88.791950 0.095611
BFGS: 7 17:35:39 -88.792650 0.099442
BFGS: 8 17:35:39 -88.793168 0.078145
BFGS: 9 17:35:39 -88.793668 0.036058
100%|██████████| 1/1 [00:01<00:00, 1.79s/it]
100%|██████████| 1/1 [00:01<00:00, 1.79s/it]
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:39 -82.946997 0.675644
BFGS: 1 17:35:39 -82.973149 0.546704
BFGS: 2 17:35:39 -83.026506 0.203005
BFGS: 3 17:35:39 -83.028072 0.183991
BFGS: 4 17:35:39 -83.033700 0.083383
BFGS: 5 17:35:39 -83.034396 0.061070
BFGS: 6 17:35:39 -83.035121 0.048406
Step Time Energy fmax
BFGS: 0 17:35:40 -88.350562 0.234982
BFGS: 1 17:35:40 -88.353605 0.203898
BFGS: 2 17:35:40 -88.362977 0.117111
BFGS: 3 17:35:40 -88.364343 0.121251
BFGS: 4 17:35:40 -88.368501 0.090149
BFGS: 5 17:35:40 -88.370482 0.073269
BFGS: 6 17:35:40 -88.371786 0.075234
BFGS: 7 17:35:40 -88.372317 0.079590
BFGS: 8 17:35:40 -88.372730 0.061422
BFGS: 9 17:35:40 -88.373020 0.027183
100%|██████████| 1/1 [00:01<00:00, 1.54s/it]
100%|██████████| 1/1 [00:01<00:00, 1.54s/it]
Rh
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:41 -100.192566 0.688933
BFGS: 1 17:35:41 -100.218240 0.600213
BFGS: 2 17:35:41 -100.286835 0.174727
BFGS: 3 17:35:41 -100.289766 0.129385
BFGS: 4 17:35:41 -100.298130 0.066626
BFGS: 5 17:35:41 -100.299264 0.053474
BFGS: 6 17:35:41 -100.299779 0.045082
Step Time Energy fmax
BFGS: 0 17:35:41 -106.921794 0.203149
BFGS: 1 17:35:41 -106.924541 0.168768
BFGS: 2 17:35:41 -106.930976 0.052503
BFGS: 3 17:35:41 -106.931583 0.045557
100%|██████████| 1/1 [00:01<00:00, 1.03s/it]
100%|██████████| 1/1 [00:01<00:00, 1.03s/it]
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:42 -100.171741 0.714774
BFGS: 1 17:35:42 -100.203866 0.626030
BFGS: 2 17:35:42 -100.287846 0.263189
BFGS: 3 17:35:42 -100.290543 0.195990
BFGS: 4 17:35:42 -100.297166 0.080369
BFGS: 5 17:35:42 -100.299033 0.056465
BFGS: 6 17:35:42 -100.299772 0.043877
Step Time Energy fmax
BFGS: 0 17:35:42 -106.876840 0.168839
BFGS: 1 17:35:42 -106.879420 0.139488
BFGS: 2 17:35:42 -106.885548 0.071259
BFGS: 3 17:35:42 -106.886238 0.064285
BFGS: 4 17:35:43 -106.886581 0.054974
BFGS: 5 17:35:43 -106.886842 0.043055
100%|██████████| 1/1 [00:01<00:00, 1.20s/it]
100%|██████████| 1/1 [00:01<00:00, 1.20s/it]
Ir
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:43 -124.235610 1.189926
BFGS: 1 17:35:43 -124.310841 0.937182
BFGS: 2 17:35:43 -124.422189 0.140824
BFGS: 3 17:35:43 -124.425271 0.133777
BFGS: 4 17:35:43 -124.429597 0.060629
BFGS: 5 17:35:43 -124.430258 0.038669
Step Time Energy fmax
BFGS: 0 17:35:43 -130.605622 0.371857
BFGS: 1 17:35:43 -130.616145 0.265889
BFGS: 2 17:35:43 -130.630318 0.110696
BFGS: 3 17:35:44 -130.633616 0.094367
BFGS: 4 17:35:44 -130.634276 0.075704
BFGS: 5 17:35:44 -130.634990 0.065711
BFGS: 6 17:35:44 -130.636198 0.083039
BFGS: 7 17:35:44 -130.636656 0.075143
BFGS: 8 17:35:44 -130.637047 0.047479
100%|██████████| 1/1 [00:01<00:00, 1.37s/it]
100%|██████████| 1/1 [00:01<00:00, 1.37s/it]
0%| | 0/1 [00:00<?, ?it/s]
Step Time Energy fmax
BFGS: 0 17:35:44 -124.227592 1.165685
BFGS: 1 17:35:44 -124.311475 0.915619
BFGS: 2 17:35:44 -124.422556 0.165148
BFGS: 3 17:35:44 -124.425478 0.200268
BFGS: 4 17:35:44 -124.428763 0.168863
BFGS: 5 17:35:45 -124.430254 0.088425
BFGS: 6 17:35:45 -124.430559 0.041776
Step Time Energy fmax
BFGS: 0 17:35:45 -130.496158 0.406202
BFGS: 1 17:35:45 -130.507203 0.267063
BFGS: 2 17:35:45 -130.518615 0.126415
BFGS: 3 17:35:45 -130.522315 0.080271
BFGS: 4 17:35:45 -130.523180 0.066553
BFGS: 5 17:35:45 -130.523452 0.056620
BFGS: 6 17:35:45 -130.524511 0.055665
BFGS: 7 17:35:45 -130.524677 0.042688
100%|██████████| 1/1 [00:01<00:00, 1.37s/it]
100%|██████████| 1/1 [00:01<00:00, 1.37s/it]
'Elapsed time = 18.032005310058594 seconds'
First, we compare the computed data and reference data. There is a systematic difference of about 0.5 eV due to the difference between RPBE and PBE functionals, and other subtle differences like lattice constant differences and reference energy differences. This is pretty typical, and an expected deviation.
plt.plot(refdata["fcc"], data["fcc"], "r.", label="fcc")
plt.plot(refdata["hcp"], data["hcp"], "b.", label="hcp")
plt.plot([-5.5, -3.5], [-5.5, -3.5], "k-")
plt.xlabel("Ref. data (DFT)")
plt.ylabel("UMA-OC20 prediction");

Next we compare the correlation between the hcp and fcc sites. Here we see the same trends. The data falls below the parity line because the hcp sites tend to be a little weaker binding than the fcc sites.
plt.plot(refdata["hcp"], refdata["fcc"], "r.")
plt.plot(data["hcp"], data["fcc"], ".")
plt.plot([-6, -1], [-6, -1], "k-")
plt.xlabel("$H_{ads, hcp}$")
plt.ylabel("$H_{ads, fcc}$")
plt.legend(["DFT (PBE)", "UMA-OC20"]);

Exercises#
You can also explore a few other adsorbates: C, H, N.
Explore the higher coverages. The deviations from the reference data are expected to be higher, but relative differences tend to be better. You probably need fine tuning to improve this performance. This data set doesn’t have forces though, so it isn’t practical to do it here.
Next steps#
In the next step, we consider some more complex adsorbates in nitrogen reduction, and how we can leverage OCP to automate the search for the most stable adsorbate geometry. See the next step.
Convergence study#
In Calculating adsorption energies we discussed some possible reasons we might see a discrepancy. Here we investigate some factors that impact the computed energies.
In this section, the energies refer to the reaction 1/2 O2 -> O*.
Effects of number of layers#
Slab thickness could be a factor. Here we relax the whole slab, and see by about 4 layers the energy is converged to ~0.02 eV.
for nlayers in [3, 4, 5, 6, 7, 8]:
slab = fcc111("Pt", size=(2, 2, nlayers), vacuum=10.0)
slab.pbc = True
slab.set_calculator(calc)
opt_slab = BFGS(slab, logfile=None)
opt_slab.run(fmax=0.05, steps=100)
slab_e = slab.get_potential_energy()
adslab = slab.copy()
add_adsorbate(adslab, "O", height=1.2, position="fcc")
adslab.pbc = True
adslab.set_calculator(calc)
opt_adslab = BFGS(adslab, logfile=None)
opt_adslab.run(fmax=0.05, steps=100)
adslab_e = adslab.get_potential_energy()
print(
f"nlayers = {nlayers}: {adslab_e - slab_e - atomic_reference_energies['O'] + re1:1.2f} eV"
)
/tmp/ipykernel_3148/338101817.py:5: FutureWarning: Please use atoms.calc = calc
slab.set_calculator(calc)
/tmp/ipykernel_3148/338101817.py:14: FutureWarning: Please use atoms.calc = calc
adslab.set_calculator(calc)
nlayers = 3: -1.58 eV
nlayers = 4: -1.47 eV
nlayers = 5: -1.47 eV
nlayers = 6: -1.46 eV
nlayers = 7: -1.47 eV
nlayers = 8: -1.47 eV
Effects of relaxation#
It is common to only relax a few layers, and constrain lower layers to bulk coordinates. We do that here. We only relax the adsorbate and the top layer.
This has a small effect (0.1 eV).
from ase.constraints import FixAtoms
for nlayers in [3, 4, 5, 6, 7, 8]:
slab = fcc111("Pt", size=(2, 2, nlayers), vacuum=10.0)
slab.set_constraint(FixAtoms(mask=[atom.tag > 1 for atom in slab]))
slab.pbc = True
slab.set_calculator(calc)
opt_slab = BFGS(slab, logfile=None)
opt_slab.run(fmax=0.05, steps=100)
slab_e = slab.get_potential_energy()
adslab = slab.copy()
add_adsorbate(adslab, "O", height=1.2, position="fcc")
adslab.set_constraint(FixAtoms(mask=[atom.tag > 1 for atom in adslab]))
adslab.pbc = True
adslab.set_calculator(calc)
opt_adslab = BFGS(adslab, logfile=None)
opt_adslab.run(fmax=0.05, steps=100)
adslab_e = adslab.get_potential_energy()
print(
f"nlayers = {nlayers}: {adslab_e - slab_e - atomic_reference_energies['O'] + re1:1.2f} eV"
)
/tmp/ipykernel_3148/1426773950.py:8: FutureWarning: Please use atoms.calc = calc
slab.set_calculator(calc)
/tmp/ipykernel_3148/1426773950.py:18: FutureWarning: Please use atoms.calc = calc
adslab.set_calculator(calc)
nlayers = 3: -1.47 eV
nlayers = 4: -1.35 eV
nlayers = 5: -1.35 eV
nlayers = 6: -1.34 eV
nlayers = 7: -1.35 eV
nlayers = 8: -1.35 eV
Unit cell size#
Coverage effects are quite noticeable with oxygen. Here we consider larger unit cells. This effect is large, and the results don’t look right, usually adsorption energies get more favorable at lower coverage, not less. This suggests fine-tuning could be important even at low coverages.
for size in [1, 2, 3, 4, 5]:
slab = fcc111("Pt", size=(size, size, 5), vacuum=10.0)
slab.set_constraint(FixAtoms(mask=[atom.tag > 1 for atom in slab]))
slab.pbc = True
slab.set_calculator(calc)
opt_slab = BFGS(slab, logfile=None)
opt_slab.run(fmax=0.05, steps=100)
slab_e = slab.get_potential_energy()
adslab = slab.copy()
add_adsorbate(adslab, "O", height=1.2, position="fcc")
adslab.set_constraint(FixAtoms(mask=[atom.tag > 1 for atom in adslab]))
adslab.pbc = True
adslab.set_calculator(calc)
opt_adslab = BFGS(adslab, logfile=None)
opt_adslab.run(fmax=0.05, steps=100)
adslab_e = adslab.get_potential_energy()
print(
f"({size}x{size}): {adslab_e - slab_e - atomic_reference_energies['O'] + re1:1.2f} eV"
)
/tmp/ipykernel_3148/3371624330.py:7: FutureWarning: Please use atoms.calc = calc
slab.set_calculator(calc)
/tmp/ipykernel_3148/3371624330.py:17: FutureWarning: Please use atoms.calc = calc
adslab.set_calculator(calc)
(1x1): -0.12 eV
(2x2): -1.35 eV
(3x3): -1.38 eV
(4x4): -1.41 eV
(5x5): -1.42 eV
Summary#
As with DFT, you should take care to see how these kinds of decisions affect your results, and determine if they would change any interpretations or not.