NequIP与Allegro:机器学习势能函数

1. 引言

传统分子动力学(MD)模拟依赖经验力场(如CHARMM、AMBER)或量子力学(QM)计算。前者速度快但不准确,后者准确但计算量巨大(只能模拟数百个原子、皮秒级别)。**机器学习势能函数(ML Potentials)**通过学习量子化学精度的能量和力,实现”又快又准”的分子模拟。1

ML势能函数的核心挑战

  1. 数据效率:训练数据(DFT计算)昂贵,需要在有限数据下达到高精度
  2. 可扩展性:从几十个原子到数百万原子
  3. 对称性:必须精确满足物理对称性(平移、旋转不变性)
  4. 能量守恒:力是能量的负梯度,必须保证

2. NequIP架构详解

2.1 核心设计思想

NequIP(Neural Equivariant Interatomic Potentials)的核心创新是:

使用E(3)等变消息传递,在消息中编码方向信息

2.2 架构概览

输入:原子类型 + 3D坐标
  ↓
Embedding层:原子类型 → 初始特征
  ↓
等变消息传递层(重复L次)
  ↓
Readout层:聚合 → 原子能量贡献
  ↓
输出:总能量 + 原子受力

2.3 等变消息传递

NequIP的消息传递在球谐函数域进行:

class NequIPConvolution(nn.Module):
    """
    NequIP等变卷积层
    核心:将方向信息编码为球谐函数
    """
    def __init__(self, l_max, num_species, hidden_channels):
        super().__init__()
        self.l_max = l_max
        
        # 边方向编码:计算 r_ij 的球谐函数
        self.spherical_harmonics = o3.SphericalHarmonics(
            o3.Irreps([(l, 1 if l % 2 == 0 else -1) for l in range(l_max + 1)])
        )
        
        # 径向MLP(只依赖于距离,不破坏等变性)
        self.radial_mlp = nn.Sequential(
            nn.Linear(1, 32),
            nn.SiLU(),
            nn.Linear(32, 64),
            nn.SiLU(),
            nn.Linear(64, (l_max + 1) ** 2 * hidden_channels)
        )
        
        # 等变张量积
        self.tp = o3.TensorProduct(
            o3.Irreps([(l, 1 if l % 2 == 0 else -1) for l in range(l_max + 1)]),
            o3.Irreps([(l, 1) for l in range(l_max + 1)]),
            # ...
        )
    
    def forward(self, node_features, edge_vectors, edge_lengths):
        # 1. 编码边方向
        Y = self.spherical_harmonics(edge_vectors)  # [B, N, N, (l_max+1)^2]
        
        # 2. 径向特征(距离的函数)
        radial = self.radial_mlp(edge_lengths.unsqueeze(-1))  # [B, N, N, H]
        
        # 3. 消息 = 径向特征 × 球谐函数
        messages = Y.unsqueeze(-1) * radial.unsqueeze(-2)  # [B, N, N, (l_max+1)^2, H]
        
        # 4. 张量积聚合
        # 聚合来自邻居的消息
        output = self.aggregate_messages(node_features, messages)
        
        return output

2.4 关键创新点

创新说明优势
连续滤波卷积核是连续的径向函数,而非离散更好的泛化能力
等变消息消息包含方向信息更高的数据效率
自相互作用支持特征自旋转变增加表达能力
门控机制标量门控非线性保持等变性

2.5 训练流程

# NequIP训练示例(简化)
from nequip.train import Trainer
from nequip.model import model_builder
 
# 1. 准备数据集
dataset = nequip.dataset.AtomicDataModule(
    train_url="train.xyz",
    validation_url="val.xyz",
    batch_size=16,
    num_workers=4
)
 
# 2. 构建模型
model = model_builder(
    r_max=6.0,           # 截断半径
    num_species=3,       # 原子种类数
    l_max=3,             # 球谐函数最高阶
    hidden_channels=64   # 隐藏维度
)
 
# 3. 训练配置
trainer = Trainer(
    model=model,
    optimizers={
        "optimizer": torch.optim.AdamW,
        "lr": 1e-2,
    },
    loss={
        "energy": 1.0,   # 能量损失权重
        "forces": 100.0  # 力损失权重(通常更大)
    },
    metrics={
        "energy": ["mae", "rmse"],
        "forces": ["mae"]
    }
)
 
# 4. 训练
trainer.fit()

3. Allegro架构详解

3.1 核心设计思想

Allegro的核心创新是逐对(Pairwise)分解

不是对每个原子做消息传递,而是直接学习原子对之间的等变交互

3.2 架构对比

特性NequIPAllegro
消息传递原子中心原子对中心
邻居聚合先消息后聚合先分解后组合
复杂度
等变性完全等变完全等变

3.3 Allegro的核心公式

Allegro学习一个逐对等变函数

然后通过张量积组合

class AllegroLayer(nn.Module):
    """
    Allegro等变层
    关键:分解为逐对项的组合
    """
    def __init__(self, l_max, hidden_dim, num_basis):
        super().__init__()
        
        # 逐对编码器:r_ij -> 逐对特征
        self.pair_encoder = nn.ModuleList([
            nn.Sequential(
                nn.Linear(num_basis, hidden_dim),
                nn.SiLU(),
                nn.Linear(hidden_dim, hidden_dim)
            ) for _ in range(l_max + 1)
        ])
        
        # 张量积组合
        self.tp_rules = self._build_tensor_product_rules(l_max)
    
    def forward(self, edge_attrs, edge_lengths):
        """
        Args:
            edge_attrs: [B, num_edges, num_basis] 边特征
            edge_lengths: [B, num_edges] 边长度
        """
        # 1. 逐对特征编码
        pair_features = []
        for l in range(self.l_max + 1):
            pf = self.pair_encoder[l](edge_attrs)  # [B, E, H]
            pair_features.append(pf)
        
        # 2. 张量积组合
        combined = self.combine_pairs(pair_features, self.tp_rules)
        
        return combined
    
    def combine_pairs(self, features, rules):
        """
        根据CG规则组合逐对特征
        """
        output = []
        for (l1, l2, l3), mode in rules:
            # 组合特征
            combined = torch.einsum('bij,bjk->bik', features[l1], features[l2])
            output.append(combined)
        return output

3.4 高阶等变性

Allegro支持任意阶的等变特征,通过Wigner-D矩阵实现:

# Allegro支持高阶张量特征
class HighOrderAllegro(nn.Module):
    def __init__(self, max_l, hidden_dim):
        super().__init__()
        # 阶数从0(标量)到max_l
        self.irreps = o3.Irreps([(l, 1 if l % 2 == 0 else -1) 
                                 for l in range(max_l + 1)])

4. 与LAMMPS集成

4.1 NequIP-LAMMPS接口

NequIP可以直接导出为LAMMPS势能文件:

# 1. 训练并导出模型
nequip-deploy deploy \
    --train-dir /path/to/training \
    --output /path/to/deployed.pth \
    --final
 
# 2. 转换为LAMMPS格式
nequip2lammps \
    --model deployed.pth \
    --species O H C \
    --output nequip.eam.fs

4.2 MD模拟示例

# 使用OpenMM + NequIP进行MD模拟
import torch
from nequip.data import AtomicData
from nequip.model import model_builder
 
# 加载模型
model = model_builder(
    file_name="deployed.pth",
    device="cuda"
)
model.eval()
 
# 初始化OpenMM系统
from openmm import *
from openmm.app import *
 
# 创建分子系统
pdb = PDBFile("molecule.pdb")
forcefield = ForceField("tip3p.xml")
 
# 替换力场为NequIP
system = forcefield.createSystem(
    pdb.topology,
    nonbondedMethod=NoCutoff,
    constraints=None
)
 
# 添加自定义力(使用NequIP势能)
class NequIPForce(CustomExternalForce):
    def __init__(self, model, positions):
        super().__init__("0")
        self.model = model
        
        # 设置原子索引
        for i in range(len(positions)):
            self.addParticle(i)
    
    def calcForceAndEnergy(self, positions, velocities, forces, energy):
        # 转换为NequIP格式
        data = AtomicData.from_points(
            positions=positions,
            species=["H", "O", "C"],  # 根据实际分子
            r_max=6.0
        )
        
        # 计算能量和力
        with torch.no_grad():
            output = self.model(data.to("cuda"))
        
        energy[0] = output["energy"].item()
        forces[:, :] = output["forces"].cpu().numpy()

5. 应用案例

5.1 液态水模拟

方法能量RMSE (meV/atom)力RMSE (meV/Å)
经典力场 (TIP4P)~50-
NequIP~8~50
Allegro~6~40

5.2 化学反应

NequIP已成功应用于:

  • 光合作用:光捕获复合物的能量转移
  • 催化:甲醇合成的表面反应
  • 电化学:电极-电解质界面

6. 训练数据生成

6.1 DFT计算流程

# 使用ASE进行DFT计算
from ase import Atoms
from ase.calculators import Vasp
 
# 1. 准备分子结构
atoms = Atoms("H2O", positions=[[0, 0, 0], [0.96, 0, 0], [-0.24, 0.96, 0]])
 
# 2. DFT计算
calc = Vasp(
   xc="PBE",
    kpts=(4, 4, 4),
    encut=500
)
atoms.calc = calc
 
# 3. 获取能量和力
energy = atoms.get_potential_energy()
forces = atoms.get_forces()
 
# 4. 保存为训练数据
with open("h2o_train.xyz", "a") as f:
    f.write(f"{len(atoms)}\n")
    f.write(f"energy={energy} pbc=F\n")
    for sym, pos, f in zip(atoms.symbols, atoms.positions, forces):
        f.write(f"{sym} {pos[0]} {pos[1]} {pos[2]} {f[0]} {f[1]} {f[2]}\n")

6.2 主动学习

# 不确定性驱动的主动学习
class ActiveLearningLoop:
    def __init__(self, model, calculator, uncertainty_threshold=0.1):
        self.model = model
        self.calculator = calculator
        self.threshold = uncertainty_threshold
    
    def select_new_configurations(self, trajectory, n_samples=10):
        """
        从MD轨迹中选择不确定性高的构型
        """
        uncertainties = []
        
        for config in trajectory:
            # 使用多个随机dropout前向传播估计不确定性
            preds = [self.model(config + noise) for _ in range(10)]
            uncertainty = np.std([p["energy"] for p in preds])
            uncertainties.append((config, uncertainty))
        
        # 选择不确定性最高的构型
        uncertainties.sort(key=lambda x: x[1], reverse=True)
        return [c for c, u in uncertainties[:n_samples] 
                if u > self.threshold]
    
    def run(self, initial_configs, n_iterations=5):
        """
        运行主动学习循环
        """
        training_data = list(initial_configs)
        
        for i in range(n_iterations):
            # 1. 训练模型
            self.train(training_data)
            
            # 2. 运行短MD模拟
            trajectory = self.run_md_simulation()
            
            # 3. 选择新构型
            new_configs = self.select_new_configurations(trajectory)
            
            # 4. 计算DFT能量
            for config in new_configs:
                dft_result = self.calculator.calculate(config)
                training_data.append(dft_result)

7. 性能对比

模型参数数量MDPS@H(O)可扩展性
NequIP-3L~300K98.5%
Allegro~500K98.8%优秀
SchNet~500K95.2%优秀
PhysNet~1M97.0%一般

参考文献


相关主题等变图神经网络, PINNs, 神经算子

Footnotes

  1. Batzner et al. “E(3)-Equivariant Graph Neural Networks for Data-Efficient and Accurate Molecular Dynamics.” arXiv:2201.01288, 2022.