Skip to main content

libpetri_debug/
place_analysis.rs

1//! Analyze places for start/end/environment classification.
2
3use std::collections::HashMap;
4
5use libpetri_core::output;
6use libpetri_core::petri_net::PetriNet;
7
8/// Analysis info for a single place.
9#[derive(Debug, Clone)]
10pub struct PlaceAnalysisInfo {
11    pub token_type: String,
12    pub has_incoming: bool,
13    pub has_outgoing: bool,
14}
15
16/// Analyzes all places in a Petri net for structural classification.
17#[derive(Debug, Clone)]
18pub struct PlaceAnalysis {
19    data: HashMap<String, PlaceAnalysisInfo>,
20}
21
22impl PlaceAnalysis {
23    /// Build place analysis from a PetriNet.
24    pub fn from_net(net: &PetriNet) -> Self {
25        let mut data: HashMap<String, PlaceAnalysisInfo> = HashMap::new();
26
27        fn ensure<'a>(
28            data: &'a mut HashMap<String, PlaceAnalysisInfo>,
29            name: &str,
30        ) -> &'a mut PlaceAnalysisInfo {
31            data.entry(name.to_string())
32                .or_insert_with(|| PlaceAnalysisInfo {
33                    token_type: "unknown".into(),
34                    has_incoming: false,
35                    has_outgoing: false,
36                })
37        }
38
39        for t in net.transitions() {
40            // Input arcs: place → transition (place has outgoing)
41            for input in t.input_specs() {
42                ensure(&mut data, input.place_name()).has_outgoing = true;
43            }
44
45            // Output arcs: transition → place (place has incoming)
46            if let Some(out) = t.output_spec() {
47                for p in output::all_places(out) {
48                    ensure(&mut data, p.name()).has_incoming = true;
49                }
50            }
51
52            // Inhibitor arcs: just ensure place exists
53            for inh in t.inhibitors() {
54                ensure(&mut data, inh.place.name());
55            }
56
57            // Read arcs: place has outgoing
58            for r in t.reads() {
59                ensure(&mut data, r.place.name()).has_outgoing = true;
60            }
61
62            // Reset arcs: just ensure place exists
63            for r in t.resets() {
64                ensure(&mut data, r.place.name());
65            }
66        }
67
68        Self { data }
69    }
70
71    /// Returns the analysis data.
72    pub fn data(&self) -> &HashMap<String, PlaceAnalysisInfo> {
73        &self.data
74    }
75
76    /// Returns `true` if the place is a start place (no incoming arcs).
77    pub fn is_start(&self, place_name: &str) -> bool {
78        self.data
79            .get(place_name)
80            .is_some_and(|info| !info.has_incoming)
81    }
82
83    /// Returns `true` if the place is an end place (no outgoing arcs).
84    pub fn is_end(&self, place_name: &str) -> bool {
85        self.data
86            .get(place_name)
87            .is_some_and(|info| !info.has_outgoing)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use libpetri_core::input::one;
95    use libpetri_core::output::out_place;
96    use libpetri_core::place::Place;
97    use libpetri_core::transition::Transition;
98
99    #[test]
100    fn start_and_end_classification() {
101        let p1 = Place::<i32>::new("start");
102        let p2 = Place::<i32>::new("mid");
103        let p3 = Place::<i32>::new("end");
104
105        let t1 = Transition::builder("t1")
106            .input(one(&p1))
107            .output(out_place(&p2))
108            .build();
109        let t2 = Transition::builder("t2")
110            .input(one(&p2))
111            .output(out_place(&p3))
112            .build();
113
114        let net = PetriNet::builder("test").transitions([t1, t2]).build();
115        let analysis = PlaceAnalysis::from_net(&net);
116
117        assert!(analysis.is_start("start"));
118        assert!(!analysis.is_end("start"));
119        assert!(!analysis.is_start("mid"));
120        assert!(!analysis.is_end("mid"));
121        assert!(!analysis.is_start("end"));
122        assert!(analysis.is_end("end"));
123    }
124}