SelectBox in Frame(转)

首先,建立Frameset帧结构,为每个frame页面起名(红色部分):




以上请自己调整适应自己的页面。

下面是左侧的页面nav.htm的代码:




parent.right.location.href = document.selectform.select.options[indexbox].value;
}
//--&gt



selectbox(this.selectedIndex);" size="6" name="select">连接页面1连接页面2连接页面3连接页面4连接页面5连接页面6 //一共6个连接页面,与上面的数字一致。


右侧的页面随便。注意:颜色相同部分要一致。right是左侧的frame的名字啊。

如果不需要在FRAME中打开页面,用location替换parent.right.location.href 即可在全页面打开连接,当然不必再建立FRAMESET了.

[@more@]

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/9650775/viewspace-922736/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/9650775/viewspace-922736/

import tkinter as tk import ttkbootstrap as ttk from ttkbootstrap.constants import * from ttkbootstrap.dialogs import Dialog, Messagebox import json import os import threading import time from tkinter import filedialog class DeviceManagerApp: def __init__(self): self.root = ttk.Window(themename="minty") self.root.title("音频设备刷机工具") self.root.geometry("1000x700") self.center_window(self.root) # 初始化状态变量 self.connected_device = None self.device_categories = [] self.device_list = {} self.load_device_config() # 创建主界面 self.create_main_interface() self.root.mainloop() def center_window(self, window): """居中显示窗口""" window.update_idletasks() width = window.winfo_width() height = window.winfo_height() x = (window.winfo_screenwidth() // 2) - (width // 2) y = (window.winfo_screenheight() // 2) - (height // 2) window.geometry(f'{width}x{height}+{x}+{y}') def load_device_config(self): """加载设备配置文件""" try: # 模拟读取devices.json self.device_list = { "pro系列": { "pro1": {"产品名称": "专业版1代"}, "pro2": {"产品名称": "专业版2代"}, "pro3": {"产品名称": "专业版3代"}, }, "数字系列": { "digi1": {"产品名称": "数字旗舰1代"}, "digi2": {"产品名称": "数字旗舰2代"}, }, "眼镜系列": { "glass1": {"产品名称": "智能眼镜1代"}, "glass2": {"产品名称": "智能眼镜2代"}, } } self.device_categories = list(self.device_list.keys()) except: self.device_categories = ["pro系列", "数字系列", "眼镜系列"] self.device_list = {cat: {} for cat in self.device_categories} def create_main_interface(self): """创建主界面布局""" # 主框架 main_frame = ttk.Frame(self.root) main_frame.pack(fill=BOTH, expand=True, padx=15, pady=15) # 左右分栏 left_panel = ttk.LabelFrame(main_frame, text="设备管理", padding=10) left_panel.pack(side=LEFT, fill=Y, padx=(0, 10)) right_panel = ttk.Frame(main_frame) right_panel.pack(side=RIGHT, fill=BOTH, expand=True) # ======= 左侧面板 ======= # 上部分 - 串口选择 serial_frame = ttk.Frame(left_panel) serial_frame.pack(fill=X, pady=(0, 10)) ttk.Label(serial_frame, text="串口选择:").pack(side=LEFT) self.serial_combobox = ttk.Combobox(serial_frame, width=15) self.serial_combobox.pack(side=LEFT, padx=5) self.serial_combobox['values'] = ['COM1', 'COM2', 'COM3', 'COM4'] self.serial_combobox.current(0) ttk.Button( serial_frame, text="刷新", width=6, command=self.refresh_serial_ports, bootstyle=OUTLINE ).pack(side=LEFT, padx=5) # 中部分 - 设备信息 device_info_frame = ttk.LabelFrame(left_panel, text="设备信息", padding=10) device_info_frame.pack(fill=X, pady=(0, 10)) ttk.Label(device_info_frame, text="设备型号:").grid(row=0, column=0, sticky=W) self.device_name_var = tk.StringVar(value="未选择设备") ttk.Label(device_info_frame, textvariable=self.device_name_var).grid(row=0, column=1, sticky=W) ttk.Label(device_info_frame, text="设备名称:").grid(row=1, column=0, sticky=W) self.product_name_var = tk.StringVar(value="未选择设备") ttk.Label(device_info_frame, textvariable=self.product_name_var).grid(row=0, column=1, sticky=W) ttk.Label(device_info_frame, text="连接状态:").grid(row=2, column=0, sticky=W, pady=(5, 0)) self.device_status_var = tk.StringVar(value="设备未连接") self.status_label = ttk.Label( device_info_frame, textvariable=self.device_status_var, bootstyle=DANGER ) self.status_label.grid(row=2, column=1, sticky=W, pady=(5, 0)) # 下部分 - 搜索按钮 search_btn_frame = ttk.Frame(left_panel) search_btn_frame.pack(fill=X, pady=(10, 0)) ttk.Button( search_btn_frame, text="搜索设备", width=15, command=self.show_device_search, bootstyle=INFO ).pack(fill=X) # ======= 右侧面板 ======= # 第一部分 - 设备信息 version_table_frame = ttk.LabelFrame(right_panel, text="设备信息", padding=10) version_table_frame.pack(fill=X, pady=(0, 15)) # 创建表格 columns = ("device", "sn", "internal", "external", "mcu") # 创建自定义样式 style = ttk.Style() style.configure("Custom.Treeview", rowheight=40) # 设置行高为40像素 self.device_table = ttk.Treeview( version_table_frame, style="Custom.Treeview", columns=columns, show="headings", height=4, bootstyle=PRIMARY ) # 设置列 self.device_table.heading("device", text="设备对象") self.device_table.heading("sn", text="SN") self.device_table.heading("internal", text="内部版本") self.device_table.heading("external", text="外部版本") self.device_table.heading("mcu", text="MCU版本") # 设置列宽 self.device_table.column("device", width=100, anchor=CENTER) self.device_table.column("sn", width=150, anchor=CENTER) self.device_table.column("internal", width=100, anchor=CENTER) self.device_table.column("external", width=100, anchor=CENTER) self.device_table.column("mcu", width=100, anchor=CENTER) # 添加数据 self.device_table.insert("", "end", values=("充电盒", "", "", "", ""), tags=("disabled",)) self.device_table.insert("", "end", values=("左耳", "", "", "", ""), tags=("disabled",)) self.device_table.insert("", "end", values=("右耳", "", "", "", ""), tags=("disabled",)) self.device_table.tag_configure("disabled", foreground="gray") self.device_table.pack(fill=X) # 第二部分 - 功能标签页 self.notebook = ttk.Notebook(right_panel) self.notebook.pack(fill=BOTH, expand=True) # 创建三个功能页 self.create_flash_page() self.create_export_page() self.create_command_page() # 初始状态禁用功能页 self.disable_function_pages() def create_flash_page(self): """创建设备刷机页面""" self.flash_frame = ttk.Frame(self.notebook) self.notebook.add(self.flash_frame, text="设备刷机") # 第一部分 - 刷机设置 setting_frame = ttk.LabelFrame(self.flash_frame, text="刷机设置", padding=10) setting_frame.pack(fill=X, pady=(0, 10)) # 设备版本选择 device_version_frame = ttk.Frame(setting_frame) device_version_frame.pack(fill=X, pady=(0, 5)) ttk.Label(device_version_frame, text="设备版本:").pack(side=LEFT) self.version_var = tk.StringVar() self.version_combobox = ttk.Combobox(device_version_frame, textvariable=self.version_var, width=25) self.version_combobox.pack(side=LEFT, padx=5) # 机盒版本文件 box_file_frame = ttk.Frame(setting_frame) box_file_frame.pack(fill=X, pady=5) ttk.Label(box_file_frame, text="机盒版本文件:").pack(side=LEFT) self.box_file_var = tk.StringVar() ttk.Entry(box_file_frame, textvariable=self.box_file_var, width=30).pack(side=LEFT, padx=5) ttk.Button( box_file_frame, text="浏览", command=lambda: self.browse_file("box"), width=6 ).pack(side=LEFT) # 耳机版本文件 ear_file_frame = ttk.Frame(setting_frame) ear_file_frame.pack(fill=X, pady=5) ttk.Label(ear_file_frame, text="耳机版本文件:").pack(side=LEFT) self.ear_file_var = tk.StringVar() ttk.Entry(ear_file_frame, textvariable=self.ear_file_var, width=30).pack(side=LEFT, padx=5) ttk.Button( ear_file_frame, text="浏览", command=lambda: self.browse_file("ear"), width=6 ).pack(side=LEFT) # 设备选择和刷机按钮 device_select_frame = ttk.Frame(setting_frame) device_select_frame.pack(fill=X, pady=(10, 0)) self.box_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_select_frame, text="充电盒", variable=self.box_var).pack(side=LEFT, padx=5) self.left_ear_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_select_frame, text="左耳", variable=self.left_ear_var).pack(side=LEFT, padx=5) self.right_ear_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_select_frame, text="右耳", variable=self.right_ear_var).pack(side=LEFT, padx=5) ttk.Button( device_select_frame, text="开始刷机", bootstyle=SUCCESS, command=self.start_flashing ).pack(side=RIGHT) # 第二部分 - 刷机进度条 progress_frame = ttk.LabelFrame(self.flash_frame, text="刷机进度", padding=10) progress_frame.pack(fill=X, pady=(10, 0)) self.flash_progress = ttk.Progressbar( progress_frame, orient=HORIZONTAL, length=100, mode='determinate', bootstyle=SUCCESS ) self.flash_progress.pack(fill=X) self.progress_var = tk.StringVar(value="0%") ttk.Label(progress_frame, textvariable=self.progress_var, bootstyle=INFO).pack(anchor=E) # 第三部分 - 刷机日志 log_frame = ttk.LabelFrame(self.flash_frame, text="刷机日志", padding=10) log_frame.pack(fill=BOTH, expand=True, pady=(10, 0)) self.flash_log = tk.Text(log_frame, height=8) self.flash_log.pack(fill=BOTH, expand=True, side=LEFT) scrollbar = ttk.Scrollbar(log_frame, command=self.flash_log.yview) scrollbar.pack(fill=Y, side=RIGHT) self.flash_log.config(yscrollcommand=scrollbar.set) btn_frame = ttk.Frame(log_frame) btn_frame.pack(fill=Y, side=RIGHT, padx=(5, 0)) ttk.Button( btn_frame, text="清除", width=6, command=lambda: self.flash_log.delete(1.0, END) ).pack(pady=2) def create_export_page(self): """创建日志导出页面""" self.export_frame = ttk.Frame(self.notebook) self.notebook.add(self.export_frame, text="日志导出") # 第一部分 - 设备选择 select_frame = ttk.LabelFrame(self.export_frame, text="选择设备", padding=10) select_frame.pack(fill=X, pady=(0, 10)) device_frame = ttk.Frame(select_frame) device_frame.pack(fill=X) self.export_box_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_frame, text="充电盒", variable=self.export_box_var).pack(side=LEFT, padx=5) self.export_left_ear_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_frame, text="左耳", variable=self.export_left_ear_var).pack(side=LEFT, padx=5) self.export_right_ear_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_frame, text="右耳", variable=self.export_right_ear_var).pack(side=LEFT, padx=5) ttk.Button( device_frame, text="开始导出", bootstyle=SUCCESS, command=self.start_export ).pack(side=RIGHT) # 第二部分 - 导出进度 progress_frame = ttk.LabelFrame(self.export_frame, text="导出进度", padding=10) progress_frame.pack(fill=X, pady=(10, 0)) self.export_progress = ttk.Progressbar( progress_frame, orient=HORIZONTAL, length=100, mode='determinate', bootstyle=INFO ) self.export_progress.pack(fill=X) self.export_progress_var = tk.StringVar(value="0%") ttk.Label(progress_frame, textvariable=self.export_progress_var, bootstyle=INFO).pack(anchor=E) # 第三部分 - 导出日志 log_frame = ttk.LabelFrame(self.export_frame, text="导出日志", padding=10) log_frame.pack(fill=BOTH, expand=True, pady=(10, 0)) self.export_log = tk.Text(log_frame, height=8) self.export_log.pack(fill=BOTH, expand=True, side=LEFT) scrollbar = ttk.Scrollbar(log_frame, command=self.export_log.yview) scrollbar.pack(fill=Y, side=RIGHT) self.export_log.config(yscrollcommand=scrollbar.set) btn_frame = ttk.Frame(log_frame) btn_frame.pack(fill=Y, side=RIGHT, padx=(5, 0)) ttk.Button( btn_frame, text="清除", width=6, command=lambda: self.export_log.delete(1.0, END) ).pack(pady=2) def create_command_page(self): """创建发送指令页面""" self.command_frame = ttk.Frame(self.notebook) self.notebook.add(self.command_frame, text="发送指令") # 指令日志 log_frame = ttk.LabelFrame(self.command_frame, text="指令响应", padding=10) log_frame.pack(fill=BOTH, expand=True, pady=(0, 10)) self.command_log = tk.Text(log_frame, height=15) self.command_log.pack(fill=BOTH, expand=True, side=LEFT) scrollbar = ttk.Scrollbar(log_frame, command=self.command_log.yview) scrollbar.pack(fill=Y, side=RIGHT) self.command_log.config(yscrollcommand=scrollbar.set) # 指令输入区域 input_frame = ttk.Frame(self.command_frame) input_frame.pack(fill=X, pady=10) # 指令选择 ttk.Label(input_frame, text="指令选择:").pack(side=LEFT, padx=(0, 5)) self.cmd_var = tk.StringVar() self.cmd_combobox = ttk.Combobox( input_frame, textvariable=self.cmd_var, width=25, state="readonly" ) self.cmd_combobox.pack(side=LEFT, padx=5) self.cmd_combobox['values'] = [ "获取版本信息", "重启设备", "恢复出厂设置", "进入升级模式", "读取日志", "自定义指令" ] self.cmd_combobox.current(0) # 参数输入 ttk.Label(input_frame, text="参数:").pack(side=LEFT, padx=(10, 5)) self.param_var = tk.StringVar() ttk.Entry(input_frame, textvariable=self.param_var, width=15).pack(side=LEFT) # 设备选择 device_frame = ttk.Frame(input_frame) device_frame.pack(side=LEFT, padx=(20, 0)) self.cmd_box_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_frame, text="充电盒", variable=self.cmd_box_var).pack(side=LEFT, padx=5) self.cmd_left_ear_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_frame, text="左耳", variable=self.cmd_left_ear_var).pack(side=LEFT, padx=5) self.cmd_right_ear_var = tk.BooleanVar(value=True) ttk.Checkbutton(device_frame, text="右耳", variable=self.cmd_right_ear_var).pack(side=LEFT, padx=5) # 发送按钮 ttk.Button( input_frame, text="发送指令", bootstyle=INFO, command=self.send_command ).pack(side=RIGHT, padx=(10, 0)) # 指令说明 help_frame = ttk.LabelFrame(self.command_frame, text="指令说明", padding=10) help_frame.pack(fill=X, pady=(0, 10)) help_text = """常用指令说明: 1. 获取版本信息: 查询设备固件版本 2. 重启设备: 重启选中的设备 3. 恢复出厂设置: 清除设备配置 4. 进入升级模式: 准备刷机操作 5. 读取日志: 导出设备运行日志 6. 自定义指令: 输入HEX格式指令(如A5F1)""" help_label = ttk.Label(help_frame, text=help_text, justify=LEFT) help_label.pack(anchor=W) def disable_function_pages(self): """禁用功能页""" self.notebook.tab(0, state="disabled") self.notebook.tab(1, state="disabled") self.notebook.tab(2, state="disabled") def refresh_serial_ports(self): """刷新串口列表""" # 模拟获取串口 ports = ['COM1', 'COM2', 'COM3', 'COM4', 'USB1'] self.serial_combobox['values'] = ports self.serial_combobox.current(0) self.add_log("已刷新串口列表") def show_device_search(self): """显示设备搜索对话框""" dialog = DeviceSearchDialog(self.root, self.device_categories, self.device_list) if dialog.result: category, device = dialog.result self.connected_device = device self.device_name_var.set(self.device_list[category][device]["产品名称"]) self.product_model_var.set(device) self.product_name_var.set(self.device_list[category][device]["产品名称"]) # 更新状态 self.device_status_var.set("设备已连接") self.status_label.config(bootstyle=SUCCESS) # 启用功能页 self.notebook.tab(0, state="normal") self.notebook.tab(1, state="normal") self.notebook.tab(2, state="normal") # 更新表格数据(模拟) self.update_device_table() def update_device_table(self): """更新设备版本表格(模拟)""" self.device_table.delete(*self.device_table.get_children()) self.device_table.insert("", "end", values=("充电盒", "SN202405001", "V1.2.3", "V1.0", "V0.8"), tags=("enabled",)) self.device_table.insert("", "end", values=("左耳", "SN202405002", "V1.2.1", "V1.0", "V0.7"), tags=("enabled",)) self.device_table.insert("", "end", values=("右耳", "SN202405003", "V1.2.1", "V1.0", "V0.7"), tags=("enabled",)) self.device_table.tag_configure("enabled", foreground="black") def browse_file(self, file_type): """浏览文件""" file_path = filedialog.askopenfilename( title=f"选择{file_type}文件", filetypes=[("固件文件", "*.bin *.hex"), ("所有文件", "*.*")] ) if file_path: if file_type == "box": self.box_file_var.set(file_path) else: self.ear_file_var.set(file_path) def start_flashing(self): """开始刷机""" # 验证文件 if not self.box_file_var.get() and not self.ear_file_var.get(): Messagebox.show_warning("请至少选择一个固件文件", "刷机错误") return # 验证设备选择 if not (self.box_var.get() or self.left_ear_var.get() or self.right_ear_var.get()): Messagebox.show_warning("请至少选择一个设备", "刷机错误") return # 模拟刷机过程 self.flash_log.delete(1.0, END) self.add_flash_log("开始刷机流程...") self.add_flash_log(f"使用串口: {self.serial_combobox.get()}") # 启动进度条更新 threading.Thread(target=self.simulate_flash_progress).start() def simulate_flash_progress(self): """模拟刷机进度""" for i in range(1, 101): time.sleep(0.05) self.flash_progress['value'] = i self.progress_var.set(f"{i}%") # 模拟日志输出 if i == 20: self.add_flash_log("设备连接成功") elif i == 40: self.add_flash_log("开始写入充电盒固件...") elif i == 60: self.add_flash_log("充电盒固件写入完成") elif i == 70: self.add_flash_log("开始写入耳机固件...") elif i == 90: self.add_flash_log("耳机固件写入完成") elif i == 100: self.add_flash_log("刷机完成!设备即将重启") def add_flash_log(self, message): """添加刷机日志""" timestamp = time.strftime("%H:%M:%S") self.flash_log.insert(END, f"[{timestamp}] {message}\n") self.flash_log.see(END) def start_export(self): """开始导出日志""" # 验证设备选择 if not (self.export_box_var.get() or self.export_left_ear_var.get() or self.export_right_ear_var.get()): Messagebox.show_warning("请至少选择一个设备", "导出错误") return # 模拟导出过程 self.export_log.delete(1.0, END) self.add_export_log("开始日志导出...") # 启动进度条更新 threading.Thread(target=self.simulate_export_progress).start() def simulate_export_progress(self): """模拟导出进度""" for i in range(1, 101): time.sleep(0.03) self.export_progress['value'] = i self.export_progress_var.set(f"{i}%") # 模拟日志输出 if i == 30: self.add_export_log("正在读取充电盒日志...") elif i == 60: self.add_export_log("正在读取耳机日志...") elif i == 90: self.add_export_log("日志数据整合完成") elif i == 100: self.add_export_log("导出完成!已保存到log_export.txt") def add_export_log(self, message): """添加导出日志""" timestamp = time.strftime("%H:%M:%S") self.export_log.insert(END, f"[{timestamp}] {message}\n") self.export_log.see(END) def send_command(self): """发送指令""" command = self.cmd_var.get() param = self.param_var.get() if not command: Messagebox.show_warning("请选择指令", "指令错误") return # 获取选择的设备 devices = [] if self.cmd_box_var.get(): devices.append("充电盒") if self.cmd_left_ear_var.get(): devices.append("左耳") if self.cmd_right_ear_var.get(): devices.append("右耳") if not devices: Messagebox.show_warning("请至少选择一个设备", "指令错误") return # 添加指令日志 timestamp = time.strftime("%H:%M:%S") self.command_log.insert(END, f"[{timestamp}] 发送指令: {command}") if param: self.command_log.insert(END, f" 参数: {param}") self.command_log.insert(END, f" 目标设备: {', '.join(devices)}\n") # 模拟响应 time.sleep(0.5) self.command_log.insert(END, f"[{timestamp}] 响应: 指令执行成功\n") self.command_log.see(END) def add_log(self, message): """添加日志(通用)""" # 这里可以扩展为添加到全局日志区域 print(f"LOG: {message}") class DeviceSearchDialog(Dialog): """设备搜索对话框""" def __init__(self, parent, categories, device_list): self.categories = categories self.device_list = device_list self._dialog_result = None # 使用不同的属性名 super().__init__(parent, "设备搜索", alert=True) @property def result(self): """提供对结果的访问接口""" return self._dialog_result def create_body(self, master): frame = ttk.Frame(master, padding=10) # 类别选择 ttk.Label(frame, text="设备类别:").grid(row=0, column=0, sticky=W, padx=5, pady=5) self.category_var = tk.StringVar() self.category_combobox = ttk.Combobox( frame, textvariable=self.category_var, state="readonly" ) self.category_combobox['values'] = self.categories self.category_combobox.current(0) self.category_combobox.grid(row=0, column=1, sticky=EW, padx=5, pady=5) self.category_combobox.bind("<<ComboboxSelected>>", self.update_device_list) # 设备选择 ttk.Label(frame, text="设备型号:").grid(row=1, column=0, sticky=W, padx=5, pady=5) self.device_var = tk.StringVar() self.device_combobox = ttk.Combobox( frame, textvariable=self.device_var, state="readonly" ) self.device_combobox.grid(row=1, column=1, sticky=EW, padx=5, pady=5) # 产品信息 ttk.Label(frame, text="产品名称:").grid(row=2, column=0, sticky=W, padx=5, pady=5) self.product_var = tk.StringVar() ttk.Label(frame, textvariable=self.product_var).grid(row=2, column=1, sticky=W, padx=5, pady=5) # 初始化设备列表 self.update_device_list() frame.pack(fill=BOTH, expand=True) return frame def update_device_list(self, event=None): """更新设备列表""" category = self.category_var.get() or self.categories[0] devices = list(self.device_list[category].keys()) self.device_combobox['values'] = devices if devices: self.device_combobox.current(0) self.update_product_info() def update_product_info(self): """更新产品信息""" category = self.category_var.get() or self.categories[0] device = self.device_var.get() if device and device in self.device_list[category]: self.product_var.set(self.device_list[category][device]["产品名称"]) def create_buttons(self, master): btn_frame = ttk.Frame(master) btn_frame.pack(fill=X, pady=10) ttk.Button( btn_frame, text="连接", bootstyle=SUCCESS, command=self.on_connect, width=8 ).pack(side=RIGHT, padx=5) ttk.Button( btn_frame, text="取消", bootstyle=DANGER, command=self.on_cancel, width=8 ).pack(side=RIGHT) return btn_frame def on_connect(self): category = self.category_var.get() device = self.device_var.get() if category and device: self._dialog_result = (category, device) # 使用新属性名 self._toplevel.destroy() def on_cancel(self): self._dialog_result = None # 使用新属性名 self._toplevel.destroy() if __name__ == "__main__": app = DeviceManagerApp() app.mainloop() 点击搜索设备没有弹出相关界面,仅修改相关代码,把修改后的代码输出出来
12-19
import numpy as np import matplotlib.pyplot as plt from scipy.spatial import cKDTree from tqdm import tqdm import os import sys import time import argparse import multiprocessing from multiprocessing import Pool, Manager, cpu_count, Array, Value, Lock from collections import defaultdict import csv import matplotlib as mpl import warnings import re import traceback import ctypes import psutil from datetime import datetime, timedelta import itertools # 忽略可能的警告 warnings.filterwarnings("ignore", category=UserWarning) # 原子类型映射 (根据Masses部分动态更新) ATOM_TYPE_MAPPING = {} TYPE_SYMBOL_MAPPING = {} # 专业绘图设置 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, }) # 全局共享内存结构 class SharedMemoryManager: def __init__(self, num_frames, num_hbond_types): # 氢键事件存储: [frame][hbond_type] = set of (donor, acceptor) self.hbond_events = [dict() for _ in range(num_frames)] self.lock = Lock() self.num_frames = num_frames self.num_hbond_types = num_hbond_types self.processed_frames = Value('i', 0) self.start_time = Value('d', time.time()) def add_hbond_event(self, frame_idx, hbond_type, donor, acceptor): with self.lock: if hbond_type not in self.hbond_events[frame_idx]: self.hbond_events[frame_idx][hbond_type] = set() self.hbond_events[frame_idx][hbond_type].add((donor, acceptor)) def get_hbond_events(self, frame_idx, hbond_type): with self.lock: return self.hbond_events[frame_idx].get(hbond_type, set()).copy() def increment_processed(self): with self.lock: self.processed_frames.value += 1 def get_progress(self): with self.lock: processed = self.processed_frames.value elapsed = time.time() - self.start_time.value if processed > 0: time_per_frame = elapsed / processed remaining = self.num_frames - processed eta = time_per_frame * remaining else: eta = float('inf') return processed, elapsed, eta def wrap_coordinates(coords, box): """将坐标映射到周期性盒子内 [0, box] 区间""" wrapped_coords = coords.copy() for dim in range(3): if box[dim] > 0: # 将坐标调整到 [0, box[dim]) 区间 wrapped_coords[:, dim] = wrapped_coords[:, dim] % box[dim] return wrapped_coords 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(coords, lattice, donor_idx, h_idx, acceptor_idx): """计算D-H···A键角 (角度制),使用笛卡尔向量表示并处理周期性""" # 获取分数坐标 frac_coords = coords # 获取氢原子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) # H->D向量 vec_ha = np.dot(ah_frac, lattice) # 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 identify_atom_types(atom_types, coords, lattice, box): """原子类型识别函数 - 优化版本""" # 初始化数据结构 atom_categories = { "phosphate_oxygens": {"P-O/P=O": [], "P-OH": []}, "phosphate_hydrogens": [], "water_oxygens": [], "water_hydrogens": [], "hydronium_oxygens": [], "hydronium_hydrogens": [], "fluoride_atoms": np.where(atom_types == "F")[0].tolist(), "aluminum_atoms": np.where(atom_types == "Al")[0].tolist() } # 将坐标映射到盒子内 [0, box] 区间 wrapped_coords = wrap_coordinates(coords, box) # 构建全局KDTree kdtree = cKDTree(wrapped_coords, boxsize=box) # 识别磷酸基团 p_atoms = np.where(atom_types == "P")[0] phosphate_oxygens = [] for p_idx in p_atoms: # 查找P周围的O原子 (距离 < 1.6&Aring;) neighbors = kdtree.query_ball_point(wrapped_coords[p_idx], r=1.6) p_o_indices = [idx for idx in neighbors if idx != p_idx and atom_types[idx] == "O"] phosphate_oxygens.extend(p_o_indices) # 识别所有H原子并确定归属 hydrogen_owners = {} h_atoms = np.where(atom_types == "H")[0] for h_idx in h_atoms: neighbors = kdtree.query_ball_point(wrapped_coords[h_idx], r=1.2) candidate_os = [idx for idx in neighbors if idx != h_idx and atom_types[idx] == "O"] if not candidate_os: continue min_dist = float('inf') owner_o = None for o_idx in candidate_os: # 使用映射后的坐标计算距离 dist = np.linalg.norm(wrapped_coords[h_idx] - wrapped_coords[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_categories["phosphate_oxygens"]["P-OH"].append(o_idx) else: atom_categories["phosphate_oxygens"]["P-O/P=O"].append(o_idx) # 识别水和水合氢离子 all_o_indices = np.where(atom_types == "O")[0] phosphate_oxygens_set = set(phosphate_oxygens) non_phosphate_os = [o_idx for o_idx in all_o_indices if o_idx not in phosphate_oxygens_set] 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_categories["water_oxygens"].append(o_idx) atom_categories["water_hydrogens"].extend(attached_hs) elif h_count == 3: atom_categories["hydronium_oxygens"].append(o_idx) atom_categories["hydronium_hydrogens"].extend(attached_hs) # 识别磷酸基团的H原子 for o_idx in atom_categories["phosphate_oxygens"]["P-OH"]: attached_hs = [h_idx for h_idx, owner_o in hydrogen_owners.items() if owner_o == o_idx] atom_categories["phosphate_hydrogens"].extend(attached_hs) # 换为数组以提高访问速度 for key in atom_categories: if isinstance(atom_categories[key], dict): for subkey in atom_categories[key]: atom_categories[key][subkey] = np.array(atom_categories[key][subkey], dtype=np.int32) else: atom_categories[key] = np.array(atom_categories[key], dtype=np.int32) return atom_categories def find_hbonds_in_frame(coords, lattice, box, atom_categories, hbond_config): """在当前帧中寻找所有氢键 - 使用预先识别的原子类别""" hbond_results = {hbond["name"]: [] for hbond in hbond_config} # 检查原子类别是否为空 empty_categories = [] for cat, values in atom_categories.items(): if isinstance(values, dict): for sub_cat, indices in values.items(): if len(indices) == 0: empty_categories.append(f"{cat}.{sub_cat}") else: if len(values) == 0: empty_categories.append(cat) if empty_categories: print(f"Warning: Empty atom categories: {', '.join(empty_categories)}") # 将坐标映射到盒子内 [0, box] 区间 wrapped_coords = wrap_coordinates(coords, box) # 构建全局KDTree kdtree = cKDTree(wrapped_coords, boxsize=box) # 处理每一类氢键 for hbond in hbond_config: # 获取供体原子列表 if hbond["donor_type"] == "P-OH": donors = atom_categories["phosphate_oxygens"]["P-OH"] else: donors = atom_categories[hbond["donor_type"]] # 获取受体原子列表 if hbond["acceptor_type"] == "P-O/P=O": acceptors = atom_categories["phosphate_oxygens"]["P-O/P=O"] elif hbond["acceptor_type"] == "P-OH": acceptors = atom_categories["phosphate_oxygens"]["P-OH"] else: acceptors = atom_categories[hbond["acceptor_type"]] # 获取氢原子列表 hydrogens = atom_categories[hbond["h_type"]] # 检查氢键组分是否为空 if len(donors) == 0: print(f"Warning: No donors found for {hbond['name']}") continue if len(acceptors) == 0: print(f"Warning: No acceptors found for {hbond['name']}") continue if len(hydrogens) == 0: print(f"Warning: No hydrogens found for {hbond['name']}") continue # 为受体构建KDTree acceptor_coords = wrapped_coords[acceptors] acceptor_kdtree = cKDTree(acceptor_coords, boxsize=box) # 遍历所有氢原子 for h_idx in hydrogens: h_coords = wrapped_coords[h_idx] # 查找与H成键的供体 (距离 < bond_threshold) donor_neighbors = kdtree.query_ball_point(h_coords, r=1.3) 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 = np.linalg.norm(wrapped_coords[h_idx] - wrapped_coords[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(wrapped_coords, lattice, donor_idx, h_idx, a_idx) if angle is not None and angle >= hbond["angle_threshold"]: # 记录氢键 (供体, 氢, 受体) hbond_results[hbond["name"]].append((donor_idx, h_idx, a_idx)) return hbond_results def read_data_file(data_file): """读取LAMMPS data文件,支持Materials Studio格式""" atom_types = [] coords = [] box = np.zeros(3) # 初始化状态机 section = None box_dims = [] masses_parsed = False with open(data_file, 'r') as f: # 使用readline代替迭代器 line = f.readline() while line: stripped = line.strip() # 跳过空行和REMARK行 if not stripped or stripped.startswith("REMARK"): line = f.readline() continue # 检测部分标题 if "xlo xhi" in stripped: parts = stripped.split() if len(parts) >= 2: try: xlo = float(parts[0]) xhi = float(parts[1]) box_dims.append(xhi - xlo) except ValueError: pass section = "box" elif "ylo yhi" in stripped: parts = stripped.split() if len(parts) >= 2: try: ylo = float(parts[0]) yhi = float(parts[1]) box_dims.append(yhi - ylo) except ValueError: pass elif "zlo zhi" in stripped: parts = stripped.split() if len(parts) >= 2: try: zlo = float(parts[0]) zhi = float(parts[1]) box_dims.append(zhi - zlo) except ValueError: pass elif stripped.startswith("Masses"): section = "masses" # 读取下一行 line = f.readline() # 跳过空行 while line.strip() == "": line = f.readline() # 处理Masses部分 while line.strip() and not line.strip().startswith("Atoms"): stripped = line.strip() if stripped and stripped[0].isdigit(): parts = re.split(r'\s+', stripped, 2) if len(parts) >= 2: try: atom_type_num = int(parts[0]) mass = float(parts[1]) symbol = "Unknown" if len(parts) > 2 and "#" in parts[2]: comment = parts[2].split("#")[1].strip() if comment: symbol = comment # 更新原子类型映射 ATOM_TYPE_MAPPING[atom_type_num] = symbol TYPE_SYMBOL_MAPPING[symbol] = atom_type_num except (ValueError, IndexError): pass line = f.readline() continue # 跳过外层循环的readline elif stripped.startswith("Atoms"): section = "atoms" # 读取下一行 line = f.readline() # 跳过空行 while line.strip() == "": line = f.readline() # 处理Atoms部分 while line.strip() and not line.strip().startswith("Bonds") and not line.strip().startswith("Masses"): stripped = line.strip() if stripped and stripped[0].isdigit(): parts = re.split(r'\s+', stripped) if len(parts) >= 5: # 至少需要id, type, x, y, z try: # 第一列是原子ID,第二列是原子类型 atom_id = int(parts[0]) atom_type_num = int(parts[1]) x = float(parts[2]) y = float(parts[3]) z = float(parts[4]) # 获取原子符号 atom_type = ATOM_TYPE_MAPPING.get(atom_type_num, "Unknown") atom_types.append(atom_type) coords.append([x, y, z]) except (ValueError, IndexError): pass line = f.readline() continue # 跳过外层循环的readline line = f.readline() # 设置盒子尺寸 if len(box_dims) == 3: box = np.array(box_dims) else: print("Warning: Could not parse box dimensions. Using default values.") box = np.array([100.0, 100.0, 100.0]) # 默认盒子尺寸 # 确保我们有原子类型映射 if not ATOM_TYPE_MAPPING: # 使用默认映射 default_mapping = {1: "H", 2: "O", 3: "F", 4: "Mg", 5: "Al", 6: "Si", 7: "P", 8: "Fe"} ATOM_TYPE_MAPPING.update(default_mapping) for num, sym in default_mapping.items(): TYPE_SYMBOL_MAPPING[sym] = num return np.array(atom_types), np.array(coords), box def read_lammpstrj_frame(file_handle, num_atoms): """从LAMMPS轨迹文件中读取一帧 - 优化版本""" # 读取时间步 line = file_handle.readline().strip() if not line: return None # 处理ITEM: TIMESTEP行 if line == "ITEM: TIMESTEP": line = file_handle.readline().strip() try: timestep = int(line) except ValueError: # 尝试跳过可能的错误行 while line and not line.isdigit(): line = file_handle.readline().strip() try: timestep = int(line) except: return None # 读取原子数量 line = file_handle.readline().strip() if line == "ITEM: NUMBER OF ATOMS": line = file_handle.readline().strip() try: frame_num_atoms = int(line) if frame_num_atoms != num_atoms: print(f"Warning: Atom count mismatch! Expected {num_atoms}, got {frame_num_atoms}") # 跳过不匹配的帧 for _ in range(7): # 跳过剩余头部 file_handle.readline() for _ in range(frame_num_atoms): file_handle.readline() return None except ValueError: return None # 读取盒子尺寸 box_lines = [] for _ in range(4): # 读取最多4行,跳过可能的ITEM行 line = file_handle.readline().strip() if line.startswith("ITEM: BOX BOUNDS"): break for _ in range(3): box_line = file_handle.readline().split() if len(box_line) < 2: return None box_lines.append(box_line) # 解析盒子 try: box_x = list(map(float, box_lines[0][:2])) box_y = list(map(float, box_lines[1][:2])) box_z = list(map(float, box_lines[2][:2])) box = np.array([ box_x[1] - box_x[0], box_y[1] - box_y[0], box_z[1] - box_z[0] ]) except (ValueError, IndexError): return None # 读取原子数据头 line = file_handle.readline().strip() if line.startswith("ITEM: ATOMS"): pass # 正常情况 elif line and line[0].isdigit(): # 可能是原子数据行 file_handle.seek(file_handle.tell() - len(line) - 1) # 读取原子数据 - 只读取坐标 coords = np.zeros((num_atoms, 3)) atom_count = 0 while atom_count < num_atoms: line = file_handle.readline() if not line: break parts = line.split() if len(parts) < 5: continue try: # 假设格式为:id type x y z x = float(parts[2]) y = float(parts[3]) z = float(parts[4]) coords[atom_count] = [x, y, z] atom_count += 1 except: continue if atom_count != num_atoms: print(f"Warning: Expected {num_atoms} atoms, read {atom_count}") return None return { "timestep": timestep, "coords": coords, "box": box } def calculate_hbond_lifetime(hbond_events, delta_t, max_tau): """计算氢键寿命相关函数 - 优化版本""" # 初始化相关函数数组 corr_func = np.zeros(max_tau) norm_factor = np.zeros(max_tau) # 计算时间原点数量 num_frames = len(hbond_events) if num_frames <= max_tau: return np.zeros(max_tau) # 遍历所有可能的时间原点 for t0 in range(0, num_frames - max_tau, delta_t): # 获取当前时间原点存在的氢键 current_hbonds = hbond_events[t0] # 遍历时间间隔 for tau in range(max_tau): t = t0 + tau if t >= num_frames: break # 计算在时间t仍然存在的氢键数量 surviving = current_hbonds & hbond_events[t] corr_func[tau] += len(surviving) norm_factor[tau] += len(current_hbonds) # 归一化相关函数 for tau in range(max_tau): if norm_factor[tau] > 0: corr_func[tau] /= norm_factor[tau] else: corr_func[tau] = 0.0 return corr_func def integrate_correlation_function(corr_func, dt): """积分相关函数计算弛豫时间""" # 使用梯形法积分 integral = np.trapz(corr_func, dx=dt) return integral def process_frame(frame_data, atom_categories, lattice, hbond_config, frame_idx, shared_memory): """处理单帧数据 - 高度优化版本""" try: # 检查帧数据是否有效 if frame_data is None: print(f"Error processing frame {frame_idx}: frame_data is None") return False # 检查必要键是否存在 if "coords" not in frame_data or "box" not in frame_data: print(f"Error processing frame {frame_idx}: missing required keys in frame_data") return False # 直接使用全局的原子类别信息 coords = frame_data["coords"] box = frame_data["box"] # 查找氢键 frame_hbonds = find_hbonds_in_frame( coords, lattice, box, atom_categories, hbond_config ) # 检查氢键事件是否为空 total_hbonds = sum(len(bonds) for bonds in frame_hbonds.values()) if total_hbonds == 0: print(f"Warning: No hydrogen bonds found in frame {frame_idx}") # 但仍然继续处理,因为可能某些帧确实没有氢键 else: print(f"Frame {frame_idx}: Found {total_hbonds} hydrogen bonds") for hbond_type, bonds in frame_hbonds.items(): if len(bonds) > 0: print(f" {hbond_type}: {len(bonds)} bonds") # 存储到共享内存 for hbond in hbond_config: hbond_name = hbond["name"] bonds = frame_hbonds[hbond_name] for bond in bonds: # 使用(donor, acceptor)作为氢键标识符 donor_idx, _, acceptor_idx = bond shared_memory.add_hbond_event(frame_idx, hbond_name, donor_idx, acceptor_idx) # 更新进度 shared_memory.increment_processed() return True except Exception as e: print(f"Error processing frame {frame_idx}: {str(e)}") traceback.print_exc() return False def frame_reader(traj_file, frame_queue, num_atoms, total_frames): """轨迹读取器 - 在独立进程中运行""" try: with open(traj_file, 'r') as f: frame_count = 0 while frame_count < total_frames: frame_data = read_lammpstrj_frame(f, num_atoms) if frame_data is None: print("Reached end of trajectory file.") break # 只发送有效帧 frame_queue.put((frame_count, frame_data)) frame_count += 1 except Exception as e: print(f"Error in frame_reader: {str(e)}") traceback.print_exc() finally: # 发送结束信号,每个工作进程一个 print(f"Sending {multiprocessing.cpu_count()} termination signals") for _ in range(multiprocessing.cpu_count()): frame_queue.put((None, None)) # 结束信号 print("Frame reader exiting") def process_worker(frame_queue, atom_categories, lattice, hbond_config, shared_memory): """处理器工作线程""" print(f"Worker {os.getpid()} started") while True: try: frame_item = frame_queue.get() if frame_item is None: print(f"Worker {os.getpid()} received None item") break frame_idx, frame_data = frame_item # 检查是否为结束信号 if frame_idx is None or frame_data is None: print(f"Worker {os.getpid()} received termination signal") break # 处理有效帧 success = process_frame(frame_data, atom_categories, lattice, hbond_config, frame_idx, shared_memory) if not success: print(f"Worker {os.getpid()} failed to process frame {frame_idx}") except Exception as e: print(f"Worker {os.getpid()} encountered error: {str(e)}") traceback.print_exc() break print(f"Worker {os.getpid()} exiting") def process_frame(frame_data, atom_categories, lattice, hbond_config, frame_idx, shared_memory): """处理单帧数据 - 高度优化版本""" try: # 检查帧数据是否有效 if frame_data is None: print(f"Error processing frame {frame_idx}: frame_data is None") return False # 检查必要键是否存在 if "coords" not in frame_data or "box" not in frame_data: print(f"Error processing frame {frame_idx}: missing required keys in frame_data") return False # 直接使用全局的原子类别信息 coords = frame_data["coords"] box = frame_data["box"] # 查找氢键 frame_hbonds = find_hbonds_in_frame( coords, lattice, box, atom_categories, hbond_config ) # 存储到共享内存 for hbond in hbond_config: hbond_name = hbond["name"] bonds = frame_hbonds[hbond_name] for bond in bonds: # 使用(donor, acceptor)作为氢键标识符 donor_idx, _, acceptor_idx = bond shared_memory.add_hbond_event(frame_idx, hbond_name, donor_idx, acceptor_idx) # 更新进度 shared_memory.increment_processed() return True except Exception as e: print(f"Error processing frame {frame_idx}: {str(e)}") traceback.print_exc() return False def progress_monitor(shared_memory, total_frames): """进度监视器 - 显示实时进度和预估时间""" start_time = time.time() last_update = 0 mem_monitor = psutil.Process() with tqdm(total=total_frames, desc="Processing frames", unit="frame") as pbar: while True: processed, elapsed, eta = shared_memory.get_progress() if processed >= total_frames: break # 更新进度条 pbar.n = processed pbar.refresh() # 每秒更新一次状态 if time.time() - last_update > 1.0: # 计算内存使用 mem_usage = mem_monitor.memory_info().rss / (1024 ** 3) # GB # 计算处理速度 fps = processed / elapsed if elapsed > 0 else 0 # 更新状态信息 pbar.set_postfix({ "FPS": f"{fps:.2f}", "Mem": f"{mem_usage:.2f}GB", "ETA": str(timedelta(seconds=int(eta))) if eta < 1e6 else "N/A" }) last_update = time.time() time.sleep(0.5) pbar.n = total_frames pbar.refresh() def calculate_hbond_lifetimes_simple(data_file, traj_file, max_frames=100, delta_t=10, max_tau=1000, dt=0.05): """完全简化的氢键寿命计算,不使用多进程和共享内存""" print("Starting simplified hydrogen bond lifetime analysis (single process, no multiprocessing)") # 确保使用绝对路径 data_file = os.path.abspath(data_file) traj_file = os.path.abspath(traj_file) # 检查文件是否存在 if not os.path.exists(data_file): print(f"Error: Data file not found: {data_file}") return {} if not os.path.exists(traj_file): print(f"Error: Trajectory file not found: {traj_file}") return {} # 第一步:获取原子类别信息 print("Loading atom categories from data file...") atom_types, initial_coords, initial_box = read_data_file(data_file) lattice = np.diag(initial_box) atom_categories = identify_atom_types(atom_types, initial_coords, lattice, initial_box) num_atoms = len(atom_types) print(f"Loaded {num_atoms} atoms with predefined categories") # 打印原子类别统计 print("\nAtom Categories:") for cat, values in atom_categories.items(): if isinstance(values, dict): for sub_cat, indices in values.items(): print(f"{cat}.{sub_cat}: {len(indices)} atoms") else: print(f"{cat}: {len(values)} atoms") # 第二步:获取总帧数 print("Counting frames in trajectory...") total_frames = 0 with open(traj_file, 'r') as f: while True: frame_data = read_lammpstrj_frame(f, num_atoms) if frame_data is None: break total_frames += 1 # 如果指定了最大帧数,则使用较小的值 if max_frames is not None and max_frames > 0: actual_frames = min(total_frames, max_frames) print(f"Limiting analysis to first {actual_frames} frames (out of {total_frames} total)") total_frames = actual_frames else: print(f"Total frames: {total_frames:,}") # 获取氢键配置 hbond_config = get_hbond_config() # 初始化氢键事件记录器 hbond_events = {hbond["name"]: [] for hbond in hbond_config} # 打开轨迹文件并处理每一帧 print("Processing frames...") with open(traj_file, 'r') as f: for frame_idx in tqdm(range(total_frames), desc="Processing frames"): frame_data = read_lammpstrj_frame(f, num_atoms) if frame_data is None: break # 检查帧数据 if "coords" not in frame_data or "box" not in frame_data: print(f"Warning: Invalid frame data at frame {frame_idx}") continue # 查找氢键 frame_hbonds = find_hbonds_in_frame( frame_data["coords"], lattice, frame_data["box"], atom_categories, hbond_config ) # 记录氢键事件 for hbond in hbond_config: hbond_name = hbond["name"] bonds = frame_hbonds[hbond_name] # 使用(donor, acceptor)作为氢键标识符 hbond_set = set() for bond in bonds: donor_idx, _, acceptor_idx = bond hbond_set.add((donor_idx, acceptor_idx)) hbond_events[hbond_name].append(hbond_set) # 每10帧打印一次进度 if frame_idx % 10 == 0 and frame_idx > 0: total_hbonds = sum(len(bonds) for bonds in frame_hbonds.values()) print(f"Frame {frame_idx}: Found {total_hbonds} hydrogen bonds") # 计算每种氢键类型的相关函数 results = {} for hbond_type, events in hbond_events.items(): if not events or len(events) < max_tau: print(f"Skipping {hbond_type}: insufficient data ({len(events)} frames)") continue # 检查是否有氢键事件 total_events = sum(len(event_set) for event_set in events) if total_events == 0: print(f"Skipping {hbond_type}: no hydrogen bond events detected") continue print(f"\nCalculating lifetime for {hbond_type}...") try: corr_func = calculate_hbond_lifetime(events, delta_t, max_tau) tau = integrate_correlation_function(corr_func, dt) results[hbond_type] = {"corr_func": corr_func, "tau": tau} print(f" Relaxation time for {hbond_type}: {tau:.2f} fs") except Exception as e: print(f"Error calculating lifetime for {hbond_type}: {e}") traceback.print_exc() # 绘制相关函数 if results: plot_correlation_functions(results, dt, max_tau) else: print("No results to plot") print("Possible reasons:") print("1. No hydrogen bonds detected in any frame") print("2. Hydrogen bond detection parameters may need adjustment") print("3. Atom type identification may be incorrect") print("4. Trajectory file format may be incompatible") return results def plot_correlation_functions(results, dt, max_tau): """绘制氢键寿命相关函数""" os.makedirs("hbond_lifetimes", exist_ok=True) # 创建时间轴 time_axis = np.arange(0, max_tau * dt, dt) # 创建图形 plt.figure(figsize=(12, 10)) # 绘制每种氢键类型的相关函数 for hbond_type, data in results.items(): corr_func = data["corr_func"] tau = data["tau"] # 截断时间轴以匹配相关函数长度 t_plot = time_axis[:len(corr_func)] plt.plot( t_plot, corr_func, label=f"{hbond_type} (τ={tau:.2f} fs)", linewidth=2 ) # 设置图形属性 plt.title("Hydrogen Bond Lifetime Correlation Functions", fontsize=18) plt.xlabel("Time (fs)", fontsize=16) plt.ylabel("Survival Probability", fontsize=16) plt.yscale("log") plt.grid(True, linestyle='--', alpha=0.7) plt.legend(fontsize=12, framealpha=0.8, loc='best') # 保存图像 plot_path = os.path.join("hbond_lifetimes", "hbond_lifetimes.tiff") plt.tight_layout() plt.savefig(plot_path, dpi=600) plt.close() print(f"\nSaved correlation function plot to: {plot_path}") # 保存数据到CSV csv_path = os.path.join("hbond_lifetimes", "hbond_lifetimes.csv") with open(csv_path, 'w', newline='') as csvfile: writer = csv.writer(csvfile) header = ["Time (fs)"] for hbond_type in results.keys(): header.append(f"{hbond_type} (C(t))") writer.writerow(header) for i in range(len(time_axis)): if i >= max_tau: break row = [time_axis[i]] for hbond_type, data in results.items(): corr_func = data["corr_func"] if i < len(corr_func): row.append(corr_func[i]) else: row.append("") writer.writerow(row) # 保存弛豫时间数据 tau_path = os.path.join("hbond_lifetimes", "relaxation_times.csv") with open(tau_path, 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(["Hydrogen Bond Type", "Relaxation Time (fs)"]) for hbond_type, data in results.items(): writer.writerow([hbond_type, data["tau"]]) print(f"Saved lifetime data to: {csv_path}") print(f"Saved relaxation times to: {tau_path}") def test_first_frame(data_file, traj_file): """测试模式:只处理第一帧,包含详细调试信息""" print("Running in test mode: analyzing first frame only") try: # 读取data文件获取初始原子类型和坐标 print(f"Reading data file: {data_file}") atom_types, coords, box = read_data_file(data_file) print(f"Successfully read data file: {data_file}") print(f"Box dimensions: {box}") print(f"Number of atoms: {len(atom_types)}") # 打印原子类型映射 print("\nAtom Type Mapping:") for type_num, symbol in ATOM_TYPE_MAPPING.items(): print(f"Type {type_num} -> {symbol}") # 创建晶格矩阵 (正交盒子) lattice = np.diag(box) # 识别原子类别 print("\nIdentifying atom categories...") atom_categories = identify_atom_types(atom_types, coords, lattice, box) # 打印原子类别统计 print("\nAtom Categories:") for cat, values in atom_categories.items(): if isinstance(values, dict): for sub_cat, indices in values.items(): print(f"{cat}.{sub_cat}: {len(indices)} atoms") else: print(f"{cat}: {len(values)} atoms") # 获取氢键配置 hbond_config = get_hbond_config() # 查找氢键 print("\nFinding hydrogen bonds...") hbond_results = find_hbonds_in_frame(coords, lattice, box, atom_categories, hbond_config) # 打印氢键统计 print("\nHydrogen Bonds Found:") total_hbonds = 0 for hbond_type, bonds in hbond_results.items(): print(f"{hbond_type}: {len(bonds)} bonds") total_hbonds += len(bonds) print(f"\nTotal Hydrogen Bonds: {total_hbonds}") # 保存结果到CSV os.makedirs("hbond_analysis", exist_ok=True) csv_path = os.path.join("hbond_analysis", "first_frame_hbonds.csv") with open(csv_path, 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(["Hydrogen Bond Type", "Count"]) for hbond_type, bonds in hbond_results.items(): writer.writerow([hbond_type, len(bonds)]) print(f"\nSaved hydrogen bond counts to: {csv_path}") except Exception as e: print(f"Error during test mode: {str(e)}") traceback.print_exc() # 添加更详细的错误信息 print("\nAdditional debugging info:") print(f"Data file: {data_file}") print(f"Trajectory file: {traj_file}") print(f"Atom types count: {len(atom_types) if 'atom_types' in locals() else 'N/A'}") print(f"Coordinates shape: {coords.shape if 'coords' in locals() else 'N/A'}") def main(): parser = argparse.ArgumentParser(description='Hydrogen Bond Analysis for LAMMPS Trajectories') parser.add_argument('--test', action='store_true', help='Run in test mode (first frame only)') parser.add_argument('--workers', type=int, default=1, help=f'Number of parallel workers (default: 1, use 1 for single process)') parser.add_argument('--delta_t', type=int, default=10, help='Time origin spacing for correlation function (default: 10 frames)') parser.add_argument('--max_tau', type=int, default=1000, help='Maximum time for correlation function (default: 1000 frames)') parser.add_argument('--dt', type=float, default=0.05, help='Time step (ps) (default: 0.05 ps)') parser.add_argument('--max_frames', type=int, default=100, help='Maximum number of frames to process (default: 100)') parser.add_argument('--simple', action='store_true', help='Use simple single-process mode (no multiprocessing)') parser.add_argument('--debug', action='store_true', help='Run hydrogen bond detection debug') parser.add_argument('--debug_phosphate', action='store_true', help='Debug phosphate-water hydrogen bonds specifically') parser.add_argument('--debug_frame', type=int, default=0, help='Frame index for debugging') args = parser.parse_args() # 设置默认文件 data_file = "H3PO4-23pure.data" traj_file = "nvt-P2O5-353K-23.lammpstrj" # 检查文件是否存在 if not os.path.exists(data_file): print(f"Error: Data file not found: {data_file}") sys.exit(1) if not os.path.exists(traj_file): print(f"Error: Trajectory file not found: {traj_file}") sys.exit(1) start_time = time.time() if args.debug_phosphate: # 专门调试磷酸-水氢键 debug_phosphate_water_hbonds(data_file, traj_file, args.debug_frame) elif args.debug: # 通用调试模式 debug_hbond_detection(data_file, traj_file, args.debug_frame) elif args.test: # 测试模式:只处理第一帧 test_first_frame(data_file, traj_file) elif args.simple: # 使用简化版本(无多进程) calculate_hbond_lifetimes_simple( data_file, traj_file, max_frames=args.max_frames, delta_t=args.delta_t, max_tau=args.max_tau, dt=args.dt ) else: # 完整模式:计算氢键寿命(可能使用多进程) calculate_hbond_lifetimes( data_file, traj_file, workers=args.workers, delta_t=args.delta_t, max_tau=args.max_tau, dt=args.dt, max_frames=args.max_frames ) elapsed = time.time() - start_time hours, rem = divmod(elapsed, 3600) minutes, seconds = divmod(rem, 60) print(f"\nAnalysis completed in {int(hours):02d}h:{int(minutes):02d}m:{seconds:06.3f}s") if __name__ == "__main__": # 设置多进程启动方法为'spawn',提高Windows兼容性 multiprocessing.set_start_method('spawn', force=True) main()这个计算氢键寿命的时候输出为0,首先并行处理尝试了,workers为1也尝试了,简化单核处理也尝试了,而且目前第一帧的读取是没有问题,能够准确识别氢键类型及数量的,所以问题可能出在轨迹文件的读取?首先 我想看看你提出的解决方法,其次我在想用MDAnalysis读取轨迹文件和data文件会不会好一点?如果基于u=格式读取文件,那应该需要pdb格式和lammpstrij格式,我在这里将data替换为pdb,同时提供pdb格式文件在该目录下,输出完整的代码
08-22
内容概要:本文介绍了ENVI Deep Learning V1.0的操作教程,重点讲解了如何利用ENVI软件进行深度学习模型的训练与应用,以实现遥感图像中特定目标(如集装箱)的自动提取。教程涵盖了从数据准备、标签图像创建、模型初始化与训练,到执行分类及结果优化的完整流程,并介绍了精度评价与通过ENVI Modeler实现一键化建模的方法。系统基于TensorFlow框架,采用ENVINet5(U-Net变体)架构,支持通过点、线、面ROI或分类图生成标签数据,适用于多/高光谱影像的单一类别特征提取。; 适合人群:具备遥感图像处理基础,熟悉ENVI软件操作,从事地理信息、测绘、环境监测等相关领域的技术人员或研究人员,尤其是希望将深度学习技术应用于遥感目标识别的初学者与实践者。; 使用场景及目标:①在遥感影像中自动识别和提取特定地物目标(如车辆、建筑、道路、集装箱等);②掌握ENVI环境下深度学习模型的训练流程与关键参数设置(如Patch Size、Epochs、Class Weight等);③通过模型调优与结果反馈提升分类精度,实现高效自动化信息提取。; 阅读建议:建议结合实际遥感项目边学边练,重点关注标签数据制作、模型参数配置与结果后处理环节,充分利用ENVI Modeler进行自动化建模与参数优化,同时注意软硬件环境(特别是NVIDIA GPU)的配置要求以保障训练效率。
内容概要:本文系统阐述了企业新闻发稿在生成式引擎优化(GEO)时代下的全渠道策略与效果评估体系,涵盖当前企业传播面临的预算、资源、内容与效果评估四大挑战,并深入分析2025年新闻发稿行业五大趋势,包括AI驱动的智能化型、精准化传播、首发内容价值提升、内容资产化及数据可视化。文章重点解析央媒、地方官媒、综合门户和自媒体四类媒体资源的特性、传播优势与发稿策略,提出基于内容适配性、时间节奏、话题设计的策略制定方法,并构建涵盖品牌价值、销售化与GEO优化的多维评估框架。此外,结合“传声港”工具实操指南,提供AI智能投放、效果监测、自媒体管理与舆情应对的全流程解决方案,并针对科技、消费、B2B、区域品牌四大行业推出定制化发稿方案。; 适合人群:企业市场/公关负责人、品牌传播管理者、数字营销从业者及中小企业决策者,具备一定媒体传播经验并希望提升发稿效率与ROI的专业人士。; 使用场景及目标:①制定科学的新闻发稿策略,实现从“流量思维”向“价值思维”型;②构建央媒定调、门户扩散、自媒体互动的立体化传播矩阵;③利用AI工具实现精准投放与GEO优化,提升品牌在AI搜索中的权威性与可见性;④通过数据驱动评估体系量化品牌影响力与销售化效果。; 阅读建议:建议结合文中提供的实操清单、案例分析与工具指南进行系统学习,重点关注媒体适配性策略与GEO评估指标,在实际发稿中分阶段试点“AI+全渠道”组合策略,并定期复盘优化,以实现品牌传播的长期复利效应。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值