Skip to main content

ebi_objects/conversions/
to_labelled_petri_net.rs

1use crate::{
2    ActivityKeyTranslator, HasActivityKey, PetriNetMarkupLanguage,
3    StochasticNondeterministicFiniteAutomaton,
4    ebi_objects::{
5        deterministic_finite_automaton::DeterministicFiniteAutomaton,
6        directly_follows_graph::DirectlyFollowsGraph,
7        directly_follows_model::DirectlyFollowsModel,
8        labelled_petri_net::LabelledPetriNet,
9        lola_net::LolaNet,
10        process_tree::{Node, Operator, ProcessTree},
11        process_tree_markup_language::ProcessTreeMarkupLanguage,
12        stochastic_deterministic_finite_automaton::StochasticDeterministicFiniteAutomaton,
13        stochastic_directly_follows_model::StochasticDirectlyFollowsModel,
14        stochastic_labelled_petri_net::StochasticLabelledPetriNet,
15        stochastic_process_tree::StochasticProcessTree,
16    },
17    marking::Marking,
18};
19use ebi_arithmetic::anyhow::{Error, Result, anyhow};
20use std::collections::HashMap;
21
22macro_rules! tree {
23    ($t:ident) => {
24        impl From<$t> for LabelledPetriNet {
25            fn from(value: $t) -> Self {
26                log::info!("convert (stochastic) process tree to LPN");
27
28                if value.tree.is_empty() {
29                    return Self::new_empty_language();
30                }
31
32                let mut result = LabelledPetriNet::new();
33                let translator =
34                    ActivityKeyTranslator::new(value.activity_key(), result.activity_key_mut());
35                let source = result.add_place();
36                let sink = result.add_place();
37                result
38                    .get_initial_marking_mut()
39                    .increase(source, 1)
40                    .unwrap();
41
42                value
43                    .node_to_lpn(0, &mut result, &translator, source, sink)
44                    .unwrap();
45
46                result
47            }
48        }
49
50        impl $t {
51            pub fn node_to_lpn(
52                &self,
53                node: usize,
54                net: &mut LabelledPetriNet,
55                translator: &ActivityKeyTranslator,
56                source: usize,
57                sink: usize,
58            ) -> Result<usize> {
59                match self.tree[node] {
60                    Node::Tau => {
61                        let transition = net.add_transition(None);
62                        net.add_place_transition_arc(source, transition, 1)?;
63                        net.add_transition_place_arc(transition, sink, 1)?;
64                        Ok(node + 1)
65                    }
66                    Node::Activity(activity) => {
67                        let transition =
68                            net.add_transition(Some(translator.translate_activity(&activity)));
69                        net.add_place_transition_arc(source, transition, 1)?;
70                        net.add_transition_place_arc(transition, sink, 1)?;
71                        Ok(node + 1)
72                    }
73                    Node::Operator(Operator::Concurrent, number_of_children) => {
74                        let split = net.add_transition(None);
75                        net.add_place_transition_arc(source, split, 1)?;
76                        let join = net.add_transition(None);
77                        net.add_transition_place_arc(join, sink, 1)?;
78
79                        let mut child = node + 1;
80                        for _ in 0..number_of_children {
81                            let child_source = net.add_place();
82                            net.add_transition_place_arc(split, child_source, 1)?;
83                            let child_sink = net.add_place();
84                            net.add_place_transition_arc(child_sink, join, 1)?;
85                            child =
86                                self.node_to_lpn(child, net, translator, child_source, child_sink)?;
87                        }
88                        Ok(child)
89                    }
90                    Node::Operator(Operator::Interleaved, number_of_children) => {
91                        let split = net.add_transition(None);
92                        net.add_place_transition_arc(source, split, 1)?;
93                        let join = net.add_transition(None);
94                        net.add_transition_place_arc(join, sink, 1)?;
95                        let milestone = net.add_place();
96                        net.add_transition_place_arc(split, milestone, 1)?;
97                        net.add_place_transition_arc(milestone, join, 1)?;
98
99                        let mut child = node + 1;
100                        for _ in 0..number_of_children {
101                            let child_source = net.add_place();
102                            net.add_transition_place_arc(split, child_source, 1)?;
103
104                            let child_start = net.add_transition(None);
105                            net.add_place_transition_arc(child_source, child_start, 1)?;
106                            net.add_place_transition_arc(milestone, child_start, 1)?;
107
108                            let child_source_2 = net.add_place();
109                            net.add_transition_place_arc(child_start, child_source_2, 1)?;
110
111                            let child_sink = net.add_place();
112                            net.add_place_transition_arc(child_sink, join, 1)?;
113
114                            let child_stop = net.add_transition(None);
115                            net.add_transition_place_arc(child_stop, child_sink, 1)?;
116                            net.add_transition_place_arc(child_stop, milestone, 1)?;
117
118                            let child_sink_2 = net.add_place();
119                            net.add_place_transition_arc(child_sink_2, child_stop, 1)?;
120
121                            child = self.node_to_lpn(
122                                child,
123                                net,
124                                translator,
125                                child_source_2,
126                                child_sink_2,
127                            )?;
128                        }
129                        Ok(child)
130                    }
131                    Node::Operator(Operator::Loop, number_of_children) => {
132                        let start = net.add_transition(None);
133                        net.add_place_transition_arc(source, start, 1)?;
134
135                        let join = net.add_place();
136                        net.add_transition_place_arc(start, join, 1)?;
137
138                        let split = net.add_place();
139                        let stop = net.add_transition(None);
140                        net.add_place_transition_arc(split, stop, 1)?;
141                        net.add_transition_place_arc(stop, sink, 1)?;
142
143                        let mut child = node + 1;
144                        child = self.node_to_lpn(child, net, translator, join, split)?;
145
146                        if number_of_children > 1 {
147                            for _ in 1..number_of_children {
148                                child = self.node_to_lpn(child, net, translator, split, join)?;
149                            }
150                        } else {
151                            let redo = net.add_transition(None);
152                            net.add_place_transition_arc(split, redo, 1)?;
153                            net.add_transition_place_arc(redo, join, 1)?;
154                        }
155
156                        Ok(child)
157                    }
158                    Node::Operator(Operator::Or, number_of_children) => {
159                        let start = net.add_transition(None);
160                        net.add_place_transition_arc(source, start, 1)?;
161
162                        let not_done_first = net.add_place();
163                        net.add_transition_place_arc(start, not_done_first, 1)?;
164
165                        let done_first = net.add_place();
166                        let end = net.add_transition(None);
167                        net.add_place_transition_arc(done_first, end, 1)?;
168                        net.add_transition_place_arc(end, sink, 1)?;
169
170                        let mut child = node + 1;
171                        for _ in 0..number_of_children {
172                            let child_source = net.add_place();
173                            net.add_transition_place_arc(start, child_source, 1)?;
174                            let child_sink = net.add_place();
175                            net.add_place_transition_arc(child_sink, end, 1)?;
176                            let do_child = net.add_place();
177
178                            //skip
179                            let skip_child = net.add_transition(None);
180                            net.add_place_transition_arc(child_source, skip_child, 1)?;
181                            net.add_transition_place_arc(skip_child, child_sink, 1)?;
182                            net.add_transition_place_arc(skip_child, done_first, 1)?;
183                            net.add_place_transition_arc(done_first, skip_child, 1)?;
184
185                            //first do
186                            let first_do_child = net.add_transition(None);
187                            net.add_place_transition_arc(child_source, first_do_child, 1)?;
188                            net.add_place_transition_arc(not_done_first, first_do_child, 1)?;
189                            net.add_transition_place_arc(first_do_child, done_first, 1)?;
190                            net.add_transition_place_arc(first_do_child, do_child, 1)?;
191
192                            //later do
193                            let later_do_child = net.add_transition(None);
194                            net.add_place_transition_arc(child_source, later_do_child, 1)?;
195                            net.add_transition_place_arc(later_do_child, do_child, 1)?;
196                            net.add_transition_place_arc(later_do_child, done_first, 1)?;
197                            net.add_place_transition_arc(done_first, later_do_child, 1)?;
198
199                            child =
200                                self.node_to_lpn(child, net, translator, do_child, child_sink)?;
201                        }
202
203                        Ok(child)
204                    }
205                    Node::Operator(Operator::Sequence, number_of_children) => {
206                        let intermediate_nodes = (0..(number_of_children - 1))
207                            .map(|_| net.add_place())
208                            .collect::<Vec<_>>();
209
210                        let mut child = node + 1;
211                        for i in 0..number_of_children {
212                            let child_entry = if i == 0 {
213                                source
214                            } else {
215                                intermediate_nodes[i - 1]
216                            };
217                            let child_exit = if i == number_of_children - 1 {
218                                sink
219                            } else {
220                                intermediate_nodes[i]
221                            };
222
223                            child = $t::node_to_lpn(
224                                &self,
225                                child,
226                                net,
227                                translator,
228                                child_entry,
229                                child_exit,
230                            )?;
231                        }
232                        Ok(child)
233                    }
234                    Node::Operator(Operator::Xor, number_of_children) => {
235                        let mut child = node + 1;
236                        for _ in 0..number_of_children {
237                            child = $t::node_to_lpn(&self, child, net, translator, source, sink)?;
238                        }
239                        Ok(child)
240                    }
241                }
242            }
243        }
244    };
245}
246
247tree!(ProcessTree);
248tree!(StochasticProcessTree);
249
250impl From<ProcessTreeMarkupLanguage> for LabelledPetriNet {
251    fn from(value: ProcessTreeMarkupLanguage) -> Self {
252        log::info!("convert process tree markup language to LPN");
253        value.tree.into()
254    }
255}
256
257impl From<LolaNet> for LabelledPetriNet {
258    fn from(value: LolaNet) -> Self {
259        log::info!("convert Lola net to LPN");
260        value.0
261    }
262}
263
264impl From<PetriNetMarkupLanguage> for LabelledPetriNet {
265    fn from(value: PetriNetMarkupLanguage) -> Self {
266        log::info!("convert PNML to LPN");
267        value.0
268    }
269}
270
271macro_rules! dfm {
272    ($t:ident) => {
273        impl From<$t> for LabelledPetriNet {
274            fn from(value: $t) -> LabelledPetriNet {
275                log::info!("convert (S)DFM to LPN");
276
277                if value.node_2_activity.is_empty() && !value.has_empty_traces() {
278                    //SDFA has an empty language, return a livelocked SLPN
279                    return Self::new_empty_language();
280                }
281
282                let mut result = LabelledPetriNet::new();
283                let translator =
284                    ActivityKeyTranslator::new(value.activity_key(), result.activity_key_mut());
285                let source = result.add_place();
286                let sink = result.add_place();
287                result
288                    .get_initial_marking_mut()
289                    .increase(source, 1)
290                    .unwrap();
291
292                /*
293                 * empty traces
294                 */
295                if value.has_empty_traces() {
296                    let transition = result.add_transition(None);
297
298                    result
299                        .add_place_transition_arc(source, transition, 1)
300                        .unwrap();
301                    result
302                        .add_transition_place_arc(transition, sink, 1)
303                        .unwrap();
304                }
305
306                /*
307                 * Nodes (states): after doing a node you end up in the corresponding place.
308                 */
309                let mut node2place = vec![];
310                for _ in 0..value.node_2_activity.len() {
311                    let place = result.add_place();
312                    node2place.push(place);
313                }
314
315                /*
316                 * Transitions
317                 */
318                for (edge, (source_node, target_node)) in
319                    value.sources.iter().zip(value.targets.iter()).enumerate()
320                {
321                    if value.can_execute_edge(edge) {
322                        let from_place = node2place[*source_node];
323                        let to_place = node2place[*target_node];
324                        let activity =
325                            translator.translate_activity(&value.node_2_activity[*target_node]);
326                        let transition = result.add_transition(Some(activity));
327
328                        result
329                            .add_place_transition_arc(from_place, transition, 1)
330                            .unwrap();
331                        result
332                            .add_transition_place_arc(transition, to_place, 1)
333                            .unwrap();
334                    }
335                }
336
337                /*
338                 * Starts
339                 */
340                for node in 0..value.node_2_activity.len() {
341                    if value.is_start_node(node) {
342                        let activity = translator.translate_activity(&value.node_2_activity[node]);
343                        let transition = result.add_transition(Some(activity));
344                        result
345                            .add_place_transition_arc(source, transition, 1)
346                            .unwrap();
347                        let target_place = node2place[node];
348                        result
349                            .add_transition_place_arc(transition, target_place, 1)
350                            .unwrap();
351                    }
352                }
353
354                /*
355                 * Ends
356                 */
357                for node in 0..value.node_2_activity.len() {
358                    if value.is_end_node(node) {
359                        let transition = result.add_transition(None);
360                        let source_place = node2place[node];
361                        result
362                            .add_place_transition_arc(source_place, transition, 1)
363                            .unwrap();
364                        result
365                            .add_transition_place_arc(transition, sink, 1)
366                            .unwrap();
367                    }
368                }
369
370                result
371            }
372        }
373    };
374}
375
376dfm!(DirectlyFollowsModel);
377dfm!(StochasticDirectlyFollowsModel);
378
379impl From<StochasticLabelledPetriNet> for LabelledPetriNet {
380    fn from(value: StochasticLabelledPetriNet) -> Self {
381        log::info!("convert SLPN to LPN");
382        Self {
383            activity_key: value.activity_key,
384            initial_marking: value.initial_marking,
385            labels: value.labels,
386            place2output_transitions: value.place2output_transitions,
387            transition2input_places: value.transition2input_places,
388            transition2output_places: value.transition2output_places,
389            transition2input_places_cardinality: value.transition2input_places_cardinality,
390            transition2output_places_cardinality: value.transition2output_places_cardinality,
391        }
392    }
393}
394
395impl From<DeterministicFiniteAutomaton> for LabelledPetriNet {
396    fn from(value: DeterministicFiniteAutomaton) -> Self {
397        log::info!("convert DFA to LPN");
398
399        let source = if let Some(s) = value.initial_state {
400            s
401        } else {
402            //DFA has an empty language, return a livelocked LPN
403            return Self {
404                activity_key: value.activity_key,
405                initial_marking: Marking::new(0),
406                labels: vec![None],
407                transition2input_places: vec![vec![]],
408                transition2output_places: vec![vec![]],
409                transition2input_places_cardinality: vec![vec![]],
410                transition2output_places_cardinality: vec![vec![]],
411                place2output_transitions: vec![],
412            };
413        };
414
415        let mut result = LabelledPetriNet::new();
416        let translator =
417            ActivityKeyTranslator::new(value.activity_key(), result.activity_key_mut());
418
419        //add places
420        let mut state2place = vec![];
421        for state in 0..value.number_of_states() {
422            let lpn_place = result.add_place();
423            state2place.push(lpn_place);
424
425            //add termination
426            if value.can_terminate_in_state(state) {
427                let lpn_transition = result.add_transition(None);
428                result
429                    .add_place_transition_arc(lpn_place, lpn_transition, 1)
430                    .unwrap();
431            }
432        }
433
434        //initial marking
435        result
436            .get_initial_marking_mut()
437            .increase(source, 1)
438            .unwrap();
439
440        //add edges
441        for (source, (target, activity)) in value
442            .sources
443            .iter()
444            .zip(value.targets.iter().zip(value.activities.iter()))
445        {
446            //add transition
447            let lpn_activity = translator.translate_activity(activity);
448            let lpn_transition = result.add_transition(Some(lpn_activity));
449            let source_place = state2place[*source];
450            let target_place = state2place[*target];
451            result
452                .add_place_transition_arc(source_place, lpn_transition, 1)
453                .unwrap();
454            result
455                .add_transition_place_arc(lpn_transition, target_place, 1)
456                .unwrap();
457        }
458
459        result
460    }
461}
462
463impl TryFrom<process_mining::PetriNet> for LabelledPetriNet {
464    type Error = Error;
465
466    fn try_from(pnml: process_mining::PetriNet) -> Result<Self, Self::Error> {
467        log::info!("convert PNML to LPN");
468
469        let mut result = LabelledPetriNet::new();
470
471        //create map of places
472        let mut place2index = HashMap::new();
473        for (place_id, _) in pnml.places {
474            let place = result.add_place();
475            place2index.insert(place_id, place);
476        }
477
478        //transitions
479        let mut transition2index = HashMap::new();
480        for (transition_id, transition) in &pnml.transitions {
481            let label = match &transition.label {
482                Some(activity) => Some(result.activity_key_mut().process_activity(activity)),
483                None => None,
484            };
485            let transition = result.add_transition(label);
486
487            transition2index.insert(transition_id, transition);
488        }
489
490        //arcs
491        for arc in pnml.arcs.iter() {
492            match arc.from_to {
493                process_mining::core::process_models::petri_net::ArcType::PlaceTransition(
494                    place_id,
495                    transition_id,
496                ) => {
497                    let new_place = place2index
498                        .get(&place_id)
499                        .ok_or(anyhow!("Undeclared place referenced."))?;
500                    let new_transition = transition2index
501                        .get(&transition_id)
502                        .ok_or(anyhow!("undeclared transition referenced"))?;
503                    result.add_place_transition_arc(
504                        *new_place,
505                        *new_transition,
506                        arc.weight.into(),
507                    )?;
508                }
509                process_mining::core::process_models::petri_net::ArcType::TransitionPlace(
510                    transition_id,
511                    place_id,
512                ) => {
513                    let new_place = place2index
514                        .get(&place_id)
515                        .ok_or(anyhow!("Undeclared place referenced."))?;
516                    let new_transition = transition2index
517                        .get(&transition_id)
518                        .ok_or(anyhow!("undeclared transition referenced"))?;
519                    result.add_transition_place_arc(
520                        *new_transition,
521                        *new_place,
522                        arc.weight.into(),
523                    )?;
524                }
525            };
526        }
527
528        //initial marking
529        // for (place_id, cardinality) in pnml.net.initial_marking.as_ref().ok_or(anyhow!("The given net has no initial marking. Ebi requires an initial marking for its Petri nets."))?.iter() {
530        if let Some(marking) = pnml.initial_marking.as_ref() {
531            for (place_id, cardinality) in marking {
532                let new_place = place2index
533                    .get(&place_id.get_uuid())
534                    .ok_or(anyhow!("Undeclared place found in the initial marking."))?;
535                result
536                    .get_initial_marking_mut()
537                    .increase(*new_place, *cardinality)?;
538            }
539        }
540
541        //final markings
542        if let Some(final_markings) = &pnml.final_markings {
543            //The nets used by Ebi do not have final markings, as each of their deadlocks is taken as a final marking.
544            //The best we can do here is to verify that no non-deadlocks have been declared as final markings.
545            for final_marking in final_markings.iter() {
546                //transform to an Ebi-final marking
547                let mut new_final_marking = Marking::new(result.get_number_of_places());
548                for (place_id, cardinality) in final_marking.iter() {
549                    let new_place = place2index
550                        .get(&place_id.get_uuid())
551                        .ok_or(anyhow!("Undeclared place found."))?;
552                    new_final_marking.increase(*new_place, *cardinality)?;
553                }
554
555                //verify that this is a deadlock marking
556                for transition in 0..result.transition2input_places.len() {
557                    let mut marking = new_final_marking.clone();
558                    let mut enabled = true;
559                    for (in_place_pos, in_place) in result.transition2input_places[transition]
560                        .iter()
561                        .enumerate()
562                    {
563                        if marking.place2token[*in_place]
564                            < result.transition2input_places_cardinality[transition][in_place_pos]
565                        {
566                            //transition not enabled
567                            enabled = false;
568                            break;
569                        } else {
570                            //transition may be enabled
571                            marking.place2token[*in_place] -=
572                                result.transition2input_places_cardinality[transition][in_place_pos]
573                        }
574                    }
575
576                    if enabled {
577                        return Err(anyhow!(
578                            "This PNML file has a final marking that is not a deadlock. In Ebi, each final marking must be a deadlock. This final marking is {:?}",
579                            final_marking
580                        ));
581                    }
582                }
583            }
584        }
585
586        Ok(result)
587    }
588}
589
590impl From<StochasticDeterministicFiniteAutomaton> for LabelledPetriNet {
591    fn from(value: StochasticDeterministicFiniteAutomaton) -> Self {
592        let dfa: DeterministicFiniteAutomaton = value.into();
593        dfa.into()
594    }
595}
596
597impl From<StochasticNondeterministicFiniteAutomaton> for LabelledPetriNet {
598    fn from(value: StochasticNondeterministicFiniteAutomaton) -> Self {
599        let slpn = StochasticLabelledPetriNet::from(value);
600        slpn.into()
601    }
602}
603
604impl From<DirectlyFollowsGraph> for LabelledPetriNet {
605    fn from(value: DirectlyFollowsGraph) -> Self {
606        <DirectlyFollowsGraph as Into<DirectlyFollowsModel>>::into(value).into()
607    }
608}
609
610#[cfg(test)]
611mod tests {
612    use std::fs;
613
614    use crate::{
615        StochasticDeterministicFiniteAutomaton,
616        ebi_objects::{
617            deterministic_finite_automaton::DeterministicFiniteAutomaton,
618            labelled_petri_net::LabelledPetriNet,
619        },
620    };
621
622    #[test]
623    fn dfa_to_lpn() {
624        let fin = fs::read_to_string("testfiles/a-loop.dfa").unwrap();
625        let dfa = fin.parse::<DeterministicFiniteAutomaton>().unwrap();
626        let lpn: LabelledPetriNet = dfa.into();
627
628        let fin2 = fs::read_to_string("testfiles/a-loop.lpn").unwrap();
629        let lpn2 = fin2.parse::<LabelledPetriNet>().unwrap();
630
631        assert_eq!(lpn.to_string(), lpn2.to_string());
632    }
633
634    #[test]
635    fn sdfa_dfa_lpn() {
636        let fin = fs::read_to_string("testfiles/a-livelock-zeroweight.sdfa").unwrap();
637        let sdfa = fin
638            .parse::<StochasticDeterministicFiniteAutomaton>()
639            .unwrap();
640        let _lpn: LabelledPetriNet = sdfa.into();
641    }
642}