RESEARCH NOTE · DP-GEN

DP-GEN Note

从安装与初始化,到并行训练、模型偏差筛选和最终测试的一份工作流笔记。

1. DP-GEN 原理及安装

1.1 DP-GEN 原理

DP-GEN (Deep Generator) :控制工作流

  • * a)init:调用第一性原理计算软件产生初始数据(init_bulk / init_surf)
  • * b)run: DP-GEN 主流程,调用 DeePMD-kit、Lammps、VASP,实现同步学习策略,自动迭代扩充数据集并提升模型质量

00.train (training):DeePMD-kit 训练势函数

01.model_devi (exploration):DP-GEN 探索相空间(初始数据集,经典 MD),并标记不准确的数据点

02.fp (labeling):DFT 重新计算这些不准确的数据点(candidate),并使用 dpdata 转化数据格式,添加扩充数据集

循环训练

  • * c)test:应用 DP 模型、第一性原理方法及经验力场,计算体系的多种性质

1.2 DP-GEN 安装

  • * git 安装
git clone https://gitee.com/deepmodeling/dpgen

cd apgen
pip install setuptools_scm
pip install --user .
  • * pip安装
pip install dpgen

pip install oss2 # 安装依赖
dpgen -h

2. DP-GEN 训练流程

Reference: DP-GEN运行 无痛上手指南--LiSi晶体的DP势函数构建 ↗

DP-GEN 运行文件:

  • * 输入参数文件: param.json(init & run)
  • * 机器配置文件: machine.json
  • * VASP计算参数文件:
  1. INCAR_rlx:init 过程,晶体结构弛豫
  2. INCAR_md:init 过程,AIMD 模拟
  3. INCAR_scf:run 过程,labeling 阶段单点计算
  • * K 点信息在 INCAR 中设置(KSPACING,KGAMMA)
  • * VASP 赝势文件: POTCAR_E1POTCAR_E2,根据体系元素种类分别设置
  • * VASP 结构文件: POSCAR

2.1 init

2.1.1 init 过程主要步骤
  • * 根据体系是 bulk / surface,选择使用子命令:init_bulk / init_surf
  • * 分 4 个顺序步骤(stages),输出文件夹为 "POSCAR.01x01x01"
  1. 生成初始结构(s1):可选择是否优化初始结构,输出文件存储在 "00.place_ele"
  2. 产生MD起始构象(s2):放缩扰动 s1 中的结构,输出文件存储在 "01.scale_pert"
  3. 执行AIMD(s3):调用 VASP 对 s2 起始构象执行少步数 AIMD,输出文件存储在 **"02.md/scale-*"**
  4. 整理初始数据(s4):调用 dpdata 提取 s3 中各 snapshot 数据,输出文件存储在 "02.md/deepmd"
2.1.2 dpgen init_bulk 输入文件
INCAR_rlx  INCAR_md  POSCAR  POTCAR_E1  POTCAR_E2  machine.json  param.json
  • * 输入参数文件 param.json
{
    "stages":           [1, 2, 3, 4],        // 顺序执行4个步骤, 可只执行其中部分步骤
    "elements":         ["Li", "Si"],        // 原子类型
    "cell_type":        "diamond",           // s1生成结构的晶型: fcc, hcp, bcc, sc, diamond
    "latt":             10.0,                // s1生成结构的单胞晶格常数
    "super_cell":       [1, 1, 1],           // s1生成结构的超胞, 对POSCAR也适用
    "from_poscar":      true,                // s1是否采用POSCAR作为初始弛豫结构。true: cell_type, latt失效, false: cell_type, latt须赋值
    "from_poscar_path": "./POSCAR_LiSi",     // s1 POSCAR文件路径
    "potcars":          ["./POTCAR_Li", "./POTCAR_Si"],    // VASP计算POTCAR文件路径
    "relax_incar":      "./INCAR_rlx",       // VASP弛豫计算的INCAR路径, 含s1时必须赋值, 且此时不受skip_relax赋值的影响
    "md_incar" :        "./INCAR_md",        // VASP AIMD模拟的INCAR路径, 含s3时必须赋值
    "skip_relax":       false,               // true: s2对s1未弛豫的POSCAR做扰动, false: s2对s1弛豫后的CONTCAR做扰动
    "scale":            [0.99, 1.00, 1.01],  // s2缩放超胞盒子的缩放系数
    "pert_numb":        10,                  // 控制每种scale下生成多少个微扰结构(同时扰动晶格矢量和原子位置)
    "pert_box":         0.03,                // 扰动盒子尺寸矢量的比率, 扰动值/原值, 从[-pert_box, pert_box]中随机采样
    "pert_atom":        0.01,                // 扰动原子坐标位置的大小(Å), 从[-pert_atom, pert_atom]中随机采样
    "md_nstep" :        10,                  // s3中vasp做AIMD模拟的步数, 以NSW赋值为准
    "coll_ndata":       5000,                // s4收集AIMD数据的最大数量
    "type_map" :        ["Li", "Si"],        // s4按deepmd文件格式记录元素种类的顺序, 需与'elements', 'potcars'及POSCAR文件顺序一致
    "_comment":         "that's all"
}
2.1.3 提交 dpgen init_bulk 任务
nohup dpgen init_bulk param.json machine.json 1>log 2>err &

# 或
tmux
module load deepmd/2.2.7 && source activate && conda activate deepmd-2.2.7
dpgen init_bulk param.json machine.json 1>log 2>err &
2.1.4 dpgen init_bulk 输出文件
POSCAR.01x01x01 # init_bulk 过程特有, 保存输出文件和数据
dpgen.log # 正常结束显示 INFO: Current stage is 4, collect data, 任务中断会停止在某个stage
dpdispatcher.log
log, err # 运行日志,报错信息
  • * 初始训练数据集 "POSCAR.01x01x01/02.md/deepmd"
# 模拟含 n 帧 (AIMD 离子步),每帧含 N 个原子 (体系原子数) 时间(ps) 长度(Angstrom) 能量(eV) 力(eV/Angstrom) Virial(eV) Pressure(Bar)
# 如果训练非周期系统,应该在系统目录下放一个空的 nopbc 文件,此时 box.raw 不是必需的
.
├── box.raw        # nx9     每帧含 3x3 盒子基矢
├── coord.raw      # nx(Nx3) 每个原子的 xyz 坐标
├── energy.raw     # nx1     每帧含 1 个能量值
├── force.raw      # nx(Nx3) 每个原子的受力 xyz 分量
├── set.000        # Numpy   二进制数据集,可用脚本 $deepmd_source_dir/data/raw/raw_to_set.sh + 每个 set 的数量,将原始文件转换为数据集
│   ├── box.npy
│   ├── coord.npy
│   ├── energy.npy
│   ├── force.npy
│   └── virial.npy
├── type_map.raw   # typex1  元素类型的顺序
├── type.raw       # Nx1     原子种类 (0, 1, 2, ...)
└── virial.raw     # nx9     每帧含 3x3 的维里张量 XX XY XZ YX YY YZ ZX ZY ZZ

2.2 run

2.2.1 run 过程主要步骤
  • * dpgen run 主流程包含一系列迭代 (iteration) ,覆盖势能面(PES)上尽可能多的样本空间
  • * 各迭代根据所选系综在给定的温度压力体积等条件下进行 DPMD 采样与筛选,实现同步学习
  • * 输出文件存储在:__".../iter.00000*/00.train & 01.model_devi & 02.fp"__ 文件夹
  • * 每个迭代 __iter.00000\*__ 包含 3 个顺序步骤:
  1. 00.train (Training):调用 DeePMD-kit dp train input.json
  2. 基于 init 准备的以及之前迭代积累的训练数据,训练多个(默认 4 个)新模型

    不同的模型使用不同的随机数种子初始化神经网络

  1. 01.model_devi (Exploration):调用 LAMMPS lmp -i input.lammps
  2. 使用 00.train 的 1 个模型进行 DeePMD-kit 采样(MD模拟)

    引入模型间预测偏差(model-deviation)作为误差判据,并设定上下限

    任一 MD 中的 snapshot 构象的模型间预测偏差越大,则当前模型系综对该构象的精度越低

    挑选出有希望改进模对 PES 整体预测精度的构象

  1. 02.fp (Labeling):调用 VASP mpirun -n 30 vasp_std
  2. 对 01.model_devi 挑选的 snapshot 候选构象进行第一性原标理标定(单点计算)

    调用 dpdata 收集整理所得数据,加入训练数据集以扩展数据集

  • * 每个步骤分为 3 个顺序阶段:
  1. make:生成相应步骤的输入文件
  1. run:根据机器配置执行训练任务
  1. post:收集整理输出文件,分析训练结果
  • * DeePMD-kit 的数据单位

| Time | Length | Energy | Force | Pressure |

|:------:|:--------:|:--------:|:-------:|:----------:|

| ps | Å | eV | eV/Å | bar |

$\mathrm{1\ ps = 10^{-12}\ s}$

$\mathrm{1\ eV = 1.602\times10^{-19}\ J\ (N·m)}$

$\mathrm{1\ eV/\AA = 1.602\times10^{-9}\ N}$

$\mathrm{1\ GPa = 10^9\ Pa\ (N/m^2) = 10\ kbar}$

2.2.2 dpgen run 输入文件

注:param.json 参数文件为 run 过程特有,machine.json 无需重新配置

POSCAR.01x01x01(POSCARs, deepmd)  INCAR_scf  POTCAR_E1  POTCAR_E2  machine.json  param.json
  • * 输入参数文件 param.json
  • 包含用于生成 input.lammps、input.json 等文件的信息

{
    "type_map": ["Zr", "Hf", "O"],
    "mass_map": [91.22, 178.49, 15.99],
    "init_data_prefix": "../init/",
    "init_data_sys": [
        "./POSCAR.01x01x01/02.md/sys-0032-0064/deepmd"  // 初始数据集
    ],
    "sys_configs_prefix": "../init/",
    "sys_configs": [
        ["./POSCAR.01x01x01/02.md/sys-0032-0064/scale-1.000/00000[0-9]/POSCAR"]  // 需要训练的结构
    ],

    "_comment": " 00.train ",
    "numb_models": 4,
    "_model_devi_activation_func": [["tanh","tanh"], ["tanh","gelu"], ["gelu","tanh"], ["gelu","gelu"]],
    "default_training_param": {
        "model": {
            "type_map": ["Zr", "Hf", "O"],
            "descriptor": {  // 描述符网络
                "type": "se_e2_a",  // 二阶平滑版本的嵌入原子对称描述符
                "sel": "auto", // [100, 100, 100] 每个中心原子周围最大选择数
                "rcut_smth": 0.5,  // 描述符平滑截止半径(Å),在此距离内原子间作用力平滑过渡到 0
                "rcut": 6.0,  // 截断半径(Å),超出此距离的原子不计入环境描述。
                "neuron": [25, 50, 100],  // 描述符神经网络层结构,3 层分别含 25、50、100 个神经元
                "resnet_dt": false,  // 禁用残差连接(ResNet)的时间步参数
                "_type_one_side": true,
                "axis_neuron": 12,  // 旋转不变性描述符的轴神经元数,影响特征维度
                "seed": 1  // 随机数种子
                },
            "fitting_net": {  // 拟合网络
                "neuron": [240, 240, 240],  // 3 层全连接层,每层 240 个神经元,用于从描述符到原子能量的映射
                "resnet_dt": true,  // 启用残差连接,缓解深层网络梯度消失问题
                "seed": 1  // 初始化种子
                }
        },
        "learning_rate": {  // 学习率策略
            "type": "exp",  // 指数衰减学习率,公式:lr = start_lr * (decay_rate)^(step/decay_steps)
            "start_lr": 0.001,  // 初始学习率(较高,用于快速收敛)
            "_stop_lr": 3.51e-8,
            "decay_steps": 4000,  // 每 4000 训练步衰减一次 = stop_batch / 200
            "_decay_rate": 0.99  // 每次衰减保留原学习率的 99%
        },
        "loss": {  // 损失函数权重
            "_type": "ener",
            "start_pref_e": 0.02,  // 能量项权重,从 0.02 逐步提升到 1
            "limit_pref_e": 1,
            "start_pref_f": 1000,  // 力项权重,初始极高(1000),逐步降到1,强制优先拟合力的准确性
            "limit_pref_f": 1,
            "start_pref_v": 0.02,  // 维里应力项权重,从 0.02 到 1,用于压力相关训练
            "limit_pref_v": 1 
        },
        "training": {  // 训练过程参数
            "_set_prefix": "set",  // 训练集/验证集路径前缀,如 set.000
            "stop_batch": 800000,  // 最大训练步数
            "disp_file": "lcurve.out",  // 损失曲线输出文件
            "disp_freq": 2000,  // 每 2000 步输出一次训练信息
            "numb_test": 4,  // 验证集每次测试用 4 个样本
            "save_freq": 2000,  // 每 2000 步保存一次模型
            "save_ckpt": "model.ckpt", // 模型检查点保存路径
            "disp_training": true,  // 显示训练集误差
            "time_training": true,  // 记录各阶段耗时
            "profiling": false,  // 禁用性能分析,启用会生成 timeline.json
            "profiling_file": "timeline.json"
        }
    },

    "_comment": " 01.model_devi ",
    "model_devi_dt": 0.001,  //  LAMMPS timestep 0.001 ps
    "model_devi_skip": 0,  // 分析时跳过的初始步数,0 表示从第一步开始分析,常用于排除平衡阶段的不稳定数据
    "model_devi_f_trust_lo": 0.15,  // "0.10" 力预测偏差的信任下限(eV/Å)
    "model_devi_f_trust_hi": 0.35,  // "0.25" 力预测偏差的信任上限(eV/Å)
    "model_devi_clean_traj": true, // 任务完成后自动删除原始轨迹文件
    "model_devi_jobs": [
        {"sys_idx": [0],"temps": [200, 300, 400, 500], "press": [1.0, 100.0, 1000.0, 10000.0, 50000.0], "trj_freq": 5, "nsteps": 1000, "ensemble": "npt_tri", "_idx": "00"},
        {"sys_idx": [0],"temps": [200, 300, 400, 500], "press": [1.0, 100.0, 1000.0, 10000.0, 50000.0], "trj_freq": 5, "nsteps": 1000, "ensemble": "npt_tri", "_idx": "01"},
        {"sys_idx": [0],"temps": [200, 300, 400, 500], "press": [1.0, 100.0, 1000.0, 10000.0, 50000.0], "trj_freq": 10, "nsteps": 3000, "ensemble": "npt_tri", "_idx": "02"},
        {"sys_idx": [0],"temps": [200, 300, 400, 500], "press": [1.0, 100.0, 1000.0, 10000.0, 50000.0], "trj_freq": 10, "nsteps": 3000, "ensemble": "npt_tri", "_idx": "03"},
        {"sys_idx": [0],"temps": [200, 300, 400, 500], "press": [1.0, 100.0, 1000.0, 10000.0, 50000.0], "trj_freq": 20, "nsteps": 5000, "ensemble": "npt_tri", "_idx": "04"},
        {"sys_idx": [0],"temps": [200, 300, 400, 500], "press": [1.0, 100.0, 1000.0, 10000.0, 50000.0], "trj_freq": 20, "nsteps": 8000, "ensemble": "npt_tri", "_idx": "05"}
    ],

    "_comment": " 02.fp ",
    "fp_style": "vasp",
    "shuffle_poscar": false,  // 保持筛选出的 POSCAR 文件顺序,不打乱结构的时间序列
    "fp_accurate_threshold": 0.999,  // 如果精确比例大于这个数值,则不会执行 fp 计算,即 fp_task_max = 0
    "fp_accurate_soft_threshold": 0.997, // 如果精确比例在这个数字和 fp_accurate_threshold 之间,fp_task_max 会线性衰减至零
    "fp_task_max": 30,  // 每个VASP任务最大包含50个结构(避免单个任务过大)
    "fp_task_min": 5,  // 至少积累10个新结构才启动VASP计算(提高计算效率)
    "ratio_failed": 0.05,  // 检查未成功终止的任务比例,如果过多 FP 任务未收敛,引发 RuntimeError
    "fp_pp_path": "./",
    "fp_pp_files": ["POTCAR_Zr", "POTCAR_Hf", "POTCAR_O"],
    "fp_incar": "./INCAR_scf"
}
2.2.3 提交 dpgen run 任务
nohup dpgen run param.json machine.json > log &

# 或
tmux
module load deepmd/2.2.7 && source activate && conda activate deepmd-2.2.7
dpgen run param.json machine.json 1>log 2>err &
2.2.4 dpgen run 输出文件
inter.00000* # 一系列文件夹, 每个按序进行 training -> exploration -> labeling 过程,详情见下
record.dpgen # 记录已执行的任务阶段,DP-GEN 据此恢复 run 主进程
dpgen.log, dpdispatcher.log # 任务执行、分配的详细信息
log # 运行日志
  • * dpgen.log
  • 记录详细计算流程

    注意观察 02.fp 步骤中的 candidate、failed、accurate

  • * record.dpgen
  • 记录已执行的任务阶段

| 迭代步骤<br>(iter.00000*) | 迭代阶段<br>(task.0*) |迭代阶段名称<br>(make & run & post)|

|:------------------------------------------:|:--------------------------------------:|:------------------------------:|

| 0 | 0 | make_train |

| 0 | 1 | run_train |

| 0 | 2 | post_train |

|---------------|-----------|-------------------------|

| 0 | 3 | make_model_dev |

| 0 | 4 | run_model_devi |

| 0 | 5 | post_model_devi |

|---------------|-----------|-------------------------|

| 0 | 6 | make_fp |

| 0 | 7 | run_fp |

| 0 | 8 | post_fp |

|---------------|-----------|-------------------------|

| 1 | 0 | make_train |

| 1 | 1 | run_train |

| ... | ... | ... |

  • * 势函数最终长训需调整 numb_steps 至数百万步,并调整 decay_steps = numb_steps/200

2.3 test

通过能量与体积的关系曲线(EOS)弹性常数表面能点缺陷形成能层错能声子等性质评估 DP 模型

2.3.1 模型测试
  • * 压缩势函数,并链接到 LAMMPS 计算目录
cd run/iter.0000**/00.train/00[0-3]
dp compress -i frozen_model.pb -o graph.pb

cd work_dir
ln -s ~/home/work/hzo_dp/run/iter.000061/00.train/000/graph.pb
  • * 准备结构文件,运行 LAMMPS
module load deepmd/2.2.7 && source activate && conda activate deepmd-2.2.7

lmp -i in.lammps
2.3.2 使用 autotest 模块自动化测试
  • * 在当前文件夹准备 graph.pb、in.lammps 或 INCAR、POTCAR_*
  • * 创建 "confs" 文件夹,并在其中准备 POSCAR 原胞文件;创建 "work" 文件夹
  • * 分别对 relax 和 property 进行:准备 “make” 、运行 “run” 和后处理 “post”
dpgen autotest make relaxation.json && cp ./confs/relaxation/in.lammps . && dpgen autotest run relaxation.json machine.json && dpgen autotest post relaxation.json
# cp confs/relaxation/POTCAR .

dpgen autotest make property.json && dpgen autotest run property.json machine.json && dpgen autotest post property.json
  • * 机器配置文件 machine.json 可直接使用
  • * VASP 自动化测试文件
# 相同部分
{
    "structures": ["./confs"],
    "interaction": {
        "type": "vasp",
        "incar": "./INCAR",
        "potcar_prefix": "./",
        "potcars": {"Zr": "POTCAR_Zr", "Hf": "POTCAR_Hf", "O": "POTCAR_O"}
    },

# 结构优化 relaxation.json
    "relaxation": {
        "cal_type": "relaxation",
        "cal_setting": {
            "relax_pos": true,
            "relax_shape": true,
            "relax_vol": true,
            "ediff": 1e-6,
            "ediffg": -0.01,
            "encut": 600,
            "kspacing": 0.30,
            "kgamma": false}
        }
}

# 性质计算 property.json
    "properties": [
        {
            "type": "eos",
            "vol_start": 0.9,
            "vol_end": 1.1,
            "vol_step": 0.01,
            "fix_shape": true
        },
        {
            "type": "vacancy",
            "supercell": [1, 1, 1]
        }
    ]
}
  • * LAMMPS 自动化测试文件
# 相同部分
{
    "structures": ["./confs"],
    "interaction": {
        "type": "deepmd",
        "model": "./graph.pb",
        "deepmd_version": "2.2.7",
        "in_lammps": "./in.lammps",
        "type_map": {"Zr": 0, "Hf": 1, "O": 2}
    },

# 结构优化 relaxation.json
    "relaxation": {
        "cal_setting":{
            "etol": 1e-12,
            "ftol": 1e-10,
            "maxiter": 10000,
            "maximal": 500000,
            "relax_shape": true,
            "relax_vol": true
        }
    }
}

# 性质计算 property.json
    "properties": [
        {
            "type": "eos",
            "vol_start": 0.9,
            "vol_end": 1.1,
            "vol_step": 0.01,
            "fix_shape": true
        },
        {
            "type": "vacancy",
            "supercell": [1, 1, 1]
        }
    ]
}

3. dpgen run 输出文件夹

3.1 __"inter.00000*/00.train"__ 文件夹(training

  • * graph.00*.pb:指向 00*/frozen.pb 的软链接文件(DeePMD-kit 的训练模型)
  • * data.init:指向 param.json 中 init_data_prefix 的软链接文件
  • * "00*" 文件夹(4个模型,000-003):
frozen_model.pb # DeePMD-kit 训练产生的模型
input.json # 当前任务的 DeePMD-kit 训练的输入文件
lcurve.out # 训练过程的误差,判断模型质量
train.log # 训练过程的标准输出

3.2 __"inter.00000*/01.model_devi"__ 文件夹(exploration

  • * "conf" 文件夹:存储起始结构 POSCAR 与相应的 LAMMPS 格式起始结构
  • * cur_job.json:model_devi_jobs 指定的当前迭代任务的 MD 参数信息
  • * "task.00*.00000*" 文件夹:
  1. 00*:model_devi_jobs["sys_idx"] 中的某个元素(对应 sys_configs)的起始构象探索
  2. 00000*:若每次迭代只调用第 1 个元素 [0](10个构象),温度和压强各设置 4 和 5 个值,则有 200 个MD任务,对应 "task.000.00000[0-199]" 文件夹
  3. 完成后分别将轨迹和构象存储在此文件夹下:

conf.lmp # 当前任务 LAMMPS 格式的起始构象
input.lammps # 当前任务由 DP-GEN 自动生成的 LAMMPS 参数脚本
model_devi.out # 记录 DPMD 采样构象的能量和力的模型偏差
  1. 根据 'trj_freq': 20 ,每 20 帧保存一帧构象轨迹
  2. 根据力误差设定值 'model_devi_f_trust_lo'"model_devi_f_trust_hi" 将探索构象筛选为 “候选构象”,发送到 "02.fp" 文件夹
  3. model_devi_jobs 指定的迭代进行完毕,会额外进行最后一个迭代:使用初始数据及迭代积累的所有新增数据,只进行 00.train 后主进程执行完成

3.3 __"inter.00000*/02.fp"__ 文件夹(labeling

  • * candidate.shuffle.00*.out:记录 “候选构象” 结构在 01.model_devi 中的任务号和帧数
  • * rest_accurate.shuffle.00*.out:记录 max_devi_f 小于 model_devi_f_trust_lo 的构象在 01.model_devi 中的任务号和帧数
  • * rest_failed.shuffle.00*.out:记录 max_devi_f 大于 model_devi_f_trust_hi 的构象在 01.model_devi 中的任务号和帧数
  • * POTCAR.00*:相应结构的赝势文件
  • * "task.00*.00000*" 文件夹:
  1. 00*:以某个元素为起始构象的探索
  2. 00000*:以 fp_task_max 指定值进行DFT计算的任务序号
  3. 01.model_devi 筛选出的 “候选构象” 数量通常远大于 fp_task_max 设定的上限,DP-GEN 随机选择设定值数量的构象进行 DFT 计算
  • * "data.00*" 文件夹:
  1. 存储 “候选构象” 的能量、力、维里等性质的DFT计算完成后,DP-GEN 将其收集并转换为 DeePMD-kit 格式的训练数据
  2. 新数据与初始数据一起被加入到扩大的数据集,在下一个迭代的 00.train 中开始新的DP势函数训练
  3. 若最后一个迭代的候选构象数小于 fp_task_min 指定值,则该迭代直接拷贝上一迭代的 "00.train" 文件夹

4. DP 训练模型分析

4.1 模型训练分析

4.1.1 每次迭代准确、候选和失败构型的百分比
  • * 模型精度达到 99.9% 以上,可认为势函数较好
  • * 以 4 个迭代为一轮数据,进一步调参
cd run
python ~/home/document/scripts/iter_task_stats.py 60 60 # 指定开始和结束的迭代
4.1.2 损失函数分析 lcurve.out
  • * lcurve.out 共 8 列:训练步长、验证损失、训练损失、能量的训练误差、能量的测试误差、力的训练误差、力的测试误差、学习效率
  • * 力的训练精度、测试误差比初始小一个数量级,则训练有效
cd run/iter.0000**/00.train/00[0-3]
python ~/home/document/scripts/lcurve_plot.py
  • * 绘图
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

mpl.rcParams['pdf.fonttype'] = 42
plt.rcParams['mathtext.fontset'] = 'stix'
mpl.rcParams['font.serif'] = ['Times New Roman']
font = {'family':'Times New Roman', 'size':17, 'weight':'normal'}
font_l = {'family':'Times New Roman', 'size':14, 'weight':'normal'}

fig, ax = plt.subplots(1, 1, figsize=(5, 4), dpi=100)

# step  rmse_trn  rmse_e_trn (eV/atom)  rmse_f_trn (eV/Å)  rmse_v_trn (eV/atom)  lr
data = np.genfromtxt("./data/lcurve_002.out", names=True)
for name in data.dtype.names[1:-1]:
    # print(name)
    # if name == 'rmse_f_trn':
        print(data[name][-2000:].mean())
        ax.plot(data['step'], data[name], label=name)

# gen  L_t  L_1  L_2  L_e_train (eV/atom)  L_f_train (eV/Å)  L_v_train (eV/atom)  L_e_test  L_f_test  L_v_test
# data = np.genfromtxt("./data/loss.out", names=None)
# ax.plot(data[:, 0], data[:, 4], color="tab:red", label='rmse_e')
# ax.plot(data[:, 0], data[:, 5], color="tab:blue", label='rmse_f')
# ax.plot(data[:, 0], data[:, 6], color="tab:green", label='rmse_v')

ax.set_xlabel('Training Steps', fontdict=font)
ax.set_ylabel('Loss', fontdict=font)
# ax.set_xscale('symlog')
ax.set_yscale('log')
ax.grid(True, linestyle=':', alpha=0.7)
ax.legend(loc=0, prop=font_l)
# ax.set_xlim(-10000, 3000000)
# ax.set_ylim(0, 0.8)

ax.xaxis.get_offset_text().set_fontproperties({'family': 'Times New Roman', 'size': 14})
ax.tick_params(axis='both', which='major', direction='in', length=4, width=1.2, top=True, right=True, labelsize=16)
for tick in ax.get_xticklabels() + ax.get_yticklabels():
    tick.set_fontname('Times New Roman')

for spine in ax.spines.values():
    spine.set_linewidth(1.2)

plt.show()
# fig.savefig(f'HZO_DP_Loss.png', dpi=1000, bbox_inches='tight', transparent=False)

4.2 最大偏差力的分布

  • * 新模型对 MD 模拟中采样构象的模型偏差力判据保存在 "iter.00000*/01.model_devi/task*/model_devi.out"
  • * 最大偏差力范围设置过高会导致非物理构型成为候选构型,参考:https://doi.org/10.1016/j.cpc.2020.107206
  • * 迭代分析参考:https://doi.org/10.1063/5.0041849
4.2.1 迭代分析
  • * 基于 max_devi_f_iter.py 脚本获得的 **max_devi_f_\*\*.dat**
cd run
python ~/home/document/scripts/max_devi_f_iter.py --iter 60 --plot 56 60 --xlim 0 0.3 # 不画图则去掉 --plot 参数
  • * 绘图
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

mpl.rcParams['pdf.fonttype'] = 42
plt.rcParams['mathtext.fontset'] = 'stix'
font = {'family':'Times New Roman', 'size':17, 'weight':'normal'}
font_l = {'family':'Times New Roman', 'size':14, 'weight':'normal'}

fig, ax = plt.subplots(1, 1, figsize=(5, 4), dpi=100)

iter = 56
for i in range(iter-5, iter+1):
    data = np.loadtxt(f'./data/max_devi_f_iter/max_devi_f_{i:02}.dat', skiprows=1)
    ax.plot(data[:, 0], data[:, 1], linestyle='-',  linewidth=1.5, alpha=0.9, label=f'iter {i:02}')

ax.set_xlabel(r'$\sigma^{max}_{f}\ \mathrm{(eV/\AA)}$', fontdict=font)
ax.set_ylabel('Distribution (%)', fontdict=font)
# ax.set_title("Distribution of Maximum Deviation Forces for " + r'$\mathrm{Hf_xZr_{1-x}O_2}$', fontdict=font)
ax.set_xlim(0, 0.3)

ax.legend(loc=0, edgecolor = 'k', fancybox=False,
          prop=font_l, frameon=False).get_frame().set_linewidth(1)
ax.grid(True, linestyle='--', linewidth=0.6, alpha=0.7)

# ax.xaxis.set_minor_locator(AutoMinorLocator(2)) # 主刻度线间2个次刻度线
# ax.yaxis.set_minor_locator(AutoMinorLocator(2)) # 主刻度线间2个次刻度线
ax.tick_params(axis='both', which='major', direction='in', length=4, width=1, top=True, right=True, labelsize=17)
for tick in ax.get_xticklabels() + ax.get_yticklabels():
    tick.set_fontname('Times New Roman')

for spine in ax.spines.values():
    spine.set_linewidth(1)

# 插入辅助线及文本
ax.axvline(x=0.15, linestyle='--', dashes=(8, 4), linewidth=1, color='k', alpha=0.9)
ax.axvline(x=0.3, linestyle='--', dashes=(8, 4), linewidth=1, color='k', alpha=0.9)
ax.text(0.147, 2.2, r'$\sigma_{lo}=0.15$', horizontalalignment='right', fontdict=font_l)
ax.text(0.297, 2.2, r'$\sigma_{hi}=0.30$', horizontalalignment='right', fontdict=font_l)

plt.show()
# fig.savefig(f'HZO_DP_MDF_iter_{iter-5}-{iter}.png', dpi=1000, bbox_inches='tight', transparent=False)
4.2.2 温度分析
  • * 基于 max_devi_f_temp.sy 脚本获得的 **max_devi_f_temp_\*\*_\*\*-\*\*.dat**
cd run
python ~/home/document/scripts/max_devi_f_temp.py --iter 60 --temps 2700,2800,2900,3000,3100,3200 --plot --xlim 0 0.3 # 不画图则去掉 --plot 参数
  • * 绘图
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

mpl.rcParams['pdf.fonttype'] = 42
plt.rcParams['mathtext.fontset'] = 'stix'
font = {'family':'Times New Roman', 'size':17, 'weight':'normal'}
font_l = {'family':'Times New Roman', 'size':14, 'weight':'normal'}

# 读取数据文件
file_path = './data/max_devi_temp/max_devi_f_50_2200-2600.dat'
with open(file_path, 'r') as f:
    first_line = f.readline().strip()
    
temp_parts = first_line.lstrip('#').split()
temps = [temp_parts[i] + ' ' + temp_parts[i+1] for i in range(0, len(temp_parts), 2)]
print(temps)

data = np.loadtxt(file_path, skiprows=1)

fig, ax = plt.subplots(1, 1, figsize=(5, 4), dpi=100)

for i in range(len(temps)):
    hist, bin_edges = np.histogram(data[:, i], range=(0, 0.6), bins=100)
    hist_percentage = hist / len(data[:, i]) * 100
    ax.plot(bin_edges[:-1], hist_percentage, linestyle='-', linewidth=1.5, alpha=0.9, label=temps[i])

ax.set_xlabel(r'$\sigma^{max}_{f}\ \mathrm{(eV/\AA)}$', fontdict=font)
ax.set_ylabel('Distribution (%)', fontdict=font)
# ax.set_title("Distribution of Maximum Deviation Forces for " + r'$\mathrm{Hf_xZr_{1-x}O_2}$', fontdict=font)
ax.set_xlim(0, 0.2)

ax.legend(loc=0, edgecolor = 'k', fancybox=False,
          prop=font_l, frameon=False).get_frame().set_linewidth(1)
ax.grid(True, linestyle='--', linewidth=0.6, alpha=0.7)

# ax.xaxis.set_minor_locator(AutoMinorLocator(2)) # 主刻度线间2个次刻度线
# ax.yaxis.set_minor_locator(AutoMinorLocator(2)) # 主刻度线间2个次刻度线
ax.tick_params(axis='both', which='major', direction='in', length=4, width=1, top=True, right=True, labelsize=17)
for tick in ax.get_xticklabels() + ax.get_yticklabels():
    tick.set_fontname('Times New Roman')

for spine in ax.spines.values():
    spine.set_linewidth(1)

ax.axvline(x=0.15, linestyle='--', dashes=(8, 4), linewidth=1, color='k', alpha=0.9)
ax.axvline(x=0.30, linestyle='--', dashes=(8, 4), linewidth=1, color='k', alpha=0.9)
ax.text(0.147, 15, r'$\sigma_{lo}=0.15$', horizontalalignment='right', fontdict=font_l)
# ax.text(0.297, 15, r'$\sigma_{hi}=0.30$', horizontalalignment='right', fontdict=font_l)

plt.show()
# fig.savefig('HfO2_DP_MDF_temp_50.png', dpi=1000, bbox_inches='tight', transparent=False)

4.3 DP 与 DFT 的误差分布对比

4.3.1 增加 DFT 数据
  • * 使用 extract_e_dft.py 脚本提取 data.00* 中的 OUTCAR 数据,生成的 test_data 文件夹中包含整理后的 .npy 和 .raw 文件
  • * 进入所有迭代(一般前 5 个)的 "iter.0000[20-24]/02.fp" 文件夹提取数据:
cd run
for i in $(seq -f "%06g" 24 29); do cd iter.$i/02.fp && pwd && python ~/home/document/scripts/extract_e_dft.py && cd ../../; done
4.3.2 获取数据
  • * DP 数据:最后一次迭代 "iter.000030/00.train" 的 00*/frozen_model.pb (4 个模型)
  • * DFT 数据:前几次迭代 "iter.0000[24-29]/02.fp" 的 data.00* (candidates)
  • * 创建并进入最后一个迭代的 "iter.000030/00.train/00x/dp_dft_data" 文件夹,执行:
cd run

[ ! -d "iter.000030/00.train/000/dp_dft_data" ] && mkdir iter.000030/00.train/000/dp_dft_data; cd iter.000030/00.train/000/dp_dft_data # 创建文件夹

for i in $(seq -f "%06g" 24 29); do dp test -m ../frozen_model.pb -s ../../../../iter.$i/02.fp/test_data -n 0 -d dp_dft_$i.out; done # 获取数据

# 第二次训练
python ~/home/document/scripts/extract_e_dft.py
dp test -m ./frozen_model.pb -s ~/home/work/hzo/run1/iter.000060/02.fp/test_data/ -n 0 -d dp_dft.out
  • * 模型准确率测试输出
DEEPMD INFO    # ---------------output of dp test--------------- 
DEEPMD INFO    # testing system : ../../../iter.000028/02.fp/test_data/Zr4O64Hf28
DEEPMD INFO    # number of test data : 14 
DEEPMD INFO    Energy MAE         : 2.409264e-01 eV
DEEPMD INFO    Energy RMSE        : 2.795323e-01 eV
DEEPMD INFO    Energy MAE/Natoms  : 2.509650e-03 eV
DEEPMD INFO    Energy RMSE/Natoms : 2.911795e-03 eV
DEEPMD INFO    Force  MAE         : 1.189774e-01 eV/A
DEEPMD INFO    Force  RMSE        : 1.522510e-01 eV/A
DEEPMD INFO    Virial MAE         : 2.797758e+00 eV
DEEPMD INFO    Virial RMSE        : 4.525023e+00 eV
DEEPMD INFO    Virial MAE/Natoms  : 2.914332e-02 eV
DEEPMD INFO    Virial RMSE/Natoms : 4.713566e-02 eV
4.3.3 合并数据
  • * 力的误差对比数据足够,不再合并
cd iter.000030/00.train/000/dp_dft_data

cat dp_dft_*.e.out > dp_dft_e.out && cat dp_dft_*.e_peratom.out > dp_dft_e_peratom.out && cat dp_dft_*.v.out > dp_dft_v.out && cat dp_dft_*.v_peratom.out > dp_dft_v_peratom.out
4.3.4 绘图
  • * 基于 dp_dft.e_peratom.out,__dp_dft_0000**.f.out__
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import AutoMinorLocator, FormatStrFormatter
import matplotlib as mpl
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import glob
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
import re
from matplotlib import cm

mpl.rcParams['pdf.fonttype'] = 42 # 可编辑pdf,字体:STIXGeneral-Regular
plt.rcParams['mathtext.fontset'] = 'stix' # 公式字体单独设置

font = {'family':'Times New Roman', 'size':18, 'weight':'normal'}
font_in = {'family':'Times New Roman', 'size':10, 'weight':'normal'}
font_le = {'family':'Times New Roman', 'size':12, 'weight':'normal'}
font_le2 = {'family':'Times New Roman', 'size':14, 'weight':'normal'}
font_text = {'family':'Times New Roman', 'size':20, 'weight':'normal'}

# 定义误差分析函数
def calc_err(true_val, pred_val):
    r2 = r2_score(true_val, pred_val)
    rmse = np.sqrt(mean_squared_error(true_val, pred_val))
    return r2, rmse

# 按浓度分类读取能量数据
files = glob.glob('./data/dp_dft/dp_dft_e_peratom.out')
e_data = {}
for f in files:
    with open(f, 'r') as file:
        lines = file.readlines()
        current_group = None
        for line in lines:
            if line.startswith("#"):
                # 使用正则表达式提取Hf后的数字
                match = re.search(r'Hf(\d+)', line)
                if match:
                    Hf = float(match.group(1))
                    gkey = Hf/32
                    if gkey not in e_data:
                        e_data[gkey] = {'e_dft': [], 'e_dp': []}
                    current_group = gkey
            else:
                data = line.strip().split() # 读取数据行
                if len(data) == 2 and current_group is not None:
                    e_dft = float(data[0])
                    e_dp = float(data[1])
                    e_data[current_group]['e_dft'].append(e_dft)
                    e_data[current_group]['e_dp'].append(e_dp)

# 正常读取能量数据
# e_data = np.vstack([np.loadtxt(f, comments='#') for f in glob.glob('./data/dp_dft/dp_dft_e_peratom.out')])
f_data = np.loadtxt('./data/dp_dft/dp_dft_000024.f.out', comments='#')

# e_dft = e_data[:, 0]
# e_dp = e_data[:, 1]

fx_dft, fy_dft, fz_dft = f_data[:, 0], f_data[:, 1], f_data[:, 2]
fx_dp, fy_dp, fz_dp = f_data[:, 3], f_data[:, 4], f_data[:, 5]

x = np.linspace(-20, 20, 400)
y = x

fig, axes = plt.subplots(1, 2, figsize=(10, 4), dpi=130)

#------------------------------------------Plot Energy Data-----------------------------------------#

sorted_e_data = sorted(e_data.items(), key=lambda x: x[0]) # 分类的能量数据从小到大排序
colors = cm.viridis(np.linspace(0, 1, len(sorted_e_data)))  # 使用viridis颜色图

for i, (gkey, data) in enumerate(sorted_e_data):
    e_dft = np.array(data['e_dft'])
    e_dp = np.array(data['e_dp'])
    axes[0].scatter(e_dft, e_dp, s=5, color=colors[i], alpha=0.8,  label=f'x = {gkey:.3f}'.rstrip('0').rstrip('.'))

# axes[0].scatter(e_dft, e_dp, s=5, color='#1f78b4', alpha=0.8, label=r'$\mathrm{E}$')

axes[0].plot(x, y, color='grey', linestyle='-', linewidth=1.3, alpha=0.8)

tick_min_1 = -10.5
tick_max_1 = -9.4
axes[0].set_xlim(tick_min_1, tick_max_1)
axes[0].set_ylim(tick_min_1, tick_max_1)
axes[0].set_xticks(np.linspace(tick_min_1, tick_max_1, num=6))
axes[0].set_yticks(np.linspace(tick_min_1, tick_max_1, num=6))

# axes[0].set_title(r'$\mathrm{Hf_xZr_{1\minus x}O_2\ DP\ &\ DFT\ Energy}$', fontdict=font, pad=10)
axes[0].set_xlabel(r'$E_{\mathrm{DFT}}$ (eV/atom)', fontdict=font)
axes[0].set_ylabel(r'$E_{\mathrm{DP}}$ (eV/atom)', fontdict=font)
axes[0].xaxis.set_major_formatter(FormatStrFormatter('%.2f'))
axes[0].yaxis.set_major_formatter(FormatStrFormatter('%.2f'))

# 刻度线、标签及框线设置
axes[0].xaxis.set_minor_locator(AutoMinorLocator(2)) # 主刻度线间2个次刻度线
axes[0].yaxis.set_minor_locator(AutoMinorLocator(2)) # 主刻度线间2个次刻度线
axes[0].tick_params(axis='x', which='major', direction='in', length=5, width=1.2, top=True, labelsize=16)
axes[0].tick_params(axis='y', which='major', direction='in', length=5, width=1.2, right=True, labelsize=16)
axes[0].tick_params(axis='x', which='minor', direction='in', length=3, width=1.2, top=True)
axes[0].tick_params(axis='y', which='minor', direction='in', length=3, width=1.2, right=True)

labels = axes[0].get_xticklabels() + axes[0].get_yticklabels()
for label in labels:
    label.set_fontname('Times New Roman')
    label.set_weight('normal')

for spine in axes[0].spines.values():
    spine.set_linewidth(1)

# # 线性拟合线
# coefficients = np.polyfit(e_dft, e_dp, 1)
# polynomial = np.poly1d(coefficients)
# e_dft_fit = np.linspace(min(e_dft), max(e_dft), 100)
# e_dp_fit = polynomial(e_dft_fit)

# axes[0].plot(e_dft_fit, e_dp_fit, color='grey', linestyle='-', linewidth=1.5)

axes[0].legend(loc=0, edgecolor = 'k', fancybox=False, title=r'$\mathrm{Hf_xZr_{1\minus x}O_2}$', title_fontsize=12,
          prop=font_le, frameon=False, labelspacing=0.2, handletextpad=0.2).get_frame().set_linewidth(1) # frameon=False不显示边框

#-----------------------------------------Plot Delta_E Data----------------------------------------#

ax1_in = inset_axes(axes[0], width="35%", height="35%", loc='lower right',
                   bbox_to_anchor=(-0.02, 0.09, 1, 1), bbox_transform=axes[0].transAxes)

e_diff = np.abs(e_dft-e_dp)*1000 # eV -> meV
e_diff_mean = np.mean(e_diff)
counts_1, bins_1, patches_1 = ax1_in.hist(e_diff, bins=10, color='#fcc797', alpha=0.8,
                                 edgecolor='black', linewidth=0.5, rwidth=1)

n_1 = np.sum(counts_1)
p2 = (counts_1/n_1)*100
ax1_in.clear()
width1 = np.diff(bins_1)
ax1_in.bar(bins_1[:-1], p2, width=0.8*width1, color='#fcc797', alpha=0.9,
        edgecolor='black', linewidth=0.5, align='edge')

ax1_in.set_title(r'$\left|E_{\mathrm{DFT}} - E_{\mathrm{DP}}\right|$ (meV/atom)', fontdict=font_in)
ax1_in.set_ylabel('Frequency (%)', fontdict=font_in)

xlim_in1 = ax1_in.get_xlim()
# ax1_in.set_xlim(0, xlim_in1[1])
ax1_in.set_xticks(np.linspace(0, 12, num=4))
# ax1_in.set_yticks(np.linspace(0, 60, num=4))

ax1_in.tick_params(axis='both', which='major', labelsize=10, direction='out')
for tick in ax1_in.get_xticklabels() + ax1_in.get_yticklabels():
    tick.set_fontname('Times New Roman')

# 计算 MAE, R² 和 RMSE
r2_e, rmse_e = calc_err(e_dft, e_dp)
# print(f"R²_E: {r2_e:.4f}, RMSE_E: {rmse_e:.4f} meV/atom")

axes[0].text(0.85, 0.94, rf'$\mathrm{{RMSE={rmse_e*1000:.3f}\ meV/atom}}$',
             transform=axes[0].transAxes, fontsize=11, color='#441d6c', fontname='Times New Roman', ha='right', va='top')
axes[0].text(0.575, 0.84, rf'$\mathrm{{R^2}}={r2_e:.3f}$',
             transform=axes[0].transAxes, fontsize=11, color='#441d6c', fontname='Times New Roman', ha='right', va='top')

print(f"MAE_E: {e_diff_mean:.4f} meV/atom")
# axes[0].text(0.86, 0.96, rf'$\mathrm{{\langle\left|E_{{DFT}} \minus E_{{DP}}\right|\rangle = {e_diff_mean:.2f}\ meV/atom}}$',
#              transform=axes[0].transAxes, fontsize=10, color='k', fontname='Times New Roman', ha='right', va='top')

#------------------------------------------Plot Force Data------------------------------------------#

axes[1].scatter(fx_dft, fx_dp, s=5, color='#fbe625', alpha=0.8, label=r'$F_\mathrm{x}$')
axes[1].scatter(fy_dft, fy_dp, s=5, color='#31af7c', alpha=0.8, label=r'$F_\mathrm{y}$')
axes[1].scatter(fz_dft, fz_dp, s=5, color='#441d6c', alpha=0.8, label=r'$F_\mathrm{z}$')

axes[1].plot(x, y, color='grey', linestyle='-', linewidth=1.3, alpha=0.8)

tick_min_2 = -15
tick_max_2 = 15
axes[1].set_xlim(tick_min_2 , tick_max_2)
axes[1].set_ylim(tick_min_2 , tick_max_2)
axes[1].set_xticks(np.linspace(tick_min_2 , tick_max_2, num=7))
axes[1].set_yticks(np.linspace(tick_min_2 , tick_max_2, num=7))

# axes[1].set_title(r'$\mathrm{Hf_xZr_{1\minus x}O_2\ DP\ &\ DFT\ Force}$', fontdict=font, pad=10)
axes[1].set_xlabel(r'$F_\mathrm{DFT}$ ($\mathrm{eV/\AA}$)', fontdict=font)
axes[1].set_ylabel(r'$F_\mathrm{DP}$ ($\mathrm{eV/\AA}$)', fontdict=font)
# axes[1].xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
# axes[1].yaxis.set_major_formatter(FormatStrFormatter('%.0f'))

# 刻度线、标签及框线设置
axes[1].xaxis.set_minor_locator(AutoMinorLocator(2)) # 主刻度线间2个次刻度线
axes[1].yaxis.set_minor_locator(AutoMinorLocator(2)) # 主刻度线间2个次刻度线
axes[1].tick_params(axis='x', which='major', direction='in', length=5, width=1.2, top=True, labelsize=16)
axes[1].tick_params(axis='y', which='major', direction='in', length=5, width=1.2, right=True, labelsize=16)
axes[1].tick_params(axis='x', which='minor', direction='in', length=3, width=1.2, top=True)
axes[1].tick_params(axis='y', which='minor', direction='in', length=3, width=1.2, right=True)

labels = axes[1].get_xticklabels() + axes[1].get_yticklabels()
for label in labels:
    label.set_fontname('Times New Roman')
    label.set_weight('normal')

for spine in axes[1].spines.values():
    spine.set_linewidth(1)

axes[1].legend(loc=0, edgecolor = 'k', fancybox=False, handletextpad=0.2,
          prop=font_le2, frameon=False).get_frame().set_linewidth(1) # frameon=False不显示边框

#-----------------------------------------Plot Delta_F Data----------------------------------------#

ax2_in = inset_axes(axes[1], width="35%", height="35%", loc='lower right',
                   bbox_to_anchor=(-0.02, 0.09, 1, 1), bbox_transform=axes[1].transAxes)

f_diff = np.abs(fx_dft-fx_dp)
f_diff_mean = np.mean(f_diff)
counts_2, bins_2, patches_2 = ax2_in.hist(f_diff, bins=10, color='#fcc797', alpha=0.8,
                                 edgecolor='black', linewidth=0.5, rwidth=1) # 将数据分为 10 个区间

n_2 = np.sum(counts_2)
p2 = (counts_2/n_2)*100
ax2_in.clear()
width2 = np.diff(bins_2)
ax2_in.bar(bins_2[:-1], p2, width=0.8*width2, color='#fcc797', alpha=0.9,
        edgecolor='black', linewidth=0.5, align='edge')

ax2_in.set_title(r'$\left|F_\mathrm{DFT} - F_\mathrm{DP}\right|\ (\mathrm{eV/\AA})$', fontdict=font_in)
ax2_in.set_ylabel('Frequency (%)', fontdict=font_in)

xlim_in2 = ax2_in.get_xlim()
# ax2_in.set_xlim(0, xlim_in2[1])
ax2_in.set_xticks(np.linspace(0, 1.2, num=4))
# ax2_in.set_yticks(np.linspace(0, 40, num=3))

ax2_in.tick_params(axis='both', which='major', labelsize=10, direction='out')
for tick in ax2_in.get_xticklabels() + ax2_in.get_yticklabels():
    tick.set_fontname('Times New Roman')

# 计算 MAE, R² 和 RMSE
r2_fz, rmse_fz = calc_err(fz_dft, fz_dp)
# print(f"R²_Fz: {r2_fz:.4f}, RMSE_Fz: {rmse_fz:.4f} eV/Å")

axes[1].text(0.76, 0.94, rf'$\mathrm{{RMSE={rmse_fz:.3f}\ eV/\AA}}$',
             transform=axes[1].transAxes, fontsize=11, color='#441d6c', fontname='Times New Roman', ha='right', va='top')
axes[1].text(0.58, 0.84, rf'$\mathrm{{R^2}}={r2_fz:.3f}$',
             transform=axes[1].transAxes, fontsize=11, color='#441d6c', fontname='Times New Roman', ha='right', va='top')

print(f"MAE_Fz: {f_diff_mean:.4f} eV/Å")
# axes[1].text(0.86, 0.96, rf'$\mathrm{{\langle\left|F_{{DFT}} \minus F_{{DP}}\right|\rangle = {f_diff_mean:.2f}\ eV/\AA}}$',
#              transform=axes[1].transAxes, fontsize=10, color='k', fontname='Times New Roman', ha='right', va='top')

# axes[0].text(-0.26, 0.95, '(a)', font=font_text, transform=axes[0].transAxes)
# axes[1].text(-0.21, 0.95, '(b)', font=font_text, transform=axes[1].transAxes)

# 调整子图间的垂直/水平间隔
fig.subplots_adjust(hspace=0.0, wspace=0.3)

plt.show()
# fig.savefig('HZO_DP_DFT_iter_20-24.png', dpi=1000, bbox_inches='tight', transparent=False)

5. 机器配置文件 machine.json

  • * 控制工作流,涉及计算任务收发、环境设置及提交命令、远程计算节点的机型等,可在同一机器上重复使用
  • * 针对 training、exploration、labeling 3 个训练步骤分别独立设置
  • * 每步指定 3 个模块:
  1. "command": 任务运行命令
  2. "machine": 调用的机器(本地机器、SSH远程、队列系统)、任务路径
  3. "resources": 调用的资源(CPU/GPU数、加载环境)
  • * 注:检查"resouce"下的"source_list"、"module_list",以及fp的"command"
{
        "api_version": "1.0",
        "deepmd_version": "2.2.11",
        "train": [
                {
                        "command": "dp",
                        "machine": {
                                "batch_type": "Slurm",
                                "context_type": "local",
                                "local_root" : "./",
                                "remote_root": "./work"
                        },
                        "resources": {
                                "number_node": 2,
                                "cpu_per_node": 30,
                                "gpu_per_node": 0,
                                "queue_name": "huawei",
                                "_source_list": [""],
                                "module_list": [""],
                                "group_size": 4
                        }
                }
        ],
        "model_devi": [
                {
                        "command": "lmp",
                        "machine": {
                                "batch_type": "Slurm",
                                "context_type": "local",
                                "local_root" : "./",
                                "remote_root": "./work"
                        },
                        "resources": {
                                "number_node": 2,
                                "cpu_per_node": 30,
                                "gpu_per_node": 0,
                                "queue_name": "huawei",
                                "_source_list": [""],
                                "_module_list": [""],
                                "group_size": 30
                        }
                }
        ],
        "fp": [
                {
                        "command": "mpirun -n 60 vasp_std",
                        "machine": {
                                "batch_type": "Slurm",
                                "context_type": "local",
                                "local_root" : "./",
                                "remote_root": "./work"
                        },
                        "resources": {
                                "number_node": 2,
                                "cpu_per_node": 30,
                                "gpu_per_node": 0,
                                "queue_name": "huawei",
                                "_source_list": [""],
                                "_module_list": [""],
                                "group_size": 30
                        }
                }
        ]
}