ionic 运行报 parse Ionic Config file

本文介绍了一种常见的Ionic开发中遇到的问题——无法解析Ionic配置文件,并提供了解决方案,即删除当前账户下的.ionic文件夹。


All of a sudden, I'm getting this while trying to run ionic. What's wrong?

C:\Users>ionic
Unable to parse Ionic Config file. Please make sure it is valid JSON (.ionic/ionic.config)

Caught exception:
 SyntaxError: Unexpected token
    at Object.parse (native)
    at Object.module.exports.load (C:\Users\BBytes\AppData\Roaming\npm\node_modules\ionic\node_modules\ionic-app-lib\lib\config.js:14:26)
    at Object.<anonymous> (C:\Users\BBytes\AppData\Roaming\npm\node_modules\ionic\lib\utils\stats.js:31:31)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Module.require (module.js:353:17)
    at require (internal/module.js:12:17)
    at Object.<anonymous> (C:\Users\BBytes\AppData\Roaming\npm\node_modules\ionic\lib\cli.js:3:18)

Mind letting us know? https://github.com/driftyco/ionic-cli/issues
正在分析湿法磷酸体系,体系中存在水-磷酸-水合氢离子,探讨其中氢键网络情况,尤其观察F在氢键网络中的影响,计算了RDF,来计算了P-O的第一波峰来区别磷酸上的O,再通过Op与H的RDF第一波峰来区别磷酸上的H,这样子来区分P-O/P=O和P-OH这两类,将排除Op来计算剩下的O,剩下的O与周围全部的H计算RDF的第一波来看O-H的成键距离,看成键数量来判断水还是水合氢离子,然后这些数值将作为D化学键成键的参考值,同样也作为D-H的参考值,再计算对应的氢键的RDF,计算出来的波峰作为H-A的距离阈值,基于这两个阈值计算角度分布,依照占比90%的氢键角度作为阈值,如下氢键键长的计算:(依照距离-角度来识别氢键)import numpy as np import matplotlib.pyplot as plt from pymatgen.io.vasp import Vasprun from pymatgen.core.structure import Structure from scipy.spatial import cKDTree from tqdm import tqdm import matplotlib as mpl import warnings import os import csv import argparse import multiprocessing import time import sys 忽略可能的警告 warnings.filterwarnings(“ignore”, category=UserWarning) 专业绘图设置 plt.style.use(‘seaborn-v0_8-whitegrid’) mpl.rcParams.update({ ‘font.family’: ‘serif’, ‘font.serif’: [‘Times New Roman’, ‘DejaVu Serif’], ‘font.size’: 12, ‘axes.labelsize’: 14, ‘axes.titlesize’: 16, ‘xtick.labelsize’: 12, ‘ytick.labelsize’: 12, ‘figure.dpi’: 600, ‘savefig.dpi’: 600, ‘figure.figsize’: (8, 6), ‘lines.linewidth’: 2.0, ‘legend.fontsize’: 10, ‘legend.framealpha’: 0.8, ‘mathtext.default’: ‘regular’, ‘axes.linewidth’: 1.5, ‘xtick.major.width’: 1.5, ‘ytick.major.width’: 1.5, ‘xtick.major.size’: 5, ‘ytick.major.size’: 5, }) def identify_atom_types(struct): “”“原子类型识别函数”“” # 初始化数据结构 atom_types = { “phosphate_oxygens”: {“P-O/P=O”: [], “P-OH”: []}, “phosphate_hydrogens”: [], “water_oxygens”: [], “water_hydrogens”: [], “hydronium_oxygens”: [], “hydronium_hydrogens”: [], “fluoride_atoms”: [i for i, site in enumerate(struct) if site.species_string == “F”], “aluminum_atoms”: [i for i, site in enumerate(struct) if site.species_string == “Al”] } # 构建全局KDTree all_coords = np.array([site.coords for site in struct]) kdtree = cKDTree(all_coords, boxsize=struct.lattice.abc) # 识别磷酸基团 p_atoms = [i for i, site in enumerate(struct) if site.species_string == "P"] phosphate_oxygens = [] for p_idx in p_atoms: # 查找P周围的O原子 (距离 < 1.6Å) neighbors = kdtree.query_ball_point(all_coords[p_idx], r=1.6) p_o_indices = [idx for idx in neighbors if idx != p_idx and struct[idx].species_string == "O"] phosphate_oxygens.extend(p_o_indices) # 识别所有H原子并确定归属 hydrogen_owners = {} h_atoms = [i for i, site in enumerate(struct) if site.species_string == "H"] for h_idx in h_atoms: neighbors = kdtree.query_ball_point(all_coords[h_idx], r=1.2) candidate_os = [idx for idx in neighbors if idx != h_idx and struct[idx].species_string == "O"] if not candidate_os: continue min_dist = float('inf') owner_o = None for o_idx in candidate_os: dist = struct.get_distance(h_idx, o_idx) if dist < min_dist: min_dist = dist owner_o = o_idx hydrogen_owners[h_idx] = owner_o # 分类磷酸氧:带H的为P-OH,不带H的为P-O/P=O for o_idx in phosphate_oxygens: has_hydrogen = any(owner_o == o_idx for h_idx, owner_o in hydrogen_owners.items()) if has_hydrogen: atom_types["phosphate_oxygens"]["P-OH"].append(o_idx) else: atom_types["phosphate_oxygens"]["P-O/P=O"].append(o_idx) # 识别水和水合氢离子 all_o_indices = [i for i, site in enumerate(struct) if site.species_string == "O"] non_phosphate_os = [o_idx for o_idx in all_o_indices if o_idx not in phosphate_oxygens] o_h_count = {} for h_idx, owner_o in hydrogen_owners.items(): o_h_count[owner_o] = o_h_count.get(owner_o, 0) + 1 for o_idx in non_phosphate_os: h_count = o_h_count.get(o_idx, 0) attached_hs = [h_idx for h_idx, owner_o in hydrogen_owners.items() if owner_o == o_idx] if h_count == 2: atom_types["water_oxygens"].append(o_idx) atom_types["water_hydrogens"].extend(attached_hs) elif h_count == 3: atom_types["hydronium_oxygens"].append(o_idx) atom_types["hydronium_hydrogens"].extend(attached_hs) # 识别磷酸基团的H原子 for o_idx in atom_types["phosphate_oxygens"]["P-OH"]: attached_hs = [h_idx for h_idx, owner_o in hydrogen_owners.items() if owner_o == o_idx] atom_types["phosphate_hydrogens"].extend(attached_hs) return atom_types def get_hbond_config(): “”“返回氢键配置,包含距离和角度阈值”“” return [ { “name”: “P-O/P=O···Hw”, “donor_type”: “water_oxygens”, “acceptor_type”: “P-O/P=O”, “h_type”: “water_hydrogens”, “distance_threshold”: 2.375, “angle_threshold”: 143.99, “color”: “#1f77b4” }, { “name”: “P-O/P=O···Hh”, “donor_type”: “hydronium_oxygens”, “acceptor_type”: “P-O/P=O”, “h_type”: “hydronium_hydrogens”, “distance_threshold”: 2.275, “angle_threshold”: 157.82, “color”: “#ff7f0e” }, { “name”: “P-O/P=O···Hp”, “donor_type”: “P-OH”, “acceptor_type”: “P-O/P=O”, “h_type”: “phosphate_hydrogens”, “distance_threshold”: 2.175, “angle_threshold”: 155.00, “color”: “#2ca02c” }, { “name”: “P-OH···Ow”, “donor_type”: “P-OH”, “acceptor_type”: “water_oxygens”, “h_type”: “phosphate_hydrogens”, “distance_threshold”: 2.275, “angle_threshold”: 155.13, “color”: “#d62728” }, { “name”: “Hw···Ow”, “donor_type”: “water_oxygens”, “acceptor_type”: “water_oxygens”, “h_type”: “water_hydrogens”, “distance_threshold”: 2.375, “angle_threshold”: 138.73, “color”: “#9467bd” }, { “name”: “Hh···Ow”, “donor_type”: “hydronium_oxygens”, “acceptor_type”: “water_oxygens”, “h_type”: “hydronium_hydrogens”, “distance_threshold”: 2.225, “angle_threshold”: 155.31, “color”: “#8c564b” }, { “name”: “Hw···F”, “donor_type”: “water_oxygens”, “acceptor_type”: “fluoride_atoms”, “h_type”: “water_hydrogens”, “distance_threshold”: 2.225, “angle_threshold”: 137.68, “color”: “#e377c2” }, { “name”: “Hh···F”, “donor_type”: “hydronium_oxygens”, “acceptor_type”: “fluoride_atoms”, “h_type”: “hydronium_hydrogens”, “distance_threshold”: 2.175, “angle_threshold”: 154.64, “color”: “#7f7f7f” }, { “name”: “Hp···F”, “donor_type”: “P-OH”, “acceptor_type”: “fluoride_atoms”, “h_type”: “phosphate_hydrogens”, “distance_threshold”: 2.275, “angle_threshold”: 153.71, “color”: “#bcbd22” } ] def calculate_angle(struct, donor_idx, h_idx, acceptor_idx): “”“计算D-H···A键角 (角度制),使用笛卡尔向量表示并处理周期性”“” # 获取分数坐标 frac_coords = struct.frac_coords lattice = struct.lattice # 获取氢原子H的分数坐标 h_frac = frac_coords[h_idx] # 计算供体D相对于H的分数坐标差 d_frac = frac_coords[donor_idx] dh_frac = d_frac - h_frac # 计算受体A相对于H的分数坐标差 a_frac = frac_coords[acceptor_idx] ah_frac = a_frac - h_frac # 应用周期性修正 (将分数坐标差限制在[-0.5, 0.5]范围内) dh_frac = np.where(dh_frac > 0.5, dh_frac - 1, dh_frac) dh_frac = np.where(dh_frac < -0.5, dh_frac + 1, dh_frac) ah_frac = np.where(ah_frac > 0.5, ah_frac - 1, ah_frac) ah_frac = np.where(ah_frac < -0.5, ah_frac + 1, ah_frac) # 转换为笛卡尔向量 (H->D 和 H->A) vec_hd = np.dot(dh_frac, lattice.matrix) # H->D向量 vec_ha = np.dot(ah_frac, lattice.matrix) # H->A向量 # 计算向量点积 dot_product = np.dot(vec_hd, vec_ha) # 计算向量模长 norm_hd = np.linalg.norm(vec_hd) norm_ha = np.linalg.norm(vec_ha) # 避免除以零 if norm_hd < 1e-6 or norm_ha < 1e-6: return None # 计算余弦值 cos_theta = dot_product / (norm_hd * norm_ha) # 处理数值误差 cos_theta = np.clip(cos_theta, -1.0, 1.0) # 计算角度 (弧度转角度) angle_rad = np.arccos(cos_theta) angle_deg = np.degrees(angle_rad) return angle_deg def calculate_hbond_lengths_frame(struct, atom_types, hbond_config, bond_threshold=1.3): “”“计算单帧结构中氢键键长(H···A距离)”“” # 构建全局KDTree用于快速搜索 all_coords = np.array([site.coords for site in struct]) lattice_abc = struct.lattice.abc kdtree = cKDTree(all_coords, boxsize=lattice_abc) # 结果字典: {氢键类型: [键长列表]} length_results = {hbond["name"]: [] for hbond in hbond_config} # 处理每一类氢键 for hbond in hbond_config: # 获取供体原子列表 if hbond["donor_type"] == "P-OH": donors = atom_types["phosphate_oxygens"]["P-OH"] else: donors = atom_types[hbond["donor_type"]] # 获取受体原子列表 if hbond["acceptor_type"] == "P-O/P=O": acceptors = atom_types["phosphate_oxygens"]["P-O/P=O"] elif hbond["acceptor_type"] == "P-OH": acceptors = atom_types["phosphate_oxygens"]["P-OH"] else: acceptors = atom_types[hbond["acceptor_type"]] # 获取氢原子列表 hydrogens = atom_types[hbond["h_type"]] # 如果没有氢原子或受体,跳过 if len(hydrogens) == 0 or len(acceptors) == 0: continue # 为受体构建KDTree(使用全局坐标) acceptor_coords = all_coords[acceptors] acceptor_kdtree = cKDTree(acceptor_coords, boxsize=lattice_abc) # 遍历所有氢原子 for h_idx in hydrogens: h_coords = all_coords[h_idx] # 查找与H成键的供体 (距离 < bond_threshold) donor_neighbors = kdtree.query_ball_point(h_coords, r=bond_threshold) donor_candidates = [idx for idx in donor_neighbors if idx in donors] # 如果没有找到供体,跳过 if not donor_candidates: continue # 选择最近的供体 min_dist = float('inf') donor_idx = None for d_idx in donor_candidates: dist = struct.get_distance(h_idx, d_idx) if dist < min_dist: min_dist = dist donor_idx = d_idx # 查找在距离阈值内的受体 acceptor_indices = acceptor_kdtree.query_ball_point(h_coords, r=hbond["distance_threshold"]) for a_idx_offset in acceptor_indices: a_idx = acceptors[a_idx_offset] # 排除供体自身 if a_idx == donor_idx: continue # 计算键长 (H···A距离) ha_distance = struct.get_distance(h_idx, a_idx) # 计算键角 angle = calculate_angle(struct, donor_idx, h_idx, a_idx) # 检查角度阈值 if angle is not None and angle >= hbond["angle_threshold"]: length_results[hbond["name"]].append(ha_distance) return length_results def calculate_hbond_lengths_frame_wrapper(args): “”“包装函数用于多进程处理”“” struct, hbond_config = args atom_types = identify_atom_types(struct) return calculate_hbond_lengths_frame(struct, atom_types, hbond_config) def calculate_hbond_lengths_parallel(structures, workers=1, step_interval=10): “”“并行计算氢键键长,每step_interval帧计算一次”“” hbond_config = get_hbond_config() all_results = [] # 只选择每step_interval帧的结构 selected_structures = structures[::step_interval] frame_indices = list(range(0, len(structures), step_interval)) # 准备参数列表 args_list = [(struct, hbond_config) for struct in selected_structures] # 如果没有可用的worker,则顺序执行 if workers == 1: results = [] for args in tqdm(args_list, desc="Calculating HBond Lengths"): results.append(calculate_hbond_lengths_frame_wrapper(args)) else: with multiprocessing.Pool(processes=workers) as pool: results = list(tqdm( pool.imap(calculate_hbond_lengths_frame_wrapper, args_list), total=len(selected_structures), desc="Calculating HBond Lengths" )) # 将结果与帧索引组合 for frame_idx, frame_result in zip(frame_indices, results): all_results.append({ "frame_idx": frame_idx, "results": frame_result }) return all_results def plot_hbond_length_time_series(all_results, system_name): “”“绘制氢键键长随时间变化的曲线并保存原始数据”“” # 创建输出目录 os.makedirs(“HBond_Length_Time_Series”, exist_ok=True) os.makedirs(“HBond_Length_Data”, exist_ok=True) hbond_config = get_hbond_config() # 创建统计信息汇总文件 - 使用'A'代替Å避免编码问题 summary_path = os.path.join("HBond_Length_Data", f"{system_name}_summary.csv") with open(summary_path, 'w', newline='', encoding='utf-8') as summary_file: summary_writer = csv.writer(summary_file) summary_writer.writerow(["HBond Type", "Mean Length (A)", "Std Dev (A)", "Median (A)", "Min (A)", "Max (A)"]) # 处理每种氢键类型 for hbond in hbond_config: hbond_name = hbond["name"] safe_name = hbond_name.replace("/", "").replace("=", "").replace("···", "_").replace(" ", "_") # 提取该氢键类型的所有帧的键长 frame_indices = [] all_lengths = [] for frame_data in all_results: frame_idx = frame_data["frame_idx"] lengths = frame_data["results"].get(hbond_name, []) if lengths: # 只记录有数据的帧 frame_indices.append(frame_idx) all_lengths.append(lengths) if not frame_indices: print(f"No hydrogen bonds found for {hbond_name} in {system_name}") continue # 计算每帧的统计量 mean_lengths = [np.mean(lengths) for lengths in all_lengths] median_lengths = [np.median(lengths) for lengths in all_lengths] std_lengths = [np.std(lengths) for lengths in all_lengths] # 计算全局统计量 all_flat_lengths = [length for sublist in all_lengths for length in sublist] global_mean = np.mean(all_flat_lengths) global_std = np.std(all_flat_lengths) global_median = np.median(all_flat_lengths) global_min = np.min(all_flat_lengths) global_max = np.max(all_flat_lengths) # 保存全局统计信息到汇总文件 with open(summary_path, 'a', newline='', encoding='utf-8') as summary_file: summary_writer = csv.writer(summary_file) summary_writer.writerow([ hbond_name, f"{global_mean:.4f}", f"{global_std:.4f}", f"{global_median:.4f}", f"{global_min:.4f}", f"{global_max:.4f}" ]) # ========== 保存原始时间序列数据 ========== data_path = os.path.join("HBond_Length_Data", f"{system_name}_{safe_name}_time_series.csv") with open(data_path, 'w', newline='', encoding='utf-8') as data_file: data_writer = csv.writer(data_file) data_writer.writerow(["Frame Index", "Mean Length (A)", "Median Length (A)", "Std Dev (A)"]) for i, frame_idx in enumerate(frame_indices): data_writer.writerow([ frame_idx, f"{mean_lengths[i]:.4f}", f"{median_lengths[i]:.4f}", f"{std_lengths[i]:.4f}" ]) # ========== 绘制时间序列图 ========== plt.figure(figsize=(10, 6)) # 绘制平均键长曲线 plt.plot(frame_indices, mean_lengths, color=hbond["color"], label="Mean Length", linewidth=2) # 绘制中位数键长曲线 plt.plot(frame_indices, median_lengths, color=hbond["color"], linestyle="--", label="Median Length", linewidth=1.5) # 添加标准差误差带 plt.fill_between( frame_indices, np.array(mean_lengths) - np.array(std_lengths), np.array(mean_lengths) + np.array(std_lengths), color=hbond["color"], alpha=0.2, label="±1 Std Dev" ) # 添加全局统计信息 stats_text = (f"Global Mean: {global_mean:.3f} $\mathrm{{\\AA}}$\n" f"Global Std: {global_std:.3f} $\mathrm{{\\AA}}$\n" f"Global Median: {global_median:.3f} $\mathrm{{\\AA}}$\n" f"Count: {len(all_flat_lengths)}") plt.text(0.75, 0.95, stats_text, transform=plt.gca().transAxes, verticalalignment='top', bbox=dict(boxstyle='round', alpha=0.5)) # 设置标题和标签 plt.title(f"{system_name}: {hbond_name} Bond Length Time Series", fontsize=16) plt.xlabel("Frame Index", fontsize=14) plt.ylabel(r"Bond Length ($\mathrm{\AA}$)", fontsize=14) # 使用LaTeX格式的Å符号 # 设置Y轴范围 y_min = max(0.5, global_min - 0.1) y_max = min(3.5, global_max + 0.1) plt.ylim(y_min, y_max) plt.grid(True, linestyle='--', alpha=0.7) plt.legend() # 保存图像 image_path = os.path.join("HBond_Length_Time_Series", f"{system_name}_{safe_name}_time_series.tiff") plt.savefig(image_path, dpi=600, bbox_inches='tight') plt.close() print(f"Saved HBond length time series data: {data_path}") print(f"Saved HBond length time series plot: {image_path}") print(f"Saved summary statistics: {summary_path}") def main(vasprun_files, workers=1, step_interval=10): “”“主处理函数”“” for system_name, vasprun_file in vasprun_files.items(): print(f"\n{‘=’*50}“) print(f"Processing {system_name}: {vasprun_file} with {workers} workers”) print(f"Step interval: {step_interval} frames") print(f"{‘=’*50}") start_time = time.time() try: # 加载VASP结果 vr = Vasprun(vasprun_file, ionic_step_skip=5) structures = vr.structures print(f"Loaded {len(structures)} frames") print(f"Lattice parameters: {structures[0].lattice.abc}") print(f"Periodic boundary handling: Fractional coordinates + PBC correction") # 计算氢键键长时间序列 print("Calculating hydrogen bond lengths over time...") hbond_lengths = calculate_hbond_lengths_parallel(structures, workers=workers, step_interval=step_interval) # 绘制并保存结果 plot_hbond_length_time_series(hbond_lengths, system_name) elapsed = time.time() - start_time print(f"\nCompleted processing for {system_name} in {elapsed:.2f} seconds") except Exception as e: print(f"Error processing {system_name}: {str(e)}", file=sys.stderr) import traceback traceback.print_exc() print("\nAll HBond length time series analysis completed successfully!") if name == “main”: # 设置命令行参数 parser = argparse.ArgumentParser(description=‘Calculate hydrogen bond lengths from VASP simulations’) parser.add_argument(‘–workers’, type=int, default=multiprocessing.cpu_count(), help=f’Number of parallel workers (default: {multiprocessing.cpu_count()})‘) parser.add_argument(’–step_interval’, type=int, default=10, help=‘Frame interval for analysis (default: 10)’) args = parser.parse_args() # 自动设置vasprun文件和系统名称 vasprun_files = { "System1": "vasprun1.xml", "System2": "vasprun2.xml", "System3": "vasprun3.xml", "System4": "vasprun4.xml" } # 检查文件是否存在 missing_files = [name for name, path in vasprun_files.items() if not os.path.exists(path)] if missing_files: raise FileNotFoundError(f"Missing vasprun files: {', '.join(missing_files)}") print(f"Starting HBond length analysis with {args.workers} workers...") main(vasprun_files, workers=args.workers, step_interval=args.step_interval),在投The Journal of Chemical Physics期刊中,是不是加入速度自相关函数的计算来和实际的参考文献对比更合理?那该具体怎么计算速度自相关函数呢?具体参数的选择?具体的表达已经代码?
07-30
import numpy as np import matplotlib.pyplot as plt from pymatgen.io.vasp import Vasprun from pymatgen.core.structure import Structure from scipy.spatial import cKDTree from tqdm import tqdm import matplotlib as mpl import warnings import os import csv import argparse import multiprocessing from scipy.stats import gaussian_kde import time import sys # 忽略可能的警告 warnings.filterwarnings("ignore", category=UserWarning) # 专业绘图设置 plt.style.use('seaborn-v0_8-whitegrid') mpl.rcParams.update({ 'font.family': 'serif', 'font.serif': ['Times New Roman', 'DejaVu Serif'], 'font.size': 12, 'axes.labelsize': 14, 'axes.titlesize': 16, 'xtick.labelsize': 12, 'ytick.labelsize': 12, 'figure.dpi': 600, 'savefig.dpi': 600, 'figure.figsize': (8, 6), 'lines.linewidth': 2.0, 'legend.fontsize': 10, 'legend.framealpha': 0.8, 'mathtext.default': 'regular', 'axes.linewidth': 1.5, 'xtick.major.width': 1.5, 'ytick.major.width': 1.5, 'xtick.major.size': 5, 'ytick.major.size': 5, }) def identify_atom_types(struct): """原子类型识别函数""" # 初始化数据结构 atom_types = { "phosphate_oxygens": {"P-O/P=O": [], "P-OH": []}, "phosphate_hydrogens": [], "water_oxygens": [], "water_hydrogens": [], "hydronium_oxygens": [], "hydronium_hydrogens": [], "fluoride_atoms": [i for i, site in enumerate(struct) if site.species_string == "F"], "aluminum_atoms": [i for i, site in enumerate(struct) if site.species_string == "Al"] } # 构建全局KDTree all_coords = np.array([site.coords for site in struct]) kdtree = cKDTree(all_coords, boxsize=struct.lattice.abc) # 识别磷酸基团 p_atoms = [i for i, site in enumerate(struct) if site.species_string == "P"] phosphate_oxygens = [] for p_idx in p_atoms: # 查找P周围的O原子 (距离 < 1.6Å) neighbors = kdtree.query_ball_point(all_coords[p_idx], r=1.6) p_o_indices = [idx for idx in neighbors if idx != p_idx and struct[idx].species_string == "O"] phosphate_oxygens.extend(p_o_indices) # 识别所有H原子并确定归属 hydrogen_owners = {} h_atoms = [i for i, site in enumerate(struct) if site.species_string == "H"] for h_idx in h_atoms: neighbors = kdtree.query_ball_point(all_coords[h_idx], r=1.2) candidate_os = [idx for idx in neighbors if idx != h_idx and struct[idx].species_string == "O"] if not candidate_os: continue min_dist = float('inf') owner_o = None for o_idx in candidate_os: dist = struct.get_distance(h_idx, o_idx) if dist < min_dist: min_dist = dist owner_o = o_idx hydrogen_owners[h_idx] = owner_o # 分类磷酸氧:带H的为P-OH,不带H的为P-O/P=O for o_idx in phosphate_oxygens: has_hydrogen = any(owner_o == o_idx for h_idx, owner_o in hydrogen_owners.items()) if has_hydrogen: atom_types["phosphate_oxygens"]["P-OH"].append(o_idx) else: atom_types["phosphate_oxygens"]["P-O/P=O"].append(o_idx) # 识别水和水合氢离子 all_o_indices = [i for i, site in enumerate(struct) if site.species_string == "O"] non_phosphate_os = [o_idx for o_idx in all_o_indices if o_idx not in phosphate_oxygens] o_h_count = {} for h_idx, owner_o in hydrogen_owners.items(): o_h_count[owner_o] = o_h_count.get(owner_o, 0) + 1 for o_idx in non_phosphate_os: h_count = o_h_count.get(o_idx, 0) attached_hs = [h_idx for h_idx, owner_o in hydrogen_owners.items() if owner_o == o_idx] if h_count == 2: atom_types["water_oxygens"].append(o_idx) atom_types["water_hydrogens"].extend(attached_hs) elif h_count == 3: atom_types["hydronium_oxygens"].append(o_idx) atom_types["hydronium_hydrogens"].extend(attached_hs) # 识别磷酸基团的H原子 for o_idx in atom_types["phosphate_oxygens"]["P-OH"]: attached_hs = [h_idx for h_idx, owner_o in hydrogen_owners.items() if owner_o == o_idx] atom_types["phosphate_hydrogens"].extend(attached_hs) return atom_types def get_hbond_angle_config(): """氢键分析配置""" return [ { "name": "P-O/P=O···Hw", "donor_type": "water_oxygens", "acceptor_type": "P-O/P=O", "h_type": "water_hydrogens", "threshold": 2.375, "color": "blue" }, { "name": "P-O/P=O···Hh", "donor_type": "hydronium_oxygens", "acceptor_type": "P-O/P=O", "h_type": "hydronium_hydrogens", "threshold": 2.275, "color": "green" }, { "name": "P-O/P=O···Hp", "donor_type": "P-OH", "acceptor_type": "P-O/P=O", "h_type": "phosphate_hydrogens", "threshold": 2.175, "color": "red" }, { "name": "P-OH···Ow", "donor_type": "P-OH", "acceptor_type": "water_oxygens", "h_type": "phosphate_hydrogens", "threshold": 2.275, "color": "orange" }, { "name": "Hw···Ow", "donor_type": "water_oxygens", "acceptor_type": "water_oxygens", "h_type": "water_hydrogens", "threshold": 2.375, "color": "purple" }, { "name": "Hh···Ow", "donor_type": "hydronium_oxygens", "acceptor_type": "water_oxygens", "h_type": "hydronium_hydrogens", "threshold": 2.225, "color": "cyan" }, { "name": "Hw···F", "donor_type": "water_oxygens", "acceptor_type": "fluoride_atoms", "h_type": "water_hydrogens", "threshold": 2.225, "color": "magenta" }, { "name": "Hh···F", "donor_type": "hydronium_oxygens", "acceptor_type": "fluoride_atoms", "h_type": "hydronium_hydrogens", "threshold": 2.175, "color": "brown" }, { "name": "Hp···F", "donor_type": "P-OH", "acceptor_type": "fluoride_atoms", "h_type": "phosphate_hydrogens", "threshold": 2.275, "color": "pink" } ] def calculate_angle(struct, donor_idx, h_idx, acceptor_idx): """计算D-H···A键角 (角度制),使用笛卡尔向量表示并处理周期性""" # 获取分数坐标 frac_coords = struct.frac_coords lattice = struct.lattice # 获取氢原子H的分数坐标 h_frac = frac_coords[h_idx] # 计算供体D相对于H的分数坐标差 d_frac = frac_coords[donor_idx] dh_frac = d_frac - h_frac # 计算受体A相对于H的分数坐标差 a_frac = frac_coords[acceptor_idx] ah_frac = a_frac - h_frac # 应用周期性修正 (将分数坐标差限制在[-0.5, 0.5]范围内) dh_frac = np.where(dh_frac > 0.5, dh_frac - 1, dh_frac) dh_frac = np.where(dh_frac < -0.5, dh_frac + 1, dh_frac) ah_frac = np.where(ah_frac > 0.5, ah_frac - 1, ah_frac) ah_frac = np.where(ah_frac < -0.5, ah_frac + 1, ah_frac) # 转换为笛卡尔向量 (H->D 和 H->A) vec_hd = np.dot(dh_frac, lattice.matrix) # H->D向量 vec_ha = np.dot(ah_frac, lattice.matrix) # H->A向量 # 计算向量点积 dot_product = np.dot(vec_hd, vec_ha) # 计算向量模长 norm_hd = np.linalg.norm(vec_hd) norm_ha = np.linalg.norm(vec_ha) # 避免除以零 if norm_hd < 1e-6 or norm_ha < 1e-6: return None # 计算余弦值 cos_theta = dot_product / (norm_hd * norm_ha) # 处理数值误差 cos_theta = np.clip(cos_theta, -1.0, 1.0) # 计算角度 (弧度转角度) angle_rad = np.arccos(cos_theta) angle_deg = np.degrees(angle_rad) # 检查异常角度值 if angle_deg < 90 or angle_deg > 180: print(f"警告: 异常键角值 {angle_deg:.2f}° (原子 {donor_idx}-{h_idx}-{acceptor_idx})") print(f"向量HD: {vec_hd}, 长度: {norm_hd:.4f} Å") print(f"向量HA: {vec_ha}, 长度: {norm_ha:.4f} Å") print(f"点积: {dot_product:.4f}, cosθ: {cos_theta:.4f}") return angle_deg def calculate_hbond_angles_frame(struct, atom_types, hbond_config, bond_threshold=1.3): """计算单帧结构中氢键键角""" # 构建全局KDTree用于快速搜索 all_coords = np.array([site.coords for site in struct]) lattice_abc = struct.lattice.abc kdtree = cKDTree(all_coords, boxsize=lattice_abc) # 结果字典: {氢键类型: [角度列表]} angle_results = {hbond["name"]: [] for hbond in hbond_config} # 处理每一类氢键 for hbond in hbond_config: # 获取供体原子列表 if hbond["donor_type"] == "P-OH": donors = atom_types["phosphate_oxygens"]["P-OH"] else: donors = atom_types[hbond["donor_type"]] # 获取受体原子列表 if hbond["acceptor_type"] == "P-O/P=O": acceptors = atom_types["phosphate_oxygens"]["P-O/P=O"] elif hbond["acceptor_type"] == "P-OH": acceptors = atom_types["phosphate_oxygens"]["P-OH"] else: acceptors = atom_types[hbond["acceptor_type"]] # 获取氢原子列表 hydrogens = atom_types[hbond["h_type"]] # 如果没有氢原子或受体,跳过 if len(hydrogens) == 0 or len(acceptors) == 0: continue # 为受体构建KDTree(使用全局坐标) acceptor_coords = all_coords[acceptors] acceptor_kdtree = cKDTree(acceptor_coords, boxsize=lattice_abc) # 遍历所有氢原子 for h_idx in hydrogens: h_coords = all_coords[h_idx] # 查找与H成键的供体 (距离 < bond_threshold) donor_neighbors = kdtree.query_ball_point(h_coords, r=bond_threshold) donor_candidates = [idx for idx in donor_neighbors if idx in donors] # 如果没有找到供体,跳过 if not donor_candidates: continue # 选择最近的供体 min_dist = float('inf') donor_idx = None for d_idx in donor_candidates: dist = struct.get_distance(h_idx, d_idx) if dist < min_dist: min_dist = dist donor_idx = d_idx # 查找在阈值内的受体 acceptor_indices = acceptor_kdtree.query_ball_point(h_coords, r=hbond["threshold"]) for a_idx_offset in acceptor_indices: a_idx = acceptors[a_idx_offset] # 排除供体自身 if a_idx == donor_idx: continue # 计算键角 angle = calculate_angle(struct, donor_idx, h_idx, a_idx) if angle is not None and angle >= 0.0: angle_results[hbond["name"]].append(angle) return angle_results def calculate_hbond_angles_frame_wrapper(args): """包装函数用于多进程处理""" struct, hbond_config = args atom_types = identify_atom_types(struct) return calculate_hbond_angles_frame(struct, atom_types, hbond_config) def calculate_hbond_angles_parallel(structures, workers=1): """并行计算氢键键角""" hbond_config = get_hbond_angle_config() all_results = {hbond["name"]: [] for hbond in hbond_config} # 准备参数列表 args_list = [(struct, hbond_config) for struct in structures] # 如果没有可用的worker,则顺序执行 if workers == 1: results = [] for args in tqdm(args_list, desc="Calculating HBond Angles"): results.append(calculate_hbond_angles_frame_wrapper(args)) else: with multiprocessing.Pool(processes=workers) as pool: results = list(tqdm( pool.imap(calculate_hbond_angles_frame_wrapper, args_list), total=len(structures), desc="Calculating HBond Angles" )) # 合并结果 for frame_result in results: for hbond_name, angles in frame_result.items(): all_results[hbond_name].extend(angles) return all_results def plot_hbond_angle_distribution(all_angles, system_name): """绘制氢键键角分布图并保存原始数据""" # 创建输出目录 os.makedirs("HBond_Angle_Plots", exist_ok=True) os.makedirs("HBond_Angle_Data", exist_ok=True) hbond_config = get_hbond_angle_config() # 创建统计信息汇总文件 summary_path = os.path.join("HBond_Angle_Data", f"{system_name}_summary.csv") with open(summary_path, 'w', newline='') as summary_file: summary_writer = csv.writer(summary_file) summary_writer.writerow(["HBond Type", "Count", "Mean (°)", "Std Dev (°)", "Median (°)", "90% Threshold (°)", "Min (°)", "Max (°)"]) # 添加90%阈值列 # 处理每种氢键类型 for hbond in hbond_config: angles = np.array(all_angles[hbond["name"]]) if len(angles) == 0: print(f"No angles found for {hbond['name']} in {system_name}") continue # 计算90%阈值(第10百分位数) if len(angles) >= 5: # 确保有足够的数据点 threshold_90 = np.percentile(angles, 10) # 90%的氢键角度大于等于此值 else: threshold_90 = np.nan # 创建安全文件名 safe_name = hbond["name"].replace("/", "").replace("=", "").replace("···", "_").replace(" ", "_") # ========== 保存原始角度数据 ========== data_path = os.path.join("HBond_Angle_Data", f"{system_name}_{safe_name}.csv") with open(data_path, 'w', newline='') as data_file: data_writer = csv.writer(data_file) data_writer.writerow(["Angle (degrees)"]) for angle in angles: data_writer.writerow([f"{angle:.4f}"]) # ========== 计算统计量 ========== mean_angle = np.mean(angles) std_angle = np.std(angles) median_angle = np.median(angles) min_angle = np.min(angles) max_angle = np.max(angles) # 保存统计信息到汇总文件 with open(summary_path, 'a', newline='') as summary_file: summary_writer = csv.writer(summary_file) summary_writer.writerow([ hbond["name"], len(angles), f"{mean_angle:.4f}", f"{std_angle:.4f}", f"{median_angle:.4f}", f"{threshold_90:.4f}", # 添加90%阈值 f"{min_angle:.4f}", f"{max_angle:.4f}" ]) # ========== 绘制分布图 ========== plt.figure(figsize=(8, 6)) # 绘制直方图 hist, bins, _ = plt.hist(angles, bins=30, density=True, alpha=0.5, color=hbond["color"], label="Histogram") # 绘制KDE曲线 kde = gaussian_kde(angles) x = np.linspace(0, 180, 500) plt.plot(x, kde(x), color="black", linewidth=2.5, label="KDE") # 添加90%阈值线 if not np.isnan(threshold_90): plt.axvline(x=threshold_90, color='red', linestyle='--', linewidth=2.0, label=f'90% Threshold: {threshold_90:.2f}°') # 添加统计信息 stats_text = (f"Mean: {mean_angle:.2f}°\n" f"Std: {std_angle:.2f}°\n" f"Median: {median_angle:.2f}°\n" f"90% Threshold: {threshold_90:.2f}°\n" # 添加90%阈值 f"Count: {len(angles)}") plt.text(0.75, 0.95, stats_text, transform=plt.gca().transAxes, verticalalignment='top', bbox=dict(boxstyle='round', alpha=0.5)) # 设置标题和标签 plt.title(f"{system_name}: {hbond['name']} Bond Angle Distribution", fontsize=16) plt.xlabel("Bond Angle (degrees)", fontsize=14) plt.ylabel("Probability Density", fontsize=14) plt.xlim(0, 180) plt.ylim(0, None) plt.grid(True, linestyle='--', alpha=0.7) plt.legend() # 保存图像 image_path = os.path.join("HBond_Angle_Plots", f"{system_name}_{safe_name}_angle.tiff") plt.savefig(image_path, dpi=600, bbox_inches='tight') plt.close() print(f"Saved HBond angle data: {data_path}") print(f"Saved HBond angle plot: {image_path}") print(f"Saved summary statistics: {summary_path}") def main(vasprun_files, workers=1): """主处理函数""" for system_name, vasprun_file in vasprun_files.items(): print(f"\n{'='*50}") print(f"Processing {system_name}: {vasprun_file} with {workers} workers") print(f"{'='*50}") start_time = time.time() try: # 加载VASP结果 vr = Vasprun(vasprun_file, ionic_step_skip=5) structures = vr.structures print(f"Loaded {len(structures)} frames") print(f"Lattice parameters: {structures[0].lattice.abc}") print(f"Periodic boundary handling: Fractional coordinates + PBC correction") # 计算氢键键角分布 print("Calculating hydrogen bond angles...") hbond_angles = calculate_hbond_angles_parallel(structures, workers=workers) # 绘制并保存结果 plot_hbond_angle_distribution(hbond_angles, system_name) elapsed = time.time() - start_time print(f"\nCompleted processing for {system_name} in {elapsed:.2f} seconds") except Exception as e: print(f"Error processing {system_name}: {str(e)}", file=sys.stderr) import traceback traceback.print_exc() print("\nAll HBond angle analysis completed successfully!") if __name__ == "__main__": # 设置命令行参数 parser = argparse.ArgumentParser(description='Calculate hydrogen bond angles from VASP simulations') parser.add_argument('--workers', type=int, default=multiprocessing.cpu_count(), help=f'Number of parallel workers (default: {multiprocessing.cpu_count()})') args = parser.parse_args() # 自动设置vasprun文件和系统名称 vasprun_files = { "System1": "vasprun1.xml", "System2": "vasprun2.xml", "System3": "vasprun3.xml", "System4": "vasprun4.xml" } # 检查文件是否存在 missing_files = [name for name, path in vasprun_files.items() if not os.path.exists(path)] if missing_files: raise FileNotFoundError(f"Missing vasprun files: {', '.join(missing_files)}") print(f"Starting HBond angle analysis with {args.workers} workers...") main(vasprun_files, workers=args.workers)以上代码根据D-H的化学距离,H-A之间的氢键距离来判定氢键,计算了氢键角度的分布,在这里我们需要根据原有的距离限制的基础上加入氢键角度的限制,来使氢键的存在更严谨,将代码中的部分修改def get_hbond_config(): """返回氢键配置,包含距离和角度阈值""" return [ { "name": "P-O/P=O···Hw", "donor_type": "water_oxygens", "acceptor_type": "P-O/P=O", "h_type": "water_hydrogens", "distance_threshold": 2.375, "angle_threshold": 143.99, "color": "#1f77b4" }, { "name": "P-O/P=O···Hh", "donor_type": "hydronium_oxygens", "acceptor_type": "P-O/P=O", "h_type": "hydronium_hydrogens", "distance_threshold": 2.275, "angle_threshold": 157.82, "color": "#ff7f0e" }, { "name": "P-O/P=O···Hp", "donor_type": "P-OH", "acceptor_type": "P-O/P=O", "h_type": "phosphate_hydrogens", "distance_threshold": 2.175, "angle_threshold": 155.00, "color": "#2ca02c" }, { "name": "P-OH···Ow", "donor_type": "P-OH", "acceptor_type": "water_oxygens", "h_type": "phosphate_hydrogens", "distance_threshold": 2.275, "angle_threshold": 155.13, "color": "#d62728" }, { "name": "Hw···Ow", "donor_type": "water_oxygens", "acceptor_type": "water_oxygens", "h_type": "water_hydrogens", "distance_threshold": 2.375, "angle_threshold": 138.73, "color": "#9467bd" }, { "name": "Hh···Ow", "donor_type": "hydronium_oxygens", "acceptor_type": "water_oxygens", "h_type": "hydronium_hydrogens", "distance_threshold": 2.225, "angle_threshold": 155.31, "color": "#8c564b" }, { "name": "Hw···F", "donor_type": "water_oxygens", "acceptor_type": "fluoride_atoms", "h_type": "water_hydrogens", "distance_threshold": 2.225, "angle_threshold": 137.68, "color": "#e377c2" }, { "name": "Hh···F", "donor_type": "hydronium_oxygens", "acceptor_type": "fluoride_atoms", "h_type": "hydronium_hydrogens", "distance_threshold": 2.175, "angle_threshold": 154.64, "color": "#7f7f7f" }, { "name": "Hp···F", "donor_type": "P-OH", "acceptor_type": "fluoride_atoms", "h_type": "phosphate_hydrogens", "distance_threshold": 2.275, "angle_threshold": 153.71, "color": "#bcbd22" } ],根据加入的角度限制,我们将沿用原代码中的识别逻辑,限制数值,将计算对象改为氢键键长随时间的变化,每10步输出一帧来计算,同样输出对应的Mean,median,std,对于埃的字符我们在这里采用LaTex来实现,图文的表达,则要符合The Journal of Chemical Physics期刊的要求,计算的该键长即是我们设置的distance_threshold部分的具体随时间变化的平均键长变化,实际上的氢键键长一般都在distance_threshold范围内,因为distance_threshold取的氢键键长距离的极限最大值。请确保LaTex正确表达距离埃的单位,而避免错,其次仔细输出代码避免简单的问题出错
07-30
在这里我另外编译了计算氢键寿命的代码,但是该代码当中显示能够识别不同类型的O和H的原子,但是却识别不到磷酸相关的氢键寿命,在这里我需要补充的是,磷酸相关的氢键寿命为0,而其他部分却能正常显示,这说明周期性是没有问题的,而原子识别能够正常输出,说明识别没有问题,而我在可视化中对应的阈值是能够观察到氢键存在的,说明阈值并没有设置的不合适,那到底那里出问题了呢?结合上面的代码来分析以下提供的氢键寿命代码为什么出现这个问题。import numpy as np import matplotlib.pyplot as plt from pymatgen.io.vasp import Vasprun from pymatgen.core import Structure from scipy.optimize import curve_fit from scipy.spatial import cKDTree from tqdm import tqdm import matplotlib as mpl from collections import defaultdict from multiprocessing import Pool, cpu_count import time import argparse import os import csv import warnings from scipy.stats import gaussian_kde import sys import traceback # 忽略可能的警告 warnings.filterwarnings("ignore", category=UserWarning) # ================ 配置区域 ================ # 在这里设置您的 vasprun.xml 文件默认路径 DEFAULT_VASPRUN_PATH = "/path/to/your/vasprun.xml" # 更改为您的实际路径 # ========================================= # 1. 设置期刊绘图格式 mpl.rcParams['font.family'] = 'DejaVu Sans' mpl.rcParams['font.sans-serif'] = ['DejaVu Sans'] mpl.rcParams['font.size'] = 10 mpl.rcParams['axes.linewidth'] = 1.5 mpl.rcParams['xtick.major.width'] = 1.5 mpl.rcParams['ytick.major.width'] = 1.5 mpl.rcParams['lines.linewidth'] = 2 mpl.rcParams['figure.dpi'] = 300 mpl.rcParams['figure.figsize'] = (4.0, 3.0) mpl.rcParams['savefig.dpi'] = 600 mpl.rcParams['savefig.bbox'] = 'tight' mpl.rcParams['savefig.pad_inches'] = 0.05 mpl.rcParams['pdf.fonttype'] = 42 def get_hbond_config(): """返回氢键配置,包含距离和角度阈值""" return [ { "name": "P-O/P=O···Hw", "donor_type": "water_oxygens", "acceptor_type": "P-O/P=O", "h_type": "water_hydrogens", "distance_threshold": 2.375, "angle_threshold": 143.99, "color": "#1f77b4" }, { "name": "P-O/P=O···Hh", "donor_type": "hydronium_oxygens", "acceptor_type": "P-O/P=O", "h_type": "hydronium_hydrogens", "distance_threshold": 2.275, "angle_threshold": 157.82, "color": "#ff7f0e" }, { "name": "P-O/P=O···Hp", "donor_type": "P-OH", "acceptor_type": "P-O/P=O", "h_type": "phosphate_hydrogens", "distance_threshold": 2.175, "angle_threshold": 155.00, "color": "#2ca02c" }, { "name": "P-OH···Ow", "donor_type": "P-OH", "acceptor_type": "water_oxygens", "h_type": "phosphate_hydrogens", "distance_threshold": 2.275, "angle_threshold": 155.13, "color": "#d62728" }, { "name": "Hw···Ow", "donor_type": "water_oxygens", "acceptor_type": "water_oxygens", "h_type": "water_hydrogens", "distance_threshold": 2.375, "angle_threshold": 138.73, "color": "#9467bd" }, { "name": "Hh···Ow", "donor_type": "hydronium_oxygens", "acceptor_type": "water_oxygens", "h_type": "hydronium_hydrogens", "distance_threshold": 2.225, "angle_threshold": 155.31, "color": "#8c564b" }, { "name": "Hw···F", "donor_type": "water_oxygens", "acceptor_type": "fluoride_atoms", "h_type": "water_hydrogens", "distance_threshold": 2.225, "angle_threshold": 137.68, "color": "#e377c2" }, { "name": "Hh···F", "donor_type": "hydronium_oxygens", "acceptor_type": "fluoride_atoms", "h_type": "hydronium_hydrogens", "distance_threshold": 2.175, "angle_threshold": 154.64, "color": "#7f7f7f" }, { "name": "Hp···F", "donor_type": "P-OH", "acceptor_type": "fluoride_atoms", "h_type": "phosphate_hydrogens", "distance_threshold": 2.275, "angle_threshold": 153.71, "color": "#bcbd22" } ] def identify_atom_types(struct, frame_idx=0): """原子类型识别函数 - 增强版,使用更宽松的磷酸氧识别阈值""" # 初始化数据结构 atom_types = { "phosphate_oxygens": {"P-O/P=O": [], "P-OH": []}, "phosphate_hydrogens": [], "water_oxygens": [], "water_hydrogens": [], "hydronium_oxygens": [], "hydronium_hydrogens": [], "fluoride_atoms": [i for i, site in enumerate(struct) if site.species_string == "F"], "aluminum_atoms": [i for i, site in enumerate(struct) if site.species_string == "Al"] } # 构建全局KDTree all_coords = np.array([site.coords for site in struct]) kdtree = cKDTree(all_coords, boxsize=struct.lattice.abc) # 识别磷酸基团 - 使用更宽松的距离阈值(1.8Å) p_atoms = [i for i, site in enumerate(struct) if site.species_string == "P"] phosphate_oxygens = [] for p_idx in p_atoms: # 查找P周围的O原子 (距离 < 1.8Å) neighbors = kdtree.query_ball_point(all_coords[p_idx], r=1.8) p_o_indices = [idx for idx in neighbors if idx != p_idx and struct[idx].species_string == "O"] phosphate_oxygens.extend(p_o_indices) # 去重 phosphate_oxygens = list(set(phosphate_oxygens)) # 识别所有H原子并确定归属 hydrogen_owners = {} h_atoms = [i for i, site in enumerate(struct) if site.species_string == "H"] for h_idx in h_atoms: neighbors = kdtree.query_ball_point(all_coords[h_idx], r=1.2) candidate_os = [idx for idx in neighbors if idx != h_idx and struct[idx].species_string == "O"] if not candidate_os: continue min_dist = float('inf') owner_o = None for o_idx in candidate_os: dist = struct.get_distance(h_idx, o_idx) if dist < min_dist: min_dist = dist owner_o = o_idx hydrogen_owners[h_idx] = owner_o # 分类磷酸氧:带H的为P-OH,不带H的为P-O/P=O for o_idx in phosphate_oxygens: has_hydrogen = any(owner_o == o_idx for h_idx, owner_o in hydrogen_owners.items()) if has_hydrogen: atom_types["phosphate_oxygens"]["P-OH"].append(o_idx) else: atom_types["phosphate_oxygens"]["P-O/P=O"].append(o_idx) # 识别水和水合氢离子 all_o_indices = [i for i, site in enumerate(struct) if site.species_string == "O"] non_phosphate_os = [o_idx for o_idx in all_o_indices if o_idx not in phosphate_oxygens] o_h_count = {} for h_idx, owner_o in hydrogen_owners.items(): o_h_count[owner_o] = o_h_count.get(owner_o, 0) + 1 for o_idx in non_phosphate_os: h_count = o_h_count.get(o_idx, 0) attached_hs = [h_idx for h_idx, owner_o in hydrogen_owners.items() if owner_o == o_idx] if h_count == 2: atom_types["water_oxygens"].append(o_idx) atom_types["water_hydrogens"].extend(attached_hs) elif h_count == 3: atom_types["hydronium_oxygens"].append(o_idx) atom_types["hydronium_hydrogens"].extend(attached_hs) # 识别磷酸基团的H原子 for o_idx in atom_types["phosphate_oxygens"]["P-OH"]: attached_hs = [h_idx for h_idx, owner_o in hydrogen_owners.items() if owner_o == o_idx] atom_types["phosphate_hydrogens"].extend(attached_hs) # 调试输出 - 只在部分帧输出详细信息 if frame_idx % 100 == 0 or frame_idx < 5: print(f"\nFrame {frame_idx} - Atom type counts:") print(f" P-O/P=O: {len(atom_types['phosphate_oxygens']['P-O/P=O'])}") print(f" P-OH: {len(atom_types['phosphate_oxygens']['P-OH'])}") print(f" Phosphate H: {len(atom_types['phosphate_hydrogens'])}") print(f" Water O: {len(atom_types['water_oxygens'])}") print(f" Water H: {len(atom_types['water_hydrogens'])}") print(f" Hydronium O: {len(atom_types['hydronium_oxygens'])}") print(f" Hydronium H: {len(atom_types['hydronium_hydrogens'])}") print(f" Fluoride: {len(atom_types['fluoride_atoms'])}") print(f" Aluminum: {len(atom_types['aluminum_atoms'])}") return atom_types, hydrogen_owners def calculate_angle(struct, donor_idx, h_idx, acceptor_idx): """计算D-H···A键角 (角度制),改进的PBC处理""" try: # 首先尝试使用pymatgen内置方法,自动处理周期性 angle = struct.get_angle(donor_idx, h_idx, acceptor_idx) return angle except Exception as e: # 备用方法:使用向量计算(改进的PBC处理) return calculate_angle_fallback(struct, donor_idx, h_idx, acceptor_idx) def calculate_angle_fallback(struct, donor_idx, h_idx, acceptor_idx): """备用的角度计算方法,改进的PBC处理""" # 获取分数坐标 frac_coords = struct.frac_coords lattice = struct.lattice # 获取原子分数坐标 d_frac = frac_coords[donor_idx] h_frac = frac_coords[h_idx] a_frac = frac_coords[acceptor_idx] # 计算向量差(考虑周期性) dh_frac = d_frac - h_frac ah_frac = a_frac - h_frac # 应用最小镜像约定(minimum image convention) dh_frac = dh_frac - np.round(dh_frac) ah_frac = ah_frac - np.round(ah_frac) # 转换为笛卡尔坐标 vec_dh = np.dot(dh_frac, lattice.matrix) vec_ah = np.dot(ah_frac, lattice.matrix) # 计算角度 dot_product = np.dot(vec_dh, vec_ah) norm_dh = np.linalg.norm(vec_dh) norm_ah = np.linalg.norm(vec_ah) if norm_dh < 1e-6 or norm_ah < 1e-6: return None cos_theta = dot_product / (norm_dh * norm_ah) cos_theta = np.clip(cos_theta, -1.0, 1.0) angle_rad = np.arccos(cos_theta) angle_deg = np.degrees(angle_rad) return angle_deg # 2. 氢键识别函数 (优化版) def precompute_structural_features(structure, frame_idx=0): """预计算结构特征减少重复计算""" features = { 'structure': structure, 'volume': structure.volume } # 识别原子类型 atom_types, hydrogen_owners = identify_atom_types(structure, frame_idx) features['atom_types'] = atom_types features['hydrogen_owners'] = hydrogen_owners # 创建给体到氢原子的映射 donor_to_hydrogens = defaultdict(list) for h_idx, donor_idx in hydrogen_owners.items(): donor_to_hydrogens[donor_idx].append(h_idx) features['donor_to_hydrogens'] = donor_to_hydrogens return features def identify_h_bonds_optimized(features, hbond_config, frame_idx=0): """使用预计算特征识别特定类型的氢键""" h_bonds = [] struct = features['structure'] atom_types = features['atom_types'] # 构建全局KDTree用于快速搜索 all_coords = np.array([site.coords for site in struct]) lattice_abc = struct.lattice.abc kdtree = cKDTree(all_coords, boxsize=lattice_abc) # 处理每一类氢键 # 获取受体原子列表 acceptor_type = hbond_config["acceptor_type"] if acceptor_type in atom_types: if isinstance(atom_types[acceptor_type], dict): # 处理磷酸氧的子类型 acceptors = [] for sub_type in atom_types[acceptor_type].values(): acceptors.extend(sub_type) else: acceptors = atom_types[acceptor_type] else: return h_bonds # 获取氢原子列表 h_type = hbond_config["h_type"] if h_type not in atom_types: return h_bonds hydrogens = atom_types[h_type] # 如果没有氢原子或受体,跳过 if len(hydrogens) == 0 or len(acceptors) == 0: return h_bonds # 为受体构建KDTree(使用全局坐标) acceptor_coords = all_coords[acceptors] acceptor_kdtree = cKDTree(acceptor_coords, boxsize=lattice_abc) # 遍历所有氢原子 for h_idx in hydrogens: h_coords = all_coords[h_idx] # 查找与H成键的供体 (距离 < 1.2Å) donor_neighbors = kdtree.query_ball_point(h_coords, r=1.2) donor_candidates = [] # 根据氢键类型确定供体类型 donor_type = hbond_config["donor_type"] if donor_type in atom_types: if isinstance(atom_types[donor_type], dict): # 处理磷酸氧的子类型 for sub_type in atom_types[donor_type].values(): donor_candidates.extend([idx for idx in donor_neighbors if idx in sub_type]) else: donor_candidates = [idx for idx in donor_neighbors if idx in atom_types[donor_type]] # 如果没有找到供体,跳过 if not donor_candidates: continue # 选择最近的供体 min_dist = float('inf') donor_idx = None for d_idx in donor_candidates: dist = struct.get_distance(h_idx, d_idx) if dist < min_dist: min_dist = dist donor_idx = d_idx # 查找在距离阈值内的受体 acceptor_indices = acceptor_kdtree.query_ball_point(h_coords, r=hbond_config["distance_threshold"]) for a_idx_offset in acceptor_indices: a_idx = acceptors[a_idx_offset] # 排除供体自身 if a_idx == donor_idx: continue # 计算键角 angle = calculate_angle(struct, donor_idx, h_idx, a_idx) # 检查角度阈值 if angle is not None and angle >= hbond_config["angle_threshold"]: # 使用三元组表示氢键 (给体, 氢, 受体) h_bonds.append((donor_idx, h_idx, a_idx)) # 调试输出 - 只在部分帧输出详细信息 if frame_idx % 100 == 0 or frame_idx < 5: print(f"Frame {frame_idx}: Found {len(h_bonds)} {hbond_config['name']} hydrogen bonds") return h_bonds # 3. 并行处理框架 def process_frame(args): """处理单个帧的并行函数""" try: i, structure, hbond_configs = args features = precompute_structural_features(structure, i) frame_results = {} for config in hbond_configs: bonds = identify_h_bonds_optimized(features, config, i) density = len(bonds) / features['volume'] * 1e24 / 6.022e23 frame_results[config["name"]] = (bonds, density) return i, frame_results except Exception as e: print(f"Error in process_frame for frame {i}: {str(e)}") traceback.print_exc() return i, {} def calculate_tcf_parallel(h_bond_list, max_dt, n_workers): """并行计算时间关联函数""" total_bonds = len(h_bond_list[0]) if len(h_bond_list) > 0 else 0 tcf = np.zeros(max_dt) counts = np.zeros(max_dt, dtype=int) total_frames = len(h_bond_list) if total_frames - max_dt <= 0: print(f"Warning: Not enough frames for TCF calculation (total_frames={total_frames}, max_dt={max_dt})") return np.zeros(max_dt) tasks = [(t0, h_bond_list, max_dt) for t0 in range(total_frames - max_dt)] with Pool(n_workers) as pool: results = list(tqdm(pool.imap(process_tcf_task, tasks), total=len(tasks), desc="Parallel TCF Calculation")) for tcf_part, counts_part in results: tcf += tcf_part counts += counts_part valid_counts = np.where(counts > 0, counts, 1) tcf = tcf / valid_counts return tcf / total_bonds if total_bonds > 0 else np.zeros(max_dt) def process_tcf_task(args): """处理单个TCF任务的并行函数""" t0, h_bond_list, max_dt = args ref_bonds = set(h_bond_list[t0]) local_tcf = np.zeros(max_dt) local_counts = np.zeros(max_dt, dtype=int) for dt in range(max_dt): t = t0 + dt if t >= len(h_bond_list): break current_bonds = set(h_bond_list[t]) persistent_bonds = ref_bonds & current_bonds local_tcf[dt] = len(persistent_bonds) local_counts[dt] = 1 return local_tcf, local_counts # 4. 氢键键角和键长分析函数 def calculate_hbond_angles_frame(struct, atom_types, hbond_config, bond_threshold=1.3, frame_idx=0): """计算单帧结构中氢键键角""" # 构建全局KDTree用于快速搜索 all_coords = np.array([site.coords for site in struct]) lattice_abc = struct.lattice.abc kdtree = cKDTree(all_coords, boxsize=lattice_abc) # 结果字典: {氢键类型: [角度列表]} angle_results = {hbond["name"]: [] for hbond in hbond_config} # 处理每一类氢键 for hbond in hbond_config: # 获取供体原子列表 if hbond["donor_type"] == "P-OH": donors = atom_types["phosphate_oxygens"]["P-OH"] else: donors = atom_types[hbond["donor_type"]] # 获取受体原子列表 if hbond["acceptor_type"] == "P-O/P=O": acceptors = atom_types["phosphate_oxygens"]["P-O/P=O"] elif hbond["acceptor_type"] == "P-OH": acceptors = atom_types["phosphate_oxygens"]["P-OH"] else: acceptors = atom_types[hbond["acceptor_type"]] # 获取氢原子列表 hydrogens = atom_types[hbond["h_type"]] # 如果没有氢原子或受体,跳过 if len(hydrogens) == 0 or len(acceptors) == 0: continue # 为受体构建KDTree(使用全局坐标) acceptor_coords = all_coords[acceptors] acceptor_kdtree = cKDTree(acceptor_coords, boxsize=lattice_abc) # 遍历所有氢原子 for h_idx in hydrogens: h_coords = all_coords[h_idx] # 查找与H成键的供体 (距离 < bond_threshold) donor_neighbors = kdtree.query_ball_point(h_coords, r=bond_threshold) donor_candidates = [idx for idx in donor_neighbors if idx in donors] # 如果没有找到供体,跳过 if not donor_candidates: continue # 选择最近的供体 min_dist = float('inf') donor_idx = None for d_idx in donor_candidates: dist = struct.get_distance(h_idx, d_idx) if dist < min_dist: min_dist = dist donor_idx = d_idx # 查找在阈值内的受体 acceptor_indices = acceptor_kdtree.query_ball_point(h_coords, r=hbond["distance_threshold"]) for a_idx_offset in acceptor_indices: a_idx = acceptors[a_idx_offset] # 排除供体自身 if a_idx == donor_idx: continue # 计算键角 angle = calculate_angle(struct, donor_idx, h_idx, a_idx) if angle is not None and angle >= 0.0: angle_results[hbond["name"]].append(angle) return angle_results def calculate_hbond_lengths_frame(struct, atom_types, hbond_config, bond_threshold=1.3, frame_idx=0): """计算单帧结构中氢键键长(H···A距离)""" # 构建全局KDTree用于快速搜索 all_coords = np.array([site.coords for site in struct]) lattice_abc = struct.lattice.abc kdtree = cKDTree(all_coords, boxsize=lattice_abc) # 结果字典: {氢键类型: [键长列表]} length_results = {hbond["name"]: [] for hbond in hbond_config} # 处理每一类氢键 for hbond in hbond_config: # 获取供体原子列表 if hbond["donor_type"] == "P-OH": donors = atom_types["phosphate_oxygens"]["P-OH"] else: donors = atom_types[hbond["donor_type"]] # 获取受体原子列表 if hbond["acceptor_type"] == "P-O/P=O": acceptors = atom_types["phosphate_oxygens"]["P-O/P=O"] elif hbond["acceptor_type"] == "P-OH": acceptors = atom_types["phosphate_oxygens"]["P-OH"] else: acceptors = atom_types[hbond["acceptor_type"]] # 获取氢原子列表 hydrogens = atom_types[hbond["h_type"]] # 如果没有氢原子或受体,跳过 if len(hydrogens) == 0 or len(acceptors) == 0: continue # 为受体构建KDTree(使用全局坐标) acceptor_coords = all_coords[acceptors] acceptor_kdtree = cKDTree(acceptor_coords, boxsize=lattice_abc) # 遍历所有氢原子 for h_idx in hydrogens: h_coords = all_coords[h_idx] # 查找与H成键的供体 (距离 < bond_threshold) donor_neighbors = kdtree.query_ball_point(h_coords, r=bond_threshold) donor_candidates = [idx for idx in donor_neighbors if idx in donors] # 如果没有找到供体,跳过 if not donor_candidates: continue # 选择最近的供体 min_dist = float('inf') donor_idx = None for d_idx in donor_candidates: dist = struct.get_distance(h_idx, d_idx) if dist < min_dist: min_dist = dist donor_idx = d_idx # 查找在距离阈值内的受体 acceptor_indices = acceptor_kdtree.query_ball_point(h_coords, r=hbond["distance_threshold"]) for a_idx_offset in acceptor_indices: a_idx = acceptors[a_idx_offset] # 排除供体自身 if a_idx == donor_idx: continue # 计算键长 (H···A距离) ha_distance = struct.get_distance(h_idx, a_idx) # 计算键角 angle = calculate_angle(struct, donor_idx, h_idx, a_idx) # 检查角度阈值 if angle is not None and angle >= hbond["angle_threshold"]: length_results[hbond["name"]].append(ha_distance) return length_results def analyze_hbond_geometry(structures, workers=1): """分析氢键几何参数(键长和键角)""" hbond_config = get_hbond_config() all_angles = {hbond["name"]: [] for hbond in hbond_config} all_lengths = {hbond["name"]: [] for hbond in hbond_config} print("Analyzing hydrogen bond geometry...") # 使用每10帧进行分析以减少计算量 step = max(1, len(structures) // 100) selected_structures = structures[::step] for i, struct in enumerate(tqdm(selected_structures, desc="Processing frames")): atom_types, _ = identify_atom_types(struct, i) # 计算键角 angle_results = calculate_hbond_angles_frame(struct, atom_types, hbond_config, frame_idx=i) for name, angles in angle_results.items(): all_angles[name].extend(angles) # 计算键长 length_results = calculate_hbond_lengths_frame(struct, atom_types, hbond_config, frame_idx=i) for name, lengths in length_results.items(): all_lengths[name].extend(lengths) return all_angles, all_lengths def plot_hbond_geometry(all_angles, all_lengths, system_name): """绘制氢键几何参数分布""" os.makedirs("HBond_Geometry", exist_ok=True) hbond_config = get_hbond_config() # 专业颜色方案 colors = [config["color"] for config in hbond_config] # 绘制键角分布 plt.figure(figsize=(10, 6)) for i, (name, angles) in enumerate(all_angles.items()): if angles: kde = gaussian_kde(angles) x = np.linspace(90, 180, 200) plt.plot(x, kde(x), color=colors[i], label=name, linewidth=2) plt.xlabel("Bond Angle (degrees)") plt.ylabel("Probability Density") plt.title(f"{system_name}: Hydrogen Bond Angle Distribution") plt.legend() plt.grid(True, alpha=0.3) plt.savefig(os.path.join("HBond_Geometry", f"{system_name}_angles.pdf"), dpi=300, bbox_inches='tight') plt.close() # 绘制键长分布 plt.figure(figsize=(10, 6)) for i, (name, lengths) in enumerate(all_lengths.items()): if lengths: kde = gaussian_kde(lengths) x = np.linspace(1.0, 3.0, 200) plt.plot(x, kde(x), color=colors[i], label=name, linewidth=2) plt.xlabel("Bond Length (Å)") plt.ylabel("Probability Density") plt.title(f"{system_name}: Hydrogen Bond Length Distribution") plt.legend() plt.grid(True, alpha=0.3) plt.savefig(os.path.join("HBond_Geometry", f"{system_name}_lengths.pdf"), dpi=300, bbox_inches='tight') plt.close() # 保存统计数据 with open(os.path.join("HBond_Geometry", f"{system_name}_statistics.csv"), 'w', newline='') as f: writer = csv.writer(f) writer.writerow(["Bond Type", "Angle Mean", "Angle Std", "Length Mean", "Length Std", "Count"]) for hbond in hbond_config: name = hbond["name"] angles = all_angles[name] lengths = all_lengths[name] if angles and lengths: writer.writerow([ name, f"{np.mean(angles):.2f}", f"{np.std(angles):.2f}", f"{np.mean(lengths):.3f}", f"{np.std(lengths):.3f}", len(angles) ]) # 5. 主函数(优化版) def main_optimized(workers, vasprun_path): print(f"Using {workers} workers for parallel processing") print(f"Using vasprun file: {vasprun_path}") # 时间参数定义 time_per_step = 0.2e-3 # ps skip_steps = 5 frame_interval = time_per_step * skip_steps print(f"Time per frame: {frame_interval:.6f} ps") # 读取VASP轨迹 print(f"Reading vasprun.xml...") try: vr = Vasprun(vasprun_path, ionic_step_skip=skip_steps) structures = vr.structures total_frames = len(structures) print(f"Loaded {total_frames} frames") # 测试第一帧的原子类型识别 test_atom_types, _ = identify_atom_types(structures[0]) print("Atom type counts in first frame:") for key, value in test_atom_types.items(): if isinstance(value, dict): for sub_key, sub_value in value.items(): print(f" {key}.{sub_key}: {len(sub_value)}") else: print(f" {key}: {len(value)}") except Exception as e: print(f"Error loading vasprun file: {str(e)}") return # 计算总模拟时间 total_sim_time = total_frames * frame_interval print(f"Total simulation time: {total_sim_time:.3f} ps") # 分析氢键几何参数 all_angles, all_lengths = analyze_hbond_geometry(structures, workers=workers) system_name = os.path.basename(vasprun_path).replace(".xml", "") plot_hbond_geometry(all_angles, all_lengths, system_name) # 设置时间窗口 max_time_window = min(2.0, total_sim_time * 0.5) max_dt_frames = int(max_time_window / frame_interval) print(f"Setting TCF time window: {max_time_window:.3f} ps ({max_dt_frames} frames)") # 获取氢键配置 hbond_configs = get_hbond_config() bond_names = [config["name"] for config in hbond_configs] colors = [config["color"] for config in hbond_configs] # 并行计算氢键密度 print(f"Parallel hydrogen bond calculation with {workers} workers...") start_time = time.time() # 创建任务列表 tasks = [(i, structure, hbond_configs) for i, structure in enumerate(structures)] # 使用进程池并行处理 densities = {name: np.zeros(total_frames) for name in bond_names} h_bond_lists = {name: [None] * total_frames for name in bond_names} try: with Pool(workers) as pool: results = list(tqdm(pool.imap(process_frame, tasks), total=total_frames, desc="Processing Frames")) # 整理结果 for i, frame_results in results: for name in bond_names: if name in frame_results: # 确保键存在 bonds, density = frame_results[name] densities[name][i] = density h_bond_lists[name][i] = bonds else: densities[name][i] = 0 h_bond_lists[name][i] = [] # 输出氢键数量的统计信息 print("\nHydrogen bond counts per frame (average):") for name in bond_names: avg_count = np.mean([len(bonds) for bonds in h_bond_lists[name] if bonds is not None]) print(f" {name}: {avg_count:.2f}") except Exception as e: print(f"Error during parallel processing: {str(e)}") import traceback traceback.print_exc() return # 计算平均密度 avg_densities = {name: np.mean(densities[name]) for name in bond_names} hb_time = time.time() - start_time print(f"H-bond calculation completed in {hb_time:.2f} seconds") # 检查磷酸相关氢键的情况 phosphate_bonds = [name for name in bond_names if "P-O" in name or "P=O" in name or "P-OH" in name] print("\nPhosphate-related hydrogen bond analysis:") for name in phosphate_bonds: total_count = sum(len(bonds) for bonds in h_bond_lists[name] if bonds is not None) frames_with_bonds = sum(1 for bonds in h_bond_lists[name] if bonds and len(bonds) > 0) print(f" {name}: Total bonds = {total_count}, Frames with bonds = {frames_with_bonds}/{total_frames}") # 如果有问题,输出前10帧的详细情况 if total_count == 0: print(f" Detailed analysis for first 10 frames:") for i in range(min(10, total_frames)): bonds = h_bond_lists[name][i] if bonds is not None: print(f" Frame {i}: {len(bonds)} bonds") # 并行计算TCF print("Parallel TCF calculation...") tcf_results = {} lifetimes = {} reliability = {} for name in bond_names: print(f"\nProcessing bond type: {name}") h_bond_list = h_bond_lists[name] # 检查是否有氢键 total_hbonds = sum(len(bonds) for bonds in h_bond_list if bonds is not None) if total_hbonds == 0: print(f"No hydrogen bonds found for {name}, skipping TCF calculation") tcf_results[name] = np.zeros(max_dt_frames) lifetimes[name] = np.nan reliability[name] = "no bonds found" continue start_time = time.time() try: tcf = calculate_tcf_parallel(h_bond_list, max_dt_frames, workers) tcf_results[name] = tcf except Exception as e: print(f"Error calculating TCF for {name}: {str(e)}") tcf_results[name] = np.zeros(max_dt_frames) lifetimes[name] = np.nan reliability[name] = "TCF calculation failed" continue # 创建时间轴 (转换为ps) t_frames = np.arange(len(tcf)) t_ps = t_frames * frame_interval # 指数拟合 mask = (tcf > 0.01) & (t_ps < max_time_window * 0.8) if np.sum(mask) > 3: try: # 确保TCF值在合理范围内 valid_tcf = tcf[mask] if np.max(valid_tcf) < 0.1: # TCF值太小,可能无法拟合 print(f"TCF values too small for fitting {name}") lifetimes[name] = np.nan reliability[name] = "TCF values too small" else: popt, _ = curve_fit(exp_decay, t_ps[mask], tcf[mask], p0=[1.0, 1.0], maxfev=5000) lifetimes[name] = popt[0] if lifetimes[name] > max_time_window * 0.8: reliability[name] = "unreliable (τ > 80% time window)" else: reliability[name] = "reliable" print(f"Fit successful: τ = {lifetimes[name]:.3f} ps ({reliability[name]})") except Exception as e: print(f"Fit failed for {name}: {str(e)}") lifetimes[name] = np.nan reliability[name] = "fit failed" else: print(f"Not enough data points for fitting {name}") lifetimes[name] = np.nan reliability[name] = "insufficient data" tcf_time = time.time() - start_time print(f"TCF calculation for {name} completed in {tcf_time:.2f} seconds") # 绘图 labels = {config["name"]: config["name"] for config in hbond_configs} # 绘制数量密度 fig1, ax1 = plt.subplots() time_axis = np.arange(len(structures)) * frame_interval for i, name in enumerate(bond_names): ax1.plot(time_axis, densities[name], color=colors[i], label=labels[name], alpha=0.7) ax1.set_xlabel('Time (ps)') ax1.set_ylabel('Concentration (mol/L)') ax1.legend(frameon=False, fontsize=9) fig1.tight_layout() fig1.savefig('hbond_densities.pdf') print("Saved hbond_densities.pdf") # 绘制时间关联函数 fig2, ax2 = plt.subplots() for i, name in enumerate(bond_names): t_ps = np.arange(len(tcf_results[name])) * frame_interval # 创建标签 label = f'{labels[name]}' if name in lifetimes and not np.isnan(lifetimes[name]): label += f' (τ={lifetimes[name]:.3f} ps)' if reliability.get(name, "").startswith("unreliable"): label += '*' ax2.semilogy(t_ps, tcf_results[name], color=colors[i], label=label) # 标记时间窗口上限 if i == 0: ax2.axvline(x=max_time_window, color='gray', linestyle='--', alpha=0.5) ax2.text(max_time_window, 0.5, 'Time window limit', rotation=90, va='center', ha='right', fontsize=8) ax2.set_xlabel('Time (ps)') ax2.set_ylabel('Hydrogen Bond TCF') ax2.set_xlim(0, max_time_window * 1.1) ax2.set_ylim(1e-3, 1.1) ax2.legend(frameon=False, loc='best', fontsize=9) # 添加可靠性说明 if any("unreliable" in rel for name, rel in reliability.items() if isinstance(rel, str)): ax2.text(0.95, 0.05, "* Unreliable fit (τ > 80% time window)", transform=ax2.transAxes, ha='right', fontsize=8) fig2.tight_layout() fig2.savefig('hbond_tcf.pdf') print("Saved hbond_tcf.pdf") # 保存详细结果到CSV文件 os.makedirs("HBond_Lifetime_Results", exist_ok=True) results_path = os.path.join("HBond_Lifetime_Results", "detailed_results.csv") with open(results_path, 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(["Bond Type", "Avg Density (mol/L)", "Lifetime (ps)", "Reliability"]) for name in bond_names: lifetime = lifetimes.get(name, np.nan) rel = reliability.get(name, "N/A") writer.writerow([name, f"{avg_densities[name]:.6f}", f"{lifetime:.6f}", rel]) print(f"Saved detailed results: {results_path}") # 打印结果 print("\nResults Summary:") print("="*70) print("{:<20} {:<15} {:<15} {:<20}".format( "Bond Type", "Avg Density", "Lifetime (ps)", "Reliability")) print("-"*70) for name in bond_names: lifetime = lifetimes.get(name, np.nan) rel = reliability.get(name, "N/A") print("{:<20} {:<15.4f} {:<15.3f} {:<20}".format( name, avg_densities[name], lifetime, rel )) print("="*70) def exp_decay(t, tau, A): return A * np.exp(-t / tau) if __name__ == "__main__": # 命令行参数解析 parser = argparse.ArgumentParser(description='Hydrogen Bond Lifetime Analysis') parser.add_argument('--workers', type=int, default=max(1, cpu_count() - 4), help='Number of parallel workers (default: CPU cores - 4)') parser.add_argument('--vasprun', type=str, default=DEFAULT_VASPRUN_PATH, help='Path to vasprun.xml file') args = parser.parse_args() print(f"Starting analysis with {args.workers} workers") print(f"Using vasprun file: {args.vasprun}") main_optimized(workers=args.workers, vasprun_path=args.vasprun)
最新发布
08-28
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值