1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
12
13use std::collections::{HashMap, HashSet, VecDeque};
14
15#[derive(Debug, Clone)]
21pub struct Place {
22 pub name: String,
24 pub tokens: usize,
26}
27
28impl Place {
29 pub fn new(name: impl Into<String>) -> Self {
31 Self {
32 name: name.into(),
33 tokens: 0,
34 }
35 }
36
37 pub fn with_tokens(name: impl Into<String>, tokens: usize) -> Self {
39 Self {
40 name: name.into(),
41 tokens,
42 }
43 }
44}
45
46#[derive(Clone)]
53pub struct Transition {
54 pub name: String,
56 pub guard: Option<fn(&[usize]) -> bool>,
60}
61
62impl std::fmt::Debug for Transition {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("Transition")
65 .field("name", &self.name)
66 .field("guard", &self.guard.map(|_| "<fn>"))
67 .finish()
68 }
69}
70
71impl Transition {
72 pub fn new(name: impl Into<String>) -> Self {
74 Self {
75 name: name.into(),
76 guard: None,
77 }
78 }
79
80 pub fn with_guard(name: impl Into<String>, guard: fn(&[usize]) -> bool) -> Self {
82 Self {
83 name: name.into(),
84 guard: Some(guard),
85 }
86 }
87}
88
89pub type Weight = usize;
95
96#[derive(Debug, Clone)]
104pub struct PetriNet {
105 pub places: Vec<Place>,
107 pub transitions: Vec<Transition>,
109 pub arcs_in: Vec<(usize, usize, Weight)>,
111 pub arcs_out: Vec<(usize, usize, Weight)>,
113}
114
115impl PetriNet {
116 pub fn new() -> Self {
118 Self {
119 places: Vec::new(),
120 transitions: Vec::new(),
121 arcs_in: Vec::new(),
122 arcs_out: Vec::new(),
123 }
124 }
125
126 pub fn add_place(&mut self, place: Place) -> usize {
128 let idx = self.places.len();
129 self.places.push(place);
130 idx
131 }
132
133 pub fn add_transition(&mut self, transition: Transition) -> usize {
135 let idx = self.transitions.len();
136 self.transitions.push(transition);
137 idx
138 }
139
140 pub fn add_arc_in(&mut self, t: usize, p: usize, weight: Weight) {
142 self.arcs_in.push((t, p, weight));
143 }
144
145 pub fn add_arc_out(&mut self, t: usize, p: usize, weight: Weight) {
147 self.arcs_out.push((t, p, weight));
148 }
149
150 pub fn marking(&self) -> Vec<usize> {
152 self.places.iter().map(|p| p.tokens).collect()
153 }
154
155 pub fn set_marking(&mut self, marking: &[usize]) {
157 for (p, &tok) in self.places.iter_mut().zip(marking.iter()) {
158 p.tokens = tok;
159 }
160 }
161
162 pub fn enabled_transitions(&self) -> Vec<usize> {
167 let mut enabled = Vec::new();
168 for (t_idx, transition) in self.transitions.iter().enumerate() {
169 if self.is_enabled(t_idx, transition) {
170 enabled.push(t_idx);
171 }
172 }
173 enabled
174 }
175
176 fn is_enabled(&self, t_idx: usize, transition: &Transition) -> bool {
177 let mut input_tokens: Vec<usize> = Vec::new();
179 for &(t, p, w) in &self.arcs_in {
180 if t == t_idx {
181 if self.places[p].tokens < w {
182 return false;
183 }
184 input_tokens.push(self.places[p].tokens);
185 }
186 }
187 if let Some(guard) = transition.guard
188 && !guard(&input_tokens)
189 {
190 return false;
191 }
192 true
193 }
194
195 pub fn fire(&mut self, t_idx: usize) -> Result<(), String> {
200 if t_idx >= self.transitions.len() {
201 return Err(format!("transition index {} out of range", t_idx));
202 }
203 let transition = self.transitions[t_idx].clone();
204 if !self.is_enabled(t_idx, &transition) {
205 return Err(format!(
206 "transition {} is not enabled",
207 self.transitions[t_idx].name
208 ));
209 }
210 for &(t, p, w) in &self.arcs_in {
212 if t == t_idx {
213 self.places[p].tokens -= w;
214 }
215 }
216 for &(t, p, w) in &self.arcs_out {
218 if t == t_idx {
219 self.places[p].tokens += w;
220 }
221 }
222 Ok(())
223 }
224
225 pub fn is_deadlocked(&self) -> bool {
227 self.enabled_transitions().is_empty()
228 }
229
230 pub fn marking_sum(&self) -> usize {
232 self.places.iter().map(|p| p.tokens).sum()
233 }
234
235 pub fn reachability_bfs(&self) -> HashSet<Vec<usize>> {
239 let mut visited: HashSet<Vec<usize>> = HashSet::new();
240 let mut queue: VecDeque<Vec<usize>> = VecDeque::new();
241 let initial = self.marking();
242 queue.push_back(initial.clone());
243 visited.insert(initial);
244
245 let mut sim = self.clone();
247
248 while let Some(marking) = queue.pop_front() {
249 sim.set_marking(&marking);
250 for t_idx in sim.enabled_transitions() {
251 let mut next_sim = sim.clone();
252 let _ = next_sim.fire(t_idx);
253 let next_marking = next_sim.marking();
254 if !visited.contains(&next_marking) {
255 visited.insert(next_marking.clone());
256 queue.push_back(next_marking);
257 }
258 }
259 }
260 visited
261 }
262}
263
264impl Default for PetriNet {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270pub const OMEGA: usize = usize::MAX;
277
278#[derive(Debug, Clone)]
280pub struct CoverabilityNode {
281 pub marking: Vec<usize>,
283 pub parent: Option<usize>,
285 pub fired_transition: Option<usize>,
287 pub children: Vec<usize>,
289}
290
291#[derive(Debug)]
295pub struct CoverabilityTree {
296 pub nodes: Vec<CoverabilityNode>,
298}
299
300impl CoverabilityTree {
301 pub fn build(net: &PetriNet) -> Self {
303 let mut tree = CoverabilityTree { nodes: Vec::new() };
304 let root_marking = net.marking();
305 let root = CoverabilityNode {
306 marking: root_marking,
307 parent: None,
308 fired_transition: None,
309 children: Vec::new(),
310 };
311 tree.nodes.push(root);
312
313 let mut frontier: VecDeque<usize> = VecDeque::new();
315 frontier.push_back(0);
316
317 let mut sim = net.clone();
318
319 while let Some(node_idx) = frontier.pop_front() {
320 let current_marking = tree.nodes[node_idx].marking.clone();
321 sim.set_marking(¤t_marking);
322
323 for t_idx in sim.enabled_transitions() {
324 let mut next_sim = sim.clone();
325 let tmp_marking: Vec<usize> = current_marking
327 .iter()
328 .map(|&x| if x == OMEGA { 1_000_000 } else { x })
329 .collect();
330 next_sim.set_marking(&tmp_marking);
331 if next_sim.fire(t_idx).is_err() {
332 continue;
333 }
334 let mut new_marking = next_sim.marking();
335
336 let mut anc_idx = Some(node_idx);
338 while let Some(ai) = anc_idx {
339 let anc_marking = &tree.nodes[ai].marking;
340 if anc_marking
341 .iter()
342 .zip(new_marking.iter())
343 .all(|(&a, &n)| a == OMEGA || a <= n)
344 && anc_marking
345 .iter()
346 .zip(new_marking.iter())
347 .any(|(&a, &n)| a != OMEGA && a < n)
348 {
349 for (m, a) in new_marking.iter_mut().zip(anc_marking.iter()) {
351 if *a != OMEGA && *a < *m {
352 *m = OMEGA;
353 }
354 }
355 }
356 anc_idx = tree.nodes[ai].parent;
357 }
358
359 let already_visited = tree.nodes.iter().any(|n| n.marking == new_marking);
361 let child_idx = tree.nodes.len();
362 tree.nodes.push(CoverabilityNode {
363 marking: new_marking,
364 parent: Some(node_idx),
365 fired_transition: Some(t_idx),
366 children: Vec::new(),
367 });
368 tree.nodes[node_idx].children.push(child_idx);
369
370 if !already_visited {
371 frontier.push_back(child_idx);
372 }
373 }
374 }
375
376 tree
377 }
378
379 pub fn is_bounded(&self) -> bool {
381 self.nodes
382 .iter()
383 .all(|n| n.marking.iter().all(|&t| t != OMEGA))
384 }
385
386 pub fn node_count(&self) -> usize {
388 self.nodes.len()
389 }
390}
391
392#[derive(Debug, Clone)]
399pub struct StochasticPetriNet {
400 pub net: PetriNet,
402 pub rates: Vec<f64>,
404}
405
406impl StochasticPetriNet {
407 pub fn new(net: PetriNet, rates: Vec<f64>) -> Self {
410 assert_eq!(
411 net.transitions.len(),
412 rates.len(),
413 "one rate per transition required"
414 );
415 Self { net, rates }
416 }
417
418 pub fn simulate_until(&mut self, t_end: f64) -> Vec<(f64, usize)> {
422 let mut events = Vec::new();
423 let mut t = 0.0_f64;
424 use rand::RngExt as _;
425 let mut rng = rand::rng();
426
427 loop {
428 let enabled = self.net.enabled_transitions();
429 if enabled.is_empty() {
430 break;
431 }
432 let total_rate: f64 = enabled.iter().map(|&i| self.rates[i]).sum();
434 if total_rate == 0.0 {
435 break;
436 }
437 let u: f64 = rng.random_range(0.0_f64..1.0_f64);
439 let dt = -u.ln() / total_rate;
440 t += dt;
441 if t > t_end {
442 break;
443 }
444 let selector: f64 = rng.random_range(0.0_f64..total_rate);
446 let mut cumulative = 0.0;
447 let mut chosen = enabled[0];
448 for &ti in &enabled {
449 cumulative += self.rates[ti];
450 if selector < cumulative {
451 chosen = ti;
452 break;
453 }
454 }
455 let _ = self.net.fire(chosen);
456 events.push((t, chosen));
457 }
458 events
459 }
460
461 pub fn steady_state_estimate(&self, t_end: f64, n_samples: usize) -> Vec<f64> {
466 let n_places = self.net.places.len();
467 let mut totals = vec![0.0_f64; n_places];
468
469 for _ in 0..n_samples {
470 let mut sim = self.clone();
471 let events = sim.simulate_until(t_end);
472 let _ = events; let marking = sim.net.marking();
474 for (i, &tok) in marking.iter().enumerate() {
475 totals[i] += tok as f64;
476 }
477 }
478 totals.iter().map(|&s| s / n_samples as f64).collect()
479 }
480}
481
482#[derive(Debug, Clone, PartialEq)]
488pub struct ColoredToken {
489 pub color: Vec<f64>,
491}
492
493impl ColoredToken {
494 pub fn new(color: Vec<f64>) -> Self {
496 Self { color }
497 }
498}
499
500#[derive(Debug, Clone)]
502pub struct ColoredPlace {
503 pub name: String,
505 pub tokens: Vec<ColoredToken>,
507}
508
509impl ColoredPlace {
510 pub fn new(name: impl Into<String>) -> Self {
512 Self {
513 name: name.into(),
514 tokens: Vec::new(),
515 }
516 }
517
518 pub fn add_token(&mut self, token: ColoredToken) {
520 self.tokens.push(token);
521 }
522
523 pub fn take_token(&mut self) -> Option<ColoredToken> {
525 if self.tokens.is_empty() {
526 None
527 } else {
528 Some(self.tokens.remove(0))
529 }
530 }
531
532 pub fn token_count(&self) -> usize {
534 self.tokens.len()
535 }
536}
537
538#[derive(Debug, Clone)]
540pub struct ColoredTransition {
541 pub name: String,
543 pub color_guard: Option<fn(&ColoredToken) -> bool>,
545}
546
547impl ColoredTransition {
548 pub fn new(name: impl Into<String>) -> Self {
550 Self {
551 name: name.into(),
552 color_guard: None,
553 }
554 }
555
556 pub fn with_guard(name: impl Into<String>, guard: fn(&ColoredToken) -> bool) -> Self {
558 Self {
559 name: name.into(),
560 color_guard: Some(guard),
561 }
562 }
563}
564
565#[derive(Debug, Clone)]
567pub struct ColoredPetriNet {
568 pub places: Vec<ColoredPlace>,
570 pub transitions: Vec<ColoredTransition>,
572 pub arcs_in: Vec<(usize, usize)>,
574 pub arcs_out: Vec<(usize, usize)>,
576}
577
578impl ColoredPetriNet {
579 pub fn new() -> Self {
581 Self {
582 places: Vec::new(),
583 transitions: Vec::new(),
584 arcs_in: Vec::new(),
585 arcs_out: Vec::new(),
586 }
587 }
588
589 pub fn add_place(&mut self, place: ColoredPlace) -> usize {
591 let idx = self.places.len();
592 self.places.push(place);
593 idx
594 }
595
596 pub fn add_transition(&mut self, transition: ColoredTransition) -> usize {
598 let idx = self.transitions.len();
599 self.transitions.push(transition);
600 idx
601 }
602
603 pub fn add_arc_in(&mut self, t: usize, p: usize) {
605 self.arcs_in.push((t, p));
606 }
607
608 pub fn add_arc_out(&mut self, t: usize, p: usize) {
610 self.arcs_out.push((t, p));
611 }
612
613 pub fn enabled_transitions(&self) -> Vec<usize> {
615 let mut enabled = Vec::new();
616 'outer: for (t_idx, trans) in self.transitions.iter().enumerate() {
617 for &(t, p) in &self.arcs_in {
618 if t == t_idx {
619 if self.places[p].tokens.is_empty() {
620 continue 'outer;
621 }
622 if let Some(guard) = trans.color_guard
623 && !guard(&self.places[p].tokens[0])
624 {
625 continue 'outer;
626 }
627 }
628 }
629 enabled.push(t_idx);
630 }
631 enabled
632 }
633
634 pub fn fire(&mut self, t_idx: usize) -> Result<(), String> {
637 if t_idx >= self.transitions.len() {
638 return Err(format!("transition index {} out of range", t_idx));
639 }
640 let mut consumed: Vec<(usize, ColoredToken)> = Vec::new();
642 for &(t, p) in &self.arcs_in {
643 if t == t_idx {
644 let tok = self.places[p]
645 .take_token()
646 .ok_or_else(|| format!("place {} has no tokens", p))?;
647 consumed.push((p, tok));
648 }
649 }
650 let token_to_pass = consumed
652 .first()
653 .map(|(_, tok)| tok.clone())
654 .unwrap_or_else(|| ColoredToken::new(vec![0.0]));
655 for &(t, p) in &self.arcs_out {
656 if t == t_idx {
657 self.places[p].add_token(token_to_pass.clone());
658 }
659 }
660 Ok(())
661 }
662
663 pub fn total_tokens(&self) -> usize {
665 self.places.iter().map(|p| p.token_count()).sum()
666 }
667}
668
669impl Default for ColoredPetriNet {
670 fn default() -> Self {
671 Self::new()
672 }
673}
674
675pub fn parse_pnml(_input: &str) -> PetriNet {
684 PetriNet::new()
686}
687
688pub fn producer_consumer_net() -> PetriNet {
697 let mut net = PetriNet::new();
698 let buffer = net.add_place(Place::with_tokens("buffer", 0));
699 let ready = net.add_place(Place::with_tokens("consumer_ready", 1));
700 let produce = net.add_transition(Transition::new("produce"));
701 let consume = net.add_transition(Transition::new("consume"));
702 net.add_arc_in(consume, buffer, 1);
703 net.add_arc_in(consume, ready, 1);
704 net.add_arc_out(produce, buffer, 1);
705 net.add_arc_out(consume, ready, 1);
706 net
707}
708
709pub fn mutex_net() -> PetriNet {
713 let mut net = PetriNet::new();
714 let resource = net.add_place(Place::with_tokens("resource", 1));
715 let p1_idle = net.add_place(Place::with_tokens("p1_idle", 1));
716 let p2_idle = net.add_place(Place::with_tokens("p2_idle", 1));
717 let p1_cs = net.add_place(Place::with_tokens("p1_cs", 0));
718 let p2_cs = net.add_place(Place::with_tokens("p2_cs", 0));
719 let enter1 = net.add_transition(Transition::new("enter1"));
720 let exit1 = net.add_transition(Transition::new("exit1"));
721 let enter2 = net.add_transition(Transition::new("enter2"));
722 let exit2 = net.add_transition(Transition::new("exit2"));
723 net.add_arc_in(enter1, resource, 1);
724 net.add_arc_in(enter1, p1_idle, 1);
725 net.add_arc_out(enter1, p1_cs, 1);
726 net.add_arc_in(exit1, p1_cs, 1);
727 net.add_arc_out(exit1, resource, 1);
728 net.add_arc_out(exit1, p1_idle, 1);
729 net.add_arc_in(enter2, resource, 1);
730 net.add_arc_in(enter2, p2_idle, 1);
731 net.add_arc_out(enter2, p2_cs, 1);
732 net.add_arc_in(exit2, p2_cs, 1);
733 net.add_arc_out(exit2, resource, 1);
734 net.add_arc_out(exit2, p2_idle, 1);
735 net
736}
737
738pub fn incidence_matrix(net: &PetriNet) -> Vec<Vec<i64>> {
746 let nt = net.transitions.len();
747 let np = net.places.len();
748 let mut matrix = vec![vec![0i64; np]; nt];
749 for &(t, p, w) in &net.arcs_out {
750 matrix[t][p] += w as i64;
751 }
752 for &(t, p, w) in &net.arcs_in {
753 matrix[t][p] -= w as i64;
754 }
755 matrix
756}
757
758pub fn state_equation(net: &PetriNet, sigma: &[i64]) -> Vec<i64> {
761 let matrix = incidence_matrix(net);
762 let marking: Vec<i64> = net.places.iter().map(|p| p.tokens as i64).collect();
763 let np = net.places.len();
764 let mut result = marking;
765 for (t_idx, &fires) in sigma.iter().enumerate() {
766 if t_idx < matrix.len() {
767 for p in 0..np {
768 result[p] += matrix[t_idx][p] * fires;
769 }
770 }
771 }
772 result
773}
774
775pub fn is_trap(net: &PetriNet, place_set: &[usize]) -> bool {
784 let set: HashSet<usize> = place_set.iter().copied().collect();
785 for &(t, p_in, _) in &net.arcs_in {
786 if set.contains(&p_in) {
787 let produces_into_set = net
789 .arcs_out
790 .iter()
791 .any(|&(tout, pout, _)| tout == t && set.contains(&pout));
792 if !produces_into_set {
793 return false;
794 }
795 }
796 }
797 true
798}
799
800pub fn is_siphon(net: &PetriNet, place_set: &[usize]) -> bool {
805 let set: HashSet<usize> = place_set.iter().copied().collect();
806 for &(t, p_out, _) in &net.arcs_out {
807 if set.contains(&p_out) {
808 let takes_from_set = net
809 .arcs_in
810 .iter()
811 .any(|&(tin, pin, _)| tin == t && set.contains(&pin));
812 if !takes_from_set {
813 return false;
814 }
815 }
816 }
817 true
818}
819
820pub fn find_t_invariant(net: &PetriNet, max_count: usize) -> Option<Vec<i64>> {
830 let matrix = incidence_matrix(net);
831 let nt = net.transitions.len();
832 let np = net.places.len();
833 fn rec(
835 matrix: &[Vec<i64>],
836 nt: usize,
837 np: usize,
838 max_count: usize,
839 idx: usize,
840 current: &mut Vec<i64>,
841 ) -> Option<Vec<i64>> {
842 if idx == nt {
843 let mut is_trivial = true;
845 for &c in current.iter() {
846 if c != 0 {
847 is_trivial = false;
848 break;
849 }
850 }
851 if is_trivial {
852 return None;
853 }
854 for p in 0..np {
855 let mut sum = 0i64;
856 for t in 0..nt {
857 sum += matrix[t][p] * current[t];
858 }
859 if sum != 0 {
860 return None;
861 }
862 }
863 return Some(current.clone());
864 }
865 for v in 0..=(max_count as i64) {
866 current[idx] = v;
867 if let Some(res) = rec(matrix, nt, np, max_count, idx + 1, current) {
868 return Some(res);
869 }
870 }
871 None
872 }
873 let mut current = vec![0i64; nt];
874 rec(&matrix, nt, np, max_count, 0, &mut current)
875}
876
877pub fn replay_sequence(net: &mut PetriNet, sequence: &[usize]) -> Result<Vec<usize>, usize> {
885 for (step, &t_idx) in sequence.iter().enumerate() {
886 if net.fire(t_idx).is_err() {
887 return Err(step);
888 }
889 }
890 Ok(net.marking())
891}
892
893#[derive(Debug, Clone)]
899pub struct PetriNetMetrics {
900 pub n_places: usize,
902 pub n_transitions: usize,
904 pub n_arcs: usize,
906 pub avg_out_degree: f64,
908 pub has_source_transition: bool,
910 pub has_sink_transition: bool,
912}
913
914pub fn compute_metrics(net: &PetriNet) -> PetriNetMetrics {
916 let n_transitions = net.transitions.len();
917 let total_out: usize = net
918 .transitions
919 .iter()
920 .enumerate()
921 .map(|(t, _)| net.arcs_out.iter().filter(|&&(ti, _, _)| ti == t).count())
922 .sum();
923 let avg_out_degree = if n_transitions == 0 {
924 0.0
925 } else {
926 total_out as f64 / n_transitions as f64
927 };
928 let has_source_transition =
929 (0..n_transitions).any(|t| !net.arcs_in.iter().any(|&(ti, _, _)| ti == t));
930 let has_sink_transition =
931 (0..n_transitions).any(|t| !net.arcs_out.iter().any(|&(ti, _, _)| ti == t));
932 PetriNetMetrics {
933 n_places: net.places.len(),
934 n_transitions,
935 n_arcs: net.arcs_in.len() + net.arcs_out.len(),
936 avg_out_degree,
937 has_source_transition,
938 has_sink_transition,
939 }
940}
941
942pub fn disjoint_union(a: &PetriNet, b: &PetriNet) -> PetriNet {
949 let mut result = a.clone();
950 let place_offset = a.places.len();
951 let trans_offset = a.transitions.len();
952 for p in &b.places {
953 result.places.push(p.clone());
954 }
955 for t in &b.transitions {
956 result.transitions.push(t.clone());
957 }
958 for &(t, p, w) in &b.arcs_in {
959 result.arcs_in.push((t + trans_offset, p + place_offset, w));
960 }
961 for &(t, p, w) in &b.arcs_out {
962 result
963 .arcs_out
964 .push((t + trans_offset, p + place_offset, w));
965 }
966 result
967}
968
969pub fn is_live_approx(net: &PetriNet, t_idx: usize) -> bool {
979 let reachable = net.reachability_bfs();
980 let mut sim = net.clone();
981 for marking in &reachable {
982 sim.set_marking(marking);
983 if sim.enabled_transitions().contains(&t_idx) {
984 return true;
985 }
986 }
987 false
988}
989
990pub fn workflow_with_resource(capacity: usize) -> PetriNet {
996 let mut net = PetriNet::new();
997 let start = net.add_place(Place::with_tokens("start", 1));
998 let working = net.add_place(Place::with_tokens("working", 0));
999 let done = net.add_place(Place::with_tokens("done", 0));
1000 let resource = net.add_place(Place::with_tokens("resource", capacity));
1001 let begin = net.add_transition(Transition::new("begin"));
1002 let finish = net.add_transition(Transition::new("finish"));
1003 net.add_arc_in(begin, start, 1);
1004 net.add_arc_in(begin, resource, 1);
1005 net.add_arc_out(begin, working, 1);
1006 net.add_arc_in(finish, working, 1);
1007 net.add_arc_out(finish, done, 1);
1008 net.add_arc_out(finish, resource, 1);
1009 net
1010}
1011
1012pub type SparseMarking = HashMap<String, usize>;
1018
1019pub fn dense_to_sparse(net: &PetriNet, marking: &[usize]) -> SparseMarking {
1021 net.places
1022 .iter()
1023 .zip(marking.iter())
1024 .map(|(p, &tok)| (p.name.clone(), tok))
1025 .collect()
1026}
1027
1028pub fn sparse_to_dense(net: &PetriNet, sparse: &SparseMarking) -> Vec<usize> {
1030 net.places
1031 .iter()
1032 .map(|p| *sparse.get(&p.name).unwrap_or(&0))
1033 .collect()
1034}
1035
1036#[cfg(test)]
1041mod tests {
1042 use super::*;
1043
1044 fn simple_net() -> PetriNet {
1045 let mut net = PetriNet::new();
1046 let p0 = net.add_place(Place::with_tokens("p0", 1));
1047 let p1 = net.add_place(Place::with_tokens("p1", 0));
1048 let t0 = net.add_transition(Transition::new("t0"));
1049 net.add_arc_in(t0, p0, 1);
1050 net.add_arc_out(t0, p1, 1);
1051 net
1052 }
1053
1054 #[test]
1055 fn test_place_tokens() {
1056 let p = Place::with_tokens("p", 3);
1057 assert_eq!(p.tokens, 3);
1058 }
1059
1060 #[test]
1061 fn test_transition_name() {
1062 let t = Transition::new("t1");
1063 assert_eq!(t.name, "t1");
1064 }
1065
1066 #[test]
1067 fn test_add_place_transition() {
1068 let mut net = PetriNet::new();
1069 let pi = net.add_place(Place::new("p"));
1070 let ti = net.add_transition(Transition::new("t"));
1071 assert_eq!(pi, 0);
1072 assert_eq!(ti, 0);
1073 }
1074
1075 #[test]
1076 fn test_fire_basic() {
1077 let mut net = simple_net();
1078 assert_eq!(net.marking(), vec![1, 0]);
1079 net.fire(0).unwrap();
1080 assert_eq!(net.marking(), vec![0, 1]);
1081 }
1082
1083 #[test]
1084 fn test_fire_not_enabled() {
1085 let mut net = simple_net();
1086 net.fire(0).unwrap(); assert!(net.fire(0).is_err());
1088 }
1089
1090 #[test]
1091 fn test_enabled_transitions_empty() {
1092 let mut net = simple_net();
1093 net.fire(0).unwrap();
1094 assert!(net.enabled_transitions().is_empty());
1095 }
1096
1097 #[test]
1098 fn test_is_deadlocked() {
1099 let mut net = simple_net();
1100 net.fire(0).unwrap();
1101 assert!(net.is_deadlocked());
1102 }
1103
1104 #[test]
1105 fn test_marking_sum() {
1106 let net = simple_net();
1107 assert_eq!(net.marking_sum(), 1);
1108 }
1109
1110 #[test]
1111 fn test_reachability_bfs() {
1112 let net = simple_net();
1113 let reachable = net.reachability_bfs();
1114 assert!(reachable.contains(&vec![1, 0]));
1115 assert!(reachable.contains(&vec![0, 1]));
1116 assert_eq!(reachable.len(), 2);
1117 }
1118
1119 #[test]
1120 fn test_mutex_net_reachability() {
1121 let net = mutex_net();
1122 let reachable = net.reachability_bfs();
1123 for marking in &reachable {
1125 assert!(
1127 !(marking[3] >= 1 && marking[4] >= 1),
1128 "mutual exclusion violated"
1129 );
1130 }
1131 }
1132
1133 #[test]
1134 fn test_producer_consumer_enabled() {
1135 let mut net = producer_consumer_net();
1136 let enabled = net.enabled_transitions();
1138 assert_eq!(enabled.len(), 1); net.fire(enabled[0]).unwrap(); assert_eq!(net.marking_sum(), 2); }
1142
1143 #[test]
1144 fn test_coverability_tree_bounded() {
1145 let net = simple_net();
1146 let tree = CoverabilityTree::build(&net);
1147 assert!(tree.is_bounded());
1148 }
1149
1150 #[test]
1151 fn test_coverability_tree_unbounded() {
1152 let mut net = PetriNet::new();
1154 let p0 = net.add_place(Place::with_tokens("p0", 1));
1155 let t0 = net.add_transition(Transition::new("t0"));
1156 net.add_arc_in(t0, p0, 1);
1158 net.add_arc_out(t0, p0, 2);
1159 let tree = CoverabilityTree::build(&net);
1160 assert!(!tree.is_bounded());
1161 }
1162
1163 #[test]
1164 fn test_stochastic_simulate() {
1165 let mut net = PetriNet::new();
1166 let p0 = net.add_place(Place::with_tokens("p0", 3));
1167 let p1 = net.add_place(Place::with_tokens("p1", 0));
1168 let t0 = net.add_transition(Transition::new("t0"));
1169 net.add_arc_in(t0, p0, 1);
1170 net.add_arc_out(t0, p1, 1);
1171 let mut spn = StochasticPetriNet::new(net, vec![1.0]);
1172 let events = spn.simulate_until(10.0);
1173 assert_eq!(events.len(), 3);
1175 }
1176
1177 #[test]
1178 fn test_stochastic_steady_state() {
1179 let net = simple_net();
1180 let spn = StochasticPetriNet::new(net, vec![1.0]);
1181 let ss = spn.steady_state_estimate(100.0, 5);
1182 assert!((ss.iter().sum::<f64>() - 1.0).abs() < 1e-9);
1184 }
1185
1186 #[test]
1187 fn test_colored_place_tokens() {
1188 let mut p = ColoredPlace::new("p");
1189 p.add_token(ColoredToken::new(vec![1.0, 2.0]));
1190 assert_eq!(p.token_count(), 1);
1191 let tok = p.take_token().unwrap();
1192 assert_eq!(tok.color, vec![1.0, 2.0]);
1193 assert_eq!(p.token_count(), 0);
1194 }
1195
1196 #[test]
1197 fn test_colored_petri_net_fire() {
1198 let mut cpn = ColoredPetriNet::new();
1199 let p0 = cpn.add_place(ColoredPlace::new("p0"));
1200 let p1 = cpn.add_place(ColoredPlace::new("p1"));
1201 let t0 = cpn.add_transition(ColoredTransition::new("t0"));
1202 cpn.add_arc_in(t0, p0);
1203 cpn.add_arc_out(t0, p1);
1204 cpn.places[p0].add_token(ColoredToken::new(vec![1.0]));
1205 cpn.fire(t0).unwrap();
1206 assert_eq!(cpn.places[p0].token_count(), 0);
1207 assert_eq!(cpn.places[p1].token_count(), 1);
1208 }
1209
1210 #[test]
1211 fn test_parse_pnml_stub() {
1212 let net = parse_pnml("<pnml/>");
1213 assert!(net.places.is_empty());
1214 }
1215
1216 #[test]
1217 fn test_incidence_matrix() {
1218 let net = simple_net();
1219 let m = incidence_matrix(&net);
1220 assert_eq!(m[0][0], -1);
1222 assert_eq!(m[0][1], 1);
1223 }
1224
1225 #[test]
1226 fn test_state_equation() {
1227 let net = simple_net();
1228 let result = state_equation(&net, &[1]);
1229 assert_eq!(result, vec![0, 1]);
1230 }
1231
1232 #[test]
1233 fn test_is_trap() {
1234 let simple = simple_net();
1236 assert!(is_trap(&simple, &[1]));
1237 }
1238
1239 #[test]
1240 fn test_is_siphon() {
1241 let simple = simple_net();
1242 assert!(is_siphon(&simple, &[0]));
1244 }
1245
1246 #[test]
1247 fn test_find_t_invariant() {
1248 let mut net = PetriNet::new();
1250 let p0 = net.add_place(Place::with_tokens("p0", 1));
1251 let p1 = net.add_place(Place::with_tokens("p1", 0));
1252 let t0 = net.add_transition(Transition::new("t0"));
1253 let t1 = net.add_transition(Transition::new("t1"));
1254 net.add_arc_in(t0, p0, 1);
1255 net.add_arc_out(t0, p1, 1);
1256 net.add_arc_in(t1, p1, 1);
1257 net.add_arc_out(t1, p0, 1);
1258 let inv = find_t_invariant(&net, 2);
1259 assert!(inv.is_some());
1260 }
1261
1262 #[test]
1263 fn test_replay_sequence_ok() {
1264 let mut net = simple_net();
1265 let result = replay_sequence(&mut net, &[0]);
1266 assert_eq!(result, Ok(vec![0, 1]));
1267 }
1268
1269 #[test]
1270 fn test_replay_sequence_fail() {
1271 let mut net = simple_net();
1272 let result = replay_sequence(&mut net, &[0, 0]);
1273 assert_eq!(result, Err(1));
1274 }
1275
1276 #[test]
1277 fn test_compute_metrics() {
1278 let net = simple_net();
1279 let m = compute_metrics(&net);
1280 assert_eq!(m.n_places, 2);
1281 assert_eq!(m.n_transitions, 1);
1282 assert_eq!(m.n_arcs, 2);
1283 }
1284
1285 #[test]
1286 fn test_disjoint_union() {
1287 let a = simple_net();
1288 let b = simple_net();
1289 let u = disjoint_union(&a, &b);
1290 assert_eq!(u.places.len(), 4);
1291 assert_eq!(u.transitions.len(), 2);
1292 }
1293
1294 #[test]
1295 fn test_is_live_approx() {
1296 let net = simple_net();
1297 assert!(is_live_approx(&net, 0));
1298 }
1299
1300 #[test]
1301 fn test_workflow_net() {
1302 let net = workflow_with_resource(2);
1303 assert_eq!(net.places.len(), 4);
1304 assert_eq!(net.transitions.len(), 2);
1305 }
1306
1307 #[test]
1308 fn test_dense_sparse_roundtrip() {
1309 let net = simple_net();
1310 let marking = net.marking();
1311 let sparse = dense_to_sparse(&net, &marking);
1312 let dense = sparse_to_dense(&net, &sparse);
1313 assert_eq!(marking, dense);
1314 }
1315
1316 #[test]
1317 fn test_guarded_transition() {
1318 let mut net = PetriNet::new();
1319 let p0 = net.add_place(Place::with_tokens("p0", 2));
1320 let p1 = net.add_place(Place::with_tokens("p1", 0));
1321 let t0 = net.add_transition(Transition::with_guard("t0", |tokens| {
1323 tokens.iter().all(|&t| t >= 2)
1324 }));
1325 net.add_arc_in(t0, p0, 1);
1326 net.add_arc_out(t0, p1, 1);
1327 let enabled = net.enabled_transitions();
1328 assert_eq!(enabled.len(), 1);
1329 net.fire(0).unwrap();
1330 assert!(net.enabled_transitions().is_empty());
1332 }
1333
1334 #[test]
1335 fn test_coverability_tree_node_count() {
1336 let net = simple_net();
1337 let tree = CoverabilityTree::build(&net);
1338 assert!(tree.node_count() >= 2);
1339 }
1340
1341 #[test]
1342 fn test_colored_transition_guard() {
1343 let mut cpn = ColoredPetriNet::new();
1344 let p0 = cpn.add_place(ColoredPlace::new("p0"));
1345 let p1 = cpn.add_place(ColoredPlace::new("p1"));
1346 let t0 = cpn.add_transition(ColoredTransition::with_guard("t0", |tok| {
1347 tok.color.first().copied().unwrap_or(0.0) > 0.5
1348 }));
1349 cpn.add_arc_in(t0, p0);
1350 cpn.add_arc_out(t0, p1);
1351 cpn.places[p0].add_token(ColoredToken::new(vec![0.1]));
1353 assert!(cpn.enabled_transitions().is_empty());
1354 cpn.places[p0].tokens.clear();
1356 cpn.places[p0].add_token(ColoredToken::new(vec![0.9]));
1357 assert_eq!(cpn.enabled_transitions(), vec![0]);
1358 }
1359}