1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
14use crate::traits::Problem;
15use crate::types::Or;
16use serde::de::Error as _;
17use serde::{Deserialize, Deserializer, Serialize};
18use std::collections::{HashMap, HashSet, VecDeque};
19
20inventory::submit! {
21 ProblemSchemaEntry {
22 name: "NonLivenessFreePetriNet",
23 display_name: "Non-Liveness Free Petri Net",
24 aliases: &[],
25 dimensions: &[],
26 module_path: module_path!(),
27 description: "Determine whether a free-choice Petri net is not live (some transition can become permanently dead)",
28 fields: &[
29 FieldInfo { name: "num_places", type_name: "usize", description: "Number of places |S|" },
30 FieldInfo { name: "num_transitions", type_name: "usize", description: "Number of transitions |T|" },
31 FieldInfo { name: "place_to_transition", type_name: "Vec<(usize,usize)>", description: "Arcs from places to transitions" },
32 FieldInfo { name: "transition_to_place", type_name: "Vec<(usize,usize)>", description: "Arcs from transitions to places" },
33 FieldInfo { name: "initial_marking", type_name: "Vec<usize>", description: "Initial marking M₀ (tokens per place)" },
34 ],
35 }
36}
37
38inventory::submit! {
39 ProblemSizeFieldEntry {
40 name: "NonLivenessFreePetriNet",
41 fields: &["num_places", "num_transitions", "num_arcs", "initial_token_sum"],
42 }
43}
44
45#[derive(Debug, Clone, Serialize)]
46pub struct NonLivenessFreePetriNet {
47 num_places: usize,
48 num_transitions: usize,
49 place_to_transition: Vec<(usize, usize)>,
50 transition_to_place: Vec<(usize, usize)>,
51 initial_marking: Vec<usize>,
52 #[serde(skip)]
54 globally_dead: Vec<bool>,
55}
56
57impl NonLivenessFreePetriNet {
58 fn validate_inputs(
59 num_places: usize,
60 num_transitions: usize,
61 place_to_transition: &[(usize, usize)],
62 transition_to_place: &[(usize, usize)],
63 initial_marking: &[usize],
64 ) -> Result<(), String> {
65 if num_places == 0 {
66 return Err("NonLivenessFreePetriNet requires at least one place".to_string());
67 }
68 if num_transitions == 0 {
69 return Err("NonLivenessFreePetriNet requires at least one transition".to_string());
70 }
71 if initial_marking.len() != num_places {
72 return Err(format!(
73 "initial_marking length {} does not match num_places {}",
74 initial_marking.len(),
75 num_places
76 ));
77 }
78 for (i, &(p, t)) in place_to_transition.iter().enumerate() {
79 if p >= num_places {
80 return Err(format!(
81 "place_to_transition arc {} has place {} out of range 0..{}",
82 i, p, num_places
83 ));
84 }
85 if t >= num_transitions {
86 return Err(format!(
87 "place_to_transition arc {} has transition {} out of range 0..{}",
88 i, t, num_transitions
89 ));
90 }
91 }
92 for (i, &(t, p)) in transition_to_place.iter().enumerate() {
93 if t >= num_transitions {
94 return Err(format!(
95 "transition_to_place arc {} has transition {} out of range 0..{}",
96 i, t, num_transitions
97 ));
98 }
99 if p >= num_places {
100 return Err(format!(
101 "transition_to_place arc {} has place {} out of range 0..{}",
102 i, p, num_places
103 ));
104 }
105 }
106
107 let mut preset: HashMap<usize, HashSet<usize>> = HashMap::new();
110 for &(p, t) in place_to_transition {
111 preset.entry(t).or_default().insert(p);
112 }
113 for &(p, _) in place_to_transition {
115 let transitions_from_p: Vec<usize> = place_to_transition
116 .iter()
117 .filter(|&&(pp, _)| pp == p)
118 .map(|&(_, t)| t)
119 .collect();
120 for i in 0..transitions_from_p.len() {
121 for j in (i + 1)..transitions_from_p.len() {
122 let t1 = transitions_from_p[i];
123 let t2 = transitions_from_p[j];
124 let p1 = preset.get(&t1).cloned().unwrap_or_default();
125 let p2 = preset.get(&t2).cloned().unwrap_or_default();
126 if p1 != p2 {
127 return Err(format!(
128 "Free-choice violation: transitions {} and {} share input place {} but have different presets",
129 t1, t2, p
130 ));
131 }
132 }
133 }
134 }
135
136 Ok(())
137 }
138
139 pub fn try_new(
142 num_places: usize,
143 num_transitions: usize,
144 place_to_transition: Vec<(usize, usize)>,
145 transition_to_place: Vec<(usize, usize)>,
146 initial_marking: Vec<usize>,
147 ) -> Result<Self, String> {
148 Self::validate_inputs(
149 num_places,
150 num_transitions,
151 &place_to_transition,
152 &transition_to_place,
153 &initial_marking,
154 )?;
155 let mut net = Self {
156 num_places,
157 num_transitions,
158 place_to_transition,
159 transition_to_place,
160 initial_marking,
161 globally_dead: Vec::new(),
162 };
163 net.globally_dead = net.compute_globally_dead_transitions();
164 Ok(net)
165 }
166
167 pub fn new(
174 num_places: usize,
175 num_transitions: usize,
176 place_to_transition: Vec<(usize, usize)>,
177 transition_to_place: Vec<(usize, usize)>,
178 initial_marking: Vec<usize>,
179 ) -> Self {
180 Self::try_new(
181 num_places,
182 num_transitions,
183 place_to_transition,
184 transition_to_place,
185 initial_marking,
186 )
187 .unwrap_or_else(|message| panic!("{message}"))
188 }
189
190 pub fn num_places(&self) -> usize {
192 self.num_places
193 }
194
195 pub fn num_transitions(&self) -> usize {
197 self.num_transitions
198 }
199
200 pub fn num_arcs(&self) -> usize {
202 self.place_to_transition.len() + self.transition_to_place.len()
203 }
204
205 pub fn initial_token_sum(&self) -> usize {
207 self.initial_marking.iter().sum()
208 }
209
210 pub fn place_to_transition(&self) -> &[(usize, usize)] {
212 &self.place_to_transition
213 }
214
215 pub fn transition_to_place(&self) -> &[(usize, usize)] {
217 &self.transition_to_place
218 }
219
220 pub fn initial_marking(&self) -> &[usize] {
222 &self.initial_marking
223 }
224
225 fn enabled_transitions(&self, marking: &[usize]) -> Vec<bool> {
227 let mut enabled = vec![true; self.num_transitions];
228 for &(p, t) in &self.place_to_transition {
231 if marking[p] == 0 {
232 enabled[t] = false;
233 }
234 }
235 enabled
236 }
237
238 fn fire(&self, marking: &[usize], transition: usize) -> Option<Vec<usize>> {
240 let mut new_marking = marking.to_vec();
241 for &(p, t) in &self.place_to_transition {
243 if t == transition {
244 if new_marking[p] == 0 {
245 return None;
246 }
247 new_marking[p] -= 1;
248 }
249 }
250 for &(t, p) in &self.transition_to_place {
252 if t == transition {
253 new_marking[p] += 1;
254 }
255 }
256 Some(new_marking)
257 }
258
259 fn compute_globally_dead_transitions(&self) -> Vec<bool> {
267 let token_cap = self.initial_token_sum();
268 let num_t = self.num_transitions;
269
270 let mut marking_index: HashMap<Vec<usize>, usize> = HashMap::new();
272 let mut markings: Vec<Vec<usize>> = Vec::new();
273 let mut successors: Vec<Vec<(usize, usize)>> = Vec::new();
275 let mut queue: VecDeque<usize> = VecDeque::new();
276
277 let initial = self.initial_marking.clone();
278 marking_index.insert(initial.clone(), 0);
279 markings.push(initial);
280 successors.push(Vec::new());
281 queue.push_back(0);
282
283 while let Some(m_idx) = queue.pop_front() {
284 let enabled = self.enabled_transitions(&markings[m_idx]);
285 for (t, &is_enabled) in enabled.iter().enumerate() {
286 if !is_enabled {
287 continue;
288 }
289 if let Some(new_marking) = self.fire(&markings[m_idx], t) {
290 if new_marking.iter().any(|&tokens| tokens > token_cap) {
292 continue;
293 }
294 let next_idx = if let Some(&idx) = marking_index.get(&new_marking) {
295 idx
296 } else {
297 let idx = markings.len();
298 marking_index.insert(new_marking.clone(), idx);
299 markings.push(new_marking);
300 successors.push(Vec::new());
301 queue.push_back(idx);
302 idx
303 };
304 successors[m_idx].push((t, next_idx));
305 }
306 }
307 }
308
309 let num_markings = markings.len();
310
311 let mut globally_dead = vec![false; num_t];
319
320 let mut predecessors: Vec<Vec<usize>> = vec![Vec::new(); num_markings];
322 for (m_idx, succs) in successors.iter().enumerate() {
323 for &(_tr, next_idx) in succs {
324 predecessors[next_idx].push(m_idx);
325 }
326 }
327
328 for (t, dead) in globally_dead.iter_mut().enumerate() {
329 let mut can_reach_t = vec![false; num_markings];
332 let mut bfs_queue: VecDeque<usize> = VecDeque::new();
333
334 for (m_idx, succs) in successors.iter().enumerate() {
335 if succs.iter().any(|&(tr, _)| tr == t) {
336 can_reach_t[m_idx] = true;
337 bfs_queue.push_back(m_idx);
338 }
339 }
340
341 while let Some(m_idx) = bfs_queue.pop_front() {
343 for &pred_idx in &predecessors[m_idx] {
344 if !can_reach_t[pred_idx] {
345 can_reach_t[pred_idx] = true;
346 bfs_queue.push_back(pred_idx);
347 }
348 }
349 }
350
351 if can_reach_t.iter().any(|&reached| !reached) {
353 *dead = true;
354 }
355 }
356
357 globally_dead
358 }
359}
360
361#[derive(Deserialize)]
362struct NonLivenessFreePetriNetData {
363 num_places: usize,
364 num_transitions: usize,
365 place_to_transition: Vec<(usize, usize)>,
366 transition_to_place: Vec<(usize, usize)>,
367 initial_marking: Vec<usize>,
368}
369
370impl<'de> Deserialize<'de> for NonLivenessFreePetriNet {
371 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
372 where
373 D: Deserializer<'de>,
374 {
375 let data = NonLivenessFreePetriNetData::deserialize(deserializer)?;
376 Self::try_new(
377 data.num_places,
378 data.num_transitions,
379 data.place_to_transition,
380 data.transition_to_place,
381 data.initial_marking,
382 )
383 .map_err(D::Error::custom)
384 }
385}
386
387impl Problem for NonLivenessFreePetriNet {
388 const NAME: &'static str = "NonLivenessFreePetriNet";
389 type Value = Or;
390
391 fn variant() -> Vec<(&'static str, &'static str)> {
392 crate::variant_params![]
393 }
394
395 fn dims(&self) -> Vec<usize> {
396 vec![2; self.num_transitions]
397 }
398
399 fn evaluate(&self, config: &[usize]) -> Or {
400 if config.len() != self.num_transitions {
401 return Or(false);
402 }
403
404 for (t, &selected) in config.iter().enumerate() {
407 if selected == 1 && self.globally_dead[t] {
408 return Or(true);
409 }
410 }
411
412 Or(false)
413 }
414}
415
416crate::declare_variants! {
417 default NonLivenessFreePetriNet => "(initial_token_sum + 1) ^ num_places * num_transitions",
418}
419
420#[cfg(feature = "example-db")]
421pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
422 vec![crate::example_db::specs::ModelExampleSpec {
423 id: "non_liveness_free_petri_net",
424 instance: Box::new(NonLivenessFreePetriNet::new(
425 4,
426 3,
427 vec![(0, 0), (1, 1), (2, 2)],
428 vec![(0, 1), (1, 2), (2, 3)],
429 vec![1, 0, 0, 0],
430 )),
431 optimal_config: vec![1, 1, 1],
432 optimal_value: serde_json::json!(true),
433 }]
434}
435
436#[cfg(test)]
437#[path = "../../unit_tests/models/misc/non_liveness_free_petri_net.rs"]
438mod tests;