refactor: 14个协议编译器去重精简,删除死代码

- 统一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 <noreply@anthropic.com>
This commit is contained in:
Junhan Chang
2026-03-25 13:12:10 +08:00
parent 5f36b6c04b
commit 0ab4027de7
15 changed files with 1018 additions and 3461 deletions

View File

@@ -2,20 +2,13 @@ from functools import partial
import networkx as nx import networkx as nx
import re import re
import logging
from typing import List, Dict, Any, Union from typing import List, Dict, Any, Union
from .utils.unit_parser import parse_volume_input, parse_mass_input, parse_time_input 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.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 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]") create_action_log = partial(action_log, prefix="[ADD]")

View File

@@ -1,14 +1,12 @@
from functools import partial
import networkx as nx import networkx as nx
import logging
from typing import List, Dict, Any, Union 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 from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__) create_action_log = partial(action_log, prefix="[ADJUST_PH]")
def debug_print(message):
"""调试输出"""
logger.info(f"[ADJUST_PH] {message}")
def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str: 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 return node_id
# 列出可用容器帮助调试 # 列出可用容器帮助调试
debug_print(f"📊 列出可用容器帮助调试...") available_containers = [node_id for node_id in G.nodes()
available_containers = [] if G.nodes[node_id].get('type') == 'container']
for node_id in G.nodes(): debug_print(f"所有匹配方法失败,可用容器: {available_containers}")
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"❌ 所有匹配方法都失败了")
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names[:10]}...") 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: def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float:
""" """
估算需要的试剂体积来调节pH 估算需要的试剂体积来调节pH
@@ -158,44 +115,30 @@ def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume
Returns: Returns:
float: 估算的试剂体积 (mL) float: 估算的试剂体积 (mL)
""" """
debug_print(f"🧮 计算试剂体积...") debug_print(f"计算试剂体积: pH={target_ph_value}, reagent={reagent}, vessel={vessel_volume}mL")
debug_print(f" 📍 目标pH: {target_ph_value}")
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" 📏 容器体积: {vessel_volume}mL")
# 简化的pH调节体积估算(实际应用中需要更精确的计算) # 简化的pH调节体积估算
if "acid" in reagent.lower() or "hcl" in reagent.lower(): if "acid" in reagent.lower() or "hcl" in reagent.lower():
debug_print(f"🍋 检测到酸性试剂")
# 酸性试剂pH越低需要的体积越大
if target_ph_value < 3: if target_ph_value < 3:
volume = vessel_volume * 0.05 # 5% volume = vessel_volume * 0.05
debug_print(f" 💪 强酸性 (pH<3): 使用 5% 体积")
elif target_ph_value < 5: elif target_ph_value < 5:
volume = vessel_volume * 0.02 # 2% volume = vessel_volume * 0.02
debug_print(f" 🔸 中酸性 (pH<5): 使用 2% 体积")
else: else:
volume = vessel_volume * 0.01 # 1% volume = vessel_volume * 0.01
debug_print(f" 🔹 弱酸性 (pH≥5): 使用 1% 体积")
elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower(): elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower():
debug_print(f"🧂 检测到碱性试剂")
# 碱性试剂pH越高需要的体积越大
if target_ph_value > 11: if target_ph_value > 11:
volume = vessel_volume * 0.05 # 5% volume = vessel_volume * 0.05
debug_print(f" 💪 强碱性 (pH>11): 使用 5% 体积")
elif target_ph_value > 9: elif target_ph_value > 9:
volume = vessel_volume * 0.02 # 2% volume = vessel_volume * 0.02
debug_print(f" 🔸 中碱性 (pH>9): 使用 2% 体积")
else: else:
volume = vessel_volume * 0.01 # 1% volume = vessel_volume * 0.01
debug_print(f" 🔹 弱碱性 (pH≤9): 使用 1% 体积")
else: else:
# 未知试剂,使用默认值 # 未知试剂,使用默认值
volume = vessel_volume * 0.01 volume = vessel_volume * 0.01
debug_print(f"❓ 未知试剂类型,使用默认 1% 体积")
debug_print(f"📊 计算结果: {volume:.2f}mL") debug_print(f"估算试剂体积: {volume:.2f}mL")
return volume return volume
def generate_adjust_ph_protocol( def generate_adjust_ph_protocol(
@@ -222,33 +165,18 @@ def generate_adjust_ph_protocol(
vessel_id, vessel_data = get_vessel(vessel) vessel_id, vessel_data = get_vessel(vessel)
if not vessel_id: if not vessel_id:
debug_print(f"❌ vessel 参数无效必须包含id字段或直接提供容器ID. vessel: {vessel}")
raise ValueError("vessel 参数无效必须包含id字段或直接提供容器ID") raise ValueError("vessel 参数无效必须包含id字段或直接提供容器ID")
debug_print("=" * 60) debug_print(f"开始生成pH调节协议: vessel={vessel_id}, ph={ph_value}, reagent='{reagent}'")
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)
action_sequence = [] action_sequence = []
# 从kwargs中获取可选参数,如果没有则使用默认值 # 从kwargs中获取可选参数
volume = kwargs.get('volume', 0.0) # 自动估算体积 volume = kwargs.get('volume', 0.0)
stir = kwargs.get('stir', True) # 默认搅拌 stir = kwargs.get('stir', True)
stir_speed = kwargs.get('stir_speed', 300.0) # 默认搅拌速度 stir_speed = kwargs.get('stir_speed', 300.0)
stir_time = kwargs.get('stir_time', 60.0) # 默认搅拌时间 stir_time = kwargs.get('stir_time', 60.0)
settling_time = kwargs.get('settling_time', 30.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")
# 开始处理 # 开始处理
action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪")) action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪"))
@@ -256,60 +184,46 @@ def generate_adjust_ph_protocol(
action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️")) action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️"))
# 1. 验证目标容器存在 # 1. 验证目标容器存在
debug_print(f"🔍 步骤1: 验证目标容器...")
if vessel_id not in G.nodes(): if vessel_id not in G.nodes():
debug_print(f"❌ 目标容器 '{vessel_id}' 不存在于系统中")
raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中") raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
debug_print(f"✅ 目标容器验证通过")
action_sequence.append(create_action_log("目标容器验证通过", "")) action_sequence.append(create_action_log("目标容器验证通过", ""))
# 2. 查找酸碱试剂容器 # 2. 查找酸碱试剂容器
debug_print(f"🔍 步骤2: 查找试剂容器...")
action_sequence.append(create_action_log("正在查找试剂容器...", "🔍")) action_sequence.append(create_action_log("正在查找试剂容器...", "🔍"))
try: try:
reagent_vessel = find_acid_base_vessel(G, reagent) reagent_vessel = find_acid_base_vessel(G, reagent)
debug_print(f"✅ 找到试剂容器: {reagent_vessel}")
action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪")) action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪"))
except ValueError as e: except ValueError as e:
debug_print(f"❌ 无法找到试剂容器: {str(e)}")
action_sequence.append(create_action_log(f"试剂容器查找失败: {str(e)}", "")) action_sequence.append(create_action_log(f"试剂容器查找失败: {str(e)}", ""))
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}") raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
# 3. 体积估算 # 3. 体积估算
debug_print(f"🔍 步骤3: 体积处理...")
if volume <= 0: if volume <= 0:
action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮")) action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮"))
# 获取目标容器的体积信息 # 获取目标容器的体积信息
vessel_data = G.nodes[vessel_id].get('data', {}) vessel_data = G.nodes[vessel_id].get('data', {})
vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL vessel_volume = vessel_data.get('max_volume', 100.0)
debug_print(f"📏 容器最大体积: {vessel_volume}mL")
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume) estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume)
volume = estimated_volume volume = estimated_volume
debug_print(f"✅ 自动估算试剂体积: {volume:.2f} mL")
action_sequence.append(create_action_log(f"估算试剂体积: {volume:.2f}mL", "📊")) action_sequence.append(create_action_log(f"估算试剂体积: {volume:.2f}mL", "📊"))
else: else:
debug_print(f"📏 使用指定体积: {volume}mL")
action_sequence.append(create_action_log(f"使用指定体积: {volume}mL", "📏")) action_sequence.append(create_action_log(f"使用指定体积: {volume}mL", "📏"))
# 4. 验证路径存在 # 4. 验证路径存在
debug_print(f"🔍 步骤4: 路径验证...")
action_sequence.append(create_action_log("验证转移路径...", "🛤️")) action_sequence.append(create_action_log("验证转移路径...", "🛤️"))
try: try:
path = nx.shortest_path(G, source=reagent_vessel, target=vessel_id) 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: except nx.NetworkXNoPath:
debug_print(f"❌ 无法找到转移路径")
action_sequence.append(create_action_log("转移路径不存在", "")) action_sequence.append(create_action_log("转移路径不存在", ""))
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel_id}' 没有可用路径") raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
# 5. 搅拌器设置 # 5. 搅拌器设置
debug_print(f"🔍 步骤5: 搅拌器设置...")
stirrer_id = None stirrer_id = None
if stir: if stir:
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️")) action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
@@ -318,7 +232,6 @@ def generate_adjust_ph_protocol(
stirrer_id = find_connected_stirrer(G, vessel_id) stirrer_id = find_connected_stirrer(G, vessel_id)
if stirrer_id: if stirrer_id:
debug_print(f"✅ 找到搅拌器 {stirrer_id},启动搅拌")
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🔄")) action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🔄"))
action_sequence.append({ action_sequence.append({
@@ -338,23 +251,18 @@ def generate_adjust_ph_protocol(
"action_kwargs": {"time": 5} "action_kwargs": {"time": 5}
}) })
else: else:
debug_print(f"⚠️ 未找到搅拌器,继续执行")
action_sequence.append(create_action_log("未找到搅拌器,跳过搅拌", "⚠️")) action_sequence.append(create_action_log("未找到搅拌器,跳过搅拌", "⚠️"))
except Exception as e: except Exception as e:
debug_print(f"❌ 搅拌器配置出错: {str(e)}")
action_sequence.append(create_action_log(f"搅拌器配置失败: {str(e)}", "")) action_sequence.append(create_action_log(f"搅拌器配置失败: {str(e)}", ""))
else: else:
debug_print(f"📋 跳过搅拌设置")
action_sequence.append(create_action_log("跳过搅拌设置", "⏭️")) action_sequence.append(create_action_log("跳过搅拌设置", "⏭️"))
# 6. 试剂添加 # 6. 试剂添加
debug_print(f"🔍 步骤6: 试剂添加...")
action_sequence.append(create_action_log(f"开始添加试剂 {volume:.2f}mL", "🚰")) action_sequence.append(create_action_log(f"开始添加试剂 {volume:.2f}mL", "🚰"))
# 计算添加时间pH调节需要缓慢添加 # 计算添加时间pH调节需要缓慢添加
addition_time = max(30.0, volume * 2.0) # 至少30秒每mL需要2秒 addition_time = max(30.0, volume * 2.0)
debug_print(f"⏱️ 计算添加时间: {addition_time}s (缓慢注入)")
action_sequence.append(create_action_log(f"设置添加时间: {addition_time:.0f}s (缓慢注入)", "⏱️")) action_sequence.append(create_action_log(f"设置添加时间: {addition_time:.0f}s (缓慢注入)", "⏱️"))
try: try:
@@ -377,11 +285,9 @@ def generate_adjust_ph_protocol(
) )
action_sequence.extend(pump_actions) action_sequence.extend(pump_actions)
debug_print(f"✅ 泵协议生成完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(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"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
debug_print(f"📊 添加前容器体积: {current_volume}") debug_print(f"📊 添加前容器体积: {current_volume}")

View File

@@ -1,7 +1,9 @@
from typing import List, Dict, Any from typing import List, Dict, Any
import networkx as nx 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 .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: def find_solvent_vessel_by_any_match(G: nx.DiGraph, solvent: str) -> str:
@@ -31,29 +33,9 @@ def find_waste_vessel(G: nx.DiGraph) -> str:
raise ValueError(f"未找到废液容器。尝试了以下名称: {possible_waste_names}") 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( def generate_clean_vessel_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
solvent: str, solvent: str,
volume: float, volume: float,
temp: float, temp: float,
@@ -81,24 +63,12 @@ def generate_clean_vessel_protocol(
Returns: Returns:
List[Dict[str, Any]]: 容器清洗操作的动作序列 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) vessel_id, vessel_data = get_vessel(vessel)
action_sequence = [] action_sequence = []
print(f"CLEAN_VESSEL: 开始生成容器清洗协议") debug_print(f"开始生成容器清洗协议: vessel={vessel_id}, solvent={solvent}, volume={volume}mL, temp={temp}°C, repeats={repeats}")
print(f" - 目标容器: {vessel} (ID: {vessel_id})")
print(f" - 清洗溶剂: {solvent}")
print(f" - 清洗体积: {volume} mL")
print(f" - 清洗温度: {temp}°C")
print(f" - 重复次数: {repeats}")
# 验证目标容器存在 # 验证目标容器存在
if vessel_id not in G.nodes(): if vessel_id not in G.nodes():
@@ -107,26 +77,25 @@ def generate_clean_vessel_protocol(
# 查找溶剂容器 # 查找溶剂容器
try: try:
solvent_vessel = find_solvent_vessel(G, solvent) solvent_vessel = find_solvent_vessel(G, solvent)
print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}") debug_print(f"找到溶剂容器: {solvent_vessel}")
except ValueError as e: except ValueError as e:
raise ValueError(f"无法找到溶剂容器: {str(e)}") raise ValueError(f"无法找到溶剂容器: {str(e)}")
# 查找废液容器 # 查找废液容器
try: try:
waste_vessel = find_waste_vessel(G) waste_vessel = find_waste_vessel(G)
print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}") debug_print(f"找到废液容器: {waste_vessel}")
except ValueError as e: except ValueError as e:
raise ValueError(f"无法找到废液容器: {str(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: if heatchill_id:
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}") debug_print(f"找到加热设备: {heatchill_id}")
else: else:
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗") debug_print(f"未找到加热设备,将在室温下清洗")
# 🔧 新增:记录清洗前的容器状态 # 记录清洗前的容器状态
print(f"CLEAN_VESSEL: 记录清洗前容器状态...")
original_liquid_volume = 0.0 original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -134,49 +103,44 @@ def generate_clean_vessel_protocol(
original_liquid_volume = current_volume[0] original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume original_liquid_volume = current_volume
print(f"CLEAN_VESSEL: 清洗前液体体积: {original_liquid_volume:.2f}mL")
# 第一步:如果需要加热且有加热设备,启动加热 # 第一步:如果需要加热且有加热设备,启动加热
if temp > 25.0 and heatchill_id: if temp > 25.0 and heatchill_id:
print(f"CLEAN_VESSEL: 启动加热至 {temp}°C") debug_print(f"启动加热至 {temp}°C")
heatchill_start_action = { heatchill_start_action = {
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "vessel": {"id": vessel_id},
"temp": temp, "temp": temp,
"purpose": f"cleaning with {solvent}" "purpose": f"cleaning with {solvent}"
} }
} }
action_sequence.append(heatchill_start_action) action_sequence.append(heatchill_start_action)
# 等待温度稳定
wait_action = { wait_action = {
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 30} # 等待30秒让温度稳定 "action_kwargs": {"time": 30}
} }
action_sequence.append(wait_action) action_sequence.append(wait_action)
# 第二步:重复清洗操作 # 第二步:重复清洗操作
for repeat in range(repeats): for repeat in range(repeats):
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗") debug_print(f"执行第 {repeat + 1}/{repeats} 次清洗")
# 2a. 使用 pump_protocol 将溶剂转移到目标容器 # 2a. 使用 pump_protocol 将溶剂转移到目标容器
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel_id}")
try: try:
# 调用成熟的 pump_protocol 算法
add_solvent_actions = generate_pump_protocol( add_solvent_actions = generate_pump_protocol(
G=G, G=G,
from_vessel=solvent_vessel, from_vessel=solvent_vessel,
to_vessel=vessel_id, # 🔧 使用 vessel_id to_vessel=vessel_id,
volume=volume, volume=volume,
flowrate=2.5, # 适中的流速,避免飞溅 flowrate=2.5,
transfer_flowrate=2.5 transfer_flowrate=2.5
) )
action_sequence.extend(add_solvent_actions) action_sequence.extend(add_solvent_actions)
# 🔧 新增:更新容器体积(添加清洗溶剂) # 更新容器体积(添加清洗溶剂)
print(f"CLEAN_VESSEL: 更新容器体积 - 添加清洗溶剂 {volume:.2f}mL")
if "data" not in vessel: if "data" not in vessel:
vessel["data"] = {} vessel["data"] = {}
@@ -185,21 +149,16 @@ def generate_clean_vessel_protocol(
if isinstance(current_volume, list): if isinstance(current_volume, list):
if len(current_volume) > 0: if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] += volume vessel["data"]["liquid_volume"][0] += volume
print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{volume:.2f}mL)")
else: else:
vessel["data"]["liquid_volume"] = [volume] vessel["data"]["liquid_volume"] = [volume]
print(f"CLEAN_VESSEL: 初始化清洗体积: {volume:.2f}mL")
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
vessel["data"]["liquid_volume"] += volume vessel["data"]["liquid_volume"] += volume
print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{volume:.2f}mL)")
else: else:
vessel["data"]["liquid_volume"] = volume vessel["data"]["liquid_volume"] = volume
print(f"CLEAN_VESSEL: 重置体积为: {volume:.2f}mL")
else: else:
vessel["data"]["liquid_volume"] = volume vessel["data"]["liquid_volume"] = volume
print(f"CLEAN_VESSEL: 创建新体积记录: {volume:.2f}mL")
# 🔧 同时更新图中的容器数据 # 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]: if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data'] = {}
@@ -215,14 +174,11 @@ def generate_clean_vessel_protocol(
else: else:
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + volume G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + volume
print(f"CLEAN_VESSEL: 图节点体积数据已更新")
except Exception as e: except Exception as e:
raise ValueError(f"无法将溶剂转移到容器: {str(e)}") raise ValueError(f"无法将溶剂转移到容器: {str(e)}")
# 2b. 等待清洗作用时间(让溶剂充分清洗容器) # 2b. 等待清洗作用时间
cleaning_wait_time = 60 if temp > 50.0 else 30 # 高温下等待更久 cleaning_wait_time = 60 if temp > 50.0 else 30
print(f"CLEAN_VESSEL: 等待清洗作用 {cleaning_wait_time}")
wait_action = { wait_action = {
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": cleaning_wait_time} "action_kwargs": {"time": cleaning_wait_time}
@@ -230,38 +186,31 @@ def generate_clean_vessel_protocol(
action_sequence.append(wait_action) action_sequence.append(wait_action)
# 2c. 使用 pump_protocol 将清洗液转移到废液容器 # 2c. 使用 pump_protocol 将清洗液转移到废液容器
print(f"CLEAN_VESSEL: 将清洗液从 {vessel_id} 转移到废液容器")
try: try:
# 调用成熟的 pump_protocol 算法
remove_waste_actions = generate_pump_protocol( remove_waste_actions = generate_pump_protocol(
G=G, G=G,
from_vessel=vessel_id, # 🔧 使用 vessel_id from_vessel=vessel_id,
to_vessel=waste_vessel, to_vessel=waste_vessel,
volume=volume, volume=volume,
flowrate=2.5, # 适中的流速 flowrate=2.5,
transfer_flowrate=2.5 transfer_flowrate=2.5
) )
action_sequence.extend(remove_waste_actions) action_sequence.extend(remove_waste_actions)
# 🔧 新增:更新容器体积(移除清洗液) # 更新容器体积(移除清洗液)
print(f"CLEAN_VESSEL: 更新容器体积 - 移除清洗液 {volume:.2f}mL")
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list): if isinstance(current_volume, list):
if len(current_volume) > 0: if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] = max(0.0, vessel["data"]["liquid_volume"][0] - volume) 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: else:
vessel["data"]["liquid_volume"] = [0.0] vessel["data"]["liquid_volume"] = [0.0]
print(f"CLEAN_VESSEL: 重置体积为0mL")
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
vessel["data"]["liquid_volume"] = max(0.0, current_volume - volume) vessel["data"]["liquid_volume"] = max(0.0, current_volume - volume)
print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume']:.2f}mL (-{volume:.2f}mL)")
else: else:
vessel["data"]["liquid_volume"] = 0.0 vessel["data"]["liquid_volume"] = 0.0
print(f"CLEAN_VESSEL: 重置体积为0mL")
# 🔧 同时更新图中的容器数据 # 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
vessel_node_data = G.nodes[vessel_id].get('data', {}) vessel_node_data = G.nodes[vessel_id].get('data', {})
current_node_volume = vessel_node_data.get('liquid_volume', 0.0) current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
@@ -274,14 +223,11 @@ def generate_clean_vessel_protocol(
else: else:
G.nodes[vessel_id]['data']['liquid_volume'] = max(0.0, current_node_volume - volume) G.nodes[vessel_id]['data']['liquid_volume'] = max(0.0, current_node_volume - volume)
print(f"CLEAN_VESSEL: 图节点体积数据已更新")
except Exception as e: except Exception as e:
raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}") raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}")
# 2d. 清洗循环间的短暂等待 # 2d. 清洗循环间的短暂等待
if repeat < repeats - 1: # 不是最后一次清洗 if repeat < repeats - 1:
print(f"CLEAN_VESSEL: 清洗循环间等待")
wait_action = { wait_action = {
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 10} "action_kwargs": {"time": 10}
@@ -290,17 +236,16 @@ def generate_clean_vessel_protocol(
# 第三步:如果加热了,停止加热 # 第三步:如果加热了,停止加热
if temp > 25.0 and heatchill_id: if temp > 25.0 and heatchill_id:
print(f"CLEAN_VESSEL: 停止加热")
heatchill_stop_action = { heatchill_stop_action = {
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "action_kwargs": {
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "vessel": {"id": vessel_id},
} }
} }
action_sequence.append(heatchill_stop_action) action_sequence.append(heatchill_stop_action)
# 🔧 新增:清洗完成后的状态报告 # 清洗完成后的状态
final_liquid_volume = 0.0 final_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -309,18 +254,15 @@ def generate_clean_vessel_protocol(
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
final_liquid_volume = current_volume final_liquid_volume = current_volume
print(f"CLEAN_VESSEL: 清洗完成") debug_print(f"清洗完成: {len(action_sequence)} 个动作, 体积 {original_liquid_volume:.2f} -> {final_liquid_volume:.2f}mL")
print(f" - 清洗前体积: {original_liquid_volume:.2f}mL")
print(f" - 清洗后体积: {final_liquid_volume:.2f}mL")
print(f" - 生成了 {len(action_sequence)} 个动作")
return action_sequence return action_sequence
# 便捷函数:常用清洗方案 # 便捷函数
def generate_quick_clean_protocol( def generate_quick_clean_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
solvent: str = "water", solvent: str = "water",
volume: float = 100.0 volume: float = 100.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
@@ -330,7 +272,7 @@ def generate_quick_clean_protocol(
def generate_thorough_clean_protocol( def generate_thorough_clean_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
solvent: str = "water", solvent: str = "water",
volume: float = 150.0, volume: float = 150.0,
temp: float = 60.0 temp: float = 60.0
@@ -341,7 +283,7 @@ def generate_thorough_clean_protocol(
def generate_organic_clean_protocol( def generate_organic_clean_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
volume: float = 100.0 volume: float = 100.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""有机清洗:先用有机溶剂,再用水清洗""" """有机清洗:先用有机溶剂,再用水清洗"""
@@ -354,14 +296,13 @@ def generate_organic_clean_protocol(
) )
action_sequence.extend(organic_actions) action_sequence.extend(organic_actions)
except ValueError: except ValueError:
# 如果没有丙酮,尝试乙醇
try: try:
organic_actions = generate_clean_vessel_protocol( organic_actions = generate_clean_vessel_protocol(
G, vessel, "ethanol", volume, 25.0, 2 G, vessel, "ethanol", volume, 25.0, 2
) )
action_sequence.extend(organic_actions) action_sequence.extend(organic_actions)
except ValueError: except ValueError:
print("警告:未找到有机溶剂,跳过有机清洗步骤") debug_print("未找到有机溶剂,跳过有机清洗步骤")
# 第二步:水清洗 # 第二步:水清洗
water_actions = generate_clean_vessel_protocol( water_actions = generate_clean_vessel_protocol(
@@ -372,24 +313,6 @@ def generate_organic_clean_protocol(
return action_sequence 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]: def get_vessel_liquid_types(G: nx.DiGraph, vessel: str) -> List[str]:
"""获取容器中所有液体的类型""" """获取容器中所有液体的类型"""
if vessel not in G.nodes(): if vessel not in G.nodes():
@@ -401,7 +324,6 @@ def get_vessel_liquid_types(G: nx.DiGraph, vessel: str) -> List[str]:
liquid_types = [] liquid_types = []
for liquid in liquids: for liquid in liquids:
if isinstance(liquid, dict): if isinstance(liquid, dict):
# 支持两种格式的液体类型字段
liquid_type = liquid.get('liquid_type') or liquid.get('name', '') liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type: if liquid_type:
liquid_types.append(liquid_type) liquid_types.append(liquid_type)
@@ -412,24 +334,20 @@ def get_vessel_liquid_types(G: nx.DiGraph, vessel: str) -> List[str]:
def find_vessel_by_content(G: nx.DiGraph, content: str) -> List[str]: def find_vessel_by_content(G: nx.DiGraph, content: str) -> List[str]:
""" """
根据内容物查找所有匹配的容器 根据内容物查找所有匹配的容器
返回匹配容器的ID列表
""" """
matching_vessels = [] matching_vessels = []
for node_id in G.nodes(): for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container': if G.nodes[node_id].get('type') == 'container':
# 检查容器名称匹配
node_name = G.nodes[node_id].get('name', '').lower() node_name = G.nodes[node_id].get('name', '').lower()
if content.lower() in node_id.lower() or content.lower() in node_name: if content.lower() in node_id.lower() or content.lower() in node_name:
matching_vessels.append(node_id) matching_vessels.append(node_id)
continue continue
# 检查液体类型匹配
vessel_data = G.nodes[node_id].get('data', {}) vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', []) liquids = vessel_data.get('liquid', [])
config_data = G.nodes[node_id].get('config', {}) config_data = G.nodes[node_id].get('config', {})
# 检查 reagent_name 和 config.reagent
reagent_name = vessel_data.get('reagent_name', '').lower() reagent_name = vessel_data.get('reagent_name', '').lower()
config_reagent = config_data.get('reagent', '').lower() config_reagent = config_data.get('reagent', '').lower()
@@ -438,7 +356,6 @@ def find_vessel_by_content(G: nx.DiGraph, content: str) -> List[str]:
matching_vessels.append(node_id) matching_vessels.append(node_id)
continue continue
# 检查液体列表
for liquid in liquids: for liquid in liquids:
if isinstance(liquid, dict): if isinstance(liquid, dict):
liquid_type = liquid.get('liquid_type') or liquid.get('name', '') liquid_type = liquid.get('liquid_type') or liquid.get('name', '')

View File

@@ -1,402 +1,19 @@
from functools import partial from functools import partial
import networkx as nx import networkx as nx
import re
import logging import logging
from typing import List, Dict, Any, Union from typing import List, Dict, Any, Union
from .utils.vessel_parser import get_vessel from .utils.logger_util import debug_print, action_log
from .utils.logger_util import 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 from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def debug_print(message): # 创建进度日志动作
"""调试输出"""
logger.info(f"[DISSOLVE] {message}")
# 🆕 创建进度日志动作
create_action_log = partial(action_log, prefix="[DISSOLVE]") 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( def generate_dissolve_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict, # 🔧 修改:从字符串改为字典类型
@@ -436,28 +53,16 @@ def generate_dissolve_protocol(
- mol: "0.12 mol", "16.2 mmol" - mol: "0.12 mol", "16.2 mmol"
""" """
# 🔧 核心修改:从字典中提取容器ID # 从字典中提取容器ID
vessel_id, vessel_data = get_vessel(vessel) vessel_id, vessel_data = get_vessel(vessel)
debug_print("=" * 60) debug_print(f"开始生成溶解协议: vessel={vessel_id}, solvent='{solvent}', "
debug_print("🧪 开始生成溶解协议") f"volume={volume}, mass={mass}, temp={temp}, time={time}, "
debug_print(f"📋 原始参数:") f"reagent='{reagent}', mol='{mol}', event='{event}'")
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)
action_sequence = [] action_sequence = []
# === 参数验证 === # === 参数验证 ===
debug_print("🔍 步骤1: 参数验证...")
action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel_id}", "🎬")) action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel_id}", "🎬"))
if not vessel_id: if not vessel_id:
@@ -465,14 +70,11 @@ def generate_dissolve_protocol(
raise ValueError("vessel 参数不能为空") raise ValueError("vessel 参数不能为空")
if vessel_id not in G.nodes(): if vessel_id not in G.nodes():
debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中")
raise ValueError(f"容器 '{vessel_id}' 不存在于系统中") raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
debug_print("✅ 基本参数验证通过")
action_sequence.append(create_action_log("参数验证通过", "")) action_sequence.append(create_action_log("参数验证通过", ""))
# 🔧 新增:记录溶解前的容器状态 # 记录溶解前的容器状态
debug_print("🔍 记录溶解前容器状态...")
original_liquid_volume = 0.0 original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -480,10 +82,8 @@ def generate_dissolve_protocol(
original_liquid_volume = current_volume[0] original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume original_liquid_volume = current_volume
debug_print(f"📊 溶解前液体体积: {original_liquid_volume:.2f}mL")
# === 🔧 关键修复:参数解析 === # === 参数解析 ===
debug_print("🔍 步骤2: 参数解析...")
action_sequence.append(create_action_log("正在解析溶解参数...", "🔍")) action_sequence.append(create_action_log("正在解析溶解参数...", "🔍"))
# 解析各种参数为数值 # 解析各种参数为数值
@@ -492,17 +92,10 @@ def generate_dissolve_protocol(
final_temp = parse_temperature_input(temp) final_temp = parse_temperature_input(temp)
final_time = parse_time_input(time) final_time = parse_time_input(time)
debug_print(f"📊 解析结果:") debug_print(f"解析结果: volume={final_volume}mL, mass={final_mass}g, "
debug_print(f" 📏 体积: {final_volume}mL") f"temp={final_temp}°C, time={final_time}s")
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("🔍 步骤3: 判断溶解类型...")
action_sequence.append(create_action_log("正在判断溶解类型...", "🔍")) action_sequence.append(create_action_log("正在判断溶解类型...", "🔍"))
# 判断是固体溶解还是液体溶解 # 判断是固体溶解还是液体溶解
@@ -519,12 +112,11 @@ def generate_dissolve_protocol(
dissolve_type = "固体溶解" if is_solid_dissolve else "液体溶解" dissolve_type = "固体溶解" if is_solid_dissolve else "液体溶解"
dissolve_emoji = "🧂" 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}", "📋")) action_sequence.append(create_action_log(f"确定溶解类型: {dissolve_type} {dissolve_emoji}", "📋"))
# === 查找设备 === # === 查找设备 ===
debug_print("🔍 步骤4: 查找设备...")
action_sequence.append(create_action_log("正在查找相关设备...", "🔍")) action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
# 查找加热搅拌器 # 查找加热搅拌器
@@ -533,11 +125,7 @@ def generate_dissolve_protocol(
# 优先使用加热搅拌器,否则使用独立搅拌器 # 优先使用加热搅拌器,否则使用独立搅拌器
stir_device_id = heatchill_id or stirrer_id stir_device_id = heatchill_id or stirrer_id
debug_print(f"设备映射: heatchill='{heatchill_id}', stirrer='{stirrer_id}', 使用='{stir_device_id}'")
debug_print(f"📊 设备映射:")
debug_print(f" 🔥 加热器: '{heatchill_id}'")
debug_print(f" 🌪️ 搅拌器: '{stirrer_id}'")
debug_print(f" 🎯 使用设备: '{stir_device_id}'")
if heatchill_id: if heatchill_id:
action_sequence.append(create_action_log(f"找到加热搅拌器: {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("未找到搅拌设备,将跳过搅拌", "⚠️")) action_sequence.append(create_action_log("未找到搅拌设备,将跳过搅拌", "⚠️"))
# === 执行溶解流程 === # === 执行溶解流程 ===
debug_print("🔍 步骤5: 执行溶解流程...")
try: try:
# 步骤5.1: 启动加热搅拌(如果需要) # 步骤5.1: 启动加热搅拌(如果需要)
if stir_device_id and (final_temp > 25.0 or final_time > 0 or stir_speed > 0): 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)", "🔥")) action_sequence.append(create_action_log(f"准备加热搅拌 (目标温度: {final_temp}°C)", "🔥"))
if heatchill_id and (final_temp > 25.0 or final_time > 0): if heatchill_id and (final_temp > 25.0 or final_time > 0):
@@ -603,7 +188,6 @@ def generate_dissolve_protocol(
if is_solid_dissolve: if is_solid_dissolve:
# === 固体溶解路径 === # === 固体溶解路径 ===
debug_print(f"🔍 5.2: 使用固体溶解路径")
action_sequence.append(create_action_log("开始固体溶解流程", "🧂")) action_sequence.append(create_action_log("开始固体溶解流程", "🧂"))
solid_dispenser = find_solid_dispenser(G) solid_dispenser = find_solid_dispenser(G)
@@ -632,12 +216,9 @@ def generate_dissolve_protocol(
"action_kwargs": add_kwargs "action_kwargs": add_kwargs
}) })
debug_print(f"✅ 固体加样完成")
action_sequence.append(create_action_log("固体加样完成", "")) action_sequence.append(create_action_log("固体加样完成", ""))
# 🔧 新增:固体溶解体积运算 - 固体本身不会显著增加体积,但可能有少量变化 # 固体溶解体积运算 - 固体本身不会显著增加体积
debug_print(f"🔧 固体溶解 - 体积变化很小,主要是质量变化")
# 固体通常不会显著改变液体体积,这里只记录日志
action_sequence.append(create_action_log(f"固体已添加: {final_mass}g", "📊")) action_sequence.append(create_action_log(f"固体已添加: {final_mass}g", "📊"))
else: else:
@@ -646,7 +227,6 @@ def generate_dissolve_protocol(
elif is_liquid_dissolve: elif is_liquid_dissolve:
# === 液体溶解路径 === # === 液体溶解路径 ===
debug_print(f"🔍 5.3: 使用液体溶解路径")
action_sequence.append(create_action_log("开始液体溶解流程", "💧")) action_sequence.append(create_action_log("开始液体溶解流程", "💧"))
# 查找溶剂容器 # 查找溶剂容器
@@ -688,11 +268,9 @@ def generate_dissolve_protocol(
**kwargs **kwargs
) )
action_sequence.extend(pump_actions) action_sequence.extend(pump_actions)
debug_print(f"✅ 溶剂转移完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", "")) action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", ""))
# 🔧 新增:液体溶解体积运算 - 添加溶剂后更新容器体积 # 液体溶解体积运算 - 添加溶剂后更新容器体积
debug_print(f"🔧 更新容器液体体积 - 添加溶剂 {final_volume:.2f}mL")
# 确保vessel有data字段 # 确保vessel有data字段
if "data" not in vessel: if "data" not in vessel:
@@ -703,19 +281,14 @@ def generate_dissolve_protocol(
if isinstance(current_volume, list): if isinstance(current_volume, list):
if len(current_volume) > 0: if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] += final_volume vessel["data"]["liquid_volume"][0] += final_volume
debug_print(f"📊 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{final_volume:.2f}mL)")
else: else:
vessel["data"]["liquid_volume"] = [final_volume] vessel["data"]["liquid_volume"] = [final_volume]
debug_print(f"📊 初始化溶解体积: {final_volume:.2f}mL")
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
vessel["data"]["liquid_volume"] += final_volume vessel["data"]["liquid_volume"] += final_volume
debug_print(f"📊 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{final_volume:.2f}mL)")
else: else:
vessel["data"]["liquid_volume"] = final_volume vessel["data"]["liquid_volume"] = final_volume
debug_print(f"📊 重置体积为: {final_volume:.2f}mL")
else: else:
vessel["data"]["liquid_volume"] = final_volume vessel["data"]["liquid_volume"] = final_volume
debug_print(f"📊 创建新体积记录: {final_volume:.2f}mL")
# 🔧 同时更新图中的容器数据 # 🔧 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
@@ -733,8 +306,6 @@ def generate_dissolve_protocol(
else: else:
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + final_volume 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)", "📊")) action_sequence.append(create_action_log(f"容器体积已更新 (+{final_volume:.2f}mL)", "📊"))
# 溶剂添加后等待 # 溶剂添加后等待
@@ -746,7 +317,6 @@ def generate_dissolve_protocol(
# 步骤5.4: 等待溶解完成 # 步骤5.4: 等待溶解完成
if final_time > 0: if final_time > 0:
debug_print(f"🔍 5.4: 等待溶解完成 - {final_time}s")
wait_minutes = final_time / 60 wait_minutes = final_time / 60
action_sequence.append(create_action_log(f"开始溶解等待 ({wait_minutes:.1f}分钟)", "")) action_sequence.append(create_action_log(f"开始溶解等待 ({wait_minutes:.1f}分钟)", ""))
@@ -795,7 +365,6 @@ def generate_dissolve_protocol(
# 步骤5.5: 停止加热搅拌(如果需要) # 步骤5.5: 停止加热搅拌(如果需要)
if heatchill_id and final_time == 0 and final_temp > 25.0: if heatchill_id and final_time == 0 and final_temp > 25.0:
debug_print(f"🔍 5.5: 停止加热器")
action_sequence.append(create_action_log("停止加热搅拌器", "🛑")) action_sequence.append(create_action_log("停止加热搅拌器", "🛑"))
stop_action = { stop_action = {
@@ -829,23 +398,8 @@ def generate_dissolve_protocol(
final_liquid_volume = current_volume final_liquid_volume = current_volume
# === 最终结果 === # === 最终结果 ===
debug_print("=" * 60) debug_print(f"溶解协议生成完成: {vessel_id}, 类型={dissolve_type}, "
debug_print(f"🎉 溶解协议生成完成") f"动作数={len(action_sequence)}, 前后体积={original_liquid_volume:.2f}{final_liquid_volume:.2f}mL")
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)
# 添加完成日志 # 添加完成日志
summary_msg = f"溶解协议完成: {vessel_id}" summary_msg = f"溶解协议完成: {vessel_id}"

View File

@@ -1,56 +1,15 @@
import networkx as nx import networkx as nx
from typing import List, Dict, Any from typing import List, Dict, Any
from unilabos.compile.utils.vessel_parser import get_vessel from .utils.vessel_parser import get_vessel, find_connected_heatchill
from .utils.logger_util import debug_print
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
def generate_dry_protocol( def generate_dry_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
compound: str = "", # 🔧 修改:参数顺序调整,并设置默认值 compound: str = "",
**kwargs # 接收其他可能的参数但不使用 **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成干燥协议序列 生成干燥协议序列
@@ -64,24 +23,18 @@ def generate_dry_protocol(
Returns: Returns:
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id, vessel_data = get_vessel(vessel) vessel_id, vessel_data = get_vessel(vessel)
action_sequence = [] action_sequence = []
# 默认参数 # 默认参数
dry_temp = 60.0 # 默认干燥温度 60°C dry_temp = 60.0
dry_time = 3600.0 # 默认干燥时间 1小时3600秒 dry_time = 3600.0
simulation_time = 60.0 # 模拟时间 1分钟 simulation_time = 60.0
print(f"🌡️ DRY: 开始生成干燥协议 ✨") debug_print(f"开始生成干燥协议: vessel={vessel_id}, compound={compound or '未指定'}, temp={dry_temp}°C")
print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
print(f" 🧪 化合物: {compound or '未指定'}")
print(f" 🔥 干燥温度: {dry_temp}°C")
print(f" ⏰ 干燥时间: {dry_time/60:.0f} 分钟")
# 🔧 新增:记录干燥前的容器状态 # 记录干燥前的容器状态
print(f"🔍 记录干燥前容器状态...")
original_liquid_volume = 0.0 original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -89,39 +42,30 @@ def generate_dry_protocol(
original_liquid_volume = current_volume[0] original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume original_liquid_volume = current_volume
print(f"📊 干燥前液体体积: {original_liquid_volume:.2f}mL")
# 1. 验证目标容器存在 # 1. 验证目标容器存在
print(f"\n📋 步骤1: 验证目标容器 '{vessel_id}' 是否存在...")
if vessel_id not in G.nodes(): if vessel_id not in G.nodes():
print(f"⚠️ DRY: 警告 - 容器 '{vessel_id}' 不存在于系统中,跳过干燥 😢") debug_print(f"容器 '{vessel_id}' 不存在于系统中,跳过干燥")
return action_sequence return action_sequence
print(f"✅ 容器 '{vessel_id}' 验证通过!")
# 2. 查找相连的加热器 # 2. 查找相连的加热器
print(f"\n🔍 步骤2: 查找与容器相连的加热器...") heater_id = find_connected_heatchill(G, vessel_id)
heater_id = find_connected_heater(G, vessel_id) # 🔧 使用 vessel_id
if heater_id is None: if heater_id is None:
print(f"😭 DRY: 警告 - 未找到与容器 '{vessel_id}' 相连的加热器,跳过干燥") debug_print(f"未找到与容器 '{vessel_id}' 相连的加热器,添加模拟干燥动作")
print(f"🎭 添加模拟干燥动作...")
# 添加一个等待动作,表示干燥过程(模拟)
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
"time": 10.0, # 模拟等待时间 "time": 10.0,
"description": f"模拟干燥 {compound or '化合物'} (无加热器可用)" "description": f"模拟干燥 {compound or '化合物'} (无加热器可用)"
} }
}) })
# 🔧 新增:模拟干燥的体积变化(溶剂蒸发) # 模拟干燥的体积变化
print(f"🔧 模拟干燥过程的体积减少...")
if original_liquid_volume > 0: if original_liquid_volume > 0:
# 假设干燥过程中损失10%的体积(溶剂蒸发)
volume_loss = original_liquid_volume * 0.1 volume_loss = original_liquid_volume * 0.1
new_volume = max(0.0, original_liquid_volume - volume_loss) new_volume = max(0.0, original_liquid_volume - volume_loss)
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list): if isinstance(current_volume, list):
@@ -134,7 +78,6 @@ def generate_dry_protocol(
else: else:
vessel["data"]["liquid_volume"] = new_volume vessel["data"]["liquid_volume"] = new_volume
# 🔧 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]: if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data'] = {}
@@ -150,32 +93,26 @@ def generate_dry_protocol(
else: else:
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
print(f"📊 模拟干燥体积变化: {original_liquid_volume:.2f}mL {new_volume:.2f}mL (-{volume_loss:.2f}mL)") debug_print(f"模拟干燥体积变化: {original_liquid_volume:.2f}mL -> {new_volume:.2f}mL")
print(f"📄 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯") debug_print(f"协议生成完成,共 {len(action_sequence)} 个动作")
return action_sequence return action_sequence
print(f"🎉 找到加热器: {heater_id}!") debug_print(f"找到加热器: {heater_id}")
# 3. 启动加热器进行干燥 # 3. 启动加热器进行干燥
print(f"\n🚀 步骤3: 开始执行干燥流程...")
print(f"🔥 启动加热器 {heater_id} 进行干燥")
# 3.1 启动加热 # 3.1 启动加热
print(f" ⚡ 动作1: 启动加热到 {dry_temp}°C...")
action_sequence.append({ action_sequence.append({
"device_id": heater_id, "device_id": heater_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "vessel": {"id": vessel_id},
"temp": dry_temp, "temp": dry_temp,
"purpose": f"干燥 {compound or '化合物'}" "purpose": f"干燥 {compound or '化合物'}"
} }
}) })
print(f" ✅ 加热器启动命令已添加 🔥")
# 3.2 等待温度稳定 # 3.2 等待温度稳定
print(f" ⏳ 动作2: 等待温度稳定...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
@@ -183,34 +120,27 @@ def generate_dry_protocol(
"description": f"等待温度稳定到 {dry_temp}°C" "description": f"等待温度稳定到 {dry_temp}°C"
} }
}) })
print(f" ✅ 温度稳定等待命令已添加 🌡️")
# 3.3 保持干燥温度 # 3.3 保持干燥温度
print(f" 🔄 动作3: 保持干燥温度 {simulation_time/60:.0f} 分钟...")
action_sequence.append({ action_sequence.append({
"device_id": heater_id, "device_id": heater_id,
"action_name": "heat_chill", "action_name": "heat_chill",
"action_kwargs": { "action_kwargs": {
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "vessel": {"id": vessel_id},
"temp": dry_temp, "temp": dry_temp,
"time": simulation_time, "time": simulation_time,
"purpose": f"干燥 {compound or '化合物'},保持温度 {dry_temp}°C" "purpose": f"干燥 {compound or '化合物'},保持温度 {dry_temp}°C"
} }
}) })
print(f" ✅ 温度保持命令已添加 🌡️⏰")
# 🔧 新增:干燥过程中的体积变化计算 # 干燥过程中的体积变化计算
print(f"🔧 计算干燥过程中的体积变化...")
if original_liquid_volume > 0: if original_liquid_volume > 0:
# 干燥过程中,溶剂会蒸发,固体保留 evaporation_rate = 0.001 * dry_temp
# 根据温度和时间估算蒸发量
evaporation_rate = 0.001 * dry_temp # 每秒每°C蒸发0.001mL
total_evaporation = min(original_liquid_volume * 0.8, total_evaporation = min(original_liquid_volume * 0.8,
evaporation_rate * simulation_time) # 最多蒸发80% evaporation_rate * simulation_time)
new_volume = max(0.0, original_liquid_volume - total_evaporation) new_volume = max(0.0, original_liquid_volume - total_evaporation)
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list): if isinstance(current_volume, list):
@@ -223,7 +153,6 @@ def generate_dry_protocol(
else: else:
vessel["data"]["liquid_volume"] = new_volume vessel["data"]["liquid_volume"] = new_volume
# 🔧 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]: if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data'] = {}
@@ -239,36 +168,28 @@ def generate_dry_protocol(
else: else:
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
print(f"📊 干燥体积变化计算:") debug_print(f"干燥体积变化: {original_liquid_volume:.2f}mL -> {new_volume:.2f}mL (-{total_evaporation:.2f}mL)")
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}%")
# 3.4 停止加热 # 3.4 停止加热
print(f" ⏹️ 动作4: 停止加热...")
action_sequence.append({ action_sequence.append({
"device_id": heater_id, "device_id": heater_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "action_kwargs": {
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "vessel": {"id": vessel_id},
"purpose": f"干燥完成,停止加热" "purpose": f"干燥完成,停止加热"
} }
}) })
print(f" ✅ 停止加热命令已添加 🛑")
# 3.5 等待冷却 # 3.5 等待冷却
print(f" ❄️ 动作5: 等待冷却...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
"time": 10.0, # 等待10秒冷却 "time": 10.0,
"description": f"等待 {compound or '化合物'} 冷却" "description": f"等待 {compound or '化合物'} 冷却"
} }
}) })
print(f" ✅ 冷却等待命令已添加 🧊")
# 🔧 新增:干燥完成后的状态报告 # 最终状态
final_liquid_volume = 0.0 final_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -277,58 +198,35 @@ def generate_dry_protocol(
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
final_liquid_volume = current_volume final_liquid_volume = current_volume
print(f"\n🎊 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯") debug_print(f"干燥协议生成完成: {len(action_sequence)} 个动作, 体积 {original_liquid_volume:.2f} -> {final_liquid_volume:.2f}mL")
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"🏁 所有动作序列准备就绪! ✨")
return action_sequence 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]]: 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) 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]]: 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) 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]]: 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) return generate_dry_protocol(G, vessel, compound)
# 测试函数 # 测试函数
def test_dry_protocol(): def test_dry_protocol():
"""测试干燥协议""" """测试干燥协议"""
print("=== DRY PROTOCOL 测试 ===") debug_print("=== DRY PROTOCOL 测试 ===")
print("测试完成") debug_print("测试完成")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -3,38 +3,14 @@ from functools import partial
import networkx as nx import networkx as nx
import logging import logging
import uuid import uuid
import sys
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from .utils.vessel_parser import get_vessel from .utils.vessel_parser import get_vessel, find_connected_stirrer
from .utils.logger_util import action_log from .utils.logger_util import debug_print, action_log
from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol
# 设置日志 # 设置日志
logger = logging.getLogger(__name__) 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="[抽真空充气]") create_action_log = partial(action_log, prefix="[抽真空充气]")
def find_gas_source(G: nx.DiGraph, gas: str) -> str: 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 2. 气体类型匹配data.gas_type
3. 默认气源 3. 默认气源
""" """
debug_print(f"🔍 正在查找气体 '{gas}' 的气源...") debug_print(f"正在查找气体 '{gas}' 的气源...")
# 第一步:通过容器名称匹配 # 通过容器名称匹配
debug_print(f"📋 方法1: 容器名称匹配...")
gas_source_patterns = [ gas_source_patterns = [
f"gas_source_{gas}", f"gas_source_{gas}",
f"gas_{gas}", f"gas_{gas}",
@@ -58,42 +33,35 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
f"bottle_{gas}" f"bottle_{gas}"
] ]
debug_print(f"🎯 尝试的容器名称: {gas_source_patterns}")
for pattern in gas_source_patterns: for pattern in gas_source_patterns:
if pattern in G.nodes(): if pattern in G.nodes():
debug_print(f"通过名称找到气源: {pattern}") debug_print(f"通过名称找到气源: {pattern}")
return pattern return pattern
# 第二步:通过气体类型匹配 (data.gas_type) # 通过气体类型匹配 (data.gas_type)
debug_print(f"📋 方法2: 气体类型匹配...")
for node_id in G.nodes(): for node_id in G.nodes():
node_data = G.nodes[node_id] node_data = G.nodes[node_id]
node_class = node_data.get('class', '') or '' 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 'gas' in node_id.lower() or
node_id.startswith('flask_')): node_id.startswith('flask_')):
# 检查 data.gas_type
data = node_data.get('data', {}) data = node_data.get('data', {})
gas_type = data.get('gas_type', '') gas_type = data.get('gas_type', '')
if gas_type.lower() == gas.lower(): if gas_type.lower() == gas.lower():
debug_print(f"通过气体类型找到气源: {node_id} (气体类型: {gas_type})") debug_print(f"通过气体类型找到气源: {node_id} (气体类型: {gas_type})")
return node_id return node_id
# 检查 config.gas_type
config = node_data.get('config', {}) config = node_data.get('config', {})
config_gas_type = config.get('gas_type', '') config_gas_type = config.get('gas_type', '')
if config_gas_type.lower() == gas.lower(): 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 return node_id
# 第三步:查找所有可用的气源设备 # 查找所有可用的气源设备
debug_print(f"📋 方法3: 查找可用气源...")
available_gas_sources = [] available_gas_sources = []
for node_id in G.nodes(): for node_id in G.nodes():
node_data = G.nodes[node_id] node_data = G.nodes[node_id]
@@ -107,10 +75,7 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
gas_type = data.get('gas_type', '未知') gas_type = data.get('gas_type', '未知')
available_gas_sources.append(f"{node_id} (气体类型: {gas_type})") available_gas_sources.append(f"{node_id} (气体类型: {gas_type})")
debug_print(f"📊 可用气源: {available_gas_sources}") # 如果找不到特定气体,使用默认的第一个气源
# 第四步:如果找不到特定气体,使用默认的第一个气源
debug_print(f"📋 方法4: 查找默认气源...")
default_gas_sources = [ 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 if ((G.nodes[node].get('class') or '').find('virtual_gas_source') != -1
@@ -119,16 +84,13 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
if default_gas_sources: if default_gas_sources:
default_source = default_gas_sources[0] default_source = default_gas_sources[0]
debug_print(f"⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}") debug_print(f"未找到特定气体 '{gas}',使用默认气源: {default_source}")
return default_source return default_source
debug_print(f"❌ 所有方法都失败了!")
raise ValueError(f"无法找到气体 '{gas}' 的气源。可用气源: {available_gas_sources}") raise ValueError(f"无法找到气体 '{gas}' 的气源。可用气源: {available_gas_sources}")
def find_vacuum_pump(G: nx.DiGraph) -> str: def find_vacuum_pump(G: nx.DiGraph) -> str:
"""查找真空泵设备""" """查找真空泵设备"""
debug_print("🔍 正在查找真空泵...")
vacuum_pumps = [] vacuum_pumps = []
for node in G.nodes(): for node in G.nodes():
node_data = G.nodes[node] node_data = G.nodes[node]
@@ -138,49 +100,15 @@ def find_vacuum_pump(G: nx.DiGraph) -> str:
'vacuum_pump' in node.lower() or 'vacuum_pump' in node.lower() or
'vacuum' in node_class.lower()): 'vacuum' in node_class.lower()):
vacuum_pumps.append(node) vacuum_pumps.append(node)
debug_print(f"📋 发现真空泵: {node}")
if not vacuum_pumps: if not vacuum_pumps:
debug_print(f"❌ 系统中未找到真空泵")
raise ValueError("系统中未找到真空泵") raise ValueError("系统中未找到真空泵")
debug_print(f"使用真空泵: {vacuum_pumps[0]}") debug_print(f"使用真空泵: {vacuum_pumps[0]}")
return 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
def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]: def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]:
"""查找真空泵相关的电磁阀""" """查找真空泵相关的电磁阀"""
debug_print(f"🔍 正在查找真空泵 {vacuum_pump} 的电磁阀...")
# 查找所有电磁阀
solenoid_valves = [] solenoid_valves = []
for node in G.nodes(): for node in G.nodes():
node_data = G.nodes[node] node_data = G.nodes[node]
@@ -188,32 +116,24 @@ def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()): if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
solenoid_valves.append(node) solenoid_valves.append(node)
debug_print(f"📋 发现电磁阀: {node}")
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
# 检查连接关系 # 检查连接关系
debug_print(f"📋 方法1: 检查连接关系...")
for solenoid in solenoid_valves: for solenoid in solenoid_valves:
if G.has_edge(solenoid, vacuum_pump) or G.has_edge(vacuum_pump, solenoid): if G.has_edge(solenoid, vacuum_pump) or G.has_edge(vacuum_pump, solenoid):
debug_print(f"找到连接的真空电磁阀: {solenoid}") debug_print(f"找到连接的真空电磁阀: {solenoid}")
return solenoid return solenoid
# 通过命名规则查找 # 通过命名规则查找
debug_print(f"📋 方法2: 检查命名规则...")
for solenoid in solenoid_valves: for solenoid in solenoid_valves:
if 'vacuum' in solenoid.lower() or solenoid == 'solenoid_valve_1': if 'vacuum' in solenoid.lower() or solenoid == 'solenoid_valve_1':
debug_print(f"通过命名找到真空电磁阀: {solenoid}") debug_print(f"通过命名找到真空电磁阀: {solenoid}")
return solenoid return solenoid
debug_print("⚠️ 未找到真空电磁阀") debug_print("未找到真空电磁阀")
return None return None
def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]: def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
"""查找气源相关的电磁阀""" """查找气源相关的电磁阀"""
debug_print(f"🔍 正在查找气源 {gas_source} 的电磁阀...")
# 查找所有电磁阀
solenoid_valves = [] solenoid_valves = []
for node in G.nodes(): for node in G.nodes():
node_data = G.nodes[node] node_data = G.nodes[node]
@@ -222,33 +142,29 @@ def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()): if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
solenoid_valves.append(node) solenoid_valves.append(node)
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
# 检查连接关系 # 检查连接关系
debug_print(f"📋 方法1: 检查连接关系...")
for solenoid in solenoid_valves: for solenoid in solenoid_valves:
if G.has_edge(gas_source, solenoid) or G.has_edge(solenoid, gas_source): if G.has_edge(gas_source, solenoid) or G.has_edge(solenoid, gas_source):
debug_print(f"找到连接的气源电磁阀: {solenoid}") debug_print(f"找到连接的气源电磁阀: {solenoid}")
return solenoid return solenoid
# 通过命名规则查找 # 通过命名规则查找
debug_print(f"📋 方法2: 检查命名规则...")
for solenoid in solenoid_valves: for solenoid in solenoid_valves:
if 'gas' in solenoid.lower() or solenoid == 'solenoid_valve_2': if 'gas' in solenoid.lower() or solenoid == 'solenoid_valve_2':
debug_print(f"通过命名找到气源电磁阀: {solenoid}") debug_print(f"通过命名找到气源电磁阀: {solenoid}")
return solenoid return solenoid
debug_print("⚠️ 未找到气源电磁阀") debug_print("未找到气源电磁阀")
return None return None
def generate_evacuateandrefill_protocol( def generate_evacuateandrefill_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
gas: str, gas: str,
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成抽真空和充气操作的动作序列 - 中文版 生成抽真空和充气操作的动作序列
Args: Args:
G: 设备图 G: 设备图
@@ -260,51 +176,34 @@ def generate_evacuateandrefill_protocol(
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id, vessel_data = get_vessel(vessel) vessel_id, vessel_data = get_vessel(vessel)
# 硬编码重复次数为 3 # 硬编码重复次数为 3
repeats = 3 repeats = 3
# 生成协议ID
protocol_id = str(uuid.uuid4()) protocol_id = str(uuid.uuid4())
debug_print(f"🆔 生成协议ID: {protocol_id}")
debug_print("=" * 60) debug_print(f"开始生成抽真空充气协议: vessel={vessel_id}, gas={gas}, repeats={repeats}")
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)
action_sequence = [] action_sequence = []
# === 参数验证和修正 === # === 参数验证和修正 ===
debug_print("🔍 步骤1: 参数验证和修正...")
action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel_id}", "🎬")) action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel_id}", "🎬"))
action_sequence.append(create_action_log(f"目标气体: {gas}", "💨")) action_sequence.append(create_action_log(f"目标气体: {gas}", "💨"))
action_sequence.append(create_action_log(f"循环次数: {repeats}", "🔄")) action_sequence.append(create_action_log(f"循环次数: {repeats}", "🔄"))
# 验证必需参数
if not vessel_id: if not vessel_id:
debug_print("❌ 容器参数不能为空")
raise ValueError("容器参数不能为空") raise ValueError("容器参数不能为空")
if not gas: if not gas:
debug_print("❌ 气体参数不能为空")
raise ValueError("气体参数不能为空") raise ValueError("气体参数不能为空")
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id if vessel_id not in G.nodes():
debug_print(f"❌ 容器 '{vessel_id}' 在系统中不存在")
raise ValueError(f"容器 '{vessel_id}' 在系统中不存在") raise ValueError(f"容器 '{vessel_id}' 在系统中不存在")
debug_print("✅ 基本参数验证通过")
action_sequence.append(create_action_log("参数验证通过", "")) action_sequence.append(create_action_log("参数验证通过", ""))
# 标准化气体名称 # 标准化气体名称
debug_print("🔧 标准化气体名称...")
gas_aliases = { gas_aliases = {
'n2': 'nitrogen', 'n2': 'nitrogen',
'ar': 'argon', 'ar': 'argon',
@@ -324,13 +223,12 @@ def generate_evacuateandrefill_protocol(
gas_lower = gas.lower().strip() gas_lower = gas.lower().strip()
if gas_lower in gas_aliases: if gas_lower in gas_aliases:
gas = gas_aliases[gas_lower] 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}", "🔄")) 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("正在查找相关设备...", "🔍")) action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
try: try:
@@ -352,26 +250,20 @@ def generate_evacuateandrefill_protocol(
else: else:
action_sequence.append(create_action_log("未找到气源电磁阀", "⚠️")) 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: if stirrer_id:
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️")) action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
else: else:
action_sequence.append(create_action_log("未找到搅拌器", "⚠️")) action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
debug_print(f"📊 设备配置:") debug_print(f"设备配置: 真空泵={vacuum_pump}, 气源={gas_source}, 搅拌器={stirrer_id}")
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}")
except Exception as e: 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(create_action_log(f"设备查找失败: {str(e)}", ""))
raise ValueError(f"设备查找失败: {str(e)}") raise ValueError(f"设备查找失败: {str(e)}")
# === 参数设置 === # === 参数设置 ===
debug_print("🔍 步骤3: 参数设置...")
action_sequence.append(create_action_log("设置操作参数...", "⚙️")) action_sequence.append(create_action_log("设置操作参数...", "⚙️"))
# 根据气体类型调整参数 # 根据气体类型调整参数
@@ -381,7 +273,6 @@ def generate_evacuateandrefill_protocol(
PUMP_FLOW_RATE = 2.0 PUMP_FLOW_RATE = 2.0
VACUUM_TIME = 30.0 VACUUM_TIME = 30.0
REFILL_TIME = 20.0 REFILL_TIME = 20.0
debug_print("💨 惰性气体: 使用标准参数")
action_sequence.append(create_action_log("检测到惰性气体,使用标准参数", "💨")) action_sequence.append(create_action_log("检测到惰性气体,使用标准参数", "💨"))
elif gas.lower() in ['air', 'oxygen']: elif gas.lower() in ['air', 'oxygen']:
VACUUM_VOLUME = 20.0 VACUUM_VOLUME = 20.0
@@ -389,7 +280,6 @@ def generate_evacuateandrefill_protocol(
PUMP_FLOW_RATE = 1.5 PUMP_FLOW_RATE = 1.5
VACUUM_TIME = 45.0 VACUUM_TIME = 45.0
REFILL_TIME = 25.0 REFILL_TIME = 25.0
debug_print("🔥 活性气体: 使用保守参数")
action_sequence.append(create_action_log("检测到活性气体,使用保守参数", "🔥")) action_sequence.append(create_action_log("检测到活性气体,使用保守参数", "🔥"))
else: else:
VACUUM_VOLUME = 15.0 VACUUM_VOLUME = 15.0
@@ -397,91 +287,65 @@ def generate_evacuateandrefill_protocol(
PUMP_FLOW_RATE = 1.0 PUMP_FLOW_RATE = 1.0
VACUUM_TIME = 60.0 VACUUM_TIME = 60.0
REFILL_TIME = 30.0 REFILL_TIME = 30.0
debug_print("❓ 未知气体: 使用安全参数")
action_sequence.append(create_action_log("未知气体类型,使用安全参数", "")) action_sequence.append(create_action_log("未知气体类型,使用安全参数", ""))
STIR_SPEED = 200.0 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"真空体积: {VACUUM_VOLUME}mL", "📏"))
action_sequence.append(create_action_log(f"充气体积: {REFILL_VOLUME}mL", "📏")) action_sequence.append(create_action_log(f"充气体积: {REFILL_VOLUME}mL", "📏"))
action_sequence.append(create_action_log(f"泵流速: {PUMP_FLOW_RATE}mL/s", "")) action_sequence.append(create_action_log(f"泵流速: {PUMP_FLOW_RATE}mL/s", ""))
# === 路径验证 === # === 路径验证 ===
debug_print("🔍 步骤4: 路径验证...")
action_sequence.append(create_action_log("验证传输路径...", "🛤️")) action_sequence.append(create_action_log("验证传输路径...", "🛤️"))
try: try:
# 验证抽真空路径 if nx.has_path(G, vessel_id, vacuum_pump):
if nx.has_path(G, vessel_id, vacuum_pump): # 🔧 使用 vessel_id
vacuum_path = nx.shortest_path(G, source=vessel_id, target=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)}", "🛤️")) action_sequence.append(create_action_log(f"真空路径: {' -> '.join(vacuum_path)}", "🛤️"))
else: else:
debug_print(f"⚠️ 真空路径不存在,继续执行但可能有问题")
action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️")) action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️"))
# 验证充气路径 if nx.has_path(G, gas_source, vessel_id):
if nx.has_path(G, gas_source, vessel_id): # 🔧 使用 vessel_id
gas_path = nx.shortest_path(G, source=gas_source, target=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)}", "🛤️")) action_sequence.append(create_action_log(f"气体路径: {' -> '.join(gas_path)}", "🛤️"))
else: else:
debug_print(f"⚠️ 气体路径不存在,继续执行但可能有问题")
action_sequence.append(create_action_log("气体路径检查: 路径不存在", "⚠️")) action_sequence.append(create_action_log("气体路径检查: 路径不存在", "⚠️"))
except Exception as e: except Exception as e:
debug_print(f"⚠️ 路径验证失败: {str(e)},继续执行")
action_sequence.append(create_action_log(f"路径验证失败: {str(e)}", "⚠️")) action_sequence.append(create_action_log(f"路径验证失败: {str(e)}", "⚠️"))
# === 启动搅拌器 === # === 启动搅拌器 ===
debug_print("🔍 步骤5: 启动搅拌器...")
if stirrer_id: if stirrer_id:
debug_print(f"🌪️ 启动搅拌器: {stirrer_id}")
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {STIR_SPEED}rpm)", "🌪️")) action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {STIR_SPEED}rpm)", "🌪️"))
action_sequence.append({ action_sequence.append({
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "vessel": {"id": vessel_id},
"stir_speed": STIR_SPEED, "stir_speed": STIR_SPEED,
"purpose": "抽真空充气前预搅拌" "purpose": "抽真空充气前预搅拌"
} }
}) })
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", "")) action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 5.0} "action_kwargs": {"time": 5.0}
}) })
else: else:
debug_print("⚠️ 未找到搅拌器,跳过搅拌器启动")
action_sequence.append(create_action_log("跳过搅拌器启动", "⏭️")) action_sequence.append(create_action_log("跳过搅拌器启动", "⏭️"))
# === 执行循环 === # === 执行循环 ===
debug_print("🔍 步骤6: 执行抽真空-充气循环...")
action_sequence.append(create_action_log(f"开始 {repeats} 次抽真空-充气循环", "🔄")) action_sequence.append(create_action_log(f"开始 {repeats} 次抽真空-充气循环", "🔄"))
for cycle in range(repeats): for cycle in range(repeats):
debug_print(f"=== 第 {cycle+1}/{repeats} 轮循环 ===")
action_sequence.append(create_action_log(f"{cycle+1}/{repeats} 轮循环开始", "🚀")) action_sequence.append(create_action_log(f"{cycle+1}/{repeats} 轮循环开始", "🚀"))
# ============ 抽真空阶段 ============ # ============ 抽真空阶段 ============
debug_print(f"🌪️ 抽真空阶段开始")
action_sequence.append(create_action_log("开始抽真空阶段", "🌪️")) action_sequence.append(create_action_log("开始抽真空阶段", "🌪️"))
# 启动真空泵 # 启动真空泵
debug_print(f"🔛 启动真空泵: {vacuum_pump}")
action_sequence.append(create_action_log(f"启动真空泵: {vacuum_pump}", "🔛")) action_sequence.append(create_action_log(f"启动真空泵: {vacuum_pump}", "🔛"))
action_sequence.append({ action_sequence.append({
"device_id": vacuum_pump, "device_id": vacuum_pump,
@@ -491,7 +355,6 @@ def generate_evacuateandrefill_protocol(
# 开启真空电磁阀 # 开启真空电磁阀
if vacuum_solenoid: if vacuum_solenoid:
debug_print(f"🚪 打开真空电磁阀: {vacuum_solenoid}")
action_sequence.append(create_action_log(f"打开真空电磁阀: {vacuum_solenoid}", "🚪")) action_sequence.append(create_action_log(f"打开真空电磁阀: {vacuum_solenoid}", "🚪"))
action_sequence.append({ action_sequence.append({
"device_id": vacuum_solenoid, "device_id": vacuum_solenoid,
@@ -500,13 +363,12 @@ def generate_evacuateandrefill_protocol(
}) })
# 抽真空操作 # 抽真空操作
debug_print(f"🌪️ 抽真空操作: {vessel_id} -> {vacuum_pump}")
action_sequence.append(create_action_log(f"开始抽真空: {vessel_id} -> {vacuum_pump}", "🌪️")) action_sequence.append(create_action_log(f"开始抽真空: {vessel_id} -> {vacuum_pump}", "🌪️"))
try: try:
vacuum_transfer_actions = generate_pump_protocol_with_rinsing( vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=vessel_id, # 🔧 使用 vessel_id from_vessel=vessel_id,
to_vessel=vacuum_pump, to_vessel=vacuum_pump,
volume=VACUUM_VOLUME, volume=VACUUM_VOLUME,
amount="", amount="",
@@ -522,10 +384,8 @@ def generate_evacuateandrefill_protocol(
if vacuum_transfer_actions: if vacuum_transfer_actions:
action_sequence.extend(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)} 个操作)", "")) action_sequence.append(create_action_log(f"抽真空协议完成 ({len(vacuum_transfer_actions)} 个操作)", ""))
else: else:
debug_print("⚠️ 抽真空协议返回空序列,添加手动动作")
action_sequence.append(create_action_log("抽真空协议为空,使用手动等待", "⚠️")) action_sequence.append(create_action_log("抽真空协议为空,使用手动等待", "⚠️"))
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
@@ -533,7 +393,7 @@ def generate_evacuateandrefill_protocol(
}) })
except Exception as e: 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(create_action_log(f"抽真空失败: {str(e)}", ""))
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
@@ -550,7 +410,6 @@ def generate_evacuateandrefill_protocol(
# 关闭真空电磁阀 # 关闭真空电磁阀
if vacuum_solenoid: if vacuum_solenoid:
debug_print(f"🚪 关闭真空电磁阀: {vacuum_solenoid}")
action_sequence.append(create_action_log(f"关闭真空电磁阀: {vacuum_solenoid}", "🚪")) action_sequence.append(create_action_log(f"关闭真空电磁阀: {vacuum_solenoid}", "🚪"))
action_sequence.append({ action_sequence.append({
"device_id": vacuum_solenoid, "device_id": vacuum_solenoid,
@@ -559,7 +418,6 @@ def generate_evacuateandrefill_protocol(
}) })
# 关闭真空泵 # 关闭真空泵
debug_print(f"🔴 停止真空泵: {vacuum_pump}")
action_sequence.append(create_action_log(f"停止真空泵: {vacuum_pump}", "🔴")) action_sequence.append(create_action_log(f"停止真空泵: {vacuum_pump}", "🔴"))
action_sequence.append({ action_sequence.append({
"device_id": vacuum_pump, "device_id": vacuum_pump,
@@ -575,11 +433,9 @@ def generate_evacuateandrefill_protocol(
}) })
# ============ 充气阶段 ============ # ============ 充气阶段 ============
debug_print(f"💨 充气阶段开始")
action_sequence.append(create_action_log("开始气体充气阶段", "💨")) action_sequence.append(create_action_log("开始气体充气阶段", "💨"))
# 启动气源 # 启动气源
debug_print(f"🔛 启动气源: {gas_source}")
action_sequence.append(create_action_log(f"启动气源: {gas_source}", "🔛")) action_sequence.append(create_action_log(f"启动气源: {gas_source}", "🔛"))
action_sequence.append({ action_sequence.append({
"device_id": gas_source, "device_id": gas_source,
@@ -589,7 +445,6 @@ def generate_evacuateandrefill_protocol(
# 开启气源电磁阀 # 开启气源电磁阀
if gas_solenoid: if gas_solenoid:
debug_print(f"🚪 打开气源电磁阀: {gas_solenoid}")
action_sequence.append(create_action_log(f"打开气源电磁阀: {gas_solenoid}", "🚪")) action_sequence.append(create_action_log(f"打开气源电磁阀: {gas_solenoid}", "🚪"))
action_sequence.append({ action_sequence.append({
"device_id": gas_solenoid, "device_id": gas_solenoid,
@@ -598,14 +453,13 @@ def generate_evacuateandrefill_protocol(
}) })
# 充气操作 # 充气操作
debug_print(f"💨 充气操作: {gas_source} -> {vessel_id}")
action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel_id}", "💨")) action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel_id}", "💨"))
try: try:
gas_transfer_actions = generate_pump_protocol_with_rinsing( gas_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=gas_source, from_vessel=gas_source,
to_vessel=vessel_id, # 🔧 使用 vessel_id to_vessel=vessel_id,
volume=REFILL_VOLUME, volume=REFILL_VOLUME,
amount="", amount="",
time=0.0, time=0.0,
@@ -620,10 +474,8 @@ def generate_evacuateandrefill_protocol(
if gas_transfer_actions: if gas_transfer_actions:
action_sequence.extend(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)} 个操作)", "")) action_sequence.append(create_action_log(f"气体充气协议完成 ({len(gas_transfer_actions)} 个操作)", ""))
else: else:
debug_print("⚠️ 充气协议返回空序列,添加手动动作")
action_sequence.append(create_action_log("充气协议为空,使用手动等待", "⚠️")) action_sequence.append(create_action_log("充气协议为空,使用手动等待", "⚠️"))
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
@@ -631,7 +483,7 @@ def generate_evacuateandrefill_protocol(
}) })
except Exception as e: 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(create_action_log(f"气体充气失败: {str(e)}", ""))
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
@@ -648,7 +500,6 @@ def generate_evacuateandrefill_protocol(
# 关闭气源电磁阀 # 关闭气源电磁阀
if gas_solenoid: if gas_solenoid:
debug_print(f"🚪 关闭气源电磁阀: {gas_solenoid}")
action_sequence.append(create_action_log(f"关闭气源电磁阀: {gas_solenoid}", "🚪")) action_sequence.append(create_action_log(f"关闭气源电磁阀: {gas_solenoid}", "🚪"))
action_sequence.append({ action_sequence.append({
"device_id": gas_solenoid, "device_id": gas_solenoid,
@@ -657,7 +508,6 @@ def generate_evacuateandrefill_protocol(
}) })
# 关闭气源 # 关闭气源
debug_print(f"🔴 停止气源: {gas_source}")
action_sequence.append(create_action_log(f"停止气源: {gas_source}", "🔴")) action_sequence.append(create_action_log(f"停止气源: {gas_source}", "🔴"))
action_sequence.append({ action_sequence.append({
"device_id": gas_source, "device_id": gas_source,
@@ -667,7 +517,6 @@ def generate_evacuateandrefill_protocol(
# 循环间等待 # 循环间等待
if cycle < repeats - 1: if cycle < repeats - 1:
debug_print(f"⏳ 等待下一个循环...")
action_sequence.append(create_action_log("等待下一个循环...", "")) action_sequence.append(create_action_log("等待下一个循环...", ""))
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
@@ -677,15 +526,12 @@ def generate_evacuateandrefill_protocol(
action_sequence.append(create_action_log(f"{cycle+1}/{repeats} 轮循环完成", "")) action_sequence.append(create_action_log(f"{cycle+1}/{repeats} 轮循环完成", ""))
# === 停止搅拌器 === # === 停止搅拌器 ===
debug_print("🔍 步骤7: 停止搅拌器...")
if stirrer_id: if stirrer_id:
debug_print(f"🛑 停止搅拌器: {stirrer_id}")
action_sequence.append(create_action_log(f"停止搅拌器: {stirrer_id}", "🛑")) action_sequence.append(create_action_log(f"停止搅拌器: {stirrer_id}", "🛑"))
action_sequence.append({ action_sequence.append({
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "stop_stir", "action_name": "stop_stir",
"action_kwargs": {"vessel": {"id": vessel_id},} # 🔧 使用 vessel_id "action_kwargs": {"vessel": {"id": vessel_id},}
}) })
else: else:
action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️")) action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️"))
@@ -700,17 +546,8 @@ def generate_evacuateandrefill_protocol(
# === 总结 === # === 总结 ===
total_time = (VACUUM_TIME + REFILL_TIME + 25) * repeats + 20 total_time = (VACUUM_TIME + REFILL_TIME + 25) * repeats + 20
debug_print("=" * 60) debug_print(f"抽真空充气协议生成完成: {len(action_sequence)} 个动作, 预计 {total_time:.0f}s")
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)
# 添加完成日志
summary_msg = f"抽真空充气协议完成: {vessel_id} (使用 {gas}{repeats} 次循环)" summary_msg = f"抽真空充气协议完成: {vessel_id} (使用 {gas}{repeats} 次循环)"
action_sequence.append(create_action_log(summary_msg, "🎉")) action_sequence.append(create_action_log(summary_msg, "🎉"))
@@ -718,35 +555,27 @@ def generate_evacuateandrefill_protocol(
# === 便捷函数 === # === 便捷函数 ===
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) 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) 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) 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) return generate_evacuateandrefill_protocol(G, vessel, gas, **kwargs)
# 测试函数 # 测试函数
def test_evacuateandrefill_protocol(): def test_evacuateandrefill_protocol():
"""测试抽真空充气协议""" """测试抽真空充气协议"""
debug_print("=== 抽真空充气协议增强中文版测试 ===") debug_print("=== 抽真空充气协议测试 ===")
debug_print("测试完成") debug_print("测试完成")
if __name__ == "__main__": if __name__ == "__main__":
test_evacuateandrefill_protocol() test_evacuateandrefill_protocol()

View File

@@ -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

View File

@@ -4,13 +4,10 @@ import logging
import re import re
from .utils.vessel_parser import get_vessel from .utils.vessel_parser import get_vessel
from .utils.unit_parser import parse_time_input from .utils.unit_parser import parse_time_input
from .utils.logger_util import debug_print
logger = logging.getLogger(__name__) 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]: def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]:
""" """
@@ -23,102 +20,76 @@ def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]:
Returns: Returns:
str: 找到的旋转蒸发仪设备ID如果没找到返回None str: 找到的旋转蒸发仪设备ID如果没找到返回None
""" """
debug_print("🔍 开始查找旋转蒸发仪设备... 🌪️")
# 如果指定了vessel先检查是否存在且是旋转蒸发仪 # 如果指定了vessel先检查是否存在且是旋转蒸发仪
if vessel: if vessel:
debug_print(f"🎯 检查指定设备: {vessel} 🔧")
if vessel in G.nodes(): if vessel in G.nodes():
node_data = G.nodes[vessel] node_data = G.nodes[vessel]
node_class = node_data.get('class', '') node_class = node_data.get('class', '')
node_type = node_data.get('type', '') 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']): if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']):
debug_print(f"🎉 找到指定的旋转蒸发仪: {vessel}") debug_print(f"找到指定的旋转蒸发仪: {vessel}")
return vessel return vessel
elif node_type == 'device': elif node_type == 'device':
debug_print(f"指定设备存在,尝试直接使用: {vessel} 🔧") debug_print(f"指定设备存在,尝试直接使用: {vessel}")
return vessel return vessel
else:
debug_print(f"❌ 指定的设备 {vessel} 不存在 😞")
# 在所有设备中查找旋转蒸发仪 # 在所有设备中查找旋转蒸发仪
debug_print("🔎 在所有设备中搜索旋转蒸发仪... 🕵️‍♀️")
rotavap_candidates = [] rotavap_candidates = []
for node_id, node_data in G.nodes(data=True): for node_id, node_data in G.nodes(data=True):
node_class = node_data.get('class', '') node_class = node_data.get('class', '')
node_type = node_data.get('type', '') node_type = node_data.get('type', '')
# 跳过非设备节点
if node_type != 'device': if node_type != 'device':
continue continue
# 检查设备类型
if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']): if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']):
rotavap_candidates.append(node_id) 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']): elif any(keyword in str(node_id).lower() for keyword in ['rotavap', 'rotary', 'evaporat']):
rotavap_candidates.append(node_id) rotavap_candidates.append(node_id)
debug_print(f"🌟 找到旋转蒸发仪候选 (按名称): {node_id} 🌪️")
if rotavap_candidates: if rotavap_candidates:
selected = rotavap_candidates[0] # 选择第一个找到的 selected = rotavap_candidates[0]
debug_print(f"🎯 选择旋转蒸发仪: {selected} 🏆") debug_print(f"选择旋转蒸发仪: {selected}")
return selected return selected
debug_print("😭 未找到旋转蒸发仪设备 💔") debug_print("未找到旋转蒸发仪设备")
return None return None
def find_connected_vessel(G: nx.DiGraph, rotavap_device: str) -> Optional[str]: 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] rotavap_data = G.nodes[rotavap_device]
children = rotavap_data.get('children', []) children = rotavap_data.get('children', [])
debug_print(f"👶 检查子设备: {children}")
for child_id in children: for child_id in children:
if child_id in G.nodes(): if child_id in G.nodes():
child_data = G.nodes[child_id] child_data = G.nodes[child_id]
child_type = child_data.get('type', '') child_type = child_data.get('type', '')
if child_type == 'container': if child_type == 'container':
debug_print(f"🎉 找到连接的容器: {child_id} 🥽✨") debug_print(f"找到连接的容器: {child_id}")
return child_id return child_id
# 查看邻接的容器
debug_print("🤝 检查邻接设备...")
for neighbor in G.neighbors(rotavap_device): for neighbor in G.neighbors(rotavap_device):
neighbor_data = G.nodes[neighbor] neighbor_data = G.nodes[neighbor]
neighbor_type = neighbor_data.get('type', '') neighbor_type = neighbor_data.get('type', '')
if neighbor_type == 'container': if neighbor_type == 'container':
debug_print(f"🎉 找到邻接的容器: {neighbor} 🥽✨") debug_print(f"找到邻接的容器: {neighbor}")
return neighbor return neighbor
debug_print("😞 未找到连接的容器 💔") debug_print("未找到连接的容器")
return None return None
def generate_evaporate_protocol( def generate_evaporate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
pressure: float = 0.1, pressure: float = 0.1,
temp: float = 60.0, temp: float = 60.0,
time: Union[str, float] = "180", # 🔧 修改:支持字符串时间 time: Union[str, float] = "180",
stir_speed: float = 100.0, stir_speed: float = 100.0,
solvent: str = "", solvent: str = "",
**kwargs **kwargs
@@ -140,22 +111,11 @@ def generate_evaporate_protocol(
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id, vessel_data = get_vessel(vessel) vessel_id, vessel_data = get_vessel(vessel)
debug_print("🌟" * 20) debug_print(f"开始生成蒸发协议: vessel={vessel_id}, pressure={pressure}, temp={temp}, time={time}")
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("🔍 记录蒸发前容器状态...")
original_liquid_volume = 0.0 original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -163,168 +123,97 @@ def generate_evaporate_protocol(
original_liquid_volume = current_volume[0] original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume 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) rotavap_device = find_rotavap_device(G, vessel_id)
if not rotavap_device: if not rotavap_device:
debug_print("💥 未找到旋转蒸发仪设备! 😭")
raise ValueError(f"未找到旋转蒸发仪设备。请检查组态图中是否包含 class 包含 'rotavap''rotary''evaporat' 的设备") raise ValueError(f"未找到旋转蒸发仪设备。请检查组态图中是否包含 class 包含 'rotavap''rotary''evaporat' 的设备")
debug_print(f"🎉 成功找到旋转蒸发仪: {rotavap_device}") # 确定目标容器
# === 步骤2: 确定目标容器 ===
debug_print("📍 步骤2: 确定目标容器... 🥽")
target_vessel = vessel_id target_vessel = vessel_id
# 如果vessel就是旋转蒸发仪设备查找连接的容器
if vessel_id == rotavap_device: if vessel_id == rotavap_device:
debug_print("🔄 vessel就是旋转蒸发仪查找连接的容器...")
connected_vessel = find_connected_vessel(G, rotavap_device) connected_vessel = find_connected_vessel(G, rotavap_device)
if connected_vessel: if connected_vessel:
target_vessel = connected_vessel target_vessel = connected_vessel
debug_print(f"✅ 使用连接的容器: {target_vessel} 🥽✨")
else: else:
debug_print(f"⚠️ 未找到连接的容器,使用设备本身: {rotavap_device} 🔧")
target_vessel = rotavap_device target_vessel = rotavap_device
elif vessel_id in G.nodes() and G.nodes[vessel_id].get('type') == 'container': elif vessel_id in G.nodes() and G.nodes[vessel_id].get('type') == 'container':
debug_print(f"✅ 使用指定的容器: {vessel_id} 🥽✨")
target_vessel = vessel_id target_vessel = vessel_id
else: else:
debug_print(f"⚠️ 容器 '{vessel_id}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device} 🔧")
target_vessel = rotavap_device target_vessel = rotavap_device
# === 🔧 新增步骤3单位解析处理 === # 单位解析处理
debug_print("📍 步骤3: 单位解析处理... ⚡")
# 解析时间
final_time = parse_time_input(time) final_time = parse_time_input(time)
debug_print(f"🎯 时间解析完成: {time} {final_time}s ({final_time/60:.1f}分钟) ⏰✨") debug_print(f"时间解析: {time} -> {final_time}s ({final_time/60:.1f}分钟)")
# === 步骤4: 参数验证和修正 === # 参数验证和修正
debug_print("📍 步骤4: 参数验证和修正... 🔧")
# 修正参数范围
if pressure <= 0 or pressure > 1.0: if pressure <= 0 or pressure > 1.0:
debug_print(f"⚠️ 真空度 {pressure} bar 超出范围,修正为 0.1 bar 💨")
pressure = 0.1 pressure = 0.1
else:
debug_print(f"✅ 真空度 {pressure} bar 在正常范围内 💨")
if temp < 10.0 or temp > 200.0: if temp < 10.0 or temp > 200.0:
debug_print(f"⚠️ 温度 {temp}°C 超出范围,修正为 60°C 🌡️")
temp = 60.0 temp = 60.0
else:
debug_print(f"✅ 温度 {temp}°C 在正常范围内 🌡️")
if final_time <= 0: if final_time <= 0:
debug_print(f"⚠️ 时间 {final_time}s 无效,修正为 180s (3分钟) ⏰")
final_time = 180.0 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: if stir_speed < 10.0 or stir_speed > 300.0:
debug_print(f"⚠️ 旋转速度 {stir_speed} RPM 超出范围,修正为 100 RPM 🌪️")
stir_speed = 100.0 stir_speed = 100.0
else:
debug_print(f"✅ 旋转速度 {stir_speed} RPM 在正常范围内 🌪️")
# 根据溶剂优化参数 # 根据溶剂优化参数
if solvent: if solvent:
debug_print(f"🧪 根据溶剂 '{solvent}' 优化参数... 🔬")
solvent_lower = solvent.lower() solvent_lower = solvent.lower()
if any(s in solvent_lower for s in ['water', 'aqueous', 'h2o']): if any(s in solvent_lower for s in ['water', 'aqueous', 'h2o']):
temp = max(temp, 80.0) temp = max(temp, 80.0)
pressure = max(pressure, 0.2) pressure = max(pressure, 0.2)
debug_print("💧 水系溶剂:提高温度和真空度 🌡️💨")
elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']): elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
temp = min(temp, 50.0) temp = min(temp, 50.0)
pressure = min(pressure, 0.05) pressure = min(pressure, 0.05)
debug_print("🍺 易挥发溶剂:降低温度和真空度 🌡️💨")
elif any(s in solvent_lower for s in ['dmso', 'dmi', 'toluene']): elif any(s in solvent_lower for s in ['dmso', 'dmi', 'toluene']):
temp = max(temp, 100.0) temp = max(temp, 100.0)
pressure = min(pressure, 0.01) 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 🌪️") debug_print(f"最终参数: pressure={pressure}bar, temp={temp}°C, time={final_time}s, stir_speed={stir_speed}RPM")
# === 🔧 新增步骤5蒸发体积计算 === # 蒸发体积计算
debug_print("📍 步骤5: 蒸发体积计算... 📊")
# 根据温度、真空度、时间和溶剂类型估算蒸发量
evaporation_volume = 0.0 evaporation_volume = 0.0
if original_liquid_volume > 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 temp_factor = 1.0 + (temp - 25.0) / 100.0
# 真空系数(真空度越高蒸发越快)
vacuum_factor = 1.0 + (1.0 - pressure) * 2.0 vacuum_factor = 1.0 + (1.0 - pressure) * 2.0
# 溶剂系数
solvent_factor = 1.0 solvent_factor = 1.0
if solvent: if solvent:
solvent_lower = solvent.lower() solvent_lower = solvent.lower()
if any(s in solvent_lower for s in ['water', 'h2o']): 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']): 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']): 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 total_evap_rate = base_evap_rate * temp_factor * vacuum_factor * solvent_factor
evaporation_volume = min( evaporation_volume = min(
original_liquid_volume * 0.95, # 最多蒸发95% original_liquid_volume * 0.95,
total_evap_rate * (final_time / 60.0) # 时间相关的蒸发量 total_evap_rate * (final_time / 60.0)
) )
debug_print(f"📊 蒸发量计算:") debug_print(f"预计蒸发量: {evaporation_volume:.2f}mL ({evaporation_volume/original_liquid_volume*100:.1f}%)")
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: 生成动作序列... 🎬")
# 生成动作序列
action_sequence = [] action_sequence = []
# 1. 等待稳定 # 1. 等待稳定
debug_print(" 🔄 动作1: 添加初始等待稳定... ⏳")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 10} "action_kwargs": {"time": 10}
}) })
debug_print(" ✅ 初始等待动作已添加 ⏳✨")
# 2. 执行蒸发 # 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 = { evaporate_action = {
"device_id": rotavap_device, "device_id": rotavap_device,
"action_name": "evaporate", "action_name": "evaporate",
@@ -332,20 +221,17 @@ def generate_evaporate_protocol(
"vessel": {"id": target_vessel}, "vessel": {"id": target_vessel},
"pressure": float(pressure), "pressure": float(pressure),
"temp": float(temp), "temp": float(temp),
"time": float(final_time), # 🔧 强制转换为float类型 "time": float(final_time),
"stir_speed": float(stir_speed), "stir_speed": float(stir_speed),
"solvent": str(solvent) "solvent": str(solvent)
} }
} }
action_sequence.append(evaporate_action) action_sequence.append(evaporate_action)
debug_print(" ✅ 蒸发动作已添加 🌪️✨")
# 🔧 新增:蒸发过程中的体积变化 # 蒸发过程中的体积变化
debug_print(" 🔧 更新容器体积 - 蒸发过程...")
if evaporation_volume > 0: if evaporation_volume > 0:
new_volume = max(0.0, original_liquid_volume - evaporation_volume) new_volume = max(0.0, original_liquid_volume - evaporation_volume)
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list): if isinstance(current_volume, list):
@@ -358,7 +244,6 @@ def generate_evaporate_protocol(
else: else:
vessel["data"]["liquid_volume"] = new_volume vessel["data"]["liquid_volume"] = new_volume
# 🔧 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]: if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data'] = {}
@@ -374,17 +259,15 @@ def generate_evaporate_protocol(
else: else:
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume 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. 蒸发后等待 # 3. 蒸发后等待
debug_print(" 🔄 动作3: 添加蒸发后等待... ⏳")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 10} "action_kwargs": {"time": 10}
}) })
debug_print(" ✅ 蒸发后等待动作已添加 ⏳✨")
# 🔧 新增:蒸发完成后的状态报告 # 最终状态
final_liquid_volume = 0.0 final_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -393,18 +276,6 @@ def generate_evaporate_protocol(
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
final_liquid_volume = current_volume final_liquid_volume = current_volume
# === 总结 === debug_print(f"蒸发协议生成完成: {len(action_sequence)} 个动作, 设备={rotavap_device}, 容器={target_vessel}")
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)
return action_sequence return action_sequence

View File

@@ -2,55 +2,40 @@ from typing import List, Dict, Any, Optional
import networkx as nx import networkx as nx
import logging import logging
from .utils.vessel_parser import get_vessel from .utils.vessel_parser import get_vessel
from .utils.logger_util import debug_print
from .pump_protocol import generate_pump_protocol_with_rinsing from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
logger.info(f"[FILTER] {message}")
def find_filter_device(G: nx.DiGraph) -> str: def find_filter_device(G: nx.DiGraph) -> str:
"""查找过滤器设备""" """查找过滤器设备"""
debug_print("🔍 查找过滤器设备... 🌊")
# 查找过滤器设备
for node in G.nodes(): for node in G.nodes():
node_data = G.nodes[node] node_data = G.nodes[node]
node_class = node_data.get('class', '') or '' node_class = node_data.get('class', '') or ''
if 'filter' in node_class.lower() or 'filter' in node.lower(): if 'filter' in node_class.lower() or 'filter' in node.lower():
debug_print(f"🎉 找到过滤器设备: {node}") debug_print(f"找到过滤器设备: {node}")
return node return node
# 如果没找到,寻找可能的过滤器名称
debug_print("🔎 在预定义名称中搜索过滤器... 📋")
possible_names = ["filter", "filter_1", "virtual_filter", "filtration_unit"] possible_names = ["filter", "filter_1", "virtual_filter", "filtration_unit"]
for name in possible_names: for name in possible_names:
if name in G.nodes(): if name in G.nodes():
debug_print(f"🎉 找到过滤器设备: {name}") debug_print(f"找到过滤器设备: {name}")
return name return name
debug_print("😭 未找到过滤器设备 💔")
raise ValueError("未找到过滤器设备") raise ValueError("未找到过滤器设备")
def validate_vessel(G: nx.DiGraph, vessel: str, vessel_type: str = "容器") -> None: def validate_vessel(G: nx.DiGraph, vessel: str, vessel_type: str = "容器") -> None:
"""验证容器是否存在""" """验证容器是否存在"""
debug_print(f"🔍 验证{vessel_type}: '{vessel}' 🧪")
if not vessel: if not vessel:
debug_print(f"{vessel_type}不能为空! 😱")
raise ValueError(f"{vessel_type}不能为空") raise ValueError(f"{vessel_type}不能为空")
if vessel not in G.nodes(): if vessel not in G.nodes():
debug_print(f"{vessel_type} '{vessel}' 不存在于系统中! 😞")
raise ValueError(f"{vessel_type} '{vessel}' 不存在于系统中") raise ValueError(f"{vessel_type} '{vessel}' 不存在于系统中")
debug_print(f"{vessel_type} '{vessel}' 验证通过 🎯")
def generate_filter_protocol( def generate_filter_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
filtrate_vessel: dict = {"id": "waste"}, filtrate_vessel: dict = {"id": "waste"},
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
@@ -67,22 +52,14 @@ def generate_filter_protocol(
List[Dict[str, Any]]: 过滤操作的动作序列 List[Dict[str, Any]]: 过滤操作的动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id, vessel_data = get_vessel(vessel) vessel_id, vessel_data = get_vessel(vessel)
filtrate_vessel_id, filtrate_vessel_data = get_vessel(filtrate_vessel) filtrate_vessel_id, filtrate_vessel_data = get_vessel(filtrate_vessel)
debug_print("🌊" * 20) debug_print(f"开始生成过滤协议: vessel={vessel_id}, filtrate_vessel={filtrate_vessel_id}")
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)
action_sequence = [] action_sequence = []
# 🔧 新增:记录过滤前的容器状态 # 记录过滤前的容器状态
debug_print("🔍 记录过滤前容器状态...")
original_liquid_volume = 0.0 original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -90,79 +67,45 @@ def generate_filter_protocol(
original_liquid_volume = current_volume[0] original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume original_liquid_volume = current_volume
debug_print(f"📊 过滤前液体体积: {original_liquid_volume:.2f}mL")
# === 参数验证 === # === 参数验证 ===
debug_print("📍 步骤1: 参数验证... 🔧") validate_vessel(G, vessel_id, "过滤容器")
# 验证必需参数
debug_print(" 🔍 验证必需参数...")
validate_vessel(G, vessel_id, "过滤容器") # 🔧 使用 vessel_id
debug_print(" ✅ 必需参数验证完成 🎯")
# 验证可选参数
debug_print(" 🔍 验证可选参数...")
if filtrate_vessel: if filtrate_vessel:
validate_vessel(G, filtrate_vessel_id, "滤液容器") validate_vessel(G, filtrate_vessel_id, "滤液容器")
debug_print(" 🌊 模式: 过滤并收集滤液 💧")
else:
debug_print(" 🧱 模式: 过滤并收集固体 🔬")
debug_print(" ✅ 可选参数验证完成 🎯")
# === 查找设备 === # === 查找设备 ===
debug_print("📍 步骤2: 查找设备... 🔍")
try: try:
debug_print(" 🔎 搜索过滤器设备...")
filter_device = find_filter_device(G) filter_device = find_filter_device(G)
debug_print(f" 🎉 使用过滤器设备: {filter_device} 🌊✨") debug_print(f"使用过滤器设备: {filter_device}")
except Exception as e: except Exception as e:
debug_print(f" ❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"设备查找失败: {str(e)}") raise ValueError(f"设备查找失败: {str(e)}")
# 🔧 新增:过滤效率和体积分配估算 # 过滤体积分配估算
debug_print("📍 步骤2.5: 过滤体积分配估算... 📊") solid_ratio = 0.1
liquid_ratio = 0.9
volume_loss_ratio = 0.05
# 估算过滤分离比例(基于经验数据)
solid_ratio = 0.1 # 假设10%是固体(保留在过滤器上)
liquid_ratio = 0.9 # 假设90%是液体(通过过滤器)
volume_loss_ratio = 0.05 # 假设5%体积损失(残留在过滤器等)
# 从kwargs中获取过滤参数进行优化
if "solid_content" in kwargs: if "solid_content" in kwargs:
try: try:
solid_ratio = float(kwargs["solid_content"]) solid_ratio = float(kwargs["solid_content"])
liquid_ratio = 1.0 - solid_ratio liquid_ratio = 1.0 - solid_ratio
debug_print(f"📋 使用指定的固体含量: {solid_ratio*100:.1f}%")
except: except:
debug_print("⚠️ 固体含量参数无效,使用默认值") pass
if original_liquid_volume > 0: if original_liquid_volume > 0:
expected_filtrate_volume = original_liquid_volume * liquid_ratio * (1.0 - volume_loss_ratio) expected_filtrate_volume = original_liquid_volume * liquid_ratio * (1.0 - volume_loss_ratio)
expected_solid_volume = original_liquid_volume * solid_ratio expected_solid_volume = original_liquid_volume * solid_ratio
volume_loss = original_liquid_volume * volume_loss_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:
if vessel_id != filter_device: # 🔧 使用 vessel_id
debug_print(f" 🚛 需要转移: {vessel_id}{filter_device} 📦")
try: try:
debug_print(" 🔄 开始执行转移操作...")
# 使用pump protocol转移液体到过滤器
transfer_actions = generate_pump_protocol_with_rinsing( transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel={"id": vessel_id}, # 🔧 使用 vessel_id from_vessel={"id": vessel_id},
to_vessel={"id": filter_device}, to_vessel={"id": filter_device},
volume=0.0, # 转移所有液体 volume=0.0,
amount="", amount="",
time=0.0, time=0.0,
viscous=False, viscous=False,
@@ -176,12 +119,9 @@ def generate_filter_protocol(
if transfer_actions: if transfer_actions:
action_sequence.extend(transfer_actions) action_sequence.extend(transfer_actions)
debug_print(f"添加了 {len(transfer_actions)} 个转移动作 🚚✨") debug_print(f"添加了 {len(transfer_actions)} 个转移动作")
# 🔧 新增:转移后更新容器体积 # 更新容器体积
debug_print(" 🔧 更新转移后的容器体积...")
# 原容器体积变为0所有液体已转移
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list): if isinstance(current_volume, list):
@@ -189,72 +129,46 @@ def generate_filter_protocol(
else: else:
vessel["data"]["liquid_volume"] = 0.0 vessel["data"]["liquid_volume"] = 0.0
# 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]: if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data'] = {}
G.nodes[vessel_id]['data']['liquid_volume'] = 0.0 G.nodes[vessel_id]['data']['liquid_volume'] = 0.0
debug_print(f" 📊 转移完成,{vessel_id} 体积更新为 0.0mL")
else:
debug_print(" ⚠️ 转移协议返回空序列 🤔")
except Exception as e: except Exception as e:
debug_print(f"转移失败: {str(e)} 😞") debug_print(f"转移失败: {str(e)},继续执行")
debug_print(" 🔄 继续执行,可能是直接连接的过滤器 🤞")
else:
debug_print(" ✅ 过滤容器就是过滤器,无需转移 🎯")
# === 执行过滤操作 === # === 执行过滤操作 ===
debug_print("📍 步骤4: 执行过滤操作... 🌊")
# 构建过滤动作参数
debug_print(" ⚙️ 构建过滤参数...")
filter_kwargs = { filter_kwargs = {
"vessel": {"id": filter_device}, # 过滤器设备 "vessel": {"id": filter_device},
"filtrate_vessel": {"id": filtrate_vessel_id}, # 滤液容器(可能为空) "filtrate_vessel": {"id": filtrate_vessel_id},
"stir": kwargs.get("stir", False), "stir": kwargs.get("stir", False),
"stir_speed": kwargs.get("stir_speed", 0.0), "stir_speed": kwargs.get("stir_speed", 0.0),
"temp": kwargs.get("temp", 25.0), "temp": kwargs.get("temp", 25.0),
"continue_heatchill": kwargs.get("continue_heatchill", False), "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 = { filter_action = {
"device_id": filter_device, "device_id": filter_device,
"action_name": "filter", "action_name": "filter",
"action_kwargs": filter_kwargs "action_kwargs": filter_kwargs
} }
action_sequence.append(filter_action) action_sequence.append(filter_action)
debug_print(" ✅ 过滤动作已添加 🌊✨")
# 过滤后等待 # 过滤后等待
debug_print(" ⏳ 添加过滤后等待...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 10.0} "action_kwargs": {"time": 10.0}
}) })
debug_print(" ✅ 过滤后等待动作已添加 ⏰✨")
# === 收集滤液(如果需要)=== # === 收集滤液(如果需要)===
debug_print("📍 步骤5: 收集滤液... 💧")
if filtrate_vessel_id and filtrate_vessel_id not in G.neighbors(filter_device): if filtrate_vessel_id and filtrate_vessel_id not in G.neighbors(filter_device):
debug_print(f" 🧪 收集滤液: {filter_device}{filtrate_vessel_id} 💧")
try: try:
debug_print(" 🔄 开始执行收集操作...")
# 使用pump protocol收集滤液
collect_actions = generate_pump_protocol_with_rinsing( collect_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=filter_device, from_vessel=filter_device,
to_vessel=filtrate_vessel, to_vessel=filtrate_vessel,
volume=0.0, # 收集所有滤液 volume=0.0,
amount="", amount="",
time=0.0, time=0.0,
viscous=False, viscous=False,
@@ -268,12 +182,8 @@ def generate_filter_protocol(
if collect_actions: if collect_actions:
action_sequence.extend(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 filtrate_vessel_id in G.nodes():
if 'data' not in G.nodes[filtrate_vessel_id]: if 'data' not in G.nodes[filtrate_vessel_id]:
G.nodes[filtrate_vessel_id]['data'] = {} G.nodes[filtrate_vessel_id]['data'] = {}
@@ -287,33 +197,17 @@ def generate_filter_protocol(
else: else:
G.nodes[filtrate_vessel_id]['data']['liquid_volume'] = current_filtrate_volume + expected_filtrate_volume 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: except Exception as e:
debug_print(f"收集滤液失败: {str(e)} 😞") debug_print(f"收集滤液失败: {str(e)},继续执行")
debug_print(" 🔄 继续执行,可能滤液直接流入指定容器 🤞")
else:
debug_print(" 🧱 未指定滤液容器,固体保留在过滤器中 🔬")
# 🔧 新增:过滤完成后的容器状态更新
debug_print("📍 步骤5.5: 过滤完成后状态更新... 📊")
# 过滤完成后容器状态更新
if vessel_id == filter_device: if vessel_id == filter_device:
# 如果过滤容器就是过滤器,需要更新其体积状态
if original_liquid_volume > 0: if original_liquid_volume > 0:
if filtrate_vessel: if filtrate_vessel:
# 收集滤液模式:过滤器中主要保留固体
remaining_volume = expected_solid_volume remaining_volume = expected_solid_volume
debug_print(f" 🧱 过滤器中保留固体: {remaining_volume:.2f}mL")
else: else:
# 保留固体模式:过滤器中保留所有物质
remaining_volume = original_liquid_volume * (1.0 - volume_loss_ratio) 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"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list): if isinstance(current_volume, list):
@@ -321,23 +215,18 @@ def generate_filter_protocol(
else: else:
vessel["data"]["liquid_volume"] = remaining_volume vessel["data"]["liquid_volume"] = remaining_volume
# 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]: if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data'] = {}
G.nodes[vessel_id]['data']['liquid_volume'] = remaining_volume 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_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 5.0} "action_kwargs": {"time": 5.0}
}) })
debug_print(" ✅ 最终等待动作已添加 ⏰✨")
# 🔧 新增:过滤完成后的状态报告 # 最终状态
final_vessel_volume = 0.0 final_vessel_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -346,21 +235,6 @@ def generate_filter_protocol(
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
final_vessel_volume = current_volume final_vessel_volume = current_volume
# === 总结 === debug_print(f"过滤协议生成完成: {len(action_sequence)} 个动作, 容器={vessel_id}, 过滤器={filter_device}")
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)
return action_sequence return action_sequence

View File

@@ -2,99 +2,18 @@ import traceback
import numpy as np import numpy as np
import networkx as nx import networkx as nx
import asyncio import asyncio
import time as time_module # 🔧 重命名time模块 import time as time_module # 重命名time模块
from typing import List, Dict, Any from typing import List, Dict, Any
import logging import logging
import sys 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__) 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: 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) to_vessel_id, _ = get_vessel(to_vessel)
with generate_pump_protocol_with_rinsing._lock: with generate_pump_protocol_with_rinsing._lock:
debug_print("=" * 60) debug_print(f"PUMP_TRANSFER: {from_vessel_id} -> {to_vessel_id}, volume={volume}, flowrate={flowrate}")
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)
# 短暂延迟,避免快速重复调用 # 短暂延迟,避免快速重复调用
time_module.sleep(0.01) time_module.sleep(0.01)
debug_print("🔍 步骤1: 开始体积处理...")
# 1. 处理体积参数 # 1. 处理体积参数
final_volume = volume final_volume = volume
debug_print(f"📋 初始设置: final_volume = {final_volume}") debug_print(f"初始体积: {final_volume}")
# 🔧 修复:如果volume为0ROS2传入的空值,从容器读取实际体积 # 如果volume为0从容器读取实际体积
if volume == 0.0: if volume == 0.0:
debug_print("🎯 检测到 volume=0.0,开始自动体积检测...")
# 直接从源容器读取实际体积 actual_volume = get_resource_liquid_volume(G.nodes.get(from_vessel_id, {}))
actual_volume = get_vessel_liquid_volume(G, from_vessel_id)
debug_print(f"📖 从容器 '{from_vessel_id}' 读取到体积: {actual_volume}mL")
if actual_volume > 0: if actual_volume > 0:
final_volume = actual_volume final_volume = actual_volume
debug_print(f"✅ 成功设置体积: {final_volume}mL") debug_print(f"从容器读取体积: {final_volume}mL")
else: else:
final_volume = 10.0 # 如果读取失败,使用默认值 final_volume = 10.0
logger.warning(f"⚠️ 无法从容器读取体积,使用默认值: {final_volume}mL") logger.warning(f"无法从容器读取体积,使用默认值: {final_volume}mL")
else: else:
debug_print(f"📌 体积非零,直接使用: {final_volume}mL") debug_print(f"使用指定体积: {final_volume}mL")
# 处理 amount 参数 # 处理 amount 参数
if amount and amount.strip(): if amount and amount.strip():
debug_print(f"🔍 检测到 amount 参数: '{amount}',开始解析...")
parsed_volume = _parse_amount_to_volume(amount) parsed_volume = _parse_amount_to_volume(amount)
debug_print(f"📖 从 amount 解析得到体积: {parsed_volume}mL")
if parsed_volume > 0: if parsed_volume > 0:
final_volume = parsed_volume final_volume = parsed_volume
debug_print(f"✅ 使用从 amount 解析的体积: {final_volume}mL")
elif parsed_volume == 0.0 and amount.lower().strip() == "all": elif parsed_volume == 0.0 and amount.lower().strip() == "all":
debug_print("🎯 检测到 amount='all',从容器读取全部体积...") actual_volume = get_resource_liquid_volume(G.nodes.get(from_vessel_id, {}))
actual_volume = get_vessel_liquid_volume(G, from_vessel_id)
if actual_volume > 0: if actual_volume > 0:
final_volume = actual_volume final_volume = actual_volume
debug_print(f"✅ amount='all',设置体积为: {final_volume}mL")
# 最终体积验证 # 最终体积验证
debug_print(f"🔍 步骤2: 最终体积验证...")
if final_volume <= 0: if final_volume <= 0:
logger.error(f"体积无效: {final_volume}mL") logger.error(f"体积无效: {final_volume}mL")
final_volume = 10.0 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. 处理流速参数 # 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_flowrate = flowrate if flowrate > 0 else 2.5
final_transfer_flowrate = transfer_flowrate if transfer_flowrate > 0 else 0.5 final_transfer_flowrate = transfer_flowrate if transfer_flowrate > 0 else 0.5
if flowrate <= 0: 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: if transfer_flowrate <= 0:
logger.warning(f"⚠️ transfer_flowrate <= 0修正为: {final_transfer_flowrate}mL/s") 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")
# 3. 根据时间计算流速 # 3. 根据时间计算流速
if time > 0 and final_volume > 0: if time > 0 and final_volume > 0:
debug_print(f"🔍 步骤4: 根据时间计算流速...")
calculated_flowrate = final_volume / time calculated_flowrate = final_volume / time
debug_print(f" - 计算得到流速: {calculated_flowrate}mL/s")
if flowrate <= 0 or flowrate == 2.5: if flowrate <= 0 or flowrate == 2.5:
final_flowrate = min(calculated_flowrate, 10.0) final_flowrate = min(calculated_flowrate, 10.0)
debug_print(f" - 调整 flowrate 为: {final_flowrate}mL/s")
if transfer_flowrate <= 0 or transfer_flowrate == 0.5: if transfer_flowrate <= 0 or transfer_flowrate == 0.5:
final_transfer_flowrate = min(calculated_flowrate, 5.0) final_transfer_flowrate = min(calculated_flowrate, 5.0)
debug_print(f" - 调整 transfer_flowrate 为: {final_transfer_flowrate}mL/s")
# 4. 根据速度规格调整 # 4. 根据速度规格调整
if rate_spec: if rate_spec:
debug_print(f"🔍 步骤5: 根据速度规格调整...")
debug_print(f" - 速度规格: '{rate_spec}'")
if rate_spec == "dropwise": if rate_spec == "dropwise":
final_flowrate = min(final_flowrate, 0.1) final_flowrate = min(final_flowrate, 0.1)
final_transfer_flowrate = min(final_transfer_flowrate, 0.1) final_transfer_flowrate = min(final_transfer_flowrate, 0.1)
debug_print(f" - dropwise模式流速调整为: {final_flowrate}mL/s")
elif rate_spec == "slowly": elif rate_spec == "slowly":
final_flowrate = min(final_flowrate, 0.5) final_flowrate = min(final_flowrate, 0.5)
final_transfer_flowrate = min(final_transfer_flowrate, 0.3) final_transfer_flowrate = min(final_transfer_flowrate, 0.3)
debug_print(f" - slowly模式流速调整为: {final_flowrate}mL/s")
elif rate_spec == "quickly": elif rate_spec == "quickly":
final_flowrate = max(final_flowrate, 5.0) final_flowrate = max(final_flowrate, 5.0)
final_transfer_flowrate = max(final_transfer_flowrate, 2.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. 处理冲洗参数 # 5. 处理冲洗参数
debug_print(f"🔍 步骤6: 处理冲洗参数...")
final_rinsing_solvent = rinsing_solvent final_rinsing_solvent = rinsing_solvent
final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0 final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0
final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2 final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2
if rinsing_volume <= 0: 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: 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: if viscous or solid:
final_rinsing_repeats = max(final_rinsing_repeats, 3) final_rinsing_repeats = max(final_rinsing_repeats, 3)
final_rinsing_volume = max(final_rinsing_volume, 10.0) final_rinsing_volume = max(final_rinsing_volume, 10.0)
debug_print(f"🧪 粘稠/固体物质,调整冲洗参数:{final_rinsing_repeats}次,{final_rinsing_volume}mL")
# 参数总结 # 参数总结
debug_print("📊 最终参数总结:") debug_print(f"最终参数: volume={final_volume}mL, flowrate={final_flowrate}mL/s, "
debug_print(f" - 体积: {final_volume}mL") f"transfer_flowrate={final_transfer_flowrate}mL/s, "
debug_print(f" - 流速: {final_flowrate}mL/s") f"rinsing={final_rinsing_solvent}/{final_rinsing_volume}mL/{final_rinsing_repeats}")
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("🔧 步骤7: 开始执行基础转移...")
try: 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( pump_action_sequence = generate_pump_protocol(
G, from_vessel_id, to_vessel_id, final_volume, G, from_vessel_id, to_vessel_id, final_volume,
final_flowrate, final_transfer_flowrate 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)}")
debug_print(f" - 动作序列是否为空: {len(pump_action_sequence) == 0}")
if not pump_action_sequence: if not pump_action_sequence:
debug_print("基础转移协议生成为空,可能是路径问题") debug_print("基础转移协议生成为空,可能是路径问题")
debug_print(f" - 源容器存在: {from_vessel_id in G.nodes()}")
debug_print(f" - 目标容器存在: {to_vessel_id in G.nodes()}")
if from_vessel_id in G.nodes() and to_vessel_id in G.nodes(): if from_vessel_id in G.nodes() and to_vessel_id in G.nodes():
try: try:
path = nx.shortest_path(G, source=from_vessel_id, target=to_vessel_id) 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: except Exception as path_error:
debug_print(f" - 无法找到路径: {str(path_error)}") debug_print(f"无法找到路径: {str(path_error)}")
return [ return [
{ {
"device_id": "system", "device_id": "system",
"action_name": "log_message", "action_name": "log_message",
"action_kwargs": { "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: except Exception as e:
debug_print(f"基础转移失败: {str(e)}") debug_print(f"基础转移失败: {str(e)}")
import traceback import traceback
debug_print(f"详细错误: {traceback.format_exc()}") debug_print(f"详细错误: {traceback.format_exc()}")
return [ return [
@@ -704,111 +567,82 @@ def generate_pump_protocol_with_rinsing(
"device_id": "system", "device_id": "system",
"action_name": "log_message", "action_name": "log_message",
"action_kwargs": { "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: 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: try:
if final_rinsing_solvent.strip() != "air": if final_rinsing_solvent.strip() != "air":
debug_print(" - 执行液体冲洗...")
rinsing_actions = _generate_rinsing_sequence( rinsing_actions = _generate_rinsing_sequence(
G, from_vessel_id, to_vessel_id, final_rinsing_solvent, G, from_vessel_id, to_vessel_id, final_rinsing_solvent,
final_rinsing_volume, final_rinsing_repeats, final_rinsing_volume, final_rinsing_repeats,
final_flowrate, final_transfer_flowrate final_flowrate, final_transfer_flowrate
) )
pump_action_sequence.extend(rinsing_actions) pump_action_sequence.extend(rinsing_actions)
debug_print(f" - 添加了 {len(rinsing_actions)} 个冲洗动作")
else: else:
debug_print(" - 执行空气冲洗...")
air_rinsing_actions = _generate_air_rinsing_sequence( air_rinsing_actions = _generate_air_rinsing_sequence(
G, from_vessel_id, to_vessel_id, final_rinsing_volume, final_rinsing_repeats, G, from_vessel_id, to_vessel_id, final_rinsing_volume, final_rinsing_repeats,
final_flowrate, final_transfer_flowrate final_flowrate, final_transfer_flowrate
) )
pump_action_sequence.extend(air_rinsing_actions) pump_action_sequence.extend(air_rinsing_actions)
debug_print(f" - 添加了 {len(air_rinsing_actions)} 个空气冲洗动作")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 冲洗操作失败: {str(e)},跳过冲洗") debug_print(f"冲洗操作失败: {str(e)},跳过冲洗")
else: else:
debug_print(f"⏭️ 跳过冲洗操作") debug_print(f"跳过冲洗操作 (solvent='{final_rinsing_solvent}', repeats={final_rinsing_repeats})")
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"PUMP_TRANSFER 完成: {from_vessel_id} -> {to_vessel_id}, "
debug_print("=" * 60) f"volume={final_volume}mL, 动作数={len(pump_action_sequence)}")
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}")
# 最终验证 # 最终验证
if len(pump_action_sequence) == 0: if len(pump_action_sequence) == 0:
debug_print("🚨 协议生成结果为空!这是异常情况") debug_print("协议生成结果为空!")
return [ return [
{ {
"device_id": "system", "device_id": "system",
"action_name": "log_message", "action_name": "log_message",
"action_kwargs": { "action_kwargs": {
"message": f"🚨 协议生成失败: 无法生成任何动作序列" "message": "协议生成失败: 无法生成任何动作序列"
} }
} }
] ]
debug_print("=" * 60)
return pump_action_sequence return pump_action_sequence
def _parse_amount_to_volume(amount: str) -> float: def _parse_amount_to_volume(amount: str) -> float:
"""解析 amount 字符串为体积""" """解析 amount 字符串为体积"""
debug_print(f"🔍 解析 amount: '{amount}'")
if not amount: if not amount:
debug_print(" - amount 为空,返回 0.0")
return 0.0 return 0.0
amount = amount.lower().strip() amount = amount.lower().strip()
debug_print(f" - 处理后的 amount: '{amount}'")
# 处理特殊关键词 # 处理特殊关键词
if amount == "all": if amount == "all":
debug_print(" - 检测到 'all',返回 0.0(需要后续处理)")
return 0.0 # 返回0.0,让调用者处理 return 0.0 # 返回0.0,让调用者处理
# 提取数字 # 提取数字
import re import re
numbers = re.findall(r'[\d.]+', amount) numbers = re.findall(r'[\d.]+', amount)
debug_print(f" - 提取到的数字: {numbers}")
if numbers: if numbers:
volume = float(numbers[0]) volume = float(numbers[0])
debug_print(f" - 基础体积: {volume}")
# 单位转换 # 单位转换
if 'ml' in amount or 'milliliter' in amount: if 'ml' in amount or 'milliliter' in amount:
debug_print(f" - 单位: mL最终体积: {volume}")
return volume return volume
elif 'l' in amount and 'ml' not in amount: elif 'l' in amount and 'ml' not in amount:
final_volume = volume * 1000 return volume * 1000
debug_print(f" - 单位: L最终体积: {final_volume}mL")
return final_volume
elif 'μl' in amount or 'microliter' in amount: elif 'μl' in amount or 'microliter' in amount:
final_volume = volume / 1000 return volume / 1000
debug_print(f" - 单位: μL最终体积: {final_volume}mL")
return final_volume
else: else:
debug_print(f" - 无单位,假设为 mL: {volume}") return volume # 默认mL
return volume
debug_print(" - 无法解析,返回 0.0")
return 0.0 return 0.0

View File

@@ -4,14 +4,11 @@ import logging
from typing import List, Dict, Any, Tuple, Union from typing import List, Dict, Any, Tuple, Union
from .utils.vessel_parser import get_vessel, find_solvent_vessel from .utils.vessel_parser import get_vessel, find_solvent_vessel
from .utils.unit_parser import parse_volume_input from .utils.unit_parser import parse_volume_input
from .utils.logger_util import debug_print
from .pump_protocol import generate_pump_protocol_with_rinsing from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
logger.info(f"[RECRYSTALLIZE] {message}")
def parse_ratio(ratio_str: str) -> Tuple[float, float]: def parse_ratio(ratio_str: str) -> Tuple[float, float]:
""" """
@@ -23,52 +20,43 @@ def parse_ratio(ratio_str: str) -> Tuple[float, float]:
Returns: Returns:
Tuple[float, float]: 比例元组 (ratio1, ratio2) Tuple[float, float]: 比例元组 (ratio1, ratio2)
""" """
debug_print(f"⚖️ 开始解析比例: '{ratio_str}' 📊")
try: try:
# 处理 "1:1", "3:7", "50:50" 等格式
if ":" in ratio_str: if ":" in ratio_str:
parts = ratio_str.split(":") parts = ratio_str.split(":")
if len(parts) == 2: if len(parts) == 2:
ratio1 = float(parts[0]) ratio1 = float(parts[0])
ratio2 = float(parts[1]) ratio2 = float(parts[1])
debug_print(f"✅ 冒号格式解析成功: {ratio1}:{ratio2} 🎯")
return ratio1, ratio2 return ratio1, ratio2
# 处理 "1-1", "3-7" 等格式
if "-" in ratio_str: if "-" in ratio_str:
parts = ratio_str.split("-") parts = ratio_str.split("-")
if len(parts) == 2: if len(parts) == 2:
ratio1 = float(parts[0]) ratio1 = float(parts[0])
ratio2 = float(parts[1]) ratio2 = float(parts[1])
debug_print(f"✅ 横线格式解析成功: {ratio1}:{ratio2} 🎯")
return ratio1, ratio2 return ratio1, ratio2
# 处理 "1,1", "3,7" 等格式
if "," in ratio_str: if "," in ratio_str:
parts = ratio_str.split(",") parts = ratio_str.split(",")
if len(parts) == 2: if len(parts) == 2:
ratio1 = float(parts[0]) ratio1 = float(parts[0])
ratio2 = float(parts[1]) ratio2 = float(parts[1])
debug_print(f"✅ 逗号格式解析成功: {ratio1}:{ratio2} 🎯")
return 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 return 1.0, 1.0
except ValueError: except ValueError:
debug_print(f"比例解析错误 '{ratio_str}',使用默认比例 1:1 🎭") debug_print(f"比例解析错误 '{ratio_str}',使用默认比例 1:1")
return 1.0, 1.0 return 1.0, 1.0
def generate_recrystallize_protocol( def generate_recrystallize_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型 vessel: dict,
ratio: str, ratio: str,
solvent1: str, solvent1: str,
solvent2: str, solvent2: str,
volume: Union[str, float], # 支持字符串和数值 volume: Union[str, float],
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
@@ -87,23 +75,13 @@ def generate_recrystallize_protocol(
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id, vessel_data = get_vessel(vessel) vessel_id, vessel_data = get_vessel(vessel)
action_sequence = [] action_sequence = []
debug_print("💎" * 20) debug_print(f"开始生成重结晶协议: vessel={vessel_id}, ratio={ratio}, solvent1={solvent1}, solvent2={solvent2}, volume={volume}")
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("🔍 记录重结晶前容器状态...")
original_liquid_volume = 0.0 original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
@@ -111,102 +89,73 @@ def generate_recrystallize_protocol(
original_liquid_volume = current_volume[0] original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)): elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume original_liquid_volume = current_volume
debug_print(f"📊 重结晶前液体体积: {original_liquid_volume:.2f}mL")
# 1. 验证目标容器存在 # 1. 验证目标容器存在
debug_print("📍 步骤1: 验证目标容器... 🔧") if vessel_id not in G.nodes():
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print(f"❌ 目标容器 '{vessel_id}' 不存在于系统中! 😱")
raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中") raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
debug_print(f"✅ 目标容器 '{vessel_id}' 验证通过 🎯")
# 2. 解析体积(支持单位) # 2. 解析体积(支持单位)
debug_print("📍 步骤2: 解析体积(支持单位)... 💧")
final_volume = parse_volume_input(volume, "mL") final_volume = parse_volume_input(volume, "mL")
debug_print(f"🎯 体积解析完成: {volume} {final_volume}mL") debug_print(f"体积解析: {volume} -> {final_volume}mL")
# 3. 解析比例 # 3. 解析比例
debug_print("📍 步骤3: 解析比例... ⚖️")
ratio1, ratio2 = parse_ratio(ratio) ratio1, ratio2 = parse_ratio(ratio)
total_ratio = ratio1 + ratio2 total_ratio = ratio1 + ratio2
debug_print(f"🎯 比例解析完成: {ratio1}:{ratio2} (总比例: {total_ratio}) ✨")
# 4. 计算各溶剂体积 # 4. 计算各溶剂体积
debug_print("📍 步骤4: 计算各溶剂体积... 🧮")
volume1 = final_volume * (ratio1 / total_ratio) volume1 = final_volume * (ratio1 / total_ratio)
volume2 = final_volume * (ratio2 / total_ratio) volume2 = final_volume * (ratio2 / total_ratio)
debug_print(f"🧪 {solvent1} 体积: {volume1:.2f} mL ({ratio1}/{total_ratio} × {final_volume})") debug_print(f"溶剂体积: {solvent1}={volume1:.2f}mL, {solvent2}={volume2:.2f}mL")
debug_print(f"🧪 {solvent2} 体积: {volume2:.2f} mL ({ratio2}/{total_ratio} × {final_volume})")
debug_print(f"✅ 体积计算完成: 总计 {volume1 + volume2:.2f} mL 🎯")
# 5. 查找溶剂容器 # 5. 查找溶剂容器
debug_print("📍 步骤5: 查找溶剂容器... 🔍")
try: try:
debug_print(f" 🔍 查找溶剂1容器...")
solvent1_vessel = find_solvent_vessel(G, solvent1) solvent1_vessel = find_solvent_vessel(G, solvent1)
debug_print(f" 🎉 找到溶剂1容器: {solvent1_vessel}")
except ValueError as e: except ValueError as e:
debug_print(f" ❌ 溶剂1容器查找失败: {str(e)} 😭")
raise ValueError(f"无法找到溶剂1 '{solvent1}': {str(e)}") raise ValueError(f"无法找到溶剂1 '{solvent1}': {str(e)}")
try: try:
debug_print(f" 🔍 查找溶剂2容器...")
solvent2_vessel = find_solvent_vessel(G, solvent2) solvent2_vessel = find_solvent_vessel(G, solvent2)
debug_print(f" 🎉 找到溶剂2容器: {solvent2_vessel}")
except ValueError as e: except ValueError as e:
debug_print(f" ❌ 溶剂2容器查找失败: {str(e)} 😭")
raise ValueError(f"无法找到溶剂2 '{solvent2}': {str(e)}") raise ValueError(f"无法找到溶剂2 '{solvent2}': {str(e)}")
# 6. 验证路径存在 # 6. 验证路径存在
debug_print("📍 步骤6: 验证传输路径... 🛤️")
try: try:
path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel_id) # 🔧 使用 vessel_id path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel_id)
debug_print(f" 🛤️ 溶剂1路径: {''.join(path1)}")
except nx.NetworkXNoPath: except nx.NetworkXNoPath:
debug_print(f" ❌ 溶剂1路径不可达: {solvent1_vessel}{vessel_id} 😞")
raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel_id}' 没有可用路径") raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
try: try:
path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel_id) # 🔧 使用 vessel_id path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel_id)
debug_print(f" 🛤️ 溶剂2路径: {''.join(path2)}")
except nx.NetworkXNoPath: except nx.NetworkXNoPath:
debug_print(f" ❌ 溶剂2路径不可达: {solvent2_vessel}{vessel_id} 😞")
raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel_id}' 没有可用路径") raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
# 7. 添加第一种溶剂 # 7. 添加第一种溶剂
debug_print("📍 步骤7: 添加第一种溶剂... 🧪")
debug_print(f" 🚰 开始添加溶剂1: {solvent1} ({volume1:.2f} mL)")
try: try:
pump_actions1 = generate_pump_protocol_with_rinsing( pump_actions1 = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=solvent1_vessel, from_vessel=solvent1_vessel,
to_vessel=vessel_id, # 🔧 使用 vessel_id to_vessel=vessel_id,
volume=volume1, # 使用解析后的体积 volume=volume1,
amount="", amount="",
time=0.0, time=0.0,
viscous=False, viscous=False,
rinsing_solvent="", # 重结晶不需要清洗 rinsing_solvent="",
rinsing_volume=0.0, rinsing_volume=0.0,
rinsing_repeats=0, rinsing_repeats=0,
solid=False, solid=False,
flowrate=2.0, # 正常流速 flowrate=2.0,
transfer_flowrate=0.5 transfer_flowrate=0.5
) )
action_sequence.extend(pump_actions1) action_sequence.extend(pump_actions1)
debug_print(f" ✅ 溶剂1泵送动作已添加: {len(pump_actions1)} 个动作 🚰✨")
except Exception as e: except Exception as e:
debug_print(f" ❌ 溶剂1泵协议生成失败: {str(e)} 😭")
raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}") raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}")
# 🔧 新增:更新容器体积 - 添加溶剂1后 # 更新容器体积 - 添加溶剂1后
debug_print(" 🔧 更新容器体积 - 添加溶剂1后...")
new_volume_after_solvent1 = original_liquid_volume + volume1 new_volume_after_solvent1 = original_liquid_volume + volume1
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list): if isinstance(current_volume, list):
@@ -217,7 +166,6 @@ def generate_recrystallize_protocol(
else: else:
vessel["data"]["liquid_volume"] = new_volume_after_solvent1 vessel["data"]["liquid_volume"] = new_volume_after_solvent1
# 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]: if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data'] = {}
@@ -233,52 +181,41 @@ def generate_recrystallize_protocol(
else: else:
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume_after_solvent1 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稳定 # 8. 等待溶剂1稳定
debug_print(" ⏳ 添加溶剂1稳定等待...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
"time": 5.0, # 缩短等待时间 "time": 5.0,
"description": f"等待溶剂1 {solvent1} 稳定" "description": f"等待溶剂1 {solvent1} 稳定"
} }
}) })
debug_print(" ✅ 溶剂1稳定等待已添加 ⏰✨")
# 9. 添加第二种溶剂 # 9. 添加第二种溶剂
debug_print("📍 步骤8: 添加第二种溶剂... 🧪")
debug_print(f" 🚰 开始添加溶剂2: {solvent2} ({volume2:.2f} mL)")
try: try:
pump_actions2 = generate_pump_protocol_with_rinsing( pump_actions2 = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=solvent2_vessel, from_vessel=solvent2_vessel,
to_vessel=vessel_id, # 🔧 使用 vessel_id to_vessel=vessel_id,
volume=volume2, # 使用解析后的体积 volume=volume2,
amount="", amount="",
time=0.0, time=0.0,
viscous=False, viscous=False,
rinsing_solvent="", # 重结晶不需要清洗 rinsing_solvent="",
rinsing_volume=0.0, rinsing_volume=0.0,
rinsing_repeats=0, rinsing_repeats=0,
solid=False, solid=False,
flowrate=2.0, # 正常流速 flowrate=2.0,
transfer_flowrate=0.5 transfer_flowrate=0.5
) )
action_sequence.extend(pump_actions2) action_sequence.extend(pump_actions2)
debug_print(f" ✅ 溶剂2泵送动作已添加: {len(pump_actions2)} 个动作 🚰✨")
except Exception as e: except Exception as e:
debug_print(f" ❌ 溶剂2泵协议生成失败: {str(e)} 😭")
raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}") raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}")
# 🔧 新增:更新容器体积 - 添加溶剂2后 # 更新容器体积 - 添加溶剂2后
debug_print(" 🔧 更新容器体积 - 添加溶剂2后...")
final_liquid_volume = new_volume_after_solvent1 + volume2 final_liquid_volume = new_volume_after_solvent1 + volume2
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]: if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"] current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list): if isinstance(current_volume, list):
@@ -289,7 +226,6 @@ def generate_recrystallize_protocol(
else: else:
vessel["data"]["liquid_volume"] = final_liquid_volume vessel["data"]["liquid_volume"] = final_liquid_volume
# 同时更新图中的容器数据
if vessel_id in G.nodes(): if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]: if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {} G.nodes[vessel_id]['data'] = {}
@@ -305,34 +241,23 @@ def generate_recrystallize_protocol(
else: else:
G.nodes[vessel_id]['data']['liquid_volume'] = final_liquid_volume 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稳定 # 10. 等待溶剂2稳定
debug_print(" ⏳ 添加溶剂2稳定等待...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
"time": 5.0, # 缩短等待时间 "time": 5.0,
"description": f"等待溶剂2 {solvent2} 稳定" "description": f"等待溶剂2 {solvent2} 稳定"
} }
}) })
debug_print(" ✅ 溶剂2稳定等待已添加 ⏰✨")
# 11. 等待重结晶完成 # 11. 等待重结晶完成
debug_print("📍 步骤9: 等待重结晶完成... 💎") original_crystallize_time = 600.0
simulation_time_limit = 60.0
# 模拟运行时间优化
debug_print(" ⏱️ 检查模拟运行时间限制...")
original_crystallize_time = 600.0 # 原始重结晶时间
simulation_time_limit = 60.0 # 模拟运行时间限制60秒
final_crystallize_time = min(original_crystallize_time, simulation_time_limit) final_crystallize_time = min(original_crystallize_time, simulation_time_limit)
if 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}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 保持不变 🎯")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
@@ -341,26 +266,8 @@ def generate_recrystallize_protocol(
"description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio},总体积 {final_volume}mL" + (f" (模拟时间)" if original_crystallize_time != final_crystallize_time else "") "description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio},总体积 {final_volume}mL" + (f" (模拟时间)" if original_crystallize_time != final_crystallize_time else "")
} }
}) })
debug_print(f" ✅ 重结晶等待已添加: {final_crystallize_time}s 💎✨")
# 显示时间调整信息 debug_print(f"重结晶协议生成完成: {len(action_sequence)} 个动作, 容器={vessel_id}, 体积变化: {original_liquid_volume:.2f} -> {final_liquid_volume:.2f}mL")
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)
return action_sequence return action_sequence
@@ -368,23 +275,19 @@ def generate_recrystallize_protocol(
# 测试函数 # 测试函数
def test_recrystallize_protocol(): def test_recrystallize_protocol():
"""测试重结晶协议""" """测试重结晶协议"""
debug_print("🧪 === RECRYSTALLIZE PROTOCOL 测试 ===") debug_print("=== RECRYSTALLIZE PROTOCOL 测试 ===")
# 测试体积解析
debug_print("💧 测试体积解析...")
test_volumes = ["100 mL", "2.5 L", "500", "50.5", "?", "invalid"] test_volumes = ["100 mL", "2.5 L", "500", "50.5", "?", "invalid"]
for vol in test_volumes: for vol in test_volumes:
parsed = parse_volume_input(vol) parsed = parse_volume_input(vol)
debug_print(f" 📊 体积 '{vol}' -> {parsed}mL") debug_print(f"体积 '{vol}' -> {parsed}mL")
# 测试比例解析
debug_print("⚖️ 测试比例解析...")
test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"] test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"]
for ratio in test_ratios: for ratio in test_ratios:
r1, r2 = parse_ratio(ratio) r1, r2 = parse_ratio(ratio)
debug_print(f" 📊 比例 '{ratio}' -> {r1}:{r2}") debug_print(f"比例 '{ratio}' -> {r1}:{r2}")
debug_print("测试完成 🎉") debug_print("测试完成")
if __name__ == "__main__": if __name__ == "__main__":
test_recrystallize_protocol() test_recrystallize_protocol()

View File

@@ -1,165 +1,20 @@
import networkx as nx import networkx as nx
import logging import logging
import sys
from typing import List, Dict, Any, Optional 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 from .pump_protocol import generate_pump_protocol_with_rinsing
# 设置日志 # 设置日志
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# 确保输出编码为UTF-8 create_action_log = action_log
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]}...")
def generate_reset_handling_protocol( def generate_reset_handling_protocol(
G: nx.DiGraph, G: nx.DiGraph,
solvent: str, solvent: str,
vessel: Optional[str] = None, # 🆕 新增可选vessel参数 vessel: Optional[str] = None,
**kwargs # 接收其他可能的参数但不使用 **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成重置处理协议序列 - 支持自定义容器 生成重置处理协议序列 - 支持自定义容器
@@ -175,78 +30,57 @@ def generate_reset_handling_protocol(
""" """
action_sequence = [] action_sequence = []
# 🔧 修改支持自定义vessel参数 target_vessel = vessel if vessel is not None else "main_reactor"
target_vessel = vessel if vessel is not None else "main_reactor" # 默认目标容器 volume = 50.0
volume = 50.0 # 默认体积 50 mL
debug_print("=" * 60) debug_print(f"开始生成重置处理协议: solvent={solvent}, vessel={target_vessel}, volume={volume}mL")
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)
# 添加初始日志 # 添加初始日志
action_sequence.append(create_action_log(f"开始重置处理操作 - 容器: {target_vessel}", "🎬")) action_sequence.append(action_log(f"开始重置处理操作 - 容器: {target_vessel}", "🎬"))
action_sequence.append(create_action_log(f"使用溶剂: {solvent}", "🧪")) action_sequence.append(action_log(f"使用溶剂: {solvent}", "🧪"))
action_sequence.append(create_action_log(f"重置体积: {volume}mL", "💧")) action_sequence.append(action_log(f"重置体积: {volume}mL", "💧"))
if vessel is None: if vessel is None:
action_sequence.append(create_action_log("使用默认目标容器: main_reactor", "⚙️")) action_sequence.append(action_log("使用默认目标容器: main_reactor", "⚙️"))
else: else:
action_sequence.append(create_action_log(f"使用指定目标容器: {vessel}", "🎯")) action_sequence.append(action_log(f"使用指定目标容器: {vessel}", "🎯"))
# 1. 验证目标容器存在 # 1. 验证目标容器存在
debug_print("🔍 步骤1: 验证目标容器...") action_sequence.append(action_log("正在验证目标容器...", "🔍"))
action_sequence.append(create_action_log("正在验证目标容器...", "🔍"))
if target_vessel not in G.nodes(): if target_vessel not in G.nodes():
debug_print(f"目标容器 '{target_vessel}' 不存在于系统中!") action_sequence.append(action_log(f"目标容器 '{target_vessel}' 不存在", ""))
action_sequence.append(create_action_log(f"目标容器 '{target_vessel}' 不存在", ""))
raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中") raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中")
debug_print(f"目标容器 '{target_vessel}' 验证通过") action_sequence.append(action_log(f"目标容器验证通过: {target_vessel}", ""))
action_sequence.append(create_action_log(f"目标容器验证通过: {target_vessel}", ""))
# 2. 查找溶剂容器 # 2. 查找溶剂容器
debug_print("🔍 步骤2: 查找溶剂容器...") action_sequence.append(action_log("正在查找溶剂容器...", "🔍"))
action_sequence.append(create_action_log("正在查找溶剂容器...", "🔍"))
try: try:
solvent_vessel = find_solvent_vessel(G, solvent) solvent_vessel = find_solvent_vessel(G, solvent)
debug_print(f"找到溶剂容器: {solvent_vessel}") debug_print(f"找到溶剂容器: {solvent_vessel}")
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "")) action_sequence.append(action_log(f"找到溶剂容器: {solvent_vessel}", ""))
except ValueError as e: except ValueError as e:
debug_print(f"溶剂容器查找失败: {str(e)}") action_sequence.append(action_log(f"溶剂容器查找失败: {str(e)}", ""))
action_sequence.append(create_action_log(f"溶剂容器查找失败: {str(e)}", ""))
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}") raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
# 3. 验证路径存在 # 3. 验证路径存在
debug_print("🔍 步骤3: 验证传输路径...") action_sequence.append(action_log("正在验证传输路径...", "🛤️"))
action_sequence.append(create_action_log("正在验证传输路径...", "🛤️"))
try: try:
path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel) path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel)
debug_print(f"✅ 找到路径: {''.join(path)}") action_sequence.append(action_log(f"传输路径: {''.join(path)}", "🛤️"))
action_sequence.append(create_action_log(f"传输路径: {''.join(path)}", "🛤️"))
except nx.NetworkXNoPath: except nx.NetworkXNoPath:
debug_print(f"路径不可达: {solvent_vessel}{target_vessel}") action_sequence.append(action_log(f"路径不可达: {solvent_vessel}{target_vessel}", ""))
action_sequence.append(create_action_log(f"路径不可达: {solvent_vessel}{target_vessel}", ""))
raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径") raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径")
# 4. 使用pump_protocol转移溶剂 # 4. 使用pump_protocol转移溶剂
debug_print("🔍 步骤4: 转移溶剂...") action_sequence.append(action_log("开始溶剂转移操作...", "🚰"))
action_sequence.append(create_action_log("开始溶剂转移操作...", "🚰")) action_sequence.append(action_log(f"转移: {solvent_vessel}{target_vessel} ({volume}mL)", "🚛"))
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)", "🚛"))
try: try:
debug_print("🔄 生成泵送协议...") action_sequence.append(action_log("正在生成泵送协议...", "🔄"))
action_sequence.append(create_action_log("正在生成泵送协议...", "🔄"))
pump_actions = generate_pump_protocol_with_rinsing( pump_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
@@ -256,40 +90,33 @@ def generate_reset_handling_protocol(
amount="", amount="",
time=0.0, time=0.0,
viscous=False, viscous=False,
rinsing_solvent="", # 重置处理不需要清洗 rinsing_solvent="",
rinsing_volume=0.0, rinsing_volume=0.0,
rinsing_repeats=0, rinsing_repeats=0,
solid=False, solid=False,
flowrate=2.5, # 正常流速 flowrate=2.5,
transfer_flowrate=0.5 # 正常转移流速 transfer_flowrate=0.5
) )
action_sequence.extend(pump_actions) action_sequence.extend(pump_actions)
debug_print(f"泵送协议已添加: {len(pump_actions)} 个动作") debug_print(f"泵送协议已添加: {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"泵送协议完成 ({len(pump_actions)} 个操作)", "")) action_sequence.append(action_log(f"泵送协议完成 ({len(pump_actions)} 个操作)", ""))
except Exception as e: except Exception as e:
debug_print(f"泵送协议生成失败: {str(e)}") action_sequence.append(action_log(f"泵送协议生成失败: {str(e)}", ""))
action_sequence.append(create_action_log(f"泵送协议生成失败: {str(e)}", ""))
raise ValueError(f"生成泵协议时出错: {str(e)}") raise ValueError(f"生成泵协议时出错: {str(e)}")
# 5. 等待溶剂稳定 # 5. 等待溶剂稳定
debug_print("🔍 步骤5: 等待溶剂稳定...") action_sequence.append(action_log("等待溶剂稳定...", ""))
action_sequence.append(create_action_log("等待溶剂稳定...", ""))
# 模拟运行时间优化
debug_print("⏱️ 检查模拟运行时间限制...")
original_wait_time = 10.0 # 原始等待时间
simulation_time_limit = 5.0 # 模拟运行时间限制5秒
original_wait_time = 10.0
simulation_time_limit = 5.0
final_wait_time = min(original_wait_time, simulation_time_limit) final_wait_time = min(original_wait_time, simulation_time_limit)
if 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(action_log(f"时间优化: {original_wait_time}s → {final_wait_time}s", ""))
action_sequence.append(create_action_log(f"时间优化: {original_wait_time}s → {final_wait_time}s", ""))
else: else:
debug_print(f"✅ 时间在限制内: {final_wait_time}s 保持不变") action_sequence.append(action_log(f"等待时间: {final_wait_time}s", ""))
action_sequence.append(create_action_log(f"等待时间: {final_wait_time}s", ""))
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
@@ -298,34 +125,20 @@ def generate_reset_handling_protocol(
"description": f"等待溶剂 {solvent} 在容器 {target_vessel} 中稳定" + (f" (模拟时间)" if original_wait_time != final_wait_time else "") "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: if original_wait_time != final_wait_time:
debug_print(f"🎭 模拟优化说明: 原计划 {original_wait_time}s实际模拟 {final_wait_time}s") action_sequence.append(action_log("应用模拟时间优化", "🎭"))
action_sequence.append(create_action_log("应用模拟时间优化", "🎭"))
# 总结 # 总结
debug_print("=" * 60) debug_print(f"重置处理协议生成完成: {len(action_sequence)} 个动作, {solvent_vessel} -> {target_vessel}, {volume}mL")
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)
# 添加完成日志
summary_msg = f"重置处理完成: {target_vessel} (使用 {volume}mL {solvent})" summary_msg = f"重置处理完成: {target_vessel} (使用 {volume}mL {solvent})"
if vessel is None: if vessel is None:
summary_msg += " [默认容器]" summary_msg += " [默认容器]"
else: else:
summary_msg += " [指定容器]" summary_msg += " [指定容器]"
action_sequence.append(create_action_log(summary_msg, "🎉")) action_sequence.append(action_log(summary_msg, "🎉"))
return action_sequence return action_sequence
@@ -333,55 +146,29 @@ def generate_reset_handling_protocol(
def reset_main_reactor(G: nx.DiGraph, solvent: str = "methanol", **kwargs) -> List[Dict[str, Any]]: 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) 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]]: 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) 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]]: 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) 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]]: 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) 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]]: 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) return generate_reset_handling_protocol(G, solvent="ethanol", vessel=vessel, **kwargs)
# 测试函数 # 测试函数
def test_reset_handling_protocol(): def test_reset_handling_protocol():
"""测试重置处理协议""" """测试重置处理协议"""
debug_print("=== 重置处理协议增强中文版测试 ===") debug_print("=== 重置处理协议测试 ===")
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("✅ 测试完成")
if __name__ == "__main__": if __name__ == "__main__":
test_reset_handling_protocol() test_reset_handling_protocol()

View File

@@ -2,15 +2,13 @@ from typing import List, Dict, Any, Union
import networkx as nx import networkx as nx
import logging import logging
import re 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 from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
logger.info(f"[RUN_COLUMN] {message}")
def parse_percentage(pct_str: str) -> float: def parse_percentage(pct_str: str) -> float:
""" """
解析百分比字符串为数值 解析百分比字符串为数值
@@ -25,19 +23,16 @@ def parse_percentage(pct_str: str) -> float:
return 0.0 return 0.0
pct_str = pct_str.strip().lower() pct_str = pct_str.strip().lower()
debug_print(f"🔍 解析百分比: '{pct_str}'")
# 移除百分号和空格 # 移除百分号和空格
pct_clean = re.sub(r'[%\s]', '', pct_str) pct_clean = re.sub(r'[%\s]', '', pct_str)
# 提取数字
match = re.search(r'([0-9]*\.?[0-9]+)', pct_clean) match = re.search(r'([0-9]*\.?[0-9]+)', pct_clean)
if match: if match:
value = float(match.group(1)) value = float(match.group(1))
debug_print(f"✅ 百分比解析结果: {value}%")
return value return value
debug_print(f"⚠️ 无法解析百分比: '{pct_str}'返回0.0") debug_print(f"无法解析百分比: '{pct_str}'返回0.0")
return 0.0 return 0.0
def parse_ratio(ratio_str: str) -> tuple: def parse_ratio(ratio_str: str) -> tuple:
@@ -48,13 +43,12 @@ def parse_ratio(ratio_str: str) -> tuple:
ratio_str: 比例字符串(如 "5:95", "1:1", "40:60" ratio_str: 比例字符串(如 "5:95", "1:1", "40:60"
Returns: Returns:
tuple: (ratio1, ratio2) 两个比例值 tuple: (ratio1, ratio2) 两个比例值(百分比)
""" """
if not ratio_str or not ratio_str.strip(): 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() ratio_str = ratio_str.strip()
debug_print(f"🔍 解析比例: '{ratio_str}'")
# 支持多种分隔符:: / - # 支持多种分隔符:: / -
if ':' in ratio_str: if ':' in ratio_str:
@@ -66,7 +60,7 @@ def parse_ratio(ratio_str: str) -> tuple:
elif 'to' in ratio_str.lower(): elif 'to' in ratio_str.lower():
parts = ratio_str.lower().split('to') parts = ratio_str.lower().split('to')
else: else:
debug_print(f"⚠️ 无法解析比例格式: '{ratio_str}'使用默认1:1") debug_print(f"无法解析比例格式: '{ratio_str}'使用默认1:1")
return (50.0, 50.0) return (50.0, 50.0)
if len(parts) >= 2: if len(parts) >= 2:
@@ -75,16 +69,14 @@ def parse_ratio(ratio_str: str) -> tuple:
ratio2 = float(parts[1].strip()) ratio2 = float(parts[1].strip())
total = ratio1 + ratio2 total = ratio1 + ratio2
# 转换为百分比
pct1 = (ratio1 / total) * 100 pct1 = (ratio1 / total) * 100
pct2 = (ratio2 / total) * 100 pct2 = (ratio2 / total) * 100
debug_print(f"✅ 比例解析结果: {ratio1}:{ratio2} -> {pct1:.1f}%:{pct2:.1f}%")
return (pct1, pct2) return (pct1, pct2)
except ValueError as e: except ValueError as e:
debug_print(f"⚠️ 比例数值转换失败: {str(e)}") debug_print(f"比例数值转换失败: {str(e)}")
debug_print(f"⚠️ 比例解析失败使用默认1:1") debug_print(f"比例解析失败使用默认1:1")
return (50.0, 50.0) return (50.0, 50.0)
def parse_rf_value(rf_str: str) -> float: def parse_rf_value(rf_str: str) -> float:
@@ -98,66 +90,49 @@ def parse_rf_value(rf_str: str) -> float:
float: Rf值0-1 float: Rf值0-1
""" """
if not rf_str or not rf_str.strip(): if not rf_str or not rf_str.strip():
return 0.3 # 默认Rf值 return 0.3
rf_str = rf_str.strip().lower() rf_str = rf_str.strip().lower()
debug_print(f"🔍 解析Rf值: '{rf_str}'")
# 处理未知Rf值
if rf_str in ['?', 'unknown', 'tbd', 'to be determined']: if rf_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_rf = 0.3 return 0.3
debug_print(f"❓ 检测到未知Rf值使用默认值: {default_rf}")
return default_rf
# 提取数字
match = re.search(r'([0-9]*\.?[0-9]+)', rf_str) match = re.search(r'([0-9]*\.?[0-9]+)', rf_str)
if match: if match:
value = float(match.group(1)) value = float(match.group(1))
# 确保Rf值在0-1范围内
if value > 1.0: if value > 1.0:
value = value / 100.0 # 可能是百分比形式 value = value / 100.0
value = max(0.0, min(1.0, value)) # 限制在0-1范围 value = max(0.0, min(1.0, value))
debug_print(f"✅ Rf值解析结果: {value}")
return value return value
debug_print(f"⚠️ 无法解析Rf值: '{rf_str}'使用默认值0.3")
return 0.3 return 0.3
def find_column_device(G: nx.DiGraph) -> str: def find_column_device(G: nx.DiGraph) -> str:
"""查找柱层析设备""" """查找柱层析设备"""
debug_print("🔍 查找柱层析设备...")
# 查找虚拟柱设备
for node in G.nodes(): for node in G.nodes():
node_data = G.nodes[node] node_data = G.nodes[node]
node_class = node_data.get('class', '') or '' node_class = node_data.get('class', '') or ''
if 'virtual_column' in node_class.lower() or 'column' in node_class.lower(): if 'virtual_column' in node_class.lower() or 'column' in node_class.lower():
debug_print(f"🎉 找到柱层析设备: {node}") debug_print(f"找到柱层析设备: {node}")
return node return node
# 如果没有找到,尝试创建虚拟设备名称
possible_names = ['column_1', 'virtual_column_1', 'chromatography_column_1'] possible_names = ['column_1', 'virtual_column_1', 'chromatography_column_1']
for name in possible_names: for name in possible_names:
if name in G.nodes(): if name in G.nodes():
debug_print(f"🎉 找到柱设备: {name}") debug_print(f"找到柱设备: {name}")
return name return name
debug_print("⚠️ 未找到柱层析设备将使用pump protocol直接转移") debug_print("未找到柱层析设备将使用pump protocol直接转移")
return "" return ""
def find_column_vessel(G: nx.DiGraph, column: str) -> str: def find_column_vessel(G: nx.DiGraph, column: str) -> str:
"""查找柱容器""" """查找柱容器"""
debug_print(f"🔍 查找柱容器: '{column}'")
# 直接检查column参数是否是容器
if column in G.nodes(): if column in G.nodes():
node_type = G.nodes[column].get('type', '') node_type = G.nodes[column].get('type', '')
if node_type == 'container': if node_type == 'container':
debug_print(f"🎉 找到柱容器: {column}")
return column return column
# 尝试常见的命名规则
possible_names = [ possible_names = [
f"column_{column}", f"column_{column}",
f"{column}_column", f"{column}_column",
@@ -174,206 +149,20 @@ def find_column_vessel(G: nx.DiGraph, column: str) -> str:
if vessel_name in G.nodes(): if vessel_name in G.nodes():
node_type = G.nodes[vessel_name].get('type', '') node_type = G.nodes[vessel_name].get('type', '')
if node_type == 'container': if node_type == 'container':
debug_print(f"🎉 找到柱容器: {vessel_name}")
return vessel_name return vessel_name
debug_print(f"⚠️ 未找到柱容器,将直接在源容器中进行分离")
return "" 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: def calculate_solvent_volumes(total_volume: float, pct1: float, pct2: float) -> tuple:
"""根据百分比计算溶剂体积""" """根据百分比计算溶剂体积"""
volume1 = (total_volume * pct1) / 100.0 volume1 = (total_volume * pct1) / 100.0
volume2 = (total_volume * pct2) / 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) return (volume1, volume2)
def generate_run_column_protocol( def generate_run_column_protocol(
G: nx.DiGraph, G: nx.DiGraph,
from_vessel: dict, # 🔧 修改:从字符串改为字典类型 from_vessel: dict,
to_vessel: dict, # 🔧 修改:从字符串改为字典类型 to_vessel: dict,
column: str, column: str,
rf: str = "", rf: str = "",
pct1: str = "", pct1: str = "",
@@ -403,65 +192,39 @@ def generate_run_column_protocol(
List[Dict[str, Any]]: 柱层析分离操作的动作序列 List[Dict[str, Any]]: 柱层析分离操作的动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
from_vessel_id, _ = get_vessel(from_vessel) from_vessel_id, _ = get_vessel(from_vessel)
to_vessel_id, _ = get_vessel(to_vessel) to_vessel_id, _ = get_vessel(to_vessel)
debug_print("🏛️" * 20) debug_print(f"开始生成柱层析协议: {from_vessel_id} -> {to_vessel_id}, column={column}")
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)
action_sequence = [] action_sequence = []
# 🔧 新增:记录柱层析前的容器状态 # 记录柱层析前的容器状态
debug_print("🔍 记录柱层析前容器状态...") original_from_volume = get_resource_liquid_volume(from_vessel)
original_from_volume = get_vessel_liquid_volume(from_vessel) original_to_volume = get_resource_liquid_volume(to_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")
# === 参数验证 === # === 参数验证 ===
debug_print("📍 步骤1: 参数验证...") if not from_vessel_id:
if not from_vessel_id: # 🔧 使用 from_vessel_id
raise ValueError("from_vessel 参数不能为空") raise ValueError("from_vessel 参数不能为空")
if not to_vessel_id: # 🔧 使用 to_vessel_id if not to_vessel_id:
raise ValueError("to_vessel 参数不能为空") raise ValueError("to_vessel 参数不能为空")
if not column: if not column:
raise ValueError("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}' 不存在于系统中") 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}' 不存在于系统中") raise ValueError(f"目标容器 '{to_vessel_id}' 不存在于系统中")
debug_print("✅ 基本参数验证通过")
# === 参数解析 === # === 参数解析 ===
debug_print("📍 步骤2: 参数解析...")
# 解析Rf值
final_rf = parse_rf_value(rf) final_rf = parse_rf_value(rf)
debug_print(f"🎯 最终Rf值: {final_rf}")
# 解析溶剂比例ratio优先级高于pct1/pct2
if ratio and ratio.strip(): if ratio and ratio.strip():
final_pct1, final_pct2 = parse_ratio(ratio) final_pct1, final_pct2 = parse_ratio(ratio)
debug_print(f"📊 使用ratio参数: {final_pct1:.1f}% : {final_pct2:.1f}%")
else: else:
final_pct1 = parse_percentage(pct1) if pct1 else 50.0 final_pct1 = parse_percentage(pct1) if pct1 else 50.0
final_pct2 = parse_percentage(pct2) if pct2 else 50.0 final_pct2 = parse_percentage(pct2) if pct2 else 50.0
# 如果百分比和不是100%,进行归一化
total_pct = final_pct1 + final_pct2 total_pct = final_pct1 + final_pct2
if total_pct == 0: if total_pct == 0:
final_pct1, final_pct2 = 50.0, 50.0 final_pct1, final_pct2 = 50.0, 50.0
@@ -469,102 +232,67 @@ def generate_run_column_protocol(
final_pct1 = (final_pct1 / total_pct) * 100 final_pct1 = (final_pct1 / total_pct) * 100
final_pct2 = (final_pct2 / 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_solvent1 = solvent1.strip() if solvent1 else "ethyl_acetate"
final_solvent2 = solvent2.strip() if solvent2 else "hexane" 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_device_id = find_column_device(G)
# 查找柱容器
column_vessel = find_column_vessel(G, column) column_vessel = find_column_vessel(G, column)
# 查找溶剂容器
solvent1_vessel = find_solvent_vessel(G, final_solvent1) solvent1_vessel = find_solvent_vessel(G, final_solvent1)
solvent2_vessel = find_solvent_vessel(G, final_solvent2) 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 source_volume = original_from_volume
if source_volume <= 0: if source_volume <= 0:
source_volume = 50.0 # 默认体积 source_volume = 50.0
debug_print(f"⚠️ 无法获取源容器体积,使用默认值: {source_volume}mL")
else:
debug_print(f"✅ 源容器体积: {source_volume}mL")
# === 计算溶剂体积 === # === 计算溶剂体积 ===
debug_print("📍 步骤5: 计算溶剂体积...")
# 洗脱溶剂通常是样品体积的2-5倍
total_elution_volume = source_volume * 3.0 total_elution_volume = source_volume * 3.0
solvent1_volume, solvent2_volume = calculate_solvent_volumes( solvent1_volume, solvent2_volume = calculate_solvent_volumes(
total_elution_volume, final_pct1, final_pct2 total_elution_volume, final_pct1, final_pct2
) )
# === 执行柱层析流程 === # === 执行柱层析流程 ===
debug_print("📍 步骤6: 执行柱层析流程...")
# 🔧 新增:体积变化跟踪变量
current_from_volume = source_volume current_from_volume = source_volume
current_to_volume = original_to_volume current_to_volume = original_to_volume
current_column_volume = 0.0 current_column_volume = 0.0
try: try:
# 步骤6.1: 样品上柱(如果有独立的柱容器) # 步骤1: 样品上柱
if column_vessel and column_vessel != from_vessel_id: # 🔧 使用 from_vessel_id if column_vessel and column_vessel != from_vessel_id:
debug_print(f"📍 6.1: 样品上柱 - {source_volume}mL 从 {from_vessel_id}{column_vessel}")
try: try:
sample_transfer_actions = generate_pump_protocol_with_rinsing( sample_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=from_vessel_id, # 🔧 使用 from_vessel_id from_vessel=from_vessel_id,
to_vessel=column_vessel, to_vessel=column_vessel,
volume=source_volume, volume=source_volume,
flowrate=1.0, # 慢速上柱 flowrate=1.0,
transfer_flowrate=0.5, transfer_flowrate=0.5,
rinsing_solvent="", # 暂不冲洗 rinsing_solvent="",
rinsing_volume=0.0, rinsing_volume=0.0,
rinsing_repeats=0 rinsing_repeats=0
) )
action_sequence.extend(sample_transfer_actions) action_sequence.extend(sample_transfer_actions)
debug_print(f"✅ 样品上柱完成,添加了 {len(sample_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 样品转移到柱上 current_from_volume = 0.0
current_from_volume = 0.0 # 源容器体积变为0 current_column_volume = source_volume
current_column_volume = source_volume # 柱容器体积增加
update_vessel_volume(from_vessel, G, current_from_volume, "样品上柱后,源容器清空") update_vessel_volume(from_vessel, G, current_from_volume, "样品上柱后,源容器清空")
# 如果柱容器在图中,也更新其体积
if column_vessel in G.nodes(): if column_vessel in G.nodes():
if 'data' not in G.nodes[column_vessel]: if 'data' not in G.nodes[column_vessel]:
G.nodes[column_vessel]['data'] = {} G.nodes[column_vessel]['data'] = {}
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume
debug_print(f"📊 柱容器 '{column_vessel}' 体积更新为: {current_column_volume:.2f}mL")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 样品上柱失败: {str(e)}") debug_print(f"样品上柱失败: {str(e)}")
# 步骤6.2: 添加洗脱溶剂1(如果有溶剂容器) # 步骤2: 添加洗脱溶剂1
if solvent1_vessel and solvent1_volume > 0: if solvent1_vessel and solvent1_volume > 0:
debug_print(f"📍 6.2: 添加洗脱溶剂1 - {solvent1_volume:.1f}mL {final_solvent1}")
try: 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( solvent1_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=solvent1_vessel, from_vessel=solvent1_vessel,
@@ -574,27 +302,22 @@ def generate_run_column_protocol(
transfer_flowrate=1.0 transfer_flowrate=1.0
) )
action_sequence.extend(solvent1_transfer_actions) action_sequence.extend(solvent1_transfer_actions)
debug_print(f"✅ 溶剂1添加完成添加了 {len(solvent1_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 添加溶剂1
if target_vessel == column_vessel: if target_vessel == column_vessel:
current_column_volume += solvent1_volume current_column_volume += solvent1_volume
if column_vessel in G.nodes(): if column_vessel in G.nodes():
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume 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: elif target_vessel == from_vessel_id:
current_from_volume += solvent1_volume current_from_volume += solvent1_volume
update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂1后") update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂1后")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 溶剂1添加失败: {str(e)}") debug_print(f"溶剂1添加失败: {str(e)}")
# 步骤6.3: 添加洗脱溶剂2(如果有溶剂容器) # 步骤3: 添加洗脱溶剂2
if solvent2_vessel and solvent2_volume > 0: if solvent2_vessel and solvent2_volume > 0:
debug_print(f"📍 6.3: 添加洗脱溶剂2 - {solvent2_volume:.1f}mL {final_solvent2}")
try: 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( solvent2_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=solvent2_vessel, from_vessel=solvent2_vessel,
@@ -604,31 +327,26 @@ def generate_run_column_protocol(
transfer_flowrate=1.0 transfer_flowrate=1.0
) )
action_sequence.extend(solvent2_transfer_actions) action_sequence.extend(solvent2_transfer_actions)
debug_print(f"✅ 溶剂2添加完成添加了 {len(solvent2_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 添加溶剂2
if target_vessel == column_vessel: if target_vessel == column_vessel:
current_column_volume += solvent2_volume current_column_volume += solvent2_volume
if column_vessel in G.nodes(): if column_vessel in G.nodes():
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume 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: elif target_vessel == from_vessel_id:
current_from_volume += solvent2_volume current_from_volume += solvent2_volume
update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂2后") update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂2后")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 溶剂2添加失败: {str(e)}") debug_print(f"溶剂2添加失败: {str(e)}")
# 步骤6.4: 使用柱层析设备执行分离(如果有设备) # 步骤4: 使用柱层析设备执行分离
if column_device_id: if column_device_id:
debug_print(f"📍 6.4: 使用柱层析设备执行分离")
column_separation_action = { column_separation_action = {
"device_id": column_device_id, "device_id": column_device_id,
"action_name": "run_column", "action_name": "run_column",
"action_kwargs": { "action_kwargs": {
"from_vessel": from_vessel_id, # 🔧 使用 from_vessel_id "from_vessel": from_vessel_id,
"to_vessel": to_vessel_id, # 🔧 使用 to_vessel_id "to_vessel": to_vessel_id,
"column": column, "column": column,
"rf": rf, "rf": rf,
"pct1": pct1, "pct1": pct1,
@@ -639,85 +357,65 @@ def generate_run_column_protocol(
} }
} }
action_sequence.append(column_separation_action) action_sequence.append(column_separation_action)
debug_print(f"✅ 柱层析设备动作已添加")
# 等待分离完成 separation_time = max(30, min(120, int(total_elution_volume / 2)))
separation_time = max(30, min(120, int(total_elution_volume / 2))) # 30-120秒基于体积
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": separation_time} "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: try:
# 估算产物体积原始样品体积的70-90%,收率考虑)
product_volume = source_volume * 0.8 product_volume = source_volume * 0.8
product_transfer_actions = generate_pump_protocol_with_rinsing( product_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=column_vessel, from_vessel=column_vessel,
to_vessel=to_vessel_id, # 🔧 使用 to_vessel_id to_vessel=to_vessel_id,
volume=product_volume, volume=product_volume,
flowrate=1.5, flowrate=1.5,
transfer_flowrate=0.8 transfer_flowrate=0.8
) )
action_sequence.extend(product_transfer_actions) action_sequence.extend(product_transfer_actions)
debug_print(f"✅ 产物收集完成,添加了 {len(product_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 产物收集到目标容器
current_to_volume += product_volume current_to_volume += product_volume
current_column_volume -= product_volume # 柱容器体积减少 current_column_volume -= product_volume
update_vessel_volume(to_vessel, G, current_to_volume, "产物收集后") update_vessel_volume(to_vessel, G, current_to_volume, "产物收集后")
# 更新柱容器体积
if column_vessel in G.nodes(): if column_vessel in G.nodes():
G.nodes[column_vessel]['data']['liquid_volume'] = max(0.0, current_column_volume) 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: except Exception as e:
debug_print(f"⚠️ 产物收集失败: {str(e)}") debug_print(f"产物收集失败: {str(e)}")
# 步骤6.6: 如果没有独立的柱设备和容器,执行简化的直接转移 # 步骤6: 简化模式 - 直接转移
if not column_device_id and not column_vessel: if not column_device_id and not column_vessel:
debug_print(f"📍 6.6: 简化模式 - 直接转移 {source_volume}mL 从 {from_vessel_id}{to_vessel_id}")
try: try:
direct_transfer_actions = generate_pump_protocol_with_rinsing( direct_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=from_vessel_id, # 🔧 使用 from_vessel_id from_vessel=from_vessel_id,
to_vessel=to_vessel_id, # 🔧 使用 to_vessel_id to_vessel=to_vessel_id,
volume=source_volume, volume=source_volume,
flowrate=2.0, flowrate=2.0,
transfer_flowrate=1.0 transfer_flowrate=1.0
) )
action_sequence.extend(direct_transfer_actions) action_sequence.extend(direct_transfer_actions)
debug_print(f"✅ 直接转移完成,添加了 {len(direct_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 直接转移 current_from_volume = 0.0
current_from_volume = 0.0 # 源容器清空 current_to_volume += source_volume
current_to_volume += source_volume # 目标容器增加
update_vessel_volume(from_vessel, G, current_from_volume, "直接转移后,源容器清空") update_vessel_volume(from_vessel, G, current_from_volume, "直接转移后,源容器清空")
update_vessel_volume(to_vessel, G, current_to_volume, "直接转移后,目标容器增加") update_vessel_volume(to_vessel, G, current_to_volume, "直接转移后,目标容器增加")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 直接转移失败: {str(e)}") debug_print(f"直接转移失败: {str(e)}")
except Exception as e: except Exception as e:
debug_print(f"协议生成失败: {str(e)} 😭") debug_print(f"协议生成失败: {str(e)}")
# 不添加不确定的动作直接让action_sequence保持为空列表
# action_sequence 已经在函数开始时初始化为 []
# 确保至少有一个有效的动作,如果完全失败就返回空列表
if not action_sequence: if not action_sequence:
debug_print("⚠️ 没有生成任何有效动作")
# 可以选择返回空列表或添加一个基本的等待动作
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
@@ -726,46 +424,23 @@ def generate_run_column_protocol(
} }
}) })
# 🔧 新增:柱层析完成后的最终状态报告 final_from_volume = get_resource_liquid_volume(from_vessel)
final_from_volume = get_vessel_liquid_volume(from_vessel) final_to_volume = get_resource_liquid_volume(to_vessel)
final_to_volume = get_vessel_liquid_volume(to_vessel)
# 🎊 总结 debug_print(f"柱层析协议生成完成: {len(action_sequence)} 个动作, {from_vessel_id} -> {to_vessel_id}, 收集={final_to_volume - original_to_volume:.2f}mL")
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)
return action_sequence 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]]: 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) 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]]: 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) solvent1="methanol", solvent2="dichloromethane", ratio=ratio)
@@ -773,35 +448,25 @@ def generate_gradient_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vesse
column: str, start_ratio: str = "10:90", column: str, start_ratio: str = "10:90",
end_ratio: str = "50:50") -> List[Dict[str, Any]]: 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") 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]]: 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") 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]]: 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") solvent1="ethyl_acetate", solvent2="hexane", ratio="5:95")
# 测试函数 # 测试函数
def test_run_column_protocol(): def test_run_column_protocol():
"""测试柱层析协议""" """测试柱层析协议"""
debug_print("🧪 === RUN COLUMN PROTOCOL 测试 ===") debug_print("=== RUN COLUMN PROTOCOL 测试 ===")
debug_print("测试完成 🎉") debug_print("测试完成")
if __name__ == "__main__": if __name__ == "__main__":
test_run_column_protocol() test_run_column_protocol()

View File

@@ -1,48 +1,14 @@
from typing import List, Dict, Any, Union from typing import List, Dict, Any, Union
import networkx as nx import networkx as nx
import logging import logging
import re
from .utils.unit_parser import parse_time_input 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__) 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: 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 return stir_time, stir_speed, settling_time
def extract_vessel_id(vessel: Union[str, dict]) -> str: def extract_vessel_id(vessel) -> str:
""" """从vessel参数中提取vessel_id兼容 str / dict / ResourceDictInstance"""
从vessel参数中提取vessel_id return get_resource_id(vessel)
Args: def get_vessel_display_info(vessel) -> str:
vessel: vessel字典或vessel_id字符串 """获取容器的显示信息(用于日志),兼容 str / dict / ResourceDictInstance"""
return get_resource_display_info(vessel)
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 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 generate_stir_protocol( def generate_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -152,48 +85,28 @@ def generate_stir_protocol(
} }
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}") debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}")
debug_print("🌪️" * 20) debug_print(f"开始生成搅拌协议: vessel={vessel_id}, time={time}, "
debug_print("🚀 开始生成搅拌协议支持vessel字典") f"stir_time={stir_time}, stir_speed={stir_speed}RPM, settling={settling_time}")
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:
if not vessel_id: # 🔧 使用 vessel_id
debug_print("❌ vessel 参数不能为空! 😱")
raise ValueError("vessel 参数不能为空") raise ValueError("vessel 参数不能为空")
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id if vessel_id not in G.nodes():
debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中! 😞")
raise ValueError(f"容器 '{vessel_id}' 不存在于系统中") raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
debug_print("✅ 基础参数验证通过 🎯") # 参数解析 — 确定实际时间优先级time_spec > stir_time > time
# 🔄 参数解析
debug_print("📍 步骤2: 参数解析... ⚡")
# 确定实际时间优先级time_spec > stir_time > time
if time_spec: if time_spec:
parsed_time = parse_time_input(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]: elif stir_time not in ["0", 0, 0.0]:
parsed_time = parse_time_input(stir_time) parsed_time = parse_time_input(stir_time)
debug_print(f"🎯 使用stir_time: {stir_time}{parsed_time}s")
else: else:
parsed_time = parse_time_input(time) parsed_time = parse_time_input(time)
debug_print(f"🎯 使用time: {time}{parsed_time}s")
# 解析沉降时间 # 解析沉降时间
parsed_settling_time = parse_time_input(settling_time) parsed_settling_time = parse_time_input(settling_time)
# 🕐 模拟运行时间优化 # 模拟运行时间优化
debug_print(" ⏱️ 检查模拟运行时间限制...")
original_stir_time = parsed_time original_stir_time = parsed_time
original_settling_time = parsed_settling_time original_settling_time = parsed_settling_time
@@ -201,32 +114,26 @@ def generate_stir_protocol(
stir_time_limit = 60.0 stir_time_limit = 60.0
if parsed_time > stir_time_limit: if parsed_time > stir_time_limit:
parsed_time = stir_time_limit parsed_time = stir_time_limit
debug_print(f" 🎮 搅拌时间优化: {original_stir_time}s → {parsed_time}s ⚡")
# 沉降时间限制为30秒 # 沉降时间限制为30秒
settling_time_limit = 30.0 settling_time_limit = 30.0
if parsed_settling_time > settling_time_limit: if parsed_settling_time > settling_time_limit:
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 = validate_and_fix_params(
parsed_time, stir_speed, parsed_settling_time 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(f"最终参数: time={parsed_time}s, speed={stir_speed}RPM, settling={parsed_settling_time}s")
# 🔍 查找设备 # 查找设备
debug_print("📍 步骤3: 查找搅拌设备... 🔍")
try: try:
stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id stirrer_id = find_connected_stirrer(G, vessel_id)
debug_print(f"🎉 使用搅拌设备: {stirrer_id}")
except Exception as e: except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"无法找到搅拌设备: {str(e)}") raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 🚀 生成动作 # 生成动作
debug_print("📍 步骤4: 生成搅拌动作... 🌪️")
action_sequence = [] action_sequence = []
stir_action = { stir_action = {
@@ -244,22 +151,14 @@ def generate_stir_protocol(
} }
} }
action_sequence.append(stir_action) action_sequence.append(stir_action)
debug_print("✅ 搅拌动作已添加 🌪️✨")
# 显示时间优化信息 # 时间优化信息
if original_stir_time != parsed_time or original_settling_time != parsed_settling_time: if original_stir_time != parsed_time or original_settling_time != parsed_settling_time:
debug_print(f" 🎭 模拟优化说明:") debug_print(f"模拟优化: 搅拌 {original_stir_time/60:.1f}min→{parsed_time/60:.1f}min, "
debug_print(f" 搅拌时间: {original_stir_time/60:.1f}分钟 → {parsed_time/60:.1f}分钟") f"沉降 {original_settling_time/60:.1f}min→{parsed_settling_time/60:.1f}min")
debug_print(f" 沉降时间: {original_settling_time/60:.1f}分钟 → {parsed_settling_time/60:.1f}分钟")
# 🎊 总结 debug_print(f"搅拌协议生成完成: {vessel_display}, {stir_speed}RPM, "
debug_print("🎊" * 20) f"{parsed_time}s, 沉降{parsed_settling_time}s, 总{(parsed_time + parsed_settling_time)/60:.1f}min")
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)
return action_sequence return action_sequence
@@ -297,21 +196,16 @@ def generate_start_stir_protocol(
"sample_id": "", "sample_id": "",
"type": "" "type": ""
} }
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}") debug_print(f"构建了基本的vessel Resource对象: {vessel}")
debug_print("🔄 开始生成启动搅拌协议修复vessel参数") debug_print(f"启动搅拌协议: vessel={vessel_id}, speed={stir_speed}RPM, purpose='{purpose}'")
debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})")
debug_print(f"🌪️ speed: {stir_speed} RPM")
debug_print(f"🎯 purpose: {purpose}")
# 基础验证 # 基础验证
if not vessel_id or vessel_id not in G.nodes(): if not vessel_id or vessel_id not in G.nodes():
debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效") raise ValueError("vessel 参数无效")
# 参数修正 # 参数修正
if stir_speed < 10.0 or stir_speed > 1500.0: if stir_speed < 10.0 or stir_speed > 1500.0:
debug_print(f"⚠️ 搅拌速度修正: {stir_speed} → 300 RPM 🌪️")
stir_speed = 300.0 stir_speed = 300.0
# 查找设备 # 查找设备
@@ -329,7 +223,7 @@ def generate_start_stir_protocol(
} }
}] }]
debug_print(f"启动搅拌协议生成完成 🎯") debug_print(f"启动搅拌协议生成完成: {stirrer_id}")
return action_sequence return action_sequence
def generate_stop_stir_protocol( def generate_stop_stir_protocol(
@@ -364,14 +258,12 @@ def generate_stop_stir_protocol(
"sample_id": "", "sample_id": "",
"type": "" "type": ""
} }
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}") debug_print(f"构建了基本的vessel Resource对象: {vessel}")
debug_print("🛑 开始生成停止搅拌协议(修复vessel参数)✨") debug_print(f"停止搅拌协议: vessel={vessel_id}")
debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})")
# 基础验证 # 基础验证
if not vessel_id or vessel_id not in G.nodes(): if not vessel_id or vessel_id not in G.nodes():
debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效") raise ValueError("vessel 参数无效")
# 查找设备 # 查找设备
@@ -387,10 +279,10 @@ def generate_stop_stir_protocol(
} }
}] }]
debug_print(f"停止搅拌协议生成完成 🎯") debug_print(f"停止搅拌协议生成完成: {stirrer_id}")
return action_sequence return action_sequence
# 🔧 新增:便捷函数 # 便捷函数
def stir_briefly(G: nx.DiGraph, vessel: Union[str, dict], def stir_briefly(G: nx.DiGraph, vessel: Union[str, dict],
speed: float = 300.0) -> List[Dict[str, Any]]: speed: float = 300.0) -> List[Dict[str, Any]]:
"""短时间搅拌30秒""" """短时间搅拌30秒"""

View File

@@ -4,19 +4,14 @@ import logging
import re import re
from .utils.unit_parser import parse_time_input, parse_volume_input 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__) logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
logger.info(f"[WASH_SOLID] {message}")
def find_solvent_source(G: nx.DiGraph, solvent: str) -> str: def find_solvent_source(G: nx.DiGraph, solvent: str) -> str:
"""查找溶剂源(精简版)""" """查找溶剂源"""
debug_print(f"🔍 查找溶剂源: {solvent}")
# 简化搜索列表
search_patterns = [ search_patterns = [
f"flask_{solvent}", f"bottle_{solvent}", f"reagent_{solvent}", f"flask_{solvent}", f"bottle_{solvent}", f"reagent_{solvent}",
"liquid_reagent_bottle_1", "flask_1", "solvent_bottle" "liquid_reagent_bottle_1", "flask_1", "solvent_bottle"
@@ -24,179 +19,40 @@ def find_solvent_source(G: nx.DiGraph, solvent: str) -> str:
for pattern in search_patterns: for pattern in search_patterns:
if pattern in G.nodes(): if pattern in G.nodes():
debug_print(f"🎉 找到溶剂源: {pattern}") debug_print(f"找到溶剂源: {pattern}")
return pattern return pattern
debug_print(f"⚠️ 使用默认溶剂源: flask_{solvent}") debug_print(f"使用默认溶剂源: flask_{solvent}")
return f"flask_{solvent}" return f"flask_{solvent}"
def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str: 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(): if filtrate_vessel and filtrate_vessel in G.nodes():
debug_print(f"✅ 使用指定容器: {filtrate_vessel}")
return filtrate_vessel return filtrate_vessel
# 简化搜索列表
default_vessels = ["waste_workup", "filtrate_vessel", "flask_1", "collection_bottle_1"] default_vessels = ["waste_workup", "filtrate_vessel", "flask_1", "collection_bottle_1"]
for vessel in default_vessels: for vessel in default_vessels:
if vessel in G.nodes(): if vessel in G.nodes():
debug_print(f"🎉 找到滤液容器: {vessel}") debug_print(f"找到滤液容器: {vessel}")
return vessel return vessel
debug_print(f"⚠️ 使用默认滤液容器: waste_workup")
return "waste_workup" return "waste_workup"
def extract_vessel_id(vessel: Union[str, dict]) -> str: def extract_vessel_id(vessel) -> str:
""" """从vessel参数中提取vessel_id兼容 str / dict / ResourceDictInstance"""
从vessel参数中提取vessel_id return get_resource_id(vessel)
Args: def get_vessel_display_info(vessel) -> str:
vessel: vessel字典或vessel_id字符串 """获取容器的显示信息(用于日志),兼容 str / dict / ResourceDictInstance"""
return get_resource_display_info(vessel)
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 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 generate_wash_solid_protocol( def generate_wash_solid_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: Union[str, dict], # 🔧 修改支持vessel字典 vessel: Union[str, dict],
solvent: str, solvent: str,
volume: Union[float, str] = "50", volume: Union[float, str] = "50",
filtrate_vessel: Union[str, dict] = "", # 🔧 修改支持vessel字典 filtrate_vessel: Union[str, dict] = "",
temp: float = 25.0, temp: float = 25.0,
stir: bool = False, stir: bool = False,
stir_speed: float = 0.0, stir_speed: float = 0.0,
@@ -232,101 +88,73 @@ def generate_wash_solid_protocol(
List[Dict[str, Any]]: 固体清洗操作的动作序列 List[Dict[str, Any]]: 固体清洗操作的动作序列
""" """
# 🔧 核心修改从vessel参数中提取vessel_id
vessel_id = extract_vessel_id(vessel) vessel_id = extract_vessel_id(vessel)
vessel_display = get_vessel_display_info(vessel) vessel_display = get_vessel_display_info(vessel)
# 🔧 处理filtrate_vessel参数
filtrate_vessel_id = extract_vessel_id(filtrate_vessel) if filtrate_vessel else "" filtrate_vessel_id = extract_vessel_id(filtrate_vessel) if filtrate_vessel else ""
debug_print("🧼" * 20) debug_print(f"开始生成固体清洗协议: vessel={vessel_id}, solvent={solvent}, volume={volume}, repeats={repeats}")
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("🔍 记录清洗前容器状态...")
if isinstance(vessel, dict): if isinstance(vessel, dict):
original_volume = get_vessel_liquid_volume(vessel) original_volume = get_resource_liquid_volume(vessel)
debug_print(f"📊 清洗前液体体积: {original_volume:.2f}mL")
else: else:
original_volume = 0.0 original_volume = 0.0
debug_print(f"📊 vessel为字符串格式无法获取体积信息")
# 📋 快速验证 # 快速验证
if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id if not vessel_id or vessel_id not in G.nodes():
debug_print("❌ 容器验证失败! 😱")
raise ValueError("vessel 参数无效") raise ValueError("vessel 参数无效")
if not solvent: if not solvent:
debug_print("❌ 溶剂不能为空! 😱")
raise ValueError("solvent 参数不能为空") raise ValueError("solvent 参数不能为空")
debug_print("✅ 基础验证通过 🎯") # 参数解析
# 🔄 参数解析
debug_print("📍 步骤1: 参数解析... ⚡")
final_volume = parse_volume_input(volume, volume_spec, mass) final_volume = parse_volume_input(volume, volume_spec, mass)
final_time = parse_time_input(time) final_time = parse_time_input(time)
# 重复次数处理(简化) # 重复次数处理
if repeats_spec: if repeats_spec:
spec_map = {'few': 2, 'several': 3, 'many': 4, 'thorough': 5} 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) final_repeats = next((v for k, v in spec_map.items() if k in repeats_spec.lower()), repeats)
else: else:
final_repeats = max(1, min(repeats, 5)) # 限制1-5次 final_repeats = max(1, min(repeats, 5))
# 🕐 模拟时间优化 # 模拟时间优化
debug_print(" ⏱️ 模拟时间优化...")
original_time = final_time original_time = final_time
if final_time > 60.0: if final_time > 60.0:
final_time = 60.0 # 限制最长60秒 final_time = 60.0
debug_print(f" 🎮 时间优化: {original_time}s {final_time}s") debug_print(f"时间优化: {original_time}s -> {final_time}s")
# 参数修正 # 参数修正
temp = max(25.0, min(temp, 80.0)) # 温度范围25-80°C temp = max(25.0, min(temp, 80.0))
stir_speed = max(0.0, min(stir_speed, 300.0)) if stir else 0.0 # 速度范围0-300 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}") debug_print(f"最终参数: 体积={final_volume}mL, 时间={final_time}s, 重复={final_repeats}")
# 🔍 查找设备 # 查找设备
debug_print("📍 步骤2: 查找设备... 🔍")
try: try:
solvent_source = find_solvent_source(G, solvent) solvent_source = find_solvent_source(G, solvent)
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel_id) 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: except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"设备查找失败: {str(e)}") raise ValueError(f"设备查找失败: {str(e)}")
# 🚀 生成动作序列 # 生成动作序列
debug_print("📍 步骤3: 生成清洗动作... 🧼")
action_sequence = [] action_sequence = []
# 🔧 新增:体积变化跟踪变量
current_volume = original_volume current_volume = original_volume
total_solvent_used = 0.0 total_solvent_used = 0.0
for cycle in range(final_repeats): for cycle in range(final_repeats):
debug_print(f" 🔄 {cycle+1}/{final_repeats}次清洗...") debug_print(f"{cycle+1}/{final_repeats}次清洗")
# 1. 转移溶剂 # 1. 转移溶剂
try: try:
from .pump_protocol import generate_pump_protocol_with_rinsing from .pump_protocol import generate_pump_protocol_with_rinsing
debug_print(f" 💧 添加溶剂: {final_volume}mL {solvent}")
transfer_actions = generate_pump_protocol_with_rinsing( transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=solvent_source, from_vessel=solvent_source,
to_vessel=vessel_id, # 🔧 使用 vessel_id to_vessel=vessel_id,
volume=final_volume, volume=final_volume,
amount="", amount="",
time=0.0, time=0.0,
@@ -341,9 +169,7 @@ def generate_wash_solid_protocol(
if transfer_actions: if transfer_actions:
action_sequence.extend(transfer_actions) action_sequence.extend(transfer_actions)
debug_print(f" ✅ 转移动作: {len(transfer_actions)}个 🚚")
# 🔧 新增:更新体积 - 添加溶剂后
current_volume += final_volume current_volume += final_volume
total_solvent_used += final_volume total_solvent_used += final_volume
@@ -352,121 +178,90 @@ def generate_wash_solid_protocol(
f"{cycle+1}次清洗添加{final_volume}mL溶剂后") f"{cycle+1}次清洗添加{final_volume}mL溶剂后")
except Exception as e: except Exception as e:
debug_print(f"转移失败: {str(e)} 😞") debug_print(f"转移失败: {str(e)}")
# 2. 搅拌(如果需要) # 2. 搅拌(如果需要)
if stir and final_time > 0: if stir and final_time > 0:
debug_print(f" 🌪️ 搅拌: {final_time}s @ {stir_speed}RPM")
stir_action = { stir_action = {
"device_id": "stirrer_1", "device_id": "stirrer_1",
"action_name": "stir", "action_name": "stir",
"action_kwargs": { "action_kwargs": {
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "vessel": {"id": vessel_id},
"time": str(time), "time": str(time),
"stir_time": final_time, "stir_time": final_time,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"settling_time": 10.0 # 🕐 缩短沉降时间 "settling_time": 10.0
} }
} }
action_sequence.append(stir_action) action_sequence.append(stir_action)
debug_print(f" ✅ 搅拌动作: {final_time}s, {stir_speed}RPM 🌪️")
# 3. 过滤 # 3. 过滤
debug_print(f" 🌊 过滤到: {actual_filtrate_vessel}")
filter_action = { filter_action = {
"device_id": "filter_1", "device_id": "filter_1",
"action_name": "filter", "action_name": "filter",
"action_kwargs": { "action_kwargs": {
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id "vessel": {"id": vessel_id},
"filtrate_vessel": actual_filtrate_vessel, "filtrate_vessel": actual_filtrate_vessel,
"temp": temp, "temp": temp,
"volume": final_volume "volume": final_volume
} }
} }
action_sequence.append(filter_action) action_sequence.append(filter_action)
debug_print(f" ✅ 过滤动作: → {actual_filtrate_vessel} 🌊")
# 🔧 新增:更新体积 - 过滤后(液体被滤除) # 更新体积 - 过滤后
# 假设滤液完全被移除,固体残留在容器中 filtered_volume = current_volume * 0.9
filtered_volume = current_volume * 0.9 # 假设90%的液体被过滤掉
current_volume = current_volume - filtered_volume current_volume = current_volume - filtered_volume
if isinstance(vessel, dict): if isinstance(vessel, dict):
update_vessel_volume(vessel, G, current_volume, update_vessel_volume(vessel, G, current_volume,
f"{cycle+1}次清洗过滤后") f"{cycle+1}次清洗过滤后")
# 4. 等待(缩短时间) # 4. 等待
wait_time = 5.0 # 🕐 缩短等待时间10s → 5s wait_time = 5.0
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": wait_time} "action_kwargs": {"time": wait_time}
}) })
debug_print(f" ✅ 等待: {wait_time}s ⏰")
# 🔧 新增:清洗完成后的最终状态报告 # 最终状态
if isinstance(vessel, dict): if isinstance(vessel, dict):
final_volume_vessel = get_vessel_liquid_volume(vessel) final_volume_vessel = get_resource_liquid_volume(vessel)
else: else:
final_volume_vessel = current_volume final_volume_vessel = current_volume
# 🎊 总结 debug_print(f"固体清洗协议生成完成: {len(action_sequence)} 个动作, {final_repeats}次清洗, 溶剂总用量={total_solvent_used:.2f}mL")
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)
return action_sequence return action_sequence
# 🔧 新增:便捷函数 # 便捷函数
def wash_with_water(G: nx.DiGraph, vessel: Union[str, dict], def wash_with_water(G: nx.DiGraph, vessel: Union[str, dict],
volume: Union[float, str] = "50", volume: Union[float, str] = "50",
repeats: int = 2) -> List[Dict[str, Any]]: 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) return generate_wash_solid_protocol(G, vessel, "water", volume=volume, repeats=repeats)
def wash_with_ethanol(G: nx.DiGraph, vessel: Union[str, dict], def wash_with_ethanol(G: nx.DiGraph, vessel: Union[str, dict],
volume: Union[float, str] = "30", volume: Union[float, str] = "30",
repeats: int = 1) -> List[Dict[str, Any]]: 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) return generate_wash_solid_protocol(G, vessel, "ethanol", volume=volume, repeats=repeats)
def wash_with_acetone(G: nx.DiGraph, vessel: Union[str, dict], def wash_with_acetone(G: nx.DiGraph, vessel: Union[str, dict],
volume: Union[float, str] = "25", volume: Union[float, str] = "25",
repeats: int = 1) -> List[Dict[str, Any]]: 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) return generate_wash_solid_protocol(G, vessel, "acetone", volume=volume, repeats=repeats)
def wash_with_ether(G: nx.DiGraph, vessel: Union[str, dict], def wash_with_ether(G: nx.DiGraph, vessel: Union[str, dict],
volume: Union[float, str] = "40", volume: Union[float, str] = "40",
repeats: int = 2) -> List[Dict[str, Any]]: 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) 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], def wash_with_cold_solvent(G: nx.DiGraph, vessel: Union[str, dict],
solvent: str, volume: Union[float, str] = "30", solvent: str, volume: Union[float, str] = "30",
repeats: int = 1) -> List[Dict[str, Any]]: 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) temp=5.0, repeats=repeats)
@@ -474,8 +269,6 @@ def wash_with_hot_solvent(G: nx.DiGraph, vessel: Union[str, dict],
solvent: str, volume: Union[float, str] = "50", solvent: str, volume: Union[float, str] = "50",
repeats: int = 1) -> List[Dict[str, Any]]: 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) temp=60.0, repeats=repeats)
@@ -484,8 +277,6 @@ def wash_with_stirring(G: nx.DiGraph, vessel: Union[str, dict],
stir_time: Union[str, float] = "5 min", stir_time: Union[str, float] = "5 min",
repeats: int = 1) -> List[Dict[str, Any]]: 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, return generate_wash_solid_protocol(G, vessel, solvent, volume=volume,
stir=True, stir_speed=200.0, stir=True, stir_speed=200.0,
time=stir_time, repeats=repeats) time=stir_time, repeats=repeats)
@@ -493,23 +284,16 @@ def wash_with_stirring(G: nx.DiGraph, vessel: Union[str, dict],
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]]: 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) 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]]: 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) 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]]: 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 = [] action_sequence = []
for solvent in solvents: for solvent in solvents:
wash_actions = generate_wash_solid_protocol(G, vessel, solvent, wash_actions = generate_wash_solid_protocol(G, vessel, solvent,
@@ -521,28 +305,21 @@ def sequential_wash(G: nx.DiGraph, vessel: Union[str, dict],
# 测试函数 # 测试函数
def test_wash_solid_protocol(): def test_wash_solid_protocol():
"""测试固体清洗协议""" """测试固体清洗协议"""
debug_print("🧪 === WASH SOLID PROTOCOL 测试 ===") debug_print("=== WASH SOLID PROTOCOL 测试 ===")
# 测试vessel参数处理
debug_print("🔧 测试vessel参数处理...")
# 测试字典格式
vessel_dict = {"id": "filter_flask_1", "name": "过滤瓶1", vessel_dict = {"id": "filter_flask_1", "name": "过滤瓶1",
"data": {"liquid_volume": 25.0}} "data": {"liquid_volume": 25.0}}
vessel_id = extract_vessel_id(vessel_dict) vessel_id = extract_vessel_id(vessel_dict)
vessel_display = get_vessel_display_info(vessel_dict) vessel_display = get_vessel_display_info(vessel_dict)
volume = get_vessel_liquid_volume(vessel_dict) volume = get_resource_liquid_volume(vessel_dict)
debug_print(f" 字典格式: {vessel_dict}") debug_print(f"字典格式: ID={vessel_id}, 显示={vessel_display}, 体积={volume}mL")
debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}, 体积: {volume}mL")
# 测试字符串格式
vessel_str = "filter_flask_2" vessel_str = "filter_flask_2"
vessel_id = extract_vessel_id(vessel_str) vessel_id = extract_vessel_id(vessel_str)
vessel_display = get_vessel_display_info(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(f" → ID: {vessel_id}, 显示: {vessel_display}")
debug_print("测试完成 🎉") debug_print("测试完成")
if __name__ == "__main__": if __name__ == "__main__":
test_wash_solid_protocol() test_wash_solid_protocol()