action to resource & 0.9.12

This commit is contained in:
KCFeng425
2025-07-17 04:10:15 +08:00
parent f90be18926
commit 6b7564b9f9
53 changed files with 2526 additions and 761 deletions

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# 现阶段,需要安装 `unilabos_msgs` 包 # 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装 # 可以前往 Release 页面下载系统对应的包进行安装
conda install ros-humble-unilabos-msgs-0.9.11-xxxxx.tar.bz2 conda install ros-humble-unilabos-msgs-0.9.12-xxxxx.tar.bz2
# 安装PyLabRobot等前置 # 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot plr_repo git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -1,6 +1,6 @@
package: package:
name: ros-humble-unilabos-msgs name: ros-humble-unilabos-msgs
version: 0.9.11 version: 0.9.12
source: source:
path: ../../unilabos_msgs path: ../../unilabos_msgs
folder: ros-humble-unilabos-msgs/src/work folder: ros-humble-unilabos-msgs/src/work

View File

@@ -1,6 +1,6 @@
package: package:
name: unilabos name: unilabos
version: "0.9.11" version: "0.9.12"
source: source:
path: ../.. path: ../..

View File

@@ -4,7 +4,7 @@ package_name = 'unilabos'
setup( setup(
name=package_name, name=package_name,
version='0.9.11', version='0.9.12',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
install_requires=['setuptools'], install_requires=['setuptools'],

View File

@@ -311,7 +311,7 @@ def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
def generate_add_protocol( def generate_add_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:现在接收字典类型的 vessel
reagent: str, reagent: str,
# 🔧 修复:所有参数都用 Union 类型,支持字符串和数值 # 🔧 修复:所有参数都用 Union 类型,支持字符串和数值
volume: Union[str, float] = 0.0, volume: Union[str, float] = 0.0,
@@ -334,6 +334,7 @@ def generate_add_protocol(
生成添加试剂协议 - 修复版 生成添加试剂协议 - 修复版
支持所有XDL参数和单位 支持所有XDL参数和单位
- vessel: Resource类型字典包含id字段
- volume: "2.7 mL", "2.67 mL", "?" 或数值 - volume: "2.7 mL", "2.67 mL", "?" 或数值
- mass: "19.3 g", "4.5 g" 或数值 - mass: "19.3 g", "4.5 g" 或数值
- time: "1 h", "20 min" 或数值(秒) - time: "1 h", "20 min" 或数值(秒)
@@ -343,11 +344,19 @@ def generate_add_protocol(
- equiv: "1.1" - equiv: "1.1"
- ratio: "?", "1:1" - ratio: "?", "1:1"
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
# 🔧 修改:更新容器的液体体积(假设有 liquid_volume 字段)
if "data" in vessel and "liquid_volume" in vessel["data"]:
if isinstance(vessel["data"]["liquid_volume"], list) and len(vessel["data"]["liquid_volume"]) > 0:
vessel["data"]["liquid_volume"][0] -= parse_volume_input(volume)
debug_print("=" * 60) debug_print("=" * 60)
debug_print("🚀 开始生成添加试剂协议") debug_print("🚀 开始生成添加试剂协议")
debug_print(f"📋 原始参数:") debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'") debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 🧪 reagent: '{reagent}'") debug_print(f" 🧪 reagent: '{reagent}'")
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})") debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})") debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
@@ -363,18 +372,18 @@ def generate_add_protocol(
# === 参数验证 === # === 参数验证 ===
debug_print("🔍 步骤1: 参数验证...") debug_print("🔍 步骤1: 参数验证...")
action_sequence.append(create_action_log(f"开始添加试剂 '{reagent}' 到容器 '{vessel}'", "🎬")) action_sequence.append(create_action_log(f"开始添加试剂 '{reagent}' 到容器 '{vessel_id}'", "🎬"))
if not vessel: if not vessel or not vessel_id:
debug_print("❌ vessel 参数不能为空") debug_print("❌ vessel 参数不能为空")
raise ValueError("vessel 参数不能为空") raise ValueError("vessel 参数不能为空")
if not reagent: if not reagent:
debug_print("❌ reagent 参数不能为空") debug_print("❌ reagent 参数不能为空")
raise ValueError("reagent 参数不能为空") raise ValueError("reagent 参数不能为空")
if vessel not in G.nodes(): if vessel_id not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中") debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中")
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
debug_print("✅ 基本参数验证通过") debug_print("✅ 基本参数验证通过")
@@ -432,7 +441,7 @@ def generate_add_protocol(
debug_print("🌪️ 准备启动搅拌...") debug_print("🌪️ 准备启动搅拌...")
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️")) action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
if stirrer_id: if 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)", "🔄"))
@@ -440,7 +449,7 @@ def generate_add_protocol(
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": f"准备添加固体 {reagent}" "purpose": f"准备添加固体 {reagent}"
} }
@@ -454,7 +463,7 @@ def generate_add_protocol(
# 固体加样 # 固体加样
add_kwargs = { add_kwargs = {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"reagent": reagent, "reagent": reagent,
"purpose": purpose, "purpose": purpose,
"event": event, "event": event,
@@ -509,7 +518,7 @@ def generate_add_protocol(
debug_print("🌪️ 准备启动搅拌...") debug_print("🌪️ 准备启动搅拌...")
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️")) action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
if stirrer_id: if 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)", "🔄"))
@@ -517,7 +526,7 @@ def generate_add_protocol(
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": f"准备添加液体 {reagent}" "purpose": f"准备添加液体 {reagent}"
} }
@@ -555,7 +564,7 @@ def generate_add_protocol(
pump_actions = generate_pump_protocol_with_rinsing( pump_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=reagent_vessel, from_vessel=reagent_vessel,
to_vessel=vessel, to_vessel=vessel_id, # 🔧 使用 vessel_id
volume=final_volume, volume=final_volume,
amount=amount, amount=amount,
time=final_time, time=final_time,
@@ -594,7 +603,7 @@ def generate_add_protocol(
debug_print(f"📋 处理总结:") debug_print(f"📋 处理总结:")
debug_print(f" 🧪 试剂: {reagent}") debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" {add_emoji} 添加类型: {add_type}") debug_print(f" {add_emoji} 添加类型: {add_type}")
debug_print(f" 🥼 目标容器: {vessel}") debug_print(f" 🥼 目标容器: {vessel_id}")
if is_liquid: if is_liquid:
debug_print(f" 📏 体积: {final_volume}mL") debug_print(f" 📏 体积: {final_volume}mL")
if is_solid: if is_solid:
@@ -603,7 +612,7 @@ def generate_add_protocol(
debug_print("=" * 60) debug_print("=" * 60)
# 添加完成日志 # 添加完成日志
summary_msg = f"试剂添加协议完成: {reagent}{vessel}" summary_msg = f"试剂添加协议完成: {reagent}{vessel_id}"
if is_liquid: if is_liquid:
summary_msg += f" ({final_volume}mL)" summary_msg += f" ({final_volume}mL)"
if is_solid: if is_solid:
@@ -614,11 +623,13 @@ def generate_add_protocol(
return action_sequence return action_sequence
# === 便捷函数 === # === 便捷函数 ===
# 🔧 修改便捷函数的参数类型
def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float], def add_liquid_volume(G: nx.DiGraph, vessel: dict, reagent: str, volume: Union[str, float],
time: Union[str, float] = 0.0, rate_spec: str = "") -> List[Dict[str, Any]]: time: Union[str, float] = 0.0, rate_spec: str = "") -> List[Dict[str, Any]]:
"""添加指定体积的液体试剂""" """添加指定体积的液体试剂"""
debug_print(f"💧 快速添加液体: {reagent} ({volume}) → {vessel}") vessel_id = vessel["id"]
debug_print(f"💧 快速添加液体: {reagent} ({volume}) → {vessel_id}")
return generate_add_protocol( return generate_add_protocol(
G, vessel, reagent, G, vessel, reagent,
volume=volume, volume=volume,
@@ -626,30 +637,33 @@ def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[st
rate_spec=rate_spec rate_spec=rate_spec
) )
def add_solid_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float], def add_solid_mass(G: nx.DiGraph, vessel: dict, reagent: str, mass: Union[str, float],
event: str = "") -> List[Dict[str, Any]]: event: str = "") -> List[Dict[str, Any]]:
"""添加指定质量的固体试剂""" """添加指定质量的固体试剂"""
debug_print(f"🧂 快速添加固体: {reagent} ({mass}) → {vessel}") vessel_id = vessel["id"]
debug_print(f"🧂 快速添加固体: {reagent} ({mass}) → {vessel_id}")
return generate_add_protocol( return generate_add_protocol(
G, vessel, reagent, G, vessel, reagent,
mass=mass, mass=mass,
event=event event=event
) )
def add_solid_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str, def add_solid_moles(G: nx.DiGraph, vessel: dict, reagent: str, mol: str,
event: str = "") -> List[Dict[str, Any]]: event: str = "") -> List[Dict[str, Any]]:
"""按摩尔数添加固体试剂""" """按摩尔数添加固体试剂"""
debug_print(f"🧬 按摩尔数添加固体: {reagent} ({mol}) → {vessel}") vessel_id = vessel["id"]
debug_print(f"🧬 按摩尔数添加固体: {reagent} ({mol}) → {vessel_id}")
return generate_add_protocol( return generate_add_protocol(
G, vessel, reagent, G, vessel, reagent,
mol=mol, mol=mol,
event=event event=event
) )
def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float], def add_dropwise_liquid(G: nx.DiGraph, vessel: dict, reagent: str, volume: Union[str, float],
time: Union[str, float] = "20 min", event: str = "") -> List[Dict[str, Any]]: time: Union[str, float] = "20 min", event: str = "") -> List[Dict[str, Any]]:
"""滴加液体试剂""" """滴加液体试剂"""
debug_print(f"💧 滴加液体: {reagent} ({volume}) → {vessel} (用时: {time})") vessel_id = vessel["id"]
debug_print(f"💧 滴加液体: {reagent} ({volume}) → {vessel_id} (用时: {time})")
return generate_add_protocol( return generate_add_protocol(
G, vessel, reagent, G, vessel, reagent,
volume=volume, volume=volume,
@@ -658,10 +672,11 @@ def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[
event=event event=event
) )
def add_portionwise_solid(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float], def add_portionwise_solid(G: nx.DiGraph, vessel: dict, reagent: str, mass: Union[str, float],
time: Union[str, float] = "1 h", event: str = "") -> List[Dict[str, Any]]: time: Union[str, float] = "1 h", event: str = "") -> List[Dict[str, Any]]:
"""分批添加固体试剂""" """分批添加固体试剂"""
debug_print(f"🧂 分批添加固体: {reagent} ({mass}) → {vessel} (用时: {time})") vessel_id = vessel["id"]
debug_print(f"🧂 分批添加固体: {reagent} ({mass}) → {vessel_id} (用时: {time})")
return generate_add_protocol( return generate_add_protocol(
G, vessel, reagent, G, vessel, reagent,
mass=mass, mass=mass,

View File

@@ -216,7 +216,7 @@ def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume
def generate_adjust_ph_protocol( def generate_adjust_ph_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
ph_value: float, ph_value: float,
reagent: str, reagent: str,
**kwargs **kwargs
@@ -226,7 +226,7 @@ def generate_adjust_ph_protocol(
Args: Args:
G: 有向图,节点为容器和设备 G: 有向图,节点为容器和设备
vessel: 目标容器需要调节pH的容器 vessel: 目标容器字典需要调节pH的容器
ph_value: 目标pH值从XDL传入 ph_value: 目标pH值从XDL传入
reagent: 酸碱试剂名称从XDL传入 reagent: 酸碱试剂名称从XDL传入
**kwargs: 其他可选参数,使用默认值 **kwargs: 其他可选参数,使用默认值
@@ -235,10 +235,13 @@ def generate_adjust_ph_protocol(
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("=" * 60) debug_print("=" * 60)
debug_print("🧪 开始生成pH调节协议") debug_print("🧪 开始生成pH调节协议")
debug_print(f"📋 原始参数:") debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'") debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 📊 ph_value: {ph_value}") debug_print(f" 📊 ph_value: {ph_value}")
debug_print(f" 🧪 reagent: '{reagent}'") debug_print(f" 🧪 reagent: '{reagent}'")
debug_print(f" 📦 kwargs: {kwargs}") debug_print(f" 📦 kwargs: {kwargs}")
@@ -262,14 +265,14 @@ def generate_adjust_ph_protocol(
# 开始处理 # 开始处理
action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪")) action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪"))
action_sequence.append(create_action_log(f"目标容器: {vessel}", "🥼")) action_sequence.append(create_action_log(f"目标容器: {vessel_id}", "🥼"))
action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️")) action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️"))
# 1. 验证目标容器存在 # 1. 验证目标容器存在
debug_print(f"🔍 步骤1: 验证目标容器...") debug_print(f"🔍 步骤1: 验证目标容器...")
if vessel not in G.nodes(): if vessel_id not in G.nodes():
debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中") debug_print(f"❌ 目标容器 '{vessel_id}' 不存在于系统中")
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中") raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
debug_print(f"✅ 目标容器验证通过") debug_print(f"✅ 目标容器验证通过")
action_sequence.append(create_action_log("目标容器验证通过", "")) action_sequence.append(create_action_log("目标容器验证通过", ""))
@@ -293,7 +296,7 @@ def generate_adjust_ph_protocol(
action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮")) action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮"))
# 获取目标容器的体积信息 # 获取目标容器的体积信息
vessel_data = G.nodes[vessel].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) # 默认100mL
debug_print(f"📏 容器最大体积: {vessel_volume}mL") debug_print(f"📏 容器最大体积: {vessel_volume}mL")
@@ -310,13 +313,13 @@ def generate_adjust_ph_protocol(
action_sequence.append(create_action_log("验证转移路径...", "🛤️")) action_sequence.append(create_action_log("验证转移路径...", "🛤️"))
try: try:
path = nx.shortest_path(G, source=reagent_vessel, target=vessel) path = nx.shortest_path(G, source=reagent_vessel, target=vessel_id)
debug_print(f"✅ 找到路径: {''.join(path)}") 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"❌ 无法找到转移路径") debug_print(f"❌ 无法找到转移路径")
action_sequence.append(create_action_log("转移路径不存在", "")) action_sequence.append(create_action_log("转移路径不存在", ""))
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径") raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
# 5. 搅拌器设置 # 5. 搅拌器设置
debug_print(f"🔍 步骤5: 搅拌器设置...") debug_print(f"🔍 步骤5: 搅拌器设置...")
@@ -325,7 +328,7 @@ def generate_adjust_ph_protocol(
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️")) action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
try: try:
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel_id)
if stirrer_id: if stirrer_id:
debug_print(f"✅ 找到搅拌器 {stirrer_id},启动搅拌") debug_print(f"✅ 找到搅拌器 {stirrer_id},启动搅拌")
@@ -335,7 +338,7 @@ def generate_adjust_ph_protocol(
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": f"pH调节: 启动搅拌,准备添加 {reagent}" "purpose": f"pH调节: 启动搅拌,准备添加 {reagent}"
} }
@@ -373,7 +376,7 @@ def generate_adjust_ph_protocol(
pump_actions = generate_pump_protocol_with_rinsing( pump_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=reagent_vessel, from_vessel=reagent_vessel,
to_vessel=vessel, to_vessel=vessel_id,
volume=volume, volume=volume,
amount="", amount="",
time=addition_time, time=addition_time,
@@ -390,6 +393,54 @@ def generate_adjust_ph_protocol(
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(create_action_log(f"试剂转移完成 ({len(pump_actions)} 个操作)", ""))
# 🔧 修复体积运算 - 试剂添加成功后更新容器液体体积
debug_print(f"🔧 更新容器液体体积...")
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
debug_print(f"📊 添加前容器体积: {current_volume}")
# 处理不同的体积数据格式
if isinstance(current_volume, list):
if len(current_volume) > 0:
# 增加体积(添加试剂)
vessel["data"]["liquid_volume"][0] += volume
debug_print(f"📊 添加后容器体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{volume:.2f}mL)")
else:
# 如果列表为空,创建新的体积记录
vessel["data"]["liquid_volume"] = [volume]
debug_print(f"📊 初始化容器体积: {volume:.2f}mL")
elif isinstance(current_volume, (int, float)):
# 直接数值类型
vessel["data"]["liquid_volume"] += volume
debug_print(f"📊 添加后容器体积: {vessel['data']['liquid_volume']:.2f}mL (+{volume:.2f}mL)")
else:
debug_print(f"⚠️ 未知的体积数据格式: {type(current_volume)}")
# 创建新的体积记录
vessel["data"]["liquid_volume"] = volume
else:
debug_print(f"📊 容器无液体体积数据,创建新记录: {volume:.2f}mL")
# 确保vessel有data字段
if "data" not in vessel:
vessel["data"] = {}
vessel["data"]["liquid_volume"] = volume
# 🔧 同时更新图中的容器数据
if vessel_id in G.nodes():
vessel_node_data = G.nodes[vessel_id].get('data', {})
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
if isinstance(current_node_volume, list):
if len(current_node_volume) > 0:
G.nodes[vessel_id]['data']['liquid_volume'][0] += volume
else:
G.nodes[vessel_id]['data']['liquid_volume'] = [volume]
else:
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + volume
debug_print(f"✅ 图节点体积数据已更新")
action_sequence.append(create_action_log(f"容器体积已更新 (+{volume:.2f}mL)", "📊"))
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)}", ""))
@@ -439,18 +490,18 @@ def generate_adjust_ph_protocol(
debug_print(f" 🧪 试剂: {reagent}") debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" 📏 体积: {volume:.2f}mL") debug_print(f" 📏 体积: {volume:.2f}mL")
debug_print(f" 📊 目标pH: {ph_value}") debug_print(f" 📊 目标pH: {ph_value}")
debug_print(f" 🥼 目标容器: {vessel}") debug_print(f" 🥼 目标容器: {vessel_id}")
debug_print("=" * 60) debug_print("=" * 60)
# 添加完成日志 # 添加完成日志
summary_msg = f"pH调节协议完成: {vessel} → pH {ph_value} (使用 {volume:.2f}mL {reagent})" summary_msg = f"pH调节协议完成: {vessel_id} → pH {ph_value} (使用 {volume:.2f}mL {reagent})"
action_sequence.append(create_action_log(summary_msg, "🎉")) action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence return action_sequence
def generate_adjust_ph_protocol_stepwise( def generate_adjust_ph_protocol_stepwise(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
ph_value: float, ph_value: float,
reagent: str, reagent: str,
max_volume: float = 10.0, max_volume: float = 10.0,
@@ -461,7 +512,7 @@ def generate_adjust_ph_protocol_stepwise(
Args: Args:
G: 网络图 G: 网络图
vessel: 目标容器 vessel: 目标容器字典
ph_value: 目标pH值 ph_value: 目标pH值
reagent: 酸碱试剂 reagent: 酸碱试剂
max_volume: 最大试剂体积 max_volume: 最大试剂体积
@@ -470,10 +521,13 @@ def generate_adjust_ph_protocol_stepwise(
Returns: Returns:
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("=" * 60) debug_print("=" * 60)
debug_print(f"🔄 开始分步pH调节") debug_print(f"🔄 开始分步pH调节")
debug_print(f"📋 分步参数:") debug_print(f"📋 分步参数:")
debug_print(f" 🥼 vessel: {vessel}") debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 📊 ph_value: {ph_value}") debug_print(f" 📊 ph_value: {ph_value}")
debug_print(f" 🧪 reagent: {reagent}") debug_print(f" 🧪 reagent: {reagent}")
debug_print(f" 📏 max_volume: {max_volume}mL") debug_print(f" 📏 max_volume: {max_volume}mL")
@@ -496,7 +550,7 @@ def generate_adjust_ph_protocol_stepwise(
# 生成单步协议 # 生成单步协议
step_actions = generate_adjust_ph_protocol( step_actions = generate_adjust_ph_protocol(
G=G, G=G,
vessel=vessel, vessel=vessel, # 🔧 直接传递vessel字典
ph_value=ph_value, ph_value=ph_value,
reagent=reagent, reagent=reagent,
volume=step_volume, volume=step_volume,
@@ -530,35 +584,38 @@ def generate_adjust_ph_protocol_stepwise(
# 便捷函数常用pH调节 # 便捷函数常用pH调节
def generate_acidify_protocol( def generate_acidify_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
target_ph: float = 2.0, target_ph: float = 2.0,
acid: str = "hydrochloric acid" acid: str = "hydrochloric acid"
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""酸化协议""" """酸化协议"""
debug_print(f"🍋 生成酸化协议: {vessel} → pH {target_ph} (使用 {acid})") vessel_id = vessel["id"]
debug_print(f"🍋 生成酸化协议: {vessel_id} → pH {target_ph} (使用 {acid})")
return generate_adjust_ph_protocol( return generate_adjust_ph_protocol(
G, vessel, target_ph, acid G, vessel, target_ph, acid
) )
def generate_basify_protocol( def generate_basify_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
target_ph: float = 12.0, target_ph: float = 12.0,
base: str = "sodium hydroxide" base: str = "sodium hydroxide"
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""碱化协议""" """碱化协议"""
debug_print(f"🧂 生成碱化协议: {vessel} → pH {target_ph} (使用 {base})") vessel_id = vessel["id"]
debug_print(f"🧂 生成碱化协议: {vessel_id} → pH {target_ph} (使用 {base})")
return generate_adjust_ph_protocol( return generate_adjust_ph_protocol(
G, vessel, target_ph, base G, vessel, target_ph, base
) )
def generate_neutralize_protocol( def generate_neutralize_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
reagent: str = "sodium hydroxide" reagent: str = "sodium hydroxide"
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""中和协议pH=7""" """中和协议pH=7"""
debug_print(f"⚖️ 生成中和协议: {vessel} → pH 7.0 (使用 {reagent})") vessel_id = vessel["id"]
debug_print(f"⚖️ 生成中和协议: {vessel_id} → pH 7.0 (使用 {reagent})")
return generate_adjust_ph_protocol( return generate_adjust_ph_protocol(
G, vessel, 7.0, reagent G, vessel, 7.0, reagent
) )

View File

@@ -145,7 +145,7 @@ def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
def generate_clean_vessel_protocol( def generate_clean_vessel_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
solvent: str, solvent: str,
volume: float, volume: float,
temp: float, temp: float,
@@ -165,7 +165,7 @@ def generate_clean_vessel_protocol(
Args: Args:
G: 有向图,节点为设备和容器,边为流体管道 G: 有向图,节点为设备和容器,边为流体管道
vessel: 要清洗的容器名称 vessel: 要清洗的容器字典包含id字段
solvent: 用于清洗的溶剂名称 solvent: 用于清洗的溶剂名称
volume: 每次清洗使用的溶剂体积 volume: 每次清洗使用的溶剂体积
temp: 清洗时的温度 temp: 清洗时的温度
@@ -178,20 +178,23 @@ def generate_clean_vessel_protocol(
ValueError: 当找不到必要的容器或设备时抛出异常 ValueError: 当找不到必要的容器或设备时抛出异常
Examples: Examples:
clean_protocol = generate_clean_vessel_protocol(G, "main_reactor", "water", 100.0, 60.0, 2) clean_protocol = generate_clean_vessel_protocol(G, {"id": "main_reactor"}, "water", 100.0, 60.0, 2)
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
action_sequence = [] action_sequence = []
print(f"CLEAN_VESSEL: 开始生成容器清洗协议") print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
print(f" - 目标容器: {vessel}") print(f" - 目标容器: {vessel} (ID: {vessel_id})")
print(f" - 清洗溶剂: {solvent}") print(f" - 清洗溶剂: {solvent}")
print(f" - 清洗体积: {volume} mL") print(f" - 清洗体积: {volume} mL")
print(f" - 清洗温度: {temp}°C") print(f" - 清洗温度: {temp}°C")
print(f" - 重复次数: {repeats}") print(f" - 重复次数: {repeats}")
# 验证目标容器存在 # 验证目标容器存在
if vessel not in G.nodes(): if vessel_id not in G.nodes():
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中") raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
# 查找溶剂容器 # 查找溶剂容器
try: try:
@@ -208,12 +211,23 @@ def generate_clean_vessel_protocol(
raise ValueError(f"无法找到废液容器: {str(e)}") raise ValueError(f"无法找到废液容器: {str(e)}")
# 查找加热设备(可选) # 查找加热设备(可选)
heatchill_id = find_connected_heatchill(G, vessel) heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
if heatchill_id: if heatchill_id:
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}") print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
else: else:
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗") print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
# 🔧 新增:记录清洗前的容器状态
print(f"CLEAN_VESSEL: 记录清洗前容器状态...")
original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume
print(f"CLEAN_VESSEL: 清洗前液体体积: {original_liquid_volume:.2f}mL")
# 第一步:如果需要加热且有加热设备,启动加热 # 第一步:如果需要加热且有加热设备,启动加热
if temp > 25.0 and heatchill_id: if temp > 25.0 and heatchill_id:
print(f"CLEAN_VESSEL: 启动加热至 {temp}°C") print(f"CLEAN_VESSEL: 启动加热至 {temp}°C")
@@ -221,7 +235,7 @@ def generate_clean_vessel_protocol(
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"temp": temp, "temp": temp,
"purpose": f"cleaning with {solvent}" "purpose": f"cleaning with {solvent}"
} }
@@ -240,18 +254,61 @@ def generate_clean_vessel_protocol(
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗") print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
# 2a. 使用 pump_protocol 将溶剂转移到目标容器 # 2a. 使用 pump_protocol 将溶剂转移到目标容器
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel}") print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel_id}")
try: try:
# 调用成熟的 pump_protocol 算法 # 调用成熟的 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, to_vessel=vessel_id, # 🔧 使用 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:
vessel["data"] = {}
if "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] += volume
print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{volume:.2f}mL)")
else:
vessel["data"]["liquid_volume"] = [volume]
print(f"CLEAN_VESSEL: 初始化清洗体积: {volume:.2f}mL")
elif isinstance(current_volume, (int, float)):
vessel["data"]["liquid_volume"] += volume
print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{volume:.2f}mL)")
else:
vessel["data"]["liquid_volume"] = volume
print(f"CLEAN_VESSEL: 重置体积为: {volume:.2f}mL")
else:
vessel["data"]["liquid_volume"] = volume
print(f"CLEAN_VESSEL: 创建新体积记录: {volume:.2f}mL")
# 🔧 同时更新图中的容器数据
if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {}
vessel_node_data = G.nodes[vessel_id]['data']
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
if isinstance(current_node_volume, list):
if len(current_node_volume) > 0:
G.nodes[vessel_id]['data']['liquid_volume'][0] += volume
else:
G.nodes[vessel_id]['data']['liquid_volume'] = [volume]
else:
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + volume
print(f"CLEAN_VESSEL: 图节点体积数据已更新")
except Exception as e: except Exception as e:
raise ValueError(f"无法将溶剂转移到容器: {str(e)}") raise ValueError(f"无法将溶剂转移到容器: {str(e)}")
@@ -265,18 +322,52 @@ 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} 转移到废液容器") print(f"CLEAN_VESSEL: 将清洗液从 {vessel_id} 转移到废液容器")
try: try:
# 调用成熟的 pump_protocol 算法 # 调用成熟的 pump_protocol 算法
remove_waste_actions = generate_pump_protocol( remove_waste_actions = generate_pump_protocol(
G=G, G=G,
from_vessel=vessel, from_vessel=vessel_id, # 🔧 使用 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"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] = max(0.0, vessel["data"]["liquid_volume"][0] - volume)
print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (-{volume:.2f}mL)")
else:
vessel["data"]["liquid_volume"] = [0.0]
print(f"CLEAN_VESSEL: 重置体积为0mL")
elif isinstance(current_volume, (int, float)):
vessel["data"]["liquid_volume"] = max(0.0, current_volume - volume)
print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume']:.2f}mL (-{volume:.2f}mL)")
else:
vessel["data"]["liquid_volume"] = 0.0
print(f"CLEAN_VESSEL: 重置体积为0mL")
# 🔧 同时更新图中的容器数据
if vessel_id in G.nodes():
vessel_node_data = G.nodes[vessel_id].get('data', {})
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
if isinstance(current_node_volume, list):
if len(current_node_volume) > 0:
G.nodes[vessel_id]['data']['liquid_volume'][0] = max(0.0, current_node_volume[0] - volume)
else:
G.nodes[vessel_id]['data']['liquid_volume'] = [0.0]
else:
G.nodes[vessel_id]['data']['liquid_volume'] = max(0.0, current_node_volume - volume)
print(f"CLEAN_VESSEL: 图节点体积数据已更新")
except Exception as e: except Exception as e:
raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}") raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}")
@@ -296,13 +387,24 @@ def generate_clean_vessel_protocol(
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "action_kwargs": {
"vessel": vessel "vessel": vessel_id # 🔧 使用 vessel_id
} }
} }
action_sequence.append(heatchill_stop_action) action_sequence.append(heatchill_stop_action)
print(f"CLEAN_VESSEL: 生成了 {len(action_sequence)} 个动作") # 🔧 新增:清洗完成后的状态报告
print(f"CLEAN_VESSEL: 清洗协议生成完成") final_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
final_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
final_liquid_volume = current_volume
print(f"CLEAN_VESSEL: 清洗完成")
print(f" - 清洗前体积: {original_liquid_volume:.2f}mL")
print(f" - 清洗后体积: {final_liquid_volume:.2f}mL")
print(f" - 生成了 {len(action_sequence)} 个动作")
return action_sequence return action_sequence
@@ -310,7 +412,7 @@ def generate_clean_vessel_protocol(
# 便捷函数:常用清洗方案 # 便捷函数:常用清洗方案
def generate_quick_clean_protocol( def generate_quick_clean_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
solvent: str = "water", solvent: str = "water",
volume: float = 100.0 volume: float = 100.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
@@ -320,7 +422,7 @@ def generate_quick_clean_protocol(
def generate_thorough_clean_protocol( def generate_thorough_clean_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
solvent: str = "water", solvent: str = "water",
volume: float = 150.0, volume: float = 150.0,
temp: float = 60.0 temp: float = 60.0
@@ -331,7 +433,7 @@ def generate_thorough_clean_protocol(
def generate_organic_clean_protocol( def generate_organic_clean_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
volume: float = 100.0 volume: float = 100.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""有机清洗:先用有机溶剂,再用水清洗""" """有机清洗:先用有机溶剂,再用水清洗"""

View File

@@ -408,7 +408,7 @@ def find_solid_dispenser(G: nx.DiGraph) -> str:
def generate_dissolve_protocol( def generate_dissolve_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
# 🔧 修复按照checklist.md的DissolveProtocol参数 # 🔧 修复按照checklist.md的DissolveProtocol参数
solvent: str = "", solvent: str = "",
volume: Union[str, float] = 0.0, volume: Union[str, float] = 0.0,
@@ -427,10 +427,11 @@ def generate_dissolve_protocol(
生成溶解操作的协议序列 - 增强版 生成溶解操作的协议序列 - 增强版
🔧 修复要点: 🔧 修复要点:
1. 添加action文件中的所有参数mass, mol, reagent, event 1. 修改vessel参数类型为dict并提取vessel_id
2. 使用 **kwargs 接受所有额外参数,防止 unexpected keyword argument 错误 2. 添加action文件中的所有参数mass, mol, reagent, event
3. 支持固体溶解和液体溶解两种模式 3. 使用 **kwargs 接受所有额外参数,防止 unexpected keyword argument 错误
4. 添加详细的emoji日志系统 4. 支持固体溶解和液体溶解两种模式
5. 添加详细的体积运算逻辑
支持两种溶解模式: 支持两种溶解模式:
1. 液体溶解:指定 solvent + volume使用pump protocol转移溶剂 1. 液体溶解:指定 solvent + volume使用pump protocol转移溶剂
@@ -444,10 +445,13 @@ def generate_dissolve_protocol(
- mol: "0.12 mol", "16.2 mmol" - mol: "0.12 mol", "16.2 mmol"
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("=" * 60) debug_print("=" * 60)
debug_print("🧪 开始生成溶解协议") debug_print("🧪 开始生成溶解协议")
debug_print(f"📋 原始参数:") debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'") debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 💧 solvent: '{solvent}'") debug_print(f" 💧 solvent: '{solvent}'")
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})") debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})") debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
@@ -463,19 +467,30 @@ def generate_dissolve_protocol(
# === 参数验证 === # === 参数验证 ===
debug_print("🔍 步骤1: 参数验证...") debug_print("🔍 步骤1: 参数验证...")
action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel}", "🎬")) action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel_id}", "🎬"))
if not vessel: if not vessel_id:
debug_print("❌ vessel 参数不能为空") debug_print("❌ vessel 参数不能为空")
raise ValueError("vessel 参数不能为空") raise ValueError("vessel 参数不能为空")
if vessel not in G.nodes(): if vessel_id not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中") debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中")
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
debug_print("✅ 基本参数验证通过") debug_print("✅ 基本参数验证通过")
action_sequence.append(create_action_log("参数验证通过", "")) action_sequence.append(create_action_log("参数验证通过", ""))
# 🔧 新增:记录溶解前的容器状态
debug_print("🔍 记录溶解前容器状态...")
original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume
debug_print(f"📊 溶解前液体体积: {original_liquid_volume:.2f}mL")
# === 🔧 关键修复:参数解析 === # === 🔧 关键修复:参数解析 ===
debug_print("🔍 步骤2: 参数解析...") debug_print("🔍 步骤2: 参数解析...")
action_sequence.append(create_action_log("正在解析溶解参数...", "🔍")) action_sequence.append(create_action_log("正在解析溶解参数...", "🔍"))
@@ -522,8 +537,8 @@ def generate_dissolve_protocol(
action_sequence.append(create_action_log("正在查找相关设备...", "🔍")) action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
# 查找加热搅拌器 # 查找加热搅拌器
heatchill_id = find_connected_heatchill(G, vessel) heatchill_id = find_connected_heatchill(G, vessel_id)
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel_id)
# 优先使用加热搅拌器,否则使用独立搅拌器 # 优先使用加热搅拌器,否则使用独立搅拌器
stir_device_id = heatchill_id or stirrer_id stir_device_id = heatchill_id or stirrer_id
@@ -557,7 +572,7 @@ def generate_dissolve_protocol(
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id,
"temp": final_temp, "temp": final_temp,
"purpose": f"溶解准备 - {event}" if event else "溶解准备" "purpose": f"溶解准备 - {event}" if event else "溶解准备"
} }
@@ -581,7 +596,7 @@ def generate_dissolve_protocol(
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": f"溶解搅拌 - {event}" if event else "溶解搅拌" "purpose": f"溶解搅拌 - {event}" if event else "溶解搅拌"
} }
@@ -606,7 +621,7 @@ def generate_dissolve_protocol(
# 固体加样 # 固体加样
add_kwargs = { add_kwargs = {
"vessel": vessel, "vessel": vessel_id,
"reagent": reagent or amount or "solid reagent", "reagent": reagent or amount or "solid reagent",
"purpose": f"溶解固体试剂 - {event}" if event else "溶解固体试剂", "purpose": f"溶解固体试剂 - {event}" if event else "溶解固体试剂",
"event": event "event": event
@@ -628,6 +643,12 @@ def generate_dissolve_protocol(
debug_print(f"✅ 固体加样完成") 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", "📊"))
else: else:
debug_print("⚠️ 未找到固体加样器,跳过固体添加") debug_print("⚠️ 未找到固体加样器,跳过固体添加")
action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", "")) action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", ""))
@@ -659,7 +680,7 @@ def generate_dissolve_protocol(
pump_actions = generate_pump_protocol_with_rinsing( pump_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=solvent_vessel, from_vessel=solvent_vessel,
to_vessel=vessel, to_vessel=vessel_id,
volume=final_volume, volume=final_volume,
amount=amount, amount=amount,
time=0.0, # 不在pump level控制时间 time=0.0, # 不在pump level控制时间
@@ -679,6 +700,52 @@ def generate_dissolve_protocol(
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(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", ""))
# 🔧 新增:液体溶解体积运算 - 添加溶剂后更新容器体积
debug_print(f"🔧 更新容器液体体积 - 添加溶剂 {final_volume:.2f}mL")
# 确保vessel有data字段
if "data" not in vessel:
vessel["data"] = {}
if "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] += final_volume
debug_print(f"📊 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{final_volume:.2f}mL)")
else:
vessel["data"]["liquid_volume"] = [final_volume]
debug_print(f"📊 初始化溶解体积: {final_volume:.2f}mL")
elif isinstance(current_volume, (int, float)):
vessel["data"]["liquid_volume"] += final_volume
debug_print(f"📊 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{final_volume:.2f}mL)")
else:
vessel["data"]["liquid_volume"] = final_volume
debug_print(f"📊 重置体积为: {final_volume:.2f}mL")
else:
vessel["data"]["liquid_volume"] = final_volume
debug_print(f"📊 创建新体积记录: {final_volume:.2f}mL")
# 🔧 同时更新图中的容器数据
if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {}
vessel_node_data = G.nodes[vessel_id]['data']
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
if isinstance(current_node_volume, list):
if len(current_node_volume) > 0:
G.nodes[vessel_id]['data']['liquid_volume'][0] += final_volume
else:
G.nodes[vessel_id]['data']['liquid_volume'] = [final_volume]
else:
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + final_volume
debug_print(f"✅ 图节点体积数据已更新")
action_sequence.append(create_action_log(f"容器体积已更新 (+{final_volume:.2f}mL)", "📊"))
# 溶剂添加后等待 # 溶剂添加后等待
action_sequence.append(create_action_log("溶剂添加后短暂等待...", "")) action_sequence.append(create_action_log("溶剂添加后短暂等待...", ""))
action_sequence.append({ action_sequence.append({
@@ -700,7 +767,7 @@ def generate_dissolve_protocol(
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill", "action_name": "heat_chill",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id,
"temp": final_temp, "temp": final_temp,
"time": final_time, "time": final_time,
"stir": True, "stir": True,
@@ -718,7 +785,7 @@ def generate_dissolve_protocol(
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "stir", "action_name": "stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id,
"stir_time": final_time, "stir_time": final_time,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"settling_time": 0, "settling_time": 0,
@@ -744,7 +811,7 @@ def generate_dissolve_protocol(
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "action_kwargs": {
"vessel": vessel "vessel": vessel_id
} }
} }
action_sequence.append(stop_action) action_sequence.append(stop_action)
@@ -761,12 +828,21 @@ def generate_dissolve_protocol(
} }
}) })
# 🔧 新增:溶解完成后的状态报告
final_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
final_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
final_liquid_volume = current_volume
# === 最终结果 === # === 最终结果 ===
debug_print("=" * 60) debug_print("=" * 60)
debug_print(f"🎉 溶解协议生成完成") debug_print(f"🎉 溶解协议生成完成")
debug_print(f"📊 协议统计:") debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}") debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" 🥼 容器: {vessel}") debug_print(f" 🥼 容器: {vessel_id}")
debug_print(f" {dissolve_emoji} 溶解类型: {dissolve_type}") debug_print(f" {dissolve_emoji} 溶解类型: {dissolve_type}")
if is_liquid_dissolve: if is_liquid_dissolve:
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)") debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
@@ -776,10 +852,12 @@ def generate_dissolve_protocol(
debug_print(f" 🧬 摩尔: {mol}") debug_print(f" 🧬 摩尔: {mol}")
debug_print(f" 🌡️ 温度: {final_temp}°C") debug_print(f" 🌡️ 温度: {final_temp}°C")
debug_print(f" ⏱️ 时间: {final_time}s") debug_print(f" ⏱️ 时间: {final_time}s")
debug_print(f" 📊 溶解前体积: {original_liquid_volume:.2f}mL")
debug_print(f" 📊 溶解后体积: {final_liquid_volume:.2f}mL")
debug_print("=" * 60) debug_print("=" * 60)
# 添加完成日志 # 添加完成日志
summary_msg = f"溶解协议完成: {vessel}" summary_msg = f"溶解协议完成: {vessel_id}"
if is_liquid_dissolve: if is_liquid_dissolve:
summary_msg += f" (使用 {final_volume}mL {solvent})" summary_msg += f" (使用 {final_volume}mL {solvent})"
if is_solid_dissolve: if is_solid_dissolve:
@@ -789,12 +867,15 @@ def generate_dissolve_protocol(
return action_sequence return action_sequence
# === 便捷函数 ===
def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float], # === 便捷函数 ===
# 🔧 修改便捷函数的参数类型
def dissolve_solid_by_mass(G: nx.DiGraph, vessel: dict, reagent: str, mass: Union[str, float],
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]: temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
"""按质量溶解固体""" """按质量溶解固体"""
debug_print(f"🧂 快速固体溶解: {reagent} ({mass}) → {vessel}") vessel_id = vessel["id"]
debug_print(f"🧂 快速固体溶解: {reagent} ({mass}) → {vessel_id}")
return generate_dissolve_protocol( return generate_dissolve_protocol(
G, vessel, G, vessel,
mass=mass, mass=mass,
@@ -803,10 +884,11 @@ def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union
time=time time=time
) )
def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str, def dissolve_solid_by_moles(G: nx.DiGraph, vessel: dict, reagent: str, mol: str,
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]: temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
"""按摩尔数溶解固体""" """按摩尔数溶解固体"""
debug_print(f"🧬 按摩尔数溶解固体: {reagent} ({mol}) → {vessel}") vessel_id = vessel["id"]
debug_print(f"🧬 按摩尔数溶解固体: {reagent} ({mol}) → {vessel_id}")
return generate_dissolve_protocol( return generate_dissolve_protocol(
G, vessel, G, vessel,
mol=mol, mol=mol,
@@ -815,10 +897,11 @@ def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
time=time time=time
) )
def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float], def dissolve_with_solvent(G: nx.DiGraph, vessel: dict, solvent: str, volume: Union[str, float],
temp: Union[str, float] = 25.0, time: Union[str, float] = "5 min") -> List[Dict[str, Any]]: temp: Union[str, float] = 25.0, time: Union[str, float] = "5 min") -> List[Dict[str, Any]]:
"""用溶剂溶解""" """用溶剂溶解"""
debug_print(f"💧 溶剂溶解: {solvent} ({volume}) → {vessel}") vessel_id = vessel["id"]
debug_print(f"💧 溶剂溶解: {solvent} ({volume}) → {vessel_id}")
return generate_dissolve_protocol( return generate_dissolve_protocol(
G, vessel, G, vessel,
solvent=solvent, solvent=solvent,
@@ -827,9 +910,10 @@ def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
time=time time=time
) )
def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float]) -> List[Dict[str, Any]]: def dissolve_at_room_temp(G: nx.DiGraph, vessel: dict, solvent: str, volume: Union[str, float]) -> List[Dict[str, Any]]:
"""室温溶解""" """室温溶解"""
debug_print(f"🌡️ 室温溶解: {solvent} ({volume}) → {vessel}") vessel_id = vessel["id"]
debug_print(f"🌡️ 室温溶解: {solvent} ({volume}) → {vessel_id}")
return generate_dissolve_protocol( return generate_dissolve_protocol(
G, vessel, G, vessel,
solvent=solvent, solvent=solvent,
@@ -838,10 +922,11 @@ def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
time="5 min" time="5 min"
) )
def dissolve_with_heating(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float], def dissolve_with_heating(G: nx.DiGraph, vessel: dict, solvent: str, volume: Union[str, float],
temp: Union[str, float] = "60 °C", time: Union[str, float] = "15 min") -> List[Dict[str, Any]]: temp: Union[str, float] = "60 °C", time: Union[str, float] = "15 min") -> List[Dict[str, Any]]:
"""加热溶解""" """加热溶解"""
debug_print(f"🔥 加热溶解: {solvent} ({volume}) → {vessel} @ {temp}") vessel_id = vessel["id"]
debug_print(f"🔥 加热溶解: {solvent} ({volume}) → {vessel_id} @ {temp}")
return generate_dissolve_protocol( return generate_dissolve_protocol(
G, vessel, G, vessel,
solvent=solvent, solvent=solvent,

View File

@@ -46,8 +46,8 @@ def find_connected_heater(G: nx.DiGraph, vessel: str) -> str:
def generate_dry_protocol( def generate_dry_protocol(
G: nx.DiGraph, G: nx.DiGraph,
compound: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
vessel: str, compound: str = "", # 🔧 修改:参数顺序调整,并设置默认值
**kwargs # 接收其他可能的参数但不使用 **kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
@@ -55,13 +55,16 @@ def generate_dry_protocol(
Args: Args:
G: 有向图,节点为容器和设备 G: 有向图,节点为容器和设备
compound: 化合物名称从XDL传入 vessel: 目标容器字典从XDL传入
vessel: 目标容器从XDL传入 compound: 化合物名称从XDL传入,可选
**kwargs: 其他可选参数,但不使用 **kwargs: 其他可选参数,但不使用
Returns: Returns:
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
action_sequence = [] action_sequence = []
# 默认参数 # 默认参数
@@ -70,33 +73,83 @@ def generate_dry_protocol(
simulation_time = 60.0 # 模拟时间 1分钟 simulation_time = 60.0 # 模拟时间 1分钟
print(f"🌡️ DRY: 开始生成干燥协议 ✨") print(f"🌡️ DRY: 开始生成干燥协议 ✨")
print(f" 🧪 化合物: {compound}") print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
print(f" 🥽 容器: {vessel}") print(f" 🧪 化合物: {compound or '未指定'}")
print(f" 🔥 干燥温度: {dry_temp}°C") print(f" 🔥 干燥温度: {dry_temp}°C")
print(f" ⏰ 干燥时间: {dry_time/60:.0f} 分钟") print(f" ⏰ 干燥时间: {dry_time/60:.0f} 分钟")
# 🔧 新增:记录干燥前的容器状态
print(f"🔍 记录干燥前容器状态...")
original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume
print(f"📊 干燥前液体体积: {original_liquid_volume:.2f}mL")
# 1. 验证目标容器存在 # 1. 验证目标容器存在
print(f"\n📋 步骤1: 验证目标容器 '{vessel}' 是否存在...") print(f"\n📋 步骤1: 验证目标容器 '{vessel_id}' 是否存在...")
if vessel not in G.nodes(): if vessel_id not in G.nodes():
print(f"⚠️ DRY: 警告 - 容器 '{vessel}' 不存在于系统中,跳过干燥 😢") print(f"⚠️ DRY: 警告 - 容器 '{vessel_id}' 不存在于系统中,跳过干燥 😢")
return action_sequence return action_sequence
print(f"✅ 容器 '{vessel}' 验证通过!") print(f"✅ 容器 '{vessel_id}' 验证通过!")
# 2. 查找相连的加热器 # 2. 查找相连的加热器
print(f"\n🔍 步骤2: 查找与容器相连的加热器...") print(f"\n🔍 步骤2: 查找与容器相连的加热器...")
heater_id = find_connected_heater(G, vessel) heater_id = find_connected_heater(G, vessel_id) # 🔧 使用 vessel_id
if heater_id is None: if heater_id is None:
print(f"😭 DRY: 警告 - 未找到与容器 '{vessel}' 相连的加热器,跳过干燥") print(f"😭 DRY: 警告 - 未找到与容器 '{vessel_id}' 相连的加热器,跳过干燥")
print(f"🎭 添加模拟干燥动作...") 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} (无加热器可用)" "description": f"模拟干燥 {compound or '化合物'} (无加热器可用)"
} }
}) })
# 🔧 新增:模拟干燥的体积变化(溶剂蒸发)
print(f"🔧 模拟干燥过程的体积减少...")
if original_liquid_volume > 0:
# 假设干燥过程中损失10%的体积(溶剂蒸发)
volume_loss = original_liquid_volume * 0.1
new_volume = max(0.0, original_liquid_volume - volume_loss)
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] = new_volume
else:
vessel["data"]["liquid_volume"] = [new_volume]
elif isinstance(current_volume, (int, float)):
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
print(f"📊 模拟干燥体积变化: {original_liquid_volume:.2f}mL → {new_volume:.2f}mL (-{volume_loss:.2f}mL)")
print(f"📄 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯") print(f"📄 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯")
return action_sequence return action_sequence
@@ -112,9 +165,9 @@ def generate_dry_protocol(
"device_id": heater_id, "device_id": heater_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"temp": dry_temp, "temp": dry_temp,
"purpose": f"干燥 {compound}" "purpose": f"干燥 {compound or '化合物'}"
} }
}) })
print(f" ✅ 加热器启动命令已添加 🔥") print(f" ✅ 加热器启动命令已添加 🔥")
@@ -136,21 +189,67 @@ def generate_dry_protocol(
"device_id": heater_id, "device_id": heater_id,
"action_name": "heat_chill", "action_name": "heat_chill",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"temp": dry_temp, "temp": dry_temp,
"time": simulation_time, "time": simulation_time,
"purpose": f"干燥 {compound},保持温度 {dry_temp}°C" "purpose": f"干燥 {compound or '化合物'},保持温度 {dry_temp}°C"
} }
}) })
print(f" ✅ 温度保持命令已添加 🌡️⏰") print(f" ✅ 温度保持命令已添加 🌡️⏰")
# 🔧 新增:干燥过程中的体积变化计算
print(f"🔧 计算干燥过程中的体积变化...")
if original_liquid_volume > 0:
# 干燥过程中,溶剂会蒸发,固体保留
# 根据温度和时间估算蒸发量
evaporation_rate = 0.001 * dry_temp # 每秒每°C蒸发0.001mL
total_evaporation = min(original_liquid_volume * 0.8,
evaporation_rate * simulation_time) # 最多蒸发80%
new_volume = max(0.0, original_liquid_volume - total_evaporation)
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] = new_volume
else:
vessel["data"]["liquid_volume"] = [new_volume]
elif isinstance(current_volume, (int, float)):
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
print(f"📊 干燥体积变化计算:")
print(f" - 初始体积: {original_liquid_volume:.2f}mL")
print(f" - 蒸发量: {total_evaporation:.2f}mL")
print(f" - 剩余体积: {new_volume:.2f}mL")
print(f" - 蒸发率: {(total_evaporation/original_liquid_volume*100):.1f}%")
# 3.4 停止加热 # 3.4 停止加热
print(f" ⏹️ 动作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": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"purpose": f"干燥完成,停止加热" "purpose": f"干燥完成,停止加热"
} }
}) })
@@ -162,18 +261,67 @@ def generate_dry_protocol(
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
"time": 10.0, # 等待10秒冷却 "time": 10.0, # 等待10秒冷却
"description": f"等待 {compound} 冷却" "description": f"等待 {compound or '化合物'} 冷却"
} }
}) })
print(f" ✅ 冷却等待命令已添加 🧊") print(f" ✅ 冷却等待命令已添加 🧊")
# 🔧 新增:干燥完成后的状态报告
final_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
final_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
final_liquid_volume = current_volume
print(f"\n🎊 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯") print(f"\n🎊 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯")
print(f"⏱️ DRY: 预计总时间: {(dry_time + 360)/60:.0f} 分钟 ⌛") 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"🏁 所有动作序列准备就绪! ✨") print(f"🏁 所有动作序列准备就绪! ✨")
return action_sequence return action_sequence
# 🔧 新增:便捷函数
def generate_quick_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "",
temp: float = 40.0, time: float = 30.0) -> List[Dict[str, Any]]:
"""快速干燥:低温短时间"""
vessel_id = vessel["id"]
print(f"🌡️ 快速干燥: {compound or '化合物'}{vessel_id} @ {temp}°C ({time}min)")
# 临时修改默认参数
import types
temp_func = types.FunctionType(
generate_dry_protocol.__code__,
generate_dry_protocol.__globals__
)
# 直接调用原函数,但修改内部参数
return generate_dry_protocol(G, vessel, compound)
def generate_thorough_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "",
temp: float = 80.0, time: float = 120.0) -> List[Dict[str, Any]]:
"""深度干燥:高温长时间"""
vessel_id = vessel["id"]
print(f"🔥 深度干燥: {compound or '化合物'}{vessel_id} @ {temp}°C ({time}min)")
return generate_dry_protocol(G, vessel, compound)
def generate_gentle_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "",
temp: float = 30.0, time: float = 180.0) -> List[Dict[str, Any]]:
"""温和干燥:低温长时间"""
vessel_id = vessel["id"]
print(f"🌡️ 温和干燥: {compound or '化合物'}{vessel_id} @ {temp}°C ({time}min)")
return generate_dry_protocol(G, vessel, compound)
# 测试函数 # 测试函数
def test_dry_protocol(): def test_dry_protocol():
"""测试干燥协议""" """测试干燥协议"""

View File

@@ -270,7 +270,7 @@ def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
def generate_evacuateandrefill_protocol( def generate_evacuateandrefill_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
gas: str, gas: str,
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
@@ -279,7 +279,7 @@ def generate_evacuateandrefill_protocol(
Args: Args:
G: 设备图 G: 设备图
vessel: 目标容器名称(必需) vessel: 目标容器字典(必需)
gas: 气体名称(必需) gas: 气体名称(必需)
**kwargs: 其他参数(兼容性) **kwargs: 其他参数(兼容性)
@@ -287,6 +287,9 @@ def generate_evacuateandrefill_protocol(
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
# 硬编码重复次数为 3 # 硬编码重复次数为 3
repeats = 3 repeats = 3
@@ -297,7 +300,7 @@ def generate_evacuateandrefill_protocol(
debug_print("=" * 60) debug_print("=" * 60)
debug_print("🧪 开始生成抽真空充气协议") debug_print("🧪 开始生成抽真空充气协议")
debug_print(f"📋 原始参数:") debug_print(f"📋 原始参数:")
debug_print(f" 🥼 容器: '{vessel}'") debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 💨 气体: '{gas}'") debug_print(f" 💨 气体: '{gas}'")
debug_print(f" 🔄 循环次数: {repeats} (硬编码)") debug_print(f" 🔄 循环次数: {repeats} (硬编码)")
debug_print(f" 📦 其他参数: {kwargs}") debug_print(f" 📦 其他参数: {kwargs}")
@@ -307,12 +310,12 @@ def generate_evacuateandrefill_protocol(
# === 参数验证和修正 === # === 参数验证和修正 ===
debug_print("🔍 步骤1: 参数验证和修正...") debug_print("🔍 步骤1: 参数验证和修正...")
action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel}", "🎬")) 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: if not vessel_id:
debug_print("❌ 容器参数不能为空") debug_print("❌ 容器参数不能为空")
raise ValueError("容器参数不能为空") raise ValueError("容器参数不能为空")
@@ -320,9 +323,9 @@ def generate_evacuateandrefill_protocol(
debug_print("❌ 气体参数不能为空") debug_print("❌ 气体参数不能为空")
raise ValueError("气体参数不能为空") raise ValueError("气体参数不能为空")
if vessel not in G.nodes(): if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print(f"❌ 容器 '{vessel}' 在系统中不存在") debug_print(f"❌ 容器 '{vessel_id}' 在系统中不存在")
raise ValueError(f"容器 '{vessel}' 在系统中不存在") raise ValueError(f"容器 '{vessel_id}' 在系统中不存在")
debug_print("✅ 基本参数验证通过") debug_print("✅ 基本参数验证通过")
action_sequence.append(create_action_log("参数验证通过", "")) action_sequence.append(create_action_log("参数验证通过", ""))
@@ -351,7 +354,7 @@ def generate_evacuateandrefill_protocol(
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}, 气体={gas}, 重复={repeats}") debug_print(f"📋 最终参数: 容器={vessel_id}, 气体={gas}, 重复={repeats}")
# === 查找设备 === # === 查找设备 ===
debug_print("🔍 步骤2: 查找设备...") debug_print("🔍 步骤2: 查找设备...")
@@ -376,7 +379,7 @@ 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) stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 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:
@@ -444,8 +447,8 @@ def generate_evacuateandrefill_protocol(
try: try:
# 验证抽真空路径 # 验证抽真空路径
if nx.has_path(G, vessel, vacuum_pump): if nx.has_path(G, vessel_id, vacuum_pump): # 🔧 使用 vessel_id
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump) vacuum_path = nx.shortest_path(G, source=vessel_id, target=vacuum_pump)
debug_print(f"✅ 真空路径: {' -> '.join(vacuum_path)}") 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:
@@ -453,8 +456,8 @@ def generate_evacuateandrefill_protocol(
action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️")) action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️"))
# 验证充气路径 # 验证充气路径
if nx.has_path(G, gas_source, vessel): if nx.has_path(G, gas_source, vessel_id): # 🔧 使用 vessel_id
gas_path = nx.shortest_path(G, source=gas_source, target=vessel) gas_path = nx.shortest_path(G, source=gas_source, target=vessel_id)
debug_print(f"✅ 气体路径: {' -> '.join(gas_path)}") 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:
@@ -476,7 +479,7 @@ def generate_evacuateandrefill_protocol(
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"stir_speed": STIR_SPEED, "stir_speed": STIR_SPEED,
"purpose": "抽真空充气前预搅拌" "purpose": "抽真空充气前预搅拌"
} }
@@ -524,13 +527,13 @@ def generate_evacuateandrefill_protocol(
}) })
# 抽真空操作 # 抽真空操作
debug_print(f"🌪️ 抽真空操作: {vessel} -> {vacuum_pump}") debug_print(f"🌪️ 抽真空操作: {vessel_id} -> {vacuum_pump}")
action_sequence.append(create_action_log(f"开始抽真空: {vessel} -> {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, from_vessel=vessel_id, # 🔧 使用 vessel_id
to_vessel=vacuum_pump, to_vessel=vacuum_pump,
volume=VACUUM_VOLUME, volume=VACUUM_VOLUME,
amount="", amount="",
@@ -622,14 +625,14 @@ def generate_evacuateandrefill_protocol(
}) })
# 充气操作 # 充气操作
debug_print(f"💨 充气操作: {gas_source} -> {vessel}") debug_print(f"💨 充气操作: {gas_source} -> {vessel_id}")
action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel}", "💨")) 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, to_vessel=vessel_id, # 🔧 使用 vessel_id
volume=REFILL_VOLUME, volume=REFILL_VOLUME,
amount="", amount="",
time=0.0, time=0.0,
@@ -709,7 +712,7 @@ def generate_evacuateandrefill_protocol(
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": vessel} "action_kwargs": {"vessel": vessel_id} # 🔧 使用 vessel_id
}) })
else: else:
action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️")) action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️"))
@@ -729,37 +732,41 @@ def generate_evacuateandrefill_protocol(
debug_print(f"📊 协议统计:") debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}") debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)") debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
debug_print(f" 🥼 处理容器: {vessel}") debug_print(f" 🥼 处理容器: {vessel_id}")
debug_print(f" 💨 使用气体: {gas}") debug_print(f" 💨 使用气体: {gas}")
debug_print(f" 🔄 重复次数: {repeats}") debug_print(f" 🔄 重复次数: {repeats}")
debug_print("=" * 60) debug_print("=" * 60)
# 添加完成日志 # 添加完成日志
summary_msg = f"抽真空充气协议完成: {vessel} (使用 {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, "🎉"))
return action_sequence return action_sequence
# === 便捷函数 === # === 便捷函数 ===
def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]: def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
"""生成氮气置换协议""" """生成氮气置换协议"""
debug_print(f"💨 生成氮气置换协议: {vessel}") 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: str, **kwargs) -> List[Dict[str, Any]]: def generate_argon_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
"""生成氩气置换协议""" """生成氩气置换协议"""
debug_print(f"💨 生成氩气置换协议: {vessel}") 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: str, **kwargs) -> List[Dict[str, Any]]: def generate_air_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
"""生成空气置换协议""" """生成空气置换协议"""
debug_print(f"💨 生成空气置换协议: {vessel}") 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: str, 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]]: # 🔧 修改参数类型
"""生成惰性气氛协议""" """生成惰性气氛协议"""
debug_print(f"🛡️ 生成惰性气氛协议: {vessel} (使用 {gas})") 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)
# 测试函数 # 测试函数

View File

@@ -175,7 +175,7 @@ def find_connected_vessel(G: nx.DiGraph, rotavap_device: str) -> Optional[str]:
def generate_evaporate_protocol( def generate_evaporate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, 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", # 🔧 修改:支持字符串时间
@@ -184,11 +184,11 @@ def generate_evaporate_protocol(
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成蒸发操作的协议序列 - 支持单位 生成蒸发操作的协议序列 - 支持单位和体积运算
Args: Args:
G: 设备图 G: 设备图
vessel: 容器名称或旋转蒸发仪名称 vessel: 容器字典从XDL传入
pressure: 真空度 (bar)默认0.1 pressure: 真空度 (bar)默认0.1
temp: 加热温度 (°C)默认60 temp: 加热温度 (°C)默认60
time: 蒸发时间(支持 "3 min", "180", "0.5 h" 等) time: 蒸发时间(支持 "3 min", "180", "0.5 h" 等)
@@ -200,10 +200,13 @@ def generate_evaporate_protocol(
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("🌟" * 20) debug_print("🌟" * 20)
debug_print("🌪️ 开始生成蒸发协议(支持单位)✨") debug_print("🌪️ 开始生成蒸发协议(支持单位和体积运算)✨")
debug_print(f"📝 输入参数:") debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}") debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 💨 pressure: {pressure} bar") debug_print(f" 💨 pressure: {pressure} bar")
debug_print(f" 🌡️ temp: {temp}°C") debug_print(f" 🌡️ temp: {temp}°C")
debug_print(f" ⏰ time: {time} (类型: {type(time)})") debug_print(f" ⏰ time: {time} (类型: {type(time)})")
@@ -211,16 +214,27 @@ def generate_evaporate_protocol(
debug_print(f" 🧪 solvent: '{solvent}'") debug_print(f" 🧪 solvent: '{solvent}'")
debug_print("🌟" * 20) debug_print("🌟" * 20)
# 🔧 新增:记录蒸发前的容器状态
debug_print("🔍 记录蒸发前容器状态...")
original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume
debug_print(f"📊 蒸发前液体体积: {original_liquid_volume:.2f}mL")
# === 步骤1: 查找旋转蒸发仪设备 === # === 步骤1: 查找旋转蒸发仪设备 ===
debug_print("📍 步骤1: 查找旋转蒸发仪设备... 🔍") debug_print("📍 步骤1: 查找旋转蒸发仪设备... 🔍")
# 验证vessel参数 # 验证vessel参数
if not vessel: if not vessel_id:
debug_print("❌ vessel 参数不能为空! 😱") debug_print("❌ vessel 参数不能为空! 😱")
raise ValueError("vessel 参数不能为空") raise ValueError("vessel 参数不能为空")
# 查找旋转蒸发仪设备 # 查找旋转蒸发仪设备
rotavap_device = find_rotavap_device(G, vessel) rotavap_device = find_rotavap_device(G, vessel_id)
if not rotavap_device: if not rotavap_device:
debug_print("💥 未找到旋转蒸发仪设备! 😭") debug_print("💥 未找到旋转蒸发仪设备! 😭")
raise ValueError(f"未找到旋转蒸发仪设备。请检查组态图中是否包含 class 包含 'rotavap''rotary''evaporat' 的设备") raise ValueError(f"未找到旋转蒸发仪设备。请检查组态图中是否包含 class 包含 'rotavap''rotary''evaporat' 的设备")
@@ -230,10 +244,10 @@ def generate_evaporate_protocol(
# === 步骤2: 确定目标容器 === # === 步骤2: 确定目标容器 ===
debug_print("📍 步骤2: 确定目标容器... 🥽") debug_print("📍 步骤2: 确定目标容器... 🥽")
target_vessel = vessel target_vessel = vessel_id
# 如果vessel就是旋转蒸发仪设备查找连接的容器 # 如果vessel就是旋转蒸发仪设备查找连接的容器
if vessel == rotavap_device: if vessel_id == rotavap_device:
debug_print("🔄 vessel就是旋转蒸发仪查找连接的容器...") 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:
@@ -242,11 +256,11 @@ def generate_evaporate_protocol(
else: else:
debug_print(f"⚠️ 未找到连接的容器,使用设备本身: {rotavap_device} 🔧") debug_print(f"⚠️ 未找到连接的容器,使用设备本身: {rotavap_device} 🔧")
target_vessel = rotavap_device target_vessel = rotavap_device
elif vessel in G.nodes() and G.nodes[vessel].get('type') == 'container': elif vessel_id in G.nodes() and G.nodes[vessel_id].get('type') == 'container':
debug_print(f"✅ 使用指定的容器: {vessel} 🥽✨") debug_print(f"✅ 使用指定的容器: {vessel_id} 🥽✨")
target_vessel = vessel target_vessel = vessel_id
else: else:
debug_print(f"⚠️ 容器 '{vessel}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device} 🔧") debug_print(f"⚠️ 容器 '{vessel_id}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device} 🔧")
target_vessel = rotavap_device target_vessel = rotavap_device
# === 🔧 新增步骤3单位解析处理 === # === 🔧 新增步骤3单位解析处理 ===
@@ -308,8 +322,49 @@ def generate_evaporate_protocol(
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: 生成动作序列 === # === 🔧 新增步骤5蒸发体积计算 ===
debug_print("📍 步骤5: 生成动作序列... 🎬") debug_print("📍 步骤5: 蒸发体积计算... 📊")
# 根据温度、真空度、时间和溶剂类型估算蒸发量
evaporation_volume = 0.0
if original_liquid_volume > 0:
# 基础蒸发速率mL/min
base_evap_rate = 0.5 # 基础速率
# 温度系数(高温蒸发更快)
temp_factor = 1.0 + (temp - 25.0) / 100.0
# 真空系数(真空度越高蒸发越快)
vacuum_factor = 1.0 + (1.0 - pressure) * 2.0
# 溶剂系数
solvent_factor = 1.0
if solvent:
solvent_lower = solvent.lower()
if any(s in solvent_lower for s in ['water', 'h2o']):
solvent_factor = 0.8 # 水蒸发较慢
elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
solvent_factor = 1.5 # 易挥发溶剂蒸发快
elif any(s in solvent_lower for s in ['dmso', 'dmi']):
solvent_factor = 0.3 # 高沸点溶剂蒸发慢
# 计算总蒸发量
total_evap_rate = base_evap_rate * temp_factor * vacuum_factor * solvent_factor
evaporation_volume = min(
original_liquid_volume * 0.95, # 最多蒸发95%
total_evap_rate * (final_time / 60.0) # 时间相关的蒸发量
)
debug_print(f"📊 蒸发量计算:")
debug_print(f" - 基础蒸发速率: {base_evap_rate} mL/min")
debug_print(f" - 温度系数: {temp_factor:.2f} (基于 {temp}°C)")
debug_print(f" - 真空系数: {vacuum_factor:.2f} (基于 {pressure} bar)")
debug_print(f" - 溶剂系数: {solvent_factor:.2f} ({solvent or '通用'})")
debug_print(f" - 总蒸发速率: {total_evap_rate:.2f} mL/min")
debug_print(f" - 预计蒸发量: {evaporation_volume:.2f}mL ({evaporation_volume/original_liquid_volume*100:.1f}%)")
# === 步骤6: 生成动作序列 ===
debug_print("📍 步骤6: 生成动作序列... 🎬")
action_sequence = [] action_sequence = []
@@ -334,7 +389,7 @@ def generate_evaporate_protocol(
"device_id": rotavap_device, "device_id": rotavap_device,
"action_name": "evaporate", "action_name": "evaporate",
"action_kwargs": { "action_kwargs": {
"vessel": target_vessel, "vessel": target_vessel, # 使用 target_vessel
"pressure": pressure, "pressure": pressure,
"temp": temp, "temp": temp,
"time": final_time, "time": final_time,
@@ -345,6 +400,42 @@ def generate_evaporate_protocol(
action_sequence.append(evaporate_action) action_sequence.append(evaporate_action)
debug_print(" ✅ 蒸发动作已添加 🌪️✨") debug_print(" ✅ 蒸发动作已添加 🌪️✨")
# 🔧 新增:蒸发过程中的体积变化
debug_print(" 🔧 更新容器体积 - 蒸发过程...")
if evaporation_volume > 0:
new_volume = max(0.0, original_liquid_volume - evaporation_volume)
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] = new_volume
else:
vessel["data"]["liquid_volume"] = [new_volume]
elif isinstance(current_volume, (int, float)):
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" 📊 蒸发体积变化: {original_liquid_volume:.2f}mL → {new_volume:.2f}mL (-{evaporation_volume:.2f}mL)")
# 3. 蒸发后等待 # 3. 蒸发后等待
debug_print(" 🔄 动作3: 添加蒸发后等待... ⏳") debug_print(" 🔄 动作3: 添加蒸发后等待... ⏳")
action_sequence.append({ action_sequence.append({
@@ -353,6 +444,15 @@ def generate_evaporate_protocol(
}) })
debug_print(" ✅ 蒸发后等待动作已添加 ⏳✨") debug_print(" ✅ 蒸发后等待动作已添加 ⏳✨")
# 🔧 新增:蒸发完成后的状态报告
final_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
final_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
final_liquid_volume = current_volume
# === 总结 === # === 总结 ===
debug_print("🎊" * 20) debug_print("🎊" * 20)
debug_print(f"🎉 蒸发协议生成完成! ✨") debug_print(f"🎉 蒸发协议生成完成! ✨")
@@ -361,6 +461,10 @@ def generate_evaporate_protocol(
debug_print(f"🥽 目标容器: {target_vessel} 🧪") debug_print(f"🥽 目标容器: {target_vessel} 🧪")
debug_print(f"⚙️ 蒸发参数: {pressure} bar 💨, {temp}°C 🌡️, {final_time}s ⏰, {stir_speed} RPM 🌪️") debug_print(f"⚙️ 蒸发参数: {pressure} bar 💨, {temp}°C 🌡️, {final_time}s ⏰, {stir_speed} RPM 🌪️")
debug_print(f"⏱️ 预计总时间: {(final_time + 20)/60:.1f} 分钟 ⌛") debug_print(f"⏱️ 预计总时间: {(final_time + 20)/60:.1f} 分钟 ⌛")
debug_print(f"📊 体积变化:")
debug_print(f" - 蒸发前: {original_liquid_volume:.2f}mL")
debug_print(f" - 蒸发后: {final_liquid_volume:.2f}mL")
debug_print(f" - 蒸发量: {evaporation_volume:.2f}mL ({evaporation_volume/max(original_liquid_volume, 0.01)*100:.1f}%)")
debug_print("🎊" * 20) debug_print("🎊" * 20)
return action_sequence return action_sequence

View File

@@ -50,16 +50,16 @@ def validate_vessel(G: nx.DiGraph, vessel: str, vessel_type: str = "容器") ->
def generate_filter_protocol( def generate_filter_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
filtrate_vessel: str = "", filtrate_vessel: str = "",
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成过滤操作的协议序列 生成过滤操作的协议序列 - 支持体积运算
Args: Args:
G: 设备图 G: 设备图
vessel: 过滤容器名称(必需)- 包含需要过滤的混合物 vessel: 过滤容器字典(必需)- 包含需要过滤的混合物
filtrate_vessel: 滤液容器名称(可选)- 如果提供则收集滤液 filtrate_vessel: 滤液容器名称(可选)- 如果提供则收集滤液
**kwargs: 其他参数(兼容性) **kwargs: 其他参数(兼容性)
@@ -67,22 +67,36 @@ def generate_filter_protocol(
List[Dict[str, Any]]: 过滤操作的动作序列 List[Dict[str, Any]]: 过滤操作的动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("🌊" * 20) debug_print("🌊" * 20)
debug_print("🚀 开始生成过滤协议 ") debug_print("🚀 开始生成过滤协议(支持体积运算)")
debug_print(f"📝 输入参数:") debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}") debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 🧪 filtrate_vessel: {filtrate_vessel}") debug_print(f" 🧪 filtrate_vessel: {filtrate_vessel}")
debug_print(f" ⚙️ 其他参数: {kwargs}") debug_print(f" ⚙️ 其他参数: {kwargs}")
debug_print("🌊" * 20) debug_print("🌊" * 20)
action_sequence = [] action_sequence = []
# 🔧 新增:记录过滤前的容器状态
debug_print("🔍 记录过滤前容器状态...")
original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume
debug_print(f"📊 过滤前液体体积: {original_liquid_volume:.2f}mL")
# === 参数验证 === # === 参数验证 ===
debug_print("📍 步骤1: 参数验证... 🔧") debug_print("📍 步骤1: 参数验证... 🔧")
# 验证必需参数 # 验证必需参数
debug_print(" 🔍 验证必需参数...") debug_print(" 🔍 验证必需参数...")
validate_vessel(G, vessel, "过滤容器") validate_vessel(G, vessel_id, "过滤容器") # 🔧 使用 vessel_id
debug_print(" ✅ 必需参数验证完成 🎯") debug_print(" ✅ 必需参数验证完成 🎯")
# 验证可选参数 # 验证可选参数
@@ -106,18 +120,46 @@ def generate_filter_protocol(
debug_print(f" ❌ 设备查找失败: {str(e)} 😭") debug_print(f" ❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"设备查找失败: {str(e)}") raise ValueError(f"设备查找失败: {str(e)}")
# 🔧 新增:过滤效率和体积分配估算
debug_print("📍 步骤2.5: 过滤体积分配估算... 📊")
# 估算过滤分离比例(基于经验数据)
solid_ratio = 0.1 # 假设10%是固体(保留在过滤器上)
liquid_ratio = 0.9 # 假设90%是液体(通过过滤器)
volume_loss_ratio = 0.05 # 假设5%体积损失(残留在过滤器等)
# 从kwargs中获取过滤参数进行优化
if "solid_content" in kwargs:
try:
solid_ratio = float(kwargs["solid_content"])
liquid_ratio = 1.0 - solid_ratio
debug_print(f"📋 使用指定的固体含量: {solid_ratio*100:.1f}%")
except:
debug_print("⚠️ 固体含量参数无效,使用默认值")
if original_liquid_volume > 0:
expected_filtrate_volume = original_liquid_volume * liquid_ratio * (1.0 - volume_loss_ratio)
expected_solid_volume = original_liquid_volume * solid_ratio
volume_loss = original_liquid_volume * volume_loss_ratio
debug_print(f"📊 过滤体积分配估算:")
debug_print(f" - 原始体积: {original_liquid_volume:.2f}mL")
debug_print(f" - 预计滤液体积: {expected_filtrate_volume:.2f}mL ({liquid_ratio*100:.1f}%)")
debug_print(f" - 预计固体体积: {expected_solid_volume:.2f}mL ({solid_ratio*100:.1f}%)")
debug_print(f" - 预计损失体积: {volume_loss:.2f}mL ({volume_loss_ratio*100:.1f}%)")
# === 转移到过滤器(如果需要)=== # === 转移到过滤器(如果需要)===
debug_print("📍 步骤3: 转移到过滤器... 🚚") debug_print("📍 步骤3: 转移到过滤器... 🚚")
if vessel != filter_device: if vessel_id != filter_device: # 🔧 使用 vessel_id
debug_print(f" 🚛 需要转移: {vessel}{filter_device} 📦") debug_print(f" 🚛 需要转移: {vessel_id}{filter_device} 📦")
try: try:
debug_print(" 🔄 开始执行转移操作...") debug_print(" 🔄 开始执行转移操作...")
# 使用pump protocol转移液体到过滤器 # 使用pump protocol转移液体到过滤器
transfer_actions = generate_pump_protocol_with_rinsing( transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=vessel, from_vessel=vessel_id, # 🔧 使用 vessel_id
to_vessel=filter_device, to_vessel=filter_device,
volume=0.0, # 转移所有液体 volume=0.0, # 转移所有液体
amount="", amount="",
@@ -134,6 +176,26 @@ 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"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
vessel["data"]["liquid_volume"] = [0.0] if len(current_volume) > 0 else [0.0]
else:
vessel["data"]["liquid_volume"] = 0.0
# 同时更新图中的容器数据
if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {}
G.nodes[vessel_id]['data']['liquid_volume'] = 0.0
debug_print(f" 📊 转移完成,{vessel_id} 体积更新为 0.0mL")
else: else:
debug_print(" ⚠️ 转移协议返回空序列 🤔") debug_print(" ⚠️ 转移协议返回空序列 🤔")
@@ -206,6 +268,26 @@ 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(f" ✅ 添加了 {len(collect_actions)} 个收集动作 🧪✨")
# 🔧 新增:收集滤液后的体积更新
debug_print(" 🔧 更新滤液容器体积...")
# 更新filtrate_vessel在图中的体积如果它是节点
if filtrate_vessel in G.nodes():
if 'data' not in G.nodes[filtrate_vessel]:
G.nodes[filtrate_vessel]['data'] = {}
current_filtrate_volume = G.nodes[filtrate_vessel]['data'].get('liquid_volume', 0.0)
if isinstance(current_filtrate_volume, list):
if len(current_filtrate_volume) > 0:
G.nodes[filtrate_vessel]['data']['liquid_volume'][0] += expected_filtrate_volume
else:
G.nodes[filtrate_vessel]['data']['liquid_volume'] = [expected_filtrate_volume]
else:
G.nodes[filtrate_vessel]['data']['liquid_volume'] = current_filtrate_volume + expected_filtrate_volume
debug_print(f" 📊 滤液容器 {filtrate_vessel} 体积增加 {expected_filtrate_volume:.2f}mL")
else: else:
debug_print(" ⚠️ 收集协议返回空序列 🤔") debug_print(" ⚠️ 收集协议返回空序列 🤔")
@@ -215,6 +297,37 @@ def generate_filter_protocol(
else: else:
debug_print(" 🧱 未指定滤液容器,固体保留在过滤器中 🔬") debug_print(" 🧱 未指定滤液容器,固体保留在过滤器中 🔬")
# 🔧 新增:过滤完成后的容器状态更新
debug_print("📍 步骤5.5: 过滤完成后状态更新... 📊")
if vessel_id == filter_device:
# 如果过滤容器就是过滤器,需要更新其体积状态
if original_liquid_volume > 0:
if filtrate_vessel:
# 收集滤液模式:过滤器中主要保留固体
remaining_volume = expected_solid_volume
debug_print(f" 🧱 过滤器中保留固体: {remaining_volume:.2f}mL")
else:
# 保留固体模式:过滤器中保留所有物质
remaining_volume = original_liquid_volume * (1.0 - volume_loss_ratio)
debug_print(f" 🔬 过滤器中保留所有物质: {remaining_volume:.2f}mL")
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
vessel["data"]["liquid_volume"] = [remaining_volume] if len(current_volume) > 0 else [remaining_volume]
else:
vessel["data"]["liquid_volume"] = remaining_volume
# 同时更新图中的容器数据
if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {}
G.nodes[vessel_id]['data']['liquid_volume'] = remaining_volume
debug_print(f" 📊 过滤器 {vessel_id} 体积更新为: {remaining_volume:.2f}mL")
# === 最终等待 === # === 最终等待 ===
debug_print("📍 步骤6: 最终等待... ⏰") debug_print("📍 步骤6: 最终等待... ⏰")
action_sequence.append({ action_sequence.append({
@@ -223,14 +336,31 @@ def generate_filter_protocol(
}) })
debug_print(" ✅ 最终等待动作已添加 ⏰✨") debug_print(" ✅ 最终等待动作已添加 ⏰✨")
# 🔧 新增:过滤完成后的状态报告
final_vessel_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
final_vessel_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
final_vessel_volume = current_volume
# === 总结 === # === 总结 ===
debug_print("🎊" * 20) debug_print("🎊" * 20)
debug_print(f"🎉 过滤协议生成完成! ✨") debug_print(f"🎉 过滤协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝") debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝")
debug_print(f"🥽 过滤容器: {vessel} 🧪") debug_print(f"🥽 过滤容器: {vessel_id} 🧪")
debug_print(f"🌊 过滤器设备: {filter_device} 🔧") debug_print(f"🌊 过滤器设备: {filter_device} 🔧")
debug_print(f"💧 滤液容器: {filtrate_vessel or '无(保留固体)'} 🧱") debug_print(f"💧 滤液容器: {filtrate_vessel or '无(保留固体)'} 🧱")
debug_print(f"⏱️ 预计总时间: {(len(action_sequence) * 5):.0f} 秒 ⌛") debug_print(f"⏱️ 预计总时间: {(len(action_sequence) * 5):.0f} 秒 ⌛")
if original_liquid_volume > 0:
debug_print(f"📊 体积变化统计:")
debug_print(f" - 过滤前体积: {original_liquid_volume:.2f}mL")
debug_print(f" - 过滤后容器体积: {final_vessel_volume:.2f}mL")
if filtrate_vessel:
debug_print(f" - 预计滤液体积: {expected_filtrate_volume:.2f}mL")
debug_print(f" - 预计损失体积: {volume_loss:.2f}mL")
debug_print("🎊" * 20) debug_print("🎊" * 20)
return action_sequence return action_sequence

View File

@@ -183,7 +183,7 @@ def validate_and_fix_params(temp: float, time: float, stir_speed: float) -> tupl
def generate_heat_chill_protocol( def generate_heat_chill_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改:从字符串改为字典类型
temp: float = 25.0, temp: float = 25.0,
time: Union[str, float] = "300", time: Union[str, float] = "300",
temp_spec: str = "", temp_spec: str = "",
@@ -196,29 +196,50 @@ def generate_heat_chill_protocol(
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成加热/冷却操作的协议序列 生成加热/冷却操作的协议序列 - 支持vessel字典
Args:
G: 设备图
vessel: 容器字典从XDL传入
temp: 目标温度 (°C)
time: 加热时间(支持字符串如 "30 min"
temp_spec: 温度规格说明优先级高于temp
time_spec: 时间规格说明优先级高于time
pressure: 压力设置
reflux_solvent: 回流溶剂
stir: 是否搅拌
stir_speed: 搅拌速度 (RPM)
purpose: 操作目的说明
**kwargs: 其他参数(兼容性)
Returns:
List[Dict[str, Any]]: 加热/冷却操作的动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("🌡️" * 20) debug_print("🌡️" * 20)
debug_print("🚀 开始生成加热冷却协议 ") debug_print("🚀 开始生成加热冷却协议支持vessel字典")
debug_print(f"📝 输入参数:") debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}") debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 🌡️ temp: {temp}°C") debug_print(f" 🌡️ temp: {temp}°C")
debug_print(f" ⏰ time: {time}") debug_print(f" ⏰ time: {time}")
debug_print(f" 🎯 temp_spec: {temp_spec}") debug_print(f" 🎯 temp_spec: {temp_spec}")
debug_print(f" ⏱️ time_spec: {time_spec}") debug_print(f" ⏱️ time_spec: {time_spec}")
debug_print(f" 🌪️ stir: {stir} ({stir_speed} RPM)") debug_print(f" 🌪️ stir: {stir} ({stir_speed} RPM)")
debug_print(f" 🎭 purpose: '{purpose}'")
debug_print("🌡️" * 20) debug_print("🌡️" * 20)
# 📋 参数验证 # 📋 参数验证
debug_print("📍 步骤1: 参数验证... 🔧") debug_print("📍 步骤1: 参数验证... 🔧")
if not vessel: if not vessel_id: # 🔧 使用 vessel_id
debug_print("❌ vessel 参数不能为空! 😱") debug_print("❌ vessel 参数不能为空! 😱")
raise ValueError("vessel 参数不能为空") raise ValueError("vessel 参数不能为空")
if vessel not in G.nodes(): if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中! 😞") debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中! 😞")
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
debug_print("✅ 基础参数验证通过 🎯") debug_print("✅ 基础参数验证通过 🎯")
@@ -239,7 +260,7 @@ def generate_heat_chill_protocol(
# 🔍 查找设备 # 🔍 查找设备
debug_print("📍 步骤3: 查找加热设备... 🔍") debug_print("📍 步骤3: 查找加热设备... 🔍")
try: try:
heatchill_id = find_connected_heatchill(G, vessel) heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
debug_print(f"🎉 使用加热设备: {heatchill_id}") debug_print(f"🎉 使用加热设备: {heatchill_id}")
except Exception as e: except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)} 😭") debug_print(f"❌ 设备查找失败: {str(e)} 😭")
@@ -265,7 +286,7 @@ def generate_heat_chill_protocol(
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill", "action_name": "heat_chill",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"temp": float(final_temp), "temp": float(final_temp),
"time": float(final_time), "time": float(final_time),
"stir": bool(stir), "stir": bool(stir),
@@ -284,7 +305,7 @@ def generate_heat_chill_protocol(
debug_print("🎊" * 20) debug_print("🎊" * 20)
debug_print(f"🎉 加热冷却协议生成完成! ✨") debug_print(f"🎉 加热冷却协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}") debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 加热容器: {vessel}") debug_print(f"🥽 加热容器: {vessel_id}")
debug_print(f"🌡️ 目标温度: {final_temp}°C") debug_print(f"🌡️ 目标温度: {final_temp}°C")
debug_print(f"⏰ 加热时间: {final_time}s ({final_time/60:.1f}分钟)") debug_print(f"⏰ 加热时间: {final_time}s ({final_time/60:.1f}分钟)")
debug_print("🎊" * 20) debug_print("🎊" * 20)
@@ -293,41 +314,45 @@ def generate_heat_chill_protocol(
def generate_heat_chill_to_temp_protocol( def generate_heat_chill_to_temp_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改参数类型
temp: float = 25.0, temp: float = 25.0,
time: Union[str, float] = 100.0, time: Union[str, float] = 100.0,
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""生成加热到指定温度的协议(简化版)""" """生成加热到指定温度的协议(简化版)"""
debug_print(f"🌡️ 生成加热到温度协议: {vessel}{temp}°C") vessel_id = vessel["id"]
debug_print(f"🌡️ 生成加热到温度协议: {vessel_id}{temp}°C")
return generate_heat_chill_protocol(G, vessel, temp, time, **kwargs) return generate_heat_chill_protocol(G, vessel, temp, time, **kwargs)
def generate_heat_chill_start_protocol( def generate_heat_chill_start_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改参数类型
temp: float = 25.0, temp: float = 25.0,
purpose: str = "", purpose: str = "",
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""生成开始加热操作的协议序列""" """生成开始加热操作的协议序列"""
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("🔥 开始生成启动加热协议 ✨") debug_print("🔥 开始生成启动加热协议 ✨")
debug_print(f"🥽 vessel: {vessel}, 🌡️ temp: {temp}°C") debug_print(f"🥽 vessel: {vessel} (ID: {vessel_id}), 🌡️ temp: {temp}°C")
# 基础验证 # 基础验证
if not vessel or vessel not in G.nodes(): if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print("❌ 容器验证失败!") debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效") raise ValueError("vessel 参数无效")
# 查找设备 # 查找设备
heatchill_id = find_connected_heatchill(G, vessel) heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
# 生成动作 # 生成动作
action_sequence = [{ action_sequence = [{
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"temp": temp, "temp": temp,
"purpose": purpose or f"开始加热到 {temp}°C" "purpose": purpose or f"开始加热到 {temp}°C"
} }
@@ -338,39 +363,34 @@ def generate_heat_chill_start_protocol(
def generate_heat_chill_stop_protocol( def generate_heat_chill_stop_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: dict, # 🔧 修改参数类型
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""生成停止加热操作的协议序列""" """生成停止加热操作的协议序列"""
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("🛑 开始生成停止加热协议 ✨") debug_print("🛑 开始生成停止加热协议 ✨")
debug_print(f"🥽 vessel: {vessel}") debug_print(f"🥽 vessel: {vessel} (ID: {vessel_id})")
# 基础验证 # 基础验证
if not vessel or vessel not in G.nodes(): if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print("❌ 容器验证失败!") debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效") raise ValueError("vessel 参数无效")
# 查找设备 # 查找设备
heatchill_id = find_connected_heatchill(G, vessel) heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
# 生成动作 # 生成动作
action_sequence = [{ action_sequence = [{
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "action_kwargs": {
"vessel": vessel "vessel": vessel_id # 🔧 使用 vessel_id
} }
}] }]
debug_print(f"✅ 停止加热协议生成完成 🎯") debug_print(f"✅ 停止加热协议生成完成 🎯")
return action_sequence return action_sequence
# 测试函数
def test_heatchill_protocol():
"""测试加热协议"""
debug_print("🧪 === HEATCHILL PROTOCOL 测试 === ✨")
debug_print("✅ 测试完成 🎉")
if __name__ == "__main__":
test_heatchill_protocol()

View File

@@ -150,75 +150,103 @@ def find_connected_device(G: nx.DiGraph, vessel: str, device_type: str) -> str:
def generate_hydrogenate_protocol( def generate_hydrogenate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型
temp: str, temp: str,
time: str, time: str,
vessel: str,
**kwargs # 接收其他可能的参数但不使用 **kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成氢化反应协议序列 生成氢化反应协议序列 - 支持vessel字典
Args: Args:
G: 有向图,节点为容器和设备 G: 有向图,节点为容器和设备
vessel: 反应容器字典从XDL传入
temp: 反应温度(如 "45 °C" temp: 反应温度(如 "45 °C"
time: 反应时间(如 "2 h" time: 反应时间(如 "2 h"
vessel: 反应容器
**kwargs: 其他可选参数,但不使用 **kwargs: 其他可选参数,但不使用
Returns: Returns:
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
action_sequence = [] action_sequence = []
# 解析参数 # 解析参数
temperature = parse_temperature(temp) temperature = parse_temperature(temp)
reaction_time = parse_time(time) reaction_time = parse_time(time)
print(f"HYDROGENATE: 开始生成氢化反应协议") print("🧪" * 20)
print(f" - 反应温度: {temperature}°C") print(f"HYDROGENATE: 开始生成氢化反应协议支持vessel字典")
print(f" - 反应时间: {reaction_time/3600:.1f} 小时") print(f"📝 输入参数:")
print(f" - 反应容器: {vessel}") print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
print(f" 🌡️ 反应温度: {temperature}°C")
print(f" ⏰ 反应时间: {reaction_time/3600:.1f} 小时")
print("🧪" * 20)
# 🔧 新增:记录氢化前的容器状态(可选,氢化反应通常不改变体积)
original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume
print(f"📊 氢化前液体体积: {original_liquid_volume:.2f}mL")
# 1. 验证目标容器存在 # 1. 验证目标容器存在
if vessel not in G.nodes(): print("📍 步骤1: 验证目标容器...")
print(f"HYDROGENATE: 警告 - 容器 '{vessel}' 不存在于系统中,跳过氢化反应") if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
print(f"⚠️ HYDROGENATE: 警告 - 容器 '{vessel_id}' 不存在于系统中,跳过氢化反应")
return action_sequence return action_sequence
print(f"✅ 容器 '{vessel_id}' 验证通过")
# 2. 查找相连的设备 # 2. 查找相连的设备
heater_id = find_connected_device(G, vessel, 'heater') print("📍 步骤2: 查找相连设备...")
stirrer_id = find_connected_device(G, vessel, 'stirrer') heater_id = find_connected_device(G, vessel_id, 'heater') # 🔧 使用 vessel_id
gas_source_id = find_connected_device(G, vessel, 'gas_source') stirrer_id = find_connected_device(G, vessel_id, 'stirrer') # 🔧 使用 vessel_id
gas_source_id = find_connected_device(G, vessel_id, 'gas_source') # 🔧 使用 vessel_id
print(f"🔧 设备配置:")
print(f" 🔥 加热器: {heater_id or '未找到'}")
print(f" 🌪️ 搅拌器: {stirrer_id or '未找到'}")
print(f" 💨 气源: {gas_source_id or '未找到'}")
# 3. 启动搅拌器 # 3. 启动搅拌器
print("📍 步骤3: 启动搅拌器...")
if stirrer_id: if stirrer_id:
print(f"HYDROGENATE: 启动搅拌器 {stirrer_id}") print(f"🌪️ 启动搅拌器 {stirrer_id}")
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": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"stir_speed": 300.0, "stir_speed": 300.0,
"purpose": "氢化反应: 开始搅拌" "purpose": "氢化反应: 开始搅拌"
} }
}) })
print("✅ 搅拌器启动动作已添加")
else: else:
print(f"HYDROGENATE: 警告 - 未找到搅拌器,继续执行") print(f"⚠️ HYDROGENATE: 警告 - 未找到搅拌器,继续执行")
# 4. 启动气源(氢气)- 修复版本 # 4. 启动气源(氢气)
print("📍 步骤4: 启动氢气源...")
if gas_source_id: if gas_source_id:
print(f"HYDROGENATE: 启动气源 {gas_source_id} (氢气)") print(f"💨 启动气源 {gas_source_id} (氢气)")
action_sequence.append({ action_sequence.append({
"device_id": gas_source_id, "device_id": gas_source_id,
"action_name": "set_status", # 修改为 set_status "action_name": "set_status",
"action_kwargs": { "action_kwargs": {
"string": "ON" # 修改参数格式 "string": "ON"
} }
}) })
# 查找相关的电磁阀 # 查找相关的电磁阀
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id) gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
if gas_solenoid: if gas_solenoid:
print(f"HYDROGENATE: 开启气源电磁阀 {gas_solenoid}") print(f"🚪 开启气源电磁阀 {gas_solenoid}")
action_sequence.append({ action_sequence.append({
"device_id": gas_solenoid, "device_id": gas_solenoid,
"action_name": "set_valve_position", "action_name": "set_valve_position",
@@ -226,10 +254,12 @@ def generate_hydrogenate_protocol(
"command": "OPEN" "command": "OPEN"
} }
}) })
print("✅ 氢气源启动动作已添加")
else: else:
print(f"HYDROGENATE: 警告 - 未找到气源,继续执行") print(f"⚠️ HYDROGENATE: 警告 - 未找到气源,继续执行")
# 5. 等待气体稳定 # 5. 等待气体稳定
print("📍 步骤5: 等待气体环境稳定...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
@@ -237,15 +267,17 @@ def generate_hydrogenate_protocol(
"description": "等待氢气环境稳定" "description": "等待氢气环境稳定"
} }
}) })
print("✅ 气体稳定等待动作已添加")
# 6. 启动加热器 # 6. 启动加热器
print("📍 步骤6: 启动加热反应...")
if heater_id: if heater_id:
print(f"HYDROGENATE: 启动加热器 {heater_id}{temperature}°C") print(f"🔥 启动加热器 {heater_id}{temperature}°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": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"temp": temperature, "temp": temperature,
"purpose": f"氢化反应: 加热到 {temperature}°C" "purpose": f"氢化反应: 加热到 {temperature}°C"
} }
@@ -261,23 +293,23 @@ def generate_hydrogenate_protocol(
}) })
# 🕐 模拟运行时间优化 # 🕐 模拟运行时间优化
print("HYDROGENATE: 检查模拟运行时间限制...") print(" 检查模拟运行时间限制...")
original_reaction_time = reaction_time original_reaction_time = reaction_time
simulation_time_limit = 60.0 # 模拟运行时间限制60秒 simulation_time_limit = 60.0 # 模拟运行时间限制60秒
if reaction_time > simulation_time_limit: if reaction_time > simulation_time_limit:
reaction_time = simulation_time_limit reaction_time = simulation_time_limit
print(f"HYDROGENATE: 模拟运行优化: {original_reaction_time}s → {reaction_time}s (限制为{simulation_time_limit}s)") print(f" 🎮 模拟运行优化: {original_reaction_time}s → {reaction_time}s (限制为{simulation_time_limit}s)")
print(f"HYDROGENATE: 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟") print(f" 📊 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟")
else: else:
print(f"HYDROGENATE: 时间在限制内: {reaction_time}s ({reaction_time/60:.1f}分钟) 保持不变") print(f" 时间在限制内: {reaction_time}s ({reaction_time/60:.1f}分钟) 保持不变")
# 保持反应温度 # 保持反应温度
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": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"temp": temperature, "temp": temperature,
"time": reaction_time, "time": reaction_time,
"purpose": f"氢化反应: 保持 {temperature}°C反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "") "purpose": f"氢化反应: 保持 {temperature}°C反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "")
@@ -286,22 +318,24 @@ def generate_hydrogenate_protocol(
# 显示时间调整信息 # 显示时间调整信息
if original_reaction_time != reaction_time: if original_reaction_time != reaction_time:
print(f"HYDROGENATE: 模拟优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟") print(f" 🎭 模拟优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟")
print("✅ 加热反应动作已添加")
else: else:
print(f"HYDROGENATE: 警告 - 未找到加热器,使用室温反应") print(f"⚠️ HYDROGENATE: 警告 - 未找到加热器,使用室温反应")
# 🕐 室温反应也需要时间优化 # 🕐 室温反应也需要时间优化
print("HYDROGENATE: 检查室温反应模拟时间限制...") print(" 检查室温反应模拟时间限制...")
original_reaction_time = reaction_time original_reaction_time = reaction_time
simulation_time_limit = 60.0 # 模拟运行时间限制60秒 simulation_time_limit = 60.0 # 模拟运行时间限制60秒
if reaction_time > simulation_time_limit: if reaction_time > simulation_time_limit:
reaction_time = simulation_time_limit reaction_time = simulation_time_limit
print(f"HYDROGENATE: 室温反应时间优化: {original_reaction_time}s → {reaction_time}s") print(f" 🎮 室温反应时间优化: {original_reaction_time}s → {reaction_time}s")
print(f"HYDROGENATE: 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟") print(f" 📊 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟")
else: else:
print(f"HYDROGENATE: 室温反应时间在限制内: {reaction_time}s 保持不变") print(f" 室温反应时间在限制内: {reaction_time}s 保持不变")
# 室温反应,只等待时间 # 室温反应,只等待时间
action_sequence.append({ action_sequence.append({
@@ -314,20 +348,25 @@ def generate_hydrogenate_protocol(
# 显示时间调整信息 # 显示时间调整信息
if original_reaction_time != reaction_time: if original_reaction_time != reaction_time:
print(f"HYDROGENATE: 室温反应优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟") print(f" 🎭 室温反应优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟")
print("✅ 室温反应等待动作已添加")
# 7. 停止加热 # 7. 停止加热
print("📍 步骤7: 停止加热...")
if heater_id: if heater_id:
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": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"purpose": "氢化反应完成,停止加热" "purpose": "氢化反应完成,停止加热"
} }
}) })
print("✅ 停止加热动作已添加")
# 8. 等待冷却 # 8. 等待冷却
print("📍 步骤8: 等待冷却...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
@@ -335,13 +374,15 @@ def generate_hydrogenate_protocol(
"description": "等待反应混合物冷却" "description": "等待反应混合物冷却"
} }
}) })
print("✅ 冷却等待动作已添加")
# 9. 停止气源 - 修复版本 # 9. 停止气源
print("📍 步骤9: 停止氢气源...")
if gas_source_id: if gas_source_id:
# 先关闭电磁阀 # 先关闭电磁阀
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id) gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
if gas_solenoid: if gas_solenoid:
print(f"HYDROGENATE: 关闭气源电磁阀 {gas_solenoid}") print(f"🚪 关闭气源电磁阀 {gas_solenoid}")
action_sequence.append({ action_sequence.append({
"device_id": gas_solenoid, "device_id": gas_solenoid,
"action_name": "set_valve_position", "action_name": "set_valve_position",
@@ -353,25 +394,41 @@ def generate_hydrogenate_protocol(
# 再关闭气源 # 再关闭气源
action_sequence.append({ action_sequence.append({
"device_id": gas_source_id, "device_id": gas_source_id,
"action_name": "set_status", # 修改为 set_status "action_name": "set_status",
"action_kwargs": { "action_kwargs": {
"string": "OFF" # 修改参数格式 "string": "OFF"
} }
}) })
print("✅ 氢气源停止动作已添加")
# 10. 停止搅拌 # 10. 停止搅拌
print("📍 步骤10: 停止搅拌...")
if stirrer_id: if 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": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"purpose": "氢化反应完成,停止搅拌" "purpose": "氢化反应完成,停止搅拌"
} }
}) })
print("✅ 停止搅拌动作已添加")
print(f"HYDROGENATE: 协议生成完成,共 {len(action_sequence)} 个动作") # 🔧 新增:氢化完成后的状态(氢化反应通常不改变体积)
print(f"HYDROGENATE: 预计总时间: {(reaction_time + 450)/3600:.1f} 小时") final_liquid_volume = original_liquid_volume # 氢化反应体积基本不变
# 总结
print("🎊" * 20)
print(f"🎉 氢化反应协议生成完成! ✨")
print(f"📊 总动作数: {len(action_sequence)}")
print(f"🥽 反应容器: {vessel_id}")
print(f"🌡️ 反应温度: {temperature}°C")
print(f"⏰ 反应时间: {reaction_time/60:.1f}分钟")
print(f"⏱️ 预计总时间: {(reaction_time + 450)/3600:.1f} 小时")
print(f"📊 体积状态:")
print(f" - 反应前体积: {original_liquid_volume:.2f}mL")
print(f" - 反应后体积: {final_liquid_volume:.2f}mL (氢化反应体积基本不变)")
print("🎊" * 20)
return action_sequence return action_sequence
@@ -379,7 +436,7 @@ def generate_hydrogenate_protocol(
# 测试函数 # 测试函数
def test_hydrogenate_protocol(): def test_hydrogenate_protocol():
"""测试氢化反应协议""" """测试氢化反应协议"""
print("=== HYDROGENATE PROTOCOL 测试 ===") print("🧪 === HYDROGENATE PROTOCOL 测试 ===")
# 测试温度解析 # 测试温度解析
test_temps = ["45 °C", "45°C", "45", "25 C", "invalid"] test_temps = ["45 °C", "45°C", "45", "25 C", "invalid"]
@@ -393,7 +450,7 @@ def test_hydrogenate_protocol():
parsed = parse_time(time) parsed = parse_time(time)
print(f"时间 '{time}' -> {parsed/3600:.1f} 小时") print(f"时间 '{time}' -> {parsed/3600:.1f} 小时")
print("测试完成") print("测试完成 🎉")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,12 +1,15 @@
import networkx as nx import networkx as nx
import re import re
import logging
from typing import List, Dict, Any, Tuple, Union from typing import List, Dict, Any, Tuple, Union
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): def debug_print(message):
"""调试输出""" """调试输出"""
print(f"💎 [RECRYSTALLIZE] {message}", flush=True) print(f"💎 [RECRYSTALLIZE] {message}", flush=True)
logger.info(f"[RECRYSTALLIZE] {message}")
def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: str = "mL") -> float: def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: str = "mL") -> float:
@@ -199,48 +202,63 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
def generate_recrystallize_protocol( def generate_recrystallize_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: dict, # 🔧 修改:从字符串改为字典类型
ratio: str, ratio: str,
solvent1: str, solvent1: str,
solvent2: str, solvent2: str,
vessel: str, volume: Union[str, float], # 支持字符串和数值
volume: Union[str, float], # 🔧 修改:支持字符串和数值
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成重结晶协议序列 - 支持单位 生成重结晶协议序列 - 支持vessel字典和体积运算
Args: Args:
G: 有向图,节点为容器和设备 G: 有向图,节点为容器和设备
vessel: 目标容器字典从XDL传入
ratio: 溶剂比例(如 "1:1", "3:7" ratio: 溶剂比例(如 "1:1", "3:7"
solvent1: 第一种溶剂名称 solvent1: 第一种溶剂名称
solvent2: 第二种溶剂名称 solvent2: 第二种溶剂名称
vessel: 目标容器
volume: 总体积(支持 "100 mL", "50", "2.5 L" 等) volume: 总体积(支持 "100 mL", "50", "2.5 L" 等)
**kwargs: 其他可选参数 **kwargs: 其他可选参数
Returns: Returns:
List[Dict[str, Any]]: 动作序列 List[Dict[str, Any]]: 动作序列
""" """
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
action_sequence = [] action_sequence = []
debug_print("💎" * 20) debug_print("💎" * 20)
debug_print("🚀 开始生成重结晶协议(支持单位)✨") debug_print("🚀 开始生成重结晶协议(支持vessel字典和体积运算)✨")
debug_print(f"📝 输入参数:") debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" ⚖️ 比例: {ratio}") debug_print(f" ⚖️ 比例: {ratio}")
debug_print(f" 🧪 溶剂1: {solvent1}") debug_print(f" 🧪 溶剂1: {solvent1}")
debug_print(f" 🧪 溶剂2: {solvent2}") debug_print(f" 🧪 溶剂2: {solvent2}")
debug_print(f" 🥽 容器: {vessel}")
debug_print(f" 💧 总体积: {volume} (类型: {type(volume)})") debug_print(f" 💧 总体积: {volume} (类型: {type(volume)})")
debug_print("💎" * 20) debug_print("💎" * 20)
# 🔧 新增:记录重结晶前的容器状态
debug_print("🔍 记录重结晶前容器状态...")
original_liquid_volume = 0.0
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list) and len(current_volume) > 0:
original_liquid_volume = current_volume[0]
elif isinstance(current_volume, (int, float)):
original_liquid_volume = current_volume
debug_print(f"📊 重结晶前液体体积: {original_liquid_volume:.2f}mL")
# 1. 验证目标容器存在 # 1. 验证目标容器存在
debug_print("📍 步骤1: 验证目标容器... 🔧") debug_print("📍 步骤1: 验证目标容器... 🔧")
if vessel not in G.nodes(): if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中! 😱") debug_print(f"❌ 目标容器 '{vessel_id}' 不存在于系统中! 😱")
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中") raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
debug_print(f"✅ 目标容器 '{vessel}' 验证通过 🎯") debug_print(f"✅ 目标容器 '{vessel_id}' 验证通过 🎯")
# 2. 🔧 新增:解析体积(支持单位) # 2. 解析体积(支持单位)
debug_print("📍 步骤2: 解析体积(支持单位)... 💧") debug_print("📍 步骤2: 解析体积(支持单位)... 💧")
final_volume = parse_volume_with_units(volume, "mL") final_volume = parse_volume_with_units(volume, "mL")
debug_print(f"🎯 体积解析完成: {volume}{final_volume}mL ✨") debug_print(f"🎯 体积解析完成: {volume}{final_volume}mL ✨")
@@ -281,18 +299,18 @@ def generate_recrystallize_protocol(
# 6. 验证路径存在 # 6. 验证路径存在
debug_print("📍 步骤6: 验证传输路径... 🛤️") debug_print("📍 步骤6: 验证传输路径... 🛤️")
try: try:
path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel) path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel_id) # 🔧 使用 vessel_id
debug_print(f" 🛤️ 溶剂1路径: {''.join(path1)}") debug_print(f" 🛤️ 溶剂1路径: {''.join(path1)}")
except nx.NetworkXNoPath: except nx.NetworkXNoPath:
debug_print(f" ❌ 溶剂1路径不可达: {solvent1_vessel}{vessel} 😞") debug_print(f" ❌ 溶剂1路径不可达: {solvent1_vessel}{vessel_id} 😞")
raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel}' 没有可用路径") raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
try: try:
path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel) path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel_id) # 🔧 使用 vessel_id
debug_print(f" 🛤️ 溶剂2路径: {''.join(path2)}") debug_print(f" 🛤️ 溶剂2路径: {''.join(path2)}")
except nx.NetworkXNoPath: except nx.NetworkXNoPath:
debug_print(f" ❌ 溶剂2路径不可达: {solvent2_vessel}{vessel} 😞") debug_print(f" ❌ 溶剂2路径不可达: {solvent2_vessel}{vessel_id} 😞")
raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel}' 没有可用路径") raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
# 7. 添加第一种溶剂 # 7. 添加第一种溶剂
debug_print("📍 步骤7: 添加第一种溶剂... 🧪") debug_print("📍 步骤7: 添加第一种溶剂... 🧪")
@@ -302,7 +320,7 @@ def generate_recrystallize_protocol(
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, to_vessel=vessel_id, # 🔧 使用 vessel_id
volume=volume1, # 使用解析后的体积 volume=volume1, # 使用解析后的体积
amount="", amount="",
time=0.0, time=0.0,
@@ -322,12 +340,45 @@ def generate_recrystallize_protocol(
debug_print(f" ❌ 溶剂1泵协议生成失败: {str(e)} 😭") debug_print(f" ❌ 溶剂1泵协议生成失败: {str(e)} 😭")
raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}") raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}")
# 🔧 新增:更新容器体积 - 添加溶剂1后
debug_print(" 🔧 更新容器体积 - 添加溶剂1后...")
new_volume_after_solvent1 = original_liquid_volume + volume1
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] = new_volume_after_solvent1
else:
vessel["data"]["liquid_volume"] = [new_volume_after_solvent1]
else:
vessel["data"]["liquid_volume"] = new_volume_after_solvent1
# 同时更新图中的容器数据
if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {}
vessel_node_data = G.nodes[vessel_id]['data']
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
if isinstance(current_node_volume, list):
if len(current_node_volume) > 0:
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume_after_solvent1
else:
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume_after_solvent1]
else:
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume_after_solvent1
debug_print(f" 📊 体积更新: {original_liquid_volume:.2f}mL + {volume1:.2f}mL = {new_volume_after_solvent1:.2f}mL")
# 8. 等待溶剂1稳定 # 8. 等待溶剂1稳定
debug_print(" ⏳ 添加溶剂1稳定等待...") debug_print(" ⏳ 添加溶剂1稳定等待...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
"time": 5.0, # 🕐 缩短等待时间10.0s → 5.0s "time": 5.0, # 缩短等待时间
"description": f"等待溶剂1 {solvent1} 稳定" "description": f"等待溶剂1 {solvent1} 稳定"
} }
}) })
@@ -341,7 +392,7 @@ def generate_recrystallize_protocol(
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, to_vessel=vessel_id, # 🔧 使用 vessel_id
volume=volume2, # 使用解析后的体积 volume=volume2, # 使用解析后的体积
amount="", amount="",
time=0.0, time=0.0,
@@ -361,12 +412,45 @@ def generate_recrystallize_protocol(
debug_print(f" ❌ 溶剂2泵协议生成失败: {str(e)} 😭") debug_print(f" ❌ 溶剂2泵协议生成失败: {str(e)} 😭")
raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}") raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}")
# 🔧 新增:更新容器体积 - 添加溶剂2后
debug_print(" 🔧 更新容器体积 - 添加溶剂2后...")
final_liquid_volume = new_volume_after_solvent1 + volume2
# 更新vessel字典中的体积
if "data" in vessel and "liquid_volume" in vessel["data"]:
current_volume = vessel["data"]["liquid_volume"]
if isinstance(current_volume, list):
if len(current_volume) > 0:
vessel["data"]["liquid_volume"][0] = final_liquid_volume
else:
vessel["data"]["liquid_volume"] = [final_liquid_volume]
else:
vessel["data"]["liquid_volume"] = final_liquid_volume
# 同时更新图中的容器数据
if vessel_id in G.nodes():
if 'data' not in G.nodes[vessel_id]:
G.nodes[vessel_id]['data'] = {}
vessel_node_data = G.nodes[vessel_id]['data']
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
if isinstance(current_node_volume, list):
if len(current_node_volume) > 0:
G.nodes[vessel_id]['data']['liquid_volume'][0] = final_liquid_volume
else:
G.nodes[vessel_id]['data']['liquid_volume'] = [final_liquid_volume]
else:
G.nodes[vessel_id]['data']['liquid_volume'] = final_liquid_volume
debug_print(f" 📊 最终体积: {new_volume_after_solvent1:.2f}mL + {volume2:.2f}mL = {final_liquid_volume:.2f}mL")
# 10. 等待溶剂2稳定 # 10. 等待溶剂2稳定
debug_print(" ⏳ 添加溶剂2稳定等待...") debug_print(" ⏳ 添加溶剂2稳定等待...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": { "action_kwargs": {
"time": 5.0, # 🕐 缩短等待时间10.0s → 5.0s "time": 5.0, # 缩短等待时间
"description": f"等待溶剂2 {solvent2} 稳定" "description": f"等待溶剂2 {solvent2} 稳定"
} }
}) })
@@ -375,7 +459,7 @@ def generate_recrystallize_protocol(
# 11. 等待重结晶完成 # 11. 等待重结晶完成
debug_print("📍 步骤9: 等待重结晶完成... 💎") debug_print("📍 步骤9: 等待重结晶完成... 💎")
# 🕐 模拟运行时间优化 # 模拟运行时间优化
debug_print(" ⏱️ 检查模拟运行时间限制...") debug_print(" ⏱️ 检查模拟运行时间限制...")
original_crystallize_time = 600.0 # 原始重结晶时间 original_crystallize_time = 600.0 # 原始重结晶时间
simulation_time_limit = 60.0 # 模拟运行时间限制60秒 simulation_time_limit = 60.0 # 模拟运行时间限制60秒
@@ -401,12 +485,15 @@ def generate_recrystallize_protocol(
if original_crystallize_time != final_crystallize_time: if original_crystallize_time != final_crystallize_time:
debug_print(f" 🎭 模拟优化说明: 原计划 {original_crystallize_time/60:.1f}分钟,实际模拟 {final_crystallize_time/60:.1f}分钟 ⚡") debug_print(f" 🎭 模拟优化说明: 原计划 {original_crystallize_time/60:.1f}分钟,实际模拟 {final_crystallize_time/60:.1f}分钟 ⚡")
# 🎊 总结 # 总结
debug_print("💎" * 20) debug_print("💎" * 20)
debug_print(f"🎉 重结晶协议生成完成! ✨") debug_print(f"🎉 重结晶协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}") debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 目标容器: {vessel}") debug_print(f"🥽 目标容器: {vessel_id}")
debug_print(f"💧 总体积: {final_volume}mL") 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"⚖️ 溶剂比例: {solvent1}:{solvent2} = {ratio1}:{ratio2}")
debug_print(f"🧪 溶剂1: {solvent1} ({volume1:.2f}mL)") debug_print(f"🧪 溶剂1: {solvent1} ({volume1:.2f}mL)")
debug_print(f"🧪 溶剂2: {solvent2} ({volume2:.2f}mL)") debug_print(f"🧪 溶剂2: {solvent2} ({volume2:.2f}mL)")
@@ -421,6 +508,13 @@ 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"]
for vol in test_volumes:
parsed = parse_volume_with_units(vol)
debug_print(f" 📊 体积 '{vol}' -> {parsed}mL")
# 测试比例解析 # 测试比例解析
debug_print("⚖️ 测试比例解析...") 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"]
@@ -430,6 +524,5 @@ def test_recrystallize_protocol():
debug_print("✅ 测试完成 🎉") debug_print("✅ 测试完成 🎉")
if __name__ == "__main__": if __name__ == "__main__":
test_recrystallize_protocol() test_recrystallize_protocol()

View File

@@ -1,12 +1,67 @@
import networkx as nx import networkx as nx
from typing import List, Dict, Any import logging
import sys
from typing import List, Dict, Any, Optional
from .pump_protocol import generate_pump_protocol_with_rinsing from .pump_protocol import generate_pump_protocol_with_rinsing
# 设置日志
logger = logging.getLogger(__name__)
# 确保输出编码为UTF-8
if hasattr(sys.stdout, 'reconfigure'):
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except:
pass
def debug_print(message): def debug_print(message):
"""调试输出""" """调试输出函数 - 支持中文"""
print(f"🔄 [RESET_HANDLING] {message}", flush=True) 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: def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
""" """
@@ -19,7 +74,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
Returns: Returns:
str: 溶剂容器ID str: 溶剂容器ID
""" """
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器... 🧪") debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器...")
# 构建可能的容器名称 # 构建可能的容器名称
possible_names = [ possible_names = [
@@ -33,30 +88,30 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
f"vessel_{solvent}", # vessel_methanol f"vessel_{solvent}", # vessel_methanol
] ]
debug_print(f"📋 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个) 📝") debug_print(f"🎯 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个)")
# 第一步:通过容器名称匹配 # 第一步:通过容器名称匹配
debug_print(" 🎯 步骤1: 精确名称匹配...") debug_print("📋 方法1: 精确名称匹配...")
for vessel_name in possible_names: for vessel_name in possible_names:
if vessel_name in G.nodes(): if vessel_name in G.nodes():
debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name}") debug_print(f" 通过名称匹配找到容器: {vessel_name}")
return vessel_name return vessel_name
debug_print(" 😞 精确名称匹配失败,尝试模糊匹配... 🔍") debug_print("⚠️ 精确名称匹配失败,尝试模糊匹配...")
# 第二步:通过模糊匹配 # 第二步:通过模糊匹配
debug_print(" 🔍 步骤2: 模糊名称匹配...") debug_print("📋 方法2: 模糊名称匹配...")
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 solvent.lower() in node_id.lower() or solvent.lower() in node_name: if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id}") debug_print(f" 通过模糊匹配找到容器: {node_id}")
return node_id return node_id
debug_print(" 😞 模糊匹配失败,尝试液体类型匹配... 🧪") debug_print("⚠️ 模糊匹配失败,尝试液体类型匹配...")
# 第三步:通过液体类型匹配 # 第三步:通过液体类型匹配
debug_print(" 🧪 步骤3: 液体类型匹配...") debug_print("📋 方法3: 液体类型匹配...")
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':
vessel_data = G.nodes[node_id].get('data', {}) vessel_data = G.nodes[node_id].get('data', {})
@@ -68,11 +123,11 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
reagent_name = vessel_data.get('reagent_name', '').lower() reagent_name = vessel_data.get('reagent_name', '').lower()
if solvent.lower() in liquid_type or solvent.lower() in reagent_name: if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id}") debug_print(f" 通过液体类型匹配找到容器: {node_id}")
return node_id return node_id
# 列出可用容器帮助调试 # 列出可用容器帮助调试
debug_print(" 📊 显示可用容器信息...") debug_print("📊 显示可用容器信息...")
available_containers = [] available_containers = []
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':
@@ -88,30 +143,31 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
'reagent_name': vessel_data.get('reagent_name', '') 'reagent_name': vessel_data.get('reagent_name', '')
}) })
debug_print(f" 📋 可用容器列表 (共{len(available_containers)}个):") debug_print(f"📋 可用容器列表 (共{len(available_containers)}个):")
for i, container in enumerate(available_containers[:5]): # 只显示前5个 for i, container in enumerate(available_containers[:5]): # 只显示前5个
debug_print(f" {i+1}. 🥽 {container['id']}: {container['name']}") debug_print(f" {i+1}. 🥽 {container['id']}: {container['name']}")
debug_print(f" 💧 液体: {container['liquids']}") debug_print(f" 💧 液体: {container['liquids']}")
debug_print(f" 🧪 试剂: {container['reagent_name']}") debug_print(f" 🧪 试剂: {container['reagent_name']}")
if len(available_containers) > 5: if len(available_containers) > 5:
debug_print(f" ... 还有 {len(available_containers)-5} 个容器 📦") debug_print(f" ... 还有 {len(available_containers)-5} 个容器")
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭") debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器")
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names[:3]}...") 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参数
**kwargs # 接收其他可能的参数但不使用 **kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成重置处理协议序列 生成重置处理协议序列 - 支持自定义容器
Args: Args:
G: 有向图,节点为容器和设备 G: 有向图,节点为容器和设备
solvent: 溶剂名称从XDL传入 solvent: 溶剂名称从XDL传入
vessel: 目标容器名称(可选,默认为 "main_reactor"
**kwargs: 其他可选参数,但不使用 **kwargs: 其他可选参数,但不使用
Returns: Returns:
@@ -119,51 +175,79 @@ def generate_reset_handling_protocol(
""" """
action_sequence = [] action_sequence = []
# 固定参数 # 🔧 修改支持自定义vessel参数
target_vessel = "main_reactor" # 默认目标容器 target_vessel = vessel if vessel is not None else "main_reactor" # 默认目标容器
volume = 50.0 # 默认体积 50 mL volume = 50.0 # 默认体积 50 mL
debug_print("🔄" * 20) debug_print("=" * 60)
debug_print("🚀 开始生成重置处理协议") debug_print("🚀 开始生成重置处理协议")
debug_print(f"📝 输入参数:") debug_print(f"📋 输入参数:")
debug_print(f" 🧪 溶剂: {solvent}") debug_print(f" 🧪 溶剂: {solvent}")
debug_print(f" 🥽 目标容器: {target_vessel}") debug_print(f" 🥽 目标容器: {target_vessel} {'(默认)' if vessel is None else '(指定)'}")
debug_print(f" 💧 体积: {volume} mL") debug_print(f" 💧 体积: {volume} mL")
debug_print(f" ⚙️ 其他参数: {kwargs}") debug_print(f" ⚙️ 其他参数: {kwargs}")
debug_print("🔄" * 20) debug_print("=" * 60)
# 添加初始日志
action_sequence.append(create_action_log(f"开始重置处理操作 - 容器: {target_vessel}", "🎬"))
action_sequence.append(create_action_log(f"使用溶剂: {solvent}", "🧪"))
action_sequence.append(create_action_log(f"重置体积: {volume}mL", "💧"))
if vessel is None:
action_sequence.append(create_action_log("使用默认目标容器: main_reactor", "⚙️"))
else:
action_sequence.append(create_action_log(f"使用指定目标容器: {vessel}", "🎯"))
# 1. 验证目标容器存在 # 1. 验证目标容器存在
debug_print("📍 步骤1: 验证目标容器... 🔧") debug_print("🔍 步骤1: 验证目标容器...")
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}' 不存在于系统中! 😱") debug_print(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}' 验证通过 🎯")
debug_print(f"✅ 目标容器 '{target_vessel}' 验证通过")
action_sequence.append(create_action_log(f"目标容器验证通过: {target_vessel}", ""))
# 2. 查找溶剂容器 # 2. 查找溶剂容器
debug_print("📍 步骤2: 查找溶剂容器... 🔍") debug_print("🔍 步骤2: 查找溶剂容器...")
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}", ""))
except ValueError as e: except ValueError as e:
debug_print(f"❌ 溶剂容器查找失败: {str(e)} 😭") debug_print(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: 验证传输路径... 🛤️") debug_print("🔍 步骤3: 验证传输路径...")
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)}") debug_print(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} 😞") debug_print(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: 转移溶剂... 🚰") debug_print("🔍 步骤4: 转移溶剂...")
debug_print(f" 🚛 开始转移: {solvent_vessel}{target_vessel}") action_sequence.append(create_action_log("开始溶剂转移操作...", "🚰"))
debug_print(f" 💧 转移体积: {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(" 🔄 生成泵送协议...") debug_print("🔄 生成泵送协议...")
action_sequence.append(create_action_log("正在生成泵送协议...", "🔄"))
pump_actions = generate_pump_protocol_with_rinsing( pump_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=solvent_vessel, from_vessel=solvent_vessel,
@@ -181,60 +265,104 @@ def generate_reset_handling_protocol(
) )
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)} 个操作)", ""))
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)}", ""))
raise ValueError(f"生成泵协议时出错: {str(e)}") raise ValueError(f"生成泵协议时出错: {str(e)}")
# 5. 等待溶剂稳定 # 5. 等待溶剂稳定
debug_print("📍 步骤5: 等待溶剂稳定...") debug_print("🔍 步骤5: 等待溶剂稳定...")
action_sequence.append(create_action_log("等待溶剂稳定...", ""))
# 🕐 模拟运行时间优化 # 模拟运行时间优化
debug_print(" ⏱️ 检查模拟运行时间限制...") debug_print("⏱️ 检查模拟运行时间限制...")
original_wait_time = 10.0 # 原始等待时间 original_wait_time = 10.0 # 原始等待时间
simulation_time_limit = 5.0 # 模拟运行时间限制5秒 simulation_time_limit = 5.0 # 模拟运行时间限制5秒
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") debug_print(f"🎮 模拟运行优化: {original_wait_time}s → {final_wait_time}s")
debug_print(f" 📊 时间缩短: {original_wait_time}s → {final_wait_time}s 🚀") action_sequence.append(create_action_log(f"时间优化: {original_wait_time}s → {final_wait_time}s", ""))
else: else:
debug_print(f" ✅ 时间在限制内: {final_wait_time}s 保持不变 🎯") debug_print(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",
"action_kwargs": { "action_kwargs": {
"time": final_wait_time, "time": final_wait_time,
"description": f"等待溶剂 {solvent} 稳定" + (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 ⏰✨") 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") debug_print(f"🎭 模拟优化说明: 原计划 {original_wait_time}s实际模拟 {final_wait_time}s")
action_sequence.append(create_action_log("应用模拟时间优化", "🎭"))
# 🎊 总结 # 总结
debug_print("🔄" * 20) debug_print("=" * 60)
debug_print(f"🎉 重置处理协议生成完成!") debug_print(f"🎉 重置处理协议生成完成!")
debug_print(f"📊 总动作数: {len(action_sequence)}") debug_print(f"📊 总结信息:")
debug_print(f"🧪 溶剂: {solvent}") debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f"🥽 源容器: {solvent_vessel}") debug_print(f" 🧪 溶剂: {solvent}")
debug_print(f"🥽 目标容器: {target_vessel}") debug_print(f" 🥽 源容器: {solvent_vessel}")
debug_print(f"💧 转移体积: {volume} mL") debug_print(f" 🥽 目标容器: {target_vessel} {'(默认)' if vessel is None else '(指定)'}")
debug_print(f"⏱️ 预计总时间: {(final_wait_time + 5):.0f} 秒 ⌛") debug_print(f" 💧 转移体积: {volume} mL")
debug_print(f"🎯 已添加 {volume} mL {solvent}{target_vessel} 🚰✨") debug_print(f" ⏱️ 预计总时间: {(final_wait_time + 5):.0f} ")
debug_print("🔄" * 20) debug_print(f" 🎯 操作结果: 已添加 {volume} mL {solvent}{target_vessel}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"重置处理完成: {target_vessel} (使用 {volume}mL {solvent})"
if vessel is None:
summary_msg += " [默认容器]"
else:
summary_msg += " [指定容器]"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence return action_sequence
# === 便捷函数 ===
def reset_main_reactor(G: nx.DiGraph, solvent: str = "methanol", **kwargs) -> List[Dict[str, Any]]:
"""重置主反应器 (默认行为)"""
debug_print(f"🔄 重置主反应器,使用溶剂: {solvent}")
return generate_reset_handling_protocol(G, solvent=solvent, vessel=None, **kwargs)
def reset_custom_vessel(G: nx.DiGraph, vessel: str, solvent: str = "methanol", **kwargs) -> List[Dict[str, Any]]:
"""重置指定容器"""
debug_print(f"🔄 重置指定容器: {vessel},使用溶剂: {solvent}")
return generate_reset_handling_protocol(G, solvent=solvent, vessel=vessel, **kwargs)
def reset_with_water(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]:
"""使用水重置容器"""
target = vessel or "main_reactor"
debug_print(f"💧 使用水重置容器: {target}")
return generate_reset_handling_protocol(G, solvent="water", vessel=vessel, **kwargs)
def reset_with_methanol(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]:
"""使用甲醇重置容器"""
target = vessel or "main_reactor"
debug_print(f"🧪 使用甲醇重置容器: {target}")
return generate_reset_handling_protocol(G, solvent="methanol", vessel=vessel, **kwargs)
def reset_with_ethanol(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]:
"""使用乙醇重置容器"""
target = vessel or "main_reactor"
debug_print(f"🧪 使用乙醇重置容器: {target}")
return generate_reset_handling_protocol(G, solvent="ethanol", vessel=vessel, **kwargs)
# 测试函数 # 测试函数
def test_reset_handling_protocol(): def test_reset_handling_protocol():
"""测试重置处理协议""" """测试重置处理协议"""
debug_print("🧪 === RESET HANDLING PROTOCOL 测试 ===") debug_print("=== 重置处理协议增强中文版测试 ===")
# 测试溶剂名称 # 测试溶剂名称
debug_print("🧪 测试常用溶剂名称...") debug_print("🧪 测试常用溶剂名称...")
@@ -242,8 +370,18 @@ def test_reset_handling_protocol():
for solvent in test_solvents: for solvent in test_solvents:
debug_print(f" 🔍 测试溶剂: {solvent}") debug_print(f" 🔍 测试溶剂: {solvent}")
debug_print("✅ 测试完成 🎉") # 测试容器参数
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

@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
def debug_print(message): def debug_print(message):
"""调试输出""" """调试输出"""
print(f"[RUN_COLUMN] {message}", flush=True) print(f"🏛️ [RUN_COLUMN] {message}", flush=True)
logger.info(f"[RUN_COLUMN] {message}") logger.info(f"[RUN_COLUMN] {message}")
def parse_percentage(pct_str: str) -> float: def parse_percentage(pct_str: str) -> float:
@@ -25,7 +25,7 @@ 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}'") debug_print(f"🔍 解析百分比: '{pct_str}'")
# 移除百分号和空格 # 移除百分号和空格
pct_clean = re.sub(r'[%\s]', '', pct_str) pct_clean = re.sub(r'[%\s]', '', pct_str)
@@ -34,7 +34,7 @@ def parse_percentage(pct_str: str) -> float:
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}%") debug_print(f"百分比解析结果: {value}%")
return value return value
debug_print(f"⚠️ 无法解析百分比: '{pct_str}'返回0.0") debug_print(f"⚠️ 无法解析百分比: '{pct_str}'返回0.0")
@@ -54,7 +54,7 @@ def parse_ratio(ratio_str: str) -> tuple:
return (50.0, 50.0) # 默认1:1 return (50.0, 50.0) # 默认1:1
ratio_str = ratio_str.strip() ratio_str = ratio_str.strip()
debug_print(f"解析比例: '{ratio_str}'") debug_print(f"🔍 解析比例: '{ratio_str}'")
# 支持多种分隔符:: / - # 支持多种分隔符:: / -
if ':' in ratio_str: if ':' in ratio_str:
@@ -79,7 +79,7 @@ def parse_ratio(ratio_str: str) -> tuple:
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}%") 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)}")
@@ -101,12 +101,12 @@ def parse_rf_value(rf_str: str) -> float:
return 0.3 # 默认Rf值 return 0.3 # 默认Rf值
rf_str = rf_str.strip().lower() rf_str = rf_str.strip().lower()
debug_print(f"解析Rf值: '{rf_str}'") debug_print(f"🔍 解析Rf值: '{rf_str}'")
# 处理未知Rf值 # 处理未知Rf值
if rf_str in ['?', 'unknown', 'tbd', 'to be determined']: if rf_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_rf = 0.3 default_rf = 0.3
debug_print(f"检测到未知Rf值使用默认值: {default_rf}") debug_print(f"检测到未知Rf值使用默认值: {default_rf}")
return default_rf return default_rf
# 提取数字 # 提取数字
@@ -117,7 +117,7 @@ def parse_rf_value(rf_str: str) -> float:
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)) # 限制在0-1范围
debug_print(f"Rf值解析结果: {value}") debug_print(f"Rf值解析结果: {value}")
return value return value
debug_print(f"⚠️ 无法解析Rf值: '{rf_str}'使用默认值0.3") debug_print(f"⚠️ 无法解析Rf值: '{rf_str}'使用默认值0.3")
@@ -125,7 +125,7 @@ def parse_rf_value(rf_str: str) -> float:
def find_column_device(G: nx.DiGraph) -> str: def find_column_device(G: nx.DiGraph) -> str:
"""查找柱层析设备""" """查找柱层析设备"""
debug_print("查找柱层析设备...") debug_print("🔍 查找柱层析设备...")
# 查找虚拟柱设备 # 查找虚拟柱设备
for node in G.nodes(): for node in G.nodes():
@@ -133,14 +133,14 @@ def find_column_device(G: nx.DiGraph) -> str:
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直接转移")
@@ -148,13 +148,13 @@ def find_column_device(G: nx.DiGraph) -> str:
def find_column_vessel(G: nx.DiGraph, column: str) -> str: def find_column_vessel(G: nx.DiGraph, column: str) -> str:
"""查找柱容器""" """查找柱容器"""
debug_print(f"查找柱容器: '{column}'") debug_print(f"🔍 查找柱容器: '{column}'")
# 直接检查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}") debug_print(f"🎉 找到柱容器: {column}")
return column return column
# 尝试常见的命名规则 # 尝试常见的命名规则
@@ -174,7 +174,7 @@ 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}") debug_print(f"🎉 找到柱容器: {vessel_name}")
return vessel_name return vessel_name
debug_print(f"⚠️ 未找到柱容器,将直接在源容器中进行分离") debug_print(f"⚠️ 未找到柱容器,将直接在源容器中进行分离")
@@ -186,7 +186,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
return "" return ""
solvent = solvent.strip().replace(' ', '_').lower() solvent = solvent.strip().replace(' ', '_').lower()
debug_print(f"查找溶剂容器: '{solvent}'") debug_print(f"🔍 查找溶剂容器: '{solvent}'")
# 🔧 方法1直接搜索 data.reagent_name # 🔧 方法1直接搜索 data.reagent_name
for node in G.nodes(): for node in G.nodes():
@@ -200,16 +200,16 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
# 检查 data.reagent_name 和 config.reagent # 检查 data.reagent_name 和 config.reagent
if reagent_name == solvent or reagent_config == solvent: if reagent_name == solvent or reagent_config == solvent:
debug_print(f" 通过reagent_name找到溶剂容器: {node} (reagent: {reagent_name or reagent_config})") debug_print(f"🎉 通过reagent_name找到溶剂容器: {node} (reagent: {reagent_name or reagent_config})")
return node return node
# 模糊匹配 reagent_name # 模糊匹配 reagent_name
if solvent in reagent_name or reagent_name in solvent: if solvent in reagent_name or reagent_name in solvent:
debug_print(f" 通过reagent_name模糊匹配到溶剂容器: {node} (reagent: {reagent_name})") debug_print(f"🎉 通过reagent_name模糊匹配到溶剂容器: {node} (reagent: {reagent_name})")
return node return node
if solvent in reagent_config or reagent_config in solvent: if solvent in reagent_config or reagent_config in solvent:
debug_print(f" 通过config.reagent模糊匹配到溶剂容器: {node} (reagent: {reagent_config})") debug_print(f"🎉 通过config.reagent模糊匹配到溶剂容器: {node} (reagent: {reagent_config})")
return node return node
# 🔧 方法2常见的溶剂容器命名规则 # 🔧 方法2常见的溶剂容器命名规则
@@ -227,7 +227,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: 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}") debug_print(f"🎉 通过命名规则找到溶剂容器: {vessel_name}")
return vessel_name return vessel_name
# 🔧 方法3节点名称模糊匹配 # 🔧 方法3节点名称模糊匹配
@@ -235,7 +235,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
node_type = G.nodes[node].get('type', '') node_type = G.nodes[node].get('type', '')
if node_type == 'container': if node_type == 'container':
if ('flask_' in node or 'bottle_' in node or 'reagent_' in node) and solvent in node.lower(): if ('flask_' in node or 'bottle_' in node or 'reagent_' in node) and solvent in node.lower():
debug_print(f" 通过节点名称模糊匹配到溶剂容器: {node}") debug_print(f"🎉 通过节点名称模糊匹配到溶剂容器: {node}")
return node return node
# 🔧 方法4特殊溶剂名称映射 # 🔧 方法4特殊溶剂名称映射
@@ -253,84 +253,118 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
# 查找映射的同义词 # 查找映射的同义词
for canonical_name, synonyms in solvent_mapping.items(): for canonical_name, synonyms in solvent_mapping.items():
if solvent in synonyms: if solvent in synonyms:
debug_print(f"检测到溶剂同义词: '{solvent}' -> '{canonical_name}'") debug_print(f"🔍 检测到溶剂同义词: '{solvent}' -> '{canonical_name}'")
return find_solvent_vessel(G, canonical_name) # 递归搜索 return find_solvent_vessel(G, canonical_name) # 递归搜索
debug_print(f"⚠️ 未找到溶剂 '{solvent}' 的容器") debug_print(f"⚠️ 未找到溶剂 '{solvent}' 的容器")
return "" return ""
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float: def get_vessel_liquid_volume(vessel: dict) -> float:
"""获取容器中的液体体积 - 增强版""" """
if vessel not in G.nodes(): 获取容器中的液体体积 - 支持vessel字典
debug_print(f"⚠️ 节点 '{vessel}' 不存在")
Args:
vessel: 容器字典
Returns:
float: 液体体积mL
"""
if not vessel or "data" not in vessel:
debug_print(f"⚠️ 容器数据为空,返回 0.0mL")
return 0.0 return 0.0
node_type = G.nodes[vessel].get('type', '') vessel_data = vessel["data"]
vessel_data = G.nodes[vessel].get('data', {}) vessel_id = vessel.get("id", "unknown")
debug_print(f"读取节点 '{vessel}' (类型: {node_type}) 体积数据: {vessel_data}") debug_print(f"🔍 读取容器 '{vessel_id}' 体积数据: {vessel_data}")
# 🔧 如果是设备类型,尝试查找关联的容器 # 检查liquid_volume字段
if node_type == 'device': if "liquid_volume" in vessel_data:
debug_print(f"'{vessel}' 是设备,尝试查找关联容器...") liquid_volume = vessel_data["liquid_volume"]
# 查找是否有内置容器数据 # 处理列表格式
config_data = G.nodes[vessel].get('config', {}) if isinstance(liquid_volume, list):
if 'volume' in config_data: if len(liquid_volume) > 0:
default_volume = config_data.get('volume', 50.0) volume = liquid_volume[0]
debug_print(f"使用设备默认容量: {default_volume}mL") if isinstance(volume, (int, float)):
return default_volume debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (列表格式)")
return float(volume)
# 对于旋蒸等设备,使用默认值 # 处理直接数值格式
if 'rotavap' in vessel.lower(): elif isinstance(liquid_volume, (int, float)):
default_volume = 50.0 debug_print(f"✅ 容器 '{vessel_id}' 体积: {liquid_volume}mL (数值格式)")
debug_print(f"旋蒸设备使用默认容量: {default_volume}mL") return float(liquid_volume)
return default_volume
debug_print(f"⚠️ 设备 '{vessel}' 无法确定容量返回0")
return 0.0
# 🔧 如果是容器类型,正常读取体积 # 检查其他可能的体积字段
total_volume = 0.0 volume_keys = ['current_volume', 'total_volume', 'volume']
for key in volume_keys:
# 方法1检查液体列表 if key in vessel_data:
liquids = vessel_data.get('liquid', [])
if isinstance(liquids, list):
for liquid in liquids:
if isinstance(liquid, dict):
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
total_volume += volume
# 方法2检查直接体积字段
if total_volume == 0.0:
volume_keys = ['current_volume', 'total_volume', 'volume', 'liquid_volume']
for key in volume_keys:
if key in vessel_data:
try:
total_volume = float(vessel_data[key])
if total_volume > 0:
break
except (ValueError, TypeError):
continue
# 方法3检查配置中的初始体积
if total_volume == 0.0:
config_data = G.nodes[vessel].get('config', {})
if 'current_volume' in config_data:
try: try:
total_volume = float(config_data['current_volume']) volume = float(vessel_data[key])
if volume > 0:
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (字段: {key})")
return volume
except (ValueError, TypeError): except (ValueError, TypeError):
pass continue
debug_print(f"容器 '{vessel}' 体积: {total_volume}mL") debug_print(f"⚠️ 无法获取容器 '{vessel_id}' 体积,返回默认值 50.0mL")
return total_volume 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"🧮 溶剂体积计算: 总体积{total_volume}mL")
debug_print(f" - 溶剂1: {pct1}% = {volume1}mL") debug_print(f" - 溶剂1: {pct1}% = {volume1}mL")
debug_print(f" - 溶剂2: {pct2}% = {volume2}mL") debug_print(f" - 溶剂2: {pct2}% = {volume2}mL")
@@ -338,8 +372,8 @@ def calculate_solvent_volumes(total_volume: float, pct1: float, pct2: float) ->
def generate_run_column_protocol( def generate_run_column_protocol(
G: nx.DiGraph, G: nx.DiGraph,
from_vessel: str, from_vessel: dict, # 🔧 修改:从字符串改为字典类型
to_vessel: str, to_vessel: dict, # 🔧 修改:从字符串改为字典类型
column: str, column: str,
rf: str = "", rf: str = "",
pct1: str = "", pct1: str = "",
@@ -350,14 +384,12 @@ def generate_run_column_protocol(
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成柱层析分离的协议序列 - 增强版 生成柱层析分离的协议序列 - 支持vessel字典和体积运算
支持新版XDL的所有参数具有高兼容性和容错性
Args: Args:
G: 有向图,节点为设备和容器,边为流体管道 G: 有向图,节点为设备和容器,边为流体管道
from_vessel: 源容器的名称,即样品起始所在的容器(必需 from_vessel: 源容器字典从XDL传入
to_vessel: 目标容器的名称,分离后的样品要到达的容器(必需 to_vessel: 目标容器字典从XDL传入
column: 所使用的柱子的名称(必需) column: 所使用的柱子的名称(必需)
rf: Rf值可选支持 "?" 表示未知) rf: Rf值可选支持 "?" 表示未知)
pct1: 第一种溶剂百分比(如 "40 %",可选) pct1: 第一种溶剂百分比(如 "40 %",可选)
@@ -371,51 +403,60 @@ def generate_run_column_protocol(
List[Dict[str, Any]]: 柱层析分离操作的动作序列 List[Dict[str, Any]]: 柱层析分离操作的动作序列
""" """
debug_print("=" * 60) # 🔧 核心修改从字典中提取容器ID
debug_print("开始生成柱层析协议") from_vessel_id = from_vessel["id"]
debug_print(f"输入参数:") to_vessel_id = to_vessel["id"]
debug_print(f" - from_vessel: '{from_vessel}'")
debug_print(f" - to_vessel: '{to_vessel}'") debug_print("🏛️" * 20)
debug_print(f" - column: '{column}'") debug_print("🚀 开始生成柱层析协议支持vessel字典和体积运算")
debug_print(f" - rf: '{rf}'") debug_print(f"📝 输入参数:")
debug_print(f" - pct1: '{pct1}'") debug_print(f" 🥽 from_vessel: {from_vessel} (ID: {from_vessel_id})")
debug_print(f" - pct2: '{pct2}'") debug_print(f" 🥽 to_vessel: {to_vessel} (ID: {to_vessel_id})")
debug_print(f" - solvent1: '{solvent1}'") debug_print(f" 🏛️ column: '{column}'")
debug_print(f" - solvent2: '{solvent2}'") debug_print(f" 📊 rf: '{rf}'")
debug_print(f" - ratio: '{ratio}'") debug_print(f" 🧪 溶剂配比: pct1='{pct1}', pct2='{pct2}', ratio='{ratio}'")
debug_print(f" - 其他参数: {kwargs}") debug_print(f" 🧪 溶剂名称: solvent1='{solvent1}', solvent2='{solvent2}'")
debug_print("=" * 60) debug_print("🏛️" * 20)
action_sequence = [] action_sequence = []
# === 参数验证 === # 🔧 新增:记录柱层析前的容器状态
debug_print("步骤1: 参数验证...") debug_print("🔍 记录柱层析前容器状态...")
original_from_volume = get_vessel_liquid_volume(from_vessel)
original_to_volume = get_vessel_liquid_volume(to_vessel)
if not from_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: # 🔧 使用 from_vessel_id
raise ValueError("from_vessel 参数不能为空") raise ValueError("from_vessel 参数不能为空")
if not to_vessel: if not to_vessel_id: # 🔧 使用 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 not in G.nodes(): if from_vessel_id not in G.nodes(): # 🔧 使用 from_vessel_id
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中") raise ValueError(f"源容器 '{from_vessel_id}' 不存在于系统中")
if to_vessel not in G.nodes(): if to_vessel_id not in G.nodes(): # 🔧 使用 to_vessel_id
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中") raise ValueError(f"目标容器 '{to_vessel_id}' 不存在于系统中")
debug_print("✅ 基本参数验证通过") debug_print("✅ 基本参数验证通过")
# === 参数解析 === # === 参数解析 ===
debug_print("步骤2: 参数解析...") debug_print("📍 步骤2: 参数解析...")
# 解析Rf值 # 解析Rf值
final_rf = parse_rf_value(rf) final_rf = parse_rf_value(rf)
debug_print(f"最终Rf值: {final_rf}") debug_print(f"🎯 最终Rf值: {final_rf}")
# 解析溶剂比例ratio优先级高于pct1/pct2 # 解析溶剂比例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}%") 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
@@ -428,16 +469,16 @@ 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}%") 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"🧪 最终溶剂: {final_solvent1} : {final_solvent2}")
# === 查找设备和容器 === # === 查找设备和容器 ===
debug_print("步骤3: 查找设备和容器...") debug_print("📍 步骤3: 查找设备和容器...")
# 查找柱层析设备 # 查找柱层析设备
column_device_id = find_column_device(G) column_device_id = find_column_device(G)
@@ -449,16 +490,16 @@ def generate_run_column_protocol(
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"🔧 设备映射:")
debug_print(f" - 柱设备: '{column_device_id}'") debug_print(f" - 柱设备: '{column_device_id}'")
debug_print(f" - 柱容器: '{column_vessel}'") debug_print(f" - 柱容器: '{column_vessel}'")
debug_print(f" - 溶剂1容器: '{solvent1_vessel}'") debug_print(f" - 溶剂1容器: '{solvent1_vessel}'")
debug_print(f" - 溶剂2容器: '{solvent2_vessel}'") debug_print(f" - 溶剂2容器: '{solvent2_vessel}'")
# === 获取源容器体积 === # === 获取源容器体积 ===
debug_print("步骤4: 获取源容器体积...") debug_print("📍 步骤4: 获取源容器体积...")
source_volume = get_vessel_liquid_volume(G, from_vessel) 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") debug_print(f"⚠️ 无法获取源容器体积,使用默认值: {source_volume}mL")
@@ -466,7 +507,7 @@ def generate_run_column_protocol(
debug_print(f"✅ 源容器体积: {source_volume}mL") debug_print(f"✅ 源容器体积: {source_volume}mL")
# === 计算溶剂体积 === # === 计算溶剂体积 ===
debug_print("步骤5: 计算溶剂体积...") debug_print("📍 步骤5: 计算溶剂体积...")
# 洗脱溶剂通常是样品体积的2-5倍 # 洗脱溶剂通常是样品体积的2-5倍
total_elution_volume = source_volume * 3.0 total_elution_volume = source_volume * 3.0
@@ -475,17 +516,22 @@ def generate_run_column_protocol(
) )
# === 执行柱层析流程 === # === 执行柱层析流程 ===
debug_print("步骤6: 执行柱层析流程...") debug_print("📍 步骤6: 执行柱层析流程...")
# 🔧 新增:体积变化跟踪变量
current_from_volume = source_volume
current_to_volume = original_to_volume
current_column_volume = 0.0
try: try:
# 步骤6.1: 样品上柱(如果有独立的柱容器) # 步骤6.1: 样品上柱(如果有独立的柱容器)
if column_vessel and column_vessel != from_vessel: if column_vessel and column_vessel != from_vessel_id: # 🔧 使用 from_vessel_id
debug_print(f"6.1: 样品上柱 - {source_volume}mL 从 {from_vessel}{column_vessel}") 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, from_vessel=from_vessel_id, # 🔧 使用 from_vessel_id
to_vessel=column_vessel, to_vessel=column_vessel,
volume=source_volume, volume=source_volume,
flowrate=1.0, # 慢速上柱 flowrate=1.0, # 慢速上柱
@@ -496,15 +542,29 @@ def generate_run_column_protocol(
) )
action_sequence.extend(sample_transfer_actions) action_sequence.extend(sample_transfer_actions)
debug_print(f"✅ 样品上柱完成,添加了 {len(sample_transfer_actions)} 个动作") debug_print(f"✅ 样品上柱完成,添加了 {len(sample_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 样品转移到柱上
current_from_volume = 0.0 # 源容器体积变为0
current_column_volume = source_volume # 柱容器体积增加
update_vessel_volume(from_vessel, G, current_from_volume, "样品上柱后,源容器清空")
# 如果柱容器在图中,也更新其体积
if column_vessel in G.nodes():
if 'data' not in G.nodes[column_vessel]:
G.nodes[column_vessel]['data'] = {}
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume
debug_print(f"📊 柱容器 '{column_vessel}' 体积更新为: {current_column_volume:.2f}mL")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 样品上柱失败: {str(e)}") debug_print(f"⚠️ 样品上柱失败: {str(e)}")
# 步骤6.2: 添加洗脱溶剂1如果有溶剂容器 # 步骤6.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}") debug_print(f"📍 6.2: 添加洗脱溶剂1 - {solvent1_volume:.1f}mL {final_solvent1}")
try: try:
target_vessel = column_vessel if column_vessel else from_vessel target_vessel = column_vessel if column_vessel else from_vessel_id # 🔧 使用 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,
@@ -515,15 +575,26 @@ def generate_run_column_protocol(
) )
action_sequence.extend(solvent1_transfer_actions) action_sequence.extend(solvent1_transfer_actions)
debug_print(f"✅ 溶剂1添加完成添加了 {len(solvent1_transfer_actions)} 个动作") debug_print(f"✅ 溶剂1添加完成添加了 {len(solvent1_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 添加溶剂1
if target_vessel == column_vessel:
current_column_volume += solvent1_volume
if column_vessel in G.nodes():
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume
debug_print(f"📊 柱容器体积增加: +{solvent1_volume:.2f}mL = {current_column_volume:.2f}mL")
elif target_vessel == from_vessel_id:
current_from_volume += solvent1_volume
update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂1后")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 溶剂1添加失败: {str(e)}") debug_print(f"⚠️ 溶剂1添加失败: {str(e)}")
# 步骤6.3: 添加洗脱溶剂2如果有溶剂容器 # 步骤6.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}") debug_print(f"📍 6.3: 添加洗脱溶剂2 - {solvent2_volume:.1f}mL {final_solvent2}")
try: try:
target_vessel = column_vessel if column_vessel else from_vessel target_vessel = column_vessel if column_vessel else from_vessel_id # 🔧 使用 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,
@@ -534,19 +605,30 @@ def generate_run_column_protocol(
) )
action_sequence.extend(solvent2_transfer_actions) action_sequence.extend(solvent2_transfer_actions)
debug_print(f"✅ 溶剂2添加完成添加了 {len(solvent2_transfer_actions)} 个动作") debug_print(f"✅ 溶剂2添加完成添加了 {len(solvent2_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 添加溶剂2
if target_vessel == column_vessel:
current_column_volume += solvent2_volume
if column_vessel in G.nodes():
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume
debug_print(f"📊 柱容器体积增加: +{solvent2_volume:.2f}mL = {current_column_volume:.2f}mL")
elif target_vessel == from_vessel_id:
current_from_volume += solvent2_volume
update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂2后")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 溶剂2添加失败: {str(e)}") debug_print(f"⚠️ 溶剂2添加失败: {str(e)}")
# 步骤6.4: 使用柱层析设备执行分离(如果有设备) # 步骤6.4: 使用柱层析设备执行分离(如果有设备)
if column_device_id: if column_device_id:
debug_print(f"6.4: 使用柱层析设备执行分离") 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, "from_vessel": from_vessel_id, # 🔧 使用 from_vessel_id
"to_vessel": to_vessel, "to_vessel": to_vessel_id, # 🔧 使用 to_vessel_id
"column": column, "column": column,
"rf": rf, "rf": rf,
"pct1": pct1, "pct1": pct1,
@@ -560,7 +642,7 @@ def generate_run_column_protocol(
debug_print(f"✅ 柱层析设备动作已添加") debug_print(f"✅ 柱层析设备动作已添加")
# 等待分离完成 # 等待分离完成
separation_time = max(30, 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}
@@ -568,41 +650,61 @@ def generate_run_column_protocol(
debug_print(f"✅ 等待分离完成: {separation_time}") debug_print(f"✅ 等待分离完成: {separation_time}")
# 步骤6.5: 产物收集(从柱容器到目标容器) # 步骤6.5: 产物收集(从柱容器到目标容器)
if column_vessel and column_vessel != to_vessel: if column_vessel and column_vessel != to_vessel_id: # 🔧 使用 to_vessel_id
debug_print(f"6.5: 产物收集 - 从 {column_vessel}{to_vessel}") debug_print(f"📍 6.5: 产物收集 - 从 {column_vessel}{to_vessel_id}")
try: try:
# 估算产物体积原始样品体积的70-90% # 估算产物体积原始样品体积的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, to_vessel=to_vessel_id, # 🔧 使用 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)} 个动作") debug_print(f"✅ 产物收集完成,添加了 {len(product_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 产物收集到目标容器
current_to_volume += product_volume
current_column_volume -= product_volume # 柱容器体积减少
update_vessel_volume(to_vessel, G, current_to_volume, "产物收集后")
# 更新柱容器体积
if column_vessel in G.nodes():
G.nodes[column_vessel]['data']['liquid_volume'] = max(0.0, current_column_volume)
debug_print(f"📊 柱容器体积减少: -{product_volume:.2f}mL = {current_column_volume:.2f}mL")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 产物收集失败: {str(e)}") debug_print(f"⚠️ 产物收集失败: {str(e)}")
# 步骤6.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}{to_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, from_vessel=from_vessel_id, # 🔧 使用 from_vessel_id
to_vessel=to_vessel, to_vessel=to_vessel_id, # 🔧 使用 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)} 个动作") debug_print(f"✅ 直接转移完成,添加了 {len(direct_transfer_actions)} 个动作")
# 🔧 新增:更新体积 - 直接转移
current_from_volume = 0.0 # 源容器清空
current_to_volume += source_volume # 目标容器增加
update_vessel_volume(from_vessel, G, current_from_volume, "直接转移后,源容器清空")
update_vessel_volume(to_vessel, G, current_to_volume, "直接转移后,目标容器增加")
except Exception as e: except Exception as e:
debug_print(f"⚠️ 直接转移失败: {str(e)}") debug_print(f"⚠️ 直接转移失败: {str(e)}")
@@ -624,17 +726,77 @@ def generate_run_column_protocol(
} }
}) })
# 🔧 新增:柱层析完成后的最终状态报告
final_from_volume = get_vessel_liquid_volume(from_vessel)
final_to_volume = get_vessel_liquid_volume(to_vessel)
# 🎊 总结 # 🎊 总结
debug_print("🧪" * 20) debug_print("🏛️" * 20)
debug_print(f"🎉 柱层析协议生成完成! ✨") debug_print(f"🎉 柱层析协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}") debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 路径: {from_vessel}{to_vessel}") debug_print(f"🥽 路径: {from_vessel_id}{to_vessel_id}")
debug_print(f"🏛️ 柱子: {column}") debug_print(f"🏛️ 柱子: {column}")
debug_print(f"🧪 溶剂: {final_solvent1}:{final_solvent2}") debug_print(f"🧪 溶剂: {final_solvent1}:{final_solvent2} = {final_pct1:.1f}%:{final_pct2:.1f}%")
debug_print("🧪" * 20) 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,
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,
solvent1="ethyl_acetate", solvent2="hexane", ratio=ratio)
def generate_methanol_dcm_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
column: str, ratio: str = "5:95") -> List[Dict[str, Any]]:
"""甲醇-二氯甲烷柱层析"""
from_vessel_id = from_vessel["id"]
to_vessel_id = to_vessel["id"]
debug_print(f"🧪🧪 甲醇-DCM柱层析: {from_vessel_id}{to_vessel_id} @ {ratio}")
return generate_run_column_protocol(G, from_vessel, to_vessel, column,
solvent1="methanol", solvent2="dichloromethane", ratio=ratio)
def generate_gradient_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
column: str, start_ratio: str = "10:90",
end_ratio: str = "50:50") -> 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} ({start_ratio}{end_ratio})")
# 使用中间比例作为近似
return generate_run_column_protocol(G, from_vessel, to_vessel, column, ratio="30:70")
def generate_polar_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
column: str) -> 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}")
return generate_run_column_protocol(G, from_vessel, to_vessel, column,
solvent1="ethyl_acetate", solvent2="hexane", ratio="70:30")
def generate_nonpolar_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
column: str) -> 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}")
return generate_run_column_protocol(G, from_vessel, to_vessel, column,
solvent1="ethyl_acetate", solvent2="hexane", ratio="5:95")
# 测试函数 # 测试函数
def test_run_column_protocol(): def test_run_column_protocol():
"""测试柱层析协议""" """测试柱层析协议"""

View File

@@ -20,18 +20,18 @@ def debug_print(message):
try: try:
# 确保消息是字符串格式 # 确保消息是字符串格式
safe_message = str(message) safe_message = str(message)
print(f"[分离协议] {safe_message}", flush=True) print(f"🌀 [SEPARATE] {safe_message}", flush=True)
logger.info(f"[分离协议] {safe_message}") logger.info(f"[SEPARATE] {safe_message}")
except UnicodeEncodeError: except UnicodeEncodeError:
# 如果编码失败,尝试替换不支持的字符 # 如果编码失败,尝试替换不支持的字符
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8') safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
print(f"[分离协议] {safe_message}", flush=True) print(f"🌀 [SEPARATE] {safe_message}", flush=True)
logger.info(f"[分离协议] {safe_message}") logger.info(f"[SEPARATE] {safe_message}")
except Exception as e: except Exception as e:
# 最后的安全措施 # 最后的安全措施
fallback_message = f"日志输出错误: {repr(message)}" fallback_message = f"日志输出错误: {repr(message)}"
print(f"[分离协议] {fallback_message}", flush=True) print(f"🌀 [SEPARATE] {fallback_message}", flush=True)
logger.info(f"[分离协议] {fallback_message}") logger.info(f"[SEPARATE] {fallback_message}")
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]: def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志 - 支持中文和emoji""" """创建一个动作日志 - 支持中文和emoji"""
@@ -264,19 +264,119 @@ def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
debug_print("❌ 未找到搅拌器") debug_print("❌ 未找到搅拌器")
return "" 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 generate_separate_protocol( def generate_separate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
# 🔧 基础参数支持XDL的vessel参数 # 🔧 基础参数支持XDL的vessel参数
vessel: str = "", # XDL: 分离容器 vessel: dict = None, # 🔧 修改:从字符串改为字典类型
purpose: str = "separate", # 分离目的 purpose: str = "separate", # 分离目的
product_phase: str = "top", # 产物相 product_phase: str = "top", # 产物相
# 🔧 可选的详细参数 # 🔧 可选的详细参数
from_vessel: str = "", # 源容器通常在separate前已经transfer了 from_vessel: Union[str, dict] = "", # 源容器通常在separate前已经transfer了
separation_vessel: str = "", # 分离容器与vessel同义 separation_vessel: Union[str, dict] = "", # 分离容器与vessel同义
to_vessel: str = "", # 目标容器(可选) to_vessel: Union[str, dict] = "", # 目标容器(可选)
waste_phase_to_vessel: str = "", # 废相目标容器 waste_phase_to_vessel: Union[str, dict] = "", # 废相目标容器
product_vessel: str = "", # XDL: 产物容器与to_vessel同义 product_vessel: Union[str, dict] = "", # XDL: 产物容器与to_vessel同义
waste_vessel: str = "", # XDL: 废液容器与waste_phase_to_vessel同义 waste_vessel: Union[str, dict] = "", # XDL: 废液容器与waste_phase_to_vessel同义
# 🔧 溶剂相关参数 # 🔧 溶剂相关参数
solvent: str = "", # 溶剂名称 solvent: str = "", # 溶剂名称
solvent_volume: Union[str, float] = 0.0, # 溶剂体积 solvent_volume: Union[str, float] = 0.0, # 溶剂体积
@@ -290,10 +390,10 @@ def generate_separate_protocol(
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成分离操作的协议序列 - 增强中文版 生成分离操作的协议序列 - 支持vessel字典和体积运算
支持XDL参数格式 支持XDL参数格式
- vessel: 分离容器(必需) - vessel: 分离容器字典(必需)
- purpose: "wash", "extract", "separate" - purpose: "wash", "extract", "separate"
- product_phase: "top", "bottom" - product_phase: "top", "bottom"
- product_vessel: 产物收集容器 - product_vessel: 产物收集容器
@@ -310,10 +410,20 @@ def generate_separate_protocol(
5. 重复指定次数 5. 重复指定次数
""" """
debug_print("=" * 60) # 🔧 核心修改vessel参数兼容处理
debug_print("🧪 开始生成分离协议 - 增强中文版") if vessel is None:
debug_print(f"📋 原始参数:") if isinstance(separation_vessel, dict):
debug_print(f" 🥼 容器: '{vessel}'") vessel = separation_vessel
else:
raise ValueError("必须提供vessel字典参数")
# 🔧 核心修改从字典中提取容器ID
vessel_id = vessel["id"]
debug_print("🌀" * 20)
debug_print("🚀 开始生成分离协议支持vessel字典和体积运算")
debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
debug_print(f" 🎯 分离目的: '{purpose}'") debug_print(f" 🎯 分离目的: '{purpose}'")
debug_print(f" 📊 产物相: '{product_phase}'") debug_print(f" 📊 产物相: '{product_phase}'")
debug_print(f" 💧 溶剂: '{solvent}'") debug_print(f" 💧 溶剂: '{solvent}'")
@@ -322,24 +432,33 @@ def generate_separate_protocol(
debug_print(f" 🎯 产物容器: '{product_vessel}'") debug_print(f" 🎯 产物容器: '{product_vessel}'")
debug_print(f" 🗑️ 废液容器: '{waste_vessel}'") debug_print(f" 🗑️ 废液容器: '{waste_vessel}'")
debug_print(f" 📦 其他参数: {kwargs}") debug_print(f" 📦 其他参数: {kwargs}")
debug_print("=" * 60) debug_print("🌀" * 20)
action_sequence = [] action_sequence = []
# 🔧 新增:记录分离前的容器状态
debug_print("🔍 记录分离前容器状态...")
original_liquid_volume = get_vessel_liquid_volume(vessel)
debug_print(f"📊 分离前液体体积: {original_liquid_volume:.2f}mL")
# === 参数验证和标准化 === # === 参数验证和标准化 ===
debug_print("🔍 步骤1: 参数验证和标准化...") debug_print("🔍 步骤1: 参数验证和标准化...")
action_sequence.append(create_action_log(f"开始分离操作 - 容器: {vessel}", "🎬")) action_sequence.append(create_action_log(f"开始分离操作 - 容器: {vessel_id}", "🎬"))
action_sequence.append(create_action_log(f"分离目的: {purpose}", "🧪")) action_sequence.append(create_action_log(f"分离目的: {purpose}", "🧪"))
action_sequence.append(create_action_log(f"产物相: {product_phase}", "📊")) action_sequence.append(create_action_log(f"产物相: {product_phase}", "📊"))
# 统一容器参数 # 统一容器参数 - 支持字典和字符串
final_vessel = vessel or separation_vessel def extract_vessel_id(vessel_param):
if not final_vessel: if isinstance(vessel_param, dict):
debug_print("❌ 必须指定分离容器") return vessel_param.get("id", "")
raise ValueError("必须指定分离容器 (vessel 或 separation_vessel)") elif isinstance(vessel_param, str):
return vessel_param
else:
return ""
final_to_vessel = to_vessel or product_vessel final_vessel_id = vessel_id
final_waste_vessel = waste_phase_to_vessel or waste_vessel final_to_vessel_id = extract_vessel_id(to_vessel) or extract_vessel_id(product_vessel)
final_waste_vessel_id = extract_vessel_id(waste_phase_to_vessel) or extract_vessel_id(waste_vessel)
# 统一体积参数 # 统一体积参数
final_volume = parse_volume_input(volume or solvent_volume) final_volume = parse_volume_input(volume or solvent_volume)
@@ -350,13 +469,13 @@ def generate_separate_protocol(
debug_print(f"⚠️ 重复次数参数 <= 0自动设置为 1") debug_print(f"⚠️ 重复次数参数 <= 0自动设置为 1")
debug_print(f"🔧 标准化后的参数:") debug_print(f"🔧 标准化后的参数:")
debug_print(f" 🥼 分离容器: '{final_vessel}'") debug_print(f" 🥼 分离容器: '{final_vessel_id}'")
debug_print(f" 🎯 产物容器: '{final_to_vessel}'") debug_print(f" 🎯 产物容器: '{final_to_vessel_id}'")
debug_print(f" 🗑️ 废液容器: '{final_waste_vessel}'") debug_print(f" 🗑️ 废液容器: '{final_waste_vessel_id}'")
debug_print(f" 📏 溶剂体积: {final_volume}mL") debug_print(f" 📏 溶剂体积: {final_volume}mL")
debug_print(f" 🔄 重复次数: {repeats}") debug_print(f" 🔄 重复次数: {repeats}")
action_sequence.append(create_action_log(f"分离容器: {final_vessel}", "🧪")) action_sequence.append(create_action_log(f"分离容器: {final_vessel_id}", "🧪"))
action_sequence.append(create_action_log(f"溶剂体积: {final_volume}mL", "📏")) action_sequence.append(create_action_log(f"溶剂体积: {final_volume}mL", "📏"))
action_sequence.append(create_action_log(f"重复次数: {repeats}", "🔄")) action_sequence.append(create_action_log(f"重复次数: {repeats}", "🔄"))
@@ -382,7 +501,7 @@ def generate_separate_protocol(
action_sequence.append(create_action_log("正在查找相关设备...", "🔍")) action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
# 查找分离器设备 # 查找分离器设备
separator_device = find_separator_device(G, final_vessel) separator_device = find_separator_device(G, final_vessel_id) # 🔧 使用 final_vessel_id
if separator_device: if separator_device:
action_sequence.append(create_action_log(f"找到分离器设备: {separator_device}", "🧪")) action_sequence.append(create_action_log(f"找到分离器设备: {separator_device}", "🧪"))
else: else:
@@ -390,7 +509,7 @@ def generate_separate_protocol(
action_sequence.append(create_action_log("未找到分离器设备", "⚠️")) action_sequence.append(create_action_log("未找到分离器设备", "⚠️"))
# 查找搅拌器 # 查找搅拌器
stirrer_device = find_connected_stirrer(G, final_vessel) stirrer_device = find_connected_stirrer(G, final_vessel_id) # 🔧 使用 final_vessel_id
if stirrer_device: if stirrer_device:
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_device}", "🌪️")) action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_device}", "🌪️"))
else: else:
@@ -414,6 +533,9 @@ def generate_separate_protocol(
debug_print("🔍 步骤3: 执行分离流程...") debug_print("🔍 步骤3: 执行分离流程...")
action_sequence.append(create_action_log("开始分离工作流程", "🎯")) action_sequence.append(create_action_log("开始分离工作流程", "🎯"))
# 🔧 新增:体积变化跟踪变量
current_volume = original_liquid_volume
try: try:
for repeat_idx in range(repeats): for repeat_idx in range(repeats):
cycle_num = repeat_idx + 1 cycle_num = repeat_idx + 1
@@ -430,7 +552,7 @@ def generate_separate_protocol(
pump_actions = generate_pump_protocol_with_rinsing( pump_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
from_vessel=solvent_vessel, from_vessel=solvent_vessel,
to_vessel=final_vessel, to_vessel=final_vessel_id, # 🔧 使用 final_vessel_id
volume=final_volume, volume=final_volume,
amount="", amount="",
time=0.0, time=0.0,
@@ -450,6 +572,10 @@ def generate_separate_protocol(
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(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", ""))
# 🔧 新增:更新体积 - 添加溶剂后
current_volume += final_volume
update_vessel_volume(vessel, G, current_volume, f"添加{final_volume}mL {solvent}")
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)}", ""))
@@ -466,7 +592,7 @@ def generate_separate_protocol(
"device_id": stirrer_device, "device_id": stirrer_device,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": final_vessel, "vessel": final_vessel_id, # 🔧 使用 final_vessel_id
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": f"分离混合 - {purpose}" "purpose": f"分离混合 - {purpose}"
} }
@@ -485,7 +611,7 @@ def generate_separate_protocol(
action_sequence.append({ action_sequence.append({
"device_id": stirrer_device, "device_id": stirrer_device,
"action_name": "stop_stir", "action_name": "stop_stir",
"action_kwargs": {"vessel": final_vessel} "action_kwargs": {"vessel": final_vessel_id} # 🔧 使用 final_vessel_id
}) })
else: else:
@@ -517,10 +643,10 @@ def generate_separate_protocol(
"action_kwargs": { "action_kwargs": {
"purpose": purpose, "purpose": purpose,
"product_phase": product_phase, "product_phase": product_phase,
"from_vessel": from_vessel or final_vessel, "from_vessel": extract_vessel_id(from_vessel) or final_vessel_id, # 🔧 使用vessel_id
"separation_vessel": final_vessel, "separation_vessel": final_vessel_id, # 🔧 使用 final_vessel_id
"to_vessel": final_to_vessel or final_vessel, "to_vessel": final_to_vessel_id or final_vessel_id, # 🔧 使用vessel_id
"waste_phase_to_vessel": final_waste_vessel or final_vessel, "waste_phase_to_vessel": final_waste_vessel_id or final_vessel_id, # 🔧 使用vessel_id
"solvent": solvent, "solvent": solvent,
"solvent_volume": final_volume, "solvent_volume": final_volume,
"through": through, "through": through,
@@ -534,11 +660,17 @@ def generate_separate_protocol(
debug_print(f"✅ 分离操作已添加") debug_print(f"✅ 分离操作已添加")
action_sequence.append(create_action_log("分离操作完成", "")) action_sequence.append(create_action_log("分离操作完成", ""))
# 🔧 新增:分离后体积估算(分离通常不改变总体积,但会重新分配)
# 假设分离后保持体积(实际情况可能有少量损失)
separated_volume = current_volume * 0.95 # 假设5%损失
update_vessel_volume(vessel, G, separated_volume, f"分离操作后(第{cycle_num}轮)")
current_volume = separated_volume
# 收集结果 # 收集结果
if final_to_vessel: if final_to_vessel_id:
action_sequence.append(create_action_log(f"产物 ({product_phase}相) 收集到: {final_to_vessel}", "📦")) action_sequence.append(create_action_log(f"产物 ({product_phase}相) 收集到: {final_to_vessel_id}", "📦"))
if final_waste_vessel: if final_waste_vessel_id:
action_sequence.append(create_action_log(f"废相收集到: {final_waste_vessel}", "🗑️")) action_sequence.append(create_action_log(f"废相收集到: {final_waste_vessel_id}", "🗑️"))
else: else:
debug_print(f"🔄 第{cycle_num}轮 步骤4: 无分离器设备,跳过分离") debug_print(f"🔄 第{cycle_num}轮 步骤4: 无分离器设备,跳过分离")
@@ -572,99 +704,37 @@ def generate_separate_protocol(
} }
}) })
# 🔧 新增:分离完成后的最终状态报告
final_liquid_volume = get_vessel_liquid_volume(vessel)
# === 最终结果 === # === 最终结果 ===
total_time = (stir_time + settling_time + 15) * repeats # 估算总时间 total_time = (stir_time + settling_time + 15) * repeats # 估算总时间
debug_print("=" * 60) debug_print("🌀" * 20)
debug_print(f"🎉 分离协议生成完成") debug_print(f"🎉 分离协议生成完成")
debug_print(f"📊 协议统计:") debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}") debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)") debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
debug_print(f" 🥼 分离容器: {final_vessel}") debug_print(f" 🥼 分离容器: {final_vessel_id}")
debug_print(f" 🎯 分离目的: {purpose}") debug_print(f" 🎯 分离目的: {purpose}")
debug_print(f" 📊 产物相: {product_phase}") debug_print(f" 📊 产物相: {product_phase}")
debug_print(f" 🔄 重复次数: {repeats}") debug_print(f" 🔄 重复次数: {repeats}")
debug_print(f"💧 体积变化统计:")
debug_print(f" - 分离前体积: {original_liquid_volume:.2f}mL")
debug_print(f" - 分离后体积: {final_liquid_volume:.2f}mL")
if solvent: if solvent:
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)") debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL × {repeats}轮 = {final_volume * repeats:.2f}mL)")
if final_to_vessel: if final_to_vessel_id:
debug_print(f" 🎯 产物容器: {final_to_vessel}") debug_print(f" 🎯 产物容器: {final_to_vessel_id}")
if final_waste_vessel: if final_waste_vessel_id:
debug_print(f" 🗑️ 废液容器: {final_waste_vessel}") debug_print(f" 🗑️ 废液容器: {final_waste_vessel_id}")
debug_print("=" * 60) debug_print("🌀" * 20)
# 添加完成日志 # 添加完成日志
summary_msg = f"分离协议完成: {final_vessel} ({purpose}{repeats} 次循环)" summary_msg = f"分离协议完成: {final_vessel_id} ({purpose}{repeats} 次循环)"
if solvent: if solvent:
summary_msg += f",使用 {final_volume}mL {solvent}" summary_msg += f",使用 {final_volume * repeats:.2f}mL {solvent}"
action_sequence.append(create_action_log(summary_msg, "🎉")) action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence return action_sequence
# === 便捷函数 ===
def separate_phases_only(G: nx.DiGraph, vessel: str, product_phase: str = "top",
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
"""仅进行相分离(不添加溶剂)"""
debug_print(f"⚡ 快速相分离: {vessel} ({product_phase}相)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="separate",
product_phase=product_phase,
product_vessel=product_vessel,
waste_vessel=waste_vessel
)
def wash_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
product_phase: str = "top", repeats: int = 1) -> List[Dict[str, Any]]:
"""用溶剂洗涤"""
debug_print(f"🧽 用{solvent}洗涤: {vessel} ({repeats} 次)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="wash",
product_phase=product_phase,
solvent=solvent,
volume=volume,
repeats=repeats
)
def extract_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
product_phase: str = "bottom", repeats: int = 3) -> List[Dict[str, Any]]:
"""用溶剂萃取"""
debug_print(f"🧪 用{solvent}萃取: {vessel} ({repeats} 次)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="extract",
product_phase=product_phase,
solvent=solvent,
volume=volume,
repeats=repeats
)
def separate_aqueous_organic(G: nx.DiGraph, vessel: str, organic_phase: str = "top",
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
"""水-有机相分离"""
debug_print(f"💧 水-有机相分离: {vessel} (有机相: {organic_phase})")
return generate_separate_protocol(
G, vessel=vessel,
purpose="separate",
product_phase=organic_phase,
product_vessel=product_vessel,
waste_vessel=waste_vessel
)
# 测试函数
def test_separate_protocol():
"""测试分离协议的各种参数解析"""
debug_print("=== 分离协议增强中文版测试 ===")
# 测试体积解析
debug_print("🧪 测试体积解析...")
volumes = ["200 mL", "?", 100.0, "1 L", "500 μL", "未知", "50毫升"]
for vol in volumes:
result = parse_volume_input(vol)
debug_print(f"📊 体积解析结果: {vol} -> {result}mL")
debug_print("✅ 测试完成")
if __name__ == "__main__":
test_separate_protocol()

View File

@@ -138,9 +138,50 @@ 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:
"""
从vessel参数中提取vessel_id
Args:
vessel: vessel字典或vessel_id字符串
Returns:
str: vessel_id
"""
if isinstance(vessel, dict):
vessel_id = vessel.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,
vessel: str, vessel: Union[str, dict], # 🔧 修改支持vessel字典
time: Union[str, float, int] = "300", time: Union[str, float, int] = "300",
stir_time: Union[str, float, int] = "0", stir_time: Union[str, float, int] = "0",
time_spec: str = "", time_spec: str = "",
@@ -150,13 +191,31 @@ def generate_stir_protocol(
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成搅拌操作的协议序列(精简版) 生成搅拌操作的协议序列 - 支持vessel字典
Args:
G: 有向图,节点为设备和容器,边为流体管道
vessel: 搅拌容器字典从XDL传入或容器ID字符串
time: 搅拌时间(支持 "300", "5 min", "1 h" 等格式)
stir_time: 指定搅拌时间优先级比time低
time_spec: 时间规格(优先级最高)
event: 事件描述
stir_speed: 搅拌速度RPM
settling_time: 沉降时间
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 搅拌操作的动作序列
""" """
# 🔧 核心修改从vessel参数中提取vessel_id
vessel_id = extract_vessel_id(vessel)
vessel_display = get_vessel_display_info(vessel)
debug_print("🌪️" * 20) debug_print("🌪️" * 20)
debug_print("🚀 开始生成搅拌协议 ") debug_print("🚀 开始生成搅拌协议支持vessel字典")
debug_print(f"📝 输入参数:") debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}") debug_print(f" 🥽 vessel: {vessel_display} (ID: {vessel_id})")
debug_print(f" ⏰ time: {time}") debug_print(f" ⏰ time: {time}")
debug_print(f" 🕐 stir_time: {stir_time}") debug_print(f" 🕐 stir_time: {stir_time}")
debug_print(f" 🎯 time_spec: {time_spec}") debug_print(f" 🎯 time_spec: {time_spec}")
@@ -166,13 +225,13 @@ def generate_stir_protocol(
# 📋 参数验证 # 📋 参数验证
debug_print("📍 步骤1: 参数验证... 🔧") debug_print("📍 步骤1: 参数验证... 🔧")
if not vessel: if not vessel_id: # 🔧 使用 vessel_id
debug_print("❌ vessel 参数不能为空! 😱") debug_print("❌ vessel 参数不能为空! 😱")
raise ValueError("vessel 参数不能为空") raise ValueError("vessel 参数不能为空")
if vessel not in G.nodes(): if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中! 😞") debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中! 😞")
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
debug_print("✅ 基础参数验证通过 🎯") debug_print("✅ 基础参数验证通过 🎯")
@@ -220,7 +279,7 @@ def generate_stir_protocol(
# 🔍 查找设备 # 🔍 查找设备
debug_print("📍 步骤3: 查找搅拌设备... 🔍") debug_print("📍 步骤3: 查找搅拌设备... 🔍")
try: try:
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
debug_print(f"🎉 使用搅拌设备: {stirrer_id}") debug_print(f"🎉 使用搅拌设备: {stirrer_id}")
except Exception as e: except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)} 😭") debug_print(f"❌ 设备查找失败: {str(e)} 😭")
@@ -234,7 +293,7 @@ def generate_stir_protocol(
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "stir", "action_name": "stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"time": str(time), # 保持原始格式 "time": str(time), # 保持原始格式
"event": event, "event": event,
"time_spec": time_spec, "time_spec": time_spec,
@@ -256,7 +315,7 @@ def generate_stir_protocol(
debug_print("🎊" * 20) debug_print("🎊" * 20)
debug_print(f"🎉 搅拌协议生成完成! ✨") debug_print(f"🎉 搅拌协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}") debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"🥽 搅拌容器: {vessel}") debug_print(f"🥽 搅拌容器: {vessel_display}")
debug_print(f"🌪️ 搅拌参数: {stir_speed} RPM, {parsed_time}s, 沉降 {parsed_settling_time}s") 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(f"⏱️ 预计总时间: {(parsed_time + parsed_settling_time)/60:.1f} 分钟 ⌛")
debug_print("🎊" * 20) debug_print("🎊" * 20)
@@ -265,18 +324,36 @@ def generate_stir_protocol(
def generate_start_stir_protocol( def generate_start_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: Union[str, dict], # 🔧 修改支持vessel字典
stir_speed: float = 300.0, stir_speed: float = 300.0,
purpose: str = "", purpose: str = "",
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""生成开始搅拌操作的协议序列""" """
生成开始搅拌操作的协议序列 - 支持vessel字典
debug_print("🔄 开始生成启动搅拌协议 ✨") Args:
debug_print(f"🥽 vessel: {vessel}, 🌪️ speed: {stir_speed} RPM") G: 有向图
vessel: 搅拌容器字典或容器ID字符串
stir_speed: 搅拌速度RPM
purpose: 搅拌目的描述
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 开始搅拌操作的动作序列
"""
# 🔧 核心修改从vessel参数中提取vessel_id
vessel_id = extract_vessel_id(vessel)
vessel_display = get_vessel_display_info(vessel)
debug_print("🔄 开始生成启动搅拌协议支持vessel字典")
debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})")
debug_print(f"🌪️ speed: {stir_speed} RPM")
debug_print(f"🎯 purpose: {purpose}")
# 基础验证 # 基础验证
if not vessel or vessel not in G.nodes(): if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print("❌ 容器验证失败!") debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效") raise ValueError("vessel 参数无效")
@@ -286,14 +363,14 @@ def generate_start_stir_protocol(
stir_speed = 300.0 stir_speed = 300.0
# 查找设备 # 查找设备
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
# 生成动作 # 生成动作
action_sequence = [{ action_sequence = [{
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"stir_speed": stir_speed, "stir_speed": stir_speed,
"purpose": purpose or f"启动搅拌 {stir_speed} RPM" "purpose": purpose or f"启动搅拌 {stir_speed} RPM"
} }
@@ -304,38 +381,130 @@ def generate_start_stir_protocol(
def generate_stop_stir_protocol( def generate_stop_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: Union[str, dict], # 🔧 修改支持vessel字典
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""生成停止搅拌操作的协议序列""" """
生成停止搅拌操作的协议序列 - 支持vessel字典
debug_print("🛑 开始生成停止搅拌协议 ✨") Args:
debug_print(f"🥽 vessel: {vessel}") G: 有向图
vessel: 搅拌容器字典或容器ID字符串
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 停止搅拌操作的动作序列
"""
# 🔧 核心修改从vessel参数中提取vessel_id
vessel_id = extract_vessel_id(vessel)
vessel_display = get_vessel_display_info(vessel)
debug_print("🛑 开始生成停止搅拌协议支持vessel字典")
debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})")
# 基础验证 # 基础验证
if not vessel or vessel not in G.nodes(): if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print("❌ 容器验证失败!") debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效") raise ValueError("vessel 参数无效")
# 查找设备 # 查找设备
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
# 生成动作 # 生成动作
action_sequence = [{ action_sequence = [{
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "stop_stir", "action_name": "stop_stir",
"action_kwargs": { "action_kwargs": {
"vessel": vessel "vessel": vessel_id # 🔧 使用 vessel_id
} }
}] }]
debug_print(f"✅ 停止搅拌协议生成完成 🎯") debug_print(f"✅ 停止搅拌协议生成完成 🎯")
return action_sequence return action_sequence
# 🔧 新增:便捷函数
def stir_briefly(G: nx.DiGraph, vessel: Union[str, dict],
speed: float = 300.0) -> List[Dict[str, Any]]:
"""短时间搅拌30秒"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"⚡ 短时间搅拌: {vessel_display} @ {speed}RPM (30s)")
return generate_stir_protocol(G, vessel, time="30", stir_speed=speed)
def stir_slowly(G: nx.DiGraph, vessel: Union[str, dict],
time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
"""慢速搅拌"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🐌 慢速搅拌: {vessel_display} @ 150RPM")
return generate_stir_protocol(G, vessel, time=time, stir_speed=150.0)
def stir_vigorously(G: nx.DiGraph, vessel: Union[str, dict],
time: Union[str, float] = "5 min") -> List[Dict[str, Any]]:
"""剧烈搅拌"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"💨 剧烈搅拌: {vessel_display} @ 800RPM")
return generate_stir_protocol(G, vessel, time=time, stir_speed=800.0)
def stir_for_reaction(G: nx.DiGraph, vessel: Union[str, dict],
time: Union[str, float] = "1 h") -> List[Dict[str, Any]]:
"""反应搅拌(标准速度,长时间)"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🧪 反应搅拌: {vessel_display} @ 400RPM")
return generate_stir_protocol(G, vessel, time=time, stir_speed=400.0)
def stir_for_dissolution(G: nx.DiGraph, vessel: Union[str, dict],
time: Union[str, float] = "15 min") -> List[Dict[str, Any]]:
"""溶解搅拌(中等速度)"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"💧 溶解搅拌: {vessel_display} @ 500RPM")
return generate_stir_protocol(G, vessel, time=time, stir_speed=500.0)
def stir_gently(G: nx.DiGraph, vessel: Union[str, dict],
time: Union[str, float] = "30 min") -> List[Dict[str, Any]]:
"""温和搅拌"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🍃 温和搅拌: {vessel_display} @ 200RPM")
return generate_stir_protocol(G, vessel, time=time, stir_speed=200.0)
def stir_overnight(G: nx.DiGraph, vessel: Union[str, dict]) -> List[Dict[str, Any]]:
"""过夜搅拌模拟时缩短为2小时"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🌙 过夜搅拌模拟2小时: {vessel_display} @ 300RPM")
return generate_stir_protocol(G, vessel, time="2 h", stir_speed=300.0)
def start_continuous_stirring(G: nx.DiGraph, vessel: Union[str, dict],
speed: float = 300.0, purpose: str = "continuous stirring") -> List[Dict[str, Any]]:
"""开始连续搅拌"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🔄 开始连续搅拌: {vessel_display} @ {speed}RPM")
return generate_start_stir_protocol(G, vessel, stir_speed=speed, purpose=purpose)
def stop_all_stirring(G: nx.DiGraph, vessel: Union[str, dict]) -> List[Dict[str, Any]]:
"""停止所有搅拌"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🛑 停止搅拌: {vessel_display}")
return generate_stop_stir_protocol(G, vessel)
# 测试函数 # 测试函数
def test_stir_protocol(): def test_stir_protocol():
"""测试搅拌协议""" """测试搅拌协议"""
debug_print("🧪 === STIR PROTOCOL 测试 === ✨") debug_print("🧪 === STIR PROTOCOL 测试 === ✨")
# 测试vessel参数处理
debug_print("🔧 测试vessel参数处理...")
# 测试字典格式
vessel_dict = {"id": "flask_1", "name": "反应瓶1"}
vessel_id = extract_vessel_id(vessel_dict)
vessel_display = get_vessel_display_info(vessel_dict)
debug_print(f" 字典格式: {vessel_dict} → ID: {vessel_id}, 显示: {vessel_display}")
# 测试字符串格式
vessel_str = "flask_2"
vessel_id = extract_vessel_id(vessel_str)
vessel_display = get_vessel_display_info(vessel_str)
debug_print(f" 字符串格式: {vessel_str} → ID: {vessel_id}, 显示: {vessel_display}")
debug_print("✅ 测试完成 🎉") debug_print("✅ 测试完成 🎉")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -154,12 +154,153 @@ def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
debug_print(f"⚠️ 使用默认滤液容器: waste_workup") debug_print(f"⚠️ 使用默认滤液容器: waste_workup")
return "waste_workup" return "waste_workup"
def extract_vessel_id(vessel: Union[str, dict]) -> str:
"""
从vessel参数中提取vessel_id
Args:
vessel: vessel字典或vessel_id字符串
Returns:
str: vessel_id
"""
if isinstance(vessel, dict):
vessel_id = vessel.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: str, vessel: Union[str, dict], # 🔧 修改支持vessel字典
solvent: str, solvent: str,
volume: Union[float, str] = "50", volume: Union[float, str] = "50",
filtrate_vessel: str = "", filtrate_vessel: Union[str, dict] = "", # 🔧 修改支持vessel字典
temp: float = 25.0, temp: float = 25.0,
stir: bool = False, stir: bool = False,
stir_speed: float = 0.0, stir_speed: float = 0.0,
@@ -172,21 +313,58 @@ def generate_wash_solid_protocol(
**kwargs **kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成固体清洗协议(精简版) 生成固体清洗协议 - 支持vessel字典和体积运算
Args:
G: 有向图,节点为设备和容器,边为流体管道
vessel: 清洗容器字典从XDL传入或容器ID字符串
solvent: 清洗溶剂名称
volume: 溶剂体积(每次清洗)
filtrate_vessel: 滤液收集容器字典或容器ID字符串
temp: 清洗温度°C
stir: 是否搅拌
stir_speed: 搅拌速度RPM
time: 搅拌时间
repeats: 清洗重复次数
volume_spec: 体积规格small/medium/large
repeats_spec: 重复次数规格few/several/many
mass: 固体质量(用于计算溶剂用量)
event: 事件描述
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 固体清洗操作的动作序列
""" """
# 🔧 核心修改从vessel参数中提取vessel_id
vessel_id = extract_vessel_id(vessel)
vessel_display = get_vessel_display_info(vessel)
# 🔧 处理filtrate_vessel参数
filtrate_vessel_id = extract_vessel_id(filtrate_vessel) if filtrate_vessel else ""
debug_print("🧼" * 20) debug_print("🧼" * 20)
debug_print("🚀 开始生成固体清洗协议 ") debug_print("🚀 开始生成固体清洗协议支持vessel字典和体积运算")
debug_print(f"📝 输入参数:") debug_print(f"📝 输入参数:")
debug_print(f" 🥽 vessel: {vessel}") debug_print(f" 🥽 vessel: {vessel_display} (ID: {vessel_id})")
debug_print(f" 🧪 solvent: {solvent}") debug_print(f" 🧪 solvent: {solvent}")
debug_print(f" 💧 volume: {volume}") debug_print(f" 💧 volume: {volume}")
debug_print(f" 🗑️ filtrate_vessel: {filtrate_vessel_id}")
debug_print(f" ⏰ time: {time}") debug_print(f" ⏰ time: {time}")
debug_print(f" 🔄 repeats: {repeats}") debug_print(f" 🔄 repeats: {repeats}")
debug_print("🧼" * 20) debug_print("🧼" * 20)
# 🔧 新增:记录清洗前的容器状态
debug_print("🔍 记录清洗前容器状态...")
if isinstance(vessel, dict):
original_volume = get_vessel_liquid_volume(vessel)
debug_print(f"📊 清洗前液体体积: {original_volume:.2f}mL")
else:
original_volume = 0.0
debug_print(f"📊 vessel为字符串格式无法获取体积信息")
# 📋 快速验证 # 📋 快速验证
if not vessel or vessel not in G.nodes(): if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
debug_print("❌ 容器验证失败! 😱") debug_print("❌ 容器验证失败! 😱")
raise ValueError("vessel 参数无效") raise ValueError("vessel 参数无效")
@@ -225,8 +403,10 @@ def generate_wash_solid_protocol(
debug_print("📍 步骤2: 查找设备... 🔍") 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) actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel_id)
debug_print(f"🎉 设备配置完成 ✨") 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)} 😭") debug_print(f"❌ 设备查找失败: {str(e)} 😭")
raise ValueError(f"设备查找失败: {str(e)}") raise ValueError(f"设备查找失败: {str(e)}")
@@ -235,6 +415,10 @@ def generate_wash_solid_protocol(
debug_print("📍 步骤3: 生成清洗动作... 🧼") debug_print("📍 步骤3: 生成清洗动作... 🧼")
action_sequence = [] action_sequence = []
# 🔧 新增:体积变化跟踪变量
current_volume = original_volume
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}次清洗...")
@@ -242,10 +426,11 @@ def generate_wash_solid_protocol(
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, to_vessel=vessel_id, # 🔧 使用 vessel_id
volume=final_volume, volume=final_volume,
amount="", amount="",
time=0.0, time=0.0,
@@ -261,17 +446,26 @@ 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)}个 🚚") debug_print(f" ✅ 转移动作: {len(transfer_actions)}个 🚚")
# 🔧 新增:更新体积 - 添加溶剂后
current_volume += final_volume
total_solvent_used += final_volume
if isinstance(vessel, dict):
update_vessel_volume(vessel, G, current_volume,
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": vessel, "vessel": 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,
@@ -282,11 +476,12 @@ def generate_wash_solid_protocol(
debug_print(f" ✅ 搅拌动作: {final_time}s, {stir_speed}RPM 🌪️") 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": vessel, "vessel": vessel_id, # 🔧 使用 vessel_id
"filtrate_vessel": actual_filtrate_vessel, "filtrate_vessel": actual_filtrate_vessel,
"temp": temp, "temp": temp,
"volume": final_volume "volume": final_volume
@@ -295,6 +490,15 @@ def generate_wash_solid_protocol(
action_sequence.append(filter_action) action_sequence.append(filter_action)
debug_print(f" ✅ 过滤动作: → {actual_filtrate_vessel} 🌊") debug_print(f" ✅ 过滤动作: → {actual_filtrate_vessel} 🌊")
# 🔧 新增:更新体积 - 过滤后(液体被滤除)
# 假设滤液完全被移除,固体残留在容器中
filtered_volume = current_volume * 0.9 # 假设90%的液体被过滤掉
current_volume = current_volume - filtered_volume
if isinstance(vessel, dict):
update_vessel_volume(vessel, G, current_volume,
f"{cycle+1}次清洗过滤后")
# 4. 等待(缩短时间) # 4. 等待(缩短时间)
wait_time = 5.0 # 🕐 缩短等待时间10s → 5s wait_time = 5.0 # 🕐 缩短等待时间10s → 5s
action_sequence.append({ action_sequence.append({
@@ -303,14 +507,146 @@ def generate_wash_solid_protocol(
}) })
debug_print(f" ✅ 等待: {wait_time}s ⏰") debug_print(f" ✅ 等待: {wait_time}s ⏰")
# 🔧 新增:清洗完成后的最终状态报告
if isinstance(vessel, dict):
final_volume_vessel = get_vessel_liquid_volume(vessel)
else:
final_volume_vessel = current_volume
# 🎊 总结 # 🎊 总结
debug_print("🧼" * 20) debug_print("🧼" * 20)
debug_print(f"🎉 固体清洗协议生成完成! ✨") debug_print(f"🎉 固体清洗协议生成完成! ✨")
debug_print(f"📊 总动作数: {len(action_sequence)}") debug_print(f"📊 协议统计:")
debug_print(f"🥽 清洗容器: {vessel}") debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f"🧪 使用溶剂: {solvent}") debug_print(f" 🥽 清洗容器: {vessel_display}")
debug_print(f"💧 清洗体积: {final_volume}mL × {final_repeats}") 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(f"⏱️ 预计总时间: {(final_time + 5) * final_repeats / 60:.1f} 分钟")
debug_print("🧼" * 20) debug_print("🧼" * 20)
return action_sequence return action_sequence
# 🔧 新增:便捷函数
def wash_with_water(G: nx.DiGraph, vessel: Union[str, dict],
volume: Union[float, str] = "50",
repeats: int = 2) -> List[Dict[str, Any]]:
"""用水清洗固体"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"💧 水洗固体: {vessel_display} ({repeats} 次)")
return generate_wash_solid_protocol(G, vessel, "water", volume=volume, repeats=repeats)
def wash_with_ethanol(G: nx.DiGraph, vessel: Union[str, dict],
volume: Union[float, str] = "30",
repeats: int = 1) -> List[Dict[str, Any]]:
"""用乙醇清洗固体"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🍺 乙醇洗固体: {vessel_display} ({repeats} 次)")
return generate_wash_solid_protocol(G, vessel, "ethanol", volume=volume, repeats=repeats)
def wash_with_acetone(G: nx.DiGraph, vessel: Union[str, dict],
volume: Union[float, str] = "25",
repeats: int = 1) -> List[Dict[str, Any]]:
"""用丙酮清洗固体"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"💨 丙酮洗固体: {vessel_display} ({repeats} 次)")
return generate_wash_solid_protocol(G, vessel, "acetone", volume=volume, repeats=repeats)
def wash_with_ether(G: nx.DiGraph, vessel: Union[str, dict],
volume: Union[float, str] = "40",
repeats: int = 2) -> List[Dict[str, Any]]:
"""用乙醚清洗固体"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🌬️ 乙醚洗固体: {vessel_display} ({repeats} 次)")
return generate_wash_solid_protocol(G, vessel, "diethyl_ether", volume=volume, repeats=repeats)
def wash_with_cold_solvent(G: nx.DiGraph, vessel: Union[str, dict],
solvent: str, volume: Union[float, str] = "30",
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,
temp=5.0, repeats=repeats)
def wash_with_hot_solvent(G: nx.DiGraph, vessel: Union[str, dict],
solvent: str, volume: Union[float, str] = "50",
repeats: int = 1) -> List[Dict[str, Any]]:
"""用热溶剂清洗固体"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🔥 热{solvent}洗固体: {vessel_display} ({repeats} 次)")
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume,
temp=60.0, repeats=repeats)
def wash_with_stirring(G: nx.DiGraph, vessel: Union[str, dict],
solvent: str, volume: Union[float, str] = "50",
stir_time: Union[str, float] = "5 min",
repeats: int = 1) -> List[Dict[str, Any]]:
"""带搅拌的溶剂清洗"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🌪️ 搅拌清洗: {vessel_display} with {solvent} ({repeats} 次)")
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume,
stir=True, stir_speed=200.0,
time=stir_time, repeats=repeats)
def thorough_wash(G: nx.DiGraph, vessel: Union[str, dict],
solvent: str, volume: Union[float, str] = "50") -> List[Dict[str, Any]]:
"""彻底清洗(多次重复)"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"🔄 彻底清洗: {vessel_display} with {solvent} (5 次)")
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=5)
def quick_rinse(G: nx.DiGraph, vessel: Union[str, dict],
solvent: str, volume: Union[float, str] = "20") -> List[Dict[str, Any]]:
"""快速冲洗(单次,小体积)"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"⚡ 快速冲洗: {vessel_display} with {solvent}")
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=1)
def sequential_wash(G: nx.DiGraph, vessel: Union[str, dict],
solvents: list, volume: Union[float, str] = "40") -> List[Dict[str, Any]]:
"""连续多溶剂清洗"""
vessel_display = get_vessel_display_info(vessel)
debug_print(f"📝 连续清洗: {vessel_display} with {''.join(solvents)}")
action_sequence = []
for solvent in solvents:
wash_actions = generate_wash_solid_protocol(G, vessel, solvent,
volume=volume, repeats=1)
action_sequence.extend(wash_actions)
return action_sequence
# 测试函数
def test_wash_solid_protocol():
"""测试固体清洗协议"""
debug_print("🧪 === WASH SOLID PROTOCOL 测试 === ✨")
# 测试vessel参数处理
debug_print("🔧 测试vessel参数处理...")
# 测试字典格式
vessel_dict = {"id": "filter_flask_1", "name": "过滤瓶1",
"data": {"liquid_volume": 25.0}}
vessel_id = extract_vessel_id(vessel_dict)
vessel_display = get_vessel_display_info(vessel_dict)
volume = get_vessel_liquid_volume(vessel_dict)
debug_print(f" 字典格式: {vessel_dict}")
debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}, 体积: {volume}mL")
# 测试字符串格式
vessel_str = "filter_flask_2"
vessel_id = extract_vessel_id(vessel_str)
vessel_display = get_vessel_display_info(vessel_str)
debug_print(f" 字符串格式: {vessel_str}")
debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}")
debug_print("✅ 测试完成 🎉")
if __name__ == "__main__":
test_wash_solid_protocol()

View File

@@ -11,8 +11,8 @@ class Point3D(BaseModel):
class PumpTransferProtocol(BaseModel): class PumpTransferProtocol(BaseModel):
# === 核心参数(保持必需) === # === 核心参数(保持必需) ===
from_vessel: str from_vessel: dict
to_vessel: str to_vessel: dict
# === 所有其他参数都改为可选,添加默认值 === # === 所有其他参数都改为可选,添加默认值 ===
volume: float = 0.0 # 🔧 改为-1表示转移全部体积 volume: float = 0.0 # 🔧 改为-1表示转移全部体积
@@ -95,7 +95,7 @@ class PumpTransferProtocol(BaseModel):
class CleanProtocol(BaseModel): class CleanProtocol(BaseModel):
vessel: str vessel: dict
solvent: str solvent: str
volume: float volume: float
temp: float temp: float
@@ -105,10 +105,10 @@ class CleanProtocol(BaseModel):
class SeparateProtocol(BaseModel): class SeparateProtocol(BaseModel):
purpose: str purpose: str
product_phase: str product_phase: str
from_vessel: str from_vessel: dict
separation_vessel: str separation_vessel: dict
to_vessel: str to_vessel: dict
waste_phase_to_vessel: str waste_phase_to_vessel: dict
solvent: str solvent: str
solvent_volume: float solvent_volume: float
through: str through: str
@@ -120,7 +120,7 @@ class SeparateProtocol(BaseModel):
class EvaporateProtocol(BaseModel): class EvaporateProtocol(BaseModel):
# === 核心参数(必需) === # === 核心参数(必需) ===
vessel: str = Field(..., description="蒸发容器名称") vessel: dict = Field(..., description="蒸发容器名称")
# === 所有其他参数都改为可选,添加默认值 === # === 所有其他参数都改为可选,添加默认值 ===
pressure: float = Field(0.1, description="真空度 (bar)默认0.1 bar") pressure: float = Field(0.1, description="真空度 (bar)默认0.1 bar")
@@ -183,7 +183,7 @@ class EvaporateProtocol(BaseModel):
class EvacuateAndRefillProtocol(BaseModel): class EvacuateAndRefillProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="目标容器名称") vessel: dict = Field(..., description="目标容器名称")
gas: str = Field(..., description="气体名称") gas: str = Field(..., description="气体名称")
# 🔧 删除 repeats 参数,直接在代码中硬编码为 3 次 # 🔧 删除 repeats 参数,直接在代码中硬编码为 3 次
@@ -219,7 +219,7 @@ class AGVTransferProtocol(BaseModel):
#=============新添加的新的协议================ #=============新添加的新的协议================
class AddProtocol(BaseModel): class AddProtocol(BaseModel):
vessel: str vessel: dict
reagent: str reagent: str
volume: float volume: float
mass: float mass: float
@@ -231,17 +231,17 @@ class AddProtocol(BaseModel):
purpose: str purpose: str
class CentrifugeProtocol(BaseModel): class CentrifugeProtocol(BaseModel):
vessel: str vessel: dict
speed: float speed: float
time: float time: float
temp: float temp: float
class FilterProtocol(BaseModel): class FilterProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="过滤容器名称") vessel: dict = Field(..., description="过滤容器名称")
# === 可选参数 === # === 可选参数 ===
filtrate_vessel: str = Field("", description="滤液容器名称(可选,自动查找)") filtrate_vessel: dict = Field("", description="滤液容器名称(可选,自动查找)")
def model_post_init(self, __context): def model_post_init(self, __context):
"""后处理:参数验证""" """后处理:参数验证"""
@@ -251,7 +251,7 @@ class FilterProtocol(BaseModel):
class HeatChillProtocol(BaseModel): class HeatChillProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="加热容器名称") vessel: dict = Field(..., description="加热容器名称")
# === 可选参数 - 温度相关 === # === 可选参数 - 温度相关 ===
temp: float = Field(25.0, description="目标温度 (°C)") temp: float = Field(25.0, description="目标温度 (°C)")
@@ -375,7 +375,7 @@ class HeatChillProtocol(BaseModel):
class HeatChillStartProtocol(BaseModel): class HeatChillStartProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="加热容器名称") vessel: dict = Field(..., description="加热容器名称")
# === 可选参数 - 温度相关 === # === 可选参数 - 温度相关 ===
temp: float = Field(25.0, description="目标温度 (°C)") temp: float = Field(25.0, description="目标温度 (°C)")
@@ -393,12 +393,12 @@ class HeatChillStartProtocol(BaseModel):
class HeatChillStopProtocol(BaseModel): class HeatChillStopProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="加热容器名称") vessel: dict = Field(..., description="加热容器名称")
class StirProtocol(BaseModel): class StirProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="搅拌容器名称") vessel: dict = Field(..., description="搅拌容器名称")
# === 可选参数 === # === 可选参数 ===
time: str = Field("5 min", description="搅拌时间(如 '0.5 h', '30 min'") time: str = Field("5 min", description="搅拌时间(如 '0.5 h', '30 min'")
@@ -482,7 +482,7 @@ class StirProtocol(BaseModel):
class StartStirProtocol(BaseModel): class StartStirProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="搅拌容器名称") vessel: dict = Field(..., description="搅拌容器名称")
# === 可选参数,添加默认值 === # === 可选参数,添加默认值 ===
stir_speed: float = Field(200.0, description="搅拌速度 (RPM)默认200 RPM") stir_speed: float = Field(200.0, description="搅拌速度 (RPM)默认200 RPM")
@@ -505,7 +505,7 @@ class StartStirProtocol(BaseModel):
class StopStirProtocol(BaseModel): class StopStirProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="搅拌容器名称") vessel: dict = Field(..., description="搅拌容器名称")
def model_post_init(self, __context): def model_post_init(self, __context):
"""后处理:参数验证""" """后处理:参数验证"""
@@ -515,8 +515,8 @@ class StopStirProtocol(BaseModel):
raise ValueError("vessel 参数不能为空") raise ValueError("vessel 参数不能为空")
class TransferProtocol(BaseModel): class TransferProtocol(BaseModel):
from_vessel: str from_vessel: dict
to_vessel: str to_vessel: dict
volume: float volume: float
amount: str = "" amount: str = ""
time: float = 0 time: float = 0
@@ -527,14 +527,14 @@ class TransferProtocol(BaseModel):
solid: bool = False solid: bool = False
class CleanVesselProtocol(BaseModel): class CleanVesselProtocol(BaseModel):
vessel: str vessel: dict
solvent: str solvent: str
volume: float volume: float
temp: float temp: float
repeats: int = 1 repeats: int = 1
class DissolveProtocol(BaseModel): class DissolveProtocol(BaseModel):
vessel: str vessel: dict
solvent: str solvent: str
volume: float volume: float
amount: str = "" amount: str = ""
@@ -543,8 +543,8 @@ class DissolveProtocol(BaseModel):
stir_speed: float = 0.0 stir_speed: float = 0.0
class FilterThroughProtocol(BaseModel): class FilterThroughProtocol(BaseModel):
from_vessel: str from_vessel: dict
to_vessel: str to_vessel: dict
filter_through: str filter_through: str
eluting_solvent: str = "" eluting_solvent: str = ""
eluting_volume: float = 0.0 eluting_volume: float = 0.0
@@ -552,18 +552,18 @@ class FilterThroughProtocol(BaseModel):
residence_time: float = 0.0 residence_time: float = 0.0
class RunColumnProtocol(BaseModel): class RunColumnProtocol(BaseModel):
from_vessel: str from_vessel: dict
to_vessel: str to_vessel: dict
column: str column: str
class WashSolidProtocol(BaseModel): class WashSolidProtocol(BaseModel):
# === 必需参数 === # === 必需参数 ===
vessel: str = Field(..., description="装有固体的容器名称") vessel: dict = Field(..., description="装有固体的容器名称")
solvent: str = Field(..., description="清洗溶剂名称") solvent: str = Field(..., description="清洗溶剂名称")
volume: float = Field(..., description="清洗溶剂体积 (mL)") volume: float = Field(..., description="清洗溶剂体积 (mL)")
# === 可选参数,添加默认值 === # === 可选参数,添加默认值 ===
filtrate_vessel: str = Field("", description="滤液收集容器(可选,自动查找)") filtrate_vessel: dict = Field("", description="滤液收集容器(可选,自动查找)")
temp: float = Field(25.0, description="清洗温度 (°C)默认25°C") temp: float = Field(25.0, description="清洗温度 (°C)默认25°C")
stir: bool = Field(False, description="是否搅拌默认False") stir: bool = Field(False, description="是否搅拌默认False")
stir_speed: float = Field(0.0, description="搅拌速度 (RPM)默认0") stir_speed: float = Field(0.0, description="搅拌速度 (RPM)默认0")
@@ -604,7 +604,7 @@ class WashSolidProtocol(BaseModel):
self.repeats = 10 self.repeats = 10
class AdjustPHProtocol(BaseModel): class AdjustPHProtocol(BaseModel):
vessel: str = Field(..., description="目标容器") vessel: dict = Field(..., description="目标容器")
ph_value: float = Field(..., description="目标pH值") # 改为 ph_value ph_value: float = Field(..., description="目标pH值") # 改为 ph_value
reagent: str = Field(..., description="酸碱试剂名称") reagent: str = Field(..., description="酸碱试剂名称")
# 移除其他可选参数,使用默认值 # 移除其他可选参数,使用默认值
@@ -614,19 +614,19 @@ class ResetHandlingProtocol(BaseModel):
class DryProtocol(BaseModel): class DryProtocol(BaseModel):
compound: str = Field(..., description="化合物名称") compound: str = Field(..., description="化合物名称")
vessel: str = Field(..., description="目标容器") vessel: dict = Field(..., description="目标容器")
class RecrystallizeProtocol(BaseModel): class RecrystallizeProtocol(BaseModel):
ratio: str = Field(..., description="溶剂比例(如 '1:1', '3:7'") ratio: str = Field(..., description="溶剂比例(如 '1:1', '3:7'")
solvent1: str = Field(..., description="第一种溶剂名称") solvent1: str = Field(..., description="第一种溶剂名称")
solvent2: str = Field(..., description="第二种溶剂名称") solvent2: str = Field(..., description="第二种溶剂名称")
vessel: str = Field(..., description="目标容器") vessel: dict = Field(..., description="目标容器")
volume: float = Field(..., description="总体积 (mL)") volume: float = Field(..., description="总体积 (mL)")
class HydrogenateProtocol(BaseModel): class HydrogenateProtocol(BaseModel):
temp: str = Field(..., description="反应温度(如 '45 °C'") temp: str = Field(..., description="反应温度(如 '45 °C'")
time: str = Field(..., description="反应时间(如 '2 h'") time: str = Field(..., description="反应时间(如 '2 h'")
vessel: str = Field(..., description="反应容器") vessel: dict = Field(..., description="反应容器")
__all__ = [ __all__ = [
"Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol",

View File

@@ -508,47 +508,90 @@ def convert_from_ros_msg_with_mapping(ros_msg: Any, value_mapping: Dict[str, str
Python字典 Python字典
""" """
data: Dict[str, Any] = {} data: Dict[str, Any] = {}
# 🔧 添加调试信息
print(f"🔍 convert_from_ros_msg_with_mapping 开始")
print(f"🔍 ros_msg 类型: {type(ros_msg)}")
print(f"🔍 ros_msg 内容: {ros_msg}")
print(f"🔍 value_mapping: {value_mapping}")
print("-" * 60)
for msg_name, attr_name in value_mapping.items(): for msg_name, attr_name in value_mapping.items():
print(f"🔍 处理映射: {msg_name} -> {attr_name}")
msg_path = msg_name.split(".") msg_path = msg_name.split(".")
current = ros_msg current = ros_msg
print(f"🔍 msg_path: {msg_path}")
print(f"🔍 current 初始值: {current} (类型: {type(current)})")
try: try:
if not attr_name.endswith("[]"): if not attr_name.endswith("[]"):
# 处理单值映射 # 处理单值映射
for name in msg_path: print(f"🔍 处理单值映射")
current = getattr(current, name) for i, name in enumerate(msg_path):
data[attr_name] = convert_from_ros_msg(current) print(f"🔍 步骤 {i}: 获取属性 '{name}'{type(current)}")
if hasattr(current, name):
current = getattr(current, name)
print(f"🔍 获取到: {current} (类型: {type(current)})")
else:
print(f"❌ 属性 '{name}' 不存在于 {type(current)}")
break
converted_value = convert_from_ros_msg(current)
print(f"🔍 转换后的值: {converted_value} (类型: {type(converted_value)})")
data[attr_name] = converted_value
print(f"✅ 设置 data['{attr_name}'] = {converted_value}")
else: else:
# 处理列表值映射 # 处理列表值映射
for name in msg_path: print(f"🔍 处理列表值映射")
for i, name in enumerate(msg_path):
print(f"🔍 列表步骤 {i}: 处理 '{name}'{type(current)}")
if name.endswith("[]"): if name.endswith("[]"):
base_name = name[:-2] base_name = name[:-2]
print(f"🔍 数组字段 base_name: '{base_name}'")
if hasattr(current, base_name): if hasattr(current, base_name):
current = list(getattr(current, base_name)) current = list(getattr(current, base_name))
print(f"🔍 获取数组: {current} (长度: {len(current)})")
else: else:
print(f"❌ 数组字段 '{base_name}' 不存在")
current = [] current = []
break break
else: else:
if isinstance(current, list): if isinstance(current, list):
print(f"🔍 从列表中获取属性 '{name}'")
next_level = [] next_level = []
for item in current: for item in current:
if hasattr(item, name): if hasattr(item, name):
next_level.append(getattr(item, name)) next_level.append(getattr(item, name))
current = next_level current = next_level
print(f"🔍 列表处理结果: {current} (长度: {len(current)})")
elif hasattr(current, name): elif hasattr(current, name):
current = getattr(current, name) current = getattr(current, name)
print(f"🔍 获取到属性: {current} (类型: {type(current)})")
else: else:
print(f"❌ 属性 '{name}' 不存在")
current = [] current = []
break break
attr_key = attr_name[:-2] attr_key = attr_name[:-2]
if current: if current:
data[attr_key] = [convert_from_ros_msg(item) for item in current] converted_list = [convert_from_ros_msg(item) for item in current]
except (AttributeError, TypeError): data[attr_key] = converted_list
print(f"✅ 设置 data['{attr_key}'] = {converted_list}")
else:
print(f"⚠️ 列表为空,跳过 '{attr_key}'")
except (AttributeError, TypeError) as e:
print(f"❌ 映射转换错误 {msg_name} -> {attr_name}: {e}")
logger.debug(f"Mapping conversion error for {msg_name} -> {attr_name}") logger.debug(f"Mapping conversion error for {msg_name} -> {attr_name}")
continue continue
print(f"🔍 当前 data 状态: {data}")
print("-" * 40)
print(f"🔍 convert_from_ros_msg_with_mapping 结束")
print(f"🔍 最终 data: {data}")
print("=" * 60)
return data return data

View File

@@ -562,9 +562,9 @@ class HostNode(BaseROS2DeviceNode):
for bridge in self.bridges: for bridge in self.bridges:
if hasattr(bridge, "publish_device_status"): if hasattr(bridge, "publish_device_status"):
bridge.publish_device_status(self.device_status, device_id, property_name) bridge.publish_device_status(self.device_status, device_id, property_name)
self.lab_logger().debug( #self.lab_logger().debug(
f"[Host Node] Status updated: {device_id}.{property_name} = {msg.data}" # f"[Host Node] Status updated: {device_id}.{property_name} = {msg.data}"
) #)
def send_goal( def send_goal(
self, self,

View File

@@ -182,26 +182,54 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
# 从目标消息中提取参数, 并调用Protocol生成器(根据设备连接图)生成action步骤 # 从目标消息中提取参数, 并调用Protocol生成器(根据设备连接图)生成action步骤
goal = goal_handle.request goal = goal_handle.request
protocol_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"]) protocol_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
# 🔧 添加调试信息
print(f"🔍 转换后的 protocol_kwargs: {protocol_kwargs}")
print(f"🔍 vessel 在转换后: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
# 向Host查询物料当前状态 # 🔧 完全禁用Host查询直接使用转换后的数据
for k, v in goal.get_fields_and_field_types().items(): print(f"🔧 跳过Host查询直接使用转换后的数据")
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
r = ResourceGet.Request() # 🔧 额外验证确保vessel数据完整
resource_id = ( if 'vessel' in protocol_kwargs:
protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"] vessel_data = protocol_kwargs['vessel']
) print(f"🔍 验证vessel数据: {vessel_data}")
r.id = resource_id
r.with_children = True # 如果vessel是空字典尝试重新构建
response = await self._resource_clients["resource_get"].call_async(r) if not vessel_data or (isinstance(vessel_data, dict) and not vessel_data):
protocol_kwargs[k] = list_to_nested_dict( print(f"⚠️ vessel数据为空尝试从原始goal重新提取...")
[convert_from_ros_msg(rs) for rs in response.resources]
) # 直接从原始goal提取vessel
if hasattr(goal, 'vessel') and goal.vessel:
print(f"🔍 原始goal.vessel: {goal.vessel}")
# 手动转换vessel
vessel_data = {
'id': goal.vessel.id,
'name': goal.vessel.name,
'type': goal.vessel.type,
'category': goal.vessel.category,
'config': goal.vessel.config,
'data': goal.vessel.data
}
protocol_kwargs['vessel'] = vessel_data
print(f"✅ 手动重建vessel数据: {vessel_data}")
else:
print(f"❌ 无法从原始goal提取vessel数据")
# 创建一个基本的vessel
vessel_data = {'id': 'default_vessel'}
protocol_kwargs['vessel'] = vessel_data
print(f"🔧 创建默认vessel: {vessel_data}")
print(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
print(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
from unilabos.resources.graphio import physical_setup_graph from unilabos.resources.graphio import physical_setup_graph
self.lab_logger().info(f"Working on physical setup: {physical_setup_graph}") self.lab_logger().info(f"Working on physical setup: {physical_setup_graph}")
self.lab_logger().info(f"Protocol kwargs: {goal}")
self.lab_logger().info(f"Protocol kwargs: {action_value_mapping}")
protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs) protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)
self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: \n{protocol_steps}") self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: \n{protocol_steps}")
time_start = time.time() time_start = time.time()
@@ -235,14 +263,14 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
} }
) )
# 向Host更新物料当前状态 # # 向Host更新物料当前状态
for k, v in goal.get_fields_and_field_types().items(): # for k, v in goal.get_fields_and_field_types().items():
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]: # if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
r = ResourceUpdate.Request() # r = ResourceUpdate.Request()
r.resources = [ # r.resources = [
convert_to_ros_msg(Resource, rs) for rs in nested_dict_to_list(protocol_kwargs[k]) # convert_to_ros_msg(Resource, rs) for rs in nested_dict_to_list(protocol_kwargs[k])
] # ]
response = await self._resource_clients["resource_update"].call_async(r) # response = await self._resource_clients["resource_update"].call_async(r)
# 设置成功状态和返回值 # 设置成功状态和返回值
execution_success = True execution_success = True

View File

@@ -1,5 +1,5 @@
# Goal - 添加试剂的目标参数 # Goal - 添加试剂的目标参数
string vessel # 目标容器(必需) Resource vessel # 目标容器(必需)
string reagent # 试剂名称(必需) string reagent # 试剂名称(必需)
string volume # 体积(如 "2.7 mL",可选) string volume # 体积(如 "2.7 mL",可选)
string mass # 质量(如 "19.3 g",可选) string mass # 质量(如 "19.3 g",可选)

View File

@@ -1,5 +1,5 @@
# Goal - 固体加样操作的目标参数 # Goal - 固体加样操作的目标参数
string vessel # 目标容器(必需) Resource vessel # 目标容器(必需)
string reagent # 试剂名称(必需) string reagent # 试剂名称(必需)
string mass # 质量字符串(如 "2.9 g",可选) string mass # 质量字符串(如 "2.9 g",可选)
string mol # 摩尔数字符串(如 "0.12 mol",可选) string mol # 摩尔数字符串(如 "0.12 mol",可选)

View File

@@ -1,5 +1,5 @@
# Request - 与您的 AdjustPHProtocol 类匹配 # Request - 与您的 AdjustPHProtocol 类匹配
string vessel Resource vessel
float64 ph_value float64 ph_value
string reagent string reagent
--- ---

View File

@@ -1,5 +1,5 @@
# Goal - 离心操作的目标参数 # Goal - 离心操作的目标参数
string vessel # 离心容器 Resource vessel # 离心容器
float64 speed # 离心速度 (rpm) float64 speed # 离心速度 (rpm)
float64 time # 离心时间 (秒) float64 time # 离心时间 (秒)
float64 temp # 温度 (可选,摄氏度) float64 temp # 温度 (可选,摄氏度)

View File

@@ -1,5 +1,5 @@
# Organic # Organic
string vessel # Vessel to clean. Resource vessel # Vessel to clean.
string solvent # Solvent to clean vessel with. string solvent # Solvent to clean vessel with.
float64 volume # Optional. Volume of solvent to clean vessel with. float64 volume # Optional. Volume of solvent to clean vessel with.
float64 temp # Optional. Temperature to heat vessel to while cleaning. float64 temp # Optional. Temperature to heat vessel to while cleaning.

View File

@@ -1,4 +1,4 @@
string vessel # 要清洗的容器名称 Resource vessel # 要清洗的容器名称
string solvent # 用于清洗容器的溶剂名称 string solvent # 用于清洗容器的溶剂名称
float64 volume # 清洗溶剂的体积,可选参数 float64 volume # 清洗溶剂的体积,可选参数
float64 temp # 清洗时的温度,可选参数 float64 temp # 清洗时的温度,可选参数

View File

@@ -1,5 +1,5 @@
# Goal - 结晶操作的目标参数 # Goal - 结晶操作的目标参数
string vessel # 结晶容器 Resource vessel # 结晶容器
float64 ramp_time # 升温/降温时间 (可选,秒) float64 ramp_time # 升温/降温时间 (可选,秒)
float64 ramp_temp # 目标温度 (可选,摄氏度) float64 ramp_temp # 目标温度 (可选,摄氏度)
--- ---

View File

@@ -1,5 +1,5 @@
# Goal - 溶解操作的目标参数 # Goal - 溶解操作的目标参数
string vessel # 装有要溶解物质的容器名称(必需) Resource vessel # 装有要溶解物质的容器名称(必需)
string solvent # 用于溶解物质的溶剂名称(可选) string solvent # 用于溶解物质的溶剂名称(可选)
string volume # 溶剂的体积(如 "10 mL",可选) string volume # 溶剂的体积(如 "10 mL",可选)
string amount # 要溶解物质的量描述(可选) string amount # 要溶解物质的量描述(可选)

View File

@@ -1,6 +1,6 @@
# Request # Request
string compound # 化合物 string compound # 化合物
string vessel # 干燥容器 Resource vessel # 干燥容器
--- ---
# Result # Result
bool success # 操作是否成功 bool success # 操作是否成功

View File

@@ -1,5 +1,5 @@
# Organic Synthesis Station EvacuateAndRefill Action # Organic Synthesis Station EvacuateAndRefill Action
string vessel Resource vessel
string gas string gas
--- ---
string return_info string return_info

View File

@@ -1,5 +1,5 @@
# Organic Synthesis Station Evaporate Action # Organic Synthesis Station Evaporate Action
string vessel # 目标容器 Resource vessel # 目标容器
float64 pressure # 真空度 float64 pressure # 真空度
float64 temp # 温度 float64 temp # 温度
string time # 🔧 蒸发时间(支持带单位,如"3 min","180",默认秒) string time # 🔧 蒸发时间(支持带单位,如"3 min","180",默认秒)

View File

@@ -1,6 +1,6 @@
# Goal - 过滤操作的目标参数 # Goal - 过滤操作的目标参数
string vessel # 过滤容器(必需) Resource vessel # 过滤容器(必需)
string filtrate_vessel # 滤液容器(可选) Resource filtrate_vessel # 滤液容器(可选)
bool stir # 是否搅拌默认false bool stir # 是否搅拌默认false
float64 stir_speed # 搅拌速度默认0.0 float64 stir_speed # 搅拌速度默认0.0
float64 temp # 温度默认25.0 float64 temp # 温度默认25.0

View File

@@ -1,6 +1,6 @@
string from_vessel # 源容器的名称,即物质起始所在的容器 Resource from_vessel # 源容器的名称,即物质起始所在的容器
string to_vessel # 目标容器的名称,物质过滤后要到达的容器 Resource to_vessel # 目标容器的名称,物质过滤后要到达的容器
string filter_through # 过滤时所通过的介质,如滤纸、柱子等 Resource filter_through # 过滤时所通过的介质,如滤纸、柱子等
string eluting_solvent # 洗脱溶剂的名称,可选参数 string eluting_solvent # 洗脱溶剂的名称,可选参数
float64 eluting_volume # 洗脱溶剂的体积,可选参数 float64 eluting_volume # 洗脱溶剂的体积,可选参数
int32 eluting_repeats # 洗脱操作的重复次数,默认为 0 int32 eluting_repeats # 洗脱操作的重复次数,默认为 0

View File

@@ -1,5 +1,5 @@
# Goal - 加热冷却操作的目标参数 # Goal - 加热冷却操作的目标参数
string vessel # 加热容器名称(必需) Resource vessel # 加热容器名称(必需)
float64 temp # 目标温度可选默认25.0 float64 temp # 目标温度可选默认25.0
string time # 🔧 加热时间(支持带单位,如"5 min","300",默认秒) string time # 🔧 加热时间(支持带单位,如"5 min","300",默认秒)
string temp_spec # 温度规格(可选) string temp_spec # 温度规格(可选)

View File

@@ -1,5 +1,5 @@
# Organic # Organic
string vessel Resource vessel
float64 temp float64 temp
string purpose string purpose
--- ---

View File

@@ -1,5 +1,5 @@
# Organic # Organic
string vessel Resource vessel
--- ---
string return_info string return_info
bool success bool success

View File

@@ -1,7 +1,7 @@
# Request # Request
string temp string temp
string time string time
string vessel Resource vessel
--- ---
# Result # Result
bool success bool success

View File

@@ -1,6 +1,6 @@
# Organic # Organic
string from_vessel Resource from_vessel
string to_vessel Resource to_vessel
float64 volume float64 volume
string amount string amount
float64 time float64 time

View File

@@ -1,5 +1,5 @@
# Goal - 清洗/吹扫操作的目标参数 # Goal - 清洗/吹扫操作的目标参数
string vessel # 清洗容器 Resource vessel # 清洗容器
string gas # 清洗气体 (可选) string gas # 清洗气体 (可选)
float64 time # 清洗时间 (可选,秒) float64 time # 清洗时间 (可选,秒)
float64 pressure # 压力 (可选Pa) float64 pressure # 压力 (可选Pa)

View File

@@ -2,7 +2,7 @@
string ratio # 溶剂比例(如"1:1","3:7" string ratio # 溶剂比例(如"1:1","3:7"
string solvent1 # 第一种溶剂 string solvent1 # 第一种溶剂
string solvent2 # 第二种溶剂 string solvent2 # 第二种溶剂
string vessel # 目标容器 Resource vessel # 目标容器
string volume # 🔧 总体积(支持带单位,如"100 mL","50"默认mL string volume # 🔧 总体积(支持带单位,如"100 mL","50"默认mL
--- ---
# Result # Result

View File

@@ -1,5 +1,6 @@
# Request # Request
string solvent string solvent
Resource vessel
--- ---
# Result # Result
bool success bool success

View File

@@ -1,6 +1,6 @@
# Goal - 柱层析操作的目标参数 # Goal - 柱层析操作的目标参数
string from_vessel # 源容器的名称,即样品起始所在的容器(必需) Resource from_vessel # 源容器的名称,即样品起始所在的容器(必需)
string to_vessel # 目标容器的名称,分离后的样品要到达的容器(必需) Resource to_vessel # 目标容器的名称,分离后的样品要到达的容器(必需)
string column # 所使用的柱子的名称(必需) string column # 所使用的柱子的名称(必需)
string rf # Rf值可选 string rf # Rf值可选
string pct1 # 第一种溶剂百分比(如 "40 %",可选) string pct1 # 第一种溶剂百分比(如 "40 %",可选)

View File

@@ -1,13 +1,13 @@
# Goal - 分离操作的目标参数 # Goal - 分离操作的目标参数
string vessel # 分离容器名称XDL参数必需 Resource vessel # 分离容器名称XDL参数必需
string purpose # 分离目的 ('wash', 'extract', 'separate',可选) string purpose # 分离目的 ('wash', 'extract', 'separate',可选)
string product_phase # 产物相 ('top', 'bottom',可选) string product_phase # 产物相 ('top', 'bottom',可选)
string from_vessel # 源容器(可选) Resource from_vessel # 源容器(可选)
string separation_vessel # 分离容器与vessel同义可选 Resource separation_vessel # 分离容器与vessel同义可选
string to_vessel # 目标容器(可选) Resource to_vessel # 目标容器(可选)
string waste_phase_to_vessel # 废相目标容器(可选) Resource waste_phase_to_vessel # 废相目标容器(可选)
string product_vessel # 产物收集容器XDL参数可选 Resource product_vessel # 产物收集容器XDL参数可选
string waste_vessel # 废液收集容器XDL参数可选 Resource waste_vessel # 废液收集容器XDL参数可选
string solvent # 溶剂名称(可选) string solvent # 溶剂名称(可选)
string solvent_volume # 溶剂体积(如 "200 mL",可选) string solvent_volume # 溶剂体积(如 "200 mL",可选)
string volume # 体积规格XDL参数如 "?",可选) string volume # 体积规格XDL参数如 "?",可选)

View File

@@ -1,5 +1,5 @@
# Goal - 启动清洗/吹扫操作的目标参数 # Goal - 启动清洗/吹扫操作的目标参数
string vessel # 清洗容器 Resource vessel # 清洗容器
string gas # 清洗气体 (可选) string gas # 清洗气体 (可选)
float64 pressure # 压力 (可选Pa) float64 pressure # 压力 (可选Pa)
float64 flow_rate # 流速 (可选mL/min) float64 flow_rate # 流速 (可选mL/min)

View File

@@ -1,5 +1,5 @@
# Goal - 启动搅拌操作的目标参数 # Goal - 启动搅拌操作的目标参数
string vessel # 搅拌容器 Resource vessel # 搅拌容器
float64 stir_speed # 搅拌速度 (可选rpm) float64 stir_speed # 搅拌速度 (可选rpm)
string purpose # 搅拌目的 (可选) string purpose # 搅拌目的 (可选)
--- ---

View File

@@ -1,5 +1,5 @@
# Goal - 搅拌操作的目标参数 # Goal - 搅拌操作的目标参数
string vessel # 搅拌容器名称(必需) Resource vessel # 搅拌容器名称(必需)
string time # 🔧 搅拌时间(如 "0.5 h", "30 min", "300",默认秒) string time # 🔧 搅拌时间(如 "0.5 h", "30 min", "300",默认秒)
string event # 事件标识(如 "A", "B" string event # 事件标识(如 "A", "B"
string time_spec # 时间规格(如 "several minutes" string time_spec # 时间规格(如 "several minutes"

View File

@@ -1,5 +1,5 @@
# Goal - 停止清洗/吹扫操作的目标参数 # Goal - 停止清洗/吹扫操作的目标参数
string vessel # 清洗容器 Resource vessel # 清洗容器
--- ---
# Result - 操作结果 # Result - 操作结果
bool success # 操作是否成功 bool success # 操作是否成功

View File

@@ -1,5 +1,5 @@
# Goal - 停止搅拌操作的目标参数 # Goal - 停止搅拌操作的目标参数
string vessel # 搅拌容器 Resource vessel # 搅拌容器
--- ---
# Result - 操作结果 # Result - 操作结果
bool success # 操作是否成功 bool success # 操作是否成功

View File

@@ -1,8 +1,8 @@
# Goal - 固体清洗操作的目标参数 # Goal - 固体清洗操作的目标参数
string vessel # 装有固体的容器名称(必需) Resource vessel # 装有固体的容器名称(必需)
string solvent # 清洗溶剂名称(必需) string solvent # 清洗溶剂名称(必需)
string volume # 🔧 体积(支持数字和带单位的字符串,如"100 mL","?" string volume # 🔧 体积(支持数字和带单位的字符串,如"100 mL","?"
string filtrate_vessel # 滤液收集容器(可选,默认"" Resource filtrate_vessel # 滤液收集容器(可选,默认""
float64 temp # 清洗温度可选默认25.0 float64 temp # 清洗温度可选默认25.0
bool stir # 是否搅拌可选默认false bool stir # 是否搅拌可选默认false
float64 stir_speed # 搅拌速度可选默认0.0 float64 stir_speed # 搅拌速度可选默认0.0