mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-25 10:29:16 +00:00
- separate_protocol: 修复vessel_id字符串解包crash和tuple truthy or逻辑错误
- heatchill_protocol: 修复vessel字段传入enriched dict而非vessel_id的问题
- hydrogenate_protocol: 修复5处vessel格式错误(裸字符串→{"id": vessel_id})
- 三个文件同时完成debug_print统一和工具函数去重
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
319 lines
10 KiB
Python
319 lines
10 KiB
Python
import networkx as nx
|
||
from typing import List, Dict, Any, Optional
|
||
from .utils.vessel_parser import get_vessel
|
||
from .utils.logger_util import debug_print
|
||
from .utils.unit_parser import parse_temperature_input, parse_time_input
|
||
|
||
|
||
def find_associated_solenoid_valve(G: nx.DiGraph, device_id: str) -> Optional[str]:
|
||
"""查找与指定设备相关联的电磁阀"""
|
||
solenoid_valves = [
|
||
node for node in G.nodes()
|
||
if ('solenoid' in (G.nodes[node].get('class') or '').lower()
|
||
or 'solenoid_valve' in node)
|
||
]
|
||
|
||
# 通过网络连接查找直接相连的电磁阀
|
||
for solenoid in solenoid_valves:
|
||
if G.has_edge(device_id, solenoid) or G.has_edge(solenoid, device_id):
|
||
return solenoid
|
||
|
||
# 通过命名规则查找关联的电磁阀
|
||
device_type = ""
|
||
if 'gas' in device_id.lower():
|
||
device_type = "gas"
|
||
elif 'h2' in device_id.lower() or 'hydrogen' in device_id.lower():
|
||
device_type = "gas"
|
||
|
||
if device_type:
|
||
for solenoid in solenoid_valves:
|
||
if device_type in solenoid.lower():
|
||
return solenoid
|
||
|
||
return None
|
||
|
||
|
||
def find_connected_device(G: nx.DiGraph, vessel: str, device_type: str) -> str:
|
||
"""
|
||
查找与容器相连的指定类型设备
|
||
|
||
Args:
|
||
G: 网络图
|
||
vessel: 容器名称
|
||
device_type: 设备类型 ('heater', 'stirrer', 'gas_source')
|
||
|
||
Returns:
|
||
str: 设备ID,如果没有则返回None
|
||
"""
|
||
# 根据设备类型定义搜索关键词
|
||
if device_type == 'heater':
|
||
keywords = ['heater', 'heat', 'heatchill']
|
||
device_class = 'virtual_heatchill'
|
||
elif device_type == 'stirrer':
|
||
keywords = ['stirrer', 'stir']
|
||
device_class = 'virtual_stirrer'
|
||
elif device_type == 'gas_source':
|
||
keywords = ['gas', 'h2', 'hydrogen']
|
||
device_class = 'virtual_gas_source'
|
||
else:
|
||
return None
|
||
|
||
# 查找设备节点
|
||
device_nodes = []
|
||
for node in G.nodes():
|
||
node_data = G.nodes[node]
|
||
node_name = node.lower()
|
||
node_class = node_data.get('class', '').lower()
|
||
|
||
if any(keyword in node_name for keyword in keywords):
|
||
device_nodes.append(node)
|
||
elif device_class in node_class:
|
||
device_nodes.append(node)
|
||
|
||
debug_print(f"找到的{device_type}节点: {device_nodes}")
|
||
|
||
# 检查是否有设备与目标容器相连
|
||
for device in device_nodes:
|
||
if G.has_edge(device, vessel) or G.has_edge(vessel, device):
|
||
debug_print(f"找到与容器 '{vessel}' 相连的{device_type}: {device}")
|
||
return device
|
||
|
||
# 如果没有直接连接,查找距离最近的设备
|
||
for device in device_nodes:
|
||
try:
|
||
path = nx.shortest_path(G, source=device, target=vessel)
|
||
if len(path) <= 3: # 最多2个中间节点
|
||
debug_print(f"找到距离较近的{device_type}: {device}")
|
||
return device
|
||
except nx.NetworkXNoPath:
|
||
continue
|
||
|
||
debug_print(f"未找到与容器 '{vessel}' 相连的{device_type}")
|
||
return None
|
||
|
||
|
||
def generate_hydrogenate_protocol(
|
||
G: nx.DiGraph,
|
||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||
temp: str,
|
||
time: str,
|
||
**kwargs # 接收其他可能的参数但不使用
|
||
) -> List[Dict[str, Any]]:
|
||
"""
|
||
生成氢化反应协议序列 - 支持vessel字典
|
||
|
||
Args:
|
||
G: 有向图,节点为容器和设备
|
||
vessel: 反应容器字典(从XDL传入)
|
||
temp: 反应温度(如 "45 °C")
|
||
time: 反应时间(如 "2 h")
|
||
**kwargs: 其他可选参数,但不使用
|
||
|
||
Returns:
|
||
List[Dict[str, Any]]: 动作序列
|
||
"""
|
||
|
||
# 🔧 核心修改:从字典中提取容器ID
|
||
vessel_id, vessel_data = get_vessel(vessel)
|
||
|
||
action_sequence = []
|
||
|
||
# 解析参数
|
||
temperature = parse_temperature_input(temp)
|
||
reaction_time = parse_time_input(time)
|
||
|
||
debug_print(f"开始生成氢化反应协议: vessel={vessel_id}, "
|
||
f"temp={temperature}°C, time={reaction_time/3600:.1f}h")
|
||
|
||
# 记录氢化前的容器状态
|
||
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
|
||
|
||
# 1. 验证目标容器存在
|
||
if vessel_id not in G.nodes():
|
||
debug_print(f"⚠️ 容器 '{vessel_id}' 不存在于系统中,跳过氢化反应")
|
||
return action_sequence
|
||
|
||
# 2. 查找相连的设备
|
||
heater_id = find_connected_device(G, vessel_id, 'heater')
|
||
stirrer_id = find_connected_device(G, vessel_id, 'stirrer')
|
||
gas_source_id = find_connected_device(G, vessel_id, 'gas_source')
|
||
|
||
debug_print(f"设备配置: heater={heater_id or '未找到'}, "
|
||
f"stirrer={stirrer_id or '未找到'}, gas={gas_source_id or '未找到'}")
|
||
|
||
# 3. 启动搅拌器
|
||
if stirrer_id:
|
||
action_sequence.append({
|
||
"device_id": stirrer_id,
|
||
"action_name": "start_stir",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id},
|
||
"stir_speed": 300.0,
|
||
"purpose": "氢化反应: 开始搅拌"
|
||
}
|
||
})
|
||
else:
|
||
debug_print(f"⚠️ 未找到搅拌器,继续执行")
|
||
|
||
# 4. 启动气源(氢气)
|
||
if gas_source_id:
|
||
action_sequence.append({
|
||
"device_id": gas_source_id,
|
||
"action_name": "set_status",
|
||
"action_kwargs": {
|
||
"string": "ON"
|
||
}
|
||
})
|
||
|
||
# 查找相关的电磁阀
|
||
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
|
||
if gas_solenoid:
|
||
action_sequence.append({
|
||
"device_id": gas_solenoid,
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {
|
||
"command": "OPEN"
|
||
}
|
||
})
|
||
else:
|
||
debug_print(f"⚠️ 未找到气源,继续执行")
|
||
|
||
# 5. 等待气体稳定
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {
|
||
"time": 30.0,
|
||
"description": "等待氢气环境稳定"
|
||
}
|
||
})
|
||
|
||
# 6. 启动加热器
|
||
if heater_id:
|
||
action_sequence.append({
|
||
"device_id": heater_id,
|
||
"action_name": "heat_chill_start",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id},
|
||
"temp": temperature,
|
||
"purpose": f"氢化反应: 加热到 {temperature}°C"
|
||
}
|
||
})
|
||
|
||
# 等待温度稳定
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {
|
||
"time": 20.0,
|
||
"description": f"等待温度稳定到 {temperature}°C"
|
||
}
|
||
})
|
||
|
||
# 模拟运行时间优化
|
||
original_reaction_time = reaction_time
|
||
simulation_time_limit = 60.0
|
||
|
||
if reaction_time > simulation_time_limit:
|
||
reaction_time = simulation_time_limit
|
||
debug_print(f"模拟运行优化: {original_reaction_time}s → {reaction_time}s")
|
||
|
||
# 保持反应温度
|
||
action_sequence.append({
|
||
"device_id": heater_id,
|
||
"action_name": "heat_chill",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id},
|
||
"temp": temperature,
|
||
"time": reaction_time,
|
||
"purpose": f"氢化反应: 保持 {temperature}°C,反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "")
|
||
}
|
||
})
|
||
|
||
else:
|
||
debug_print(f"⚠️ 未找到加热器,使用室温反应")
|
||
|
||
# 室温反应也需要时间优化
|
||
original_reaction_time = reaction_time
|
||
simulation_time_limit = 60.0
|
||
|
||
if reaction_time > simulation_time_limit:
|
||
reaction_time = simulation_time_limit
|
||
debug_print(f"室温反应时间优化: {original_reaction_time}s → {reaction_time}s")
|
||
|
||
# 室温反应,只等待时间
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {
|
||
"time": reaction_time,
|
||
"description": f"室温氢化反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "")
|
||
}
|
||
})
|
||
|
||
# 7. 停止加热
|
||
if heater_id:
|
||
action_sequence.append({
|
||
"device_id": heater_id,
|
||
"action_name": "heat_chill_stop",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id},
|
||
"purpose": "氢化反应完成,停止加热"
|
||
}
|
||
})
|
||
|
||
# 8. 等待冷却
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {
|
||
"time": 300.0,
|
||
"description": "等待反应混合物冷却"
|
||
}
|
||
})
|
||
|
||
# 9. 停止气源
|
||
if gas_source_id:
|
||
# 先关闭电磁阀
|
||
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
|
||
if gas_solenoid:
|
||
action_sequence.append({
|
||
"device_id": gas_solenoid,
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {
|
||
"command": "CLOSED"
|
||
}
|
||
})
|
||
|
||
# 再关闭气源
|
||
action_sequence.append({
|
||
"device_id": gas_source_id,
|
||
"action_name": "set_status",
|
||
"action_kwargs": {
|
||
"string": "OFF"
|
||
}
|
||
})
|
||
|
||
# 10. 停止搅拌
|
||
if stirrer_id:
|
||
action_sequence.append({
|
||
"device_id": stirrer_id,
|
||
"action_name": "stop_stir",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id},
|
||
"purpose": "氢化反应完成,停止搅拌"
|
||
}
|
||
})
|
||
|
||
# 氢化完成后的状态(氢化反应通常不改变体积)
|
||
final_liquid_volume = original_liquid_volume
|
||
|
||
# 总结
|
||
debug_print(f"氢化反应协议生成完成: {len(action_sequence)} 个动作, "
|
||
f"vessel={vessel_id}, temp={temperature}°C, time={reaction_time/60:.1f}min, "
|
||
f"volume={original_liquid_volume:.2f}→{final_liquid_volume:.2f}mL")
|
||
|
||
return action_sequence
|