使用scipy.kdtree时出现distance = [inf inf ... inf] ,idx = out of boundary问题

本文针对在算法中遇到的距离计算异常情况,如distance为0或inf的问题,提出了有效的解决方案。通过增加判断条件并适时终止循环,确保算法稳定运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

是由于有临近点的distance = 0 或者 distance = inf(无穷大)导致的问题

我的解决方案是 加一个判断条件然后break

``` import numpy as np from stl import mesh import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.spatial import KDTree def extract_boundary_edges(stl_file): stl_mesh = mesh.Mesh.from_file(stl_file) triangles = stl_mesh.vectors edge_count = {} for triangle in triangles: for i in range(3): edge = tuple(sorted([tuple(triangle[i]), tuple(triangle[(i + 1) % 3])])) edge_count[edge] = edge_count.get(edge, 0) + 1 boundary_edges = [edge for edge, count in edge_count.items() if count == 1] return boundary_edges def connect_boundary_edges(boundary_edges): # 构建边索引(以起点为键) edge_dict = {} for edge in boundary_edges: p1, p2 = edge edge_dict.setdefault(p1, []).append((p2, edge)) edge_dict.setdefault(p2, []).append((p1, edge)) connected_paths = [] visited_edges = set() for edge in boundary_edges: if edge in visited_edges: continue path = [] current_edge = edge p1, p2 = current_edge while True: visited_edges.add(current_edge) path.append(current_edge) # 寻找下一个连接的边 next_edges = edge_dict.get(p2, []) for next_p, next_edge in next_edges: if next_edge not in visited_edges and next_p != p1: current_edge = next_edge p1, p2 = current_edge break else: break if path: connected_paths.append(path) return connected_paths def generate_new_edges(boundary_edges): all_points = [] # 从每个边界边取两个端点和中心点 for edge in boundary_edges: p1, p2 = np.array(edge) center = (p1 + p2) / 2 all_points.extend([p1, p2, center]) all_points = np.array(all_points) # 使用 KDTree 查找每个点的最近邻多个点 tree = KDTree(all_points) new_edges = [] for point in all_points: _, indices = tree.query(point, k=10) # 查找更多的最近邻点 indices = indices[1:] # 去掉自身点 valid_neighbors = [] for idx in indices: neighbor = all_points[idx] vector = neighbor - point if len(valid_neighbors) == 0: valid_neighbors.append(neighbor) elif len(valid_neighbors) == 1: prev_vector = valid_neighbors[0] - point dot_product = np.dot(vector, prev_vector) if dot_product <= 0: # 判断方向是否不同 valid_neighbors.append(neighbor) if len(valid_neighbors) == 2: break for neighbor in valid_neighbors: new_edge = tuple(sorted([tuple(point), tuple(neighbor)])) if new_edge not in new_edges: new_edges.append(new_edge) return new_edges def visualize_boundary_edges(edges): fig = plt.figure() ax = fig.add_subplot(111, projection='3d') for edge in edges: p1, p2 = np.array(edge) ax.plot([p1[0], p2[0]], [p1[1], p2[1]], [p1[2], p2[2]], 'b-') ax.set_box_aspect([1, 1, 0.1]) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') plt.show() # 示例用法 stl_file = '片2.stl' boundary_edges = extract_boundary_edges(stl_file) connected_paths = connect_boundary_edges(boundary_edges) # 展平连接路径中的边界边 flattened_boundary_edges = [edge for path in connected_paths for edge in path] # 生成新的边 new_edges = generate_new_edges(flattened_boundary_edges) print(f"生成的新边数量: {len(new_edges)}") visualize_boundary_edges(new_edges)```优化代码
03-15
i优化以下代码,不使用scipy库,且显示优化后的所有程序代码 import numpy as np import matplotlib.pyplot as plt from scipy.spatial import KDTree class Edge: def __init__(self, n1, n2): self.nodes = (n1, n2) # 边的两个端点坐标 self.active = True # 是否在波前中 class Triangle: def __init__(self, n1, n2, n3): self.nodes = (n1, n2, n3) # 三角形的三个端点坐标 class Mesh: def __init__(self): self.nodes = [] # 节点坐标列表 [(x1,y1), (x2,y2), ...] self.edges = [] # 边对象列表 self.triangles = [] # 三角形对象列表 def initialize_front(mesh, boundary_points): for i in range(len(boundary_points)): n1 = boundary_points[i] n2 = boundary_points[(i+1)%len(boundary_points)] if n1 not in mesh.nodes: mesh.nodes.append(n1) if n2 not in mesh.nodes: mesh.nodes.append(n2) mesh.edges.append(Edge(n1, n2)) # 修正:使用append添加边 def compute_normal(p1, p2): dx = p2[0] - p1[0] dy = p2[1] - p1[1] normal = np.array([dy, -dx]) # 修正:法向量指向顺针边界的内部 length = np.linalg.norm(normal) return normal / length if length != 0 else np.zeros(2) def advancing_front(mesh, target_length=0.1): while True: active_edges = [e for e in mesh.edges if e.active] if not active_edges: break front_edge = active_edges[0] n1, n2 = front_edge.nodes # 计算新节点坐标 midpoint = (np.array(n1) + np.array(n2)) / 2 normal = compute_normal(n1, n2) new_node = midpoint + target_length * normal # 碰撞检测(排除当前边端点) if len(mesh.nodes) > 0: tree = KDTree(mesh.nodes) dist, idx = tree.query(new_node, k=5) too_close = any( not np.allclose(mesh.nodes[i], n1) and not np.allclose(mesh.nodes[i], n2) and d < 0.8*target_length for i, d in zip(idx, dist) if i < len(mesh.nodes) ) if too_close: front_edge.active = False continue # 添加新节点和三角形 mesh.nodes.append(tuple(new_node)) new_node_tuple = tuple(new_node) mesh.triangles.append(Triangle(n1, n2, new_node_tuple)) # 更新波前边 front_edge.active = False mesh.edges.append(Edge(n1, new_node_tuple)) mesh.edges.append(Edge(new_node_tuple, n2)) # 清理不活跃边 mesh.edges = [e for e in mesh.edges if e.active] def plot_mesh(mesh): plt.figure(figsize=(8, 8)) # 收集所有唯一边 edges = set() for tri in mesh.triangles: nodes = tri.nodes for i in range(3): edge = tuple(sorted([nodes[i], nodes[(i+1)%3]])) edges.add(edge) # 绘制边 for edge in edges: x = [edge[0][0], edge[1][0]] y = [edge[0][1], edge[1][1]] plt.plot(x, y, 'b-', linewidth=0.5) # 绘制节点 nodes = np.array(mesh.nodes) plt.scatter(nodes[:,0], nodes[:,1], c='r', s=10) plt.axis('equal') plt.title(f"生成网格:共{len(mesh.nodes)}个节点,{len(mesh.triangles)}个三角形") plt.show() # 定义初始边界(顺针矩形) boundary = [(0,0), (1,0), (1,1), (0,1)] # 创建网格并初始化 mesh = Mesh() initialize_front(mesh, boundary) # 执行推进波前算法 advancing_front(mesh, target_length=0.15) # 可视化结果 plot_mesh(mesh)
03-27
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
import numpy as np import matplotlib.pyplot as plt import networkx as nx from matplotlib.patches import Circle from scipy.spatial.distance import cdist from scipy.sparse import lil_matrix from scipy.interpolate import CubicSpline from scipy.stats import gaussian_kde from sklearn.cluster import KMeans from scipy.spatial import KDTree # 设置随机种子 np.random.seed(42) # 参数设置 - 面积和点数比例5:3:2 total_points = 5000 red_ratio, black_ratio, blue_ratio = 5, 3, 2 total_ratio = red_ratio + black_ratio + blue_ratio red_points = int(total_points * red_ratio / total_ratio) black_points = int(total_points * black_ratio / total_ratio) blue_points = int(total_points * blue_ratio / total_ratio) # 创建平滑自然的边界曲线(避免S形) def create_smooth_boundary(start, end, num_points=100, wave_count=2): """创建自然平滑的边界曲线""" # 使用更少的关键点创建更自然的曲线 key_x = np.linspace(0, 1, 5) key_y = np.linspace(start, end, 5) # 添加随机扰动但保持平滑 key_y[1:-1] += np.random.uniform(-0.1, 0.1, 3) # 使用三次样条插值创建平滑曲线 cs = CubicSpline(key_x, key_y) x = np.linspace(0, 1, num_points) return cs(x) # 创建两条平滑边界曲线 boundary1 = create_smooth_boundary(0.2, 0.8) # 红黑边界 boundary2 = create_smooth_boundary(0.4, 0.6) # 黑蓝边界 # 确保边界顺序正确且间距合理 for i in range(len(boundary1)): # 确保边界1 < 边界2 if boundary1[i] > boundary2[i]: boundary1[i], boundary2[i] = boundary2[i], boundary1[i] # 确保有足够的空间容纳中间区域 min_gap = 0.15 if boundary2[i] - boundary1[i] < min_gap: # 同调整两条边界保持比例 adjustment = (min_gap - (boundary2[i] - boundary1[i])) / 2 boundary1[i] -= adjustment boundary2[i] += adjustment # 创建存储数据的列表 x_coords = [] y_coords = [] colors = [] # 计算区域面积以验证比例 def calculate_area(boundary_low, boundary_high): """计算两条边界之间的面积""" area = np.trapz(boundary_high - boundary_low, dx=1 / len(boundary_low)) return area # 计算各区域面积 red_area = calculate_area(np.zeros_like(boundary1), boundary1) black_area = calculate_area(boundary1, boundary2) blue_area = calculate_area(boundary2, np.ones_like(boundary2)) total_area = red_area + black_area + blue_area # 调整边界以确保面积比例严格为5:3:2 target_red = 0.5 * total_area target_black = 0.3 * total_area target_blue = 0.2 * total_area # 缩放边界以满足面积比例 scale_red = np.sqrt(target_red / red_area) # 面积缩放因子 scale_blue = np.sqrt(target_blue / blue_area) # 应用缩放 boundary1 *= scale_red boundary2 = 1 - (1 - boundary2) * scale_blue # 重新计算面积 red_area = calculate_area(np.zeros_like(boundary1), boundary1) black_area = calculate_area(boundary1, boundary2) blue_area = calculate_area(boundary2, np.ones_like(boundary2)) total_area = red_area + black_area + blue_area # 生成点分布(使用核密度估计使分布更自然) def generate_points_with_kde(num_points, x_range, y_range, kde_points=None): """使用KDE生成自然分布的点""" if kde_points is None or len(kde_points) < 2: # 如果没有参考点,生成均匀分布 x = np.random.uniform(x_range[0], x_range[1], num_points) y = np.random.uniform(y_range[0], y_range[1], num_points) return x, y # 使用KDE基于参考点生成新点 kde = gaussian_kde(kde_points.T) return kde.resample(num_points) # 生成红色点 (下方区域) x_red, y_red = [], [] for _ in range(red_points): x = np.random.uniform(0.01, 0.99) idx = min(int(x * 99), 98) max_y = boundary1[idx] * 0.55 # 留出边界缓冲区 # 使用KDE创建自然聚集效果 if len(x_red) > 50: new_x, new_y = generate_points_with_kde( 1, (0, 1), (0, max_y), np.column_stack([x_red, y_red])) x = new_x[0] y = new_y[0] else: y = np.random.uniform(0.01, max_y) x_red.append(x) y_red.append(y) colors.append('red') # 生成黑色点 (中间区域) x_black, y_black = [], [] for _ in range(black_points): x = np.random.uniform(0.01, 0.99) idx = min(int(x * 99), 98) min_y = boundary1[idx] * 0.85 max_y = boundary2[idx] * 0.85 # 确保区域有效 if max_y > min_y: # 使用KDE创建过渡效果 if len(x_black) > 50: new_x, new_y = generate_points_with_kde( 1, (0, 1), (min_y, max_y), np.column_stack([x_black, y_black])) x = new_x[0] y = new_y[0] else: y = np.random.uniform(min_y, max_y) x_black.append(x) y_black.append(y) colors.append('black') # 生成蓝色点 (上方区域) x_blue, y_blue = [], [] for _ in range(blue_points): x = np.random.uniform(0.01, 0.99) idx = min(int(x * 99), 98) min_y = boundary2[idx] * 1.10 # 使用KDE创建自然分布 if len(x_blue) > 50: new_x, new_y = generate_points_with_kde( 1, (0, 1), (min_y, 0.99), np.column_stack([x_blue, y_blue])) x = new_x[0] y = new_y[0] else: y = np.random.uniform(min_y, 0.99) x_blue.append(x) y_blue.append(y) colors.append('blue') # 合并所有点 all_x = np.array(x_red + x_black + x_blue) all_y = np.array(y_red + y_black + y_blue) colors = np.array(colors) # 粒球图类(优化版) class GranularBallGraph: def __init__(self, points, color, n_clusters=10, overlap_threshold=0.1, max_radius_ratio=0.05): """ 初始化粒球图 参数: points (np.ndarray): 输入点云数据,形状为 (n_points, 2) color (str): 粒球的颜色 n_clusters (int): 粒球数量 overlap_threshold (float): 粒球重叠判断阈值(半径比例) max_radius_ratio (float): 最大半径限制(相对于数据范围的比例) """ self.points = points self.color = color self.n_clusters = n_clusters self.overlap_threshold = overlap_threshold self.max_radius_ratio = max_radius_ratio self.balls = [] self.graph = None # 计算数据范围用于半径限制 self.x_range = np.ptp(points[:, 0]) self.y_range = np.ptp(points[:, 1]) self.max_radius = max(self.x_range, self.y_range) * max_radius_ratio def generate_granular_balls(self): """使用KMeans聚类生成粒球,确保粒球只包含同色点""" if len(self.points) < self.n_clusters: self.n_clusters = max(1, len(self.points) // 5) if len(self.points) > 0: # 使用KMeans聚类 kmeans = KMeans(n_clusters=self.n_clusters, random_state=42, n_init=10) labels = kmeans.fit_predict(self.points) self.balls = [] for i in range(self.n_clusters): cluster_points = self.points[labels == i] if len(cluster_points) > 0: center = kmeans.cluster_centers_[i] # 计算到中心的最大距离作为半径 distances = np.linalg.norm(cluster_points - center, axis=1) radius = min(np.max(distances), self.max_radius) if len(distances) > 0 else 0.01 # 确保半径合理 if radius < 0.001: radius = min(0.01, self.max_radius) self.balls.append((center, radius, cluster_points)) def build_graph(self): """构建粒球图结构""" if not self.balls: print(f"错误: 未生成粒球,请先调用 generate_granular_balls() - {self.color}") return n_balls = len(self.balls) adj_matrix = lil_matrix((n_balls, n_balls), dtype=int) centers = np.array([ball[0] for ball in self.balls]) radii = np.array([ball[1] for ball in self.balls]) # 计算粒球中心间的距离 dist_matrix = cdist(centers, centers) # 判断粒球是否相交 for i in range(n_balls): for j in range(i + 1, n_balls): # 计算重叠比例 distance = dist_matrix[i, j] min_radius = min(radii[i], radii[j]) if min_radius > 0: # 防止除以零 overlap_ratio = (radii[i] + radii[j] - distance) / min_radius if overlap_ratio > self.overlap_threshold: adj_matrix[i, j] = 1 adj_matrix[j, i] = 1 # 创建NetworkX图 self.graph = nx.Graph() for i in range(n_balls): self.graph.add_node(i, center=centers[i], radius=radii[i]) for i in range(n_balls): for j in adj_matrix.rows[i]: if i < j: # 避免重复添加边 self.graph.add_edge(i, j) # 分别处理三种颜色的点云 red_points = np.column_stack([all_x[colors == 'red'], all_y[colors == 'red']]) black_points = np.column_stack([all_x[colors == 'black'], all_y[colors == 'black']]) blue_points = np.column_stack([all_x[colors == 'blue'], all_y[colors == 'blue']]) # 创建并构建粒球图(使用KMeans聚类) print("创建红色粒球图...") gbg_red = GranularBallGraph(red_points, color='red', n_clusters=50, max_radius_ratio=0.03) gbg_red.generate_granular_balls() gbg_red.build_graph() print("创建黑色粒球图...") gbg_black = GranularBallGraph(black_points, color='black', n_clusters=30, max_radius_ratio=0.03) gbg_black.generate_granular_balls() gbg_black.build_graph() print("创建蓝色粒球图...") gbg_blue = GranularBallGraph(blue_points, color='blue', n_clusters=20, max_radius_ratio=0.03) gbg_blue.generate_granular_balls() gbg_blue.build_graph() # 可视化所有粒球图 plt.figure(figsize=(15, 15)) # 绘制原始点云(半透明) plt.scatter(all_x, all_y, c=colors, alpha=0.2, s=5, edgecolors='none') # 定义颜色映射 color_map = { 'red': ('red', 'darkred', '#ff9999'), 'black': ('black', 'dimgray', '#999999'), 'blue': ('blue', 'darkblue', '#9999ff') } # 绘制红色粒球(只包含同色点) for i, (center, radius, cluster_points) in enumerate(gbg_red.balls): # 绘制粒球边界 circle = Circle(center, radius, fill=False, edgecolor=color_map['red'][1], alpha=0.7, linestyle='-', linewidth=1.5) plt.gca().add_patch(circle) # 绘制粒球内的点(可选) # plt.scatter(cluster_points[:, 0], cluster_points[:, 1], # color=color_map['red'][0], alpha=0.5, s=2) # 绘制黑色粒球 for i, (center, radius, cluster_points) in enumerate(gbg_black.balls): circle = Circle(center, radius, fill=False, edgecolor=color_map['black'][1], alpha=0.7, linestyle='-', linewidth=1.5) plt.gca().add_patch(circle) # 绘制蓝色粒球 for i, (center, radius, cluster_points) in enumerate(gbg_blue.balls): circle = Circle(center, radius, fill=False, edgecolor=color_map['blue'][1], alpha=0.7, linestyle='-', linewidth=1.5) plt.gca().add_patch(circle) # 绘制图结构(节点和边) def draw_graph(gbg, color_map): if gbg.graph: pos = {i: ball[0] for i, ball in enumerate(gbg.balls)} # 绘制节点(使用更小的点) nx.draw_networkx_nodes(gbg.graph, pos, node_size=20, node_color=color_map[gbg.color][2], alpha=0.8, edgecolors=color_map[gbg.color][1]) # 绘制边(更细的线) nx.draw_networkx_edges(gbg.graph, pos, edge_color=color_map[gbg.color][1], width=0.8, alpha=0.4) # 绘制三种颜色的图结构 draw_graph(gbg_red, color_map) draw_graph(gbg_black, color_map) draw_graph(gbg_blue, color_map) # 添加图例 from matplotlib.lines import Line2D legend_elements = [ Line2D([0], [0], marker='o', color='w', markerfacecolor='red', markersize=10, label='红色点云'), Line2D([0], [0], marker='o', color='w', markerfacecolor='black', markersize=10, label='黑色点云'), Line2D([0], [0], marker='o', color='w', markerfacecolor='blue', markersize=10, label='蓝色点云'), Line2D([0], [0], color=color_map['red'][1], lw=2, label='红色粒球边界'), Line2D([0], [0], color=color_map['black'][1], lw=2, label='黑色粒球边界'), Line2D([0], [0], color=color_map['blue'][1], lw=2, label='蓝色粒球边界') ] plt.legend(handles=legend_elements, loc='upper right', fontsize=12) plt.title('多色点云的粒球图(优化版)', fontsize=16) plt.xlabel('X') plt.ylabel('Y') plt.grid(alpha=0.1) plt.axis('equal') plt.tight_layout() # 保存图像 plt.savefig('granular_ball_graph_optimized.png', dpi=300, bbox_inches='tight') plt.show() # 打印粒球图信息 print(f"红色粒球图: {len(gbg_red.balls)} 个粒球, {gbg_red.graph.number_of_edges()} 条边") print(f"黑色粒球图: {len(gbg_black.balls)} 个粒球, {gbg_black.graph.number_of_edges()} 条边") print(f"蓝色粒球图: {len(gbg_blue.balls)} 个粒球, {gbg_blue.graph.number_of_edges()} 条边") # 检查粒球大小 def analyze_balls(gbg, color): radii = [ball[1] for ball in gbg.balls] avg_radius = np.mean(radii) max_radius = np.max(radii) min_radius = np.min(radii) print(f"\n{color}粒球大小分析:") print(f" 平均半径: {avg_radius:.4f}") print(f" 最大半径: {max_radius:.4f}") print(f" 最小半径: {min_radius:.4f}") print(f" 粒球数量: {len(radii)}") return radii # 分析各颜色粒球大小 red_radii = analyze_balls(gbg_red, "红色") black_radii = analyze_balls(gbg_black, "黑色") blue_radii = analyze_balls(gbg_blue, "蓝色") # 绘制粒球大小分布 plt.figure(figsize=(10, 6)) plt.hist(red_radii, bins=20, alpha=0.5, color='red', label='红色粒球') plt.hist(black_radii, bins=20, alpha=0.5, color='black', label='黑色粒球') plt.hist(blue_radii, bins=20, alpha=0.5, color='blue', label='蓝色粒球') plt.title('粒球半径分布') plt.xlabel('半径') plt.ylabel('数量') plt.legend() plt.grid(alpha=0.2) plt.tight_layout() plt.savefig('granular_ball_radii_distribution.png', dpi=200) plt.show()“红色部分的粒球做的很好,请将其他颜色的粒球也像红色部分一样,保证粒球间的连通”
07-23
``` def extract_boundary_with_integral_invariant(pcd, radius=0.1, k=2.0, min_neighbors=5): """ 使用积分不变量算法提取点云边界 参数: pcd (o3d.geometry.PointCloud): 输入点云 radius (float): 邻域搜索半径 k (float): 阈值系数 (T = μ - k*σ) min_neighbors (int): 后处理中保留边界点所需的最小邻域边界点数量 返回: boundary_mask (np.array): 边界点布尔掩码 """ # ---------------------- 1. 预处理 ---------------------- # 统计滤波去噪 cl, _ = pcd.remove_statistical_outlier(nb_neighbors=10, std_ratio=5.0) # ---------------------- 2. 法线估计 ---------------------- cl.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=radius * 2, max_nn=30)) cl.orient_normals_consistent_tangent_plane(k=15) # 统一法线方向 # ---------------------- 3. 构建KD-tree加速搜索 ---------------------- points = np.asarray(cl.points) normals = np.asarray(cl.normals) tree = KDTree(points) # ---------------------- 4. 计算积分不变量 ---------------------- integral_values = [] for i in range(len(points)): # 查询球形邻域 neighbors = tree.query_ball_point(points[i], radius) if len(neighbors) < 3: integral_values.append(0) continue # 获取邻域点相对坐标 neighbor_points = points[neighbors] - points[i] # 构建局部坐标系 (z轴为法线方向) z_axis = normals[i] x_axis = np.random.randn(3) # 随机初始向量 x_axis -= x_axis.dot(z_axis) * z_axis x_axis /= np.linalg.norm(x_axis) y_axis = np.cross(z_axis, x_axis) # 投影到切平面 (忽略法线方向) proj_coords = np.array([neighbor_points.dot(x_axis), neighbor_points.dot(y_axis)]).T # 统计有效投影点数量作为积分值 integral_values.append(len(proj_coords)) # ---------------------- 5. 阈值处理 ---------------------- integrals = np.array(integral_values) mean = integrals.mean() std = integrals.std() threshold = mean - k * std boundary_mask = (integrals < threshold) # ---------------------- 6. 后处理:去除孤立边界点 ---------------------- boundary_indices = np.where(boundary_mask)[0] boundary_tree = KDTree(points[boundary_mask]) valid_boundary = np.zeros(len(points), dtype=bool) for idx in boundary_indices: # 检查周围radius内是否有足够多的边界点 count = boundary_tree.query_ball_point(points[idx], radius).__len__() if count >= min_neighbors: valid_boundary[idx] = True return valid_boundary # ====================== 使用示例 ====================== if __name__ == "__main__": # 1. 读取点云 (替换为你的PCD文件路径) pcd = o3d.io.read_point_cloud("model.pcd") # 检查点云是否为空 if len(pcd.points) == 0: print("读取的点云文件为空,请检查文件路径和内容。") else: print("成功读取点云,点数:", len(pcd.points)) # 2. 提取边界 boundary_mask = extract_boundary_with_integral_invariant(pcd, radius=2.0, k=1.5, min_neighbors=3) # 检查是否有边界点 if np.any(boundary_mask): # 3. 可视化结果 boundary_pcd = pcd.select_by_index(np.where(boundary_mask)[0]) boundary_pcd.paint_uniform_color([1, 0, 0]) # 红色为边界 o3d.visualization.draw_geometries([pcd, boundary_pcd]) else: print("未找到边界点,请调整参数重新尝试。")```能否将其中的边界点单独提取出来并展示全部代码
03-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值