En este tutorial, exploramos FAIRChem v2 y el potencial interatómico universal de aprendizaje automático de la UMA como un marco unificado para la simulación atomística en química molecular, catálisis y materiales inorgánicos. Configuramos un entorno, nos autenticamos con Hugging Face para acceder a los pesos del modelo UMA cerrado e inicializamos calculadoras de tareas específicas para los dominios omol, oc20 y omat. Luego aplicamos el mismo potencial previamente entrenado a un amplio conjunto de flujos de trabajo de química computacional, incluida la predicción de fuerza y energía de un solo punto, optimización de la geometría molecular, comparación de estados de espín, estimación de energía de reacción, análisis vibratorio, adsorción de superficies, relajación de células cristalinas, ajuste de ecuaciones de estado, dinámica molecular y escaneo de superficies de energía potencial. A lo largo del tutorial, integramos FAIRChem con el entorno de simulación atómica para gestionar estructuras atómicas, optimizadores, restricciones, cálculos termodinámicos y análisis de trayectoria mientras utilizamos la aceleración de GPU siempre que esté disponible.
import importlib.util, subprocess, sys, os def _pip(*pkgs): subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", *pkgs]) si importlib.util.find_spec("fairchem") es Ninguno: print(">> Instalar fairchem-core, ase y helpers (tarda ~2-4 min)…") _pip("fairchem-core", "ase", "matplotlib", "huggingface_hub") print(">> Instalación realizada.") else: print(">> fairchem ya instalado.") desde huggingface_hub importar inicio de sesión, whoami def hf_authenticate(): token = Ninguno intentar: desde google.colab importar token de datos de usuario = userdata.get("HF_TOKEN") excepto Excepción: pasar token = token o os.environ.get("HF_TOKEN") intente: whoami() print(">> Ya está autenticado con Hugging Face.") return excepto Excepción: pase si el token es Ninguno: from getpass import getpass token = getpass("Pegue su token de acceso de Hugging Face: ").strip() login(token=token) print(">> Hugging Face inicie sesión OK.") hf_authenticate() import numpy as np import torch import matplotlib.pyplot como plt de fairchem.core import pretrained_mlip, FAIRChemCalculator DISPOSITIVO = "cuda" if torch.cuda.is_available() else "cpu" print(f">> Usando dispositivo: {DISPOSITIVO}") MODELO = "uma-s-1p2" predictor = pretrained_mlip.get_predict_unit(MODELO, dispositivo=DISPOSITIVO) calc_mol = FAIRChemCalculator(predictor, task_name="omol") calc_cat = FAIRChemCalculator(predictor, task_name="oc20") calc_mat = FAIRChemCalculator(predictor, task_name="omat") print(f">> Cargado {MODELO} con calculadoras omol/oc20/omat.")
Instalamos las dependencias requeridas de FAIRChem, ASE, visualización y Hugging Face mientras nos aseguramos de que la configuración siga siendo segura para volver a ejecutarla en Google Colab. Nos autenticamos con Hugging Face para acceder a los pesos del modelo UMA cerrado y detectar automáticamente si la aceleración de GPU está disponible. Luego cargamos el predictor UMA y creamos calculadoras independientes para simulaciones moleculares, de catálisis y de materiales.
from ase.build importar molécula from ase importar Átomos print("n" + "="*70) print("SECCIÓN 2: Energética de punto único del agua (tarea omol)") print("="*70) h2o = molécula("H2O") h2o.info.update({"charge": 0, "spin": 1}) h2o.calc = calc_mol E_h2o = h2o.get_potential_energy() F_h2o = h2o.get_forces() print(f"E(H2O) = {E_h2o:.4f} eV") print(f"Max |fuerza| = {np.abs(F_h2o).max():.4f} eV/A") def atom_energy(símbolo, espín): a = Átomos(símbolo, posiciones=[[0, 0, 0]]) a.info.update({"charge": 0, "spin": spin}) a.calc = calc_mol return a.get_potential_energy() E_O = atom_energy("O", spin=3) E_H = atom_energy("H", spin=2) E_atomization = -(E_h2o – E_O – 2 * E_H) print(f"Energía de atomización de H2O = {E_atomización:.3f} eV " f"(experimento ~ 9,5 eV incl. efectos ZPE)") de ase.optimize import LBFGS print("n" + "="*70) print("SECCIÓN 3: Relajar una molécula de agua deliberadamente distorsionada") print("="*70) h2o_bad = molecula("H2O") h2o_bad.positions[1]+= [0.25, -0.10, 0.05] h2o_bad.info.update({"charge": 0, "spin": 1}) h2o_bad.calc = calc_mol opt = LBFGS(h2o_bad, logfile=Ninguno) energías_opt =[]opt.attach(lambda: energies_opt.append(h2o_bad.get_potential_energy())) opt.run(fmax=0.01, pasos=200) d_OH = h2o_bad.get_distance(0, 1) ang = h2o_bad.get_angle(1, 0, 2) print(f"Convergido en {opt.get_number_of_steps()} pasos") print(f"Longitud del enlace OH = {d_OH:.3f} A (expt ~0.958 A)") print(f"Ángulo HOH = {ang:.1f} grados (expt ~104.5 grados)") plt.figure(figsize=(5, 3.2)) plt.plot(energies_opt, "o-") plt.xlabel("Paso del optimizador"); plt.ylabel("Energía (eV)") plt.title("Optimización de la geometría del H2O"); plt.tight_layout(); plt.mostrar()
Usamos la calculadora molecular UMA para evaluar la energía, las fuerzas atómicas y la energía de atomización de una molécula de agua. Definimos átomos de referencia aislados de hidrógeno y oxígeno con las multiplicidades de espín correctas para construir el cálculo de energía de atomización. Luego distorsionamos la geometría del agua, la relajamos con el optimizador LBFGS y analizamos la longitud del enlace convergente, el ángulo del enlace y la trayectoria de la energía.
print("n" + "="*70) print("SECCIÓN 4: Espacio singlete-triplete CH2 (¡UMA reconoce el espín!)") print("="*70) singlete = molécula("CH2_s1A1d"); singlet.info.update({"carga": 0, "giro": 1}) triplete = molécula("CH2_s3B1d"); triplet.info.update({"charge": 0, "spin": 3}) singlet.calc = FAIRChemCalculator(predictor, task_name="omol") triplet.calc = FAIRChemCalculator(predictor, task_name="omol") brecha = triplet.get_potential_energy() – singlet.get_potential_energy() print(f"E(triplet) – E(singlet) = {gap:.3f} eV " f"(negativo => estado fundamental triplete; expt ~ -0.39 eV)") print("n" + "="*70) print("SECCIÓN 5: Energía de reacción de CH4 + 2 O2 -> CO2 + 2 H2O") print("="*70) def energía_relajada(nombre, spin=1): m = molécula(nombre) m.info.update({"carga": 0, "giro": giro}) m.calc = FAIRChemCalculator(predictor, task_name="omol") LBFGS(m, logfile=None).run(fmax=0.02, pasos=200) return m.get_potential_energy() E = { "CH4": energía_relajada("CH4"), "O2": energía_relajada("O2", giro=3), "CO2": energía_relajada("CO2"), "H2O": energía_relajada("H2O"), } dE_rxn = (E["CO2"] + 2*E["H2O"]) – (E["CH4"] + 2*E["O2"]) print(f"Delta E (electrónica) = {dE_rxn:.2f} eV = {dE_rxn*96.485:.0f} kJ/mol") print("Entalpía de combustión experimental ~ -890 kJ/mol (ZPE/térmica no incluida aquí)") from ase.vibrations import Vibraciones print("n" + "="*70) print("SECCIÓN 6: Frecuencias vibratorias de H2O relajado") print("="*70) vib = Vibraciones(h2o_bad, name="vib_h2o") vib.run() freqs = np.real(vib.get_frequencies()) real_modes = [f para f en frecuencias si f > 200] print("Modos vibratorios (cm^-1):", ", ".join(f"{f:.0f}" para f en real_modes)) print("H2O experimental: 1595 (doblar), 3657 (estiramiento sim), 3756 (estiramiento asym)") print(f"Energía de punto cero = {vib.get_zero_point_energy():.3f} eV") vib.clean()
Comparamos los estados electrónicos singlete y triplete del metileno para calcular su brecha de energía en el estado de espín. Relajamos el metano, el oxígeno, el dióxido de carbono y el agua antes de combinar sus energías previstas para estimar la energía de reacción electrónica de la combustión del metano. También realizamos un análisis vibratorio de diferencias finitas de agua relajada para obtener sus frecuencias de modo normal y energía de punto cero.
from ase.build import fcc100, add_adsorbate from ase.constraints import FixAtoms print("n" + "="*70) print("SECCIÓN 7: CO/Cu(100) relajación + energía de adsorción (tarea oc20)") print("="*70) losa = fcc100("Cu", tamaño=(3, 3, 3), vacío=8.0, periódico=True) slab.set_constraint(FixAtoms(mask=[a.tag > 1 for a in losa])) add_adsorbate(losa, molécula("CO"), altura=2.0, posición="puente") slab.calc = calc_cat opt = LBFGS(slab, logfile=Ninguno) opt.run(fmax=0.05, pasos=300) E_slab_ads = slab.get_potential_energy() print(f"CO/Cu(100) relajado en {opt.get_number_of_steps()} pasos, " f"E = {E_slab_ads:.3f} eV") clean = fcc100("Cu", tamaño=(3, 3, 3), vacío=8.0, periódico=Verdadero) clean.set_constraint(FixAtoms(mask=[a.tag > 1 for a in clean])) clean.calc = FAIRChemCalculator(predictor, task_name="oc20") LBFGS(clean, logfile=None).run(fmax=0.05, pasos=300) E_clean = clean.get_potential_energy() co = molecula("CO"); co.info.update({"charge": 0, "spin": 1}) co.calc = FAIRChemCalculator(predictor, task_name="omol") LBFGS(co, logfile=None).run(fmax=0.02, pasos=100) E_co = co.get_potential_energy() E_ads = E_slab_ads – E_clean – E_co print(f"E(clean slab) = {E_clean:.3f} eV, E(gas CO) = {E_co:.3f} eV") print(f"Energía de adsorción (ciclo ingenuo) = {E_ads:.3f} eV") print("(oc20 usa su propio esquema de referencia DFT; para números de grado de publicación") print(" mantenga todas las especies dentro de un marco de tarea/referencia consistente.)")
Construimos una losa periódica de Cu (100), colocamos una molécula de monóxido de carbono en un sitio de adsorción de puente y restringimos las capas inferiores de cobre. Relajamos el sistema adsorbato-superficie con la calculadora OC20 y optimizamos por separado las referencias de monóxido de carbono en fase gaseosa y de losa limpia. Luego evaluamos un ciclo pedagógico de adsorción-energía reconociendo al mismo tiempo que OC20 utiliza una convención de referencia de energía específica para la tarea.
from ase.build import Bulk from ase.optimize import FIRE from ase.filters import FrechetCellFilter from ase.eos import EquationOfState print("n" + "="*70) print("SECCIÓN 8: Hierro BCC: relajación celular completa y módulo de volumen (omat)") print("="*70) fe = Bulk("Fe", "bcc", a=2.9) fe.calc = calc_mat FIRE(FrechetCellFilter(fe), logfile=None).run(fmax=0.02, pasos=300) a_relaxed = fe.cell.cellpar()[0]print(f"Constante de red BCC Fe relajada = {a_relaxed:.3f} A (expt ~2.866 A)") volúmenes, energías =[],[]cell0 = fe.get_cell() para escala en np.linspace(0.94, 1.06, 9): s = fe.copy() s.set_cell(cell0 * scale, scale_atoms=True) s.calc = FAIRChemCalculator(predictor, task_name="omat") volumes.append(s.get_volume()) energías.append(s.get_potential_energy()) eos = EquationOfState(volúmenes, energías, eos="birchmurnaghan") v0, e0, B = eos.fit() de ase.units import GPa como _GPa B_GPa = B / _GPa print(f"Volumen de equilibrio = {v0:.2f} A^3/celda") print(f"Módulo masivo = {B_GPa:.0f} GPa (expt ~170 GPa para Fe)") plt.figure(figsize=(5, 3.2)) plt.plot(volúmenes, energías, "o", label="puntos UMA") vfit = np.linspace(min(volúmenes), max(volúmenes), 100) plt.plot(vfit, [eos.func(v, *eos.eos_parameters) for v in vfit], "-", label="BM fit") plt.xlabel("Volumen (A^3)"); plt.ylabel("Energía (eV)") plt.title("Ecuación de estado de BCC Fe"); plt.leyenda(); plt.tight_layout(); plt.show() de ase importar unidades de ase.md.langevin import Langevin de ase.md.velocitydistribution import MaxwellBoltzmannDistribution print("n" + "="*70) print("SECCIÓN 9: 0.5 ps Langevin MD de una molécula de agua a 300 K") print("="*70) md_atoms = molecula("H2O") md_atoms.info.update({"charge": 0, "spin": 1}) semilla = int(np.random.randint(0, np.iinfo(np.int32).max)) md_predictor = pretrained_mlip.get_predict_unit(MODELO, dispositivo=DISPOSITIVO, semilla=semilla) md_atoms.calc = FAIRChemCalculator(md_predictor, task_name="omol") MaxwellBoltzmannDistribution(md_atoms, temperatura_K=300) dyn = Langevin(md_atoms, timestep=0.5 * unidades.fs, temperatura_K=300, fricción=0.01 / unidades.fs) tiempos, temperaturas, epots, d_oh1 =[],[],[],[]def log_md(): t = dyn.get_number_of_steps() * 0,5 veces.append temps.append(md_atoms.get_temperature()) epots.append(md_atoms.get_potential_energy()) d_oh1.append(md_atoms.get_distance(0, 1)) dyn.attach(log_md, intervalo=5) dyn.run(steps=1000) print(f"MD hecho. = {np.mean(temps[10:]):.0f} K, " f" = {np.mean(d_oh1):.3f} A") fig, ax = plt.subplots(1, 3, figsize=(12, 3.2)) ax[0].plot(tiempos, temperaturas); hacha[0].set_title("Temperatura (K)") hacha[1].plot(tiempos, epots); hacha[1].set_title("Energía potencial (eV)") hacha[2].plot(veces, d_oh1); hacha[2].set_title("longitud del enlace OH (A)") para a en ax: a.set_xlabel("time (fs)") plt.tight_layout(); plt.mostrar()
Relajamos las posiciones atómicas y la celda de simulación del hierro BCC utilizando la calculadora UMA en el dominio de materiales y un filtro de celda de Frechet. Tomamos muestras de energías en una variedad de volúmenes comprimidos y expandidos, ajustamos una ecuación de estado de Birch-Murnaghan y estimamos el volumen de equilibrio y el módulo de masa. También ejecutamos la dinámica molecular de Langevin para agua a 300 K y rastreamos su temperatura, energía potencial y longitud del enlace O-H a lo largo del tiempo.
print("n" + "="*70) print("SECCIÓN 10: Escaneo de estiramiento de enlaces OH en agua") print("="*70) distancias = np.linspace(0.7, 2.5, 25) pes =[]para d en distancias: a = molécula("H2O") a.info.update({"charge": 0, "spin": 1}) vec = a.positions[1]- a.posiciones[0]a.posiciones[1]= a.posiciones[0]+ vec / np.linalg.norm(vec) * d a.calc = FAIRChemCalculator(predictor, task_name="omol") pes.append(a.get_potential_energy()) pes = np.array(pes) – min(pes) plt.figure(figsize=(5.5, 3.4)) plt.plot(distancias, pes, "o-") plt.axvline(0.958, ls="–", c="gray", label="expt r_e") plt.xlabel("Distancia OH (A)"); plt.ylabel("Energía relativa (eV)") plt.title("OH estirar PES desde UMA"); plt.legend() plt.tight_layout(); plt.show() print("n" + "="*70) print("TUTORIAL COMPLETO!") print("="*70) print(""" Próximos pasos para explorar: * Cambie MODELO a "uma-m-1p1" para mayor precisión (necesita más memoria GPU). * Pruebe task_name="odac" con un MOF CIF u "omc" para cristales moleculares. * MD más grande: fairchem admite inferencia multi-GPU a través de Workers=N (pip instala fairchem-core[extras] * Documentos: https://fair-chem.github.io/ """)
Exploramos la superficie de energía potencial del agua estirando sistemáticamente un enlace O-H a lo largo de un rango de distancia seleccionado. Evaluamos la energía molecular en cada geometría, normalizamos las energías relativas al mínimo y visualizamos el perfil de disociación resultante. Concluimos el tutorial identificando modelos UMA de mayor precisión, dominios químicos adicionales y simulaciones de múltiples GPU más grandes como posibles extensiones.
En conclusión, creamos un flujo de trabajo de simulación atomística completo en torno a FAIRChem v2 y demostramos cómo UMA proporciona un potencial aprendido compartido en dominios químicamente distintos sin requerir un modelo separado para cada tarea. Utilizamos cálculos moleculares para evaluar energías, fuerzas, comportamiento de atomización, espacios de espín, reacciones energéticas, modos de vibración y perfiles de estiramiento de enlaces; Utilizamos el dominio de catálisis para relajar el CO en una superficie de Cu (100) y examinar la energía de adsorción; y utilizamos el dominio de los materiales para relajar el hierro BCC y estimar su módulo de volumen a partir de un ajuste de ecuación de estado. También ejecutamos la dinámica molecular de Langevin para observar fluctuaciones energéticas y estructurales de temperatura finita a lo largo del tiempo. Al combinar la inferencia UMA con constructores de estructuras, optimizadores, filtros, herramientas vibratorias y utilidades de dinámica molecular de ASE, establecimos una base reutilizable para extender el flujo de trabajo a moléculas más grandes, interfaces catalíticas, materiales cristalinos, estructuras organometálicas, cristales moleculares y variantes del modelo UMA de mayor precisión.
Consulte el código completo aquí. Además, no dude en seguirnos en Twitter y no olvide unirse a nuestro SubReddit de más de 150.000 ml y suscribirse a nuestro boletín. ¡Esperar! estas en telegrama? Ahora también puedes unirte a nosotros en Telegram.
¿Necesita asociarse con nosotros para promocionar su repositorio de GitHub O su página principal de Hugging O su lanzamiento de producto O seminario web, etc.? Conéctate con nosotros
A Sana Hassan, pasante de consultoría en Marktechpost y estudiante de doble titulación en IIT Madras, le apasiona aplicar la tecnología y la inteligencia artificial para abordar los desafíos del mundo real. Con un gran interés en resolver problemas prácticos, aporta una nueva perspectiva a la intersección de la IA y las soluciones de la vida real.