netcrab/export/
lola.rs

1use crate::petri_net::{PetriNet, PlaceRef};
2use std::collections::BTreeSet;
3
4impl PetriNet {
5    /// Converts the net to a string in the format accepted by the `LoLA` model checker and returns it.
6    ///
7    /// # Errors
8    ///
9    /// If the writer fails to write the contents of the net, then an error is returned.
10    pub fn to_lola_string(&self) -> Result<String, std::io::Error> {
11        let mut writer = Vec::new();
12        self.to_lola(&mut writer)?;
13        String::from_utf8(writer).map_err(|_|
14            // This error could only be due to a bug, map it to a more standard error type.
15            std::io::Error::new(
16                std::io::ErrorKind::Other,
17                "Could not convert the string to UTF-8",
18            ))
19    }
20
21    /// Converts the net to the format accepted by the `LoLA` model checker.
22    /// Writes the output to a trait object which implements `std::io::Write`.
23    ///
24    /// # Errors
25    ///
26    /// If the writer fails to write the contents of the net, then an error is returned.
27    pub fn to_lola<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
28    where
29        T: std::io::Write,
30    {
31        self.write_lola_places(writer)?;
32        self.write_lola_initial_marking(writer)?;
33        self.write_lola_transitions(writer)?;
34        Ok(())
35    }
36
37    /// Writes the lines that define the places
38    /// to a trait object which implements `std::io::Write`.
39    fn write_lola_places<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
40    where
41        T: std::io::Write,
42    {
43        if self.get_cardinality_places() == 0 {
44            return Ok(());
45        }
46        writer.write_all(b"PLACE\n")?;
47
48        let last_index = self.get_cardinality_places() - 1;
49        for (i, (place_ref, _)) in self.places_iter().enumerate() {
50            let line = if i == last_index {
51                // Last place line has a semicolon and an empty line.
52                format!("    {place_ref};\n\n")
53            } else {
54                format!("    {place_ref},\n")
55            };
56            writer.write_all(line.as_bytes())?;
57        }
58        Ok(())
59    }
60
61    /// Writes the lines that define the initial marking
62    /// to a trait object which implements `std::io::Write`.
63    fn write_lola_initial_marking<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
64    where
65        T: std::io::Write,
66    {
67        if self.get_cardinality_places() == 0 {
68            return Ok(());
69        }
70        writer.write_all(b"MARKING\n")?;
71
72        let last_index = self.get_cardinality_places() - 1;
73        for (i, (place_ref, place)) in self.places_iter().enumerate() {
74            let marking = place.marking();
75
76            let line = if i == last_index {
77                // Last marking line has a semicolon and an empty line.
78                format!("    {place_ref} : {marking};\n\n")
79            } else {
80                format!("    {place_ref} : {marking},\n")
81            };
82            writer.write_all(line.as_bytes())?;
83        }
84        Ok(())
85    }
86
87    /// Writes the lines that define the transitions
88    /// to a trait object which implements `std::io::Write`.
89    fn write_lola_transitions<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
90    where
91        T: std::io::Write,
92    {
93        for (transition_ref, transition) in self.transitions_iter() {
94            let header_line = format!("TRANSITION {transition_ref}\n");
95            writer.write_all(header_line.as_bytes())?;
96
97            Self::write_transition_arcs(transition.get_preset(), "CONSUME", writer)?;
98            Self::write_transition_arcs(transition.get_postset(), "PRODUCE", writer)?;
99        }
100        Ok(())
101    }
102
103    // Writes the lines corresponding to either the preset or the postset of a given transition
104    // that define tokens from which places are consumed and produced when the transition is fired.
105    // The result is written to a trait object which implements `std::io::Write`.
106    fn write_transition_arcs<T>(
107        set: &BTreeSet<PlaceRef>,
108        header: &str,
109        writer: &mut T,
110    ) -> Result<(), std::io::Error>
111    where
112        T: std::io::Write,
113    {
114        // The format requires us to close the header with a semicolon if empty
115        if set.is_empty() {
116            let header_line = format!("  {header};\n");
117            writer.write_all(header_line.as_bytes())?;
118            return Ok(());
119        }
120        let header_line = format!("  {header}\n");
121        writer.write_all(header_line.as_bytes())?;
122
123        let last_index = set.len() - 1;
124        for (i, place_ref) in set.iter().enumerate() {
125            // Edge multiplicity is always 1 for now.
126            let line = if i == last_index {
127                // Last line has a semicolon and an empty line.
128                format!("    {place_ref} : 1;\n")
129            } else {
130                format!("    {place_ref} : 1,\n")
131            };
132            writer.write_all(line.as_bytes())?;
133        }
134        Ok(())
135    }
136}
137
138#[cfg(test)]
139mod lola_tests {
140    use super::*;
141    use crate::export::test_export_examples::*;
142    use crate::net_creator::*;
143
144    #[test]
145    fn lola_string_empty_net() {
146        let net = PetriNet::new();
147        let result = net.to_lola_string();
148        assert!(result.is_ok());
149        assert_eq!(result.unwrap(), String::new());
150    }
151
152    #[test]
153    fn lola_string_only_empty_places_net() {
154        let (net, _, _) = create_basic_unconnected_net(5, 0);
155        let result = net.to_lola_string();
156
157        assert!(result.is_ok());
158        assert_eq!(result.unwrap(), LOLA_STRING_ONLY_EMPTY_PLACES_NET);
159    }
160
161    #[test]
162    fn lola_string_marked_places_net() {
163        let mut net = PetriNet::new();
164        let p1 = net.add_place("P1");
165        let p2 = net.add_place("P2");
166        let p3 = net.add_place("P3");
167        let p4 = net.add_place("P4");
168        let p5 = net.add_place("P5");
169
170        assert!(net.add_token(&p1, 5).is_ok());
171        assert!(net.add_token(&p2, 6).is_ok());
172        assert!(net.add_token(&p3, 3).is_ok());
173        assert!(net.add_token(&p4, 2).is_ok());
174        assert!(net.add_token(&p5, 1).is_ok());
175        let result = net.to_lola_string();
176
177        assert!(result.is_ok());
178        assert_eq!(result.unwrap().as_str(), LOLA_STRING_MARKED_PLACES_NET);
179    }
180
181    #[test]
182    fn lola_string_only_empty_transitions_net() {
183        let (net, _, _) = create_basic_unconnected_net(0, 5);
184        let result = net.to_lola_string();
185
186        assert!(result.is_ok());
187        assert_eq!(result.unwrap(), LOLA_STRING_ONLY_EMPTY_TRANSITIONS_NET,);
188    }
189
190    #[test]
191    fn lola_string_net_with_chain_topology() {
192        let (net, _, _) = create_net_chain_topology(3);
193        let result = net.to_lola_string();
194
195        assert!(result.is_ok());
196        assert_eq!(result.unwrap(), LOLA_STRING_NET_WITH_CHAIN_TOPOLOPY);
197    }
198
199    #[test]
200    fn lola_string_net_with_loop_topology() {
201        let (net, _, _) = create_net_loop_topology();
202        let result = net.to_lola_string();
203
204        assert!(result.is_ok());
205        assert_eq!(result.unwrap(), LOLA_STRING_NET_WITH_LOOP_TOPOLOGY);
206    }
207}