libpetri_verification/
structural_check.rs1use crate::net_flattener::FlatNet;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum StructuralCheckResult {
6 NoPotentialDeadlock,
8 PotentialDeadlock,
10 Inconclusive,
12}
13
14pub fn structural_check(flat: &FlatNet) -> StructuralCheckResult {
18 if flat.place_count > 50 {
19 return StructuralCheckResult::Inconclusive;
20 }
21
22 let siphons = find_minimal_siphons(flat);
23 for siphon in &siphons {
24 let trap = find_maximal_trap_in(flat, siphon);
25 if trap.is_empty() {
26 return StructuralCheckResult::PotentialDeadlock;
27 }
28 }
29
30 StructuralCheckResult::NoPotentialDeadlock
31}
32
33fn find_minimal_siphons(flat: &FlatNet) -> Vec<Vec<usize>> {
35 let mut siphons = Vec::new();
36
37 for start_pid in 0..flat.place_count {
39 let mut siphon = vec![false; flat.place_count];
40 siphon[start_pid] = true;
41
42 let mut changed = true;
44 while changed {
45 changed = false;
46 for ft in &flat.transitions {
47 let outputs_to_siphon = ft
49 .post
50 .iter()
51 .enumerate()
52 .any(|(pid, &count)| count > 0 && siphon[pid]);
53
54 if outputs_to_siphon {
55 for (pid, &count) in ft.pre.iter().enumerate() {
57 if count > 0 && !siphon[pid] {
58 siphon[pid] = true;
59 changed = true;
60 }
61 }
62 }
63 }
64 }
65
66 let siphon_places: Vec<usize> = siphon
67 .iter()
68 .enumerate()
69 .filter(|(_, in_s)| **in_s)
70 .map(|(i, _)| i)
71 .collect();
72
73 let is_superset = siphons
75 .iter()
76 .any(|existing: &Vec<usize>| existing.iter().all(|p| siphon_places.contains(p)));
77
78 if !is_superset {
79 siphons.push(siphon_places);
80 }
81 }
82
83 siphons
84}
85
86fn find_maximal_trap_in(flat: &FlatNet, siphon: &[usize]) -> Vec<usize> {
88 let mut trap = vec![false; flat.place_count];
89 for &pid in siphon {
90 trap[pid] = true;
91 }
92
93 let mut changed = true;
95 while changed {
96 changed = false;
97 for &pid in siphon {
98 if !trap[pid] {
99 continue;
100 }
101
102 let mut satisfies = true;
104 for ft in &flat.transitions {
105 if ft.pre[pid] > 0 {
106 let produces_to_trap = ft
108 .post
109 .iter()
110 .enumerate()
111 .any(|(p, &count)| count > 0 && trap[p]);
112
113 if !produces_to_trap {
114 satisfies = false;
115 break;
116 }
117 }
118 }
119
120 if !satisfies {
121 trap[pid] = false;
122 changed = true;
123 }
124 }
125 }
126
127 trap.iter()
128 .enumerate()
129 .filter(|(_, in_t)| **in_t)
130 .map(|(i, _)| i)
131 .collect()
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::net_flattener::flatten;
138 use libpetri_core::input::one;
139 use libpetri_core::output::out_place;
140 use libpetri_core::petri_net::PetriNet;
141 use libpetri_core::place::Place;
142 use libpetri_core::transition::Transition;
143
144 #[test]
145 fn simple_chain_potential_deadlock() {
146 let p1 = Place::<i32>::new("p1");
149 let p2 = Place::<i32>::new("p2");
150 let t = Transition::builder("t1")
151 .input(one(&p1))
152 .output(out_place(&p2))
153 .build();
154 let net = PetriNet::builder("test").transition(t).build();
155
156 let flat = flatten(&net);
157 let result = structural_check(&flat);
158 assert_eq!(result, StructuralCheckResult::PotentialDeadlock);
159 }
160
161 #[test]
162 fn cycle_no_deadlock() {
163 let p1 = Place::<i32>::new("p1");
164 let p2 = Place::<i32>::new("p2");
165 let t1 = Transition::builder("t1")
166 .input(one(&p1))
167 .output(out_place(&p2))
168 .build();
169 let t2 = Transition::builder("t2")
170 .input(one(&p2))
171 .output(out_place(&p1))
172 .build();
173 let net = PetriNet::builder("cycle").transitions([t1, t2]).build();
174
175 let flat = flatten(&net);
176 let result = structural_check(&flat);
177 assert_eq!(result, StructuralCheckResult::NoPotentialDeadlock);
179 }
180
181 #[test]
182 fn potential_deadlock_detected() {
183 let p1 = Place::<i32>::new("p1");
185 let p2 = Place::<i32>::new("p2");
186 let shared = Place::<i32>::new("shared");
187 let out1 = Place::<i32>::new("out1");
188 let out2 = Place::<i32>::new("out2");
189
190 let t1 = Transition::builder("t1")
191 .input(one(&p1))
192 .input(one(&shared))
193 .output(out_place(&out1))
194 .build();
195 let t2 = Transition::builder("t2")
196 .input(one(&p2))
197 .input(one(&shared))
198 .output(out_place(&out2))
199 .build();
200
201 let net = PetriNet::builder("deadlock").transitions([t1, t2]).build();
202
203 let flat = flatten(&net);
204 let result = structural_check(&flat);
205 assert_eq!(result, StructuralCheckResult::PotentialDeadlock);
207 }
208}