别再死记硬背定义了!用Python+Graphviz从零画一个Petri网(附代码)
用PythonGraphviz从零构建Petri网可视化建模实战指南当你第一次接触Petri网时那些抽象的定义和数学符号是否让你望而生畏库所、变迁、流关系...这些概念在纯理论讲解中往往显得冰冷而遥远。但如果我们换一种方式——用代码和图形来理解一切就会变得生动起来。本文将带你用Python的graphviz库通过编写代码一步步构建网、纯网、T-图等结构让抽象的理论跃然屏上。1. 环境准备与基础概念可视化在开始编码之前我们需要明确几个核心概念。Petri网由三种基本元素构成库所Place用圆圈表示、变迁Transition用矩形表示和流关系Flow用箭头表示。这种二分图结构特别适合描述系统中的状态变化和事件触发关系。首先安装必要的Python库pip install graphviz让我们从最基础的网结构开始构建。创建一个新的Python文件导入graphviz并初始化一个有向图from graphviz import Digraph def create_basic_net(): net Digraph(Petri_Net) net.attr(rankdirLR) # 从左到右布局 # 添加库所节点圆形 net.node(s1, shapecircle, xlabels1) net.node(s2, shapecircle, xlabels2) # 添加变迁节点矩形 net.node(t1, shaperect, xlabelt1) # 添加流关系 net.edge(s1, t1) net.edge(t1, s2) return net basic_net create_basic_net() basic_net.render(basic_net, viewTrue)这段代码会生成一个最简单的Petri网结构库所s1通过变迁t1连接到库所s2。运行后你将看到一个清晰的图形表示这正是定义1.1中描述的网结构N(S,T;F)的具体体现。2. 构建符合数学定义的完整Petri网根据定义1.1一个合法的网必须满足四个条件S和T不能同时为空S和T互不相交F是(S×T)∪(T×S)的子集没有孤立节点让我们用代码实现一个符合所有条件的完整网结构。我们将创建图1.1中的示例网络def create_complete_net(): net Digraph(Complete_Petri_Net) net.attr(rankdirTB) # 从上到下布局 # 定义库所和变迁 places [s1, s2, s3, s4] transitions [t1, t2, t3, t4] # 添加所有节点 for p in places: net.node(p, shapecircle, xlabelp) for t in transitions: net.node(t, shaperect, xlabelt) # 添加流关系 flows [ (s1, t2), (s2, t1), (s2, t4), (s3, t3), (s3, t4), (s4, t3), (t1, s1), (t2, s2), (t2, s3), (t3, s1), (t4, s4) ] for src, dst in flows: net.edge(src, dst) return net complete_net create_complete_net() complete_net.render(complete_net, viewTrue)这个实现有几个关键细节值得注意我们明确区分了库所圆形和变迁矩形的视觉表现所有流关系都严格遵循(S×T)∪(T×S)的规则通过检查可以确认没有任何孤立节点存在运行这段代码你将得到一个与理论定义完全对应的可视化网络。这种从代码到图形的映射过程能帮助你直观理解那些抽象的形式化定义。3. 实现特殊类型的Petri网Petri网理论中定义了几种特殊类型的网络结构每种都有其独特的性质和应用场景。让我们用代码来实现这些变体。3.1 纯网的构建与验证根据定义1.3纯网要求没有任何节点的前集和后集有交集即没有自环。我们可以扩展之前的代码来创建和验证纯网def is_pure_net(flows): nodes set() input_map {} output_map {} # 构建输入输出关系字典 for src, dst in flows: nodes.add(src) nodes.add(dst) output_map.setdefault(src, set()).add(dst) input_map.setdefault(dst, set()).add(src) # 检查每个节点的前集和后集 for node in nodes: inputs input_map.get(node, set()) outputs output_map.get(node, set()) if inputs outputs: # 检查交集 return False return True # 创建一个纯网示例 def create_pure_net(): net Digraph(Pure_Petri_Net) net.attr(rankdirLR) places [s1, s2, s3] transitions [t1, t2] for p in places: net.node(p, shapecircle, xlabelp) for t in transitions: net.node(t, shaperect, xlabelt) pure_flows [ (s1, t1), (t1, s2), (s2, t2), (t2, s3) ] for src, dst in pure_flows: net.edge(src, dst) print(fIs this a pure net? {is_pure_net(pure_flows)}) return net pure_net create_pure_net() pure_net.render(pure_net, viewTrue)3.2 T-图的实现T-图是一种特殊类型的Petri网其中每个库所都恰好有一个输入变迁和一个输出变迁定义1.3。这种线性结构在实际中有很多应用def create_t_graph(): net Digraph(T_Graph) net.attr(rankdirLR, splinesline) # 强制直线连接 # 创建线性结构 nodes [ (t1, rect), (s1, circle), (t2, rect), (s2, circle), (t3, rect), (s3, circle) ] for name, shape in nodes: net.node(name, shapeshape, xlabelname) # 连接成线性结构 for i in range(len(nodes)-1): net.edge(nodes[i][0], nodes[i1][0]) # 验证T-图条件 places [name for name, shape in nodes if shape circle] transitions [name for name, shape in nodes if shape rect] t_graph_flows [] for i in range(len(nodes)-1): t_graph_flows.append((nodes[i][0], nodes[i1][0])) def is_t_graph(flows, places): place_io {p: {in: 0, out: 0} for p in places} for src, dst in flows: if src in places: place_io[src][out] 1 elif dst in places: place_io[dst][in] 1 return all(count[in] 1 and count[out] 1 for p, count in place_io.items()) print(fIs this a T-graph? {is_t_graph(t_graph_flows, places)}) return net t_graph create_t_graph() t_graph.render(t_graph, viewTrue)4. 高级应用子网提取与网络分析在实际应用中我们经常需要从大网络中提取子网进行分析。根据定义1.2子网需要保持原网络中的流关系。让我们实现一个子网提取函数def extract_subnet(main_net, selected_nodes): 从主网络中提取包含选定节点的子网 subnet Digraph(Subnet) subnet.attr(rankdirLR) # 复制节点属性 for node in main_net.body: if node.split( )[0] ! edge: node_name node.split([)[0].strip() if node_name in selected_nodes: subnet.node(node_name, **extract_attrs(node)) # 复制相关边 for edge in main_net.body: if edge.split( )[0] edge: parts edge.split(-) src parts[0].split( )[-1].strip() dst parts[1].split([)[0].strip() if src in selected_nodes and dst in selected_nodes: subnet.edge(src, dst, **extract_attrs(edge)) return subnet def extract_attrs(graph_item): 从graphviz项中提取属性字典 attrs {} if [ in graph_item: attr_str graph_item.split([)[1].split(])[0] for pair in attr_str.split(,): if in pair: key, value pair.split() attrs[key.strip()] value.strip().strip() return attrs # 使用之前创建的完整网络 selected [s1, t2, s2, s3] subnet extract_subnet(complete_net, selected) subnet.render(extracted_subnet, viewTrue)这个子网提取器可以保留原始网络中的节点属性和连接关系是分析大型Petri网的有力工具。你可以尝试选择不同的节点组合观察提取出的子网结构。5. 可视化增强与交互探索为了让我们的Petri网可视化更加实用我们可以添加一些增强功能def enhanced_petri_visualization(): net Digraph(Enhanced_Petri) net.attr(rankdirTB, compoundtrue) # 使用不同的颜色区分元素 places [s1, s2, s3] transitions [t1, t2, t3] for p in places: net.node(p, shapedoublecircle, stylefilled, fillcolorlightblue, xlabelp) for t in transitions: net.node(t, shaperect, stylefilled, fillcolorsalmon, xlabelt) # 添加流关系可以标记权重 flows [ (s1, t1, 1), (t1, s2, 1), (s2, t2, 2), (t2, s3, 1), (s3, t3, 1), (t3, s1, 1) ] for src, dst, weight in flows: net.edge(src, dst, labelweight, penwidth2) # 添加图例 with net.subgraph(namecluster_legend) as legend: legend.attr(colorlightgray, stylefilled, labelLegend) legend.node(p_legend, shapedoublecircle, labelPlace) legend.node(t_legend, shaperect, labelTransition) legend.edge(p_legend, t_legend, labelFlow) return net enhanced_net enhanced_petri_visualization() enhanced_net.render(enhanced_net, viewTrue)这种增强的可视化包含了几个实用元素使用不同形状和颜色清晰区分库所和变迁在流关系上标注权重模拟Petri网的标记添加图例说明方便不熟悉Petri网的人理解使用更粗的线条表示流关系通过这些可视化技巧我们可以创建出既符合数学定义又易于理解的Petri网图形表示。