2. 材料数据的图结构表达
图神经网络处理材料问题的第一步,不是搭模型,而是把材料“翻译”成图。翻译规则一旦确定,后续所有特征、标签、模型容量和泛化能力都被它约束。本节的核心任务是建立一套可复用的图构建规范:明确什么做节点、什么做边、什么进全局特征,并让不同材料形态(晶体、分子、非晶态、缺陷、界面)在同一套框架下可比较、可批处理。
一、节点、边、全局特征的分工
材料图通常写成 G = (V, E, u)。节点 V 承载“局部环境”,边 E 承载“相互作用与几何关系”,全局特征 u 承载“整张图共享的上下文”。三者分工清晰,模型才能把局部化学环境与整体物性解耦。
- 节点:最常见的选择是原子。节点特征包括元素种类(H、C、O、Si、Fe…)、原子序数、电负性、共价半径、价电子数、形式电荷、杂化类型、局部配位数等。对晶体也可用“晶格位点”或“Wyckoff 位置”做节点,但工程上更常用原子节点,便于跨体系迁移。
- 边:边表示原子间是否存在相互作用,以及这种作用的强度与方向。边特征可包含距离
d、键型(共价/离子/金属/范德华)、键级、方向向量、角度信息。对周期性体系,边必须考虑周期性镜像,否则边界原子会丢失邻居。 - 全局特征:整张图共享的量,如化学式、空间群、晶系、晶格常数、温度、压力、总电荷、自旋态、目标物性所属的体系类别。全局特征通过全局节点或池化向量注入,避免每个节点重复携带相同信息。
二、五类材料形态的图化策略
晶体结构:以原子为节点,以截断半径 r_cut 内的原子对为边。关键是处理周期性:对每个原子,在 3×3×3 超胞中搜索邻居,再按最小镜像距离去重。例如 NaCl,r_cut = 3.0 Å 时 Na–Cl 最近邻约 2.82 Å 成边,Na–Na 约 3.99 Å 超出截断不成边,图自然呈现岩盐结构的二部图特征。晶格常数、空间群、化学式写入全局特征。
分子:节点为原子,边优先用化学键(键级、键型),再补充 r_cut 内的非键相互作用(氢键、π–π 堆积)。分子没有周期性,但需要处理构象:同一分子不同构象应生成不同图,或把构象作为全局特征/多图集成。分子图通常还带形式电荷与自旋多重度。
非晶态:没有长程周期,只有短程有序。图化时仍用原子节点与截断边,但必须关注统计稳健性:r_cut 要覆盖第一配位壳(如 SiO₂ 非晶中 Si–O 约 1.6 Å,r_cut = 2.0 Å 可稳定捕获),并对同一成分采样多个构型,避免单帧图过拟合。配位数分布、键角分布可作为全局特征或边特征。
缺陷:空位、间隙原子、替位、位错、晶界都改变局部图。空位意味着删除节点并重连其邻居;间隙原子意味着新增节点并建立新边;替位意味着改变化学种类。缺陷图的关键是“参照系”:通常以完美晶格为基准,用缺陷类型、位置、浓度、形成能作为全局或节点标签。位错与晶界则需在局部配位畸变上体现,边特征可加入应变或键长偏离量。
界面:两个相接触时,图应保留两侧子图,并在界面区建立跨相边。界面图的核心是“界面区域”的识别:按距离或配位环境划分界面原子,界面原子之间的边权重可加强,以突出界面相互作用。界面能、粘附功、界面结构类型写入全局特征。
三、图构建的工程规范
把上述策略落成可执行代码,需要固定一组工程约定,否则同一材料在不同脚本里会生成不同图,模型无法复现。
- 截断半径与邻居搜索:
r_cut必须覆盖目标相互作用。共价/离子体系常用 3–5 Å;金属体系可到 5–6 Å;范德华主导的分子晶体需额外加色散修正或显式非键边。搜索时用scipy.spatial.cKDTree或pymatgen的get_all_neighbors,并显式处理周期性镜像。 - 节点顺序与去重:按元素符号、坐标字典序或原始结构顺序固定节点索引,保证同一结构多次读取得到相同图。周期性体系必须对镜像原子去重,避免同一物理邻居被重复计数。
- 边特征归一化:距离用
d / r_cut或高斯展开;方向向量单位化;键型用 one-hot。不同体系的边特征尺度要统一,否则批处理时梯度会被大数值主导。 - 全局特征编码:连续量(晶格常数、温度、压力)做标准化;类别量(晶系、空间群、缺陷类型)做 one-hot 或嵌入。全局特征维度固定,缺失值用掩码标记,不要用 0 冒充真实值。
- 批处理与填充:不同材料图大小不同,用
torch_geometric的DataLoader或batch向量拼接,配合edge_index偏移。填充节点必须用mask排除,避免污染池化结果。 - 可复现性:把
r_cut、邻居搜索算法、节点排序规则、特征归一化参数写入配置文件,与模型权重一起版本化。任何一次图构建参数变更,都应触发数据版本号更新。
r_cut 从 3 Å 调到 5 Å,边数可能翻数倍,显存和计算量随之上升。建议先用小体系扫描 r_cut 与验证集误差的关系,再确定最终值,而不是直接套用文献默认值。
四、一个可执行的构建示例
以岩盐 NaCl 为例,用 pymatgen 读取 CIF 后构建图:
from pymatgen.core import Structure
from pymatgen.analysis.local_env import CrystalNN
import numpy as np
s = Structure.from_file("NaCl.cif")
cnn = CrystalNN()
r_cut = 3.0
nodes, edges, edge_feats = [], [], []
for i, site in enumerate(s):
nodes.append(site.specie.Z)
for j, d in cnn.get_nn_info(s, i):
if d["weight"] > 0 and d["site_index"] != i:
vec = d["site"].coords - site.coords
dist = np.linalg.norm(vec)
if dist <= r_cut:
edges.append((i, d["site_index"]))
edge_feats.append([dist / r_cut, *vec / dist])
global_feat = [s.lattice.a, s.lattice.b, s.lattice.c,
s.lattice.alpha, s.lattice.beta, s.lattice.gamma]
运行后应检查:节点数等于晶胞原子数;边数约为每个原子最近邻数之和的一半(NaCl 中每个 Na 有 6 个 Cl 邻居,每个 Cl 有 6 个 Na 邻居,去重后边数 = 原子数 × 6 / 2);边特征中距离归一化值应落在 (0, 1];全局特征维度为 6。若边数明显偏少,通常是周期性镜像未处理或 r_cut 过小;若边数偏多,检查是否把次近邻也纳入。