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 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 for transition in 0..value.get_number_of_transitions() {
51 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 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 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 match aout {
131 Card::Zero => {
132 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 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 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 start_event
165 } else {
166 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 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 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 let mut sub_children_left = children_left.clone();
319 sub_children_left.remove(i);
320
321 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 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}