写在前面:为了减少自己的工作量,也为了防止遗忘,便写了这个脚本
脚本使用方法:把vib.py文件复制到TS路径下,记事本打开vib.py脚本,修改TS的路径和需要计算虚频的插点文件,如02,保存关闭。在该TS路径下,命令行输入python3 vib.py 回车,根据程序提示,输入需要固定的原子种类和数量,固定规则为,由指定原子从后往前固定,输入完毕回车。脚本会自行在当前文件夹下创建VIB文件夹,并准备vasp计算所需要的其他文件。
脚本如下,自行取用:
python
import os
import shutil
import subprocess
first_path = ["/fs2/home/niexiaowa/wl/FeC-CO-ML/Fe5C2/f10-1/CO2=CO+O/TS/"]
for i in first_path:
# ====================== 用户配置区 ======================
contcar_dir = i + "02"
# 2. 外部文件路径:sub.sh KPOINTS POTCAR
path_subsh = i + "sub.sh"
path_kpoints = i + "KPOINTS"
path_potcar = i + "POTCAR"
incar_content = """
SYSTEM = vib
Initial Setting :
ISTART = 0
ICHARG = 2
NCORE = 8
LWAVE = .FALSE.
LCHARG = .FALSE.
LVTOT = .FALSE.
LMAXMIX = 4
Electronic Relaxation :
ENCUT = 500
EDIFF = 1E-05
ISMEAR = 0
SIGMA = 0.05
GGA = PE
PREC = Normal
ALGO = Fast
LREAL = Auto
Ionic Relaxation :
EDIFFG = -0.01
NSW = 1
POTIM = 0.015
NFREE = 2
IBRION = 5
ISYM = 0
ISIF = 2
IVDW = 11
ISPIN = 2
#MAGMOM = 10*0 26*0 36*0 4*0 1*5 1*0
"""
# 集群提交命令:slurm填sbatch;PBS填qsub
submit_cmd = "sbatch"
#submit_cmd = "qsub"
# =======================================================
def parse_contcar(filepath: str):
"""解析CONTCAR,原样读取所有行,区分是否带Selective Dynamics"""
with open(filepath, "r", encoding="utf-8") as f:
lines = [line.rstrip("\n") for line in f]
ptr = 0
title = lines[ptr].strip()
ptr += 1
scale = float(lines[ptr])
ptr += 1
cell = []
for _ in range(3):
cell.append([float(x) for x in lines[ptr].split()])
ptr += 1
elem_list = lines[ptr].split()
ptr += 1
natom_list = [int(x) for x in lines[ptr].split()]
ptr += 1
selective_flag = False
if lines[ptr].strip().lower().startswith("s"):
selective_flag = True
ptr += 1
coord_mode = lines[ptr].strip()
ptr += 1
natom_total = sum(natom_list)
coord_raw_lines = []
for _ in range(natom_total):
coord_raw_lines.append(lines[ptr])
ptr += 1
return {
"title": title,
"scale": scale,
"cell": cell,
"elem": elem_list,
"natoms": natom_list,
"has_selective": selective_flag,
"coord_mode": coord_mode,
"coord_raw": coord_raw_lines
}
def parse_interactive_input(input_str: str):
"""交互输入解析,示例 "Fe:3 O:0 C:0" """
free_spec = {}
parts = input_str.strip().split()
for p in parts:
elem, num = p.split(":")
free_spec[elem.strip()] = int(num.strip())
return free_spec
def generate_poscar_selective(contcar_data, free_spec: dict):
"""
has_selective=True:原始每行 x y z T1 T2 T3,直接覆盖后三列,不增加字段,保持总共6列
has_selective=False:追加T/F三列,同时写入Selective Dynamics标记
同一元素:末尾N个原子 T T T;其余 F F F
"""
elem_names = contcar_data["elem"]
elem_counts = contcar_data["natoms"]
raw_coord_lines = contcar_data["coord_raw"]
atom_elem_list = []
for elem, cnt in zip(elem_names, elem_counts):
atom_elem_list.extend([elem] * cnt)
sd_target = []
log_info = []
idx_global = 0
for elem, cnt in zip(elem_names, elem_counts):
n_free = free_spec.get(elem, 0)
if n_free > cnt:
raise ValueError(f"元素 {elem}: 设置自由数目{n_free} > 该元素总原子数 {cnt}")
for i_in_type in range(cnt):
idx_global += 1
is_free = i_in_type >= (cnt - n_free)
if is_free:
sd_target.append("T T T")
log_info.append(f"Atom{idx_global:4d} {elem:4s} -> Free(T T T)")
else:
sd_target.append("F F F")
log_info.append(f"Atom{idx_global:4d} {elem:4s} -> Fix(F F F)")
out_lines = []
out_lines.append(contcar_data["title"])
out_lines.append(f"{contcar_data['scale']:.10f}")
for row in contcar_data["cell"]:
out_lines.append(f"{row[0]:12.8f} {row[1]:12.8f} {row[2]:12.8f}")
out_lines.append(" ".join(elem_names))
out_lines.append(" ".join(map(str, elem_counts)))
if contcar_data["has_selective"]:
out_lines.append("Selective Dynamics")
out_lines.append(contcar_data["coord_mode"])
for raw_line, new_sd in zip(raw_coord_lines, sd_target):
tokens = raw_line.strip().split()
xyz = tokens[0:3]
final_tokens = xyz + new_sd.split()
out_lines.append(" ".join(final_tokens))
return "\n".join(out_lines), log_info
def main():
contcar_path = os.path.join(contcar_dir, "CONTCAR")
if not os.path.exists(contcar_path):
raise FileNotFoundError(f"找不到CONTCAR: {contcar_path}")
for fpath, fname in zip([path_subsh, path_kpoints, path_potcar], ["sub.sh", "KPOINTS", "POTCAR"]):
if not os.path.exists(fpath):
raise FileNotFoundError(f"缺失文件 {fname}: {fpath}")
print("=" * 60)
print("VASP IBRION=5 振动输入生成工具")
print("示例输入: Fe:3 O:0 C:0")
print("含义:Fe最后3个原子自由,O、C全部固定;未写元素默认全部固定")
user_input = input("请输入自由原子设置:")
free_spec = parse_interactive_input(user_input)
print(f"解析得到设置:{free_spec}")
print("=" * 60)
contcar_abs = os.path.abspath(contcar_dir)
parent_dir = os.path.dirname(contcar_abs)
vib_dir = os.path.join(parent_dir, "VIB")
if os.path.exists(vib_dir):
shutil.rmtree(vib_dir)
os.makedirs(vib_dir, exist_ok=True)
print(f"[INFO] 创建振动计算目录:{vib_dir}")
contcar_data = parse_contcar(contcar_path)
poscar_text, atom_log = generate_poscar_selective(contcar_data, free_spec)
print("\n========== 原子冻结状态 ==========")
for line in atom_log:
print(line)
print("==================================\n")
#保存原子状态日志
log_file = os.path.join(vib_dir,"atom_status.log")
with open(log_file,"w",encoding="utf-8") as f:
f.write("\n".join(atom_log))
poscar_out = os.path.join(vib_dir, "POSCAR")
with open(poscar_out, "w", encoding="utf-8") as f:
f.write(poscar_text)
incar_out = os.path.join(vib_dir, "INCAR")
with open(incar_out, "w", encoding="utf-8") as f:
f.write(incar_content.lstrip("\n"))
shutil.copy(path_subsh, os.path.join(vib_dir, "sub.sh"))
shutil.copy(path_kpoints, os.path.join(vib_dir, "KPOINTS"))
shutil.copy(path_potcar, os.path.join(vib_dir, "POTCAR"))
print("[INFO] POSCAR INCAR KPOINTS POTCAR sub.sh atom_status.log 全部写入完成")
print(f"[INFO] 使用 {submit_cmd} 提交作业")
ret = subprocess.run(
[submit_cmd, "sub.sh"],
cwd=vib_dir,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True
)
if ret.returncode == 0:
print(f"[SUCCESS]任务提交成功:\n{ret.stdout}")
else:
print(f"[ERROR] 提交失败\nstdout:\n{ret.stdout}\nstderr:\n{ret.stderr}")
raise subprocess.CalledProcessError(ret.returncode, cmd=submit_cmd)
if __name__ == "__main__":
main()