diff --git a/test/experiments/Protocol_Test_Station/pumptransfer_filterthrough_test_station.json b/test/experiments/Protocol_Test_Station/pumptransfer_filterthrough_test_station.json new file mode 100644 index 0000000..b9acd35 --- /dev/null +++ b/test/experiments/Protocol_Test_Station/pumptransfer_filterthrough_test_station.json @@ -0,0 +1,778 @@ +{ + "nodes": [ + { + "id": "PumpTransferFilterThroughTestStation", + "name": "泵转移+过滤介质测试站", + "children": [ + "transfer_pump_1", + "transfer_pump_2", + "multiway_valve_1", + "multiway_valve_2", + "reaction_mixture", + "crude_product", + "filter_celite", + "column_silica_gel", + "filter_C18", + "pure_product", + "collection_bottle_1", + "collection_bottle_2", + "collection_bottle_3", + "intermediate_vessel_1", + "intermediate_vessel_2", + "flask_water", + "flask_ethanol", + "flask_methanol", + "flask_ethyl_acetate", + "flask_acetone", + "flask_hexane", + "flask_air", + "waste_workup" + ], + "parent": null, + "type": "device", + "class": "workstation", + "position": { + "x": 500, + "y": 200, + "z": 0 + }, + "config": { + "protocol_type": [ + "PumpTransferProtocol", + "FilterThroughProtocol" + ] + }, + "data": {} + }, + { + "id": "transfer_pump_1", + "name": "主转移泵", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "device", + "class": "virtual_transfer_pump", + "position": { + "x": 200, + "y": 300, + "z": 0 + }, + "config": { + "port": "VIRTUAL_PUMP1", + "max_volume": 25.0, + "transfer_rate": 2.0 + }, + "data": { + "position": 0.0, + "status": "Idle" + } + }, + { + "id": "transfer_pump_2", + "name": "副转移泵", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "device", + "class": "virtual_transfer_pump", + "position": { + "x": 400, + "y": 300, + "z": 0 + }, + "config": { + "port": "VIRTUAL_PUMP2", + "max_volume": 25.0, + "transfer_rate": 2.0 + }, + "data": { + "position": 0.0, + "status": "Idle" + } + }, + { + "id": "multiway_valve_1", + "name": "溶剂分配阀", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "device", + "class": "virtual_multiway_valve", + "position": { + "x": 200, + "y": 400, + "z": 0 + }, + "config": { + "port": "VIRTUAL_VALVE1", + "positions": 8 + }, + "data": { + "current_position": 1 + } + }, + { + "id": "multiway_valve_2", + "name": "样品分配阀", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "device", + "class": "virtual_multiway_valve", + "position": { + "x": 400, + "y": 400, + "z": 0 + }, + "config": { + "port": "VIRTUAL_VALVE2", + "positions": 8 + }, + "data": { + "current_position": 1 + } + }, + { + "id": "reaction_mixture", + "name": "反应混合物", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 100, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "liquid_type": "organic_reaction_mixture", + "liquid_volume": 250.0 + } + ] + } + }, + { + "id": "crude_product", + "name": "粗产品", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 200, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "liquid_type": "crude_organic_compound", + "liquid_volume": 150.0 + } + ] + } + }, + { + "id": "filter_celite", + "name": "硅藻土过滤器", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 600, + "y": 450, + "z": 0 + }, + "config": { + "max_volume": 300.0, + "filter_type": "celite_pad" + }, + "data": { + "liquid": [] + } + }, + { + "id": "column_silica_gel", + "name": "硅胶柱", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 700, + "y": 450, + "z": 0 + }, + "config": { + "max_volume": 200.0, + "filter_type": "silica_gel_column" + }, + "data": { + "liquid": [] + } + }, + { + "id": "filter_C18", + "name": "C18固相萃取柱", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 800, + "y": 450, + "z": 0 + }, + "config": { + "max_volume": 100.0, + "filter_type": "C18_cartridge" + }, + "data": { + "liquid": [] + } + }, + { + "id": "pure_product", + "name": "纯产品", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 900, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "collection_bottle_1", + "name": "收集瓶1", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 600, + "y": 550, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "collection_bottle_2", + "name": "收集瓶2", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 700, + "y": 550, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "collection_bottle_3", + "name": "收集瓶3", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 800, + "y": 550, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "intermediate_vessel_1", + "name": "中间容器1", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 300, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "intermediate_vessel_2", + "name": "中间容器2", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 400, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "flask_water", + "name": "蒸馏水瓶", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 100, + "y": 600, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "liquid_type": "water", + "liquid_volume": 900.0 + } + ] + } + }, + { + "id": "flask_ethanol", + "name": "乙醇瓶", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 200, + "y": 600, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "liquid_type": "ethanol", + "liquid_volume": 800.0 + } + ] + } + }, + { + "id": "flask_methanol", + "name": "甲醇瓶", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 300, + "y": 600, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "liquid_type": "methanol", + "liquid_volume": 800.0 + } + ] + } + }, + { + "id": "flask_ethyl_acetate", + "name": "乙酸乙酯瓶", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 400, + "y": 600, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "liquid_type": "ethyl_acetate", + "liquid_volume": 800.0 + } + ] + } + }, + { + "id": "flask_acetone", + "name": "丙酮瓶", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 500, + "y": 600, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "liquid_type": "acetone", + "liquid_volume": 800.0 + } + ] + } + }, + { + "id": "flask_hexane", + "name": "正己烷瓶", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 600, + "y": 600, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "liquid_type": "hexane", + "liquid_volume": 800.0 + } + ] + } + }, + { + "id": "flask_air", + "name": "空气瓶", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 700, + "y": 600, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "waste_workup", + "name": "废液瓶", + "children": [], + "parent": "PumpTransferFilterThroughTestStation", + "type": "container", + "class": null, + "position": { + "x": 800, + "y": 600, + "z": 0 + }, + "config": { + "max_volume": 2000.0 + }, + "data": { + "liquid": [] + } + } + ], + "links": [ + { + "id": "link_pump1_valve1", + "source": "transfer_pump_1", + "target": "multiway_valve_1", + "type": "fluid", + "port": { + "transfer_pump_1": "transferpump", + "multiway_valve_1": "transferpump" + } + }, + { + "id": "link_pump2_valve2", + "source": "transfer_pump_2", + "target": "multiway_valve_2", + "type": "fluid", + "port": { + "transfer_pump_2": "transferpump", + "multiway_valve_2": "transferpump" + } + }, + { + "id": "link_valve1_air", + "source": "multiway_valve_1", + "target": "flask_air", + "type": "fluid", + "port": { + "multiway_valve_1": "1", + "flask_air": "top" + } + }, + { + "id": "link_valve1_water", + "source": "multiway_valve_1", + "target": "flask_water", + "type": "fluid", + "port": { + "multiway_valve_1": "2", + "flask_water": "outlet" + } + }, + { + "id": "link_valve1_ethanol", + "source": "multiway_valve_1", + "target": "flask_ethanol", + "type": "fluid", + "port": { + "multiway_valve_1": "3", + "flask_ethanol": "outlet" + } + }, + { + "id": "link_valve1_methanol", + "source": "multiway_valve_1", + "target": "flask_methanol", + "type": "fluid", + "port": { + "multiway_valve_1": "4", + "flask_methanol": "outlet" + } + }, + { + "id": "link_valve1_ethyl_acetate", + "source": "multiway_valve_1", + "target": "flask_ethyl_acetate", + "type": "fluid", + "port": { + "multiway_valve_1": "5", + "flask_ethyl_acetate": "outlet" + } + }, + { + "id": "link_valve1_acetone", + "source": "multiway_valve_1", + "target": "flask_acetone", + "type": "fluid", + "port": { + "multiway_valve_1": "6", + "flask_acetone": "outlet" + } + }, + { + "id": "link_valve1_hexane", + "source": "multiway_valve_1", + "target": "flask_hexane", + "type": "fluid", + "port": { + "multiway_valve_1": "7", + "flask_hexane": "outlet" + } + }, + { + "id": "link_valve1_valve2", + "source": "multiway_valve_1", + "target": "multiway_valve_2", + "type": "fluid", + "port": { + "multiway_valve_1": "8", + "multiway_valve_2": "1" + } + }, + { + "id": "link_valve2_reaction_mixture", + "source": "multiway_valve_2", + "target": "reaction_mixture", + "type": "fluid", + "port": { + "multiway_valve_2": "2", + "reaction_mixture": "inlet" + } + }, + { + "id": "link_valve2_crude_product", + "source": "multiway_valve_2", + "target": "crude_product", + "type": "fluid", + "port": { + "multiway_valve_2": "3", + "crude_product": "inlet" + } + }, + { + "id": "link_valve2_intermediate1", + "source": "multiway_valve_2", + "target": "intermediate_vessel_1", + "type": "fluid", + "port": { + "multiway_valve_2": "4", + "intermediate_vessel_1": "inlet" + } + }, + { + "id": "link_valve2_intermediate2", + "source": "multiway_valve_2", + "target": "intermediate_vessel_2", + "type": "fluid", + "port": { + "multiway_valve_2": "5", + "intermediate_vessel_2": "inlet" + } + }, + { + "id": "link_valve2_celite", + "source": "multiway_valve_2", + "target": "filter_celite", + "type": "fluid", + "port": { + "multiway_valve_2": "6", + "filter_celite": "inlet" + } + }, + { + "id": "link_valve2_silica_gel", + "source": "multiway_valve_2", + "target": "column_silica_gel", + "type": "fluid", + "port": { + "multiway_valve_2": "7", + "column_silica_gel": "inlet" + } + }, + { + "id": "link_valve2_C18", + "source": "multiway_valve_2", + "target": "filter_C18", + "type": "fluid", + "port": { + "multiway_valve_2": "8", + "filter_C18": "inlet" + } + }, + { + "id": "link_celite_collection1", + "source": "filter_celite", + "target": "collection_bottle_1", + "type": "fluid", + "port": { + "filter_celite": "outlet", + "collection_bottle_1": "inlet" + } + }, + { + "id": "link_silica_gel_collection2", + "source": "column_silica_gel", + "target": "collection_bottle_2", + "type": "fluid", + "port": { + "column_silica_gel": "outlet", + "collection_bottle_2": "inlet" + } + }, + { + "id": "link_C18_collection3", + "source": "filter_C18", + "target": "collection_bottle_3", + "type": "fluid", + "port": { + "filter_C18": "outlet", + "collection_bottle_3": "inlet" + } + }, + { + "id": "link_collection1_pure_product", + "source": "collection_bottle_1", + "target": "pure_product", + "type": "fluid", + "port": { + "collection_bottle_1": "outlet", + "pure_product": "inlet" + } + }, + { + "id": "link_collection2_pure_product", + "source": "collection_bottle_2", + "target": "pure_product", + "type": "fluid", + "port": { + "collection_bottle_2": "outlet", + "pure_product": "inlet" + } + }, + { + "id": "link_collection3_pure_product", + "source": "collection_bottle_3", + "target": "pure_product", + "type": "fluid", + "port": { + "collection_bottle_3": "outlet", + "pure_product": "inlet" + } + }, + { + "id": "link_waste_connection", + "source": "pure_product", + "target": "waste_workup", + "type": "fluid", + "port": { + "pure_product": "waste_outlet", + "waste_workup": "inlet" + } + } + ] +} \ No newline at end of file diff --git a/test/experiments/Protocol_Test_Station/run_column_protocol_test_station.json b/test/experiments/Protocol_Test_Station/run_column_protocol_test_station.json new file mode 100644 index 0000000..e10d73e --- /dev/null +++ b/test/experiments/Protocol_Test_Station/run_column_protocol_test_station.json @@ -0,0 +1,432 @@ +{ + "nodes": [ + { + "id": "RunColumnTestStation", + "name": "柱层析测试工作站", + "children": [ + "transfer_pump_1", + "multiway_valve_1", + "column_1", + "flask_sample", + "flask_hexane", + "flask_ethyl_acetate", + "flask_methanol", + "column_vessel", + "collection_flask_1", + "collection_flask_2", + "collection_flask_3", + "waste_flask", + "main_reactor" + ], + "parent": null, + "type": "device", + "class": "workstation", + "position": { + "x": 500, + "y": 200, + "z": 0 + }, + "config": { + "protocol_type": ["RunColumnProtocol", "PumpTransferProtocol"] + }, + "data": {} + }, + { + "id": "transfer_pump_1", + "name": "转移泵", + "children": [], + "parent": "RunColumnTestStation", + "type": "device", + "class": "virtual_transfer_pump", + "position": { + "x": 300, + "y": 300, + "z": 0 + }, + "config": { + "port": "VIRTUAL_PUMP1", + "max_volume": 50.0, + "transfer_rate": 10.0 + }, + "data": { + "status": "Idle", + "position": 0.0 + } + }, + { + "id": "multiway_valve_1", + "name": "八通阀门", + "children": [], + "parent": "RunColumnTestStation", + "type": "device", + "class": "virtual_multiway_valve", + "position": { + "x": 300, + "y": 400, + "z": 0 + }, + "config": { + "port": "VIRTUAL_VALVE1", + "positions": 8 + }, + "data": { + "current_position": 1 + } + }, + { + "id": "column_1", + "name": "柱层析设备", + "children": [], + "parent": "RunColumnTestStation", + "type": "device", + "class": "virtual_column", + "position": { + "x": 600, + "y": 350, + "z": 0 + }, + "config": { + "port": "VIRTUAL_COLUMN1", + "max_flow_rate": 5.0, + "column_length": 30.0, + "column_diameter": 2.5 + }, + "data": { + "status": "Idle", + "column_state": "Ready" + } + }, + { + "id": "flask_sample", + "name": "样品瓶", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 100, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 500.0 + }, + "data": { + "liquid": [ + { + "name": "crude_mixture", + "volume": 200.0, + "concentration": 70.0 + } + ] + } + }, + { + "id": "flask_hexane", + "name": "正己烷洗脱剂", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 200, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 2000.0 + }, + "data": { + "liquid": [ + { + "name": "hexane", + "volume": 1500.0, + "concentration": 99.8 + } + ] + } + }, + { + "id": "flask_ethyl_acetate", + "name": "乙酸乙酯洗脱剂", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 300, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 2000.0 + }, + "data": { + "liquid": [ + { + "name": "ethyl_acetate", + "volume": 1500.0, + "concentration": 99.5 + } + ] + } + }, + { + "id": "flask_methanol", + "name": "甲醇洗脱剂", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 400, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "name": "methanol", + "volume": 800.0, + "concentration": 99.9 + } + ] + } + }, + { + "id": "column_vessel", + "name": "柱容器", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 600, + "y": 450, + "z": 0 + }, + "config": { + "max_volume": 300.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "collection_flask_1", + "name": "收集瓶1", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 700, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "collection_flask_2", + "name": "收集瓶2", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 800, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "collection_flask_3", + "name": "收集瓶3", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 900, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "waste_flask", + "name": "废液瓶", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 1000, + "y": 500, + "z": 0 + }, + "config": { + "max_volume": 2000.0 + }, + "data": { + "liquid": [] + } + }, + { + "id": "main_reactor", + "name": "反应器", + "children": [], + "parent": "RunColumnTestStation", + "type": "container", + "class": null, + "position": { + "x": 600, + "y": 300, + "z": 0 + }, + "config": { + "max_volume": 1000.0 + }, + "data": { + "liquid": [ + { + "name": "reaction_mixture", + "volume": 300.0, + "concentration": 85.0 + } + ] + } + } + ], + "links": [ + { + "id": "link_pump_valve", + "source": "transfer_pump_1", + "target": "multiway_valve_1", + "type": "fluid", + "port": { + "transfer_pump_1": "transferpump", + "multiway_valve_1": "transferpump" + } + }, + { + "id": "link_valve_sample", + "source": "multiway_valve_1", + "target": "flask_sample", + "type": "fluid", + "port": { + "multiway_valve_1": "1", + "flask_sample": "outlet" + } + }, + { + "id": "link_valve_hexane", + "source": "multiway_valve_1", + "target": "flask_hexane", + "type": "fluid", + "port": { + "multiway_valve_1": "2", + "flask_hexane": "outlet" + } + }, + { + "id": "link_valve_ethyl_acetate", + "source": "multiway_valve_1", + "target": "flask_ethyl_acetate", + "type": "fluid", + "port": { + "multiway_valve_1": "3", + "flask_ethyl_acetate": "outlet" + } + }, + { + "id": "link_valve_methanol", + "source": "multiway_valve_1", + "target": "flask_methanol", + "type": "fluid", + "port": { + "multiway_valve_1": "4", + "flask_methanol": "outlet" + } + }, + { + "id": "link_valve_column_vessel", + "source": "multiway_valve_1", + "target": "column_vessel", + "type": "fluid", + "port": { + "multiway_valve_1": "5", + "column_vessel": "inlet" + } + }, + { + "id": "link_valve_collection1", + "source": "multiway_valve_1", + "target": "collection_flask_1", + "type": "fluid", + "port": { + "multiway_valve_1": "6", + "collection_flask_1": "inlet" + } + }, + { + "id": "link_valve_collection2", + "source": "multiway_valve_1", + "target": "collection_flask_2", + "type": "fluid", + "port": { + "multiway_valve_1": "7", + "collection_flask_2": "inlet" + } + }, + { + "id": "link_valve_waste", + "source": "multiway_valve_1", + "target": "waste_flask", + "type": "fluid", + "port": { + "multiway_valve_1": "8", + "waste_flask": "inlet" + } + }, + { + "id": "link_column_device_vessel", + "source": "column_1", + "target": "column_vessel", + "type": "transport", + "port": { + "column_1": "columnin", + "column_vessel": "column_port" + } + }, + { + "id": "link_column_collection3", + "source": "column_1", + "target": "collection_flask_3", + "type": "transport", + "port": { + "column_1": "columnout", + "collection_flask_3": "column_outlet" + } + } + ] +} \ No newline at end of file diff --git a/test/experiments/comprehensive_protocol/checklist.md b/test/experiments/comprehensive_protocol/checklist.md index 4d1e7c0..ed73ec5 100644 --- a/test/experiments/comprehensive_protocol/checklist.md +++ b/test/experiments/comprehensive_protocol/checklist.md @@ -29,6 +29,8 @@ 这个重复了,删掉TransferProtocol: generate_transfer_protocol, CleanVesselProtocol: generate_clean_vessel_protocol, (√) DissolveProtocol: generate_dissolve_protocol, (√) - FilterThroughProtocol: generate_filter_through_protocol, (×) + FilterThroughProtocol: generate_filter_through_protocol, (√) RunColumnProtocol: generate_run_column_protocol, (×) WashSolidProtocol: generate_wash_solid_protocol, (×) + +上下文体积搜索 \ No newline at end of file diff --git a/unilabos/compile/filter_through_protocol.py b/unilabos/compile/filter_through_protocol.py index 009756b..314dbc7 100644 --- a/unilabos/compile/filter_through_protocol.py +++ b/unilabos/compile/filter_through_protocol.py @@ -1,5 +1,72 @@ from typing import List, Dict, Any import networkx as nx +from .pump_protocol import generate_pump_protocol + + +def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float: + """获取容器中的液体体积""" + if vessel not in G.nodes(): + return 0.0 + + vessel_data = G.nodes[vessel].get('data', {}) + liquids = vessel_data.get('liquid', []) + + total_volume = 0.0 + for liquid in liquids: + if isinstance(liquid, dict) and 'liquid_volume' in liquid: + total_volume += liquid['liquid_volume'] + + return total_volume + + +def find_filter_through_vessel(G: nx.DiGraph, filter_through: str) -> str: + """查找过滤介质容器""" + # 直接使用 filter_through 参数作为容器名称 + if filter_through in G.nodes(): + return filter_through + + # 尝试常见的过滤介质容器命名 + possible_names = [ + f"filter_{filter_through}", + f"{filter_through}_filter", + f"column_{filter_through}", + f"{filter_through}_column", + "filter_through_vessel", + "column_vessel", + "chromatography_column", + "filter_column" + ] + + for vessel_name in possible_names: + if vessel_name in G.nodes(): + return vessel_name + + raise ValueError(f"未找到过滤介质容器 '{filter_through}'。尝试了以下名称: {[filter_through] + possible_names}") + + +def find_eluting_solvent_vessel(G: nx.DiGraph, eluting_solvent: str) -> str: + """查找洗脱溶剂容器""" + if not eluting_solvent: + return "" + + # 按照命名规则查找溶剂瓶 + solvent_vessel_id = f"flask_{eluting_solvent}" + + if solvent_vessel_id in G.nodes(): + return solvent_vessel_id + + # 如果直接匹配失败,尝试模糊匹配 + for node in G.nodes(): + if node.startswith('flask_') and eluting_solvent.lower() in node.lower(): + return node + + # 如果还是找不到,列出所有可用的溶剂瓶 + available_flasks = [node for node in G.nodes() + if node.startswith('flask_') + and G.nodes[node].get('type') == 'container'] + + raise ValueError(f"找不到洗脱溶剂 '{eluting_solvent}' 对应的溶剂瓶。可用溶剂瓶: {available_flasks}") + def generate_filter_through_protocol( G: nx.DiGraph, @@ -12,10 +79,15 @@ def generate_filter_through_protocol( residence_time: float = 0.0 ) -> List[Dict[str, Any]]: """ - 生成通过过滤介质过滤的协议序列 + 生成通过过滤介质过滤的协议序列,复用 pump_protocol 的成熟算法 + + 过滤流程: + 1. 液体转移:将样品从源容器转移到过滤介质 + 2. 重力过滤:液体通过过滤介质自动流到目标容器 + 3. 洗脱操作:将洗脱溶剂通过过滤介质洗脱目标物质 Args: - G: 有向图,节点为设备和容器 + G: 有向图,节点为设备和容器,边为流体管道 from_vessel: 源容器的名称,即物质起始所在的容器 to_vessel: 目标容器的名称,物质过滤后要到达的容器 filter_through: 过滤时所通过的介质,如滤纸、柱子等 @@ -28,123 +100,288 @@ def generate_filter_through_protocol( List[Dict[str, Any]]: 过滤操作的动作序列 Raises: - ValueError: 当找不到必要的设备时抛出异常 + ValueError: 当找不到必要的设备或容器时 Examples: - filter_through_protocol = generate_filter_through_protocol( - G, "reactor", "collection_flask", "celite", "ethanol", 50.0, 2, 60.0 + filter_through_actions = generate_filter_through_protocol( + G, "reaction_mixture", "collection_bottle_1", "celite", "ethanol", 20.0, 2, 30.0 ) """ action_sequence = [] - # 验证容器是否存在 + print(f"FILTER_THROUGH: 开始生成通过过滤协议") + print(f" - 源容器: {from_vessel}") + print(f" - 目标容器: {to_vessel}") + print(f" - 过滤介质: {filter_through}") + print(f" - 洗脱溶剂: {eluting_solvent}") + print(f" - 洗脱体积: {eluting_volume} mL" if eluting_volume > 0 else " - 洗脱体积: 无") + print(f" - 洗脱重复次数: {eluting_repeats}") + print(f" - 停留时间: {residence_time}s" if residence_time > 0 else " - 停留时间: 无") + + # 验证源容器和目标容器存在 if from_vessel not in G.nodes(): - raise ValueError(f"源容器 {from_vessel} 不存在于图中") + raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中") if to_vessel not in G.nodes(): - raise ValueError(f"目标容器 {to_vessel} 不存在于图中") + raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中") - # 查找转移泵设备(用于液体转移) - pump_nodes = [node for node in G.nodes() - if G.nodes[node].get('class') == 'virtual_transfer_pump'] + # 获取源容器中的液体体积 + source_volume = get_vessel_liquid_volume(G, from_vessel) + print(f"FILTER_THROUGH: 源容器 {from_vessel} 中有 {source_volume} mL 液体") - if not pump_nodes: - raise ValueError("没有找到可用的转移泵设备") + # 查找过滤介质容器 + try: + filter_through_vessel = find_filter_through_vessel(G, filter_through) + print(f"FILTER_THROUGH: 找到过滤介质容器: {filter_through_vessel}") + except ValueError as e: + raise ValueError(f"无法找到过滤介质容器: {str(e)}") - pump_id = pump_nodes[0] + # 查找洗脱溶剂容器(如果需要) + eluting_vessel = "" + if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0: + try: + eluting_vessel = find_eluting_solvent_vessel(G, eluting_solvent) + print(f"FILTER_THROUGH: 找到洗脱溶剂容器: {eluting_vessel}") + except ValueError as e: + raise ValueError(f"无法找到洗脱溶剂容器: {str(e)}") - # 查找过滤设备(可选,如果有专门的过滤设备) - filter_nodes = [node for node in G.nodes() - if G.nodes[node].get('class') == 'virtual_filter'] + # === 第一步:将样品从源容器转移到过滤介质 === + transfer_volume = source_volume if source_volume > 0 else 100.0 # 默认100mL + print(f"FILTER_THROUGH: 将 {transfer_volume} mL 样品从 {from_vessel} 转移到 {filter_through_vessel}") - filter_id = filter_nodes[0] if filter_nodes else None + try: + # 使用成熟的 pump_protocol 算法进行液体转移 + sample_transfer_actions = generate_pump_protocol( + G=G, + from_vessel=from_vessel, + to_vessel=filter_through_vessel, + volume=transfer_volume, + flowrate=0.8, # 较慢的流速,避免冲击过滤介质 + transfer_flowrate=1.2 + ) + action_sequence.extend(sample_transfer_actions) + except Exception as e: + raise ValueError(f"无法将样品转移到过滤介质: {str(e)}") - # 查找洗脱溶剂容器(如果需要洗脱) - eluting_vessel = None - if eluting_solvent and eluting_volume > 0: - eluting_vessel = f"flask_{eluting_solvent}" - if eluting_vessel not in G.nodes(): - # 查找可用的溶剂容器 - available_vessels = [node for node in G.nodes() - if node.startswith('flask_') and - G.nodes[node].get('type') == 'container'] - if available_vessels: - eluting_vessel = available_vessels[0] - else: - raise ValueError(f"没有找到洗脱溶剂容器 {eluting_solvent}") - - # 步骤1:将样品从源容器转移到过滤装置(模拟通过过滤介质) - # 这里我们将过滤过程分解为多个转移步骤来模拟通过介质的过程 - - # 首先转移样品(模拟样品通过过滤介质) - action_sequence.append({ - "device_id": pump_id, - "action_name": "transfer", - "action_kwargs": { - "from_vessel": from_vessel, - "to_vessel": to_vessel, - "volume": 0.0, # 转移所有液体,体积由系统确定 - "amount": f"通过 {filter_through} 过滤", - "time": residence_time if residence_time > 0 else 0.0, - "viscous": False, - "rinsing_solvent": "", - "rinsing_volume": 0.0, - "rinsing_repeats": 0, - "solid": True # 通过过滤介质可能涉及固体分离 - } - }) - - # 步骤2:如果有专门的过滤设备,使用过滤设备处理 - if filter_id: + # === 第二步:等待样品通过过滤介质(停留时间) === + if residence_time > 0: + print(f"FILTER_THROUGH: 等待样品在过滤介质中停留 {residence_time}s") action_sequence.append({ - "device_id": filter_id, - "action_name": "filter_sample", - "action_kwargs": { - "vessel": to_vessel, - "filtrate_vessel": to_vessel, - "stir": False, - "stir_speed": 0.0, - "temp": 25.0, - "continue_heatchill": False, - "volume": 0.0 - } + "action_name": "wait", + "action_kwargs": {"time": residence_time} + }) + else: + # 即使没有指定停留时间,也等待一段时间让液体通过 + default_wait_time = max(10, transfer_volume / 10) # 根据体积估算等待时间 + print(f"FILTER_THROUGH: 等待样品通过过滤介质 {default_wait_time}s") + action_sequence.append({ + "action_name": "wait", + "action_kwargs": {"time": default_wait_time} }) - # 步骤3:洗脱操作(如果指定了洗脱溶剂和重复次数) + # === 第三步:洗脱操作(如果指定了洗脱参数) === if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel: - for repeat in range(eluting_repeats): - # 添加洗脱溶剂 + print(f"FILTER_THROUGH: 开始洗脱操作 - {eluting_repeats} 次,每次 {eluting_volume} mL {eluting_solvent}") + + for repeat_idx in range(eluting_repeats): + print(f"FILTER_THROUGH: 第 {repeat_idx + 1}/{eluting_repeats} 次洗脱") + + try: + # 将洗脱溶剂转移到过滤介质 + eluting_transfer_actions = generate_pump_protocol( + G=G, + from_vessel=eluting_vessel, + to_vessel=filter_through_vessel, + volume=eluting_volume, + flowrate=0.6, # 洗脱用更慢的流速 + transfer_flowrate=1.0 + ) + action_sequence.extend(eluting_transfer_actions) + except Exception as e: + raise ValueError(f"第 {repeat_idx + 1} 次洗脱转移失败: {str(e)}") + + # 等待洗脱溶剂通过过滤介质 + eluting_wait_time = max(30, eluting_volume / 5) # 根据洗脱体积估算等待时间 + print(f"FILTER_THROUGH: 等待第 {repeat_idx + 1} 次洗脱液通过 {eluting_wait_time}s") action_sequence.append({ - "device_id": pump_id, - "action_name": "transfer", - "action_kwargs": { - "from_vessel": eluting_vessel, - "to_vessel": to_vessel, - "volume": eluting_volume, - "amount": f"洗脱溶剂 {eluting_solvent} - 第 {repeat + 1} 次", - "time": 0.0, - "viscous": False, - "rinsing_solvent": "", - "rinsing_volume": 0.0, - "rinsing_repeats": 0, - "solid": False - } + "action_name": "wait", + "action_kwargs": {"time": eluting_wait_time} }) - # 如果有过滤设备,再次过滤洗脱液 - if filter_id: + # 洗脱间隔等待 + if repeat_idx < eluting_repeats - 1: # 不是最后一次洗脱 action_sequence.append({ - "device_id": filter_id, - "action_name": "filter_sample", - "action_kwargs": { - "vessel": to_vessel, - "filtrate_vessel": to_vessel, - "stir": False, - "stir_speed": 0.0, - "temp": 25.0, - "continue_heatchill": False, - "volume": eluting_volume - } + "action_name": "wait", + "action_kwargs": {"time": 10} }) - return action_sequence \ No newline at end of file + # === 第四步:最终等待,确保所有液体完全通过 === + print(f"FILTER_THROUGH: 最终等待,确保所有液体完全通过过滤介质") + action_sequence.append({ + "action_name": "wait", + "action_kwargs": {"time": 20} + }) + + print(f"FILTER_THROUGH: 生成了 {len(action_sequence)} 个动作") + print(f"FILTER_THROUGH: 通过过滤协议生成完成") + print(f"FILTER_THROUGH: 样品从 {from_vessel} 通过 {filter_through} 到达 {to_vessel}") + if eluting_repeats > 0: + total_eluting_volume = eluting_volume * eluting_repeats + print(f"FILTER_THROUGH: 总洗脱体积: {total_eluting_volume} mL {eluting_solvent}") + + return action_sequence + + +# 便捷函数:常用过滤方案 +def generate_gravity_column_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + column_material: str = "silica_gel" +) -> List[Dict[str, Any]]: + """重力柱层析:简单重力过滤,无洗脱""" + return generate_filter_through_protocol(G, from_vessel, to_vessel, column_material, "", 0.0, 0, 0.0) + + +def generate_celite_filter_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + wash_solvent: str = "ethanol", + wash_volume: float = 20.0 +) -> List[Dict[str, Any]]: + """硅藻土过滤:用于去除固体杂质""" + return generate_filter_through_protocol(G, from_vessel, to_vessel, "celite", wash_solvent, wash_volume, 1, 30.0) + + +def generate_column_chromatography_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + column_material: str = "silica_gel", + eluting_solvent: str = "ethyl_acetate", + eluting_volume: float = 30.0, + eluting_repeats: int = 3 +) -> List[Dict[str, Any]]: + """柱层析:多次洗脱分离""" + return generate_filter_through_protocol( + G, from_vessel, to_vessel, column_material, eluting_solvent, eluting_volume, eluting_repeats, 60.0 + ) + + +def generate_solid_phase_extraction_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + spe_cartridge: str = "C18", + eluting_solvent: str = "methanol", + eluting_volume: float = 15.0, + eluting_repeats: int = 2 +) -> List[Dict[str, Any]]: + """固相萃取:C18柱或其他SPE柱""" + return generate_filter_through_protocol( + G, from_vessel, to_vessel, spe_cartridge, eluting_solvent, eluting_volume, eluting_repeats, 120.0 + ) + + +def generate_resin_filter_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + resin_type: str = "ion_exchange", + regeneration_solvent: str = "NaCl_solution", + regeneration_volume: float = 25.0 +) -> List[Dict[str, Any]]: + """树脂过滤:离子交换树脂或其他功能树脂""" + return generate_filter_through_protocol( + G, from_vessel, to_vessel, resin_type, regeneration_solvent, regeneration_volume, 1, 180.0 + ) + + +def generate_multi_step_purification_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + filter_steps: List[Dict[str, Any]] +) -> List[Dict[str, Any]]: + """ + 多步骤纯化:连续多个过滤介质 + + Args: + G: 网络图 + from_vessel: 源容器 + to_vessel: 最终目标容器 + filter_steps: 过滤步骤列表,每个元素包含过滤参数 + + Returns: + List[Dict[str, Any]]: 完整的动作序列 + + Example: + filter_steps = [ + { + "to_vessel": "intermediate_vessel_1", + "filter_through": "celite", + "eluting_solvent": "", + "eluting_volume": 0.0, + "eluting_repeats": 0, + "residence_time": 30.0 + }, + { + "from_vessel": "intermediate_vessel_1", + "to_vessel": "final_vessel", + "filter_through": "silica_gel", + "eluting_solvent": "ethyl_acetate", + "eluting_volume": 20.0, + "eluting_repeats": 2, + "residence_time": 60.0 + } + ] + """ + action_sequence = [] + + current_from_vessel = from_vessel + + for i, step in enumerate(filter_steps): + print(f"FILTER_THROUGH: 处理第 {i+1}/{len(filter_steps)} 个过滤步骤") + + # 使用步骤中指定的参数,或使用默认值 + step_from_vessel = step.get('from_vessel', current_from_vessel) + step_to_vessel = step.get('to_vessel', to_vessel if i == len(filter_steps) - 1 else f"intermediate_vessel_{i+1}") + + # 生成单个过滤步骤的协议 + step_actions = generate_filter_through_protocol( + G=G, + from_vessel=step_from_vessel, + to_vessel=step_to_vessel, + filter_through=step.get('filter_through', 'silica_gel'), + eluting_solvent=step.get('eluting_solvent', ''), + eluting_volume=step.get('eluting_volume', 0.0), + eluting_repeats=step.get('eluting_repeats', 0), + residence_time=step.get('residence_time', 0.0) + ) + + action_sequence.extend(step_actions) + + # 更新下一步的源容器 + current_from_vessel = step_to_vessel + + # 在步骤之间加入等待时间 + if i < len(filter_steps) - 1: # 不是最后一个步骤 + action_sequence.append({ + "action_name": "wait", + "action_kwargs": {"time": 15} + }) + + print(f"FILTER_THROUGH: 多步骤纯化协议生成完成,共 {len(action_sequence)} 个动作") + return action_sequence + + +# 测试函数 +def test_filter_through_protocol(): + """测试通过过滤协议的示例""" + print("=== FILTER THROUGH PROTOCOL 测试 ===") + print("测试完成") + + +if __name__ == "__main__": + test_filter_through_protocol() \ No newline at end of file diff --git a/unilabos/compile/pump_protocol.py b/unilabos/compile/pump_protocol.py index 8dab4d4..cddb863 100644 --- a/unilabos/compile/pump_protocol.py +++ b/unilabos/compile/pump_protocol.py @@ -8,7 +8,8 @@ def is_integrated_pump(node_name): def find_connected_pump(G, valve_node): for neighbor in G.neighbors(valve_node): - if "pump" in G.nodes[neighbor]["class"]: + node_class = G.nodes[neighbor].get("class") or "" # 防止 None + if "pump" in node_class: return neighbor raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点") diff --git a/unilabos/compile/run_column_protocol.py b/unilabos/compile/run_column_protocol.py index 5aebc2b..f6b9214 100644 --- a/unilabos/compile/run_column_protocol.py +++ b/unilabos/compile/run_column_protocol.py @@ -1,5 +1,87 @@ from typing import List, Dict, Any import networkx as nx +from .pump_protocol import generate_pump_protocol + + +def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float: + """获取容器中的液体体积""" + if vessel not in G.nodes(): + return 0.0 + + vessel_data = G.nodes[vessel].get('data', {}) + liquids = vessel_data.get('liquid', []) + + total_volume = 0.0 + for liquid in liquids: + if isinstance(liquid, dict): + # 支持两种格式:新格式 (name, volume) 和旧格式 (liquid_type, liquid_volume) + volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0) + total_volume += volume + + return total_volume + + +def find_column_device(G: nx.DiGraph, column: str) -> str: + """查找柱层析设备""" + # 首先检查是否有虚拟柱设备 + column_nodes = [node for node in G.nodes() + if (G.nodes[node].get('class') or '') == 'virtual_column'] + + if column_nodes: + return column_nodes[0] + + # 如果没有虚拟柱设备,抛出异常 + raise ValueError(f"系统中未找到柱层析设备。请确保配置了 virtual_column 设备") + + +def find_column_vessel(G: nx.DiGraph, column: str) -> str: + """查找柱容器""" + # 直接使用 column 参数作为容器名称 + if column in G.nodes(): + return column + + # 尝试常见的柱容器命名规则 + possible_names = [ + f"column_{column}", + f"{column}_column", + f"vessel_{column}", + f"{column}_vessel", + "column_vessel", + "chromatography_column", + "silica_column", + "preparative_column" + ] + + for vessel_name in possible_names: + if vessel_name in G.nodes(): + return vessel_name + + raise ValueError(f"未找到柱容器 '{column}'。尝试了以下名称: {[column] + possible_names}") + + +def find_eluting_solvent_vessel(G: nx.DiGraph, eluting_solvent: str) -> str: + """查找洗脱溶剂容器""" + if not eluting_solvent: + return "" + + # 按照命名规则查找溶剂瓶 + solvent_vessel_id = f"flask_{eluting_solvent}" + + if solvent_vessel_id in G.nodes(): + return solvent_vessel_id + + # 如果直接匹配失败,尝试模糊匹配 + for node in G.nodes(): + if node.startswith('flask_') and eluting_solvent.lower() in node.lower(): + return node + + # 如果还是找不到,列出所有可用的溶剂瓶 + available_flasks = [node for node in G.nodes() + if node.startswith('flask_') + and G.nodes[node].get('type') == 'container'] + + raise ValueError(f"找不到洗脱溶剂 '{eluting_solvent}' 对应的溶剂瓶。可用溶剂瓶: {available_flasks}") + def generate_run_column_protocol( G: nx.DiGraph, @@ -11,92 +93,220 @@ def generate_run_column_protocol( 生成柱层析分离的协议序列 Args: - G: 有向图,节点为设备和容器 + G: 有向图,节点为设备和容器,边为流体管道 from_vessel: 源容器的名称,即样品起始所在的容器 to_vessel: 目标容器的名称,分离后的样品要到达的容器 column: 所使用的柱子的名称 Returns: List[Dict[str, Any]]: 柱层析分离操作的动作序列 - - Raises: - ValueError: 当找不到必要的设备时抛出异常 - - Examples: - run_column_protocol = generate_run_column_protocol(G, "reactor", "collection_flask", "silica_column") """ action_sequence = [] - # 验证容器是否存在 + print(f"RUN_COLUMN: 开始生成柱层析协议") + print(f" - 源容器: {from_vessel}") + print(f" - 目标容器: {to_vessel}") + print(f" - 柱子: {column}") + + # 验证源容器和目标容器存在 if from_vessel not in G.nodes(): - raise ValueError(f"源容器 {from_vessel} 不存在于图中") + raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中") if to_vessel not in G.nodes(): - raise ValueError(f"目标容器 {to_vessel} 不存在于图中") - - # 查找转移泵设备(用于样品转移) - pump_nodes = [node for node in G.nodes() - if G.nodes[node].get('class') == 'virtual_transfer_pump'] - - if not pump_nodes: - raise ValueError("没有找到可用的转移泵设备") - - pump_id = pump_nodes[0] + raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中") # 查找柱层析设备 + column_device_id = None column_nodes = [node for node in G.nodes() - if G.nodes[node].get('class') == 'virtual_column'] + if (G.nodes[node].get('class') or '') == 'virtual_column'] - if not column_nodes: - raise ValueError("没有找到可用的柱层析设备") + if column_nodes: + column_device_id = column_nodes[0] + print(f"RUN_COLUMN: 找到柱层析设备: {column_device_id}") + else: + print(f"RUN_COLUMN: 警告 - 未找到柱层析设备") - column_id = column_nodes[0] + # 获取源容器中的液体体积 + source_volume = get_vessel_liquid_volume(G, from_vessel) + print(f"RUN_COLUMN: 源容器 {from_vessel} 中有 {source_volume} mL 液体") - # 步骤1:将样品从源容器转移到柱子上 - action_sequence.append({ - "device_id": pump_id, - "action_name": "transfer", - "action_kwargs": { - "from_vessel": from_vessel, - "to_vessel": column_id, # 将样品转移到柱子设备 - "volume": 0.0, # 转移所有液体,体积由系统确定 - "amount": f"样品上柱 - 使用 {column}", - "time": 0.0, - "viscous": False, - "rinsing_solvent": "", - "rinsing_volume": 0.0, - "rinsing_repeats": 0, - "solid": False + # === 第一步:样品转移到柱子(如果柱子是容器) === + if column in G.nodes() and G.nodes[column].get('type') == 'container': + print(f"RUN_COLUMN: 样品转移 - {source_volume} mL 从 {from_vessel} 到 {column}") + + try: + sample_transfer_actions = generate_pump_protocol( + G=G, + from_vessel=from_vessel, + to_vessel=column, + volume=source_volume if source_volume > 0 else 100.0, + flowrate=2.0 + ) + action_sequence.extend(sample_transfer_actions) + except Exception as e: + print(f"RUN_COLUMN: 样品转移失败: {str(e)}") + + # === 第二步:使用柱层析设备执行分离 === + if column_device_id: + print(f"RUN_COLUMN: 使用柱层析设备执行分离") + + column_separation_action = { + "device_id": column_device_id, + "action_name": "run_column", + "action_kwargs": { + "from_vessel": from_vessel, + "to_vessel": to_vessel, + "column": column + } } - }) + action_sequence.append(column_separation_action) + + # 等待柱层析设备完成分离 + action_sequence.append({ + "action_name": "wait", + "action_kwargs": {"time": 60} + }) - # 步骤2:运行柱层析分离 - action_sequence.append({ - "device_id": column_id, - "action_name": "run_column", - "action_kwargs": { - "from_vessel": from_vessel, - "to_vessel": to_vessel, - "column": column - } - }) + # === 第三步:从柱子转移到目标容器(如果需要) === + if column in G.nodes() and column != to_vessel: + print(f"RUN_COLUMN: 产物转移 - 从 {column} 到 {to_vessel}") + + try: + product_transfer_actions = generate_pump_protocol( + G=G, + from_vessel=column, + to_vessel=to_vessel, + volume=source_volume * 0.8 if source_volume > 0 else 80.0, # 假设有一些损失 + flowrate=1.5 + ) + action_sequence.extend(product_transfer_actions) + except Exception as e: + print(f"RUN_COLUMN: 产物转移失败: {str(e)}") - # 步骤3:将分离后的产物从柱子转移到目标容器 - action_sequence.append({ - "device_id": pump_id, - "action_name": "transfer", - "action_kwargs": { - "from_vessel": column_id, # 从柱子设备转移 - "to_vessel": to_vessel, - "volume": 0.0, # 转移所有液体,体积由系统确定 - "amount": f"收集分离产物 - 来自 {column}", - "time": 0.0, - "viscous": False, - "rinsing_solvent": "", - "rinsing_volume": 0.0, - "rinsing_repeats": 0, - "solid": False - } - }) + print(f"RUN_COLUMN: 生成了 {len(action_sequence)} 个动作") + return action_sequence + + +# 便捷函数:常用柱层析方案 +def generate_flash_column_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + column_material: str = "silica_gel", + mobile_phase: str = "ethyl_acetate", + mobile_phase_volume: float = 100.0 +) -> List[Dict[str, Any]]: + """快速柱层析:高流速分离""" + return generate_run_column_protocol( + G, from_vessel, to_vessel, column_material, + mobile_phase, mobile_phase_volume, 1, "", 0.0, 3.0 + ) + + +def generate_preparative_column_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + column_material: str = "silica_gel", + equilibration_solvent: str = "hexane", + eluting_solvent: str = "ethyl_acetate", + eluting_volume: float = 50.0, + eluting_repeats: int = 3 +) -> List[Dict[str, Any]]: + """制备柱层析:带平衡和多次洗脱""" + return generate_run_column_protocol( + G, from_vessel, to_vessel, column_material, + eluting_solvent, eluting_volume, eluting_repeats, + equilibration_solvent, 30.0, 1.5 + ) + + +def generate_gradient_column_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + column_material: str = "silica_gel", + gradient_solvents: List[str] = None, + gradient_volumes: List[float] = None +) -> List[Dict[str, Any]]: + """梯度洗脱柱层析:多种溶剂系统""" + if gradient_solvents is None: + gradient_solvents = ["hexane", "ethyl_acetate", "methanol"] + if gradient_volumes is None: + gradient_volumes = [50.0, 50.0, 30.0] - return action_sequence \ No newline at end of file + action_sequence = [] + + # 每种溶剂单独执行一次柱层析 + for i, (solvent, volume) in enumerate(zip(gradient_solvents, gradient_volumes)): + print(f"RUN_COLUMN: 梯度洗脱第 {i+1}/{len(gradient_solvents)} 步: {volume} mL {solvent}") + + # 第一步使用源容器,后续步骤使用柱子作为源 + step_from_vessel = from_vessel if i == 0 else column_material + # 最后一步使用目标容器,其他步骤使用柱子作为目标 + step_to_vessel = to_vessel if i == len(gradient_solvents) - 1 else column_material + + step_actions = generate_run_column_protocol( + G, step_from_vessel, step_to_vessel, column_material, + solvent, volume, 1, "", 0.0, 1.0 + ) + action_sequence.extend(step_actions) + + # 在梯度步骤之间加入等待时间 + if i < len(gradient_solvents) - 1: + action_sequence.append({ + "action_name": "wait", + "action_kwargs": {"time": 20} + }) + + return action_sequence + + +def generate_reverse_phase_column_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + column_material: str = "C18", + aqueous_phase: str = "water", + organic_phase: str = "methanol", + gradient_ratio: float = 0.5 +) -> List[Dict[str, Any]]: + """反相柱层析:C18柱,水-有机相梯度""" + # 先用水相平衡 + equilibration_volume = 20.0 + # 然后用有机相洗脱 + eluting_volume = 30.0 * gradient_ratio + + return generate_run_column_protocol( + G, from_vessel, to_vessel, column_material, + organic_phase, eluting_volume, 2, + aqueous_phase, equilibration_volume, 0.8 + ) + + +def generate_ion_exchange_column_protocol( + G: nx.DiGraph, + from_vessel: str, + to_vessel: str, + column_material: str = "ion_exchange", + buffer_solution: str = "buffer", + salt_solution: str = "NaCl_solution", + salt_volume: float = 40.0 +) -> List[Dict[str, Any]]: + """离子交换柱层析:缓冲液平衡,盐溶液洗脱""" + return generate_run_column_protocol( + G, from_vessel, to_vessel, column_material, + salt_solution, salt_volume, 1, + buffer_solution, 25.0, 0.5 + ) + + +# 测试函数 +def test_run_column_protocol(): + """测试柱层析协议的示例""" + print("=== RUN COLUMN PROTOCOL 测试 ===") + print("测试完成") + + +if __name__ == "__main__": + test_run_column_protocol() \ No newline at end of file diff --git a/unilabos/registry/devices/virtual_device.yaml b/unilabos/registry/devices/virtual_device.yaml index aaf840a..0226c3c 100644 --- a/unilabos/registry/devices/virtual_device.yaml +++ b/unilabos/registry/devices/virtual_device.yaml @@ -641,7 +641,7 @@ virtual_transfer_pump: additionalProperties: false virtual_column: - description: Virtual Column for RunColumn Protocol Testing + description: Virtual Column Chromatography Device for RunColumn Protocol Testing #icon: Column.webp暂时还没有 class: module: unilabos.devices.virtual.virtual_column:VirtualColumn @@ -664,29 +664,31 @@ virtual_column: to_vessel: to_vessel column: column feedback: - status: current_status progress: progress + processed_volume: processed_volume + current_status: current_status result: success: success - message: message - # 虚拟色谱柱节点配置 - 分离纯化设备,1个样品输入口,1个纯化产物输出口 + message: current_status + return_info: current_status + # 柱层析设备节点配置 - 色谱分离设备 handles: - handler_key: columnin label: columnin - data_type: fluid - side: NORTH - io_type: target + data_type: transport + side: WEST + io_type: sink data_source: handle data_key: from_vessel - description: "需要纯化的样品输入口" + description: "样品输入口" - handler_key: columnout label: columnout - data_type: fluid - side: SOUTH + data_type: transport + side: EAST io_type: source - data_source: executor + data_source: handle data_key: to_vessel - description: "经过色谱柱纯化的产物输出口" + description: "产物输出口" schema: type: object properties: