Adsorption Energies#

Pre-trained ODAC models are versatile across various MOF-related tasks. To begin, we’ll start with a fundamental application: calculating the adsorption energy for a single CO2 molecule. This serves as an excellent and simple demonstration of what you can achieve with these datasets and models.

For predicting the adsorption energy of a single CO2 molecule within a MOF structure, the adsorption energy (\(E_{\mathrm{ads}}\)) is defined as:

\[ E_{\mathrm{ads}} = E_{\mathrm{MOF+CO2}} - E_{\mathrm{MOF}} - E_{\mathrm{CO2}} \tag{1}\]

Each term on the right-hand side represents the energy of the relaxed state of the indicated chemical system. For a comprehensive understanding of our methodology for computing these adsorption energies, please refer to our paper.

Loading Pre-trained Models#

A pre-trained model can be loaded using FAIRChemCalculator. In this example, we’ll employ UMA to determine the CO2 adsorption energies.

from fairchem.core import FAIRChemCalculator, pretrained_mlip

predictor = pretrained_mlip.get_predict_unit("uma-s-1p1")
calc = FAIRChemCalculator(predictor, task_name="odac")
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(

Adsorption in rigid MOFs: CO2 Adsorption Energy in Mg-MOF-74#

Let’s apply our knowledge to Mg-MOF-74, a widely studied MOF known for its excellent CO2 adsorption properties. Its structure comprises magnesium atomic complexes connected by a carboxylated and oxidized benzene ring, serving as an organic linker. Previous studies consistently report the CO2 adsorption energy for Mg-MOF-74 to be around -0.40 eV [1] [2] [3].

Our goal is to verify if we can achieve a similar value by performing a simple single-point calculation using UMA. In the ODAC23 dataset, all MOF structures are identified by their CSD (Cambridge Structural Database) code. For Mg-MOF-74, this code is OPAGIX. We’ve extracted a specific OPAGIX+CO2 configuration from the dataset, which exhibits the lowest adsorption energy among its counterparts.

import matplotlib.pyplot as plt
from ase.io import read
from ase.visualize.plot import plot_atoms

mof_co2 = read("structures/OPAGIX_w_CO2.cif")
mof = read("structures/OPAGIX.cif")
co2 = read("structures/co2.xyz")

fig, ax = plt.subplots(figsize=(5, 4.5), dpi=250)
plot_atoms(mof_co2, ax)
ax.set_axis_off()
../../_images/22c07917ac2a3c80689e2f0f17ca387429cde669faecbba218569be0d505a678.png

The final step in calculating the adsorption energy involves connecting the FAIRChemCalculator to each relaxed structure: OPAGIX+CO2, OPAGIX, and CO2. The structures used here are already relaxed from ODAC23. For simplicity, we assume here that further relaxations can be neglected. We will show how to go beyond this assumption in the next section.

mof_co2.calc = calc
mof.calc = calc
co2.calc = calc

E_ads = (
    mof_co2.get_potential_energy()
    - mof.get_potential_energy()
    - co2.get_potential_energy()
)

print(f"Adsorption energy of CO2 in Mg-MOF-74: {E_ads:.3f} eV")
Adsorption energy of CO2 in Mg-MOF-74: -0.459 eV

Adsorption in flexible MOFs#

The adsorption energy calculation method outlined above is typically performed with rigid MOFs for simplicity. Both experimental and modeling literature have shown, however, that MOF flexibility can be important in accurately capturing the underlying chemistry of adsorption [1] [2] [3]. In particular, uptake can be improved by treating MOFs as flexible. Two types of MOF flexibility can be considered: intrinsic flexibility and deformation induced by guest molecules. In the Open DAC Project, we consider the latter MOF deformation by allowing the atomic positions of the MOF to relax during geometry optimization [4]. The addition of additional degrees of freedoms can complicate the computation of the adsorption energy and necessitates an extra step in the calculation procedure.

The figure below shows water adsorption in the MOF with CSD code WOBHEB with added defects (WOBHEB_0.11_0) from a DFT simulation. A typical adsorption energy calculation would only seek to capture the effects shaded in purple, which include both chemisorption and non-bonded interactions between the host and guest molecule. When allowing the MOF to relax, however, the adsorption energy also includes the energetic effect of the MOF deformation highlighted in green.

To account for this deformation, it is vital to use the most energetically favorable MOF geometry for the empty MOF term in Eqn. 1. Including MOF atomic coordinates as degrees of freedom can result in three possible outcomes:

  1. The MOF does not deform, so the energies of the relaxed empty MOF and the MOF in the adsorbed state are the same

  2. The MOF deforms to a less energetically favorable geometry than its ground state

  3. The MOF locates a new energetically favorable geoemtry relative to the empty MOF relaxation

The first outcome requires no additional computation because the MOF rigidity assumption is valid. The second outcome represents physical and reversible deformation where the MOF returns to its empty ground state upon removal of the guest molecule. The third outcome is often the result of the guest molecule breaking local symmetry. We also found cases in ODAC in which both outcomes 2 and 3 occur within the same MOF.

To ensure the most energetically favorable empty MOF geometry is found, an addition empty MOF relaxation should be performed after MOF + adsorbate relaxation. The guest molecule should be removed, and the MOF should be relaxed starting from its geometry in the adsorbed state. If all deformation is reversible, the MOF will return to its original empty geometry. Otherwise, the lowest energy (most favorable) MOF geometry should be taken as the reference energy, \(E_{\mathrm{MOF}}\), in Eqn. 1.

H2O Adsorption Energy in Flexible WOBHEB with UMA#

The first part of this tutorial demonstrates how to perform a single point adsorption energy calculation using UMA. To treat MOFs as flexible, we perform all calculations on geometries determined by geometry optimization. The following example corresponds to the figure shown above (H2O adsorption in WOBHEB_0.11_0).

In this tutorial, \(E_{x}(r_{y})\) corresponds to the energy of \(x\) determined from geometry optimization of \(y\).

First, we obtain the energy of the empty MOF from relaxation of only the MOF: \(E_{\mathrm{MOF}}(r_{\mathrm{MOF}})\)

import ase.io
from ase.optimize import BFGS

mof = ase.io.read("structures/WOBHEB_0.11.cif")
mof.calc = calc
relax = BFGS(mof)
relax.run(fmax=0.05)
E_mof_empty = mof.get_potential_energy()
print(f"Energy of empty MOF: {E_mof_empty:.3f} eV")
      Step     Time          Energy          fmax
BFGS:    0 07:17:28    -1077.274063        0.206405
BFGS:    1 07:17:28    -1077.276779        0.152729
BFGS:    2 07:17:29    -1077.281940        0.169925
BFGS:    3 07:17:29    -1077.284766        0.155781
BFGS:    4 07:17:29    -1077.288839        0.108788
BFGS:    5 07:17:29    -1077.291021        0.086445
BFGS:    6 07:17:30    -1077.293363        0.093451
BFGS:    7 07:17:30    -1077.295414        0.100121
BFGS:    8 07:17:30    -1077.297831        0.102533
BFGS:    9 07:17:30    -1077.300016        0.091587
BFGS:   10 07:17:30    -1077.302011        0.079018
BFGS:   11 07:17:31    -1077.304143        0.105559
BFGS:   12 07:17:31    -1077.306722        0.087901
BFGS:   13 07:17:31    -1077.309513        0.086350
BFGS:   14 07:17:31    -1077.312264        0.086833
BFGS:   15 07:17:32    -1077.314702        0.106267
BFGS:   16 07:17:32    -1077.316988        0.106185
BFGS:   17 07:17:32    -1077.319481        0.085507
BFGS:   18 07:17:32    -1077.322265        0.109662
BFGS:   19 07:17:32    -1077.325137        0.148685
BFGS:   20 07:17:33    -1077.327765        0.125885
BFGS:   21 07:17:33    -1077.329924        0.069104
BFGS:   22 07:17:33    -1077.331950        0.087309
BFGS:   23 07:17:33    -1077.334272        0.125247
BFGS:   24 07:17:34    -1077.336830        0.166665
BFGS:   25 07:17:34    -1077.339539        0.145535
BFGS:   26 07:17:34    -1077.342151        0.087696
BFGS:   27 07:17:34    -1077.344541        0.076144
BFGS:   28 07:17:34    -1077.346895        0.148940
BFGS:   29 07:17:35    -1077.349781        0.170190
BFGS:   30 07:17:35    -1077.352531        0.109261
BFGS:   31 07:17:35    -1077.354751        0.070340
BFGS:   32 07:17:35    -1077.356777        0.089690
BFGS:   33 07:17:36    -1077.358659        0.124265
BFGS:   34 07:17:36    -1077.360597        0.108041
BFGS:   35 07:17:36    -1077.362473        0.068583
BFGS:   36 07:17:36    -1077.364171        0.070207
BFGS:   37 07:17:36    -1077.365712        0.105412
BFGS:   38 07:17:37    -1077.367259        0.104278
BFGS:   39 07:17:37    -1077.368768        0.062745
BFGS:   40 07:17:37    -1077.370134        0.057348
BFGS:   41 07:17:37    -1077.371366        0.062367
BFGS:   42 07:17:38    -1077.372419        0.065487
BFGS:   43 07:17:38    -1077.373387        0.058441
BFGS:   44 07:17:38    -1077.374356        0.043340
Energy of empty MOF: -1077.374 eV

Next, we add the H2O guest molecule and relax the MOF + adsorbate to obtain \(E_{\mathrm{MOF+H2O}}(r_{\mathrm{MOF+H2O}})\).

mof_h2o = ase.io.read("structures/WOBHEB_H2O.cif")
mof_h2o.calc = calc
relax = BFGS(mof_h2o)
relax.run(fmax=0.05)
E_combo = mof_h2o.get_potential_energy()
print(f"Energy of MOF + H2O: {E_combo:.3f} eV")
      Step     Time          Energy          fmax
BFGS:    0 07:17:38    -1091.565591        1.145036
BFGS:    1 07:17:39    -1091.585063        0.314149
BFGS:    2 07:17:39    -1091.590209        0.243430
BFGS:    3 07:17:39    -1091.608170        0.237258
BFGS:    4 07:17:39    -1091.614633        0.227944
BFGS:    5 07:17:40    -1091.625222        0.186786
BFGS:    6 07:17:40    -1091.632354        0.178919
BFGS:    7 07:17:40    -1091.640632        0.175098
BFGS:    8 07:17:40    -1091.648043        0.184489
BFGS:    9 07:17:40    -1091.656147        0.160887
BFGS:   10 07:17:41    -1091.663844        0.178475
BFGS:   11 07:17:41    -1091.672297        0.188754
BFGS:   12 07:17:41    -1091.682091        0.157472
BFGS:   13 07:17:41    -1091.692983        0.177290
BFGS:   14 07:17:42    -1091.704439        0.158199
BFGS:   15 07:17:42    -1091.715508        0.191726
BFGS:   16 07:17:42    -1091.725710        0.197779
BFGS:   17 07:17:42    -1091.735325        0.163766
BFGS:   18 07:17:43    -1091.745542        0.151488
BFGS:   19 07:17:43    -1091.754020        0.170854
BFGS:   20 07:17:43    -1091.761496        0.153690
BFGS:   21 07:17:43    -1091.767888        0.152499
BFGS:   22 07:17:43    -1091.774204        0.165957
BFGS:   23 07:17:44    -1091.780888        0.135465
BFGS:   24 07:17:44    -1091.788356        0.181038
BFGS:   25 07:17:44    -1091.794277        0.204507
BFGS:   26 07:17:44    -1091.800638        0.131338
BFGS:   27 07:17:45    -1091.806520        0.189816
BFGS:   28 07:17:45    -1091.812304        0.199096
BFGS:   29 07:17:45    -1091.817179        0.151657
BFGS:   30 07:17:45    -1091.822213        0.100147
BFGS:   31 07:17:45    -1091.826299        0.125395
BFGS:   32 07:17:46    -1091.832528        0.177339
BFGS:   33 07:17:46    -1091.837117        0.246275
BFGS:   34 07:17:46    -1091.842046        0.112697
BFGS:   35 07:17:46    -1091.845776        0.330662
BFGS:   36 07:17:47    -1091.850859        0.173967
BFGS:   37 07:17:47    -1091.858475        0.159647
BFGS:   38 07:17:47    -1091.865282        0.141734
BFGS:   39 07:17:47    -1091.872004        0.139947
BFGS:   40 07:17:48    -1091.878252        0.263572
BFGS:   41 07:17:48    -1091.879961        0.592121
BFGS:   42 07:17:48    -1091.887255        0.546306
BFGS:   43 07:17:48    -1091.893877        0.244667
BFGS:   44 07:17:48    -1091.900157        0.174877
BFGS:   45 07:17:49    -1091.916007        0.230474
BFGS:   46 07:17:49    -1091.924413        0.392807
BFGS:   47 07:17:49    -1091.937999        0.274773
BFGS:   48 07:17:49    -1091.953513        0.423790
BFGS:   49 07:17:50    -1091.973285        0.645524
BFGS:   50 07:17:50    -1091.965263        1.503255
BFGS:   51 07:17:50    -1092.004568        0.357958
BFGS:   52 07:17:50    -1092.020353        0.279566
BFGS:   53 07:17:51    -1092.069270        0.301214
BFGS:   54 07:17:51    -1092.085579        0.297343
BFGS:   55 07:17:51    -1092.120605        0.550708
BFGS:   56 07:17:51    -1092.128943        0.284165
BFGS:   57 07:17:51    -1092.141199        0.274942
BFGS:   58 07:17:52    -1092.155538        0.235466
BFGS:   59 07:17:52    -1092.171397        0.267352
BFGS:   60 07:17:52    -1092.180513        0.312189
BFGS:   61 07:17:52    -1092.193345        0.352141
BFGS:   62 07:17:53    -1092.203985        0.321180
BFGS:   63 07:17:53    -1092.215418        0.251109
BFGS:   64 07:17:53    -1092.225017        0.184315
BFGS:   65 07:17:53    -1092.232647        0.115919
BFGS:   66 07:17:54    -1092.238564        0.099346
BFGS:   67 07:17:54    -1092.244163        0.101901
BFGS:   68 07:17:54    -1092.249763        0.112921
BFGS:   69 07:17:54    -1092.254787        0.112793
BFGS:   70 07:17:54    -1092.260026        0.089332
BFGS:   71 07:17:55    -1092.264583        0.080115
BFGS:   72 07:17:55    -1092.268564        0.103905
BFGS:   73 07:17:55    -1092.272209        0.138534
BFGS:   74 07:17:55    -1092.275680        0.154470
BFGS:   75 07:17:56    -1092.278929        0.154707
BFGS:   76 07:17:56    -1092.282483        0.107850
BFGS:   77 07:17:56    -1092.285222        0.089181
BFGS:   78 07:17:56    -1092.287762        0.090681
BFGS:   79 07:17:57    -1092.290184        0.087397
BFGS:   80 07:17:57    -1092.292143        0.082352
BFGS:   81 07:17:57    -1092.293929        0.067644
BFGS:   82 07:17:57    -1092.295311        0.087052
BFGS:   83 07:17:57    -1092.296683        0.071880
BFGS:   84 07:17:58    -1092.298045        0.076535
BFGS:   85 07:17:58    -1092.299660        0.071528
BFGS:   86 07:17:58    -1092.301396        0.083401
BFGS:   87 07:17:58    -1092.303043        0.079340
BFGS:   88 07:17:59    -1092.304851        0.064916
BFGS:   89 07:17:59    -1092.306327        0.061176
BFGS:   90 07:17:59    -1092.307757        0.046942
Energy of MOF + H2O: -1092.308 eV

We can now isolate the MOF atoms from the relaxed MOF + H2O geometry and see that the MOF has adopted a geometry that is less energetically favorable than the empty MOF by ~0.2 eV. The energy of the MOF in the adsorbed state corresponds to \(E_{\mathrm{MOF}}(r_{\mathrm{MOF+H2O}})\).

mof_adsorbed_state = mof_h2o[:-3]
mof_adsorbed_state.calc = calc
E_mof_adsorbed_state = mof_adsorbed_state.get_potential_energy()
print(f"Energy of MOF in the adsorbed state: {E_mof_adsorbed_state:.3f} eV")
Energy of MOF in the adsorbed state: -1077.091 eV

H2O adsorption in this MOF appears to correspond to Case #2 as outlined above. We can now perform re-relaxation of the empty MOF starting from the \(r_{\mathrm{MOF+H2O}}\) geometry.

relax = BFGS(mof_adsorbed_state)
relax.run(fmax=0.05)
E_mof_rerelax = mof_adsorbed_state.get_potential_energy()
print(f"Energy of re-relaxed empty MOF: {E_mof_rerelax:.3f} eV")
      Step     Time          Energy          fmax
BFGS:    0 07:17:59    -1077.090652        0.982752
BFGS:    1 07:17:59    -1077.122977        0.871413
BFGS:    2 07:18:00    -1077.172605        0.802032
BFGS:    3 07:18:00    -1077.212352        0.502355
BFGS:    4 07:18:00    -1077.232093        0.438343
BFGS:    5 07:18:00    -1077.250013        0.283046
BFGS:    6 07:18:01    -1077.260777        0.260105
BFGS:    7 07:18:01    -1077.270249        0.246703
BFGS:    8 07:18:01    -1077.279536        0.211804
BFGS:    9 07:18:01    -1077.286609        0.183453
BFGS:   10 07:18:01    -1077.291757        0.150575
BFGS:   11 07:18:02    -1077.295854        0.141494
BFGS:   12 07:18:02    -1077.299485        0.147543
BFGS:   13 07:18:02    -1077.304172        0.185327
BFGS:   14 07:18:02    -1077.308920        0.174339
BFGS:   15 07:18:03    -1077.313520        0.135748
BFGS:   16 07:18:03    -1077.317643        0.165740
BFGS:   17 07:18:03    -1077.321854        0.150803
BFGS:   18 07:18:03    -1077.326225        0.145004
BFGS:   19 07:18:03    -1077.329782        0.113757
BFGS:   20 07:18:04    -1077.332405        0.109288
BFGS:   21 07:18:04    -1077.334648        0.099959
BFGS:   22 07:18:04    -1077.336934        0.122606
BFGS:   23 07:18:04    -1077.339635        0.118735
BFGS:   24 07:18:05    -1077.342254        0.090113
BFGS:   25 07:18:05    -1077.344579        0.091076
BFGS:   26 07:18:05    -1077.346492        0.068519
BFGS:   27 07:18:05    -1077.348038        0.067771
BFGS:   28 07:18:05    -1077.349751        0.090955
BFGS:   29 07:18:06    -1077.351299        0.095398
BFGS:   30 07:18:06    -1077.352905        0.064567
BFGS:   31 07:18:06    -1077.354095        0.050754
BFGS:   32 07:18:06    -1077.355396        0.054560
BFGS:   33 07:18:07    -1077.356764        0.083862
BFGS:   34 07:18:07    -1077.358154        0.078544
BFGS:   35 07:18:07    -1077.359674        0.068528
BFGS:   36 07:18:07    -1077.361088        0.070866
BFGS:   37 07:18:08    -1077.362371        0.073295
BFGS:   38 07:18:08    -1077.363546        0.084033
BFGS:   39 07:18:08    -1077.364857        0.081094
BFGS:   40 07:18:08    -1077.365841        0.067813
BFGS:   41 07:18:08    -1077.366858        0.058184
BFGS:   42 07:18:09    -1077.367958        0.058983
BFGS:   43 07:18:09    -1077.368984        0.070095
BFGS:   44 07:18:09    -1077.370232        0.076064
BFGS:   45 07:18:09    -1077.371392        0.048334
Energy of re-relaxed empty MOF: -1077.371 eV

The MOF returns to its original empty reference energy upon re-relaxation, confirming that this deformation is physically relevant and is induced by the adsorbate molecule. In Case #3, this re-relaxed energy will be more negative (more favorable) than the original empty MOF relaxation. Thus, we take the reference empty MOF energy (\(E_{\mathrm{MOF}}\) in Eqn. 1) to be the minimum of the original empty MOF energy and the re-relaxed MOf energy:

E_mof = min(E_mof_empty, E_mof_rerelax)

# get adsorbate reference energy
h2o = mof_h2o[-3:]
h2o.calc = calc
E_h2o = h2o.get_potential_energy()

# compute adsorption energy
E_ads = E_combo - E_mof - E_h2o
print(f"Adsorption energy of H2O in WOBHEB_0.11_0: {E_ads:.3f} eV")
Adsorption energy of H2O in WOBHEB_0.11_0: -0.685 eV

This adsorption energy closely matches that from DFT (–0.699 eV) [1]. The strong adsorption energy is a consequence of both H2O chemisorption and MOF deformation. We can decompose the adsorption energy into contributions from these two factors. Assuming rigid H2O molecules, we define \(E_{\mathrm{int}}\) and \(E_{\mathrm{MOF,deform}}\), respectively, as

\[ E_{\mathrm{int}} = E_{\mathrm{MOF+H2O}}(r_{\mathrm{MOF+H2O}}) - E_{\mathrm{MOF}}(r_{\mathrm{MOF+H2O}}) - E_{\mathrm{H2O}}(r_{\mathrm{MOF+H2O}}) \tag{2}\]
\[ E_{\mathrm{MOF,deform}} = E_{\mathrm{MOF}}(r_{\mathrm{MOF+H2O}}) - E_{\mathrm{MOF}}(r_{\mathrm{MOF}}) \tag{3}\]

\(E_{\mathrm{int}}\) describes host host–guest interactions for the MOF in the adsorbed state only. \(E_{\mathrm{MOF,deform}}\) quantifies the magnitude of deformation between the MOF in the adsorbed state and the most energetically favorable empty MOF geometry determined from the workflow presented here. It can be shown that

\[ E_{\mathrm{ads}} = E_{\mathrm{int}} + E_{\mathrm{MOF,deform}} \tag{4}\]

For H2O adsorption in WOBHEB_0.11, we have

E_int = E_combo - E_mof_adsorbed_state - E_h2o
print(f"E_int: {E_int}")
E_int: -0.9689996242521328
E_mof_deform = E_mof_adsorbed_state - E_mof_empty
print(f"E_mof_deform: {E_mof_deform}")
E_mof_deform: 0.2837033271789551
E_ads = E_int + E_mof_deform
print(f"E_ads: {E_ads}")
E_ads: -0.6852962970731777

\(E_{\mathrm{int}}\) is equivalent to \(E_{\mathrm{ads}}\) when the MOF is assumed to be rigid. In this case, failure to consider adsorbate-induced deformation would result in an overestimation of the adsorption energy magnitude.

Acknowledgements & Authors#

Logan Brabson and Sihoon Choi (Georgia Tech) and the OpenDAC project.