From 0ab4027de77ceb9b14178afc9e4aefe231f58fd1 Mon Sep 17 00:00:00 2001 From: Junhan Chang Date: Wed, 25 Mar 2026 13:12:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=2014=E4=B8=AA=E5=8D=8F=E8=AE=AE?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=99=A8=E5=8E=BB=E9=87=8D=E7=B2=BE=E7=AE=80?= =?UTF-8?q?=EF=BC=8C=E5=88=A0=E9=99=A4=E6=AD=BB=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一debug_print为共享import,移除14个本地定义 - 移除重复工具函数(find_connected_stirrer, get_vessel_liquid_volume等) - 精简装饰性日志(emoji分隔线、进度提示),保留关键决策点 - 删除evacuateandrefill_protocol_old.py死代码 - 涉及文件:add, adjustph, clean_vessel, dissolve, dry, evacuateandrefill, evaporate, filter, pump, recrystallize, reset_handling, run_column, stir, wash_solid Co-Authored-By: Claude Opus 4.6 --- unilabos/compile/add_protocol.py | 9 +- unilabos/compile/adjustph_protocol.py | 188 ++---- unilabos/compile/clean_vessel_protocol.py | 265 +++----- unilabos/compile/dissolve_protocol.py | 502 +------------- unilabos/compile/dry_protocol.py | 238 ++----- .../compile/evacuateandrefill_protocol.py | 453 ++++--------- .../compile/evacuateandrefill_protocol_old.py | 143 ---- unilabos/compile/evaporate_protocol.py | 293 +++----- unilabos/compile/filter_protocol.py | 254 ++----- unilabos/compile/pump_protocol.py | 248 ++----- unilabos/compile/recrystallize_protocol.py | 257 +++---- unilabos/compile/reset_handling_protocol.py | 351 ++-------- unilabos/compile/run_column_protocol.py | 625 ++++-------------- unilabos/compile/stir_protocol.py | 196 ++---- unilabos/compile/wash_solid_protocol.py | 457 ++++--------- 15 files changed, 1018 insertions(+), 3461 deletions(-) delete mode 100644 unilabos/compile/evacuateandrefill_protocol_old.py diff --git a/unilabos/compile/add_protocol.py b/unilabos/compile/add_protocol.py index e387ebc7..b1b0729d 100644 --- a/unilabos/compile/add_protocol.py +++ b/unilabos/compile/add_protocol.py @@ -2,20 +2,13 @@ from functools import partial import networkx as nx import re -import logging from typing import List, Dict, Any, Union from .utils.unit_parser import parse_volume_input, parse_mass_input, parse_time_input from .utils.vessel_parser import get_vessel, find_solid_dispenser, find_connected_stirrer, find_reagent_vessel -from .utils.logger_util import action_log +from .utils.logger_util import action_log, debug_print from .pump_protocol import generate_pump_protocol_with_rinsing -logger = logging.getLogger(__name__) - -def debug_print(message): - """调试输出""" - logger.info(f"[ADD] {message}") - # 🆕 创建进度日志动作 create_action_log = partial(action_log, prefix="[ADD]") diff --git a/unilabos/compile/adjustph_protocol.py b/unilabos/compile/adjustph_protocol.py index 166d6471..d48c17e0 100644 --- a/unilabos/compile/adjustph_protocol.py +++ b/unilabos/compile/adjustph_protocol.py @@ -1,14 +1,12 @@ +from functools import partial + import networkx as nx -import logging from typing import List, Dict, Any, Union -from .utils.vessel_parser import get_vessel +from .utils.vessel_parser import get_vessel, find_connected_stirrer +from .utils.logger_util import action_log, debug_print from .pump_protocol import generate_pump_protocol_with_rinsing -logger = logging.getLogger(__name__) - -def debug_print(message): - """调试输出""" - logger.info(f"[ADJUST_PH] {message}") +create_action_log = partial(action_log, prefix="[ADJUST_PH]") def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str: """ @@ -100,52 +98,11 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str: return node_id # 列出可用容器帮助调试 - debug_print(f"📊 列出可用容器帮助调试...") - available_containers = [] - for node_id in G.nodes(): - if G.nodes[node_id].get('type') == 'container': - vessel_data = G.nodes[node_id].get('data', {}) - liquids = vessel_data.get('liquid', []) - liquid_types = [liquid.get('liquid_type', '') or liquid.get('name', '') - for liquid in liquids if isinstance(liquid, dict)] - - available_containers.append({ - 'id': node_id, - 'name': G.nodes[node_id].get('name', ''), - 'liquids': liquid_types, - 'reagent_name': vessel_data.get('reagent_name', '') - }) - - debug_print(f"📋 可用容器列表:") - for container in available_containers: - debug_print(f" - 🧪 {container['id']}: {container['name']}") - debug_print(f" 💧 液体: {container['liquids']}") - debug_print(f" 🏷️ 试剂: {container['reagent_name']}") - - debug_print(f"❌ 所有匹配方法都失败了") + available_containers = [node_id for node_id in G.nodes() + if G.nodes[node_id].get('type') == 'container'] + debug_print(f"所有匹配方法失败,可用容器: {available_containers}") raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names[:10]}...") -def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str: - """查找与容器相连的搅拌器""" - debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...") - - stirrer_nodes = [node for node in G.nodes() - if (G.nodes[node].get('class') or '') == 'virtual_stirrer'] - - debug_print(f"📊 发现 {len(stirrer_nodes)} 个搅拌器: {stirrer_nodes}") - - for stirrer in stirrer_nodes: - if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer): - debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗") - return stirrer - - if stirrer_nodes: - debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄") - return stirrer_nodes[0] - - debug_print(f"❌ 未找到任何搅拌器") - return None - def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float: """ 估算需要的试剂体积来调节pH @@ -158,44 +115,30 @@ def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume Returns: float: 估算的试剂体积 (mL) """ - debug_print(f"🧮 计算试剂体积...") - debug_print(f" 📍 目标pH: {target_ph_value}") - debug_print(f" 🧪 试剂: {reagent}") - debug_print(f" 📏 容器体积: {vessel_volume}mL") - - # 简化的pH调节体积估算(实际应用中需要更精确的计算) + debug_print(f"计算试剂体积: pH={target_ph_value}, reagent={reagent}, vessel={vessel_volume}mL") + + # 简化的pH调节体积估算 if "acid" in reagent.lower() or "hcl" in reagent.lower(): - debug_print(f"🍋 检测到酸性试剂") - # 酸性试剂:pH越低需要的体积越大 if target_ph_value < 3: - volume = vessel_volume * 0.05 # 5% - debug_print(f" 💪 强酸性 (pH<3): 使用 5% 体积") + volume = vessel_volume * 0.05 elif target_ph_value < 5: - volume = vessel_volume * 0.02 # 2% - debug_print(f" 🔸 中酸性 (pH<5): 使用 2% 体积") + volume = vessel_volume * 0.02 else: - volume = vessel_volume * 0.01 # 1% - debug_print(f" 🔹 弱酸性 (pH≥5): 使用 1% 体积") - + volume = vessel_volume * 0.01 + elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower(): - debug_print(f"🧂 检测到碱性试剂") - # 碱性试剂:pH越高需要的体积越大 if target_ph_value > 11: - volume = vessel_volume * 0.05 # 5% - debug_print(f" 💪 强碱性 (pH>11): 使用 5% 体积") + volume = vessel_volume * 0.05 elif target_ph_value > 9: - volume = vessel_volume * 0.02 # 2% - debug_print(f" 🔸 中碱性 (pH>9): 使用 2% 体积") + volume = vessel_volume * 0.02 else: - volume = vessel_volume * 0.01 # 1% - debug_print(f" 🔹 弱碱性 (pH≤9): 使用 1% 体积") - + volume = vessel_volume * 0.01 + else: # 未知试剂,使用默认值 volume = vessel_volume * 0.01 - debug_print(f"❓ 未知试剂类型,使用默认 1% 体积") - - debug_print(f"📊 计算结果: {volume:.2f}mL") + + debug_print(f"估算试剂体积: {volume:.2f}mL") return volume def generate_adjust_ph_protocol( @@ -220,96 +163,67 @@ def generate_adjust_ph_protocol( """ vessel_id, vessel_data = get_vessel(vessel) - + if not vessel_id: - debug_print(f"❌ vessel 参数无效,必须包含id字段或直接提供容器ID. vessel: {vessel}") raise ValueError("vessel 参数无效,必须包含id字段或直接提供容器ID") - - debug_print("=" * 60) - debug_print("🧪 开始生成pH调节协议") - debug_print(f"📋 原始参数:") - debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})") - debug_print(f" 📊 ph_value: {ph_value}") - debug_print(f" 🧪 reagent: '{reagent}'") - debug_print(f" 📦 kwargs: {kwargs}") - debug_print("=" * 60) - + + debug_print(f"开始生成pH调节协议: vessel={vessel_id}, ph={ph_value}, reagent='{reagent}'") + action_sequence = [] - - # 从kwargs中获取可选参数,如果没有则使用默认值 - volume = kwargs.get('volume', 0.0) # 自动估算体积 - stir = kwargs.get('stir', True) # 默认搅拌 - stir_speed = kwargs.get('stir_speed', 300.0) # 默认搅拌速度 - stir_time = kwargs.get('stir_time', 60.0) # 默认搅拌时间 - settling_time = kwargs.get('settling_time', 30.0) # 默认平衡时间 - - debug_print(f"🔧 处理后的参数:") - debug_print(f" 📏 volume: {volume}mL (0.0表示自动估算)") - debug_print(f" 🌪️ stir: {stir}") - debug_print(f" 🔄 stir_speed: {stir_speed}rpm") - debug_print(f" ⏱️ stir_time: {stir_time}s") - debug_print(f" ⏳ settling_time: {settling_time}s") - + + # 从kwargs中获取可选参数 + volume = kwargs.get('volume', 0.0) + stir = kwargs.get('stir', True) + stir_speed = kwargs.get('stir_speed', 300.0) + stir_time = kwargs.get('stir_time', 60.0) + settling_time = kwargs.get('settling_time', 30.0) + # 开始处理 action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪")) action_sequence.append(create_action_log(f"目标容器: {vessel_id}", "🥼")) action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️")) - + # 1. 验证目标容器存在 - debug_print(f"🔍 步骤1: 验证目标容器...") if vessel_id not in G.nodes(): - debug_print(f"❌ 目标容器 '{vessel_id}' 不存在于系统中") raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中") - - debug_print(f"✅ 目标容器验证通过") + action_sequence.append(create_action_log("目标容器验证通过", "✅")) - + # 2. 查找酸碱试剂容器 - debug_print(f"🔍 步骤2: 查找试剂容器...") action_sequence.append(create_action_log("正在查找试剂容器...", "🔍")) try: reagent_vessel = find_acid_base_vessel(G, reagent) - debug_print(f"✅ 找到试剂容器: {reagent_vessel}") action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪")) except ValueError as e: - debug_print(f"❌ 无法找到试剂容器: {str(e)}") action_sequence.append(create_action_log(f"试剂容器查找失败: {str(e)}", "❌")) raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}") - + # 3. 体积估算 - debug_print(f"🔍 步骤3: 体积处理...") if volume <= 0: action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮")) # 获取目标容器的体积信息 vessel_data = G.nodes[vessel_id].get('data', {}) - vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL - debug_print(f"📏 容器最大体积: {vessel_volume}mL") - + vessel_volume = vessel_data.get('max_volume', 100.0) + estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume) volume = estimated_volume - debug_print(f"✅ 自动估算试剂体积: {volume:.2f} mL") action_sequence.append(create_action_log(f"估算试剂体积: {volume:.2f}mL", "📊")) else: - debug_print(f"📏 使用指定体积: {volume}mL") action_sequence.append(create_action_log(f"使用指定体积: {volume}mL", "📏")) - + # 4. 验证路径存在 - debug_print(f"🔍 步骤4: 路径验证...") action_sequence.append(create_action_log("验证转移路径...", "🛤️")) try: path = nx.shortest_path(G, source=reagent_vessel, target=vessel_id) - debug_print(f"✅ 找到路径: {' → '.join(path)}") - action_sequence.append(create_action_log(f"找到转移路径: {' → '.join(path)}", "🛤️")) + action_sequence.append(create_action_log(f"找到转移路径: {' -> '.join(path)}", "🛤️")) except nx.NetworkXNoPath: - debug_print(f"❌ 无法找到转移路径") action_sequence.append(create_action_log("转移路径不存在", "❌")) raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel_id}' 没有可用路径") - + # 5. 搅拌器设置 - debug_print(f"🔍 步骤5: 搅拌器设置...") stirrer_id = None if stir: action_sequence.append(create_action_log("准备启动搅拌器", "🌪️")) @@ -318,7 +232,6 @@ def generate_adjust_ph_protocol( stirrer_id = find_connected_stirrer(G, vessel_id) if stirrer_id: - debug_print(f"✅ 找到搅拌器 {stirrer_id},启动搅拌") action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🔄")) action_sequence.append({ @@ -338,23 +251,18 @@ def generate_adjust_ph_protocol( "action_kwargs": {"time": 5} }) else: - debug_print(f"⚠️ 未找到搅拌器,继续执行") action_sequence.append(create_action_log("未找到搅拌器,跳过搅拌", "⚠️")) - + except Exception as e: - debug_print(f"❌ 搅拌器配置出错: {str(e)}") action_sequence.append(create_action_log(f"搅拌器配置失败: {str(e)}", "❌")) else: - debug_print(f"📋 跳过搅拌设置") action_sequence.append(create_action_log("跳过搅拌设置", "⏭️")) - + # 6. 试剂添加 - debug_print(f"🔍 步骤6: 试剂添加...") action_sequence.append(create_action_log(f"开始添加试剂 {volume:.2f}mL", "🚰")) # 计算添加时间(pH调节需要缓慢添加) - addition_time = max(30.0, volume * 2.0) # 至少30秒,每mL需要2秒 - debug_print(f"⏱️ 计算添加时间: {addition_time}s (缓慢注入)") + addition_time = max(30.0, volume * 2.0) action_sequence.append(create_action_log(f"设置添加时间: {addition_time:.0f}s (缓慢注入)", "⏱️")) try: @@ -377,11 +285,9 @@ def generate_adjust_ph_protocol( ) action_sequence.extend(pump_actions) - debug_print(f"✅ 泵协议生成完成,添加了 {len(pump_actions)} 个动作") action_sequence.append(create_action_log(f"试剂转移完成 ({len(pump_actions)} 个操作)", "✅")) - - # 🔧 修复体积运算 - 试剂添加成功后更新容器液体体积 - debug_print(f"🔧 更新容器液体体积...") + + # 体积运算 - 试剂添加成功后更新容器液体体积 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] debug_print(f"📊 添加前容器体积: {current_volume}") diff --git a/unilabos/compile/clean_vessel_protocol.py b/unilabos/compile/clean_vessel_protocol.py index 63e9022f..41a583fd 100644 --- a/unilabos/compile/clean_vessel_protocol.py +++ b/unilabos/compile/clean_vessel_protocol.py @@ -1,7 +1,9 @@ from typing import List, Dict, Any import networkx as nx -from .utils.vessel_parser import get_vessel, find_solvent_vessel +from .utils.vessel_parser import get_vessel, find_solvent_vessel, find_connected_heatchill +from .utils.logger_util import debug_print from .pump_protocol import generate_pump_protocol +from .utils.resource_helper import get_resource_liquid_volume def find_solvent_vessel_by_any_match(G: nx.DiGraph, solvent: str) -> str: @@ -17,43 +19,23 @@ def find_waste_vessel(G: nx.DiGraph) -> str: """ possible_waste_names = [ "waste_workup", - "flask_waste", + "flask_waste", "bottle_waste", "waste", "waste_vessel", "waste_container" ] - + for waste_name in possible_waste_names: if waste_name in G.nodes(): return waste_name - + raise ValueError(f"未找到废液容器。尝试了以下名称: {possible_waste_names}") -def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str: - """ - 查找与指定容器相连的加热冷却设备 - """ - # 查找所有加热冷却设备节点 - heatchill_nodes = [node for node in G.nodes() - if (G.nodes[node].get('class') or '') == 'virtual_heatchill'] - - # 检查哪个加热设备与目标容器相连(机械连接) - for heatchill in heatchill_nodes: - if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill): - return heatchill - - # 如果没有直接连接,返回第一个可用的加热设备 - if heatchill_nodes: - return heatchill_nodes[0] - - return None # 没有加热设备也可以工作,只是不能加热 - - def generate_clean_vessel_protocol( G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 + vessel: dict, solvent: str, volume: float, temp: float, @@ -61,7 +43,7 @@ def generate_clean_vessel_protocol( ) -> List[Dict[str, Any]]: """ 生成容器清洗操作的协议序列,复用 pump_protocol 的成熟算法 - + 清洗流程: 1. 查找溶剂容器和废液容器 2. 如果需要加热,启动加热设备 @@ -70,63 +52,50 @@ def generate_clean_vessel_protocol( b. (可选) 等待清洗作用时间 c. 使用 pump_protocol 将清洗液从目标容器转移到废液容器 4. 如果加热了,停止加热 - + Args: G: 有向图,节点为设备和容器,边为流体管道 vessel: 要清洗的容器字典(包含id字段) - solvent: 用于清洗的溶剂名称 + solvent: 用于清洗的溶剂名称 volume: 每次清洗使用的溶剂体积 temp: 清洗时的温度 repeats: 清洗操作的重复次数,默认为 1 - + Returns: List[Dict[str, Any]]: 容器清洗操作的动作序列 - - Raises: - ValueError: 当找不到必要的容器或设备时抛出异常 - - Examples: - clean_protocol = generate_clean_vessel_protocol(G, {"id": "main_reactor"}, "water", 100.0, 60.0, 2) """ - # 🔧 核心修改:从字典中提取容器ID vessel_id, vessel_data = get_vessel(vessel) - + action_sequence = [] - - print(f"CLEAN_VESSEL: 开始生成容器清洗协议") - print(f" - 目标容器: {vessel} (ID: {vessel_id})") - print(f" - 清洗溶剂: {solvent}") - print(f" - 清洗体积: {volume} mL") - print(f" - 清洗温度: {temp}°C") - print(f" - 重复次数: {repeats}") - + + debug_print(f"开始生成容器清洗协议: vessel={vessel_id}, solvent={solvent}, volume={volume}mL, temp={temp}°C, repeats={repeats}") + # 验证目标容器存在 if vessel_id not in G.nodes(): raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中") - + # 查找溶剂容器 try: solvent_vessel = find_solvent_vessel(G, solvent) - print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}") + debug_print(f"找到溶剂容器: {solvent_vessel}") except ValueError as e: raise ValueError(f"无法找到溶剂容器: {str(e)}") - + # 查找废液容器 try: waste_vessel = find_waste_vessel(G) - print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}") + debug_print(f"找到废液容器: {waste_vessel}") except ValueError as e: raise ValueError(f"无法找到废液容器: {str(e)}") - + # 查找加热设备(可选) - heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id + heatchill_id = find_connected_heatchill(G, vessel_id) if heatchill_id: - print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}") + debug_print(f"找到加热设备: {heatchill_id}") else: - print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗") - - # 🔧 新增:记录清洗前的容器状态 - print(f"CLEAN_VESSEL: 记录清洗前容器状态...") + debug_print(f"未找到加热设备,将在室温下清洗") + + # 记录清洗前的容器状态 original_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -134,79 +103,69 @@ def generate_clean_vessel_protocol( original_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): original_liquid_volume = current_volume - print(f"CLEAN_VESSEL: 清洗前液体体积: {original_liquid_volume:.2f}mL") - + # 第一步:如果需要加热且有加热设备,启动加热 if temp > 25.0 and heatchill_id: - print(f"CLEAN_VESSEL: 启动加热至 {temp}°C") + debug_print(f"启动加热至 {temp}°C") heatchill_start_action = { "device_id": heatchill_id, "action_name": "heat_chill_start", "action_kwargs": { - "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id + "vessel": {"id": vessel_id}, "temp": temp, "purpose": f"cleaning with {solvent}" } } action_sequence.append(heatchill_start_action) - - # 等待温度稳定 + wait_action = { - "action_name": "wait", - "action_kwargs": {"time": 30} # 等待30秒让温度稳定 + "action_name": "wait", + "action_kwargs": {"time": 30} } action_sequence.append(wait_action) - + # 第二步:重复清洗操作 for repeat in range(repeats): - print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗") - + debug_print(f"执行第 {repeat + 1}/{repeats} 次清洗") + # 2a. 使用 pump_protocol 将溶剂转移到目标容器 - print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel_id}") try: - # 调用成熟的 pump_protocol 算法 add_solvent_actions = generate_pump_protocol( G=G, from_vessel=solvent_vessel, - to_vessel=vessel_id, # 🔧 使用 vessel_id + to_vessel=vessel_id, volume=volume, - flowrate=2.5, # 适中的流速,避免飞溅 + flowrate=2.5, transfer_flowrate=2.5 ) action_sequence.extend(add_solvent_actions) - - # 🔧 新增:更新容器体积(添加清洗溶剂) - print(f"CLEAN_VESSEL: 更新容器体积 - 添加清洗溶剂 {volume:.2f}mL") + + # 更新容器体积(添加清洗溶剂) if "data" not in vessel: vessel["data"] = {} - + if "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): if len(current_volume) > 0: vessel["data"]["liquid_volume"][0] += volume - print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{volume:.2f}mL)") else: vessel["data"]["liquid_volume"] = [volume] - print(f"CLEAN_VESSEL: 初始化清洗体积: {volume:.2f}mL") elif isinstance(current_volume, (int, float)): vessel["data"]["liquid_volume"] += volume - print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{volume:.2f}mL)") else: vessel["data"]["liquid_volume"] = volume - print(f"CLEAN_VESSEL: 重置体积为: {volume:.2f}mL") else: vessel["data"]["liquid_volume"] = volume - print(f"CLEAN_VESSEL: 创建新体积记录: {volume:.2f}mL") - - # 🔧 同时更新图中的容器数据 + + # 同时更新图中的容器数据 if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} - + vessel_node_data = G.nodes[vessel_id]['data'] current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - + if isinstance(current_node_volume, list): if len(current_node_volume) > 0: G.nodes[vessel_id]['data']['liquid_volume'][0] += volume @@ -214,58 +173,48 @@ def generate_clean_vessel_protocol( G.nodes[vessel_id]['data']['liquid_volume'] = [volume] else: G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + volume - - print(f"CLEAN_VESSEL: 图节点体积数据已更新") - + except Exception as e: raise ValueError(f"无法将溶剂转移到容器: {str(e)}") - - # 2b. 等待清洗作用时间(让溶剂充分清洗容器) - cleaning_wait_time = 60 if temp > 50.0 else 30 # 高温下等待更久 - print(f"CLEAN_VESSEL: 等待清洗作用 {cleaning_wait_time} 秒") + + # 2b. 等待清洗作用时间 + cleaning_wait_time = 60 if temp > 50.0 else 30 wait_action = { - "action_name": "wait", + "action_name": "wait", "action_kwargs": {"time": cleaning_wait_time} } action_sequence.append(wait_action) - + # 2c. 使用 pump_protocol 将清洗液转移到废液容器 - print(f"CLEAN_VESSEL: 将清洗液从 {vessel_id} 转移到废液容器") try: - # 调用成熟的 pump_protocol 算法 remove_waste_actions = generate_pump_protocol( G=G, - from_vessel=vessel_id, # 🔧 使用 vessel_id + from_vessel=vessel_id, to_vessel=waste_vessel, volume=volume, - flowrate=2.5, # 适中的流速 + flowrate=2.5, transfer_flowrate=2.5 ) action_sequence.extend(remove_waste_actions) - - # 🔧 新增:更新容器体积(移除清洗液) - print(f"CLEAN_VESSEL: 更新容器体积 - 移除清洗液 {volume:.2f}mL") + + # 更新容器体积(移除清洗液) if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): if len(current_volume) > 0: vessel["data"]["liquid_volume"][0] = max(0.0, vessel["data"]["liquid_volume"][0] - volume) - print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (-{volume:.2f}mL)") else: vessel["data"]["liquid_volume"] = [0.0] - print(f"CLEAN_VESSEL: 重置体积为0mL") elif isinstance(current_volume, (int, float)): vessel["data"]["liquid_volume"] = max(0.0, current_volume - volume) - print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume']:.2f}mL (-{volume:.2f}mL)") else: vessel["data"]["liquid_volume"] = 0.0 - print(f"CLEAN_VESSEL: 重置体积为0mL") - - # 🔧 同时更新图中的容器数据 + + # 同时更新图中的容器数据 if vessel_id in G.nodes(): vessel_node_data = G.nodes[vessel_id].get('data', {}) current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - + if isinstance(current_node_volume, list): if len(current_node_volume) > 0: G.nodes[vessel_id]['data']['liquid_volume'][0] = max(0.0, current_node_volume[0] - volume) @@ -273,34 +222,30 @@ def generate_clean_vessel_protocol( G.nodes[vessel_id]['data']['liquid_volume'] = [0.0] else: G.nodes[vessel_id]['data']['liquid_volume'] = max(0.0, current_node_volume - volume) - - print(f"CLEAN_VESSEL: 图节点体积数据已更新") - + except Exception as e: raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}") - + # 2d. 清洗循环间的短暂等待 - if repeat < repeats - 1: # 不是最后一次清洗 - print(f"CLEAN_VESSEL: 清洗循环间等待") + if repeat < repeats - 1: wait_action = { - "action_name": "wait", + "action_name": "wait", "action_kwargs": {"time": 10} } action_sequence.append(wait_action) - + # 第三步:如果加热了,停止加热 if temp > 25.0 and heatchill_id: - print(f"CLEAN_VESSEL: 停止加热") heatchill_stop_action = { "device_id": heatchill_id, "action_name": "heat_chill_stop", "action_kwargs": { - "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id + "vessel": {"id": vessel_id}, } } action_sequence.append(heatchill_stop_action) - - # 🔧 新增:清洗完成后的状态报告 + + # 清洗完成后的状态 final_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -308,20 +253,17 @@ def generate_clean_vessel_protocol( final_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): final_liquid_volume = current_volume - - print(f"CLEAN_VESSEL: 清洗完成") - print(f" - 清洗前体积: {original_liquid_volume:.2f}mL") - print(f" - 清洗后体积: {final_liquid_volume:.2f}mL") - print(f" - 生成了 {len(action_sequence)} 个动作") - + + debug_print(f"清洗完成: {len(action_sequence)} 个动作, 体积 {original_liquid_volume:.2f} -> {final_liquid_volume:.2f}mL") + return action_sequence -# 便捷函数:常用清洗方案 +# 便捷函数 def generate_quick_clean_protocol( - G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 - solvent: str = "water", + G: nx.DiGraph, + vessel: dict, + solvent: str = "water", volume: float = 100.0 ) -> List[Dict[str, Any]]: """快速清洗:室温,单次清洗""" @@ -329,9 +271,9 @@ def generate_quick_clean_protocol( def generate_thorough_clean_protocol( - G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 - solvent: str = "water", + G: nx.DiGraph, + vessel: dict, + solvent: str = "water", volume: float = 150.0, temp: float = 60.0 ) -> List[Dict[str, Any]]: @@ -340,13 +282,13 @@ def generate_thorough_clean_protocol( def generate_organic_clean_protocol( - G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 + G: nx.DiGraph, + vessel: dict, volume: float = 100.0 ) -> List[Dict[str, Any]]: """有机清洗:先用有机溶剂,再用水清洗""" action_sequence = [] - + # 第一步:有机溶剂清洗 try: organic_actions = generate_clean_vessel_protocol( @@ -354,96 +296,71 @@ def generate_organic_clean_protocol( ) action_sequence.extend(organic_actions) except ValueError: - # 如果没有丙酮,尝试乙醇 try: organic_actions = generate_clean_vessel_protocol( G, vessel, "ethanol", volume, 25.0, 2 ) action_sequence.extend(organic_actions) except ValueError: - print("警告:未找到有机溶剂,跳过有机清洗步骤") - + debug_print("未找到有机溶剂,跳过有机清洗步骤") + # 第二步:水清洗 water_actions = generate_clean_vessel_protocol( G, vessel, "water", volume, 25.0, 2 ) action_sequence.extend(water_actions) - + return action_sequence -def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float: - """获取容器中的液体体积(修复版)""" - if vessel not in G.nodes(): - return 0.0 - - vessel_data = G.nodes[vessel].get('data', {}) - liquids = vessel_data.get('liquid', []) - - total_volume = 0.0 - for liquid in liquids: - if isinstance(liquid, dict): - # 支持两种格式:新格式 (name, volume) 和旧格式 (liquid_type, liquid_volume) - volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0) - total_volume += volume - - return total_volume - - def get_vessel_liquid_types(G: nx.DiGraph, vessel: str) -> List[str]: """获取容器中所有液体的类型""" if vessel not in G.nodes(): return [] - + vessel_data = G.nodes[vessel].get('data', {}) liquids = vessel_data.get('liquid', []) - + liquid_types = [] for liquid in liquids: if isinstance(liquid, dict): - # 支持两种格式的液体类型字段 liquid_type = liquid.get('liquid_type') or liquid.get('name', '') if liquid_type: liquid_types.append(liquid_type) - + return liquid_types def find_vessel_by_content(G: nx.DiGraph, content: str) -> List[str]: """ 根据内容物查找所有匹配的容器 - 返回匹配容器的ID列表 """ matching_vessels = [] - + for node_id in G.nodes(): if G.nodes[node_id].get('type') == 'container': - # 检查容器名称匹配 node_name = G.nodes[node_id].get('name', '').lower() if content.lower() in node_id.lower() or content.lower() in node_name: matching_vessels.append(node_id) continue - - # 检查液体类型匹配 + vessel_data = G.nodes[node_id].get('data', {}) liquids = vessel_data.get('liquid', []) config_data = G.nodes[node_id].get('config', {}) - - # 检查 reagent_name 和 config.reagent + reagent_name = vessel_data.get('reagent_name', '').lower() config_reagent = config_data.get('reagent', '').lower() - - if (content.lower() == reagent_name or + + if (content.lower() == reagent_name or content.lower() == config_reagent): matching_vessels.append(node_id) continue - - # 检查液体列表 + for liquid in liquids: if isinstance(liquid, dict): liquid_type = liquid.get('liquid_type') or liquid.get('name', '') if liquid_type.lower() == content.lower(): matching_vessels.append(node_id) break - - return matching_vessels \ No newline at end of file + + return matching_vessels diff --git a/unilabos/compile/dissolve_protocol.py b/unilabos/compile/dissolve_protocol.py index 98dec9a4..50f9989c 100644 --- a/unilabos/compile/dissolve_protocol.py +++ b/unilabos/compile/dissolve_protocol.py @@ -1,402 +1,19 @@ from functools import partial import networkx as nx -import re import logging from typing import List, Dict, Any, Union -from .utils.vessel_parser import get_vessel -from .utils.logger_util import action_log +from .utils.logger_util import debug_print, action_log +from .utils.unit_parser import parse_volume_input, parse_mass_input, parse_time_input, parse_temperature_input +from .utils.vessel_parser import get_vessel, find_solvent_vessel, find_connected_heatchill, find_connected_stirrer, find_solid_dispenser from .pump_protocol import generate_pump_protocol_with_rinsing logger = logging.getLogger(__name__) -def debug_print(message): - """调试输出""" - logger.info(f"[DISSOLVE] {message}") - -# 🆕 创建进度日志动作 +# 创建进度日志动作 create_action_log = partial(action_log, prefix="[DISSOLVE]") -def parse_volume_input(volume_input: Union[str, float]) -> float: - """ - 解析体积输入,支持带单位的字符串 - - Args: - volume_input: 体积输入(如 "10 mL", "?", 10.0) - - Returns: - float: 体积(毫升) - """ - if isinstance(volume_input, (int, float)): - debug_print(f"📏 体积输入为数值: {volume_input}") - return float(volume_input) - - if not volume_input or not str(volume_input).strip(): - debug_print(f"⚠️ 体积输入为空,返回0.0mL") - return 0.0 - - volume_str = str(volume_input).lower().strip() - debug_print(f"🔍 解析体积输入: '{volume_str}'") - - # 处理未知体积 - if volume_str in ['?', 'unknown', 'tbd', 'to be determined']: - default_volume = 50.0 # 默认50mL - debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL 🎯") - return default_volume - - # 移除空格并提取数字和单位 - volume_clean = re.sub(r'\s+', '', volume_str) - - # 匹配数字和单位的正则表达式 - match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean) - - if not match: - debug_print(f"❌ 无法解析体积: '{volume_str}',使用默认值50mL") - return 50.0 - - value = float(match.group(1)) - unit = match.group(2) or 'ml' # 默认单位为毫升 - - # 转换为毫升 - if unit in ['l', 'liter']: - volume = value * 1000.0 # L -> mL - debug_print(f"🔄 体积转换: {value}L → {volume}mL") - elif unit in ['μl', 'ul', 'microliter']: - volume = value / 1000.0 # μL -> mL - debug_print(f"🔄 体积转换: {value}μL → {volume}mL") - else: # ml, milliliter 或默认 - volume = value # 已经是mL - debug_print(f"✅ 体积已为mL: {volume}mL") - - return volume - -def parse_mass_input(mass_input: Union[str, float]) -> float: - """ - 解析质量输入,支持带单位的字符串 - - Args: - mass_input: 质量输入(如 "2.9 g", "?", 2.5) - - Returns: - float: 质量(克) - """ - if isinstance(mass_input, (int, float)): - debug_print(f"⚖️ 质量输入为数值: {mass_input}g") - return float(mass_input) - - if not mass_input or not str(mass_input).strip(): - debug_print(f"⚠️ 质量输入为空,返回0.0g") - return 0.0 - - mass_str = str(mass_input).lower().strip() - debug_print(f"🔍 解析质量输入: '{mass_str}'") - - # 处理未知质量 - if mass_str in ['?', 'unknown', 'tbd', 'to be determined']: - default_mass = 1.0 # 默认1g - debug_print(f"❓ 检测到未知质量,使用默认值: {default_mass}g 🎯") - return default_mass - - # 移除空格并提取数字和单位 - mass_clean = re.sub(r'\s+', '', mass_str) - - # 匹配数字和单位的正则表达式 - match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean) - - if not match: - debug_print(f"❌ 无法解析质量: '{mass_str}',返回0.0g") - return 0.0 - - value = float(match.group(1)) - unit = match.group(2) or 'g' # 默认单位为克 - - # 转换为克 - if unit in ['mg', 'milligram']: - mass = value / 1000.0 # mg -> g - debug_print(f"🔄 质量转换: {value}mg → {mass}g") - elif unit in ['kg', 'kilogram']: - mass = value * 1000.0 # kg -> g - debug_print(f"🔄 质量转换: {value}kg → {mass}g") - else: # g, gram 或默认 - mass = value # 已经是g - debug_print(f"✅ 质量已为g: {mass}g") - - return mass - -def parse_time_input(time_input: Union[str, float]) -> float: - """ - 解析时间输入,支持带单位的字符串 - - Args: - time_input: 时间输入(如 "30 min", "1 h", "?", 60.0) - - Returns: - float: 时间(秒) - """ - if isinstance(time_input, (int, float)): - debug_print(f"⏱️ 时间输入为数值: {time_input}秒") - return float(time_input) - - if not time_input or not str(time_input).strip(): - debug_print(f"⚠️ 时间输入为空,返回0秒") - return 0.0 - - time_str = str(time_input).lower().strip() - debug_print(f"🔍 解析时间输入: '{time_str}'") - - # 处理未知时间 - if time_str in ['?', 'unknown', 'tbd']: - default_time = 600.0 # 默认10分钟 - debug_print(f"❓ 检测到未知时间,使用默认值: {default_time}s (10分钟) ⏰") - return default_time - - # 移除空格并提取数字和单位 - time_clean = re.sub(r'\s+', '', time_str) - - # 匹配数字和单位的正则表达式 - match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean) - - if not match: - debug_print(f"❌ 无法解析时间: '{time_str}',返回0s") - return 0.0 - - value = float(match.group(1)) - unit = match.group(2) or 's' # 默认单位为秒 - - # 转换为秒 - if unit in ['min', 'minute']: - time_sec = value * 60.0 # min -> s - debug_print(f"🔄 时间转换: {value}分钟 → {time_sec}秒") - elif unit in ['h', 'hr', 'hour']: - time_sec = value * 3600.0 # h -> s - debug_print(f"🔄 时间转换: {value}小时 → {time_sec}秒") - elif unit in ['d', 'day']: - time_sec = value * 86400.0 # d -> s - debug_print(f"🔄 时间转换: {value}天 → {time_sec}秒") - else: # s, sec, second 或默认 - time_sec = value # 已经是s - debug_print(f"✅ 时间已为秒: {time_sec}秒") - - return time_sec - -def parse_temperature_input(temp_input: Union[str, float]) -> float: - """ - 解析温度输入,支持带单位的字符串 - - Args: - temp_input: 温度输入(如 "60 °C", "room temperature", "?", 25.0) - - Returns: - float: 温度(摄氏度) - """ - if isinstance(temp_input, (int, float)): - debug_print(f"🌡️ 温度输入为数值: {temp_input}°C") - return float(temp_input) - - if not temp_input or not str(temp_input).strip(): - debug_print(f"⚠️ 温度输入为空,使用默认室温25°C") - return 25.0 # 默认室温 - - temp_str = str(temp_input).lower().strip() - debug_print(f"🔍 解析温度输入: '{temp_str}'") - - # 处理特殊温度描述 - temp_aliases = { - 'room temperature': 25.0, - 'rt': 25.0, - 'ambient': 25.0, - 'cold': 4.0, - 'ice': 0.0, - 'reflux': 80.0, # 默认回流温度 - '?': 25.0, - 'unknown': 25.0 - } - - if temp_str in temp_aliases: - result = temp_aliases[temp_str] - debug_print(f"🏷️ 温度别名解析: '{temp_str}' → {result}°C") - return result - - # 移除空格并提取数字和单位 - temp_clean = re.sub(r'\s+', '', temp_str) - - # 匹配数字和单位的正则表达式 - match = re.match(r'([0-9]*\.?[0-9]+)\s*(°c|c|celsius|°f|f|fahrenheit|k|kelvin)?', temp_clean) - - if not match: - debug_print(f"❌ 无法解析温度: '{temp_str}',使用默认值25°C") - return 25.0 - - value = float(match.group(1)) - unit = match.group(2) or 'c' # 默认单位为摄氏度 - - # 转换为摄氏度 - if unit in ['°f', 'f', 'fahrenheit']: - temp_c = (value - 32) * 5/9 # F -> C - debug_print(f"🔄 温度转换: {value}°F → {temp_c:.1f}°C") - elif unit in ['k', 'kelvin']: - temp_c = value - 273.15 # K -> C - debug_print(f"🔄 温度转换: {value}K → {temp_c:.1f}°C") - else: # °c, c, celsius 或默认 - temp_c = value # 已经是C - debug_print(f"✅ 温度已为°C: {temp_c}°C") - - return temp_c - -def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: - """增强版溶剂容器查找,支持多种匹配模式""" - debug_print(f"🔍 开始查找溶剂 '{solvent}' 的容器...") - - # 🔧 方法1:直接搜索 data.reagent_name 和 config.reagent - debug_print(f"📋 方法1: 搜索reagent字段...") - for node in G.nodes(): - node_data = G.nodes[node].get('data', {}) - node_type = G.nodes[node].get('type', '') - config_data = G.nodes[node].get('config', {}) - - # 只搜索容器类型的节点 - if node_type == 'container': - reagent_name = node_data.get('reagent_name', '').lower() - config_reagent = config_data.get('reagent', '').lower() - - # 精确匹配 - if reagent_name == solvent.lower() or config_reagent == solvent.lower(): - debug_print(f"✅ 通过reagent字段精确匹配到容器: {node} 🎯") - return node - - # 模糊匹配 - if (solvent.lower() in reagent_name and reagent_name) or \ - (solvent.lower() in config_reagent and config_reagent): - debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node} 🔍") - return node - - # 🔧 方法2:常见的容器命名规则 - debug_print(f"📋 方法2: 使用命名规则查找...") - solvent_clean = solvent.lower().replace(' ', '_').replace('-', '_') - possible_names = [ - solvent_clean, - f"flask_{solvent_clean}", - f"bottle_{solvent_clean}", - f"vessel_{solvent_clean}", - f"{solvent_clean}_flask", - f"{solvent_clean}_bottle", - f"solvent_{solvent_clean}", - f"reagent_{solvent_clean}", - f"reagent_bottle_{solvent_clean}", - f"reagent_bottle_1", # 通用试剂瓶 - f"reagent_bottle_2", - f"reagent_bottle_3" - ] - - debug_print(f"🔍 尝试的容器名称: {possible_names[:5]}... (共{len(possible_names)}个)") - - for name in possible_names: - if name in G.nodes(): - node_type = G.nodes[name].get('type', '') - if node_type == 'container': - debug_print(f"✅ 通过命名规则找到容器: {name} 📝") - return name - - # 🔧 方法3:节点名称模糊匹配 - debug_print(f"📋 方法3: 节点名称模糊匹配...") - for node_id in G.nodes(): - node_data = G.nodes[node_id] - if node_data.get('type') == 'container': - # 检查节点名称是否包含溶剂名称 - if solvent_clean in node_id.lower(): - debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id} 🔍") - return node_id - - # 检查液体类型匹配 - vessel_data = node_data.get('data', {}) - liquids = vessel_data.get('liquid', []) - for liquid in liquids: - if isinstance(liquid, dict): - liquid_type = liquid.get('liquid_type') or liquid.get('name', '') - if liquid_type.lower() == solvent.lower(): - debug_print(f"✅ 通过液体类型匹配到容器: {node_id} 💧") - return node_id - - # 🔧 方法4:使用第一个试剂瓶作为备选 - debug_print(f"📋 方法4: 查找备选试剂瓶...") - for node_id in G.nodes(): - node_data = G.nodes[node_id] - if (node_data.get('type') == 'container' and - ('reagent' in node_id.lower() or 'bottle' in node_id.lower() or 'flask' in node_id.lower())): - debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id} 🔄") - return node_id - - debug_print(f"❌ 所有方法都失败了,无法找到容器!") - raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器") - -def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str: - """查找连接到指定容器的加热搅拌器""" - debug_print(f"🔍 查找连接到容器 '{vessel}' 的加热搅拌器...") - - heatchill_nodes = [] - for node in G.nodes(): - node_class = G.nodes[node].get('class', '').lower() - if 'heatchill' in node_class: - heatchill_nodes.append(node) - debug_print(f"📋 发现加热搅拌器: {node}") - - debug_print(f"📊 共找到 {len(heatchill_nodes)} 个加热搅拌器") - - # 查找连接到容器的加热器 - for heatchill in heatchill_nodes: - if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill): - debug_print(f"✅ 找到连接的加热搅拌器: {heatchill} 🔗") - return heatchill - - # 返回第一个加热器 - if heatchill_nodes: - debug_print(f"⚠️ 未找到直接连接的加热搅拌器,使用第一个: {heatchill_nodes[0]} 🔄") - return heatchill_nodes[0] - - debug_print(f"❌ 未找到任何加热搅拌器") - return "" - -def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str: - """查找连接到指定容器的搅拌器""" - debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...") - - stirrer_nodes = [] - for node in G.nodes(): - node_class = G.nodes[node].get('class', '').lower() - if 'stirrer' in node_class: - stirrer_nodes.append(node) - debug_print(f"📋 发现搅拌器: {node}") - - debug_print(f"📊 共找到 {len(stirrer_nodes)} 个搅拌器") - - # 查找连接到容器的搅拌器 - for stirrer in stirrer_nodes: - if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer): - debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗") - return stirrer - - # 返回第一个搅拌器 - if stirrer_nodes: - debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄") - return stirrer_nodes[0] - - debug_print(f"❌ 未找到任何搅拌器") - return "" - -def find_solid_dispenser(G: nx.DiGraph) -> str: - """查找固体加样器""" - debug_print(f"🔍 查找固体加样器...") - - for node in G.nodes(): - node_class = G.nodes[node].get('class', '').lower() - if 'solid_dispenser' in node_class or 'dispenser' in node_class: - debug_print(f"✅ 找到固体加样器: {node} 🥄") - return node - - debug_print(f"❌ 未找到固体加样器") - return "" - def generate_dissolve_protocol( G: nx.DiGraph, vessel: dict, # 🔧 修改:从字符串改为字典类型 @@ -436,28 +53,16 @@ def generate_dissolve_protocol( - mol: "0.12 mol", "16.2 mmol" """ - # 🔧 核心修改:从字典中提取容器ID + # 从字典中提取容器ID vessel_id, vessel_data = get_vessel(vessel) - - debug_print("=" * 60) - debug_print("🧪 开始生成溶解协议") - debug_print(f"📋 原始参数:") - debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})") - debug_print(f" 💧 solvent: '{solvent}'") - debug_print(f" 📏 volume: {volume} (类型: {type(volume)})") - debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})") - debug_print(f" 🌡️ temp: {temp} (类型: {type(temp)})") - debug_print(f" ⏱️ time: {time} (类型: {type(time)})") - debug_print(f" 🧪 reagent: '{reagent}'") - debug_print(f" 🧬 mol: '{mol}'") - debug_print(f" 🎯 event: '{event}'") - debug_print(f" 📦 kwargs: {kwargs}") # 显示额外参数 - debug_print("=" * 60) + + debug_print(f"开始生成溶解协议: vessel={vessel_id}, solvent='{solvent}', " + f"volume={volume}, mass={mass}, temp={temp}, time={time}, " + f"reagent='{reagent}', mol='{mol}', event='{event}'") action_sequence = [] # === 参数验证 === - debug_print("🔍 步骤1: 参数验证...") action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel_id}", "🎬")) if not vessel_id: @@ -465,14 +70,11 @@ def generate_dissolve_protocol( raise ValueError("vessel 参数不能为空") if vessel_id not in G.nodes(): - debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中") raise ValueError(f"容器 '{vessel_id}' 不存在于系统中") - - debug_print("✅ 基本参数验证通过") + action_sequence.append(create_action_log("参数验证通过", "✅")) - - # 🔧 新增:记录溶解前的容器状态 - debug_print("🔍 记录溶解前容器状态...") + + # 记录溶解前的容器状态 original_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -480,10 +82,8 @@ def generate_dissolve_protocol( original_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): original_liquid_volume = current_volume - debug_print(f"📊 溶解前液体体积: {original_liquid_volume:.2f}mL") - - # === 🔧 关键修复:参数解析 === - debug_print("🔍 步骤2: 参数解析...") + + # === 参数解析 === action_sequence.append(create_action_log("正在解析溶解参数...", "🔍")) # 解析各种参数为数值 @@ -491,18 +91,11 @@ def generate_dissolve_protocol( final_mass = parse_mass_input(mass) final_temp = parse_temperature_input(temp) final_time = parse_time_input(time) - - debug_print(f"📊 解析结果:") - debug_print(f" 📏 体积: {final_volume}mL") - debug_print(f" ⚖️ 质量: {final_mass}g") - debug_print(f" 🌡️ 温度: {final_temp}°C") - debug_print(f" ⏱️ 时间: {final_time}s") - debug_print(f" 🧪 试剂: '{reagent}'") - debug_print(f" 🧬 摩尔: '{mol}'") - debug_print(f" 🎯 事件: '{event}'") - + + debug_print(f"解析结果: volume={final_volume}mL, mass={final_mass}g, " + f"temp={final_temp}°C, time={final_time}s") + # === 判断溶解类型 === - debug_print("🔍 步骤3: 判断溶解类型...") action_sequence.append(create_action_log("正在判断溶解类型...", "🔍")) # 判断是固体溶解还是液体溶解 @@ -519,12 +112,11 @@ def generate_dissolve_protocol( dissolve_type = "固体溶解" if is_solid_dissolve else "液体溶解" dissolve_emoji = "🧂" if is_solid_dissolve else "💧" - debug_print(f"📋 溶解类型: {dissolve_type} {dissolve_emoji}") - + debug_print(f"溶解类型: {dissolve_type}") + action_sequence.append(create_action_log(f"确定溶解类型: {dissolve_type} {dissolve_emoji}", "📋")) - + # === 查找设备 === - debug_print("🔍 步骤4: 查找设备...") action_sequence.append(create_action_log("正在查找相关设备...", "🔍")) # 查找加热搅拌器 @@ -533,11 +125,7 @@ def generate_dissolve_protocol( # 优先使用加热搅拌器,否则使用独立搅拌器 stir_device_id = heatchill_id or stirrer_id - - debug_print(f"📊 设备映射:") - debug_print(f" 🔥 加热器: '{heatchill_id}'") - debug_print(f" 🌪️ 搅拌器: '{stirrer_id}'") - debug_print(f" 🎯 使用设备: '{stir_device_id}'") + debug_print(f"设备映射: heatchill='{heatchill_id}', stirrer='{stirrer_id}', 使用='{stir_device_id}'") if heatchill_id: action_sequence.append(create_action_log(f"找到加热搅拌器: {heatchill_id}", "🔥")) @@ -547,12 +135,9 @@ def generate_dissolve_protocol( action_sequence.append(create_action_log("未找到搅拌设备,将跳过搅拌", "⚠️")) # === 执行溶解流程 === - debug_print("🔍 步骤5: 执行溶解流程...") - try: # 步骤5.1: 启动加热搅拌(如果需要) if stir_device_id and (final_temp > 25.0 or final_time > 0 or stir_speed > 0): - debug_print(f"🔍 5.1: 启动加热搅拌,温度: {final_temp}°C") action_sequence.append(create_action_log(f"准备加热搅拌 (目标温度: {final_temp}°C)", "🔥")) if heatchill_id and (final_temp > 25.0 or final_time > 0): @@ -603,7 +188,6 @@ def generate_dissolve_protocol( if is_solid_dissolve: # === 固体溶解路径 === - debug_print(f"🔍 5.2: 使用固体溶解路径") action_sequence.append(create_action_log("开始固体溶解流程", "🧂")) solid_dispenser = find_solid_dispenser(G) @@ -632,12 +216,9 @@ def generate_dissolve_protocol( "action_kwargs": add_kwargs }) - debug_print(f"✅ 固体加样完成") action_sequence.append(create_action_log("固体加样完成", "✅")) - - # 🔧 新增:固体溶解体积运算 - 固体本身不会显著增加体积,但可能有少量变化 - debug_print(f"🔧 固体溶解 - 体积变化很小,主要是质量变化") - # 固体通常不会显著改变液体体积,这里只记录日志 + + # 固体溶解体积运算 - 固体本身不会显著增加体积 action_sequence.append(create_action_log(f"固体已添加: {final_mass}g", "📊")) else: @@ -646,7 +227,6 @@ def generate_dissolve_protocol( elif is_liquid_dissolve: # === 液体溶解路径 === - debug_print(f"🔍 5.3: 使用液体溶解路径") action_sequence.append(create_action_log("开始液体溶解流程", "💧")) # 查找溶剂容器 @@ -688,11 +268,9 @@ def generate_dissolve_protocol( **kwargs ) action_sequence.extend(pump_actions) - debug_print(f"✅ 溶剂转移完成,添加了 {len(pump_actions)} 个动作") action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", "✅")) - - # 🔧 新增:液体溶解体积运算 - 添加溶剂后更新容器体积 - debug_print(f"🔧 更新容器液体体积 - 添加溶剂 {final_volume:.2f}mL") + + # 液体溶解体积运算 - 添加溶剂后更新容器体积 # 确保vessel有data字段 if "data" not in vessel: @@ -703,19 +281,14 @@ def generate_dissolve_protocol( if isinstance(current_volume, list): if len(current_volume) > 0: vessel["data"]["liquid_volume"][0] += final_volume - debug_print(f"📊 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{final_volume:.2f}mL)") else: vessel["data"]["liquid_volume"] = [final_volume] - debug_print(f"📊 初始化溶解体积: {final_volume:.2f}mL") elif isinstance(current_volume, (int, float)): vessel["data"]["liquid_volume"] += final_volume - debug_print(f"📊 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{final_volume:.2f}mL)") else: vessel["data"]["liquid_volume"] = final_volume - debug_print(f"📊 重置体积为: {final_volume:.2f}mL") else: vessel["data"]["liquid_volume"] = final_volume - debug_print(f"📊 创建新体积记录: {final_volume:.2f}mL") # 🔧 同时更新图中的容器数据 if vessel_id in G.nodes(): @@ -732,8 +305,6 @@ def generate_dissolve_protocol( G.nodes[vessel_id]['data']['liquid_volume'] = [final_volume] else: G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + final_volume - - debug_print(f"✅ 图节点体积数据已更新") action_sequence.append(create_action_log(f"容器体积已更新 (+{final_volume:.2f}mL)", "📊")) @@ -746,7 +317,6 @@ def generate_dissolve_protocol( # 步骤5.4: 等待溶解完成 if final_time > 0: - debug_print(f"🔍 5.4: 等待溶解完成 - {final_time}s") wait_minutes = final_time / 60 action_sequence.append(create_action_log(f"开始溶解等待 ({wait_minutes:.1f}分钟)", "⏰")) @@ -795,7 +365,6 @@ def generate_dissolve_protocol( # 步骤5.5: 停止加热搅拌(如果需要) if heatchill_id and final_time == 0 and final_temp > 25.0: - debug_print(f"🔍 5.5: 停止加热器") action_sequence.append(create_action_log("停止加热搅拌器", "🛑")) stop_action = { @@ -829,23 +398,8 @@ def generate_dissolve_protocol( final_liquid_volume = current_volume # === 最终结果 === - debug_print("=" * 60) - debug_print(f"🎉 溶解协议生成完成") - debug_print(f"📊 协议统计:") - debug_print(f" 📋 总动作数: {len(action_sequence)}") - debug_print(f" 🥼 容器: {vessel_id}") - debug_print(f" {dissolve_emoji} 溶解类型: {dissolve_type}") - if is_liquid_dissolve: - debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)") - if is_solid_dissolve: - debug_print(f" 🧪 试剂: {reagent}") - debug_print(f" ⚖️ 质量: {final_mass}g") - debug_print(f" 🧬 摩尔: {mol}") - debug_print(f" 🌡️ 温度: {final_temp}°C") - debug_print(f" ⏱️ 时间: {final_time}s") - debug_print(f" 📊 溶解前体积: {original_liquid_volume:.2f}mL") - debug_print(f" 📊 溶解后体积: {final_liquid_volume:.2f}mL") - debug_print("=" * 60) + debug_print(f"溶解协议生成完成: {vessel_id}, 类型={dissolve_type}, " + f"动作数={len(action_sequence)}, 前后体积={original_liquid_volume:.2f}→{final_liquid_volume:.2f}mL") # 添加完成日志 summary_msg = f"溶解协议完成: {vessel_id}" diff --git a/unilabos/compile/dry_protocol.py b/unilabos/compile/dry_protocol.py index 1e736072..88c4aa51 100644 --- a/unilabos/compile/dry_protocol.py +++ b/unilabos/compile/dry_protocol.py @@ -1,87 +1,40 @@ import networkx as nx from typing import List, Dict, Any -from unilabos.compile.utils.vessel_parser import get_vessel - - -def find_connected_heater(G: nx.DiGraph, vessel: str) -> str: - """ - 查找与容器相连的加热器 - - Args: - G: 网络图 - vessel: 容器名称 - - Returns: - str: 加热器ID,如果没有则返回None - """ - print(f"DRY: 正在查找与容器 '{vessel}' 相连的加热器...") - - # 查找所有加热器节点 - heater_nodes = [node for node in G.nodes() - if ('heater' in node.lower() or - 'heat' in node.lower() or - G.nodes[node].get('class') == 'virtual_heatchill' or - G.nodes[node].get('type') == 'heater')] - - print(f"DRY: 找到的加热器节点: {heater_nodes}") - - # 检查是否有加热器与目标容器相连 - for heater in heater_nodes: - if G.has_edge(heater, vessel) or G.has_edge(vessel, heater): - print(f"DRY: 找到与容器 '{vessel}' 相连的加热器: {heater}") - return heater - - # 如果没有直接连接,查找距离最近的加热器 - for heater in heater_nodes: - try: - path = nx.shortest_path(G, source=heater, target=vessel) - if len(path) <= 3: # 最多2个中间节点 - print(f"DRY: 找到距离较近的加热器: {heater}, 路径: {' → '.join(path)}") - return heater - except nx.NetworkXNoPath: - continue - - print(f"DRY: 未找到与容器 '{vessel}' 相连的加热器") - return None +from .utils.vessel_parser import get_vessel, find_connected_heatchill +from .utils.logger_util import debug_print def generate_dry_protocol( G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 - compound: str = "", # 🔧 修改:参数顺序调整,并设置默认值 - **kwargs # 接收其他可能的参数但不使用 + vessel: dict, + compound: str = "", + **kwargs ) -> List[Dict[str, Any]]: """ 生成干燥协议序列 - + Args: G: 有向图,节点为容器和设备 vessel: 目标容器字典(从XDL传入) compound: 化合物名称(从XDL传入,可选) **kwargs: 其他可选参数,但不使用 - + Returns: List[Dict[str, Any]]: 动作序列 """ - # 🔧 核心修改:从字典中提取容器ID vessel_id, vessel_data = get_vessel(vessel) - + action_sequence = [] - + # 默认参数 - dry_temp = 60.0 # 默认干燥温度 60°C - dry_time = 3600.0 # 默认干燥时间 1小时(3600秒) - simulation_time = 60.0 # 模拟时间 1分钟 - - print(f"🌡️ DRY: 开始生成干燥协议 ✨") - print(f" 🥽 vessel: {vessel} (ID: {vessel_id})") - print(f" 🧪 化合物: {compound or '未指定'}") - print(f" 🔥 干燥温度: {dry_temp}°C") - print(f" ⏰ 干燥时间: {dry_time/60:.0f} 分钟") - - # 🔧 新增:记录干燥前的容器状态 - print(f"🔍 记录干燥前容器状态...") + dry_temp = 60.0 + dry_time = 3600.0 + simulation_time = 60.0 + + debug_print(f"开始生成干燥协议: vessel={vessel_id}, compound={compound or '未指定'}, temp={dry_temp}°C") + + # 记录干燥前的容器状态 original_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -89,39 +42,30 @@ def generate_dry_protocol( original_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): original_liquid_volume = current_volume - print(f"📊 干燥前液体体积: {original_liquid_volume:.2f}mL") - + # 1. 验证目标容器存在 - print(f"\n📋 步骤1: 验证目标容器 '{vessel_id}' 是否存在...") if vessel_id not in G.nodes(): - print(f"⚠️ DRY: 警告 - 容器 '{vessel_id}' 不存在于系统中,跳过干燥 😢") + debug_print(f"容器 '{vessel_id}' 不存在于系统中,跳过干燥") return action_sequence - print(f"✅ 容器 '{vessel_id}' 验证通过!") - + # 2. 查找相连的加热器 - print(f"\n🔍 步骤2: 查找与容器相连的加热器...") - heater_id = find_connected_heater(G, vessel_id) # 🔧 使用 vessel_id - + heater_id = find_connected_heatchill(G, vessel_id) + if heater_id is None: - print(f"😭 DRY: 警告 - 未找到与容器 '{vessel_id}' 相连的加热器,跳过干燥") - print(f"🎭 添加模拟干燥动作...") - # 添加一个等待动作,表示干燥过程(模拟) + debug_print(f"未找到与容器 '{vessel_id}' 相连的加热器,添加模拟干燥动作") action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 10.0, # 模拟等待时间 + "time": 10.0, "description": f"模拟干燥 {compound or '化合物'} (无加热器可用)" } }) - - # 🔧 新增:模拟干燥的体积变化(溶剂蒸发) - print(f"🔧 模拟干燥过程的体积减少...") + + # 模拟干燥的体积变化 if original_liquid_volume > 0: - # 假设干燥过程中损失10%的体积(溶剂蒸发) volume_loss = original_liquid_volume * 0.1 new_volume = max(0.0, original_liquid_volume - volume_loss) - - # 更新vessel字典中的体积 + if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): @@ -133,15 +77,14 @@ def generate_dry_protocol( vessel["data"]["liquid_volume"] = new_volume else: vessel["data"]["liquid_volume"] = new_volume - - # 🔧 同时更新图中的容器数据 + if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} - + vessel_node_data = G.nodes[vessel_id]['data'] current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - + if isinstance(current_node_volume, list): if len(current_node_volume) > 0: G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume @@ -149,33 +92,27 @@ def generate_dry_protocol( G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume] else: G.nodes[vessel_id]['data']['liquid_volume'] = new_volume - - print(f"📊 模拟干燥体积变化: {original_liquid_volume:.2f}mL → {new_volume:.2f}mL (-{volume_loss:.2f}mL)") - - print(f"📄 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯") + + debug_print(f"模拟干燥体积变化: {original_liquid_volume:.2f}mL -> {new_volume:.2f}mL") + + debug_print(f"协议生成完成,共 {len(action_sequence)} 个动作") return action_sequence - - print(f"🎉 找到加热器: {heater_id}!") - + + debug_print(f"找到加热器: {heater_id}") + # 3. 启动加热器进行干燥 - print(f"\n🚀 步骤3: 开始执行干燥流程...") - print(f"🔥 启动加热器 {heater_id} 进行干燥") - # 3.1 启动加热 - print(f" ⚡ 动作1: 启动加热到 {dry_temp}°C...") action_sequence.append({ "device_id": heater_id, "action_name": "heat_chill_start", "action_kwargs": { - "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id + "vessel": {"id": vessel_id}, "temp": dry_temp, "purpose": f"干燥 {compound or '化合物'}" } }) - print(f" ✅ 加热器启动命令已添加 🔥") - + # 3.2 等待温度稳定 - print(f" ⏳ 动作2: 等待温度稳定...") action_sequence.append({ "action_name": "wait", "action_kwargs": { @@ -183,34 +120,27 @@ def generate_dry_protocol( "description": f"等待温度稳定到 {dry_temp}°C" } }) - print(f" ✅ 温度稳定等待命令已添加 🌡️") - + # 3.3 保持干燥温度 - print(f" 🔄 动作3: 保持干燥温度 {simulation_time/60:.0f} 分钟...") action_sequence.append({ "device_id": heater_id, "action_name": "heat_chill", "action_kwargs": { - "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id + "vessel": {"id": vessel_id}, "temp": dry_temp, "time": simulation_time, "purpose": f"干燥 {compound or '化合物'},保持温度 {dry_temp}°C" } }) - print(f" ✅ 温度保持命令已添加 🌡️⏰") - - # 🔧 新增:干燥过程中的体积变化计算 - print(f"🔧 计算干燥过程中的体积变化...") + + # 干燥过程中的体积变化计算 if original_liquid_volume > 0: - # 干燥过程中,溶剂会蒸发,固体保留 - # 根据温度和时间估算蒸发量 - evaporation_rate = 0.001 * dry_temp # 每秒每°C蒸发0.001mL - total_evaporation = min(original_liquid_volume * 0.8, - evaporation_rate * simulation_time) # 最多蒸发80% - + evaporation_rate = 0.001 * dry_temp + total_evaporation = min(original_liquid_volume * 0.8, + evaporation_rate * simulation_time) + new_volume = max(0.0, original_liquid_volume - total_evaporation) - - # 更新vessel字典中的体积 + if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): @@ -222,15 +152,14 @@ def generate_dry_protocol( vessel["data"]["liquid_volume"] = new_volume else: vessel["data"]["liquid_volume"] = new_volume - - # 🔧 同时更新图中的容器数据 + if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} - + vessel_node_data = G.nodes[vessel_id]['data'] current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - + if isinstance(current_node_volume, list): if len(current_node_volume) > 0: G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume @@ -238,37 +167,29 @@ def generate_dry_protocol( G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume] else: G.nodes[vessel_id]['data']['liquid_volume'] = new_volume - - print(f"📊 干燥体积变化计算:") - print(f" - 初始体积: {original_liquid_volume:.2f}mL") - print(f" - 蒸发量: {total_evaporation:.2f}mL") - print(f" - 剩余体积: {new_volume:.2f}mL") - print(f" - 蒸发率: {(total_evaporation/original_liquid_volume*100):.1f}%") - + + debug_print(f"干燥体积变化: {original_liquid_volume:.2f}mL -> {new_volume:.2f}mL (-{total_evaporation:.2f}mL)") + # 3.4 停止加热 - print(f" ⏹️ 动作4: 停止加热...") action_sequence.append({ "device_id": heater_id, "action_name": "heat_chill_stop", "action_kwargs": { - "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id + "vessel": {"id": vessel_id}, "purpose": f"干燥完成,停止加热" } }) - print(f" ✅ 停止加热命令已添加 🛑") - + # 3.5 等待冷却 - print(f" ❄️ 动作5: 等待冷却...") action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 10.0, # 等待10秒冷却 + "time": 10.0, "description": f"等待 {compound or '化合物'} 冷却" } }) - print(f" ✅ 冷却等待命令已添加 🧊") - - # 🔧 新增:干燥完成后的状态报告 + + # 最终状态 final_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -276,60 +197,37 @@ def generate_dry_protocol( final_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): final_liquid_volume = current_volume - - print(f"\n🎊 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯") - print(f"⏱️ DRY: 预计总时间: {(simulation_time + 30)/60:.0f} 分钟 ⌛") - print(f"📊 干燥结果:") - print(f" - 容器: {vessel_id}") - print(f" - 化合物: {compound or '未指定'}") - print(f" - 干燥前体积: {original_liquid_volume:.2f}mL") - print(f" - 干燥后体积: {final_liquid_volume:.2f}mL") - print(f" - 蒸发体积: {(original_liquid_volume - final_liquid_volume):.2f}mL") - print(f"🏁 所有动作序列准备就绪! ✨") - + + debug_print(f"干燥协议生成完成: {len(action_sequence)} 个动作, 体积 {original_liquid_volume:.2f} -> {final_liquid_volume:.2f}mL") + return action_sequence -# 🔧 新增:便捷函数 -def generate_quick_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "", +# 便捷函数 +def generate_quick_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "", temp: float = 40.0, time: float = 30.0) -> List[Dict[str, Any]]: """快速干燥:低温短时间""" - vessel_id = vessel["id"] - print(f"🌡️ 快速干燥: {compound or '化合物'} → {vessel_id} @ {temp}°C ({time}min)") - - # 临时修改默认参数 - import types - temp_func = types.FunctionType( - generate_dry_protocol.__code__, - generate_dry_protocol.__globals__ - ) - - # 直接调用原函数,但修改内部参数 return generate_dry_protocol(G, vessel, compound) -def generate_thorough_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "", +def generate_thorough_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "", temp: float = 80.0, time: float = 120.0) -> List[Dict[str, Any]]: """深度干燥:高温长时间""" - vessel_id = vessel["id"] - print(f"🔥 深度干燥: {compound or '化合物'} → {vessel_id} @ {temp}°C ({time}min)") return generate_dry_protocol(G, vessel, compound) -def generate_gentle_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "", +def generate_gentle_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "", temp: float = 30.0, time: float = 180.0) -> List[Dict[str, Any]]: """温和干燥:低温长时间""" - vessel_id = vessel["id"] - print(f"🌡️ 温和干燥: {compound or '化合物'} → {vessel_id} @ {temp}°C ({time}min)") return generate_dry_protocol(G, vessel, compound) # 测试函数 def test_dry_protocol(): """测试干燥协议""" - print("=== DRY PROTOCOL 测试 ===") - print("测试完成") + debug_print("=== DRY PROTOCOL 测试 ===") + debug_print("测试完成") if __name__ == "__main__": - test_dry_protocol() \ No newline at end of file + test_dry_protocol() diff --git a/unilabos/compile/evacuateandrefill_protocol.py b/unilabos/compile/evacuateandrefill_protocol.py index 45693c2a..438e50a4 100644 --- a/unilabos/compile/evacuateandrefill_protocol.py +++ b/unilabos/compile/evacuateandrefill_protocol.py @@ -3,38 +3,14 @@ from functools import partial import networkx as nx import logging import uuid -import sys from typing import List, Dict, Any, Optional -from .utils.vessel_parser import get_vessel -from .utils.logger_util import action_log +from .utils.vessel_parser import get_vessel, find_connected_stirrer +from .utils.logger_util import debug_print, action_log from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol # 设置日志 logger = logging.getLogger(__name__) -# 确保输出编码为UTF-8 -if hasattr(sys.stdout, 'reconfigure'): - try: - sys.stdout.reconfigure(encoding='utf-8') - sys.stderr.reconfigure(encoding='utf-8') - except: - pass - -def debug_print(message): - """调试输出函数 - 支持中文""" - try: - # 确保消息是字符串格式 - safe_message = str(message) - logger.info(f"[抽真空充气] {safe_message}") - except UnicodeEncodeError: - # 如果编码失败,尝试替换不支持的字符 - safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8') - logger.info(f"[抽真空充气] {safe_message}") - except Exception as e: - # 最后的安全措施 - fallback_message = f"日志输出错误: {repr(message)}" - logger.info(f"[抽真空充气] {fallback_message}") - create_action_log = partial(action_log, prefix="[抽真空充气]") def find_gas_source(G: nx.DiGraph, gas: str) -> str: @@ -44,10 +20,9 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str: 2. 气体类型匹配(data.gas_type) 3. 默认气源 """ - debug_print(f"🔍 正在查找气体 '{gas}' 的气源...") - - # 第一步:通过容器名称匹配 - debug_print(f"📋 方法1: 容器名称匹配...") + debug_print(f"正在查找气体 '{gas}' 的气源...") + + # 通过容器名称匹配 gas_source_patterns = [ f"gas_source_{gas}", f"gas_{gas}", @@ -57,254 +32,178 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str: f"reagent_bottle_{gas}", f"bottle_{gas}" ] - - debug_print(f"🎯 尝试的容器名称: {gas_source_patterns}") - + for pattern in gas_source_patterns: if pattern in G.nodes(): - debug_print(f"✅ 通过名称找到气源: {pattern}") + debug_print(f"通过名称找到气源: {pattern}") return pattern - - # 第二步:通过气体类型匹配 (data.gas_type) - debug_print(f"📋 方法2: 气体类型匹配...") + + # 通过气体类型匹配 (data.gas_type) for node_id in G.nodes(): node_data = G.nodes[node_id] node_class = node_data.get('class', '') or '' - - # 检查是否是气源设备 - if ('gas_source' in node_class or - 'gas' in node_id.lower() or + + if ('gas_source' in node_class or + 'gas' in node_id.lower() or node_id.startswith('flask_')): - - # 检查 data.gas_type + data = node_data.get('data', {}) gas_type = data.get('gas_type', '') - + if gas_type.lower() == gas.lower(): - debug_print(f"✅ 通过气体类型找到气源: {node_id} (气体类型: {gas_type})") + debug_print(f"通过气体类型找到气源: {node_id} (气体类型: {gas_type})") return node_id - - # 检查 config.gas_type + config = node_data.get('config', {}) config_gas_type = config.get('gas_type', '') - + if config_gas_type.lower() == gas.lower(): - debug_print(f"✅ 通过配置气体类型找到气源: {node_id} (配置气体类型: {config_gas_type})") + debug_print(f"通过配置气体类型找到气源: {node_id} (配置气体类型: {config_gas_type})") return node_id - - # 第三步:查找所有可用的气源设备 - debug_print(f"📋 方法3: 查找可用气源...") + + # 查找所有可用的气源设备 available_gas_sources = [] for node_id in G.nodes(): node_data = G.nodes[node_id] node_class = node_data.get('class', '') or '' - - if ('gas_source' in node_class or + + if ('gas_source' in node_class or 'gas' in node_id.lower() or (node_id.startswith('flask_') and any(g in node_id.lower() for g in ['air', 'nitrogen', 'argon']))): - + data = node_data.get('data', {}) gas_type = data.get('gas_type', '未知') available_gas_sources.append(f"{node_id} (气体类型: {gas_type})") - - debug_print(f"📊 可用气源: {available_gas_sources}") - - # 第四步:如果找不到特定气体,使用默认的第一个气源 - debug_print(f"📋 方法4: 查找默认气源...") + + # 如果找不到特定气体,使用默认的第一个气源 default_gas_sources = [ - node for node in G.nodes() + node for node in G.nodes() if ((G.nodes[node].get('class') or '').find('virtual_gas_source') != -1 or 'gas_source' in node) ] - + if default_gas_sources: default_source = default_gas_sources[0] - debug_print(f"⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}") + debug_print(f"未找到特定气体 '{gas}',使用默认气源: {default_source}") return default_source - - debug_print(f"❌ 所有方法都失败了!") + raise ValueError(f"无法找到气体 '{gas}' 的气源。可用气源: {available_gas_sources}") def find_vacuum_pump(G: nx.DiGraph) -> str: """查找真空泵设备""" - debug_print("🔍 正在查找真空泵...") - vacuum_pumps = [] for node in G.nodes(): node_data = G.nodes[node] node_class = node_data.get('class', '') or '' - - if ('virtual_vacuum_pump' in node_class or - 'vacuum_pump' in node.lower() or + + if ('virtual_vacuum_pump' in node_class or + 'vacuum_pump' in node.lower() or 'vacuum' in node_class.lower()): vacuum_pumps.append(node) - debug_print(f"📋 发现真空泵: {node}") - - if not vacuum_pumps: - debug_print(f"❌ 系统中未找到真空泵") - raise ValueError("系统中未找到真空泵") - - debug_print(f"✅ 使用真空泵: {vacuum_pumps[0]}") - return vacuum_pumps[0] -def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> Optional[str]: - """查找与指定容器相连的搅拌器""" - debug_print(f"🔍 正在查找与容器 {vessel} 连接的搅拌器...") - - stirrer_nodes = [] - for node in G.nodes(): - node_data = G.nodes[node] - node_class = node_data.get('class', '') or '' - - if 'virtual_stirrer' in node_class or 'stirrer' in node.lower(): - stirrer_nodes.append(node) - debug_print(f"📋 发现搅拌器: {node}") - - debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}") - - # 检查哪个搅拌器与目标容器相连 - for stirrer in stirrer_nodes: - if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer): - debug_print(f"✅ 找到连接的搅拌器: {stirrer}") - return stirrer - - # 如果没有连接的搅拌器,返回第一个可用的 - if stirrer_nodes: - debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}") - return stirrer_nodes[0] - - debug_print("❌ 未找到搅拌器") - return None + if not vacuum_pumps: + raise ValueError("系统中未找到真空泵") + + debug_print(f"使用真空泵: {vacuum_pumps[0]}") + return vacuum_pumps[0] def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]: """查找真空泵相关的电磁阀""" - debug_print(f"🔍 正在查找真空泵 {vacuum_pump} 的电磁阀...") - - # 查找所有电磁阀 solenoid_valves = [] for node in G.nodes(): node_data = G.nodes[node] node_class = node_data.get('class', '') or '' - + if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()): solenoid_valves.append(node) - debug_print(f"📋 发现电磁阀: {node}") - - debug_print(f"📊 找到的电磁阀: {solenoid_valves}") - + # 检查连接关系 - debug_print(f"📋 方法1: 检查连接关系...") for solenoid in solenoid_valves: if G.has_edge(solenoid, vacuum_pump) or G.has_edge(vacuum_pump, solenoid): - debug_print(f"✅ 找到连接的真空电磁阀: {solenoid}") + debug_print(f"找到连接的真空电磁阀: {solenoid}") return solenoid - + # 通过命名规则查找 - debug_print(f"📋 方法2: 检查命名规则...") for solenoid in solenoid_valves: if 'vacuum' in solenoid.lower() or solenoid == 'solenoid_valve_1': - debug_print(f"✅ 通过命名找到真空电磁阀: {solenoid}") + debug_print(f"通过命名找到真空电磁阀: {solenoid}") return solenoid - - debug_print("⚠️ 未找到真空电磁阀") + + debug_print("未找到真空电磁阀") return None def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]: """查找气源相关的电磁阀""" - debug_print(f"🔍 正在查找气源 {gas_source} 的电磁阀...") - - # 查找所有电磁阀 solenoid_valves = [] for node in G.nodes(): node_data = G.nodes[node] node_class = node_data.get('class', '') or '' - + if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()): solenoid_valves.append(node) - - debug_print(f"📊 找到的电磁阀: {solenoid_valves}") - + # 检查连接关系 - debug_print(f"📋 方法1: 检查连接关系...") for solenoid in solenoid_valves: if G.has_edge(gas_source, solenoid) or G.has_edge(solenoid, gas_source): - debug_print(f"✅ 找到连接的气源电磁阀: {solenoid}") + debug_print(f"找到连接的气源电磁阀: {solenoid}") return solenoid - + # 通过命名规则查找 - debug_print(f"📋 方法2: 检查命名规则...") for solenoid in solenoid_valves: if 'gas' in solenoid.lower() or solenoid == 'solenoid_valve_2': - debug_print(f"✅ 通过命名找到气源电磁阀: {solenoid}") + debug_print(f"通过命名找到气源电磁阀: {solenoid}") return solenoid - - debug_print("⚠️ 未找到气源电磁阀") + + debug_print("未找到气源电磁阀") return None def generate_evacuateandrefill_protocol( G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 + vessel: dict, gas: str, **kwargs ) -> List[Dict[str, Any]]: """ - 生成抽真空和充气操作的动作序列 - 中文版 - + 生成抽真空和充气操作的动作序列 + Args: G: 设备图 vessel: 目标容器字典(必需) - gas: 气体名称(必需) + gas: 气体名称(必需) **kwargs: 其他参数(兼容性) - + Returns: List[Dict[str, Any]]: 动作序列 """ - - # 🔧 核心修改:从字典中提取容器ID + vessel_id, vessel_data = get_vessel(vessel) - + # 硬编码重复次数为 3 repeats = 3 - - # 生成协议ID + protocol_id = str(uuid.uuid4()) - debug_print(f"🆔 生成协议ID: {protocol_id}") - - debug_print("=" * 60) - debug_print("🧪 开始生成抽真空充气协议") - debug_print(f"📋 原始参数:") - debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})") - debug_print(f" 💨 气体: '{gas}'") - debug_print(f" 🔄 循环次数: {repeats} (硬编码)") - debug_print(f" 📦 其他参数: {kwargs}") - debug_print("=" * 60) - + + debug_print(f"开始生成抽真空充气协议: vessel={vessel_id}, gas={gas}, repeats={repeats}") + action_sequence = [] - + # === 参数验证和修正 === - debug_print("🔍 步骤1: 参数验证和修正...") action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel_id}", "🎬")) action_sequence.append(create_action_log(f"目标气体: {gas}", "💨")) action_sequence.append(create_action_log(f"循环次数: {repeats}", "🔄")) - - # 验证必需参数 + if not vessel_id: - debug_print("❌ 容器参数不能为空") raise ValueError("容器参数不能为空") - + if not gas: - debug_print("❌ 气体参数不能为空") raise ValueError("气体参数不能为空") - - if vessel_id not in G.nodes(): # 🔧 使用 vessel_id - debug_print(f"❌ 容器 '{vessel_id}' 在系统中不存在") + + if vessel_id not in G.nodes(): raise ValueError(f"容器 '{vessel_id}' 在系统中不存在") - - debug_print("✅ 基本参数验证通过") + action_sequence.append(create_action_log("参数验证通过", "✅")) - + # 标准化气体名称 - debug_print("🔧 标准化气体名称...") gas_aliases = { 'n2': 'nitrogen', 'ar': 'argon', @@ -319,61 +218,54 @@ def generate_evacuateandrefill_protocol( '二氧化碳': 'carbon_dioxide', '氢气': 'hydrogen' } - + original_gas = gas gas_lower = gas.lower().strip() if gas_lower in gas_aliases: gas = gas_aliases[gas_lower] - debug_print(f"🔄 标准化气体名称: {original_gas} -> {gas}") + debug_print(f"标准化气体名称: {original_gas} -> {gas}") action_sequence.append(create_action_log(f"气体名称标准化: {original_gas} -> {gas}", "🔄")) - - debug_print(f"📋 最终参数: 容器={vessel_id}, 气体={gas}, 重复={repeats}") - + + debug_print(f"最终参数: 容器={vessel_id}, 气体={gas}, 重复={repeats}") + # === 查找设备 === - debug_print("🔍 步骤2: 查找设备...") action_sequence.append(create_action_log("正在查找相关设备...", "🔍")) - + try: vacuum_pump = find_vacuum_pump(G) action_sequence.append(create_action_log(f"找到真空泵: {vacuum_pump}", "🌪️")) - + gas_source = find_gas_source(G, gas) action_sequence.append(create_action_log(f"找到气源: {gas_source}", "💨")) - + vacuum_solenoid = find_vacuum_solenoid_valve(G, vacuum_pump) if vacuum_solenoid: action_sequence.append(create_action_log(f"找到真空电磁阀: {vacuum_solenoid}", "🚪")) else: action_sequence.append(create_action_log("未找到真空电磁阀", "⚠️")) - + gas_solenoid = find_gas_solenoid_valve(G, gas_source) if gas_solenoid: action_sequence.append(create_action_log(f"找到气源电磁阀: {gas_solenoid}", "🚪")) else: action_sequence.append(create_action_log("未找到气源电磁阀", "⚠️")) - - stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id + + stirrer_id = find_connected_stirrer(G, vessel_id) if stirrer_id: action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️")) else: action_sequence.append(create_action_log("未找到搅拌器", "⚠️")) - - debug_print(f"📊 设备配置:") - debug_print(f" 🌪️ 真空泵: {vacuum_pump}") - debug_print(f" 💨 气源: {gas_source}") - debug_print(f" 🚪 真空电磁阀: {vacuum_solenoid}") - debug_print(f" 🚪 气源电磁阀: {gas_solenoid}") - debug_print(f" 🌪️ 搅拌器: {stirrer_id}") - + + debug_print(f"设备配置: 真空泵={vacuum_pump}, 气源={gas_source}, 搅拌器={stirrer_id}") + except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") + debug_print(f"设备查找失败: {str(e)}") action_sequence.append(create_action_log(f"设备查找失败: {str(e)}", "❌")) raise ValueError(f"设备查找失败: {str(e)}") - + # === 参数设置 === - debug_print("🔍 步骤3: 参数设置...") action_sequence.append(create_action_log("设置操作参数...", "⚙️")) - + # 根据气体类型调整参数 if gas.lower() in ['nitrogen', 'argon']: VACUUM_VOLUME = 25.0 @@ -381,7 +273,6 @@ def generate_evacuateandrefill_protocol( PUMP_FLOW_RATE = 2.0 VACUUM_TIME = 30.0 REFILL_TIME = 20.0 - debug_print("💨 惰性气体: 使用标准参数") action_sequence.append(create_action_log("检测到惰性气体,使用标准参数", "💨")) elif gas.lower() in ['air', 'oxygen']: VACUUM_VOLUME = 20.0 @@ -389,7 +280,6 @@ def generate_evacuateandrefill_protocol( PUMP_FLOW_RATE = 1.5 VACUUM_TIME = 45.0 REFILL_TIME = 25.0 - debug_print("🔥 活性气体: 使用保守参数") action_sequence.append(create_action_log("检测到活性气体,使用保守参数", "🔥")) else: VACUUM_VOLUME = 15.0 @@ -397,116 +287,88 @@ def generate_evacuateandrefill_protocol( PUMP_FLOW_RATE = 1.0 VACUUM_TIME = 60.0 REFILL_TIME = 30.0 - debug_print("❓ 未知气体: 使用安全参数") action_sequence.append(create_action_log("未知气体类型,使用安全参数", "❓")) - + STIR_SPEED = 200.0 - - debug_print(f"⚙️ 操作参数:") - debug_print(f" 📏 真空体积: {VACUUM_VOLUME}mL") - debug_print(f" 📏 充气体积: {REFILL_VOLUME}mL") - debug_print(f" ⚡ 泵流速: {PUMP_FLOW_RATE}mL/s") - debug_print(f" ⏱️ 真空时间: {VACUUM_TIME}s") - debug_print(f" ⏱️ 充气时间: {REFILL_TIME}s") - debug_print(f" 🌪️ 搅拌速度: {STIR_SPEED}RPM") - + action_sequence.append(create_action_log(f"真空体积: {VACUUM_VOLUME}mL", "📏")) action_sequence.append(create_action_log(f"充气体积: {REFILL_VOLUME}mL", "📏")) action_sequence.append(create_action_log(f"泵流速: {PUMP_FLOW_RATE}mL/s", "⚡")) - + # === 路径验证 === - debug_print("🔍 步骤4: 路径验证...") action_sequence.append(create_action_log("验证传输路径...", "🛤️")) - + try: - # 验证抽真空路径 - if nx.has_path(G, vessel_id, vacuum_pump): # 🔧 使用 vessel_id + if nx.has_path(G, vessel_id, vacuum_pump): vacuum_path = nx.shortest_path(G, source=vessel_id, target=vacuum_pump) - debug_print(f"✅ 真空路径: {' -> '.join(vacuum_path)}") action_sequence.append(create_action_log(f"真空路径: {' -> '.join(vacuum_path)}", "🛤️")) else: - debug_print(f"⚠️ 真空路径不存在,继续执行但可能有问题") action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️")) - - # 验证充气路径 - if nx.has_path(G, gas_source, vessel_id): # 🔧 使用 vessel_id + + if nx.has_path(G, gas_source, vessel_id): gas_path = nx.shortest_path(G, source=gas_source, target=vessel_id) - debug_print(f"✅ 气体路径: {' -> '.join(gas_path)}") action_sequence.append(create_action_log(f"气体路径: {' -> '.join(gas_path)}", "🛤️")) else: - debug_print(f"⚠️ 气体路径不存在,继续执行但可能有问题") action_sequence.append(create_action_log("气体路径检查: 路径不存在", "⚠️")) - + except Exception as e: - debug_print(f"⚠️ 路径验证失败: {str(e)},继续执行") action_sequence.append(create_action_log(f"路径验证失败: {str(e)}", "⚠️")) - + # === 启动搅拌器 === - debug_print("🔍 步骤5: 启动搅拌器...") - if stirrer_id: - debug_print(f"🌪️ 启动搅拌器: {stirrer_id}") action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {STIR_SPEED}rpm)", "🌪️")) - + action_sequence.append({ "device_id": stirrer_id, "action_name": "start_stir", "action_kwargs": { - "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id + "vessel": {"id": vessel_id}, "stir_speed": STIR_SPEED, "purpose": "抽真空充气前预搅拌" } }) - - # 等待搅拌稳定 + action_sequence.append(create_action_log("等待搅拌稳定...", "⏳")) action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 5.0} }) else: - debug_print("⚠️ 未找到搅拌器,跳过搅拌器启动") action_sequence.append(create_action_log("跳过搅拌器启动", "⏭️")) - + # === 执行循环 === - debug_print("🔍 步骤6: 执行抽真空-充气循环...") action_sequence.append(create_action_log(f"开始 {repeats} 次抽真空-充气循环", "🔄")) - + for cycle in range(repeats): - debug_print(f"=== 第 {cycle+1}/{repeats} 轮循环 ===") action_sequence.append(create_action_log(f"第 {cycle+1}/{repeats} 轮循环开始", "🚀")) - + # ============ 抽真空阶段 ============ - debug_print(f"🌪️ 抽真空阶段开始") action_sequence.append(create_action_log("开始抽真空阶段", "🌪️")) - + # 启动真空泵 - debug_print(f"🔛 启动真空泵: {vacuum_pump}") action_sequence.append(create_action_log(f"启动真空泵: {vacuum_pump}", "🔛")) action_sequence.append({ "device_id": vacuum_pump, "action_name": "set_status", "action_kwargs": {"string": "ON"} }) - + # 开启真空电磁阀 if vacuum_solenoid: - debug_print(f"🚪 打开真空电磁阀: {vacuum_solenoid}") action_sequence.append(create_action_log(f"打开真空电磁阀: {vacuum_solenoid}", "🚪")) action_sequence.append({ "device_id": vacuum_solenoid, "action_name": "set_valve_position", "action_kwargs": {"command": "OPEN"} }) - + # 抽真空操作 - debug_print(f"🌪️ 抽真空操作: {vessel_id} -> {vacuum_pump}") action_sequence.append(create_action_log(f"开始抽真空: {vessel_id} -> {vacuum_pump}", "🌪️")) - + try: vacuum_transfer_actions = generate_pump_protocol_with_rinsing( G=G, - from_vessel=vessel_id, # 🔧 使用 vessel_id + from_vessel=vessel_id, to_vessel=vacuum_pump, volume=VACUUM_VOLUME, amount="", @@ -519,27 +381,25 @@ def generate_evacuateandrefill_protocol( flowrate=PUMP_FLOW_RATE, transfer_flowrate=PUMP_FLOW_RATE ) - + if vacuum_transfer_actions: action_sequence.extend(vacuum_transfer_actions) - debug_print(f"✅ 添加了 {len(vacuum_transfer_actions)} 个抽真空动作") action_sequence.append(create_action_log(f"抽真空协议完成 ({len(vacuum_transfer_actions)} 个操作)", "✅")) else: - debug_print("⚠️ 抽真空协议返回空序列,添加手动动作") action_sequence.append(create_action_log("抽真空协议为空,使用手动等待", "⚠️")) action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": VACUUM_TIME} }) - + except Exception as e: - debug_print(f"❌ 抽真空失败: {str(e)}") + debug_print(f"抽真空失败: {str(e)}") action_sequence.append(create_action_log(f"抽真空失败: {str(e)}", "❌")) action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": VACUUM_TIME} }) - + # 抽真空后等待 wait_minutes = VACUUM_TIME / 60 action_sequence.append(create_action_log(f"抽真空后等待 ({wait_minutes:.1f} 分钟)", "⏳")) @@ -547,65 +407,59 @@ def generate_evacuateandrefill_protocol( "action_name": "wait", "action_kwargs": {"time": VACUUM_TIME} }) - + # 关闭真空电磁阀 if vacuum_solenoid: - debug_print(f"🚪 关闭真空电磁阀: {vacuum_solenoid}") action_sequence.append(create_action_log(f"关闭真空电磁阀: {vacuum_solenoid}", "🚪")) action_sequence.append({ "device_id": vacuum_solenoid, "action_name": "set_valve_position", "action_kwargs": {"command": "CLOSED"} }) - + # 关闭真空泵 - debug_print(f"🔴 停止真空泵: {vacuum_pump}") action_sequence.append(create_action_log(f"停止真空泵: {vacuum_pump}", "🔴")) action_sequence.append({ "device_id": vacuum_pump, "action_name": "set_status", "action_kwargs": {"string": "OFF"} }) - + # 阶段间等待 action_sequence.append(create_action_log("抽真空阶段完成,短暂等待", "⏳")) action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 5.0} }) - + # ============ 充气阶段 ============ - debug_print(f"💨 充气阶段开始") action_sequence.append(create_action_log("开始气体充气阶段", "💨")) - + # 启动气源 - debug_print(f"🔛 启动气源: {gas_source}") action_sequence.append(create_action_log(f"启动气源: {gas_source}", "🔛")) action_sequence.append({ "device_id": gas_source, "action_name": "set_status", "action_kwargs": {"string": "ON"} }) - + # 开启气源电磁阀 if gas_solenoid: - debug_print(f"🚪 打开气源电磁阀: {gas_solenoid}") action_sequence.append(create_action_log(f"打开气源电磁阀: {gas_solenoid}", "🚪")) action_sequence.append({ "device_id": gas_solenoid, "action_name": "set_valve_position", "action_kwargs": {"command": "OPEN"} }) - + # 充气操作 - debug_print(f"💨 充气操作: {gas_source} -> {vessel_id}") action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel_id}", "💨")) - + try: gas_transfer_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=gas_source, - to_vessel=vessel_id, # 🔧 使用 vessel_id + to_vessel=vessel_id, volume=REFILL_VOLUME, amount="", time=0.0, @@ -617,27 +471,25 @@ def generate_evacuateandrefill_protocol( flowrate=PUMP_FLOW_RATE, transfer_flowrate=PUMP_FLOW_RATE ) - + if gas_transfer_actions: action_sequence.extend(gas_transfer_actions) - debug_print(f"✅ 添加了 {len(gas_transfer_actions)} 个充气动作") action_sequence.append(create_action_log(f"气体充气协议完成 ({len(gas_transfer_actions)} 个操作)", "✅")) else: - debug_print("⚠️ 充气协议返回空序列,添加手动动作") action_sequence.append(create_action_log("充气协议为空,使用手动等待", "⚠️")) action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": REFILL_TIME} }) - + except Exception as e: - debug_print(f"❌ 气体充气失败: {str(e)}") + debug_print(f"气体充气失败: {str(e)}") action_sequence.append(create_action_log(f"气体充气失败: {str(e)}", "❌")) action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": REFILL_TIME} }) - + # 充气后等待 refill_wait_minutes = REFILL_TIME / 60 action_sequence.append(create_action_log(f"充气后等待 ({refill_wait_minutes:.1f} 分钟)", "⏳")) @@ -645,29 +497,26 @@ def generate_evacuateandrefill_protocol( "action_name": "wait", "action_kwargs": {"time": REFILL_TIME} }) - + # 关闭气源电磁阀 if gas_solenoid: - debug_print(f"🚪 关闭气源电磁阀: {gas_solenoid}") action_sequence.append(create_action_log(f"关闭气源电磁阀: {gas_solenoid}", "🚪")) action_sequence.append({ "device_id": gas_solenoid, "action_name": "set_valve_position", "action_kwargs": {"command": "CLOSED"} }) - + # 关闭气源 - debug_print(f"🔴 停止气源: {gas_source}") action_sequence.append(create_action_log(f"停止气源: {gas_source}", "🔴")) action_sequence.append({ "device_id": gas_source, "action_name": "set_status", "action_kwargs": {"string": "OFF"} }) - + # 循环间等待 if cycle < repeats - 1: - debug_print(f"⏳ 等待下一个循环...") action_sequence.append(create_action_log("等待下一个循环...", "⏳")) action_sequence.append({ "action_name": "wait", @@ -675,78 +524,58 @@ def generate_evacuateandrefill_protocol( }) else: action_sequence.append(create_action_log(f"第 {cycle+1}/{repeats} 轮循环完成", "✅")) - + # === 停止搅拌器 === - debug_print("🔍 步骤7: 停止搅拌器...") - if stirrer_id: - debug_print(f"🛑 停止搅拌器: {stirrer_id}") action_sequence.append(create_action_log(f"停止搅拌器: {stirrer_id}", "🛑")) action_sequence.append({ "device_id": stirrer_id, "action_name": "stop_stir", - "action_kwargs": {"vessel": {"id": vessel_id},} # 🔧 使用 vessel_id + "action_kwargs": {"vessel": {"id": vessel_id},} }) else: action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️")) - + # === 最终等待 === action_sequence.append(create_action_log("最终稳定等待...", "⏳")) action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 10.0} }) - + # === 总结 === total_time = (VACUUM_TIME + REFILL_TIME + 25) * repeats + 20 - - debug_print("=" * 60) - debug_print(f"🎉 抽真空充气协议生成完成") - debug_print(f"📊 协议统计:") - debug_print(f" 📋 总动作数: {len(action_sequence)}") - debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)") - debug_print(f" 🥼 处理容器: {vessel_id}") - debug_print(f" 💨 使用气体: {gas}") - debug_print(f" 🔄 重复次数: {repeats}") - debug_print("=" * 60) - - # 添加完成日志 + + debug_print(f"抽真空充气协议生成完成: {len(action_sequence)} 个动作, 预计 {total_time:.0f}s") + summary_msg = f"抽真空充气协议完成: {vessel_id} (使用 {gas},{repeats} 次循环)" action_sequence.append(create_action_log(summary_msg, "🎉")) - + return action_sequence # === 便捷函数 === -def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型 +def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: """生成氮气置换协议""" - vessel_id = vessel["id"] - debug_print(f"💨 生成氮气置换协议: {vessel_id}") return generate_evacuateandrefill_protocol(G, vessel, "nitrogen", **kwargs) -def generate_argon_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型 +def generate_argon_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: """生成氩气置换协议""" - vessel_id = vessel["id"] - debug_print(f"💨 生成氩气置换协议: {vessel_id}") return generate_evacuateandrefill_protocol(G, vessel, "argon", **kwargs) -def generate_air_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型 +def generate_air_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: """生成空气置换协议""" - vessel_id = vessel["id"] - debug_print(f"💨 生成空气置换协议: {vessel_id}") return generate_evacuateandrefill_protocol(G, vessel, "air", **kwargs) -def generate_inert_atmosphere_protocol(G: nx.DiGraph, vessel: dict, gas: str = "nitrogen", **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型 +def generate_inert_atmosphere_protocol(G: nx.DiGraph, vessel: dict, gas: str = "nitrogen", **kwargs) -> List[Dict[str, Any]]: """生成惰性气氛协议""" - vessel_id = vessel["id"] - debug_print(f"🛡️ 生成惰性气氛协议: {vessel_id} (使用 {gas})") return generate_evacuateandrefill_protocol(G, vessel, gas, **kwargs) # 测试函数 def test_evacuateandrefill_protocol(): """测试抽真空充气协议""" - debug_print("=== 抽真空充气协议增强中文版测试 ===") - debug_print("✅ 测试完成") + debug_print("=== 抽真空充气协议测试 ===") + debug_print("测试完成") if __name__ == "__main__": - test_evacuateandrefill_protocol() \ No newline at end of file + test_evacuateandrefill_protocol() diff --git a/unilabos/compile/evacuateandrefill_protocol_old.py b/unilabos/compile/evacuateandrefill_protocol_old.py deleted file mode 100644 index 9f891ea2..00000000 --- a/unilabos/compile/evacuateandrefill_protocol_old.py +++ /dev/null @@ -1,143 +0,0 @@ -# import numpy as np -# import networkx as nx - - -# def generate_evacuateandrefill_protocol( -# G: nx.DiGraph, -# vessel: str, -# gas: str, -# repeats: int = 1 -# ) -> list[dict]: -# """ -# 生成泵操作的动作序列。 - -# :param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置 -# :param from_vessel: 容器A -# :param to_vessel: 容器B -# :param volume: 转移的体积 -# :param flowrate: 最终注入容器B时的流速 -# :param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同) -# :return: 泵操作的动作序列 -# """ - -# # 生成电磁阀、真空泵、气源操作的动作序列 -# vacuum_action_sequence = [] -# nodes = G.nodes(data=True) - -# # 找到和 vessel 相连的电磁阀和真空泵、气源 -# vacuum_backbone = {"vessel": vessel} - -# for neighbor in G.neighbors(vessel): -# if nodes[neighbor]["class"].startswith("solenoid_valve"): -# for neighbor2 in G.neighbors(neighbor): -# if neighbor2 == vessel: -# continue -# if nodes[neighbor2]["class"].startswith("vacuum_pump"): -# vacuum_backbone.update({"vacuum_valve": neighbor, "pump": neighbor2}) -# break -# elif nodes[neighbor2]["class"].startswith("gas_source"): -# vacuum_backbone.update({"gas_valve": neighbor, "gas": neighbor2}) -# break -# # 判断是否设备齐全 -# if len(vacuum_backbone) < 5: -# print(f"\n\n\n{vacuum_backbone}\n\n\n") -# raise ValueError("Not all devices are connected to the vessel.") - -# # 生成操作的动作序列 -# for i in range(repeats): -# # 打开真空泵阀门、关闭气源阀门 -# vacuum_action_sequence.append([ -# { -# "device_id": vacuum_backbone["vacuum_valve"], -# "action_name": "set_valve_position", -# "action_kwargs": { -# "command": "OPEN" -# } -# }, -# { -# "device_id": vacuum_backbone["gas_valve"], -# "action_name": "set_valve_position", -# "action_kwargs": { -# "command": "CLOSED" -# } -# } -# ]) - -# # 打开真空泵、关闭气源 -# vacuum_action_sequence.append([ -# { -# "device_id": vacuum_backbone["pump"], -# "action_name": "set_status", -# "action_kwargs": { -# "string": "ON" -# } -# }, -# { -# "device_id": vacuum_backbone["gas"], -# "action_name": "set_status", -# "action_kwargs": { -# "string": "OFF" -# } -# } -# ]) -# vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}}) - -# # 关闭真空泵阀门、打开气源阀门 -# vacuum_action_sequence.append([ -# { -# "device_id": vacuum_backbone["vacuum_valve"], -# "action_name": "set_valve_position", -# "action_kwargs": { -# "command": "CLOSED" -# } -# }, -# { -# "device_id": vacuum_backbone["gas_valve"], -# "action_name": "set_valve_position", -# "action_kwargs": { -# "command": "OPEN" -# } -# } -# ]) - -# # 关闭真空泵、打开气源 -# vacuum_action_sequence.append([ -# { -# "device_id": vacuum_backbone["pump"], -# "action_name": "set_status", -# "action_kwargs": { -# "string": "OFF" -# } -# }, -# { -# "device_id": vacuum_backbone["gas"], -# "action_name": "set_status", -# "action_kwargs": { -# "string": "ON" -# } -# } -# ]) -# vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}}) - -# # 关闭气源 -# vacuum_action_sequence.append( -# { -# "device_id": vacuum_backbone["gas"], -# "action_name": "set_status", -# "action_kwargs": { -# "string": "OFF" -# } -# } -# ) - -# # 关闭阀门 -# vacuum_action_sequence.append( -# { -# "device_id": vacuum_backbone["gas_valve"], -# "action_name": "set_valve_position", -# "action_kwargs": { -# "command": "CLOSED" -# } -# } -# ) -# return vacuum_action_sequence diff --git a/unilabos/compile/evaporate_protocol.py b/unilabos/compile/evaporate_protocol.py index fd0b12ff..fa1bfe5b 100644 --- a/unilabos/compile/evaporate_protocol.py +++ b/unilabos/compile/evaporate_protocol.py @@ -4,128 +4,99 @@ import logging import re from .utils.vessel_parser import get_vessel from .utils.unit_parser import parse_time_input +from .utils.logger_util import debug_print logger = logging.getLogger(__name__) -def debug_print(message): - """调试输出""" - logger.info(f"[EVAPORATE] {message}") - def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]: """ 在组态图中查找旋转蒸发仪设备 - + Args: G: 设备图 vessel: 指定的设备名称(可选) - + Returns: str: 找到的旋转蒸发仪设备ID,如果没找到返回None """ - debug_print("🔍 开始查找旋转蒸发仪设备... 🌪️") - # 如果指定了vessel,先检查是否存在且是旋转蒸发仪 if vessel: - debug_print(f"🎯 检查指定设备: {vessel} 🔧") if vessel in G.nodes(): node_data = G.nodes[vessel] node_class = node_data.get('class', '') node_type = node_data.get('type', '') - - debug_print(f"📋 设备信息 {vessel}: class={node_class}, type={node_type}") - - # 检查是否为旋转蒸发仪 + if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']): - debug_print(f"🎉 找到指定的旋转蒸发仪: {vessel} ✨") + debug_print(f"找到指定的旋转蒸发仪: {vessel}") return vessel elif node_type == 'device': - debug_print(f"✅ 指定设备存在,尝试直接使用: {vessel} 🔧") + debug_print(f"指定设备存在,尝试直接使用: {vessel}") return vessel - else: - debug_print(f"❌ 指定的设备 {vessel} 不存在 😞") - + # 在所有设备中查找旋转蒸发仪 - debug_print("🔎 在所有设备中搜索旋转蒸发仪... 🕵️‍♀️") rotavap_candidates = [] - + for node_id, node_data in G.nodes(data=True): node_class = node_data.get('class', '') node_type = node_data.get('type', '') - - # 跳过非设备节点 + if node_type != 'device': continue - - # 检查设备类型 + if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']): rotavap_candidates.append(node_id) - debug_print(f"🌟 找到旋转蒸发仪候选: {node_id} (class: {node_class}) 🌪️") elif any(keyword in str(node_id).lower() for keyword in ['rotavap', 'rotary', 'evaporat']): rotavap_candidates.append(node_id) - debug_print(f"🌟 找到旋转蒸发仪候选 (按名称): {node_id} 🌪️") - + if rotavap_candidates: - selected = rotavap_candidates[0] # 选择第一个找到的 - debug_print(f"🎯 选择旋转蒸发仪: {selected} 🏆") + selected = rotavap_candidates[0] + debug_print(f"选择旋转蒸发仪: {selected}") return selected - - debug_print("😭 未找到旋转蒸发仪设备 💔") + + debug_print("未找到旋转蒸发仪设备") return None def find_connected_vessel(G: nx.DiGraph, rotavap_device: str) -> Optional[str]: """ 查找与旋转蒸发仪连接的容器 - - Args: - G: 设备图 - rotavap_device: 旋转蒸发仪设备ID - - Returns: - str: 连接的容器ID,如果没找到返回None """ - debug_print(f"🔗 查找与 {rotavap_device} 连接的容器... 🥽") - - # 查看旋转蒸发仪的子设备 rotavap_data = G.nodes[rotavap_device] children = rotavap_data.get('children', []) - - debug_print(f"👶 检查子设备: {children}") + for child_id in children: if child_id in G.nodes(): child_data = G.nodes[child_id] child_type = child_data.get('type', '') - + if child_type == 'container': - debug_print(f"🎉 找到连接的容器: {child_id} 🥽✨") + debug_print(f"找到连接的容器: {child_id}") return child_id - - # 查看邻接的容器 - debug_print("🤝 检查邻接设备...") + for neighbor in G.neighbors(rotavap_device): neighbor_data = G.nodes[neighbor] neighbor_type = neighbor_data.get('type', '') - + if neighbor_type == 'container': - debug_print(f"🎉 找到邻接的容器: {neighbor} 🥽✨") + debug_print(f"找到邻接的容器: {neighbor}") return neighbor - - debug_print("😞 未找到连接的容器 💔") + + debug_print("未找到连接的容器") return None def generate_evaporate_protocol( G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 + vessel: dict, pressure: float = 0.1, temp: float = 60.0, - time: Union[str, float] = "180", # 🔧 修改:支持字符串时间 + time: Union[str, float] = "180", stir_speed: float = 100.0, solvent: str = "", **kwargs ) -> List[Dict[str, Any]]: """ 生成蒸发操作的协议序列 - 支持单位和体积运算 - + Args: G: 设备图 vessel: 容器字典(从XDL传入) @@ -135,27 +106,16 @@ def generate_evaporate_protocol( stir_speed: 旋转速度 (RPM),默认100 solvent: 溶剂名称(用于参数优化) **kwargs: 其他参数(兼容性) - + Returns: List[Dict[str, Any]]: 动作序列 """ - - # 🔧 核心修改:从字典中提取容器ID + vessel_id, vessel_data = get_vessel(vessel) - - debug_print("🌟" * 20) - debug_print("🌪️ 开始生成蒸发协议(支持单位和体积运算)✨") - debug_print(f"📝 输入参数:") - debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})") - debug_print(f" 💨 pressure: {pressure} bar") - debug_print(f" 🌡️ temp: {temp}°C") - debug_print(f" ⏰ time: {time} (类型: {type(time)})") - debug_print(f" 🌪️ stir_speed: {stir_speed} RPM") - debug_print(f" 🧪 solvent: '{solvent}'") - debug_print("🌟" * 20) - - # 🔧 新增:记录蒸发前的容器状态 - debug_print("🔍 记录蒸发前容器状态...") + + debug_print(f"开始生成蒸发协议: vessel={vessel_id}, pressure={pressure}, temp={temp}, time={time}") + + # 记录蒸发前的容器状态 original_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -163,168 +123,97 @@ def generate_evaporate_protocol( original_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): original_liquid_volume = current_volume - debug_print(f"📊 蒸发前液体体积: {original_liquid_volume:.2f}mL") - - # === 步骤1: 查找旋转蒸发仪设备 === - debug_print("📍 步骤1: 查找旋转蒸发仪设备... 🔍") - - # 验证vessel参数 - if not vessel_id: - debug_print("❌ vessel 参数不能为空! 😱") - raise ValueError("vessel 参数不能为空") - + # 查找旋转蒸发仪设备 + if not vessel_id: + raise ValueError("vessel 参数不能为空") + rotavap_device = find_rotavap_device(G, vessel_id) if not rotavap_device: - debug_print("💥 未找到旋转蒸发仪设备! 😭") raise ValueError(f"未找到旋转蒸发仪设备。请检查组态图中是否包含 class 包含 'rotavap'、'rotary' 或 'evaporat' 的设备") - - debug_print(f"🎉 成功找到旋转蒸发仪: {rotavap_device} ✨") - - # === 步骤2: 确定目标容器 === - debug_print("📍 步骤2: 确定目标容器... 🥽") - + + # 确定目标容器 target_vessel = vessel_id - - # 如果vessel就是旋转蒸发仪设备,查找连接的容器 + if vessel_id == rotavap_device: - debug_print("🔄 vessel就是旋转蒸发仪,查找连接的容器...") connected_vessel = find_connected_vessel(G, rotavap_device) if connected_vessel: target_vessel = connected_vessel - debug_print(f"✅ 使用连接的容器: {target_vessel} 🥽✨") else: - debug_print(f"⚠️ 未找到连接的容器,使用设备本身: {rotavap_device} 🔧") target_vessel = rotavap_device elif vessel_id in G.nodes() and G.nodes[vessel_id].get('type') == 'container': - debug_print(f"✅ 使用指定的容器: {vessel_id} 🥽✨") target_vessel = vessel_id else: - debug_print(f"⚠️ 容器 '{vessel_id}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device} 🔧") target_vessel = rotavap_device - - # === 🔧 新增:步骤3:单位解析处理 === - debug_print("📍 步骤3: 单位解析处理... ⚡") - - # 解析时间 + + # 单位解析处理 final_time = parse_time_input(time) - debug_print(f"🎯 时间解析完成: {time} → {final_time}s ({final_time/60:.1f}分钟) ⏰✨") - - # === 步骤4: 参数验证和修正 === - debug_print("📍 步骤4: 参数验证和修正... 🔧") - - # 修正参数范围 + debug_print(f"时间解析: {time} -> {final_time}s ({final_time/60:.1f}分钟)") + + # 参数验证和修正 if pressure <= 0 or pressure > 1.0: - debug_print(f"⚠️ 真空度 {pressure} bar 超出范围,修正为 0.1 bar 💨") pressure = 0.1 - else: - debug_print(f"✅ 真空度 {pressure} bar 在正常范围内 💨") - + if temp < 10.0 or temp > 200.0: - debug_print(f"⚠️ 温度 {temp}°C 超出范围,修正为 60°C 🌡️") temp = 60.0 - else: - debug_print(f"✅ 温度 {temp}°C 在正常范围内 🌡️") - + if final_time <= 0: - debug_print(f"⚠️ 时间 {final_time}s 无效,修正为 180s (3分钟) ⏰") final_time = 180.0 - else: - debug_print(f"✅ 时间 {final_time}s ({final_time/60:.1f}分钟) 有效 ⏰") - + if stir_speed < 10.0 or stir_speed > 300.0: - debug_print(f"⚠️ 旋转速度 {stir_speed} RPM 超出范围,修正为 100 RPM 🌪️") stir_speed = 100.0 - else: - debug_print(f"✅ 旋转速度 {stir_speed} RPM 在正常范围内 🌪️") - + # 根据溶剂优化参数 if solvent: - debug_print(f"🧪 根据溶剂 '{solvent}' 优化参数... 🔬") solvent_lower = solvent.lower() - + if any(s in solvent_lower for s in ['water', 'aqueous', 'h2o']): temp = max(temp, 80.0) pressure = max(pressure, 0.2) - debug_print("💧 水系溶剂:提高温度和真空度 🌡️💨") elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']): temp = min(temp, 50.0) pressure = min(pressure, 0.05) - debug_print("🍺 易挥发溶剂:降低温度和真空度 🌡️💨") elif any(s in solvent_lower for s in ['dmso', 'dmi', 'toluene']): temp = max(temp, 100.0) pressure = min(pressure, 0.01) - debug_print("🔥 高沸点溶剂:提高温度,降低真空度 🌡️💨") - else: - debug_print("🧪 通用溶剂,使用标准参数 ✨") - else: - debug_print("🤷‍♀️ 未指定溶剂,使用默认参数 ✨") - - debug_print(f"🎯 最终参数: pressure={pressure} bar 💨, temp={temp}°C 🌡️, time={final_time}s ⏰, stir_speed={stir_speed} RPM 🌪️") - - # === 🔧 新增:步骤5:蒸发体积计算 === - debug_print("📍 步骤5: 蒸发体积计算... 📊") - - # 根据温度、真空度、时间和溶剂类型估算蒸发量 + + debug_print(f"最终参数: pressure={pressure}bar, temp={temp}°C, time={final_time}s, stir_speed={stir_speed}RPM") + + # 蒸发体积计算 evaporation_volume = 0.0 if original_liquid_volume > 0: - # 基础蒸发速率(mL/min) - base_evap_rate = 0.5 # 基础速率 - - # 温度系数(高温蒸发更快) + base_evap_rate = 0.5 temp_factor = 1.0 + (temp - 25.0) / 100.0 - - # 真空系数(真空度越高蒸发越快) vacuum_factor = 1.0 + (1.0 - pressure) * 2.0 - - # 溶剂系数 + solvent_factor = 1.0 if solvent: solvent_lower = solvent.lower() if any(s in solvent_lower for s in ['water', 'h2o']): - solvent_factor = 0.8 # 水蒸发较慢 + solvent_factor = 0.8 elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']): - solvent_factor = 1.5 # 易挥发溶剂蒸发快 + solvent_factor = 1.5 elif any(s in solvent_lower for s in ['dmso', 'dmi']): - solvent_factor = 0.3 # 高沸点溶剂蒸发慢 - - # 计算总蒸发量 + solvent_factor = 0.3 + total_evap_rate = base_evap_rate * temp_factor * vacuum_factor * solvent_factor evaporation_volume = min( - original_liquid_volume * 0.95, # 最多蒸发95% - total_evap_rate * (final_time / 60.0) # 时间相关的蒸发量 + original_liquid_volume * 0.95, + total_evap_rate * (final_time / 60.0) ) - - debug_print(f"📊 蒸发量计算:") - debug_print(f" - 基础蒸发速率: {base_evap_rate} mL/min") - debug_print(f" - 温度系数: {temp_factor:.2f} (基于 {temp}°C)") - debug_print(f" - 真空系数: {vacuum_factor:.2f} (基于 {pressure} bar)") - debug_print(f" - 溶剂系数: {solvent_factor:.2f} ({solvent or '通用'})") - debug_print(f" - 总蒸发速率: {total_evap_rate:.2f} mL/min") - debug_print(f" - 预计蒸发量: {evaporation_volume:.2f}mL ({evaporation_volume/original_liquid_volume*100:.1f}%)") - - # === 步骤6: 生成动作序列 === - debug_print("📍 步骤6: 生成动作序列... 🎬") - + + debug_print(f"预计蒸发量: {evaporation_volume:.2f}mL ({evaporation_volume/original_liquid_volume*100:.1f}%)") + + # 生成动作序列 action_sequence = [] - + # 1. 等待稳定 - debug_print(" 🔄 动作1: 添加初始等待稳定... ⏳") action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 10} }) - debug_print(" ✅ 初始等待动作已添加 ⏳✨") - + # 2. 执行蒸发 - debug_print(f" 🌪️ 动作2: 执行蒸发操作...") - debug_print(f" 🔧 设备: {rotavap_device}") - debug_print(f" 🥽 容器: {target_vessel}") - debug_print(f" 💨 真空度: {pressure} bar") - debug_print(f" 🌡️ 温度: {temp}°C") - debug_print(f" ⏰ 时间: {final_time}s ({final_time/60:.1f}分钟)") - debug_print(f" 🌪️ 旋转速度: {stir_speed} RPM") - evaporate_action = { "device_id": rotavap_device, "action_name": "evaporate", @@ -332,20 +221,17 @@ def generate_evaporate_protocol( "vessel": {"id": target_vessel}, "pressure": float(pressure), "temp": float(temp), - "time": float(final_time), # 🔧 强制转换为float类型 + "time": float(final_time), "stir_speed": float(stir_speed), "solvent": str(solvent) } } action_sequence.append(evaporate_action) - debug_print(" ✅ 蒸发动作已添加 🌪️✨") - - # 🔧 新增:蒸发过程中的体积变化 - debug_print(" 🔧 更新容器体积 - 蒸发过程...") + + # 蒸发过程中的体积变化 if evaporation_volume > 0: new_volume = max(0.0, original_liquid_volume - evaporation_volume) - - # 更新vessel字典中的体积 + if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): @@ -357,15 +243,14 @@ def generate_evaporate_protocol( vessel["data"]["liquid_volume"] = new_volume else: vessel["data"]["liquid_volume"] = new_volume - - # 🔧 同时更新图中的容器数据 + if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} - + vessel_node_data = G.nodes[vessel_id]['data'] current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - + if isinstance(current_node_volume, list): if len(current_node_volume) > 0: G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume @@ -373,18 +258,16 @@ def generate_evaporate_protocol( G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume] else: G.nodes[vessel_id]['data']['liquid_volume'] = new_volume - - debug_print(f" 📊 蒸发体积变化: {original_liquid_volume:.2f}mL → {new_volume:.2f}mL (-{evaporation_volume:.2f}mL)") - + + debug_print(f"蒸发体积变化: {original_liquid_volume:.2f}mL -> {new_volume:.2f}mL (-{evaporation_volume:.2f}mL)") + # 3. 蒸发后等待 - debug_print(" 🔄 动作3: 添加蒸发后等待... ⏳") action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 10} }) - debug_print(" ✅ 蒸发后等待动作已添加 ⏳✨") - - # 🔧 新增:蒸发完成后的状态报告 + + # 最终状态 final_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -392,19 +275,7 @@ def generate_evaporate_protocol( final_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): final_liquid_volume = current_volume - - # === 总结 === - debug_print("🎊" * 20) - debug_print(f"🎉 蒸发协议生成完成! ✨") - debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝") - debug_print(f"🌪️ 旋转蒸发仪: {rotavap_device} 🔧") - debug_print(f"🥽 目标容器: {target_vessel} 🧪") - debug_print(f"⚙️ 蒸发参数: {pressure} bar 💨, {temp}°C 🌡️, {final_time}s ⏰, {stir_speed} RPM 🌪️") - debug_print(f"⏱️ 预计总时间: {(final_time + 20)/60:.1f} 分钟 ⌛") - debug_print(f"📊 体积变化:") - debug_print(f" - 蒸发前: {original_liquid_volume:.2f}mL") - debug_print(f" - 蒸发后: {final_liquid_volume:.2f}mL") - debug_print(f" - 蒸发量: {evaporation_volume:.2f}mL ({evaporation_volume/max(original_liquid_volume, 0.01)*100:.1f}%)") - debug_print("🎊" * 20) - + + debug_print(f"蒸发协议生成完成: {len(action_sequence)} 个动作, 设备={rotavap_device}, 容器={target_vessel}") + return action_sequence diff --git a/unilabos/compile/filter_protocol.py b/unilabos/compile/filter_protocol.py index 2777e092..2ac2f395 100644 --- a/unilabos/compile/filter_protocol.py +++ b/unilabos/compile/filter_protocol.py @@ -2,87 +2,64 @@ from typing import List, Dict, Any, Optional import networkx as nx import logging from .utils.vessel_parser import get_vessel +from .utils.logger_util import debug_print from .pump_protocol import generate_pump_protocol_with_rinsing logger = logging.getLogger(__name__) -def debug_print(message): - """调试输出""" - logger.info(f"[FILTER] {message}") - def find_filter_device(G: nx.DiGraph) -> str: """查找过滤器设备""" - debug_print("🔍 查找过滤器设备... 🌊") - - # 查找过滤器设备 for node in G.nodes(): node_data = G.nodes[node] node_class = node_data.get('class', '') or '' - + if 'filter' in node_class.lower() or 'filter' in node.lower(): - debug_print(f"🎉 找到过滤器设备: {node} ✨") + debug_print(f"找到过滤器设备: {node}") return node - - # 如果没找到,寻找可能的过滤器名称 - debug_print("🔎 在预定义名称中搜索过滤器... 📋") + possible_names = ["filter", "filter_1", "virtual_filter", "filtration_unit"] for name in possible_names: if name in G.nodes(): - debug_print(f"🎉 找到过滤器设备: {name} ✨") + debug_print(f"找到过滤器设备: {name}") return name - - debug_print("😭 未找到过滤器设备 💔") + raise ValueError("未找到过滤器设备") def validate_vessel(G: nx.DiGraph, vessel: str, vessel_type: str = "容器") -> None: """验证容器是否存在""" - debug_print(f"🔍 验证{vessel_type}: '{vessel}' 🧪") - if not vessel: - debug_print(f"❌ {vessel_type}不能为空! 😱") raise ValueError(f"{vessel_type}不能为空") - + if vessel not in G.nodes(): - debug_print(f"❌ {vessel_type} '{vessel}' 不存在于系统中! 😞") raise ValueError(f"{vessel_type} '{vessel}' 不存在于系统中") - - debug_print(f"✅ {vessel_type} '{vessel}' 验证通过 🎯") def generate_filter_protocol( G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 + vessel: dict, filtrate_vessel: dict = {"id": "waste"}, **kwargs ) -> List[Dict[str, Any]]: """ 生成过滤操作的协议序列 - 支持体积运算 - + Args: G: 设备图 vessel: 过滤容器字典(必需)- 包含需要过滤的混合物 filtrate_vessel: 滤液容器名称(可选)- 如果提供则收集滤液 **kwargs: 其他参数(兼容性) - + Returns: List[Dict[str, Any]]: 过滤操作的动作序列 """ - - # 🔧 核心修改:从字典中提取容器ID + vessel_id, vessel_data = get_vessel(vessel) filtrate_vessel_id, filtrate_vessel_data = get_vessel(filtrate_vessel) - - debug_print("🌊" * 20) - debug_print("🚀 开始生成过滤协议(支持体积运算)✨") - debug_print(f"📝 输入参数:") - debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})") - debug_print(f" 🧪 filtrate_vessel: {filtrate_vessel}") - debug_print(f" ⚙️ 其他参数: {kwargs}") - debug_print("🌊" * 20) - + + debug_print(f"开始生成过滤协议: vessel={vessel_id}, filtrate_vessel={filtrate_vessel_id}") + action_sequence = [] - - # 🔧 新增:记录过滤前的容器状态 - debug_print("🔍 记录过滤前容器状态...") + + # 记录过滤前的容器状态 original_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -90,79 +67,45 @@ def generate_filter_protocol( original_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): original_liquid_volume = current_volume - debug_print(f"📊 过滤前液体体积: {original_liquid_volume:.2f}mL") - + # === 参数验证 === - debug_print("📍 步骤1: 参数验证... 🔧") - - # 验证必需参数 - debug_print(" 🔍 验证必需参数...") - validate_vessel(G, vessel_id, "过滤容器") # 🔧 使用 vessel_id - debug_print(" ✅ 必需参数验证完成 🎯") - - # 验证可选参数 - debug_print(" 🔍 验证可选参数...") + validate_vessel(G, vessel_id, "过滤容器") + if filtrate_vessel: validate_vessel(G, filtrate_vessel_id, "滤液容器") - debug_print(" 🌊 模式: 过滤并收集滤液 💧") - else: - debug_print(" 🧱 模式: 过滤并收集固体 🔬") - debug_print(" ✅ 可选参数验证完成 🎯") - + # === 查找设备 === - debug_print("📍 步骤2: 查找设备... 🔍") - try: - debug_print(" 🔎 搜索过滤器设备...") filter_device = find_filter_device(G) - debug_print(f" 🎉 使用过滤器设备: {filter_device} 🌊✨") - + debug_print(f"使用过滤器设备: {filter_device}") except Exception as e: - debug_print(f" ❌ 设备查找失败: {str(e)} 😭") raise ValueError(f"设备查找失败: {str(e)}") - - # 🔧 新增:过滤效率和体积分配估算 - debug_print("📍 步骤2.5: 过滤体积分配估算... 📊") - - # 估算过滤分离比例(基于经验数据) - solid_ratio = 0.1 # 假设10%是固体(保留在过滤器上) - liquid_ratio = 0.9 # 假设90%是液体(通过过滤器) - volume_loss_ratio = 0.05 # 假设5%体积损失(残留在过滤器等) - - # 从kwargs中获取过滤参数进行优化 + + # 过滤体积分配估算 + solid_ratio = 0.1 + liquid_ratio = 0.9 + volume_loss_ratio = 0.05 + if "solid_content" in kwargs: try: solid_ratio = float(kwargs["solid_content"]) liquid_ratio = 1.0 - solid_ratio - debug_print(f"📋 使用指定的固体含量: {solid_ratio*100:.1f}%") except: - debug_print("⚠️ 固体含量参数无效,使用默认值") - + pass + if original_liquid_volume > 0: expected_filtrate_volume = original_liquid_volume * liquid_ratio * (1.0 - volume_loss_ratio) expected_solid_volume = original_liquid_volume * solid_ratio volume_loss = original_liquid_volume * volume_loss_ratio - - debug_print(f"📊 过滤体积分配估算:") - debug_print(f" - 原始体积: {original_liquid_volume:.2f}mL") - debug_print(f" - 预计滤液体积: {expected_filtrate_volume:.2f}mL ({liquid_ratio*100:.1f}%)") - debug_print(f" - 预计固体体积: {expected_solid_volume:.2f}mL ({solid_ratio*100:.1f}%)") - debug_print(f" - 预计损失体积: {volume_loss:.2f}mL ({volume_loss_ratio*100:.1f}%)") - + # === 转移到过滤器(如果需要)=== - debug_print("📍 步骤3: 转移到过滤器... 🚚") - - if vessel_id != filter_device: # 🔧 使用 vessel_id - debug_print(f" 🚛 需要转移: {vessel_id} → {filter_device} 📦") - + if vessel_id != filter_device: try: - debug_print(" 🔄 开始执行转移操作...") - # 使用pump protocol转移液体到过滤器 transfer_actions = generate_pump_protocol_with_rinsing( G=G, - from_vessel={"id": vessel_id}, # 🔧 使用 vessel_id + from_vessel={"id": vessel_id}, to_vessel={"id": filter_device}, - volume=0.0, # 转移所有液体 + volume=0.0, amount="", time=0.0, viscous=False, @@ -173,88 +116,59 @@ def generate_filter_protocol( flowrate=2.0, transfer_flowrate=2.0 ) - + if transfer_actions: action_sequence.extend(transfer_actions) - debug_print(f" ✅ 添加了 {len(transfer_actions)} 个转移动作 🚚✨") - - # 🔧 新增:转移后更新容器体积 - debug_print(" 🔧 更新转移后的容器体积...") - - # 原容器体积变为0(所有液体已转移) + debug_print(f"添加了 {len(transfer_actions)} 个转移动作") + + # 更新容器体积 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): vessel["data"]["liquid_volume"] = [0.0] if len(current_volume) > 0 else [0.0] else: vessel["data"]["liquid_volume"] = 0.0 - - # 同时更新图中的容器数据 + if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data']['liquid_volume'] = 0.0 - - debug_print(f" 📊 转移完成,{vessel_id} 体积更新为 0.0mL") - - else: - debug_print(" ⚠️ 转移协议返回空序列 🤔") - + except Exception as e: - debug_print(f" ❌ 转移失败: {str(e)} 😞") - debug_print(" 🔄 继续执行,可能是直接连接的过滤器 🤞") - else: - debug_print(" ✅ 过滤容器就是过滤器,无需转移 🎯") - + debug_print(f"转移失败: {str(e)},继续执行") + # === 执行过滤操作 === - debug_print("📍 步骤4: 执行过滤操作... 🌊") - - # 构建过滤动作参数 - debug_print(" ⚙️ 构建过滤参数...") filter_kwargs = { - "vessel": {"id": filter_device}, # 过滤器设备 - "filtrate_vessel": {"id": filtrate_vessel_id}, # 滤液容器(可能为空) + "vessel": {"id": filter_device}, + "filtrate_vessel": {"id": filtrate_vessel_id}, "stir": kwargs.get("stir", False), "stir_speed": kwargs.get("stir_speed", 0.0), "temp": kwargs.get("temp", 25.0), "continue_heatchill": kwargs.get("continue_heatchill", False), - "volume": kwargs.get("volume", 0.0) # 0表示过滤所有 + "volume": kwargs.get("volume", 0.0) } - - debug_print(f" 📋 过滤参数: {filter_kwargs}") - debug_print(" 🌊 开始过滤操作...") - - # 过滤动作 + filter_action = { "device_id": filter_device, "action_name": "filter", "action_kwargs": filter_kwargs } action_sequence.append(filter_action) - debug_print(" ✅ 过滤动作已添加 🌊✨") - + # 过滤后等待 - debug_print(" ⏳ 添加过滤后等待...") action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 10.0} }) - debug_print(" ✅ 过滤后等待动作已添加 ⏰✨") - + # === 收集滤液(如果需要)=== - debug_print("📍 步骤5: 收集滤液... 💧") - if filtrate_vessel_id and filtrate_vessel_id not in G.neighbors(filter_device): - debug_print(f" 🧪 收集滤液: {filter_device} → {filtrate_vessel_id} 💧") - try: - debug_print(" 🔄 开始执行收集操作...") - # 使用pump protocol收集滤液 collect_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=filter_device, to_vessel=filtrate_vessel, - volume=0.0, # 收集所有滤液 + volume=0.0, amount="", time=0.0, viscous=False, @@ -265,19 +179,15 @@ def generate_filter_protocol( flowrate=2.0, transfer_flowrate=2.0 ) - + if collect_actions: action_sequence.extend(collect_actions) - debug_print(f" ✅ 添加了 {len(collect_actions)} 个收集动作 🧪✨") - - # 🔧 新增:收集滤液后的体积更新 - debug_print(" 🔧 更新滤液容器体积...") - - # 更新filtrate_vessel在图中的体积(如果它是节点) + + # 更新滤液容器体积 if filtrate_vessel_id in G.nodes(): if 'data' not in G.nodes[filtrate_vessel_id]: G.nodes[filtrate_vessel_id]['data'] = {} - + current_filtrate_volume = G.nodes[filtrate_vessel_id]['data'].get('liquid_volume', 0.0) if isinstance(current_filtrate_volume, list): if len(current_filtrate_volume) > 0: @@ -286,58 +196,37 @@ def generate_filter_protocol( G.nodes[filtrate_vessel_id]['data']['liquid_volume'] = [expected_filtrate_volume] else: G.nodes[filtrate_vessel_id]['data']['liquid_volume'] = current_filtrate_volume + expected_filtrate_volume - - debug_print(f" 📊 滤液容器 {filtrate_vessel_id} 体积增加 {expected_filtrate_volume:.2f}mL") - - else: - debug_print(" ⚠️ 收集协议返回空序列 🤔") - + except Exception as e: - debug_print(f" ❌ 收集滤液失败: {str(e)} 😞") - debug_print(" 🔄 继续执行,可能滤液直接流入指定容器 🤞") - else: - debug_print(" 🧱 未指定滤液容器,固体保留在过滤器中 🔬") - - # 🔧 新增:过滤完成后的容器状态更新 - debug_print("📍 步骤5.5: 过滤完成后状态更新... 📊") - + debug_print(f"收集滤液失败: {str(e)},继续执行") + + # 过滤完成后容器状态更新 if vessel_id == filter_device: - # 如果过滤容器就是过滤器,需要更新其体积状态 if original_liquid_volume > 0: if filtrate_vessel: - # 收集滤液模式:过滤器中主要保留固体 remaining_volume = expected_solid_volume - debug_print(f" 🧱 过滤器中保留固体: {remaining_volume:.2f}mL") else: - # 保留固体模式:过滤器中保留所有物质 remaining_volume = original_liquid_volume * (1.0 - volume_loss_ratio) - debug_print(f" 🔬 过滤器中保留所有物质: {remaining_volume:.2f}mL") - - # 更新vessel字典中的体积 + if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): vessel["data"]["liquid_volume"] = [remaining_volume] if len(current_volume) > 0 else [remaining_volume] else: vessel["data"]["liquid_volume"] = remaining_volume - - # 同时更新图中的容器数据 + if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data']['liquid_volume'] = remaining_volume - - debug_print(f" 📊 过滤器 {vessel_id} 体积更新为: {remaining_volume:.2f}mL") - + # === 最终等待 === - debug_print("📍 步骤6: 最终等待... ⏰") action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 5.0} }) - debug_print(" ✅ 最终等待动作已添加 ⏰✨") - - # 🔧 新增:过滤完成后的状态报告 + + # 最终状态 final_vessel_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -345,22 +234,7 @@ def generate_filter_protocol( final_vessel_volume = current_volume[0] elif isinstance(current_volume, (int, float)): final_vessel_volume = current_volume - - # === 总结 === - debug_print("🎊" * 20) - debug_print(f"🎉 过滤协议生成完成! ✨") - debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝") - debug_print(f"🥽 过滤容器: {vessel_id} 🧪") - debug_print(f"🌊 过滤器设备: {filter_device} 🔧") - debug_print(f"💧 滤液容器: {filtrate_vessel_id or '无(保留固体)'} 🧱") - debug_print(f"⏱️ 预计总时间: {(len(action_sequence) * 5):.0f} 秒 ⌛") - if original_liquid_volume > 0: - debug_print(f"📊 体积变化统计:") - debug_print(f" - 过滤前体积: {original_liquid_volume:.2f}mL") - debug_print(f" - 过滤后容器体积: {final_vessel_volume:.2f}mL") - if filtrate_vessel: - debug_print(f" - 预计滤液体积: {expected_filtrate_volume:.2f}mL") - debug_print(f" - 预计损失体积: {volume_loss:.2f}mL") - debug_print("🎊" * 20) - + + debug_print(f"过滤协议生成完成: {len(action_sequence)} 个动作, 容器={vessel_id}, 过滤器={filter_device}") + return action_sequence diff --git a/unilabos/compile/pump_protocol.py b/unilabos/compile/pump_protocol.py index d2dd4978..f7e06730 100644 --- a/unilabos/compile/pump_protocol.py +++ b/unilabos/compile/pump_protocol.py @@ -2,99 +2,18 @@ import traceback import numpy as np import networkx as nx import asyncio -import time as time_module # 🔧 重命名time模块 +import time as time_module # 重命名time模块 from typing import List, Dict, Any import logging import sys -from unilabos.compile.utils.vessel_parser import get_vessel +from .utils.logger_util import debug_print +from .utils.vessel_parser import get_vessel +from .utils.resource_helper import get_resource_liquid_volume logger = logging.getLogger(__name__) -def debug_print(message): - """强制输出调试信息""" - output = f"[TRANSFER] {message}" - logger.info(output) - - -def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float: - """ - 从容器节点的数据中获取液体体积 - """ - debug_print(f"🔍 开始读取容器 '{vessel}' 的液体体积...") - - if vessel not in G.nodes(): - logger.error(f"❌ 容器 '{vessel}' 不存在于系统图中") - debug_print(f" - 系统中的容器: {list(G.nodes())}") - return 0.0 - - vessel_data = G.nodes[vessel].get('data', {}) - debug_print(f"📋 容器 '{vessel}' 的数据结构: {vessel_data}") - - total_volume = 0.0 - - # 方法1:检查 'liquid' 字段(列表格式) - debug_print("🔍 方法1: 检查 'liquid' 字段...") - if 'liquid' in vessel_data: - liquids = vessel_data['liquid'] - debug_print(f" - liquid 字段类型: {type(liquids)}") - debug_print(f" - liquid 字段内容: {liquids}") - - if isinstance(liquids, list): - debug_print(f" - liquid 是列表,包含 {len(liquids)} 个元素") - for i, liquid in enumerate(liquids): - debug_print(f" 液体 {i + 1}: {liquid}") - if isinstance(liquid, dict): - volume_keys = ['liquid_volume', 'volume', 'amount', 'quantity'] - for key in volume_keys: - if key in liquid: - try: - vol = float(liquid[key]) - total_volume += vol - debug_print(f" ✅ 从 '{key}' 读取体积: {vol}mL") - break - except (ValueError, TypeError) as e: - logger.warning(f" ⚠️ 无法转换 '{key}': {liquid[key]} -> {str(e)}") - continue - else: - debug_print(f" - liquid 不是列表: {type(liquids)}") - else: - debug_print(" - 没有 'liquid' 字段") - - # 方法2:检查直接的体积字段 - debug_print("🔍 方法2: 检查直接体积字段...") - volume_keys = ['total_volume', 'volume', 'liquid_volume', 'amount', 'current_volume'] - for key in volume_keys: - if key in vessel_data: - try: - vol = float(vessel_data[key]) - total_volume = max(total_volume, vol) # 取最大值 - debug_print(f" ✅ 从容器数据 '{key}' 读取体积: {vol}mL") - break - except (ValueError, TypeError) as e: - logger.warning(f" ⚠️ 无法转换 '{key}': {vessel_data[key]} -> {str(e)}") - continue - - # 方法3:检查 'state' 或 'status' 字段 - debug_print("🔍 方法3: 检查 'state' 字段...") - if 'state' in vessel_data and isinstance(vessel_data['state'], dict): - state = vessel_data['state'] - debug_print(f" - state 字段内容: {state}") - if 'volume' in state: - try: - vol = float(state['volume']) - total_volume = max(total_volume, vol) - debug_print(f" ✅ 从容器状态读取体积: {vol}mL") - except (ValueError, TypeError) as e: - logger.warning(f" ⚠️ 无法转换 state.volume: {state['volume']} -> {str(e)}") - else: - debug_print(" - 没有 'state' 字段或不是字典") - - debug_print(f"📊 容器 '{vessel}' 最终检测体积: {total_volume}mL") - return total_volume - - def is_integrated_pump(node_class: str, node_name: str = "") -> bool: """ 判断是否为泵阀一体设备 @@ -515,188 +434,132 @@ def generate_pump_protocol_with_rinsing( to_vessel_id, _ = get_vessel(to_vessel) with generate_pump_protocol_with_rinsing._lock: - debug_print("=" * 60) - debug_print(f"PUMP_TRANSFER: 🚀 开始生成协议 (同步版本)") - debug_print(f" 📍 路径: {from_vessel_id} -> {to_vessel_id}") - debug_print(f" 🕐 时间戳: {time_module.time()}") - debug_print(f" 🔒 获得执行锁") - debug_print("=" * 60) + debug_print(f"PUMP_TRANSFER: {from_vessel_id} -> {to_vessel_id}, volume={volume}, flowrate={flowrate}") # 短暂延迟,避免快速重复调用 time_module.sleep(0.01) - debug_print("🔍 步骤1: 开始体积处理...") - # 1. 处理体积参数 final_volume = volume - debug_print(f"📋 初始设置: final_volume = {final_volume}") + debug_print(f"初始体积: {final_volume}") - # 🔧 修复:如果volume为0(ROS2传入的空值),从容器读取实际体积 + # 如果volume为0,从容器读取实际体积 if volume == 0.0: - debug_print("🎯 检测到 volume=0.0,开始自动体积检测...") - # 直接从源容器读取实际体积 - actual_volume = get_vessel_liquid_volume(G, from_vessel_id) - debug_print(f"📖 从容器 '{from_vessel_id}' 读取到体积: {actual_volume}mL") + actual_volume = get_resource_liquid_volume(G.nodes.get(from_vessel_id, {})) if actual_volume > 0: final_volume = actual_volume - debug_print(f"✅ 成功设置体积为: {final_volume}mL") + debug_print(f"从容器读取体积: {final_volume}mL") else: - final_volume = 10.0 # 如果读取失败,使用默认值 - logger.warning(f"⚠️ 无法从容器读取体积,使用默认值: {final_volume}mL") + final_volume = 10.0 + logger.warning(f"无法从容器读取体积,使用默认值: {final_volume}mL") else: - debug_print(f"📌 体积非零,直接使用: {final_volume}mL") + debug_print(f"使用指定体积: {final_volume}mL") # 处理 amount 参数 if amount and amount.strip(): - debug_print(f"🔍 检测到 amount 参数: '{amount}',开始解析...") parsed_volume = _parse_amount_to_volume(amount) - debug_print(f"📖 从 amount 解析得到体积: {parsed_volume}mL") if parsed_volume > 0: final_volume = parsed_volume - debug_print(f"✅ 使用从 amount 解析的体积: {final_volume}mL") elif parsed_volume == 0.0 and amount.lower().strip() == "all": - debug_print("🎯 检测到 amount='all',从容器读取全部体积...") - actual_volume = get_vessel_liquid_volume(G, from_vessel_id) + actual_volume = get_resource_liquid_volume(G.nodes.get(from_vessel_id, {})) if actual_volume > 0: final_volume = actual_volume - debug_print(f"✅ amount='all',设置体积为: {final_volume}mL") # 最终体积验证 - debug_print(f"🔍 步骤2: 最终体积验证...") if final_volume <= 0: - logger.error(f"❌ 体积无效: {final_volume}mL") + logger.error(f"体积无效: {final_volume}mL") final_volume = 10.0 - logger.warning(f"⚠️ 强制设置为默认值: {final_volume}mL") + logger.warning(f"强制设置为默认值: {final_volume}mL") - debug_print(f"✅ 最终确定体积: {final_volume}mL") + debug_print(f"最终体积: {final_volume}mL") # 2. 处理流速参数 - debug_print(f"🔍 步骤3: 处理流速参数...") - debug_print(f" - 原始 flowrate: {flowrate}") - debug_print(f" - 原始 transfer_flowrate: {transfer_flowrate}") final_flowrate = flowrate if flowrate > 0 else 2.5 final_transfer_flowrate = transfer_flowrate if transfer_flowrate > 0 else 0.5 if flowrate <= 0: - logger.warning(f"⚠️ flowrate <= 0,修正为: {final_flowrate}mL/s") + logger.warning(f"flowrate <= 0,修正为: {final_flowrate}mL/s") if transfer_flowrate <= 0: - logger.warning(f"⚠️ transfer_flowrate <= 0,修正为: {final_transfer_flowrate}mL/s") - - debug_print(f"✅ 修正后流速: flowrate={final_flowrate}mL/s, transfer_flowrate={final_transfer_flowrate}mL/s") + logger.warning(f"transfer_flowrate <= 0,修正为: {final_transfer_flowrate}mL/s") # 3. 根据时间计算流速 if time > 0 and final_volume > 0: - debug_print(f"🔍 步骤4: 根据时间计算流速...") calculated_flowrate = final_volume / time - debug_print(f" - 计算得到流速: {calculated_flowrate}mL/s") if flowrate <= 0 or flowrate == 2.5: final_flowrate = min(calculated_flowrate, 10.0) - debug_print(f" - 调整 flowrate 为: {final_flowrate}mL/s") if transfer_flowrate <= 0 or transfer_flowrate == 0.5: final_transfer_flowrate = min(calculated_flowrate, 5.0) - debug_print(f" - 调整 transfer_flowrate 为: {final_transfer_flowrate}mL/s") # 4. 根据速度规格调整 if rate_spec: - debug_print(f"🔍 步骤5: 根据速度规格调整...") - debug_print(f" - 速度规格: '{rate_spec}'") - if rate_spec == "dropwise": final_flowrate = min(final_flowrate, 0.1) final_transfer_flowrate = min(final_transfer_flowrate, 0.1) - debug_print(f" - dropwise模式,流速调整为: {final_flowrate}mL/s") elif rate_spec == "slowly": final_flowrate = min(final_flowrate, 0.5) final_transfer_flowrate = min(final_transfer_flowrate, 0.3) - debug_print(f" - slowly模式,流速调整为: {final_flowrate}mL/s") elif rate_spec == "quickly": final_flowrate = max(final_flowrate, 5.0) final_transfer_flowrate = max(final_transfer_flowrate, 2.0) - debug_print(f" - quickly模式,流速调整为: {final_flowrate}mL/s") + debug_print(f"速度规格 '{rate_spec}' 应用后: flowrate={final_flowrate}, transfer={final_transfer_flowrate}") # 5. 处理冲洗参数 - debug_print(f"🔍 步骤6: 处理冲洗参数...") final_rinsing_solvent = rinsing_solvent final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0 final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2 if rinsing_volume <= 0: - logger.warning(f"⚠️ rinsing_volume <= 0,修正为: {final_rinsing_volume}mL") + logger.warning(f"rinsing_volume <= 0,修正为: {final_rinsing_volume}mL") if rinsing_repeats <= 0: - logger.warning(f"⚠️ rinsing_repeats <= 0,修正为: {final_rinsing_repeats}次") + logger.warning(f"rinsing_repeats <= 0,修正为: {final_rinsing_repeats}次") # 根据物理属性调整冲洗参数 if viscous or solid: final_rinsing_repeats = max(final_rinsing_repeats, 3) final_rinsing_volume = max(final_rinsing_volume, 10.0) - debug_print(f"🧪 粘稠/固体物质,调整冲洗参数:{final_rinsing_repeats}次,{final_rinsing_volume}mL") # 参数总结 - debug_print("📊 最终参数总结:") - debug_print(f" - 体积: {final_volume}mL") - debug_print(f" - 流速: {final_flowrate}mL/s") - debug_print(f" - 转移流速: {final_transfer_flowrate}mL/s") - debug_print(f" - 冲洗溶剂: '{final_rinsing_solvent}'") - debug_print(f" - 冲洗体积: {final_rinsing_volume}mL") - debug_print(f" - 冲洗次数: {final_rinsing_repeats}次") + debug_print(f"最终参数: volume={final_volume}mL, flowrate={final_flowrate}mL/s, " + f"transfer_flowrate={final_transfer_flowrate}mL/s, " + f"rinsing={final_rinsing_solvent}/{final_rinsing_volume}mL/{final_rinsing_repeats}次") # ========== 执行基础转移 ========== - - debug_print("🔧 步骤7: 开始执行基础转移...") - try: - debug_print(f" - 调用 generate_pump_protocol...") - debug_print( - f" - 参数: G, '{from_vessel_id}', '{to_vessel_id}', {final_volume}, {final_flowrate}, {final_transfer_flowrate}") pump_action_sequence = generate_pump_protocol( G, from_vessel_id, to_vessel_id, final_volume, final_flowrate, final_transfer_flowrate ) - debug_print(f" - generate_pump_protocol 返回结果:") - debug_print(f" - 动作序列长度: {len(pump_action_sequence)}") - debug_print(f" - 动作序列是否为空: {len(pump_action_sequence) == 0}") + debug_print(f"基础转移生成了 {len(pump_action_sequence)} 个动作") if not pump_action_sequence: - debug_print("❌ 基础转移协议生成为空,可能是路径问题") - debug_print(f" - 源容器存在: {from_vessel_id in G.nodes()}") - debug_print(f" - 目标容器存在: {to_vessel_id in G.nodes()}") + debug_print("基础转移协议生成为空,可能是路径问题") if from_vessel_id in G.nodes() and to_vessel_id in G.nodes(): try: path = nx.shortest_path(G, source=from_vessel_id, target=to_vessel_id) - debug_print(f" - 路径存在: {path}") + debug_print(f"路径存在: {path}") except Exception as path_error: - debug_print(f" - 无法找到路径: {str(path_error)}") + debug_print(f"无法找到路径: {str(path_error)}") return [ { "device_id": "system", "action_name": "log_message", "action_kwargs": { - "message": f"⚠️ 路径问题,无法转移: {final_volume}mL 从 {from_vessel_id} 到 {to_vessel_id}" + "message": f"路径问题,无法转移: {final_volume}mL 从 {from_vessel_id} 到 {to_vessel_id}" } } ] - debug_print(f"✅ 基础转移生成了 {len(pump_action_sequence)} 个动作") - - # 打印前几个动作用于调试 - if len(pump_action_sequence) > 0: - debug_print("🔍 前几个动作预览:") - for i, action in enumerate(pump_action_sequence[:3]): - debug_print(f" 动作 {i + 1}: {action}") - if len(pump_action_sequence) > 3: - debug_print(f" ... 还有 {len(pump_action_sequence) - 3} 个动作") - except Exception as e: - debug_print(f"❌ 基础转移失败: {str(e)}") + debug_print(f"基础转移失败: {str(e)}") import traceback debug_print(f"详细错误: {traceback.format_exc()}") return [ @@ -704,111 +567,82 @@ def generate_pump_protocol_with_rinsing( "device_id": "system", "action_name": "log_message", "action_kwargs": { - "message": f"❌ 转移失败: {final_volume}mL 从 {from_vessel_id} 到 {to_vessel_id}, 错误: {str(e)}" + "message": f"转移失败: {final_volume}mL 从 {from_vessel_id} 到 {to_vessel_id}, 错误: {str(e)}" } } ] # ========== 执行冲洗操作 ========== - - debug_print("🔧 步骤8: 检查冲洗操作...") - if final_rinsing_solvent and final_rinsing_solvent.strip() and final_rinsing_repeats > 0: - debug_print(f"🧽 开始冲洗操作,溶剂: '{final_rinsing_solvent}'") + debug_print(f"开始冲洗操作,溶剂: '{final_rinsing_solvent}'") try: if final_rinsing_solvent.strip() != "air": - debug_print(" - 执行液体冲洗...") rinsing_actions = _generate_rinsing_sequence( G, from_vessel_id, to_vessel_id, final_rinsing_solvent, final_rinsing_volume, final_rinsing_repeats, final_flowrate, final_transfer_flowrate ) pump_action_sequence.extend(rinsing_actions) - debug_print(f" - 添加了 {len(rinsing_actions)} 个冲洗动作") else: - debug_print(" - 执行空气冲洗...") air_rinsing_actions = _generate_air_rinsing_sequence( G, from_vessel_id, to_vessel_id, final_rinsing_volume, final_rinsing_repeats, final_flowrate, final_transfer_flowrate ) pump_action_sequence.extend(air_rinsing_actions) - debug_print(f" - 添加了 {len(air_rinsing_actions)} 个空气冲洗动作") except Exception as e: - debug_print(f"⚠️ 冲洗操作失败: {str(e)},跳过冲洗") + debug_print(f"冲洗操作失败: {str(e)},跳过冲洗") else: - debug_print(f"⏭️ 跳过冲洗操作") - debug_print(f" - 溶剂: '{final_rinsing_solvent}'") - debug_print(f" - 次数: {final_rinsing_repeats}") - debug_print(f" - 条件满足: {bool(final_rinsing_solvent and final_rinsing_solvent.strip() and final_rinsing_repeats > 0)}") + debug_print(f"跳过冲洗操作 (solvent='{final_rinsing_solvent}', repeats={final_rinsing_repeats})") # ========== 最终结果 ========== - - debug_print("=" * 60) - debug_print(f"🎉 PUMP_TRANSFER: 协议生成完成") - debug_print(f" 📊 总动作数: {len(pump_action_sequence)}") - debug_print(f" 📋 最终体积: {final_volume}mL") - debug_print(f" 🚀 执行路径: {from_vessel_id} -> {to_vessel_id}") + debug_print(f"PUMP_TRANSFER 完成: {from_vessel_id} -> {to_vessel_id}, " + f"volume={final_volume}mL, 动作数={len(pump_action_sequence)}") # 最终验证 if len(pump_action_sequence) == 0: - debug_print("🚨 协议生成结果为空!这是异常情况") + debug_print("协议生成结果为空!") return [ { "device_id": "system", "action_name": "log_message", "action_kwargs": { - "message": f"🚨 协议生成失败: 无法生成任何动作序列" + "message": "协议生成失败: 无法生成任何动作序列" } } ] - debug_print("=" * 60) return pump_action_sequence def _parse_amount_to_volume(amount: str) -> float: """解析 amount 字符串为体积""" - debug_print(f"🔍 解析 amount: '{amount}'") - if not amount: - debug_print(" - amount 为空,返回 0.0") return 0.0 amount = amount.lower().strip() - debug_print(f" - 处理后的 amount: '{amount}'") # 处理特殊关键词 if amount == "all": - debug_print(" - 检测到 'all',返回 0.0(需要后续处理)") return 0.0 # 返回0.0,让调用者处理 # 提取数字 import re numbers = re.findall(r'[\d.]+', amount) - debug_print(f" - 提取到的数字: {numbers}") if numbers: volume = float(numbers[0]) - debug_print(f" - 基础体积: {volume}") # 单位转换 if 'ml' in amount or 'milliliter' in amount: - debug_print(f" - 单位: mL,最终体积: {volume}") return volume elif 'l' in amount and 'ml' not in amount: - final_volume = volume * 1000 - debug_print(f" - 单位: L,最终体积: {final_volume}mL") - return final_volume + return volume * 1000 elif 'μl' in amount or 'microliter' in amount: - final_volume = volume / 1000 - debug_print(f" - 单位: μL,最终体积: {final_volume}mL") - return final_volume + return volume / 1000 else: - debug_print(f" - 无单位,假设为 mL: {volume}") - return volume + return volume # 默认mL - debug_print(" - 无法解析,返回 0.0") return 0.0 diff --git a/unilabos/compile/recrystallize_protocol.py b/unilabos/compile/recrystallize_protocol.py index f5e8e75d..182e0d0d 100644 --- a/unilabos/compile/recrystallize_protocol.py +++ b/unilabos/compile/recrystallize_protocol.py @@ -4,76 +4,64 @@ import logging from typing import List, Dict, Any, Tuple, Union from .utils.vessel_parser import get_vessel, find_solvent_vessel from .utils.unit_parser import parse_volume_input +from .utils.logger_util import debug_print from .pump_protocol import generate_pump_protocol_with_rinsing logger = logging.getLogger(__name__) -def debug_print(message): - """调试输出""" - logger.info(f"[RECRYSTALLIZE] {message}") - def parse_ratio(ratio_str: str) -> Tuple[float, float]: """ 解析比例字符串,支持多种格式 - + Args: ratio_str: 比例字符串(如 "1:1", "3:7", "50:50") - + Returns: Tuple[float, float]: 比例元组 (ratio1, ratio2) """ - debug_print(f"⚖️ 开始解析比例: '{ratio_str}' 📊") - try: - # 处理 "1:1", "3:7", "50:50" 等格式 if ":" in ratio_str: parts = ratio_str.split(":") if len(parts) == 2: ratio1 = float(parts[0]) ratio2 = float(parts[1]) - debug_print(f"✅ 冒号格式解析成功: {ratio1}:{ratio2} 🎯") return ratio1, ratio2 - - # 处理 "1-1", "3-7" 等格式 + if "-" in ratio_str: parts = ratio_str.split("-") if len(parts) == 2: ratio1 = float(parts[0]) ratio2 = float(parts[1]) - debug_print(f"✅ 横线格式解析成功: {ratio1}:{ratio2} 🎯") return ratio1, ratio2 - - # 处理 "1,1", "3,7" 等格式 + if "," in ratio_str: parts = ratio_str.split(",") if len(parts) == 2: ratio1 = float(parts[0]) ratio2 = float(parts[1]) - debug_print(f"✅ 逗号格式解析成功: {ratio1}:{ratio2} 🎯") return ratio1, ratio2 - - # 默认 1:1 - debug_print(f"⚠️ 无法解析比例 '{ratio_str}',使用默认比例 1:1 🎭") + + debug_print(f"无法解析比例 '{ratio_str}',使用默认比例 1:1") return 1.0, 1.0 - + except ValueError: - debug_print(f"❌ 比例解析错误 '{ratio_str}',使用默认比例 1:1 🎭") + debug_print(f"比例解析错误 '{ratio_str}',使用默认比例 1:1") return 1.0, 1.0 def generate_recrystallize_protocol( G: nx.DiGraph, - vessel: dict, # 🔧 修改:从字符串改为字典类型 + vessel: dict, ratio: str, solvent1: str, solvent2: str, - volume: Union[str, float], # 支持字符串和数值 + volume: Union[str, float], **kwargs ) -> List[Dict[str, Any]]: """ 生成重结晶协议序列 - 支持vessel字典和体积运算 - + Args: G: 有向图,节点为容器和设备 vessel: 目标容器字典(从XDL传入) @@ -82,28 +70,18 @@ def generate_recrystallize_protocol( solvent2: 第二种溶剂名称 volume: 总体积(支持 "100 mL", "50", "2.5 L" 等) **kwargs: 其他可选参数 - + Returns: List[Dict[str, Any]]: 动作序列 """ - - # 🔧 核心修改:从字典中提取容器ID + vessel_id, vessel_data = get_vessel(vessel) - + action_sequence = [] - - debug_print("💎" * 20) - debug_print("🚀 开始生成重结晶协议(支持vessel字典和体积运算)✨") - debug_print(f"📝 输入参数:") - debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})") - debug_print(f" ⚖️ 比例: {ratio}") - debug_print(f" 🧪 溶剂1: {solvent1}") - debug_print(f" 🧪 溶剂2: {solvent2}") - debug_print(f" 💧 总体积: {volume} (类型: {type(volume)})") - debug_print("💎" * 20) - - # 🔧 新增:记录重结晶前的容器状态 - debug_print("🔍 记录重结晶前容器状态...") + + debug_print(f"开始生成重结晶协议: vessel={vessel_id}, ratio={ratio}, solvent1={solvent1}, solvent2={solvent2}, volume={volume}") + + # 记录重结晶前的容器状态 original_liquid_volume = 0.0 if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] @@ -111,102 +89,73 @@ def generate_recrystallize_protocol( original_liquid_volume = current_volume[0] elif isinstance(current_volume, (int, float)): original_liquid_volume = current_volume - debug_print(f"📊 重结晶前液体体积: {original_liquid_volume:.2f}mL") - + # 1. 验证目标容器存在 - debug_print("📍 步骤1: 验证目标容器... 🔧") - if vessel_id not in G.nodes(): # 🔧 使用 vessel_id - debug_print(f"❌ 目标容器 '{vessel_id}' 不存在于系统中! 😱") + if vessel_id not in G.nodes(): raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中") - debug_print(f"✅ 目标容器 '{vessel_id}' 验证通过 🎯") - + # 2. 解析体积(支持单位) - debug_print("📍 步骤2: 解析体积(支持单位)... 💧") final_volume = parse_volume_input(volume, "mL") - debug_print(f"🎯 体积解析完成: {volume} → {final_volume}mL ✨") - + debug_print(f"体积解析: {volume} -> {final_volume}mL") + # 3. 解析比例 - debug_print("📍 步骤3: 解析比例... ⚖️") ratio1, ratio2 = parse_ratio(ratio) total_ratio = ratio1 + ratio2 - debug_print(f"🎯 比例解析完成: {ratio1}:{ratio2} (总比例: {total_ratio}) ✨") - + # 4. 计算各溶剂体积 - debug_print("📍 步骤4: 计算各溶剂体积... 🧮") volume1 = final_volume * (ratio1 / total_ratio) volume2 = final_volume * (ratio2 / total_ratio) - - debug_print(f"🧪 {solvent1} 体积: {volume1:.2f} mL ({ratio1}/{total_ratio} × {final_volume})") - debug_print(f"🧪 {solvent2} 体积: {volume2:.2f} mL ({ratio2}/{total_ratio} × {final_volume})") - debug_print(f"✅ 体积计算完成: 总计 {volume1 + volume2:.2f} mL 🎯") - + + debug_print(f"溶剂体积: {solvent1}={volume1:.2f}mL, {solvent2}={volume2:.2f}mL") + # 5. 查找溶剂容器 - debug_print("📍 步骤5: 查找溶剂容器... 🔍") try: - debug_print(f" 🔍 查找溶剂1容器...") solvent1_vessel = find_solvent_vessel(G, solvent1) - debug_print(f" 🎉 找到溶剂1容器: {solvent1_vessel} ✨") except ValueError as e: - debug_print(f" ❌ 溶剂1容器查找失败: {str(e)} 😭") raise ValueError(f"无法找到溶剂1 '{solvent1}': {str(e)}") - + try: - debug_print(f" 🔍 查找溶剂2容器...") solvent2_vessel = find_solvent_vessel(G, solvent2) - debug_print(f" 🎉 找到溶剂2容器: {solvent2_vessel} ✨") except ValueError as e: - debug_print(f" ❌ 溶剂2容器查找失败: {str(e)} 😭") raise ValueError(f"无法找到溶剂2 '{solvent2}': {str(e)}") - + # 6. 验证路径存在 - debug_print("📍 步骤6: 验证传输路径... 🛤️") try: - path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel_id) # 🔧 使用 vessel_id - debug_print(f" 🛤️ 溶剂1路径: {' → '.join(path1)} ✅") + path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel_id) except nx.NetworkXNoPath: - debug_print(f" ❌ 溶剂1路径不可达: {solvent1_vessel} → {vessel_id} 😞") raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel_id}' 没有可用路径") - + try: - path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel_id) # 🔧 使用 vessel_id - debug_print(f" 🛤️ 溶剂2路径: {' → '.join(path2)} ✅") + path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel_id) except nx.NetworkXNoPath: - debug_print(f" ❌ 溶剂2路径不可达: {solvent2_vessel} → {vessel_id} 😞") raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel_id}' 没有可用路径") - + # 7. 添加第一种溶剂 - debug_print("📍 步骤7: 添加第一种溶剂... 🧪") - debug_print(f" 🚰 开始添加溶剂1: {solvent1} ({volume1:.2f} mL)") - try: pump_actions1 = generate_pump_protocol_with_rinsing( G=G, from_vessel=solvent1_vessel, - to_vessel=vessel_id, # 🔧 使用 vessel_id - volume=volume1, # 使用解析后的体积 + to_vessel=vessel_id, + volume=volume1, amount="", time=0.0, viscous=False, - rinsing_solvent="", # 重结晶不需要清洗 + rinsing_solvent="", rinsing_volume=0.0, rinsing_repeats=0, solid=False, - flowrate=2.0, # 正常流速 + flowrate=2.0, transfer_flowrate=0.5 ) - + action_sequence.extend(pump_actions1) - debug_print(f" ✅ 溶剂1泵送动作已添加: {len(pump_actions1)} 个动作 🚰✨") - + except Exception as e: - debug_print(f" ❌ 溶剂1泵协议生成失败: {str(e)} 😭") raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}") - - # 🔧 新增:更新容器体积 - 添加溶剂1后 - debug_print(" 🔧 更新容器体积 - 添加溶剂1后...") + + # 更新容器体积 - 添加溶剂1后 new_volume_after_solvent1 = original_liquid_volume + volume1 - - # 更新vessel字典中的体积 + if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): @@ -216,15 +165,14 @@ def generate_recrystallize_protocol( vessel["data"]["liquid_volume"] = [new_volume_after_solvent1] else: vessel["data"]["liquid_volume"] = new_volume_after_solvent1 - - # 同时更新图中的容器数据 + if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} - + vessel_node_data = G.nodes[vessel_id]['data'] current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - + if isinstance(current_node_volume, list): if len(current_node_volume) > 0: G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume_after_solvent1 @@ -232,53 +180,42 @@ def generate_recrystallize_protocol( G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume_after_solvent1] else: G.nodes[vessel_id]['data']['liquid_volume'] = new_volume_after_solvent1 - - debug_print(f" 📊 体积更新: {original_liquid_volume:.2f}mL + {volume1:.2f}mL = {new_volume_after_solvent1:.2f}mL") - + # 8. 等待溶剂1稳定 - debug_print(" ⏳ 添加溶剂1稳定等待...") action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 5.0, # 缩短等待时间 + "time": 5.0, "description": f"等待溶剂1 {solvent1} 稳定" } }) - debug_print(" ✅ 溶剂1稳定等待已添加 ⏰✨") - + # 9. 添加第二种溶剂 - debug_print("📍 步骤8: 添加第二种溶剂... 🧪") - debug_print(f" 🚰 开始添加溶剂2: {solvent2} ({volume2:.2f} mL)") - try: pump_actions2 = generate_pump_protocol_with_rinsing( G=G, from_vessel=solvent2_vessel, - to_vessel=vessel_id, # 🔧 使用 vessel_id - volume=volume2, # 使用解析后的体积 + to_vessel=vessel_id, + volume=volume2, amount="", time=0.0, viscous=False, - rinsing_solvent="", # 重结晶不需要清洗 + rinsing_solvent="", rinsing_volume=0.0, rinsing_repeats=0, solid=False, - flowrate=2.0, # 正常流速 + flowrate=2.0, transfer_flowrate=0.5 ) - + action_sequence.extend(pump_actions2) - debug_print(f" ✅ 溶剂2泵送动作已添加: {len(pump_actions2)} 个动作 🚰✨") - + except Exception as e: - debug_print(f" ❌ 溶剂2泵协议生成失败: {str(e)} 😭") raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}") - - # 🔧 新增:更新容器体积 - 添加溶剂2后 - debug_print(" 🔧 更新容器体积 - 添加溶剂2后...") + + # 更新容器体积 - 添加溶剂2后 final_liquid_volume = new_volume_after_solvent1 + volume2 - - # 更新vessel字典中的体积 + if "data" in vessel and "liquid_volume" in vessel["data"]: current_volume = vessel["data"]["liquid_volume"] if isinstance(current_volume, list): @@ -288,15 +225,14 @@ def generate_recrystallize_protocol( vessel["data"]["liquid_volume"] = [final_liquid_volume] else: vessel["data"]["liquid_volume"] = final_liquid_volume - - # 同时更新图中的容器数据 + if vessel_id in G.nodes(): if 'data' not in G.nodes[vessel_id]: G.nodes[vessel_id]['data'] = {} - + vessel_node_data = G.nodes[vessel_id]['data'] current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - + if isinstance(current_node_volume, list): if len(current_node_volume) > 0: G.nodes[vessel_id]['data']['liquid_volume'][0] = final_liquid_volume @@ -304,36 +240,25 @@ def generate_recrystallize_protocol( G.nodes[vessel_id]['data']['liquid_volume'] = [final_liquid_volume] else: G.nodes[vessel_id]['data']['liquid_volume'] = final_liquid_volume - - debug_print(f" 📊 最终体积: {new_volume_after_solvent1:.2f}mL + {volume2:.2f}mL = {final_liquid_volume:.2f}mL") - + # 10. 等待溶剂2稳定 - debug_print(" ⏳ 添加溶剂2稳定等待...") action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 5.0, # 缩短等待时间 + "time": 5.0, "description": f"等待溶剂2 {solvent2} 稳定" } }) - debug_print(" ✅ 溶剂2稳定等待已添加 ⏰✨") - + # 11. 等待重结晶完成 - debug_print("📍 步骤9: 等待重结晶完成... 💎") - - # 模拟运行时间优化 - debug_print(" ⏱️ 检查模拟运行时间限制...") - original_crystallize_time = 600.0 # 原始重结晶时间 - simulation_time_limit = 60.0 # 模拟运行时间限制:60秒 + original_crystallize_time = 600.0 + simulation_time_limit = 60.0 final_crystallize_time = min(original_crystallize_time, simulation_time_limit) - + if original_crystallize_time > simulation_time_limit: - debug_print(f" 🎮 模拟运行优化: {original_crystallize_time}s → {final_crystallize_time}s ⚡") - debug_print(f" 📊 时间缩短: {original_crystallize_time/60:.1f}分钟 → {final_crystallize_time/60:.1f}分钟 🚀") - else: - debug_print(f" ✅ 时间在限制内: {final_crystallize_time}s 保持不变 🎯") - + debug_print(f"模拟运行优化: {original_crystallize_time}s -> {final_crystallize_time}s") + action_sequence.append({ "action_name": "wait", "action_kwargs": { @@ -341,50 +266,28 @@ def generate_recrystallize_protocol( "description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio},总体积 {final_volume}mL)" + (f" (模拟时间)" if original_crystallize_time != final_crystallize_time else "") } }) - debug_print(f" ✅ 重结晶等待已添加: {final_crystallize_time}s 💎✨") - - # 显示时间调整信息 - if original_crystallize_time != final_crystallize_time: - debug_print(f" 🎭 模拟优化说明: 原计划 {original_crystallize_time/60:.1f}分钟,实际模拟 {final_crystallize_time/60:.1f}分钟 ⚡") - - # 总结 - debug_print("💎" * 20) - debug_print(f"🎉 重结晶协议生成完成! ✨") - debug_print(f"📊 总动作数: {len(action_sequence)} 个") - debug_print(f"🥽 目标容器: {vessel_id}") - debug_print(f"💧 总体积变化:") - debug_print(f" - 原始体积: {original_liquid_volume:.2f}mL") - debug_print(f" - 添加溶剂: {final_volume:.2f}mL") - debug_print(f" - 最终体积: {final_liquid_volume:.2f}mL") - debug_print(f"⚖️ 溶剂比例: {solvent1}:{solvent2} = {ratio1}:{ratio2}") - debug_print(f"🧪 溶剂1: {solvent1} ({volume1:.2f}mL)") - debug_print(f"🧪 溶剂2: {solvent2} ({volume2:.2f}mL)") - debug_print(f"⏱️ 预计总时间: {(final_crystallize_time + 10)/60:.1f} 分钟 ⌛") - debug_print("💎" * 20) - + + debug_print(f"重结晶协议生成完成: {len(action_sequence)} 个动作, 容器={vessel_id}, 体积变化: {original_liquid_volume:.2f} -> {final_liquid_volume:.2f}mL") + return action_sequence # 测试函数 def test_recrystallize_protocol(): """测试重结晶协议""" - debug_print("🧪 === RECRYSTALLIZE PROTOCOL 测试 === ✨") - - # 测试体积解析 - debug_print("💧 测试体积解析...") + debug_print("=== RECRYSTALLIZE PROTOCOL 测试 ===") + test_volumes = ["100 mL", "2.5 L", "500", "50.5", "?", "invalid"] for vol in test_volumes: parsed = parse_volume_input(vol) - debug_print(f" 📊 体积 '{vol}' -> {parsed}mL") - - # 测试比例解析 - debug_print("⚖️ 测试比例解析...") + debug_print(f"体积 '{vol}' -> {parsed}mL") + test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"] for ratio in test_ratios: r1, r2 = parse_ratio(ratio) - debug_print(f" 📊 比例 '{ratio}' -> {r1}:{r2}") - - debug_print("✅ 测试完成 🎉") + debug_print(f"比例 '{ratio}' -> {r1}:{r2}") + + debug_print("测试完成") if __name__ == "__main__": - test_recrystallize_protocol() \ No newline at end of file + test_recrystallize_protocol() diff --git a/unilabos/compile/reset_handling_protocol.py b/unilabos/compile/reset_handling_protocol.py index 2024676d..4206d725 100644 --- a/unilabos/compile/reset_handling_protocol.py +++ b/unilabos/compile/reset_handling_protocol.py @@ -1,253 +1,87 @@ import networkx as nx import logging -import sys from typing import List, Dict, Any, Optional +from .utils.logger_util import debug_print, action_log +from .utils.vessel_parser import find_solvent_vessel from .pump_protocol import generate_pump_protocol_with_rinsing # 设置日志 logger = logging.getLogger(__name__) -# 确保输出编码为UTF-8 -if hasattr(sys.stdout, 'reconfigure'): - try: - sys.stdout.reconfigure(encoding='utf-8') - sys.stderr.reconfigure(encoding='utf-8') - except: - pass - -def debug_print(message): - """调试输出函数 - 支持中文""" - try: - # 确保消息是字符串格式 - safe_message = str(message) - print(f"[重置处理] {safe_message}", flush=True) - logger.info(f"[重置处理] {safe_message}") - except UnicodeEncodeError: - # 如果编码失败,尝试替换不支持的字符 - safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8') - print(f"[重置处理] {safe_message}", flush=True) - logger.info(f"[重置处理] {safe_message}") - except Exception as e: - # 最后的安全措施 - fallback_message = f"日志输出错误: {repr(message)}" - print(f"[重置处理] {fallback_message}", flush=True) - logger.info(f"[重置处理] {fallback_message}") - -def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]: - """创建一个动作日志 - 支持中文和emoji""" - try: - full_message = f"{emoji} {message}" - debug_print(full_message) - logger.info(full_message) - - return { - "action_name": "wait", - "action_kwargs": { - "time": 0.1, - "log_message": full_message, - "progress_message": full_message - } - } - except Exception as e: - # 如果emoji有问题,使用纯文本 - safe_message = f"[日志] {message}" - debug_print(safe_message) - logger.info(safe_message) - - return { - "action_name": "wait", - "action_kwargs": { - "time": 0.1, - "log_message": safe_message, - "progress_message": safe_message - } - } - -def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: - """ - 查找溶剂容器,支持多种匹配模式 - - Args: - G: 网络图 - solvent: 溶剂名称(如 "methanol", "ethanol", "water") - - Returns: - str: 溶剂容器ID - """ - debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器...") - - # 构建可能的容器名称 - possible_names = [ - f"flask_{solvent}", # flask_methanol - f"bottle_{solvent}", # bottle_methanol - f"reagent_{solvent}", # reagent_methanol - f"reagent_bottle_{solvent}", # reagent_bottle_methanol - f"{solvent}_flask", # methanol_flask - f"{solvent}_bottle", # methanol_bottle - f"{solvent}", # methanol - f"vessel_{solvent}", # vessel_methanol - ] - - debug_print(f"🎯 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个)") - - # 第一步:通过容器名称匹配 - debug_print("📋 方法1: 精确名称匹配...") - for vessel_name in possible_names: - if vessel_name in G.nodes(): - debug_print(f"✅ 通过名称匹配找到容器: {vessel_name}") - return vessel_name - debug_print("⚠️ 精确名称匹配失败,尝试模糊匹配...") - - # 第二步:通过模糊匹配 - debug_print("📋 方法2: 模糊名称匹配...") - for node_id in G.nodes(): - if G.nodes[node_id].get('type') == 'container': - node_name = G.nodes[node_id].get('name', '').lower() - - # 检查是否包含溶剂名称 - if solvent.lower() in node_id.lower() or solvent.lower() in node_name: - debug_print(f"✅ 通过模糊匹配找到容器: {node_id}") - return node_id - debug_print("⚠️ 模糊匹配失败,尝试液体类型匹配...") - - # 第三步:通过液体类型匹配 - debug_print("📋 方法3: 液体类型匹配...") - for node_id in G.nodes(): - if G.nodes[node_id].get('type') == 'container': - vessel_data = G.nodes[node_id].get('data', {}) - liquids = vessel_data.get('liquid', []) - - for liquid in liquids: - if isinstance(liquid, dict): - liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower() - reagent_name = vessel_data.get('reagent_name', '').lower() - - if solvent.lower() in liquid_type or solvent.lower() in reagent_name: - debug_print(f"✅ 通过液体类型匹配找到容器: {node_id}") - return node_id - - # 列出可用容器帮助调试 - debug_print("📊 显示可用容器信息...") - available_containers = [] - for node_id in G.nodes(): - if G.nodes[node_id].get('type') == 'container': - vessel_data = G.nodes[node_id].get('data', {}) - liquids = vessel_data.get('liquid', []) - liquid_types = [liquid.get('liquid_type', '') or liquid.get('name', '') - for liquid in liquids if isinstance(liquid, dict)] - - available_containers.append({ - 'id': node_id, - 'name': G.nodes[node_id].get('name', ''), - 'liquids': liquid_types, - 'reagent_name': vessel_data.get('reagent_name', '') - }) - - debug_print(f"📋 可用容器列表 (共{len(available_containers)}个):") - for i, container in enumerate(available_containers[:5]): # 只显示前5个 - debug_print(f" {i+1}. 🥽 {container['id']}: {container['name']}") - debug_print(f" 💧 液体: {container['liquids']}") - debug_print(f" 🧪 试剂: {container['reagent_name']}") - - if len(available_containers) > 5: - debug_print(f" ... 还有 {len(available_containers)-5} 个容器") - - debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器") - raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names[:3]}...") +create_action_log = action_log def generate_reset_handling_protocol( G: nx.DiGraph, solvent: str, - vessel: Optional[str] = None, # 🆕 新增可选vessel参数 - **kwargs # 接收其他可能的参数但不使用 + vessel: Optional[str] = None, + **kwargs ) -> List[Dict[str, Any]]: """ 生成重置处理协议序列 - 支持自定义容器 - + Args: G: 有向图,节点为容器和设备 solvent: 溶剂名称(从XDL传入) vessel: 目标容器名称(可选,默认为 "main_reactor") **kwargs: 其他可选参数,但不使用 - + Returns: List[Dict[str, Any]]: 动作序列 """ action_sequence = [] - - # 🔧 修改:支持自定义vessel参数 - target_vessel = vessel if vessel is not None else "main_reactor" # 默认目标容器 - volume = 50.0 # 默认体积 50 mL - debug_print("=" * 60) - debug_print("🚀 开始生成重置处理协议") - debug_print(f"📋 输入参数:") - debug_print(f" 🧪 溶剂: {solvent}") - debug_print(f" 🥽 目标容器: {target_vessel} {'(默认)' if vessel is None else '(指定)'}") - debug_print(f" 💧 体积: {volume} mL") - debug_print(f" ⚙️ 其他参数: {kwargs}") - debug_print("=" * 60) - + target_vessel = vessel if vessel is not None else "main_reactor" + volume = 50.0 + + debug_print(f"开始生成重置处理协议: solvent={solvent}, vessel={target_vessel}, volume={volume}mL") + # 添加初始日志 - action_sequence.append(create_action_log(f"开始重置处理操作 - 容器: {target_vessel}", "🎬")) - action_sequence.append(create_action_log(f"使用溶剂: {solvent}", "🧪")) - action_sequence.append(create_action_log(f"重置体积: {volume}mL", "💧")) - + action_sequence.append(action_log(f"开始重置处理操作 - 容器: {target_vessel}", "🎬")) + action_sequence.append(action_log(f"使用溶剂: {solvent}", "🧪")) + action_sequence.append(action_log(f"重置体积: {volume}mL", "💧")) + if vessel is None: - action_sequence.append(create_action_log("使用默认目标容器: main_reactor", "⚙️")) + action_sequence.append(action_log("使用默认目标容器: main_reactor", "⚙️")) else: - action_sequence.append(create_action_log(f"使用指定目标容器: {vessel}", "🎯")) - + action_sequence.append(action_log(f"使用指定目标容器: {vessel}", "🎯")) + # 1. 验证目标容器存在 - debug_print("🔍 步骤1: 验证目标容器...") - action_sequence.append(create_action_log("正在验证目标容器...", "🔍")) - + action_sequence.append(action_log("正在验证目标容器...", "🔍")) + if target_vessel not in G.nodes(): - debug_print(f"❌ 目标容器 '{target_vessel}' 不存在于系统中!") - action_sequence.append(create_action_log(f"目标容器 '{target_vessel}' 不存在", "❌")) + action_sequence.append(action_log(f"目标容器 '{target_vessel}' 不存在", "❌")) raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中") - - debug_print(f"✅ 目标容器 '{target_vessel}' 验证通过") - action_sequence.append(create_action_log(f"目标容器验证通过: {target_vessel}", "✅")) - + + action_sequence.append(action_log(f"目标容器验证通过: {target_vessel}", "✅")) + # 2. 查找溶剂容器 - debug_print("🔍 步骤2: 查找溶剂容器...") - action_sequence.append(create_action_log("正在查找溶剂容器...", "🔍")) - + action_sequence.append(action_log("正在查找溶剂容器...", "🔍")) + try: solvent_vessel = find_solvent_vessel(G, solvent) - debug_print(f"✅ 找到溶剂容器: {solvent_vessel}") - action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "✅")) + debug_print(f"找到溶剂容器: {solvent_vessel}") + action_sequence.append(action_log(f"找到溶剂容器: {solvent_vessel}", "✅")) except ValueError as e: - debug_print(f"❌ 溶剂容器查找失败: {str(e)}") - action_sequence.append(create_action_log(f"溶剂容器查找失败: {str(e)}", "❌")) + action_sequence.append(action_log(f"溶剂容器查找失败: {str(e)}", "❌")) raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}") - + # 3. 验证路径存在 - debug_print("🔍 步骤3: 验证传输路径...") - action_sequence.append(create_action_log("正在验证传输路径...", "🛤️")) - + action_sequence.append(action_log("正在验证传输路径...", "🛤️")) + try: path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel) - debug_print(f"✅ 找到路径: {' → '.join(path)}") - action_sequence.append(create_action_log(f"传输路径: {' → '.join(path)}", "🛤️")) + action_sequence.append(action_log(f"传输路径: {' → '.join(path)}", "🛤️")) except nx.NetworkXNoPath: - debug_print(f"❌ 路径不可达: {solvent_vessel} → {target_vessel}") - action_sequence.append(create_action_log(f"路径不可达: {solvent_vessel} → {target_vessel}", "❌")) + action_sequence.append(action_log(f"路径不可达: {solvent_vessel} → {target_vessel}", "❌")) raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径") - + # 4. 使用pump_protocol转移溶剂 - debug_print("🔍 步骤4: 转移溶剂...") - action_sequence.append(create_action_log("开始溶剂转移操作...", "🚰")) - - debug_print(f"🚛 开始转移: {solvent_vessel} → {target_vessel}") - debug_print(f"💧 转移体积: {volume} mL") - action_sequence.append(create_action_log(f"转移: {solvent_vessel} → {target_vessel} ({volume}mL)", "🚛")) - + action_sequence.append(action_log("开始溶剂转移操作...", "🚰")) + action_sequence.append(action_log(f"转移: {solvent_vessel} → {target_vessel} ({volume}mL)", "🚛")) + try: - debug_print("🔄 生成泵送协议...") - action_sequence.append(create_action_log("正在生成泵送协议...", "🔄")) - + action_sequence.append(action_log("正在生成泵送协议...", "🔄")) + pump_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=solvent_vessel, @@ -256,41 +90,34 @@ def generate_reset_handling_protocol( amount="", time=0.0, viscous=False, - rinsing_solvent="", # 重置处理不需要清洗 + rinsing_solvent="", rinsing_volume=0.0, rinsing_repeats=0, solid=False, - flowrate=2.5, # 正常流速 - transfer_flowrate=0.5 # 正常转移流速 + flowrate=2.5, + transfer_flowrate=0.5 ) - + action_sequence.extend(pump_actions) - debug_print(f"✅ 泵送协议已添加: {len(pump_actions)} 个动作") - action_sequence.append(create_action_log(f"泵送协议完成 ({len(pump_actions)} 个操作)", "✅")) - + debug_print(f"泵送协议已添加: {len(pump_actions)} 个动作") + action_sequence.append(action_log(f"泵送协议完成 ({len(pump_actions)} 个操作)", "✅")) + except Exception as e: - debug_print(f"❌ 泵送协议生成失败: {str(e)}") - action_sequence.append(create_action_log(f"泵送协议生成失败: {str(e)}", "❌")) + action_sequence.append(action_log(f"泵送协议生成失败: {str(e)}", "❌")) raise ValueError(f"生成泵协议时出错: {str(e)}") - + # 5. 等待溶剂稳定 - debug_print("🔍 步骤5: 等待溶剂稳定...") - action_sequence.append(create_action_log("等待溶剂稳定...", "⏳")) - - # 模拟运行时间优化 - debug_print("⏱️ 检查模拟运行时间限制...") - original_wait_time = 10.0 # 原始等待时间 - simulation_time_limit = 5.0 # 模拟运行时间限制:5秒 - + action_sequence.append(action_log("等待溶剂稳定...", "⏳")) + + original_wait_time = 10.0 + simulation_time_limit = 5.0 final_wait_time = min(original_wait_time, simulation_time_limit) - + if original_wait_time > simulation_time_limit: - debug_print(f"🎮 模拟运行优化: {original_wait_time}s → {final_wait_time}s") - action_sequence.append(create_action_log(f"时间优化: {original_wait_time}s → {final_wait_time}s", "⚡")) + action_sequence.append(action_log(f"时间优化: {original_wait_time}s → {final_wait_time}s", "⚡")) else: - debug_print(f"✅ 时间在限制内: {final_wait_time}s 保持不变") - action_sequence.append(create_action_log(f"等待时间: {final_wait_time}s", "⏰")) - + action_sequence.append(action_log(f"等待时间: {final_wait_time}s", "⏰")) + action_sequence.append({ "action_name": "wait", "action_kwargs": { @@ -298,90 +125,50 @@ def generate_reset_handling_protocol( "description": f"等待溶剂 {solvent} 在容器 {target_vessel} 中稳定" + (f" (模拟时间)" if original_wait_time != final_wait_time else "") } }) - debug_print(f"✅ 稳定等待已添加: {final_wait_time}s") - - # 显示时间调整信息 + if original_wait_time != final_wait_time: - debug_print(f"🎭 模拟优化说明: 原计划 {original_wait_time}s,实际模拟 {final_wait_time}s") - action_sequence.append(create_action_log("应用模拟时间优化", "🎭")) - + action_sequence.append(action_log("应用模拟时间优化", "🎭")) + # 总结 - debug_print("=" * 60) - debug_print(f"🎉 重置处理协议生成完成!") - debug_print(f"📊 总结信息:") - debug_print(f" 📋 总动作数: {len(action_sequence)} 个") - debug_print(f" 🧪 溶剂: {solvent}") - debug_print(f" 🥽 源容器: {solvent_vessel}") - debug_print(f" 🥽 目标容器: {target_vessel} {'(默认)' if vessel is None else '(指定)'}") - debug_print(f" 💧 转移体积: {volume} mL") - debug_print(f" ⏱️ 预计总时间: {(final_wait_time + 5):.0f} 秒") - debug_print(f" 🎯 操作结果: 已添加 {volume} mL {solvent} 到 {target_vessel}") - debug_print("=" * 60) - - # 添加完成日志 + debug_print(f"重置处理协议生成完成: {len(action_sequence)} 个动作, {solvent_vessel} -> {target_vessel}, {volume}mL") + summary_msg = f"重置处理完成: {target_vessel} (使用 {volume}mL {solvent})" if vessel is None: summary_msg += " [默认容器]" else: summary_msg += " [指定容器]" - - action_sequence.append(create_action_log(summary_msg, "🎉")) - + + action_sequence.append(action_log(summary_msg, "🎉")) + return action_sequence # === 便捷函数 === def reset_main_reactor(G: nx.DiGraph, solvent: str = "methanol", **kwargs) -> List[Dict[str, Any]]: """重置主反应器 (默认行为)""" - debug_print(f"🔄 重置主反应器,使用溶剂: {solvent}") return generate_reset_handling_protocol(G, solvent=solvent, vessel=None, **kwargs) def reset_custom_vessel(G: nx.DiGraph, vessel: str, solvent: str = "methanol", **kwargs) -> List[Dict[str, Any]]: """重置指定容器""" - debug_print(f"🔄 重置指定容器: {vessel},使用溶剂: {solvent}") return generate_reset_handling_protocol(G, solvent=solvent, vessel=vessel, **kwargs) def reset_with_water(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]: """使用水重置容器""" - target = vessel or "main_reactor" - debug_print(f"💧 使用水重置容器: {target}") return generate_reset_handling_protocol(G, solvent="water", vessel=vessel, **kwargs) def reset_with_methanol(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]: """使用甲醇重置容器""" - target = vessel or "main_reactor" - debug_print(f"🧪 使用甲醇重置容器: {target}") return generate_reset_handling_protocol(G, solvent="methanol", vessel=vessel, **kwargs) def reset_with_ethanol(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]: """使用乙醇重置容器""" - target = vessel or "main_reactor" - debug_print(f"🧪 使用乙醇重置容器: {target}") return generate_reset_handling_protocol(G, solvent="ethanol", vessel=vessel, **kwargs) # 测试函数 def test_reset_handling_protocol(): """测试重置处理协议""" - debug_print("=== 重置处理协议增强中文版测试 ===") - - # 测试溶剂名称 - debug_print("🧪 测试常用溶剂名称...") - test_solvents = ["methanol", "ethanol", "water", "acetone", "dmso"] - for solvent in test_solvents: - debug_print(f" 🔍 测试溶剂: {solvent}") - - # 测试容器参数 - debug_print("🥽 测试容器参数...") - test_cases = [ - {"solvent": "methanol", "vessel": None, "desc": "默认容器"}, - {"solvent": "ethanol", "vessel": "reactor_2", "desc": "指定容器"}, - {"solvent": "water", "vessel": "flask_1", "desc": "自定义容器"} - ] - - for case in test_cases: - debug_print(f" 🧪 测试案例: {case['desc']} - {case['solvent']} -> {case['vessel'] or 'main_reactor'}") - - debug_print("✅ 测试完成") + debug_print("=== 重置处理协议测试 ===") + debug_print("测试完成") if __name__ == "__main__": - test_reset_handling_protocol() \ No newline at end of file + test_reset_handling_protocol() diff --git a/unilabos/compile/run_column_protocol.py b/unilabos/compile/run_column_protocol.py index 9da84150..45154fc0 100644 --- a/unilabos/compile/run_column_protocol.py +++ b/unilabos/compile/run_column_protocol.py @@ -2,60 +2,54 @@ from typing import List, Dict, Any, Union import networkx as nx import logging import re -from .utils.vessel_parser import get_vessel +from .utils.vessel_parser import get_vessel, find_solvent_vessel +from .utils.resource_helper import get_resource_id, get_resource_data, get_resource_liquid_volume, update_vessel_volume +from .utils.logger_util import debug_print from .pump_protocol import generate_pump_protocol_with_rinsing logger = logging.getLogger(__name__) -def debug_print(message): - """调试输出""" - logger.info(f"[RUN_COLUMN] {message}") - def parse_percentage(pct_str: str) -> float: """ 解析百分比字符串为数值 - + Args: pct_str: 百分比字符串(如 "40 %", "40%", "40") - + Returns: float: 百分比数值(0-100) """ if not pct_str or not pct_str.strip(): return 0.0 - + pct_str = pct_str.strip().lower() - debug_print(f"🔍 解析百分比: '{pct_str}'") - + # 移除百分号和空格 pct_clean = re.sub(r'[%\s]', '', pct_str) - - # 提取数字 + match = re.search(r'([0-9]*\.?[0-9]+)', pct_clean) if match: value = float(match.group(1)) - debug_print(f"✅ 百分比解析结果: {value}%") return value - - debug_print(f"⚠️ 无法解析百分比: '{pct_str}',返回0.0") + + debug_print(f"无法解析百分比: '{pct_str}',返回0.0") return 0.0 def parse_ratio(ratio_str: str) -> tuple: """ 解析比例字符串为两个数值 - + Args: ratio_str: 比例字符串(如 "5:95", "1:1", "40:60") - + Returns: - tuple: (ratio1, ratio2) 两个比例值 + tuple: (ratio1, ratio2) 两个比例值(百分比) """ if not ratio_str or not ratio_str.strip(): - return (50.0, 50.0) # 默认1:1 - + return (50.0, 50.0) + ratio_str = ratio_str.strip() - debug_print(f"🔍 解析比例: '{ratio_str}'") - + # 支持多种分隔符:: / - if ':' in ratio_str: parts = ratio_str.split(':') @@ -66,101 +60,82 @@ def parse_ratio(ratio_str: str) -> tuple: elif 'to' in ratio_str.lower(): parts = ratio_str.lower().split('to') else: - debug_print(f"⚠️ 无法解析比例格式: '{ratio_str}',使用默认1:1") + debug_print(f"无法解析比例格式: '{ratio_str}',使用默认1:1") return (50.0, 50.0) - + if len(parts) >= 2: try: ratio1 = float(parts[0].strip()) ratio2 = float(parts[1].strip()) total = ratio1 + ratio2 - - # 转换为百分比 + pct1 = (ratio1 / total) * 100 pct2 = (ratio2 / total) * 100 - - debug_print(f"✅ 比例解析结果: {ratio1}:{ratio2} -> {pct1:.1f}%:{pct2:.1f}%") + return (pct1, pct2) except ValueError as e: - debug_print(f"⚠️ 比例数值转换失败: {str(e)}") - - debug_print(f"⚠️ 比例解析失败,使用默认1:1") + debug_print(f"比例数值转换失败: {str(e)}") + + debug_print(f"比例解析失败,使用默认1:1") return (50.0, 50.0) def parse_rf_value(rf_str: str) -> float: """ 解析Rf值字符串 - + Args: rf_str: Rf值字符串(如 "0.3", "0.45", "?") - + Returns: float: Rf值(0-1) """ if not rf_str or not rf_str.strip(): - return 0.3 # 默认Rf值 - + return 0.3 + rf_str = rf_str.strip().lower() - debug_print(f"🔍 解析Rf值: '{rf_str}'") - - # 处理未知Rf值 + if rf_str in ['?', 'unknown', 'tbd', 'to be determined']: - default_rf = 0.3 - debug_print(f"❓ 检测到未知Rf值,使用默认值: {default_rf}") - return default_rf - - # 提取数字 + return 0.3 + match = re.search(r'([0-9]*\.?[0-9]+)', rf_str) if match: value = float(match.group(1)) - # 确保Rf值在0-1范围内 if value > 1.0: - value = value / 100.0 # 可能是百分比形式 - value = max(0.0, min(1.0, value)) # 限制在0-1范围 - debug_print(f"✅ Rf值解析结果: {value}") + value = value / 100.0 + value = max(0.0, min(1.0, value)) return value - - debug_print(f"⚠️ 无法解析Rf值: '{rf_str}',使用默认值0.3") + return 0.3 def find_column_device(G: nx.DiGraph) -> str: """查找柱层析设备""" - debug_print("🔍 查找柱层析设备...") - - # 查找虚拟柱设备 for node in G.nodes(): node_data = G.nodes[node] node_class = node_data.get('class', '') or '' - + if 'virtual_column' in node_class.lower() or 'column' in node_class.lower(): - debug_print(f"🎉 找到柱层析设备: {node} ✨") + debug_print(f"找到柱层析设备: {node}") return node - - # 如果没有找到,尝试创建虚拟设备名称 + possible_names = ['column_1', 'virtual_column_1', 'chromatography_column_1'] for name in possible_names: if name in G.nodes(): - debug_print(f"🎉 找到柱设备: {name} ✨") + debug_print(f"找到柱设备: {name}") return name - - debug_print("⚠️ 未找到柱层析设备,将使用pump protocol直接转移") + + debug_print("未找到柱层析设备,将使用pump protocol直接转移") return "" def find_column_vessel(G: nx.DiGraph, column: str) -> str: """查找柱容器""" - debug_print(f"🔍 查找柱容器: '{column}'") - - # 直接检查column参数是否是容器 if column in G.nodes(): node_type = G.nodes[column].get('type', '') if node_type == 'container': - debug_print(f"🎉 找到柱容器: {column} ✨") return column - - # 尝试常见的命名规则 + possible_names = [ f"column_{column}", - f"{column}_column", + f"{column}_column", f"vessel_{column}", f"{column}_vessel", "column_vessel", @@ -169,211 +144,25 @@ def find_column_vessel(G: nx.DiGraph, column: str) -> str: "preparative_column", "column" ] - + for vessel_name in possible_names: if vessel_name in G.nodes(): node_type = G.nodes[vessel_name].get('type', '') if node_type == 'container': - debug_print(f"🎉 找到柱容器: {vessel_name} ✨") return vessel_name - - debug_print(f"⚠️ 未找到柱容器,将直接在源容器中进行分离") + return "" -def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: - """查找溶剂容器 - 增强版""" - if not solvent or not solvent.strip(): - return "" - - solvent = solvent.strip().replace(' ', '_').lower() - debug_print(f"🔍 查找溶剂容器: '{solvent}'") - - # 🔧 方法1:直接搜索 data.reagent_name - for node in G.nodes(): - node_data = G.nodes[node].get('data', {}) - node_type = G.nodes[node].get('type', '') - - # 只搜索容器类型的节点 - if node_type == 'container': - reagent_name = node_data.get('reagent_name', '').lower() - reagent_config = G.nodes[node].get('config', {}).get('reagent', '').lower() - - # 检查 data.reagent_name 和 config.reagent - if reagent_name == solvent or reagent_config == solvent: - debug_print(f"🎉 通过reagent_name找到溶剂容器: {node} (reagent: {reagent_name or reagent_config}) ✨") - return node - - # 模糊匹配 reagent_name - if solvent in reagent_name or reagent_name in solvent: - debug_print(f"🎉 通过reagent_name模糊匹配到溶剂容器: {node} (reagent: {reagent_name}) ✨") - return node - - if solvent in reagent_config or reagent_config in solvent: - debug_print(f"🎉 通过config.reagent模糊匹配到溶剂容器: {node} (reagent: {reagent_config}) ✨") - return node - - # 🔧 方法2:常见的溶剂容器命名规则 - possible_names = [ - f"flask_{solvent}", - f"bottle_{solvent}", - f"reagent_{solvent}", - f"{solvent}_bottle", - f"{solvent}_flask", - f"solvent_{solvent}", - f"reagent_bottle_{solvent}" - ] - - for vessel_name in possible_names: - if vessel_name in G.nodes(): - node_type = G.nodes[vessel_name].get('type', '') - if node_type == 'container': - debug_print(f"🎉 通过命名规则找到溶剂容器: {vessel_name} ✨") - return vessel_name - - # 🔧 方法3:节点名称模糊匹配 - for node in G.nodes(): - node_type = G.nodes[node].get('type', '') - if node_type == 'container': - if ('flask_' in node or 'bottle_' in node or 'reagent_' in node) and solvent in node.lower(): - debug_print(f"🎉 通过节点名称模糊匹配到溶剂容器: {node} ✨") - return node - - # 🔧 方法4:特殊溶剂名称映射 - solvent_mapping = { - 'dmf': ['dmf', 'dimethylformamide', 'n,n-dimethylformamide'], - 'ethyl_acetate': ['ethyl_acetate', 'ethylacetate', 'etoac', 'ea'], - 'hexane': ['hexane', 'hexanes', 'n-hexane'], - 'methanol': ['methanol', 'meoh', 'ch3oh'], - 'water': ['water', 'h2o', 'distilled_water'], - 'acetone': ['acetone', 'ch3coch3', '2-propanone'], - 'dichloromethane': ['dichloromethane', 'dcm', 'ch2cl2', 'methylene_chloride'], - 'chloroform': ['chloroform', 'chcl3', 'trichloromethane'] - } - - # 查找映射的同义词 - for canonical_name, synonyms in solvent_mapping.items(): - if solvent in synonyms: - debug_print(f"🔍 检测到溶剂同义词: '{solvent}' -> '{canonical_name}'") - return find_solvent_vessel(G, canonical_name) # 递归搜索 - - debug_print(f"⚠️ 未找到溶剂 '{solvent}' 的容器") - return "" - -def get_vessel_liquid_volume(vessel: dict) -> float: - """ - 获取容器中的液体体积 - 支持vessel字典 - - Args: - vessel: 容器字典 - - Returns: - float: 液体体积(mL) - """ - if not vessel or "data" not in vessel: - debug_print(f"⚠️ 容器数据为空,返回 0.0mL") - return 0.0 - - vessel_data = vessel["data"] - vessel_id = vessel.get("id", "unknown") - - debug_print(f"🔍 读取容器 '{vessel_id}' 体积数据: {vessel_data}") - - # 检查liquid_volume字段 - if "liquid_volume" in vessel_data: - liquid_volume = vessel_data["liquid_volume"] - - # 处理列表格式 - if isinstance(liquid_volume, list): - if len(liquid_volume) > 0: - volume = liquid_volume[0] - if isinstance(volume, (int, float)): - debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (列表格式)") - return float(volume) - - # 处理直接数值格式 - elif isinstance(liquid_volume, (int, float)): - debug_print(f"✅ 容器 '{vessel_id}' 体积: {liquid_volume}mL (数值格式)") - return float(liquid_volume) - - # 检查其他可能的体积字段 - volume_keys = ['current_volume', 'total_volume', 'volume'] - for key in volume_keys: - if key in vessel_data: - try: - volume = float(vessel_data[key]) - if volume > 0: - debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (字段: {key})") - return volume - except (ValueError, TypeError): - continue - - debug_print(f"⚠️ 无法获取容器 '{vessel_id}' 的体积,返回默认值 50.0mL") - return 50.0 - -def update_vessel_volume(vessel: dict, G: nx.DiGraph, new_volume: float, description: str = "") -> None: - """ - 更新容器体积(同时更新vessel字典和图节点) - - Args: - vessel: 容器字典 - G: 网络图 - new_volume: 新体积 - description: 更新描述 - """ - vessel_id = vessel.get("id", "unknown") - - if description: - debug_print(f"🔧 更新容器体积 - {description}") - - # 更新vessel字典中的体积 - if "data" in vessel: - if "liquid_volume" in vessel["data"]: - current_volume = vessel["data"]["liquid_volume"] - if isinstance(current_volume, list): - if len(current_volume) > 0: - vessel["data"]["liquid_volume"][0] = new_volume - else: - vessel["data"]["liquid_volume"] = [new_volume] - else: - vessel["data"]["liquid_volume"] = new_volume - else: - vessel["data"]["liquid_volume"] = new_volume - else: - vessel["data"] = {"liquid_volume": new_volume} - - # 同时更新图中的容器数据 - if vessel_id in G.nodes(): - if 'data' not in G.nodes[vessel_id]: - G.nodes[vessel_id]['data'] = {} - - vessel_node_data = G.nodes[vessel_id]['data'] - current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - - if isinstance(current_node_volume, list): - if len(current_node_volume) > 0: - G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume - else: - G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume] - else: - G.nodes[vessel_id]['data']['liquid_volume'] = new_volume - - debug_print(f"📊 容器 '{vessel_id}' 体积已更新为: {new_volume:.2f}mL") - def calculate_solvent_volumes(total_volume: float, pct1: float, pct2: float) -> tuple: """根据百分比计算溶剂体积""" volume1 = (total_volume * pct1) / 100.0 volume2 = (total_volume * pct2) / 100.0 - - debug_print(f"🧮 溶剂体积计算: 总体积{total_volume}mL") - debug_print(f" - 溶剂1: {pct1}% = {volume1}mL") - debug_print(f" - 溶剂2: {pct2}% = {volume2}mL") - return (volume1, volume2) def generate_run_column_protocol( G: nx.DiGraph, - from_vessel: dict, # 🔧 修改:从字符串改为字典类型 - to_vessel: dict, # 🔧 修改:从字符串改为字典类型 + from_vessel: dict, + to_vessel: dict, column: str, rf: str = "", pct1: str = "", @@ -385,7 +174,7 @@ def generate_run_column_protocol( ) -> List[Dict[str, Any]]: """ 生成柱层析分离的协议序列 - 支持vessel字典和体积运算 - + Args: G: 有向图,节点为设备和容器,边为流体管道 from_vessel: 源容器字典(从XDL传入) @@ -398,173 +187,112 @@ def generate_run_column_protocol( solvent2: 第二种溶剂名称(可选) ratio: 溶剂比例(如 "5:95",可选,优先级高于pct1/pct2) **kwargs: 其他可选参数 - + Returns: List[Dict[str, Any]]: 柱层析分离操作的动作序列 """ - - # 🔧 核心修改:从字典中提取容器ID + from_vessel_id, _ = get_vessel(from_vessel) to_vessel_id, _ = get_vessel(to_vessel) - debug_print("🏛️" * 20) - debug_print("🚀 开始生成柱层析协议(支持vessel字典和体积运算)✨") - debug_print(f"📝 输入参数:") - debug_print(f" 🥽 from_vessel: {from_vessel} (ID: {from_vessel_id})") - debug_print(f" 🥽 to_vessel: {to_vessel} (ID: {to_vessel_id})") - debug_print(f" 🏛️ column: '{column}'") - debug_print(f" 📊 rf: '{rf}'") - debug_print(f" 🧪 溶剂配比: pct1='{pct1}', pct2='{pct2}', ratio='{ratio}'") - debug_print(f" 🧪 溶剂名称: solvent1='{solvent1}', solvent2='{solvent2}'") - debug_print("🏛️" * 20) - + debug_print(f"开始生成柱层析协议: {from_vessel_id} -> {to_vessel_id}, column={column}") + action_sequence = [] - - # 🔧 新增:记录柱层析前的容器状态 - debug_print("🔍 记录柱层析前容器状态...") - original_from_volume = get_vessel_liquid_volume(from_vessel) - original_to_volume = get_vessel_liquid_volume(to_vessel) - - debug_print(f"📊 柱层析前状态:") - debug_print(f" - 源容器 {from_vessel_id}: {original_from_volume:.2f}mL") - debug_print(f" - 目标容器 {to_vessel_id}: {original_to_volume:.2f}mL") - + + # 记录柱层析前的容器状态 + original_from_volume = get_resource_liquid_volume(from_vessel) + original_to_volume = get_resource_liquid_volume(to_vessel) + # === 参数验证 === - debug_print("📍 步骤1: 参数验证...") - - if not from_vessel_id: # 🔧 使用 from_vessel_id + if not from_vessel_id: raise ValueError("from_vessel 参数不能为空") - if not to_vessel_id: # 🔧 使用 to_vessel_id + if not to_vessel_id: raise ValueError("to_vessel 参数不能为空") if not column: raise ValueError("column 参数不能为空") - - if from_vessel_id not in G.nodes(): # 🔧 使用 from_vessel_id + + if from_vessel_id not in G.nodes(): raise ValueError(f"源容器 '{from_vessel_id}' 不存在于系统中") - if to_vessel_id not in G.nodes(): # 🔧 使用 to_vessel_id + if to_vessel_id not in G.nodes(): raise ValueError(f"目标容器 '{to_vessel_id}' 不存在于系统中") - - debug_print("✅ 基本参数验证通过") - + # === 参数解析 === - debug_print("📍 步骤2: 参数解析...") - - # 解析Rf值 final_rf = parse_rf_value(rf) - debug_print(f"🎯 最终Rf值: {final_rf}") - - # 解析溶剂比例(ratio优先级高于pct1/pct2) + if ratio and ratio.strip(): final_pct1, final_pct2 = parse_ratio(ratio) - debug_print(f"📊 使用ratio参数: {final_pct1:.1f}% : {final_pct2:.1f}%") else: final_pct1 = parse_percentage(pct1) if pct1 else 50.0 final_pct2 = parse_percentage(pct2) if pct2 else 50.0 - - # 如果百分比和不是100%,进行归一化 + total_pct = final_pct1 + final_pct2 if total_pct == 0: final_pct1, final_pct2 = 50.0, 50.0 elif total_pct != 100.0: final_pct1 = (final_pct1 / total_pct) * 100 final_pct2 = (final_pct2 / total_pct) * 100 - - debug_print(f"📊 使用百分比参数: {final_pct1:.1f}% : {final_pct2:.1f}%") - - # 设置默认溶剂(如果未指定) + final_solvent1 = solvent1.strip() if solvent1 else "ethyl_acetate" final_solvent2 = solvent2.strip() if solvent2 else "hexane" - - debug_print(f"🧪 最终溶剂: {final_solvent1} : {final_solvent2}") - + + debug_print(f"参数: rf={final_rf}, 溶剂={final_solvent1}:{final_solvent2} = {final_pct1:.1f}%:{final_pct2:.1f}%") + # === 查找设备和容器 === - debug_print("📍 步骤3: 查找设备和容器...") - - # 查找柱层析设备 column_device_id = find_column_device(G) - - # 查找柱容器 column_vessel = find_column_vessel(G, column) - - # 查找溶剂容器 solvent1_vessel = find_solvent_vessel(G, final_solvent1) solvent2_vessel = find_solvent_vessel(G, final_solvent2) - - debug_print(f"🔧 设备映射:") - debug_print(f" - 柱设备: '{column_device_id}'") - debug_print(f" - 柱容器: '{column_vessel}'") - debug_print(f" - 溶剂1容器: '{solvent1_vessel}'") - debug_print(f" - 溶剂2容器: '{solvent2_vessel}'") - + # === 获取源容器体积 === - debug_print("📍 步骤4: 获取源容器体积...") - source_volume = original_from_volume if source_volume <= 0: - source_volume = 50.0 # 默认体积 - debug_print(f"⚠️ 无法获取源容器体积,使用默认值: {source_volume}mL") - else: - debug_print(f"✅ 源容器体积: {source_volume}mL") - + source_volume = 50.0 + # === 计算溶剂体积 === - debug_print("📍 步骤5: 计算溶剂体积...") - - # 洗脱溶剂通常是样品体积的2-5倍 total_elution_volume = source_volume * 3.0 solvent1_volume, solvent2_volume = calculate_solvent_volumes( total_elution_volume, final_pct1, final_pct2 ) - + # === 执行柱层析流程 === - debug_print("📍 步骤6: 执行柱层析流程...") - - # 🔧 新增:体积变化跟踪变量 current_from_volume = source_volume current_to_volume = original_to_volume current_column_volume = 0.0 - + try: - # 步骤6.1: 样品上柱(如果有独立的柱容器) - if column_vessel and column_vessel != from_vessel_id: # 🔧 使用 from_vessel_id - debug_print(f"📍 6.1: 样品上柱 - {source_volume}mL 从 {from_vessel_id} 到 {column_vessel}") - + # 步骤1: 样品上柱 + if column_vessel and column_vessel != from_vessel_id: try: sample_transfer_actions = generate_pump_protocol_with_rinsing( G=G, - from_vessel=from_vessel_id, # 🔧 使用 from_vessel_id + from_vessel=from_vessel_id, to_vessel=column_vessel, volume=source_volume, - flowrate=1.0, # 慢速上柱 + flowrate=1.0, transfer_flowrate=0.5, - rinsing_solvent="", # 暂不冲洗 + rinsing_solvent="", rinsing_volume=0.0, rinsing_repeats=0 ) action_sequence.extend(sample_transfer_actions) - debug_print(f"✅ 样品上柱完成,添加了 {len(sample_transfer_actions)} 个动作") - - # 🔧 新增:更新体积 - 样品转移到柱上 - current_from_volume = 0.0 # 源容器体积变为0 - current_column_volume = source_volume # 柱容器体积增加 - + + current_from_volume = 0.0 + current_column_volume = source_volume + update_vessel_volume(from_vessel, G, current_from_volume, "样品上柱后,源容器清空") - - # 如果柱容器在图中,也更新其体积 + if column_vessel in G.nodes(): if 'data' not in G.nodes[column_vessel]: G.nodes[column_vessel]['data'] = {} G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume - debug_print(f"📊 柱容器 '{column_vessel}' 体积更新为: {current_column_volume:.2f}mL") - + except Exception as e: - debug_print(f"⚠️ 样品上柱失败: {str(e)}") - - # 步骤6.2: 添加洗脱溶剂1(如果有溶剂容器) + debug_print(f"样品上柱失败: {str(e)}") + + # 步骤2: 添加洗脱溶剂1 if solvent1_vessel and solvent1_volume > 0: - debug_print(f"📍 6.2: 添加洗脱溶剂1 - {solvent1_volume:.1f}mL {final_solvent1}") - try: - target_vessel = column_vessel if column_vessel else from_vessel_id # 🔧 使用 from_vessel_id + target_vessel = column_vessel if column_vessel else from_vessel_id solvent1_transfer_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=solvent1_vessel, @@ -574,27 +302,22 @@ def generate_run_column_protocol( transfer_flowrate=1.0 ) action_sequence.extend(solvent1_transfer_actions) - debug_print(f"✅ 溶剂1添加完成,添加了 {len(solvent1_transfer_actions)} 个动作") - - # 🔧 新增:更新体积 - 添加溶剂1 + if target_vessel == column_vessel: current_column_volume += solvent1_volume if column_vessel in G.nodes(): G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume - debug_print(f"📊 柱容器体积增加: +{solvent1_volume:.2f}mL = {current_column_volume:.2f}mL") elif target_vessel == from_vessel_id: current_from_volume += solvent1_volume update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂1后") - + except Exception as e: - debug_print(f"⚠️ 溶剂1添加失败: {str(e)}") - - # 步骤6.3: 添加洗脱溶剂2(如果有溶剂容器) + debug_print(f"溶剂1添加失败: {str(e)}") + + # 步骤3: 添加洗脱溶剂2 if solvent2_vessel and solvent2_volume > 0: - debug_print(f"📍 6.3: 添加洗脱溶剂2 - {solvent2_volume:.1f}mL {final_solvent2}") - try: - target_vessel = column_vessel if column_vessel else from_vessel_id # 🔧 使用 from_vessel_id + target_vessel = column_vessel if column_vessel else from_vessel_id solvent2_transfer_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=solvent2_vessel, @@ -604,31 +327,26 @@ def generate_run_column_protocol( transfer_flowrate=1.0 ) action_sequence.extend(solvent2_transfer_actions) - debug_print(f"✅ 溶剂2添加完成,添加了 {len(solvent2_transfer_actions)} 个动作") - - # 🔧 新增:更新体积 - 添加溶剂2 + if target_vessel == column_vessel: current_column_volume += solvent2_volume if column_vessel in G.nodes(): G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume - debug_print(f"📊 柱容器体积增加: +{solvent2_volume:.2f}mL = {current_column_volume:.2f}mL") elif target_vessel == from_vessel_id: current_from_volume += solvent2_volume update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂2后") - + except Exception as e: - debug_print(f"⚠️ 溶剂2添加失败: {str(e)}") - - # 步骤6.4: 使用柱层析设备执行分离(如果有设备) + debug_print(f"溶剂2添加失败: {str(e)}") + + # 步骤4: 使用柱层析设备执行分离 if column_device_id: - debug_print(f"📍 6.4: 使用柱层析设备执行分离") - column_separation_action = { "device_id": column_device_id, "action_name": "run_column", "action_kwargs": { - "from_vessel": from_vessel_id, # 🔧 使用 from_vessel_id - "to_vessel": to_vessel_id, # 🔧 使用 to_vessel_id + "from_vessel": from_vessel_id, + "to_vessel": to_vessel_id, "column": column, "rf": rf, "pct1": pct1, @@ -639,85 +357,65 @@ def generate_run_column_protocol( } } action_sequence.append(column_separation_action) - debug_print(f"✅ 柱层析设备动作已添加") - - # 等待分离完成 - separation_time = max(30, min(120, int(total_elution_volume / 2))) # 30-120秒,基于体积 + + separation_time = max(30, min(120, int(total_elution_volume / 2))) action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": separation_time} }) - debug_print(f"✅ 等待分离完成: {separation_time}秒") - - # 步骤6.5: 产物收集(从柱容器到目标容器) - if column_vessel and column_vessel != to_vessel_id: # 🔧 使用 to_vessel_id - debug_print(f"📍 6.5: 产物收集 - 从 {column_vessel} 到 {to_vessel_id}") - + + # 步骤5: 产物收集 + if column_vessel and column_vessel != to_vessel_id: try: - # 估算产物体积(原始样品体积的70-90%,收率考虑) product_volume = source_volume * 0.8 - + product_transfer_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=column_vessel, - to_vessel=to_vessel_id, # 🔧 使用 to_vessel_id + to_vessel=to_vessel_id, volume=product_volume, flowrate=1.5, transfer_flowrate=0.8 ) action_sequence.extend(product_transfer_actions) - debug_print(f"✅ 产物收集完成,添加了 {len(product_transfer_actions)} 个动作") - - # 🔧 新增:更新体积 - 产物收集到目标容器 + current_to_volume += product_volume - current_column_volume -= product_volume # 柱容器体积减少 - + current_column_volume -= product_volume + update_vessel_volume(to_vessel, G, current_to_volume, "产物收集后") - - # 更新柱容器体积 + if column_vessel in G.nodes(): G.nodes[column_vessel]['data']['liquid_volume'] = max(0.0, current_column_volume) - debug_print(f"📊 柱容器体积减少: -{product_volume:.2f}mL = {current_column_volume:.2f}mL") - + except Exception as e: - debug_print(f"⚠️ 产物收集失败: {str(e)}") - - # 步骤6.6: 如果没有独立的柱设备和容器,执行简化的直接转移 + debug_print(f"产物收集失败: {str(e)}") + + # 步骤6: 简化模式 - 直接转移 if not column_device_id and not column_vessel: - debug_print(f"📍 6.6: 简化模式 - 直接转移 {source_volume}mL 从 {from_vessel_id} 到 {to_vessel_id}") - try: direct_transfer_actions = generate_pump_protocol_with_rinsing( G=G, - from_vessel=from_vessel_id, # 🔧 使用 from_vessel_id - to_vessel=to_vessel_id, # 🔧 使用 to_vessel_id + from_vessel=from_vessel_id, + to_vessel=to_vessel_id, volume=source_volume, flowrate=2.0, transfer_flowrate=1.0 ) action_sequence.extend(direct_transfer_actions) - debug_print(f"✅ 直接转移完成,添加了 {len(direct_transfer_actions)} 个动作") - - # 🔧 新增:更新体积 - 直接转移 - current_from_volume = 0.0 # 源容器清空 - current_to_volume += source_volume # 目标容器增加 - + + current_from_volume = 0.0 + current_to_volume += source_volume + update_vessel_volume(from_vessel, G, current_from_volume, "直接转移后,源容器清空") update_vessel_volume(to_vessel, G, current_to_volume, "直接转移后,目标容器增加") - + except Exception as e: - debug_print(f"⚠️ 直接转移失败: {str(e)}") - + debug_print(f"直接转移失败: {str(e)}") + except Exception as e: - debug_print(f"❌ 协议生成失败: {str(e)} 😭") - - # 不添加不确定的动作,直接让action_sequence保持为空列表 - # action_sequence 已经在函数开始时初始化为 [] - - # 确保至少有一个有效的动作,如果完全失败就返回空列表 + debug_print(f"协议生成失败: {str(e)}") + if not action_sequence: - debug_print("⚠️ 没有生成任何有效动作") - # 可以选择返回空列表或添加一个基本的等待动作 action_sequence.append({ "action_name": "wait", "action_kwargs": { @@ -725,83 +423,50 @@ def generate_run_column_protocol( "description": "柱层析协议执行完成" } }) - - # 🔧 新增:柱层析完成后的最终状态报告 - final_from_volume = get_vessel_liquid_volume(from_vessel) - final_to_volume = get_vessel_liquid_volume(to_vessel) - - # 🎊 总结 - debug_print("🏛️" * 20) - debug_print(f"🎉 柱层析协议生成完成! ✨") - debug_print(f"📊 总动作数: {len(action_sequence)} 个") - debug_print(f"🥽 路径: {from_vessel_id} → {to_vessel_id}") - debug_print(f"🏛️ 柱子: {column}") - debug_print(f"🧪 溶剂: {final_solvent1}:{final_solvent2} = {final_pct1:.1f}%:{final_pct2:.1f}%") - debug_print(f"📊 体积变化统计:") - debug_print(f" 源容器 {from_vessel_id}:") - debug_print(f" - 柱层析前: {original_from_volume:.2f}mL") - debug_print(f" - 柱层析后: {final_from_volume:.2f}mL") - debug_print(f" 目标容器 {to_vessel_id}:") - debug_print(f" - 柱层析前: {original_to_volume:.2f}mL") - debug_print(f" - 柱层析后: {final_to_volume:.2f}mL") - debug_print(f" - 收集体积: {final_to_volume - original_to_volume:.2f}mL") - debug_print(f"⏱️ 预计总时间: {len(action_sequence) * 5:.0f} 秒 ⌛") - debug_print("🏛️" * 20) - + + final_from_volume = get_resource_liquid_volume(from_vessel) + final_to_volume = get_resource_liquid_volume(to_vessel) + + debug_print(f"柱层析协议生成完成: {len(action_sequence)} 个动作, {from_vessel_id} -> {to_vessel_id}, 收集={final_to_volume - original_to_volume:.2f}mL") + return action_sequence -# 🔧 新增:便捷函数 -def generate_ethyl_acetate_hexane_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, +# 便捷函数 +def generate_ethyl_acetate_hexane_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, column: str, ratio: str = "30:70") -> List[Dict[str, Any]]: """乙酸乙酯-己烷柱层析(常用组合)""" - from_vessel_id = from_vessel["id"] - to_vessel_id = to_vessel["id"] - debug_print(f"🧪⛽ 乙酸乙酯-己烷柱层析: {from_vessel_id} → {to_vessel_id} @ {ratio}") - return generate_run_column_protocol(G, from_vessel, to_vessel, column, + return generate_run_column_protocol(G, from_vessel, to_vessel, column, solvent1="ethyl_acetate", solvent2="hexane", ratio=ratio) -def generate_methanol_dcm_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, +def generate_methanol_dcm_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, column: str, ratio: str = "5:95") -> List[Dict[str, Any]]: """甲醇-二氯甲烷柱层析""" - from_vessel_id = from_vessel["id"] - to_vessel_id = to_vessel["id"] - debug_print(f"🧪🧪 甲醇-DCM柱层析: {from_vessel_id} → {to_vessel_id} @ {ratio}") - return generate_run_column_protocol(G, from_vessel, to_vessel, column, + return generate_run_column_protocol(G, from_vessel, to_vessel, column, solvent1="methanol", solvent2="dichloromethane", ratio=ratio) -def generate_gradient_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, - column: str, start_ratio: str = "10:90", +def generate_gradient_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, + column: str, start_ratio: str = "10:90", end_ratio: str = "50:50") -> List[Dict[str, Any]]: """梯度洗脱柱层析(中等比例)""" - from_vessel_id, _ = get_vessel(from_vessel) - to_vessel_id, _ = get_vessel(to_vessel) - debug_print(f"📈 梯度柱层析: {from_vessel_id} → {to_vessel_id} ({start_ratio} → {end_ratio})") - # 使用中间比例作为近似 return generate_run_column_protocol(G, from_vessel, to_vessel, column, ratio="30:70") -def generate_polar_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, +def generate_polar_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, column: str) -> List[Dict[str, Any]]: """极性化合物柱层析(高极性溶剂比例)""" - from_vessel_id, _ = get_vessel(from_vessel) - to_vessel_id, _ = get_vessel(to_vessel) - debug_print(f"⚡ 极性化合物柱层析: {from_vessel_id} → {to_vessel_id}") - return generate_run_column_protocol(G, from_vessel, to_vessel, column, + return generate_run_column_protocol(G, from_vessel, to_vessel, column, solvent1="ethyl_acetate", solvent2="hexane", ratio="70:30") -def generate_nonpolar_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, +def generate_nonpolar_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict, column: str) -> List[Dict[str, Any]]: """非极性化合物柱层析(低极性溶剂比例)""" - from_vessel_id, _ = get_vessel(from_vessel) - to_vessel_id, _ = get_vessel(to_vessel) - debug_print(f"🛢️ 非极性化合物柱层析: {from_vessel_id} → {to_vessel_id}") - return generate_run_column_protocol(G, from_vessel, to_vessel, column, + return generate_run_column_protocol(G, from_vessel, to_vessel, column, solvent1="ethyl_acetate", solvent2="hexane", ratio="5:95") # 测试函数 def test_run_column_protocol(): """测试柱层析协议""" - debug_print("🧪 === RUN COLUMN PROTOCOL 测试 === ✨") - debug_print("✅ 测试完成 🎉") + debug_print("=== RUN COLUMN PROTOCOL 测试 ===") + debug_print("测试完成") if __name__ == "__main__": test_run_column_protocol() diff --git a/unilabos/compile/stir_protocol.py b/unilabos/compile/stir_protocol.py index 73d1b878..7daf3f48 100644 --- a/unilabos/compile/stir_protocol.py +++ b/unilabos/compile/stir_protocol.py @@ -1,48 +1,14 @@ from typing import List, Dict, Any, Union import networkx as nx import logging -import re from .utils.unit_parser import parse_time_input +from .utils.resource_helper import get_resource_id, get_resource_display_info +from .utils.logger_util import debug_print +from .utils.vessel_parser import find_connected_stirrer logger = logging.getLogger(__name__) -def debug_print(message): - """调试输出""" - logger.info(f"[STIR] {message}") - - -def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str: - """查找与指定容器相连的搅拌设备""" - debug_print(f"🔍 查找搅拌设备,目标容器: {vessel} 🥽") - - # 🔧 查找所有搅拌设备 - stirrer_nodes = [] - for node in G.nodes(): - node_data = G.nodes[node] - node_class = node_data.get('class', '') or '' - - if 'stirrer' in node_class.lower() or 'virtual_stirrer' in node_class: - stirrer_nodes.append(node) - debug_print(f"🎉 找到搅拌设备: {node} 🌪️") - - # 🔗 检查连接 - if vessel and stirrer_nodes: - for stirrer in stirrer_nodes: - if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer): - debug_print(f"✅ 搅拌设备 '{stirrer}' 与容器 '{vessel}' 相连 🔗") - return stirrer - - # 🎯 使用第一个可用设备 - if stirrer_nodes: - selected = stirrer_nodes[0] - debug_print(f"🔧 使用第一个搅拌设备: {selected} 🌪️") - return selected - - # 🆘 默认设备 - debug_print("⚠️ 未找到搅拌设备,使用默认设备 🌪️") - return "stirrer_1" - def validate_and_fix_params(stir_time: float, stir_speed: float, settling_time: float) -> tuple: """验证和修正参数""" # ⏰ 搅拌时间验证 @@ -71,46 +37,13 @@ def validate_and_fix_params(stir_time: float, stir_speed: float, settling_time: return stir_time, stir_speed, settling_time -def extract_vessel_id(vessel: Union[str, dict]) -> str: - """ - 从vessel参数中提取vessel_id - - Args: - vessel: vessel字典或vessel_id字符串 - - Returns: - str: vessel_id - """ - if isinstance(vessel, dict): - vessel_id = list(vessel.values())[0].get("id", "") - debug_print(f"🔧 从vessel字典提取ID: {vessel_id}") - return vessel_id - elif isinstance(vessel, str): - debug_print(f"🔧 vessel参数为字符串: {vessel}") - return vessel - else: - debug_print(f"⚠️ 无效的vessel参数类型: {type(vessel)}") - return "" +def extract_vessel_id(vessel) -> str: + """从vessel参数中提取vessel_id,兼容 str / dict / ResourceDictInstance""" + return get_resource_id(vessel) -def get_vessel_display_info(vessel: Union[str, dict]) -> str: - """ - 获取容器的显示信息(用于日志) - - Args: - vessel: vessel字典或vessel_id字符串 - - Returns: - str: 显示信息 - """ - if isinstance(vessel, dict): - vessel_id = vessel.get("id", "unknown") - vessel_name = vessel.get("name", "") - if vessel_name: - return f"{vessel_id} ({vessel_name})" - else: - return vessel_id - else: - return str(vessel) +def get_vessel_display_info(vessel) -> str: + """获取容器的显示信息(用于日志),兼容 str / dict / ResourceDictInstance""" + return get_resource_display_info(vessel) def generate_stir_protocol( G: nx.DiGraph, @@ -152,81 +85,55 @@ def generate_stir_protocol( } debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}") - debug_print("🌪️" * 20) - debug_print("🚀 开始生成搅拌协议(支持vessel字典)✨") - debug_print(f"📝 输入参数:") - debug_print(f" 🥽 vessel: {vessel_display} (ID: {vessel_id})") - debug_print(f" ⏰ time: {time}") - debug_print(f" 🕐 stir_time: {stir_time}") - debug_print(f" 🎯 time_spec: {time_spec}") - debug_print(f" 🌪️ stir_speed: {stir_speed} RPM") - debug_print(f" ⏱️ settling_time: {settling_time}") - debug_print("🌪️" * 20) - - # 📋 参数验证 - debug_print("📍 步骤1: 参数验证... 🔧") - if not vessel_id: # 🔧 使用 vessel_id - debug_print("❌ vessel 参数不能为空! 😱") + debug_print(f"开始生成搅拌协议: vessel={vessel_id}, time={time}, " + f"stir_time={stir_time}, stir_speed={stir_speed}RPM, settling={settling_time}") + + # 参数验证 + if not vessel_id: raise ValueError("vessel 参数不能为空") - - if vessel_id not in G.nodes(): # 🔧 使用 vessel_id - debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中! 😞") + + if vessel_id not in G.nodes(): raise ValueError(f"容器 '{vessel_id}' 不存在于系统中") - debug_print("✅ 基础参数验证通过 🎯") - - # 🔄 参数解析 - debug_print("📍 步骤2: 参数解析... ⚡") - - # 确定实际时间(优先级:time_spec > stir_time > time) + # 参数解析 — 确定实际时间(优先级:time_spec > stir_time > time) if time_spec: parsed_time = parse_time_input(time_spec) - debug_print(f"🎯 使用time_spec: '{time_spec}' → {parsed_time}s") elif stir_time not in ["0", 0, 0.0]: parsed_time = parse_time_input(stir_time) - debug_print(f"🎯 使用stir_time: {stir_time} → {parsed_time}s") else: parsed_time = parse_time_input(time) - debug_print(f"🎯 使用time: {time} → {parsed_time}s") # 解析沉降时间 parsed_settling_time = parse_time_input(settling_time) - # 🕐 模拟运行时间优化 - debug_print(" ⏱️ 检查模拟运行时间限制...") + # 模拟运行时间优化 original_stir_time = parsed_time original_settling_time = parsed_settling_time - + # 搅拌时间限制为60秒 stir_time_limit = 60.0 if parsed_time > stir_time_limit: parsed_time = stir_time_limit - debug_print(f" 🎮 搅拌时间优化: {original_stir_time}s → {parsed_time}s ⚡") - + # 沉降时间限制为30秒 settling_time_limit = 30.0 if parsed_settling_time > settling_time_limit: parsed_settling_time = settling_time_limit - debug_print(f" 🎮 沉降时间优化: {original_settling_time}s → {parsed_settling_time}s ⚡") # 参数修正 parsed_time, stir_speed, parsed_settling_time = validate_and_fix_params( parsed_time, stir_speed, parsed_settling_time ) - debug_print(f"🎯 最终参数: time={parsed_time}s, speed={stir_speed}RPM, settling={parsed_settling_time}s") - - # 🔍 查找设备 - debug_print("📍 步骤3: 查找搅拌设备... 🔍") + debug_print(f"最终参数: time={parsed_time}s, speed={stir_speed}RPM, settling={parsed_settling_time}s") + + # 查找设备 try: - stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id - debug_print(f"🎉 使用搅拌设备: {stirrer_id} ✨") + stirrer_id = find_connected_stirrer(G, vessel_id) except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)} 😭") raise ValueError(f"无法找到搅拌设备: {str(e)}") - - # 🚀 生成动作 - debug_print("📍 步骤4: 生成搅拌动作... 🌪️") + + # 生成动作 action_sequence = [] stir_action = { @@ -244,22 +151,14 @@ def generate_stir_protocol( } } action_sequence.append(stir_action) - debug_print("✅ 搅拌动作已添加 🌪️✨") - - # 显示时间优化信息 + + # 时间优化信息 if original_stir_time != parsed_time or original_settling_time != parsed_settling_time: - debug_print(f" 🎭 模拟优化说明:") - debug_print(f" 搅拌时间: {original_stir_time/60:.1f}分钟 → {parsed_time/60:.1f}分钟") - debug_print(f" 沉降时间: {original_settling_time/60:.1f}分钟 → {parsed_settling_time/60:.1f}分钟") - - # 🎊 总结 - debug_print("🎊" * 20) - debug_print(f"🎉 搅拌协议生成完成! ✨") - debug_print(f"📊 总动作数: {len(action_sequence)} 个") - debug_print(f"🥽 搅拌容器: {vessel_display}") - debug_print(f"🌪️ 搅拌参数: {stir_speed} RPM, {parsed_time}s, 沉降 {parsed_settling_time}s") - debug_print(f"⏱️ 预计总时间: {(parsed_time + parsed_settling_time)/60:.1f} 分钟 ⌛") - debug_print("🎊" * 20) + debug_print(f"模拟优化: 搅拌 {original_stir_time/60:.1f}min→{parsed_time/60:.1f}min, " + f"沉降 {original_settling_time/60:.1f}min→{parsed_settling_time/60:.1f}min") + + debug_print(f"搅拌协议生成完成: {vessel_display}, {stir_speed}RPM, " + f"{parsed_time}s, 沉降{parsed_settling_time}s, 总{(parsed_time + parsed_settling_time)/60:.1f}min") return action_sequence @@ -297,21 +196,16 @@ def generate_start_stir_protocol( "sample_id": "", "type": "" } - debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}") - - debug_print("🔄 开始生成启动搅拌协议(修复vessel参数)✨") - debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})") - debug_print(f"🌪️ speed: {stir_speed} RPM") - debug_print(f"🎯 purpose: {purpose}") + debug_print(f"构建了基本的vessel Resource对象: {vessel}") + + debug_print(f"启动搅拌协议: vessel={vessel_id}, speed={stir_speed}RPM, purpose='{purpose}'") # 基础验证 if not vessel_id or vessel_id not in G.nodes(): - debug_print("❌ 容器验证失败!") raise ValueError("vessel 参数无效") - + # 参数修正 if stir_speed < 10.0 or stir_speed > 1500.0: - debug_print(f"⚠️ 搅拌速度修正: {stir_speed} → 300 RPM 🌪️") stir_speed = 300.0 # 查找设备 @@ -329,7 +223,7 @@ def generate_start_stir_protocol( } }] - debug_print(f"✅ 启动搅拌协议生成完成 🎯") + debug_print(f"启动搅拌协议生成完成: {stirrer_id}") return action_sequence def generate_stop_stir_protocol( @@ -364,14 +258,12 @@ def generate_stop_stir_protocol( "sample_id": "", "type": "" } - debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}") - - debug_print("🛑 开始生成停止搅拌协议(修复vessel参数)✨") - debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})") - + debug_print(f"构建了基本的vessel Resource对象: {vessel}") + + debug_print(f"停止搅拌协议: vessel={vessel_id}") + # 基础验证 if not vessel_id or vessel_id not in G.nodes(): - debug_print("❌ 容器验证失败!") raise ValueError("vessel 参数无效") # 查找设备 @@ -387,10 +279,10 @@ def generate_stop_stir_protocol( } }] - debug_print(f"✅ 停止搅拌协议生成完成 🎯") + debug_print(f"停止搅拌协议生成完成: {stirrer_id}") return action_sequence -# 🔧 新增:便捷函数 +# 便捷函数 def stir_briefly(G: nx.DiGraph, vessel: Union[str, dict], speed: float = 300.0) -> List[Dict[str, Any]]: """短时间搅拌(30秒)""" diff --git a/unilabos/compile/wash_solid_protocol.py b/unilabos/compile/wash_solid_protocol.py index 096476c7..84960441 100644 --- a/unilabos/compile/wash_solid_protocol.py +++ b/unilabos/compile/wash_solid_protocol.py @@ -4,199 +4,55 @@ import logging import re from .utils.unit_parser import parse_time_input, parse_volume_input +from .utils.resource_helper import get_resource_id, get_resource_display_info, get_resource_liquid_volume, update_vessel_volume +from .utils.logger_util import debug_print logger = logging.getLogger(__name__) -def debug_print(message): - """调试输出""" - logger.info(f"[WASH_SOLID] {message}") - def find_solvent_source(G: nx.DiGraph, solvent: str) -> str: - """查找溶剂源(精简版)""" - debug_print(f"🔍 查找溶剂源: {solvent}") - - # 简化搜索列表 + """查找溶剂源""" search_patterns = [ f"flask_{solvent}", f"bottle_{solvent}", f"reagent_{solvent}", "liquid_reagent_bottle_1", "flask_1", "solvent_bottle" ] - + for pattern in search_patterns: if pattern in G.nodes(): - debug_print(f"🎉 找到溶剂源: {pattern}") + debug_print(f"找到溶剂源: {pattern}") return pattern - - debug_print(f"⚠️ 使用默认溶剂源: flask_{solvent}") + + debug_print(f"使用默认溶剂源: flask_{solvent}") return f"flask_{solvent}" def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str: - """查找滤液容器(精简版)""" - debug_print(f"🔍 查找滤液容器: {filtrate_vessel}") - - # 如果指定了且存在,直接使用 + """查找滤液容器""" if filtrate_vessel and filtrate_vessel in G.nodes(): - debug_print(f"✅ 使用指定容器: {filtrate_vessel}") return filtrate_vessel - - # 简化搜索列表 + default_vessels = ["waste_workup", "filtrate_vessel", "flask_1", "collection_bottle_1"] - + for vessel in default_vessels: if vessel in G.nodes(): - debug_print(f"🎉 找到滤液容器: {vessel}") + debug_print(f"找到滤液容器: {vessel}") return vessel - - debug_print(f"⚠️ 使用默认滤液容器: waste_workup") + return "waste_workup" -def extract_vessel_id(vessel: Union[str, dict]) -> str: - """ - 从vessel参数中提取vessel_id - - Args: - vessel: vessel字典或vessel_id字符串 - - Returns: - str: vessel_id - """ - if isinstance(vessel, dict): - vessel_id = list(vessel.values())[0].get("id", "") - debug_print(f"🔧 从vessel字典提取ID: {vessel_id}") - return vessel_id - elif isinstance(vessel, str): - debug_print(f"🔧 vessel参数为字符串: {vessel}") - return vessel - else: - debug_print(f"⚠️ 无效的vessel参数类型: {type(vessel)}") - return "" +def extract_vessel_id(vessel) -> str: + """从vessel参数中提取vessel_id,兼容 str / dict / ResourceDictInstance""" + return get_resource_id(vessel) -def get_vessel_display_info(vessel: Union[str, dict]) -> str: - """ - 获取容器的显示信息(用于日志) - - Args: - vessel: vessel字典或vessel_id字符串 - - Returns: - str: 显示信息 - """ - if isinstance(vessel, dict): - vessel_id = vessel.get("id", "unknown") - vessel_name = vessel.get("name", "") - if vessel_name: - return f"{vessel_id} ({vessel_name})" - else: - return vessel_id - else: - return str(vessel) - -def get_vessel_liquid_volume(vessel: dict) -> float: - """ - 获取容器中的液体体积 - 支持vessel字典 - - Args: - vessel: 容器字典 - - Returns: - float: 液体体积(mL) - """ - if not vessel or "data" not in vessel: - debug_print(f"⚠️ 容器数据为空,返回 0.0mL") - return 0.0 - - vessel_data = vessel["data"] - vessel_id = vessel.get("id", "unknown") - - debug_print(f"🔍 读取容器 '{vessel_id}' 体积数据: {vessel_data}") - - # 检查liquid_volume字段 - if "liquid_volume" in vessel_data: - liquid_volume = vessel_data["liquid_volume"] - - # 处理列表格式 - if isinstance(liquid_volume, list): - if len(liquid_volume) > 0: - volume = liquid_volume[0] - if isinstance(volume, (int, float)): - debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (列表格式)") - return float(volume) - - # 处理直接数值格式 - elif isinstance(liquid_volume, (int, float)): - debug_print(f"✅ 容器 '{vessel_id}' 体积: {liquid_volume}mL (数值格式)") - return float(liquid_volume) - - # 检查其他可能的体积字段 - volume_keys = ['current_volume', 'total_volume', 'volume'] - for key in volume_keys: - if key in vessel_data: - try: - volume = float(vessel_data[key]) - if volume > 0: - debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (字段: {key})") - return volume - except (ValueError, TypeError): - continue - - debug_print(f"⚠️ 无法获取容器 '{vessel_id}' 的体积,返回默认值 0.0mL") - return 0.0 - -def update_vessel_volume(vessel: dict, G: nx.DiGraph, new_volume: float, description: str = "") -> None: - """ - 更新容器体积(同时更新vessel字典和图节点) - - Args: - vessel: 容器字典 - G: 网络图 - new_volume: 新体积 - description: 更新描述 - """ - vessel_id = vessel.get("id", "unknown") - - if description: - debug_print(f"🔧 更新容器体积 - {description}") - - # 更新vessel字典中的体积 - if "data" in vessel: - if "liquid_volume" in vessel["data"]: - current_volume = vessel["data"]["liquid_volume"] - if isinstance(current_volume, list): - if len(current_volume) > 0: - vessel["data"]["liquid_volume"][0] = new_volume - else: - vessel["data"]["liquid_volume"] = [new_volume] - else: - vessel["data"]["liquid_volume"] = new_volume - else: - vessel["data"]["liquid_volume"] = new_volume - else: - vessel["data"] = {"liquid_volume": new_volume} - - # 同时更新图中的容器数据 - if vessel_id in G.nodes(): - if 'data' not in G.nodes[vessel_id]: - G.nodes[vessel_id]['data'] = {} - - vessel_node_data = G.nodes[vessel_id]['data'] - current_node_volume = vessel_node_data.get('liquid_volume', 0.0) - - if isinstance(current_node_volume, list): - if len(current_node_volume) > 0: - G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume - else: - G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume] - else: - G.nodes[vessel_id]['data']['liquid_volume'] = new_volume - - debug_print(f"📊 容器 '{vessel_id}' 体积已更新为: {new_volume:.2f}mL") +def get_vessel_display_info(vessel) -> str: + """获取容器的显示信息(用于日志),兼容 str / dict / ResourceDictInstance""" + return get_resource_display_info(vessel) def generate_wash_solid_protocol( G: nx.DiGraph, - vessel: Union[str, dict], # 🔧 修改:支持vessel字典 + vessel: Union[str, dict], solvent: str, volume: Union[float, str] = "50", - filtrate_vessel: Union[str, dict] = "", # 🔧 修改:支持vessel字典 + filtrate_vessel: Union[str, dict] = "", temp: float = 25.0, stir: bool = False, stir_speed: float = 0.0, @@ -210,7 +66,7 @@ def generate_wash_solid_protocol( ) -> List[Dict[str, Any]]: """ 生成固体清洗协议 - 支持vessel字典和体积运算 - + Args: G: 有向图,节点为设备和容器,边为流体管道 vessel: 清洗容器字典(从XDL传入)或容器ID字符串 @@ -227,106 +83,78 @@ def generate_wash_solid_protocol( mass: 固体质量(用于计算溶剂用量) event: 事件描述 **kwargs: 其他可选参数 - + Returns: List[Dict[str, Any]]: 固体清洗操作的动作序列 """ - - # 🔧 核心修改:从vessel参数中提取vessel_id + vessel_id = extract_vessel_id(vessel) vessel_display = get_vessel_display_info(vessel) - - # 🔧 处理filtrate_vessel参数 + filtrate_vessel_id = extract_vessel_id(filtrate_vessel) if filtrate_vessel else "" - - debug_print("🧼" * 20) - debug_print("🚀 开始生成固体清洗协议(支持vessel字典和体积运算)✨") - debug_print(f"📝 输入参数:") - debug_print(f" 🥽 vessel: {vessel_display} (ID: {vessel_id})") - debug_print(f" 🧪 solvent: {solvent}") - debug_print(f" 💧 volume: {volume}") - debug_print(f" 🗑️ filtrate_vessel: {filtrate_vessel_id}") - debug_print(f" ⏰ time: {time}") - debug_print(f" 🔄 repeats: {repeats}") - debug_print("🧼" * 20) - - # 🔧 新增:记录清洗前的容器状态 - debug_print("🔍 记录清洗前容器状态...") + + debug_print(f"开始生成固体清洗协议: vessel={vessel_id}, solvent={solvent}, volume={volume}, repeats={repeats}") + + # 记录清洗前的容器状态 if isinstance(vessel, dict): - original_volume = get_vessel_liquid_volume(vessel) - debug_print(f"📊 清洗前液体体积: {original_volume:.2f}mL") + original_volume = get_resource_liquid_volume(vessel) else: original_volume = 0.0 - debug_print(f"📊 vessel为字符串格式,无法获取体积信息") - - # 📋 快速验证 - if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id - debug_print("❌ 容器验证失败! 😱") + + # 快速验证 + if not vessel_id or vessel_id not in G.nodes(): raise ValueError("vessel 参数无效") - + if not solvent: - debug_print("❌ 溶剂不能为空! 😱") raise ValueError("solvent 参数不能为空") - - debug_print("✅ 基础验证通过 🎯") - - # 🔄 参数解析 - debug_print("📍 步骤1: 参数解析... ⚡") + + # 参数解析 final_volume = parse_volume_input(volume, volume_spec, mass) final_time = parse_time_input(time) - - # 重复次数处理(简化) + + # 重复次数处理 if repeats_spec: spec_map = {'few': 2, 'several': 3, 'many': 4, 'thorough': 5} final_repeats = next((v for k, v in spec_map.items() if k in repeats_spec.lower()), repeats) else: - final_repeats = max(1, min(repeats, 5)) # 限制1-5次 - - # 🕐 模拟时间优化 - debug_print(" ⏱️ 模拟时间优化...") + final_repeats = max(1, min(repeats, 5)) + + # 模拟时间优化 original_time = final_time if final_time > 60.0: - final_time = 60.0 # 限制最长60秒 - debug_print(f" 🎮 时间优化: {original_time}s → {final_time}s ⚡") - + final_time = 60.0 + debug_print(f"时间优化: {original_time}s -> {final_time}s") + # 参数修正 - temp = max(25.0, min(temp, 80.0)) # 温度范围25-80°C - stir_speed = max(0.0, min(stir_speed, 300.0)) if stir else 0.0 # 速度范围0-300 - - debug_print(f"🎯 最终参数: 体积={final_volume}mL, 时间={final_time}s, 重复={final_repeats}次") - - # 🔍 查找设备 - debug_print("📍 步骤2: 查找设备... 🔍") + temp = max(25.0, min(temp, 80.0)) + stir_speed = max(0.0, min(stir_speed, 300.0)) if stir else 0.0 + + debug_print(f"最终参数: 体积={final_volume}mL, 时间={final_time}s, 重复={final_repeats}次") + + # 查找设备 try: solvent_source = find_solvent_source(G, solvent) actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel_id) - debug_print(f"🎉 设备配置完成 ✨") - debug_print(f" 🧪 溶剂源: {solvent_source}") - debug_print(f" 🗑️ 滤液容器: {actual_filtrate_vessel}") except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)} 😭") raise ValueError(f"设备查找失败: {str(e)}") - - # 🚀 生成动作序列 - debug_print("📍 步骤3: 生成清洗动作... 🧼") + + # 生成动作序列 action_sequence = [] - - # 🔧 新增:体积变化跟踪变量 + current_volume = original_volume total_solvent_used = 0.0 - + for cycle in range(final_repeats): - debug_print(f" 🔄 第{cycle+1}/{final_repeats}次清洗...") - + debug_print(f"第{cycle+1}/{final_repeats}次清洗") + # 1. 转移溶剂 try: from .pump_protocol import generate_pump_protocol_with_rinsing - - debug_print(f" 💧 添加溶剂: {final_volume}mL {solvent}") + transfer_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=solvent_source, - to_vessel=vessel_id, # 🔧 使用 vessel_id + to_vessel=vessel_id, volume=final_volume, amount="", time=0.0, @@ -338,211 +166,160 @@ def generate_wash_solid_protocol( flowrate=2.5, transfer_flowrate=0.5 ) - + if transfer_actions: action_sequence.extend(transfer_actions) - debug_print(f" ✅ 转移动作: {len(transfer_actions)}个 🚚") - - # 🔧 新增:更新体积 - 添加溶剂后 + current_volume += final_volume total_solvent_used += final_volume - + if isinstance(vessel, dict): - update_vessel_volume(vessel, G, current_volume, + update_vessel_volume(vessel, G, current_volume, f"第{cycle+1}次清洗添加{final_volume}mL溶剂后") - + except Exception as e: - debug_print(f" ❌ 转移失败: {str(e)} 😞") - + debug_print(f"转移失败: {str(e)}") + # 2. 搅拌(如果需要) if stir and final_time > 0: - debug_print(f" 🌪️ 搅拌: {final_time}s @ {stir_speed}RPM") stir_action = { "device_id": "stirrer_1", "action_name": "stir", "action_kwargs": { - "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id + "vessel": {"id": vessel_id}, "time": str(time), "stir_time": final_time, "stir_speed": stir_speed, - "settling_time": 10.0 # 🕐 缩短沉降时间 + "settling_time": 10.0 } } action_sequence.append(stir_action) - debug_print(f" ✅ 搅拌动作: {final_time}s, {stir_speed}RPM 🌪️") - + # 3. 过滤 - debug_print(f" 🌊 过滤到: {actual_filtrate_vessel}") filter_action = { "device_id": "filter_1", "action_name": "filter", "action_kwargs": { - "vessel": {"id": vessel_id}, # 🔧 使用 vessel_id + "vessel": {"id": vessel_id}, "filtrate_vessel": actual_filtrate_vessel, "temp": temp, "volume": final_volume } } action_sequence.append(filter_action) - debug_print(f" ✅ 过滤动作: → {actual_filtrate_vessel} 🌊") - - # 🔧 新增:更新体积 - 过滤后(液体被滤除) - # 假设滤液完全被移除,固体残留在容器中 - filtered_volume = current_volume * 0.9 # 假设90%的液体被过滤掉 + + # 更新体积 - 过滤后 + filtered_volume = current_volume * 0.9 current_volume = current_volume - filtered_volume - + if isinstance(vessel, dict): - update_vessel_volume(vessel, G, current_volume, + update_vessel_volume(vessel, G, current_volume, f"第{cycle+1}次清洗过滤后") - - # 4. 等待(缩短时间) - wait_time = 5.0 # 🕐 缩短等待时间:10s → 5s + + # 4. 等待 + wait_time = 5.0 action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": wait_time} }) - debug_print(f" ✅ 等待: {wait_time}s ⏰") - - # 🔧 新增:清洗完成后的最终状态报告 + + # 最终状态 if isinstance(vessel, dict): - final_volume_vessel = get_vessel_liquid_volume(vessel) + final_volume_vessel = get_resource_liquid_volume(vessel) else: final_volume_vessel = current_volume - - # 🎊 总结 - debug_print("🧼" * 20) - debug_print(f"🎉 固体清洗协议生成完成! ✨") - debug_print(f"📊 协议统计:") - debug_print(f" 📋 总动作数: {len(action_sequence)} 个") - debug_print(f" 🥽 清洗容器: {vessel_display}") - debug_print(f" 🧪 使用溶剂: {solvent}") - debug_print(f" 💧 单次体积: {final_volume}mL") - debug_print(f" 🔄 清洗次数: {final_repeats}次") - debug_print(f" 💧 总溶剂用量: {total_solvent_used:.2f}mL") - debug_print(f"📊 体积变化统计:") - debug_print(f" - 清洗前体积: {original_volume:.2f}mL") - debug_print(f" - 清洗后体积: {final_volume_vessel:.2f}mL") - debug_print(f" - 溶剂总用量: {total_solvent_used:.2f}mL") - debug_print(f"⏱️ 预计总时间: {(final_time + 5) * final_repeats / 60:.1f} 分钟") - debug_print("🧼" * 20) - + + debug_print(f"固体清洗协议生成完成: {len(action_sequence)} 个动作, {final_repeats}次清洗, 溶剂总用量={total_solvent_used:.2f}mL") + return action_sequence -# 🔧 新增:便捷函数 -def wash_with_water(G: nx.DiGraph, vessel: Union[str, dict], - volume: Union[float, str] = "50", +# 便捷函数 +def wash_with_water(G: nx.DiGraph, vessel: Union[str, dict], + volume: Union[float, str] = "50", repeats: int = 2) -> List[Dict[str, Any]]: """用水清洗固体""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"💧 水洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, "water", volume=volume, repeats=repeats) -def wash_with_ethanol(G: nx.DiGraph, vessel: Union[str, dict], - volume: Union[float, str] = "30", +def wash_with_ethanol(G: nx.DiGraph, vessel: Union[str, dict], + volume: Union[float, str] = "30", repeats: int = 1) -> List[Dict[str, Any]]: """用乙醇清洗固体""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"🍺 乙醇洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, "ethanol", volume=volume, repeats=repeats) -def wash_with_acetone(G: nx.DiGraph, vessel: Union[str, dict], - volume: Union[float, str] = "25", +def wash_with_acetone(G: nx.DiGraph, vessel: Union[str, dict], + volume: Union[float, str] = "25", repeats: int = 1) -> List[Dict[str, Any]]: """用丙酮清洗固体""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"💨 丙酮洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, "acetone", volume=volume, repeats=repeats) -def wash_with_ether(G: nx.DiGraph, vessel: Union[str, dict], - volume: Union[float, str] = "40", +def wash_with_ether(G: nx.DiGraph, vessel: Union[str, dict], + volume: Union[float, str] = "40", repeats: int = 2) -> List[Dict[str, Any]]: """用乙醚清洗固体""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"🌬️ 乙醚洗固体: {vessel_display} ({repeats} 次)") return generate_wash_solid_protocol(G, vessel, "diethyl_ether", volume=volume, repeats=repeats) -def wash_with_cold_solvent(G: nx.DiGraph, vessel: Union[str, dict], - solvent: str, volume: Union[float, str] = "30", +def wash_with_cold_solvent(G: nx.DiGraph, vessel: Union[str, dict], + solvent: str, volume: Union[float, str] = "30", repeats: int = 1) -> List[Dict[str, Any]]: """用冷溶剂清洗固体""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"❄️ 冷{solvent}洗固体: {vessel_display} ({repeats} 次)") - return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, + return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, temp=5.0, repeats=repeats) -def wash_with_hot_solvent(G: nx.DiGraph, vessel: Union[str, dict], - solvent: str, volume: Union[float, str] = "50", +def wash_with_hot_solvent(G: nx.DiGraph, vessel: Union[str, dict], + solvent: str, volume: Union[float, str] = "50", repeats: int = 1) -> List[Dict[str, Any]]: """用热溶剂清洗固体""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"🔥 热{solvent}洗固体: {vessel_display} ({repeats} 次)") - return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, + return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, temp=60.0, repeats=repeats) -def wash_with_stirring(G: nx.DiGraph, vessel: Union[str, dict], - solvent: str, volume: Union[float, str] = "50", - stir_time: Union[str, float] = "5 min", +def wash_with_stirring(G: nx.DiGraph, vessel: Union[str, dict], + solvent: str, volume: Union[float, str] = "50", + stir_time: Union[str, float] = "5 min", repeats: int = 1) -> List[Dict[str, Any]]: """带搅拌的溶剂清洗""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"🌪️ 搅拌清洗: {vessel_display} with {solvent} ({repeats} 次)") - return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, - stir=True, stir_speed=200.0, + return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, + stir=True, stir_speed=200.0, time=stir_time, repeats=repeats) -def thorough_wash(G: nx.DiGraph, vessel: Union[str, dict], +def thorough_wash(G: nx.DiGraph, vessel: Union[str, dict], solvent: str, volume: Union[float, str] = "50") -> List[Dict[str, Any]]: """彻底清洗(多次重复)""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"🔄 彻底清洗: {vessel_display} with {solvent} (5 次)") return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=5) -def quick_rinse(G: nx.DiGraph, vessel: Union[str, dict], +def quick_rinse(G: nx.DiGraph, vessel: Union[str, dict], solvent: str, volume: Union[float, str] = "20") -> List[Dict[str, Any]]: """快速冲洗(单次,小体积)""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"⚡ 快速冲洗: {vessel_display} with {solvent}") return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=1) -def sequential_wash(G: nx.DiGraph, vessel: Union[str, dict], +def sequential_wash(G: nx.DiGraph, vessel: Union[str, dict], solvents: list, volume: Union[float, str] = "40") -> List[Dict[str, Any]]: """连续多溶剂清洗""" - vessel_display = get_vessel_display_info(vessel) - debug_print(f"📝 连续清洗: {vessel_display} with {' → '.join(solvents)}") - action_sequence = [] for solvent in solvents: - wash_actions = generate_wash_solid_protocol(G, vessel, solvent, + wash_actions = generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=1) action_sequence.extend(wash_actions) - + return action_sequence # 测试函数 def test_wash_solid_protocol(): """测试固体清洗协议""" - debug_print("🧪 === WASH SOLID PROTOCOL 测试 === ✨") - - # 测试vessel参数处理 - debug_print("🔧 测试vessel参数处理...") - - # 测试字典格式 - vessel_dict = {"id": "filter_flask_1", "name": "过滤瓶1", + debug_print("=== WASH SOLID PROTOCOL 测试 ===") + + vessel_dict = {"id": "filter_flask_1", "name": "过滤瓶1", "data": {"liquid_volume": 25.0}} vessel_id = extract_vessel_id(vessel_dict) vessel_display = get_vessel_display_info(vessel_dict) - volume = get_vessel_liquid_volume(vessel_dict) - debug_print(f" 字典格式: {vessel_dict}") - debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}, 体积: {volume}mL") - - # 测试字符串格式 + volume = get_resource_liquid_volume(vessel_dict) + debug_print(f"字典格式: ID={vessel_id}, 显示={vessel_display}, 体积={volume}mL") + vessel_str = "filter_flask_2" vessel_id = extract_vessel_id(vessel_str) vessel_display = get_vessel_display_info(vessel_str) - debug_print(f" 字符串格式: {vessel_str}") - debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}") - - debug_print("✅ 测试完成 🎉") + debug_print(f"字符串格式: ID={vessel_id}, 显示={vessel_display}") + + debug_print("测试完成") if __name__ == "__main__": - test_wash_solid_protocol() \ No newline at end of file + test_wash_solid_protocol()