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 () is defined as:
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¶
Need to install fairchem-core or get UMA access or getting permissions/401 errors?
Install the necessary packages using pip, uv etc
! pip install fairchem-core fairchem-data-oc fairchem-applications-cattsunamiGet 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 loginor by setting the HF_TOKEN environment variable.
# Login using the huggingface-cli utility
! huggingface-cli login
# alternatively,
import os
os.environ['HF_TOKEN'] = 'MY_TOKEN'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-1p2p1")
calc = FAIRChemCalculator(predictor, task_name="odac")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
WARNING:root:device was not explicitly set, using device='cuda'.
WARNING:root:UMA Nitro cannot serve this request: nitro mode supports only CUDA compute capability 8.x-12.x, not 7.5.
WARNING:root:Now running with nitro=False, execution_mode='umas_fast_gpu' (TF32).
WARNING:root:Running in a reduced-precision mode: some values, such as higher-order derivatives and phonon calculations, will be less accurate. For standard FP32 precision, use inference_settings='high-precision'.
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()
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")WARNING:root:Model is being compiled. This might take a while the first time.
WARNING:root:Running (unknown commit) with inference settings: multigpu
W1027 22:03:57.516000 9750 site-packages/torch/_logging/_internal.py:1345] [0/0] Profiler record function <class 'torch.autograd.profiler.record_function'> will be ignored
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: 'Compositions differ from merged model'.
Use inference_settings='batch' for heterogeneous batched evaluations.
WARNING:root:Now running with merge_mole=False, compile=False, execution_mode='general' (TF32).
Adsorption energy of CO2 in Mg-MOF-74: -0.482 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:
The MOF does not deform, so the energies of the relaxed empty MOF and the MOF in the adsorbed state are the same
The MOF deforms to a less energetically favorable geometry than its ground state
The MOF locates a new energetically favorable geometry 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, , 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, corresponds to the energy of determined from geometry optimization of .
First, we obtain the energy of the empty MOF from relaxation of only the 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 22:04:54 -1077.361727 0.157084
BFGS: 1 22:04:54 -1077.363819 0.092716
BFGS: 2 22:04:56 -1077.366761 0.102510
BFGS: 3 22:04:57 -1077.369805 0.112169
BFGS: 4 22:05:01 -1077.372811 0.108720
BFGS: 5 22:05:02 -1077.375103 0.081800
BFGS: 6 22:05:02 -1077.377096 0.059922
BFGS: 7 22:05:05 -1077.379166 0.081639
BFGS: 8 22:05:05 -1077.381439 0.086789
BFGS: 9 22:05:08 -1077.383770 0.074891
BFGS: 10 22:05:10 -1077.386161 0.097254
BFGS: 11 22:05:13 -1077.388719 0.102982
BFGS: 12 22:05:14 -1077.391364 0.103654
BFGS: 13 22:05:16 -1077.393878 0.092150
BFGS: 14 22:05:16 -1077.396289 0.108202
BFGS: 15 22:05:17 -1077.398861 0.132714
BFGS: 16 22:05:17 -1077.401808 0.117633
BFGS: 17 22:05:17 -1077.404709 0.076484
BFGS: 18 22:05:18 -1077.407172 0.077310
BFGS: 19 22:05:19 -1077.409308 0.096843
BFGS: 20 22:05:19 -1077.411622 0.094293
BFGS: 21 22:05:19 -1077.414302 0.097035
BFGS: 22 22:05:19 -1077.417215 0.104005
BFGS: 23 22:05:20 -1077.420047 0.076152
BFGS: 24 22:05:20 -1077.422612 0.092551
BFGS: 25 22:05:24 -1077.424984 0.075289
BFGS: 26 22:05:25 -1077.427308 0.088279
BFGS: 27 22:05:25 -1077.429529 0.086770
BFGS: 28 22:05:26 -1077.431508 0.069720
BFGS: 29 22:05:26 -1077.433200 0.064812
BFGS: 30 22:05:27 -1077.434720 0.050630
BFGS: 31 22:05:31 -1077.436135 0.067306
BFGS: 32 22:05:33 -1077.437480 0.061921
BFGS: 33 22:05:35 -1077.438762 0.054473
BFGS: 34 22:05:36 -1077.440056 0.040897
Energy of empty MOF: -1077.440 eV
Next, we add the H2O guest molecule and relax the MOF + adsorbate to obtain .
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 22:05:36 -1091.648157 1.137715
BFGS: 1 22:05:36 -1091.667006 0.323337
BFGS: 2 22:05:37 -1091.671332 0.231188
BFGS: 3 22:05:37 -1091.684672 0.293404
BFGS: 4 22:05:37 -1091.690923 0.233438
BFGS: 5 22:05:37 -1091.698612 0.197352
BFGS: 6 22:05:38 -1091.705230 0.197438
BFGS: 7 22:05:38 -1091.713828 0.253893
BFGS: 8 22:05:38 -1091.721813 0.223165
BFGS: 9 22:05:39 -1091.730000 0.200523
BFGS: 10 22:05:39 -1091.738725 0.221101
BFGS: 11 22:05:41 -1091.748961 0.290274
BFGS: 12 22:05:42 -1091.760313 0.285064
BFGS: 13 22:05:42 -1091.772073 0.191162
BFGS: 14 22:05:42 -1091.783890 0.190939
BFGS: 15 22:05:43 -1091.795383 0.272141
BFGS: 16 22:05:43 -1091.806223 0.314329
BFGS: 17 22:05:44 -1091.815600 0.230961
BFGS: 18 22:05:46 -1091.823825 0.138286
BFGS: 19 22:05:50 -1091.831509 0.132151
BFGS: 20 22:05:52 -1091.839423 0.146276
BFGS: 21 22:05:54 -1091.847564 0.154044
BFGS: 22 22:05:54 -1091.855795 0.170335
BFGS: 23 22:05:55 -1091.863479 0.150795
BFGS: 24 22:05:57 -1091.870203 0.177467
BFGS: 25 22:05:59 -1091.876215 0.271320
BFGS: 26 22:06:00 -1091.880606 0.307588
BFGS: 27 22:06:01 -1091.883678 0.564534
BFGS: 28 22:06:01 -1091.890124 0.162659
BFGS: 29 22:06:02 -1091.894710 0.097671
BFGS: 30 22:06:06 -1091.901064 0.124770
BFGS: 31 22:06:09 -1091.906084 0.227916
BFGS: 32 22:06:12 -1091.912303 0.176272
BFGS: 33 22:06:12 -1091.919348 0.131771
BFGS: 34 22:06:14 -1091.924831 0.168383
BFGS: 35 22:06:15 -1091.929644 0.294082
BFGS: 36 22:06:18 -1091.926169 0.924655
BFGS: 37 22:06:18 -1091.939384 0.121863
BFGS: 38 22:06:18 -1091.944028 0.104041
BFGS: 39 22:06:21 -1091.955875 0.249834
BFGS: 40 22:06:21 -1091.960026 0.248983
BFGS: 41 22:06:23 -1091.974412 0.263744
BFGS: 42 22:06:23 -1091.981631 0.131881
BFGS: 43 22:06:24 -1091.984498 0.792926
BFGS: 44 22:06:24 -1091.998011 0.164621
BFGS: 45 22:06:25 -1092.006805 0.203590
BFGS: 46 22:06:25 -1092.030106 0.473526
BFGS: 47 22:06:26 -1092.040989 0.183707
BFGS: 48 22:06:26 -1092.065414 0.248444
BFGS: 49 22:06:28 -1092.090531 0.380776
BFGS: 50 22:06:28 -1092.097876 1.131959
BFGS: 51 22:06:29 -1092.120389 0.460874
BFGS: 52 22:06:29 -1092.142659 0.389019
BFGS: 53 22:06:29 -1092.160367 0.359601
BFGS: 54 22:06:30 -1092.178404 0.271300
BFGS: 55 22:06:31 -1092.191369 0.512656
BFGS: 56 22:06:34 -1092.207031 0.679580
BFGS: 57 22:06:34 -1092.222595 0.687631
BFGS: 58 22:06:34 -1092.235564 0.423301
BFGS: 59 22:06:37 -1092.247942 0.160092
BFGS: 60 22:06:39 -1092.259184 0.157131
BFGS: 61 22:06:42 -1092.267919 0.182829
BFGS: 62 22:06:45 -1092.274801 0.120554
BFGS: 63 22:06:46 -1092.280008 0.093289
BFGS: 64 22:06:49 -1092.284890 0.125578
BFGS: 65 22:06:50 -1092.289714 0.112042
BFGS: 66 22:06:52 -1092.294385 0.123275
BFGS: 67 22:06:53 -1092.298931 0.135737
BFGS: 68 22:06:54 -1092.303558 0.233423
BFGS: 69 22:06:55 -1092.308260 0.264182
BFGS: 70 22:06:55 -1092.312664 0.208955
BFGS: 71 22:06:55 -1092.316594 0.098742
BFGS: 72 22:06:56 -1092.320084 0.085691
BFGS: 73 22:06:56 -1092.323370 0.087570
BFGS: 74 22:06:58 -1092.326317 0.091063
BFGS: 75 22:06:58 -1092.328913 0.072368
BFGS: 76 22:06:58 -1092.331195 0.083745
BFGS: 77 22:06:59 -1092.333414 0.096796
BFGS: 78 22:07:01 -1092.335551 0.087634
BFGS: 79 22:07:02 -1092.337547 0.066482
BFGS: 80 22:07:03 -1092.339352 0.103987
BFGS: 81 22:07:04 -1092.341101 0.145929
BFGS: 82 22:07:07 -1092.342959 0.145695
BFGS: 83 22:07:10 -1092.344928 0.106599
BFGS: 84 22:07:13 -1092.346843 0.051569
BFGS: 85 22:07:14 -1092.348572 0.072896
BFGS: 86 22:07:14 -1092.350101 0.109374
BFGS: 87 22:07:16 -1092.351467 0.075661
BFGS: 88 22:07:19 -1092.352654 0.047172
Energy of MOF + H2O: -1092.353 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 .
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.124 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 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 22:07:19 -1077.124456 1.249112
BFGS: 1 22:07:21 -1077.176465 1.034951
BFGS: 2 22:07:21 -1077.238391 0.694201
BFGS: 3 22:07:24 -1077.282887 0.444090
BFGS: 4 22:07:25 -1077.302237 0.369405
BFGS: 5 22:07:26 -1077.319342 0.344145
BFGS: 6 22:07:29 -1077.334904 0.330357
BFGS: 7 22:07:30 -1077.346959 0.246280
BFGS: 8 22:07:31 -1077.354422 0.146094
BFGS: 9 22:07:32 -1077.359574 0.150676
BFGS: 10 22:07:32 -1077.364407 0.187989
BFGS: 11 22:07:32 -1077.369508 0.170277
BFGS: 12 22:07:33 -1077.374687 0.143982
BFGS: 13 22:07:33 -1077.379769 0.124602
BFGS: 14 22:07:34 -1077.384653 0.140744
BFGS: 15 22:07:35 -1077.388976 0.110850
BFGS: 16 22:07:36 -1077.392561 0.090428
BFGS: 17 22:07:36 -1077.395769 0.092970
BFGS: 18 22:07:36 -1077.398952 0.108345
BFGS: 19 22:07:39 -1077.402116 0.114093
BFGS: 20 22:07:40 -1077.405256 0.103801
BFGS: 21 22:07:42 -1077.408397 0.105788
BFGS: 22 22:07:44 -1077.411466 0.096627
BFGS: 23 22:07:45 -1077.414359 0.099751
BFGS: 24 22:07:45 -1077.417052 0.080846
BFGS: 25 22:07:48 -1077.419559 0.080246
BFGS: 26 22:07:49 -1077.421788 0.076751
BFGS: 27 22:07:50 -1077.423636 0.076364
BFGS: 28 22:07:52 -1077.425185 0.051853
BFGS: 29 22:07:54 -1077.426672 0.071259
BFGS: 30 22:07:57 -1077.428243 0.079763
BFGS: 31 22:07:58 -1077.429745 0.074225
BFGS: 32 22:08:01 -1077.431051 0.049147
Energy of re-relaxed empty MOF: -1077.431 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 ( 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.542 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 and , respectively, as
describes host–guest interactions for the MOF in the adsorbed state only. 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
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.8573709850175231
E_mof_deform = E_mof_adsorbed_state - E_mof_empty
print(f"E_mof_deform: {E_mof_deform}")E_mof_deform: 0.3156003030710508
E_ads = E_int + E_mof_deform
print(f"E_ads: {E_ads}")E_ads: -0.5417706819464723
is equivalent to 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.
- The Open DAC 2023 Dataset and Challenges for Sorbent Discovery in Direct Air Capture. (2024). 10.1021/acscentsci.3c01629
- Carbon Dioxide Adsorption in Mg-MOF-74. (2015). 10.1039/C4SC02064B
- Carbon Dioxide Adsorption in Mg-MOF-74. (2014). 10.1039/C3SC51319J
- Carbon Dioxide Adsorption in Mg-MOF-74. (2018). 10.1021/acs.jpcc.8b00938
- Framework Flexibility in Metal-Organic Frameworks. (2017). 10.1021/jacs.7b01688