Skip to main content

ebi_objects/conversions/
to_business_process_model_and_notation.rs

1use std::collections::{HashMap, hash_map::Entry};
2
3use crate::{
4    DeterministicFiniteAutomaton, DirectlyFollowsGraph, DirectlyFollowsModel, LabelledPetriNet,
5    PetriNetMarkupLanguage, ProcessTree, ProcessTreeMarkupLanguage,
6    StochasticDeterministicFiniteAutomaton, StochasticDirectlyFollowsModel,
7    StochasticLabelledPetriNet, StochasticNondeterministicFiniteAutomaton, StochasticProcessTree,
8    ebi_objects::process_tree::{Node, Operator},
9};
10use ebi_arithmetic::anyhow::{Error, Result, anyhow};
11use ebi_bpmn::{
12    BPMNCreator, BusinessProcessModelAndNotation, Container, EndEventType, GatewayType, GlobalIndex, StartEventType
13};
14
15impl TryFrom<LabelledPetriNet> for BusinessProcessModelAndNotation {
16    type Error = Error;
17    fn try_from(value: LabelledPetriNet) -> Result<Self> {
18        let mut c = BPMNCreator::new_with_activity_key(value.activity_key.clone());
19        let parent = c.add_process(None);
20
21        //transform places to xor gateways
22        let mut place_2_xor_gateway = vec![];
23        for place_index in 0..value.get_number_of_places() {
24            let is_input_of_transition = value
25                .transition2input_places
26                .iter()
27                .zip(value.transition2input_places_cardinality.iter())
28                .any(|(x, y)| {
29                    x.iter()
30                        .zip(y.iter())
31                        .any(|(place, cardinality)| place_index == *place && cardinality > &0)
32                });
33
34            if is_input_of_transition {
35                place_2_xor_gateway.push(c.add_gateway(parent, GatewayType::Exclusive)?);
36            } else {
37                place_2_xor_gateway.push(c.add_end_event(parent, EndEventType::None)?);
38            }
39        }
40
41        let mut additional_start_elements = vec![];
42
43        enum Card {
44            Zero,
45            One,
46            Multiple,
47        }
48
49        //transform transitions to input and output elements
50        for transition in 0..value.get_number_of_transitions() {
51            //store whether there are multiple input or output arcs
52            let ain = if value.transition2input_places[transition].len() == 0 {
53                Card::Zero
54            } else if value.transition2input_places[transition].len() == 1
55                && value.transition2input_places_cardinality[transition][0] == 1
56            {
57                Card::One
58            } else {
59                Card::Multiple
60            };
61
62            let aout = if value.transition2output_places[transition].len() == 0 {
63                Card::Zero
64            } else if value.transition2output_places[transition].len() == 1
65                && value.transition2output_places_cardinality[transition][0] == 1
66            {
67                Card::One
68            } else {
69                Card::Multiple
70            };
71
72            // each transition is translated into an input and an output element
73            let (input_element, output_element) =
74                match (ain, value.get_transition_label(transition)) {
75                    (Card::Zero, None) => {
76                        let inp = c.add_gateway(parent, GatewayType::Exclusive)?;
77                        let out: (usize, ()) = c.add_gateway(parent, GatewayType::Parallel)?;
78                        c.add_sequence_flow(parent, inp, out)?;
79                        c.add_sequence_flow(parent, out, inp)?;
80                        additional_start_elements.push(inp);
81                        (inp, out)
82                    }
83                    (Card::Zero, Some(activity)) => {
84                        let inp = c.add_gateway(parent, GatewayType::Exclusive)?;
85                        let task = c.add_task(parent, activity)?;
86                        let out = c.add_gateway(parent, GatewayType::Parallel)?;
87                        c.add_sequence_flow(parent, inp, task)?;
88                        c.add_sequence_flow(parent, task, out)?;
89                        c.add_sequence_flow(parent, out, inp)?;
90                        additional_start_elements.push(inp);
91                        (inp, out)
92                    }
93                    (Card::One, None) => {
94                        let gateway = c.add_gateway(parent, GatewayType::Parallel)?;
95                        (gateway, gateway)
96                    }
97                    (Card::One, Some(activity)) => {
98                        let task = c.add_task(parent, activity)?;
99                        (task, task)
100                    }
101                    (Card::Multiple, None) => {
102                        let gateway = c.add_gateway(parent, GatewayType::Parallel)?;
103                        (gateway, gateway)
104                    }
105                    (Card::Multiple, Some(activity)) => {
106                        let inp = c.add_gateway(parent, GatewayType::Parallel)?;
107                        let out = c.add_task(parent, activity)?;
108                        c.add_sequence_flow(parent, inp, out)?;
109                        (inp, out)
110                    }
111                };
112
113            //connect incoming edges
114            for (input_place, cardinality) in value.transition2input_places[transition]
115                .iter()
116                .zip(value.transition2input_places_cardinality[transition].iter())
117            {
118                let input_gateway = place_2_xor_gateway[*input_place];
119                if cardinality > &1 {
120                    return Err(anyhow!(
121                        "Arc weights are not supported, as these would lead to deadlocks in the translated BPMN model."
122                    ));
123                }
124                if cardinality > &0 {
125                    c.add_sequence_flow(parent, input_gateway, input_element)?;
126                }
127            }
128
129            //connect outgoing edges
130            match aout {
131                Card::Zero => {
132                    //there no outgoing edges of this transition; add an end event
133                    let end_event = c.add_end_event(parent, EndEventType::None)?;
134                    c.add_sequence_flow(parent, output_element, end_event)?;
135                }
136                Card::One | Card::Multiple => {
137                    //there are outgoing edges; connect them
138                    for (output_place, cardinality) in value.transition2output_places[transition]
139                        .iter()
140                        .zip(value.transition2output_places_cardinality[transition].iter())
141                    {
142                        let output_gateway = place_2_xor_gateway[*output_place];
143                        if cardinality > &1 {
144                            return Err(anyhow!(
145                                "Arc weights are not supported, as these would lead to deadlocks in the translated BPMN model."
146                            ));
147                        }
148                        if cardinality > &0 {
149                            c.add_sequence_flow(parent, output_element, output_gateway)?;
150                        }
151                    }
152                }
153            }
154        }
155
156        //add start element(s)
157        let start_element = {
158            let start_event = c.add_start_event(parent, StartEventType::None)?;
159            if value.initial_marking.get_place2token().iter().sum::<u64>()
160                + additional_start_elements.len() as u64
161                == 1
162            {
163                //no additional gateway necessary
164                start_event
165            } else {
166                //add an and gateway to start the process
167                let and_gateway = c.add_gateway(parent, GatewayType::Parallel)?;
168                c.add_sequence_flow(parent, start_event, and_gateway)?;
169                and_gateway
170            }
171        };
172
173        //connect start element
174        for (place, cardinality) in value.initial_marking.get_place2token().iter().enumerate() {
175            if cardinality > &1 {
176                return Err(anyhow!(
177                    "Non-safe nets are not supported, as these would lead to deadlocks in the translated BPMN model."
178                ));
179            } else if cardinality > &0 {
180                let xor_gateway = place_2_xor_gateway[place];
181                c.add_sequence_flow(parent, start_element, xor_gateway)?;
182            }
183        }
184        for additional_start_element in additional_start_elements {
185            c.add_sequence_flow(parent, start_element, additional_start_element)?;
186        }
187
188        c.to_bpmn()
189    }
190}
191
192macro_rules! via_lpn {
193    ($t:ident) => {
194        impl TryFrom<$t> for BusinessProcessModelAndNotation {
195            type Error = Error;
196            fn try_from(value: $t) -> Result<Self> {
197                LabelledPetriNet::from(value).try_into()
198            }
199        }
200    };
201}
202
203via_lpn!(DeterministicFiniteAutomaton);
204via_lpn!(DirectlyFollowsGraph);
205via_lpn!(DirectlyFollowsModel);
206via_lpn!(StochasticDirectlyFollowsModel);
207via_lpn!(PetriNetMarkupLanguage);
208via_lpn!(StochasticLabelledPetriNet);
209via_lpn!(StochasticDeterministicFiniteAutomaton);
210via_lpn!(StochasticNondeterministicFiniteAutomaton);
211
212impl From<ProcessTree> for BusinessProcessModelAndNotation {
213    fn from(value: ProcessTree) -> Self {
214        let mut c = BPMNCreator::new_with_activity_key(value.activity_key.clone());
215        let parent = c.add_process(None);
216
217        let source = c.add_start_event_unchecked(parent, StartEventType::None);
218        let sink = c.add_end_event_unchecked(parent, EndEventType::None);
219        let root = value.root();
220        if !transform_node(&value, root, source, sink, &mut c, parent) {
221            //empty model
222            BPMNCreator::new().to_bpmn().unwrap()
223        } else {
224            c.to_bpmn().unwrap()
225        }
226    }
227}
228
229fn transform_node(
230    tree: &ProcessTree,
231    node: usize,
232    source: GlobalIndex,
233    sink: GlobalIndex,
234    c: &mut BPMNCreator,
235    parent: Container,
236) -> bool {
237    match tree.get_node(node) {
238        Some(Node::Tau) => {
239            c.add_sequence_flow_unchecked(parent, source, sink);
240            true
241        }
242        Some(Node::Activity(activity)) => {
243            let task = c.add_task_unchecked(parent, *activity);
244            c.add_sequence_flow_unchecked(parent, source, task);
245            c.add_sequence_flow_unchecked(parent, task, sink);
246            true
247        }
248        Some(Node::Operator(Operator::Concurrent, _)) => {
249            let sub_source = c.add_gateway_unchecked(parent, GatewayType::Parallel);
250            let sub_sink = c.add_gateway_unchecked(parent, GatewayType::Parallel);
251            c.add_sequence_flow_unchecked(parent, source, sub_source);
252            c.add_sequence_flow_unchecked(parent, sub_sink, sink);
253            for child in tree.get_children(node) {
254                transform_node(tree, child, sub_source, sub_sink, c, parent);
255            }
256            true
257        }
258        Some(Node::Operator(Operator::Xor, _)) => {
259            let sub_source = c.add_gateway_unchecked(parent, GatewayType::Exclusive);
260            let sub_sink = c.add_gateway_unchecked(parent, GatewayType::Exclusive);
261            c.add_sequence_flow_unchecked(parent, source, sub_source);
262            c.add_sequence_flow_unchecked(parent, sub_sink, sink);
263            for child in tree.get_children(node) {
264                transform_node(tree, child, sub_source, sub_sink, c, parent);
265            }
266            true
267        }
268        Some(Node::Operator(Operator::Or, _)) => {
269            let sub_source = c.add_gateway_unchecked(parent, GatewayType::Inclusive);
270            let sub_sink = c.add_gateway_unchecked(parent, GatewayType::Inclusive);
271            c.add_sequence_flow_unchecked(parent, source, sub_source);
272            c.add_sequence_flow_unchecked(parent, sub_sink, sink);
273            for child in tree.get_children(node) {
274                transform_node(tree, child, sub_source, sub_sink, c, parent);
275            }
276            true
277        }
278        Some(Node::Operator(Operator::Loop, _)) => {
279            let sub_source = c.add_gateway_unchecked(parent, GatewayType::Exclusive);
280            let sub_sink = c.add_gateway_unchecked(parent, GatewayType::Exclusive);
281            c.add_sequence_flow_unchecked(parent, source, sub_source);
282            c.add_sequence_flow_unchecked(parent, sub_sink, sink);
283            let mut it = tree.get_children(node);
284            if let Some(child) = it.next() {
285                transform_node(tree, child, sub_source, sub_sink, c, parent);
286            }
287            while let Some(child) = it.next() {
288                transform_node(tree, child, sub_sink, sub_source, c, parent);
289            }
290            true
291        }
292        Some(Node::Operator(Operator::Sequence, _)) => {
293            let mut it = tree.get_children(node);
294            let mut sub_sink = c.add_gateway_unchecked(parent, GatewayType::Exclusive);
295            if let Some(child) = it.next() {
296                transform_node(tree, child, source, sub_sink, c, parent);
297            }
298            while let Some(child) = it.next() {
299                let sub_source = sub_sink;
300                sub_sink = c.add_gateway_unchecked(parent, GatewayType::Exclusive);
301                transform_node(tree, child, sub_source, sub_sink, c, parent);
302            }
303            c.add_sequence_flow_unchecked(parent, sub_sink, sink);
304            true
305        }
306        Some(Node::Operator(Operator::Interleaved, _)) => {
307            let children = tree.get_children(node).collect::<Vec<_>>();
308            let mut children_left_2_gateway = HashMap::new();
309            children_left_2_gateway.insert(vec![], sink);
310            children_left_2_gateway.insert(children.clone(), source);
311
312            let mut queue = vec![];
313            queue.push(children);
314            while let Some(children_left) = queue.pop() {
315                let sub_source = *children_left_2_gateway.get(&children_left).unwrap();
316                for (i, child) in children_left.iter().enumerate() {
317                    //remove one child
318                    let mut sub_children_left = children_left.clone();
319                    sub_children_left.remove(i);
320
321                    //fetch a sub-sink or create a new one
322                    let sub_sink = match children_left_2_gateway.entry(sub_children_left.clone()) {
323                        Entry::Occupied(occupied_entry) => *occupied_entry.get(),
324                        Entry::Vacant(vacant_entry) => {
325                            //new, add to queue
326                            queue.push(sub_children_left);
327                            let sub_sink = c.add_gateway_unchecked(parent, GatewayType::Exclusive);
328                            vacant_entry.insert(sub_sink);
329                            sub_sink
330                        }
331                    };
332
333                    transform_node(tree, *child, sub_source, sub_sink, c, parent);
334                }
335            }
336            true
337        }
338        None => false,
339    }
340}
341
342impl From<StochasticProcessTree> for BusinessProcessModelAndNotation {
343    fn from(value: StochasticProcessTree) -> Self {
344        ProcessTree::from(value).into()
345    }
346}
347
348impl From<ProcessTreeMarkupLanguage> for BusinessProcessModelAndNotation {
349    fn from(value: ProcessTreeMarkupLanguage) -> Self {
350        ProcessTree::from(value).into()
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use crate::{ProcessTree, StochasticLabelledPetriNet};
357    use ebi_bpmn::BusinessProcessModelAndNotation;
358    use std::fs;
359
360    #[test]
361    fn slpn_2_bpmn() {
362        let fin = fs::read_to_string("testfiles/a-aa-bb.slpn").unwrap();
363        let slpn = fin.parse::<StochasticLabelledPetriNet>().unwrap();
364
365        let _bpmn = BusinessProcessModelAndNotation::try_from(slpn).unwrap();
366    }
367
368    #[test]
369    fn ptree_2_bpmn() {
370        let fin = fs::read_to_string("testfiles/empty_2.ptree").unwrap();
371        let slpn = fin.parse::<ProcessTree>().unwrap();
372
373        let _bpmn = BusinessProcessModelAndNotation::from(slpn);
374    }
375}