libpetri_runtime/
compiled_net.rs1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use libpetri_core::input::{In, required_count};
5use libpetri_core::petri_net::PetriNet;
6use libpetri_core::place::PlaceRef;
7use libpetri_core::transition::Transition;
8
9use crate::bitmap;
10
11#[derive(Debug, Clone)]
13pub struct CardinalityCheck {
14 pub place_ids: Vec<usize>,
15 pub required_counts: Vec<usize>,
16}
17
18#[derive(Debug, Clone)]
23pub struct CompiledNet {
24 net: PetriNet,
25 pub(crate) place_count: usize,
26 pub(crate) transition_count: usize,
27 pub(crate) word_count: usize,
28
29 places_by_id: Vec<PlaceRef>,
31 transitions_by_id: Vec<usize>, place_index: HashMap<Arc<str>, usize>,
33 #[allow(dead_code)]
34 transition_index: HashMap<usize, usize>, needs_masks: Vec<u64>,
38 inhibitor_masks: Vec<u64>,
39
40 place_to_transitions: Vec<Vec<usize>>,
42
43 consumption_place_ids: Vec<Vec<usize>>,
45
46 cardinality_checks: Vec<Option<CardinalityCheck>>,
48 has_guards: Vec<bool>,
49}
50
51impl CompiledNet {
52 pub fn compile(net: &PetriNet) -> Self {
54 let mut place_names: Vec<Arc<str>> = net
56 .places()
57 .iter()
58 .map(|p| Arc::clone(p.name_arc()))
59 .collect();
60 place_names.sort();
61 place_names.dedup();
62
63 let place_count = place_names.len();
64 let word_count = bitmap::word_count(place_count);
65
66 let mut place_index = HashMap::new();
68 let mut places_by_id = Vec::with_capacity(place_count);
69 for (i, name) in place_names.iter().enumerate() {
70 place_index.insert(Arc::clone(name), i);
71 places_by_id.push(PlaceRef::new(Arc::clone(name)));
72 }
73
74 let transition_count = net.transitions().len();
75 let mut transitions_by_id = Vec::with_capacity(transition_count);
76 let mut transition_index = HashMap::new();
77
78 let mut needs_masks = vec![0u64; transition_count * word_count];
79 let mut inhibitor_masks = vec![0u64; transition_count * word_count];
80 let mut consumption_place_ids = Vec::with_capacity(transition_count);
81 let mut cardinality_checks: Vec<Option<CardinalityCheck>> = vec![None; transition_count];
82 let mut has_guards = vec![false; transition_count];
83
84 let mut place_to_transitions_tmp: Vec<HashSet<usize>> = vec![HashSet::new(); place_count];
85
86 for (net_idx, t) in net.transitions().iter().enumerate() {
87 let tid = net_idx;
88 transitions_by_id.push(net_idx);
89 transition_index.insert(net_idx, tid);
90
91 let mask_base = tid * word_count;
92 let mut needs_cardinality = false;
93
94 for in_spec in t.input_specs() {
96 let pid = place_index[in_spec.place().name_arc()];
97 if word_count > 0 {
98 bitmap::set_bit(&mut needs_masks[mask_base..mask_base + word_count], pid);
99 }
100 place_to_transitions_tmp[pid].insert(tid);
101
102 if !matches!(in_spec, In::One { .. }) {
103 needs_cardinality = true;
104 }
105 if in_spec.has_guard() {
106 has_guards[tid] = true;
107 }
108 }
109
110 if needs_cardinality {
112 let mut pids = Vec::new();
113 let mut reqs = Vec::new();
114 for in_spec in t.input_specs() {
115 pids.push(place_index[in_spec.place().name_arc()]);
116 reqs.push(required_count(in_spec));
117 }
118 cardinality_checks[tid] = Some(CardinalityCheck {
119 place_ids: pids,
120 required_counts: reqs,
121 });
122 }
123
124 for r in t.reads() {
126 let pid = place_index[r.place.name_arc()];
127 if word_count > 0 {
128 bitmap::set_bit(&mut needs_masks[mask_base..mask_base + word_count], pid);
129 }
130 place_to_transitions_tmp[pid].insert(tid);
131 }
132
133 for inh in t.inhibitors() {
135 let pid = place_index[inh.place.name_arc()];
136 if word_count > 0 {
137 bitmap::set_bit(&mut inhibitor_masks[mask_base..mask_base + word_count], pid);
138 }
139 place_to_transitions_tmp[pid].insert(tid);
140 }
141
142 for r in t.resets() {
144 let pid = place_index[r.place.name_arc()];
145 place_to_transitions_tmp[pid].insert(tid);
146 }
147
148 let mut consumption_set = HashSet::new();
150 for spec in t.input_specs() {
151 consumption_set.insert(place_index[spec.place().name_arc()]);
152 }
153 for r in t.resets() {
154 consumption_set.insert(place_index[r.place.name_arc()]);
155 }
156 consumption_place_ids.push(consumption_set.into_iter().collect());
157 }
158
159 let place_to_transitions: Vec<Vec<usize>> = place_to_transitions_tmp
160 .into_iter()
161 .map(|s| s.into_iter().collect())
162 .collect();
163
164 CompiledNet {
165 net: net.clone(),
166 place_count,
167 transition_count,
168 word_count,
169 places_by_id,
170 transitions_by_id,
171 place_index,
172 transition_index,
173 needs_masks,
174 inhibitor_masks,
175 place_to_transitions,
176 consumption_place_ids,
177 cardinality_checks,
178 has_guards,
179 }
180 }
181
182 pub fn net(&self) -> &PetriNet {
186 &self.net
187 }
188
189 pub fn place(&self, pid: usize) -> &PlaceRef {
191 &self.places_by_id[pid]
192 }
193
194 pub fn transition(&self, tid: usize) -> &Transition {
196 &self.net.transitions()[self.transitions_by_id[tid]]
197 }
198
199 pub fn place_id(&self, name: &str) -> Option<usize> {
201 self.place_index.get(name).copied()
202 }
203
204 pub fn place_id_or_panic(&self, name: &str) -> usize {
206 self.place_index
207 .get(name)
208 .copied()
209 .unwrap_or_else(|| panic!("Unknown place: {name}"))
210 }
211
212 pub fn affected_transitions(&self, pid: usize) -> &[usize] {
214 &self.place_to_transitions[pid]
215 }
216
217 pub fn consumption_place_ids(&self, tid: usize) -> &[usize] {
219 &self.consumption_place_ids[tid]
220 }
221
222 pub fn cardinality_check(&self, tid: usize) -> Option<&CardinalityCheck> {
224 self.cardinality_checks[tid].as_ref()
225 }
226
227 pub fn has_guards(&self, tid: usize) -> bool {
229 self.has_guards[tid]
230 }
231
232 pub fn needs_mask(&self, tid: usize) -> &[u64] {
234 let base = tid * self.word_count;
235 &self.needs_masks[base..base + self.word_count]
236 }
237
238 pub fn inhibitor_mask(&self, tid: usize) -> &[u64] {
240 let base = tid * self.word_count;
241 &self.inhibitor_masks[base..base + self.word_count]
242 }
243
244 pub fn can_enable_bitmap(&self, tid: usize, marking_snapshot: &[u64]) -> bool {
253 let needs = self.needs_mask(tid);
254 let inhibitors = self.inhibitor_mask(tid);
255
256 bitmap::contains_all(marking_snapshot, needs)
257 && !bitmap::intersects(marking_snapshot, inhibitors)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use libpetri_core::input::one;
265 use libpetri_core::output::out_place;
266 use libpetri_core::place::Place;
267
268 fn simple_chain_net() -> PetriNet {
269 let p1 = Place::<i32>::new("p1");
270 let p2 = Place::<i32>::new("p2");
271 let p3 = Place::<i32>::new("p3");
272
273 let t1 = Transition::builder("t1")
274 .input(one(&p1))
275 .output(out_place(&p2))
276 .build();
277 let t2 = Transition::builder("t2")
278 .input(one(&p2))
279 .output(out_place(&p3))
280 .build();
281
282 PetriNet::builder("chain").transitions([t1, t2]).build()
283 }
284
285 #[test]
286 fn compile_basic() {
287 let net = simple_chain_net();
288 let compiled = CompiledNet::compile(&net);
289
290 assert_eq!(compiled.place_count, 3);
291 assert_eq!(compiled.transition_count, 2);
292 assert!(compiled.word_count >= 1);
293 }
294
295 #[test]
296 fn place_id_lookup() {
297 let net = simple_chain_net();
298 let compiled = CompiledNet::compile(&net);
299
300 assert!(compiled.place_id("p1").is_some());
301 assert!(compiled.place_id("p2").is_some());
302 assert!(compiled.place_id("p3").is_some());
303 assert!(compiled.place_id("nonexistent").is_none());
304 }
305
306 #[test]
307 fn bitmap_enablement() {
308 let net = simple_chain_net();
309 let compiled = CompiledNet::compile(&net);
310
311 let mut snapshot = vec![0u64; compiled.word_count];
312
313 assert!(!compiled.can_enable_bitmap(0, &snapshot));
315 assert!(!compiled.can_enable_bitmap(1, &snapshot));
316
317 let p1_id = compiled.place_id("p1").unwrap();
319 bitmap::set_bit(&mut snapshot, p1_id);
320 assert!(compiled.can_enable_bitmap(0, &snapshot));
321 assert!(!compiled.can_enable_bitmap(1, &snapshot));
322
323 let p2_id = compiled.place_id("p2").unwrap();
325 bitmap::set_bit(&mut snapshot, p2_id);
326 assert!(compiled.can_enable_bitmap(0, &snapshot));
327 assert!(compiled.can_enable_bitmap(1, &snapshot));
328 }
329
330 #[test]
331 fn inhibitor_blocks_transition() {
332 let p1 = Place::<i32>::new("p1");
333 let p2 = Place::<i32>::new("p2");
334 let p_inh = Place::<i32>::new("inh");
335
336 let t = Transition::builder("t1")
337 .input(one(&p1))
338 .output(out_place(&p2))
339 .inhibitor(libpetri_core::arc::inhibitor(&p_inh))
340 .build();
341
342 let net = PetriNet::builder("test").transition(t).build();
343 let compiled = CompiledNet::compile(&net);
344
345 let mut snapshot = vec![0u64; compiled.word_count];
346 let p1_id = compiled.place_id("p1").unwrap();
347 let inh_id = compiled.place_id("inh").unwrap();
348
349 bitmap::set_bit(&mut snapshot, p1_id);
350 assert!(compiled.can_enable_bitmap(0, &snapshot));
351
352 bitmap::set_bit(&mut snapshot, inh_id);
354 assert!(!compiled.can_enable_bitmap(0, &snapshot));
355 }
356
357 #[test]
358 fn reverse_index() {
359 let net = simple_chain_net();
360 let compiled = CompiledNet::compile(&net);
361
362 let p2_id = compiled.place_id("p2").unwrap();
364 let affected = compiled.affected_transitions(p2_id);
365 assert!(affected.contains(&1) || affected.contains(&0));
367 }
368}