Skip to main content

ebi_objects/conversions/
to_deterministic_finite_automaton.rs

1use crate::{
2    ActivityKey, ActivityKeyTranslator, CompressedEventLog, CompressedEventLogXes,
3    DirectlyFollowsModel, EventLogCsv, EventLogTraceAttributes, EventLogXes, NumberOfTraces,
4    ProcessTree, ProcessTreeMarkupLanguage, StochasticDirectlyFollowsModel,
5    StochasticNondeterministicFiniteAutomaton, StochasticProcessTree,
6    activity_key::has_activity_key::HasActivityKey,
7    ebi_objects::{
8        compressed_event_log_trace_attributes::CompressedEventLogTraceAttributes,
9        deterministic_finite_automaton::DeterministicFiniteAutomaton,
10        event_log::EventLog,
11        event_log_python::EventLogPython,
12        finite_language::FiniteLanguage,
13        finite_stochastic_language::FiniteStochasticLanguage,
14        process_tree::{
15            execute_transition, get_enabled_transitions, get_initial_state,
16            get_transition_activity, is_final_state,
17        },
18        stochastic_deterministic_finite_automaton::StochasticDeterministicFiniteAutomaton,
19    },
20};
21use ebi_arithmetic::ebi_number::{Signed, Zero};
22use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
23
24impl From<FiniteLanguage> for DeterministicFiniteAutomaton {
25    fn from(value: FiniteLanguage) -> Self {
26        log::info!("convert finite language into a DFA");
27        let mut result = DeterministicFiniteAutomaton::new();
28        result.set_activity_key(value.activity_key().clone());
29
30        if value.number_of_traces().is_zero() {
31            result.set_initial_state(None);
32        } else {
33            for trace in value.traces.iter() {
34                let mut state = result.initial_state.unwrap();
35
36                for activity in trace {
37                    state = result.take_or_add_transition(state, *activity);
38                }
39
40                result.set_final_state(state, true);
41            }
42        }
43
44        result
45    }
46}
47
48impl From<StochasticDeterministicFiniteAutomaton> for DeterministicFiniteAutomaton {
49    fn from(value: StochasticDeterministicFiniteAutomaton) -> Self {
50        log::info!("convert SDFA into DFA");
51        let final_states = value
52            .terminating_probabilities
53            .iter()
54            .map(|p| p.is_positive())
55            .collect();
56
57        Self {
58            activity_key: value.activity_key,
59            initial_state: value.initial_state,
60            sources: value.sources,
61            targets: value.targets,
62            activities: value.activities,
63            final_states: final_states,
64        }
65    }
66}
67
68impl From<DirectlyFollowsModel> for DeterministicFiniteAutomaton {
69    fn from(value: DirectlyFollowsModel) -> Self {
70        log::info!("convert DFM into DFA");
71
72        if value.node_2_activity.is_empty() && !value.has_empty_traces() {
73            //empty language
74            return Self {
75                activity_key: ActivityKey::new(),
76                initial_state: None,
77                sources: vec![],
78                targets: vec![],
79                activities: vec![],
80                final_states: vec![],
81            };
82        }
83
84        let number_of_nodes = value.node_2_activity.len();
85
86        //copy the edges, as they did not change; just make the labels explicit
87        let DirectlyFollowsModel {
88            sources,
89            targets,
90            node_2_activity,
91            empty_traces,
92            start_nodes,
93            end_nodes,
94            ..
95        } = value;
96        let activities = targets
97            .iter()
98            .map(|target| node_2_activity[*target])
99            .collect();
100
101        //prepare final states: initial state is final if empty traces are present
102        let mut final_states = end_nodes;
103        final_states.push(empty_traces);
104
105        //construct result
106        let mut result = Self {
107            activity_key: value.activity_key,
108            initial_state: Some(number_of_nodes),
109            sources,
110            targets,
111            activities,
112            final_states,
113        };
114
115        //add the start activities
116        for start_node in 0..number_of_nodes {
117            if start_nodes[start_node] {
118                result
119                    .add_transition(number_of_nodes, node_2_activity[start_node], start_node)
120                    .unwrap(); //by construction, determinism is guaranteed
121            }
122        }
123
124        result
125    }
126}
127
128impl From<StochasticDirectlyFollowsModel> for DeterministicFiniteAutomaton {
129    fn from(value: StochasticDirectlyFollowsModel) -> Self {
130        Into::<DirectlyFollowsModel>::into(value).into()
131    }
132}
133
134impl From<FiniteStochasticLanguage> for DeterministicFiniteAutomaton {
135    fn from(value: FiniteStochasticLanguage) -> Self {
136        Into::<FiniteLanguage>::into(value).into()
137    }
138}
139
140macro_rules! via_lang {
141    ($t:ident) => {
142        impl From<$t> for DeterministicFiniteAutomaton {
143            fn from(value: $t) -> Self {
144                Into::<FiniteLanguage>::into(value).into()
145            }
146        }
147    };
148}
149
150via_lang!(EventLog);
151via_lang!(EventLogTraceAttributes);
152via_lang!(EventLogCsv);
153via_lang!(EventLogXes);
154via_lang!(EventLogPython);
155via_lang!(CompressedEventLog);
156via_lang!(CompressedEventLogTraceAttributes);
157via_lang!(CompressedEventLogXes);
158
159impl From<ProcessTree> for DeterministicFiniteAutomaton {
160    fn from(tree: ProcessTree) -> Self {
161        log::info!("convert process tree to DFA");
162
163        let initial_marking = if let Some(marking) = get_initial_state(&tree) {
164            marking
165        } else {
166            //empty language
167            return Self {
168                activity_key: ActivityKey::new(),
169                initial_state: None,
170                sources: vec![],
171                targets: vec![],
172                activities: vec![],
173                final_states: vec![],
174            };
175        };
176
177        let mut result = DeterministicFiniteAutomaton::new();
178        let translator = ActivityKeyTranslator::new(tree.activity_key(), result.activity_key_mut());
179
180        let mut markingset2node = HashMap::new();
181
182        //initial state
183        let source_node = result.initial_state.unwrap(); //exists by contract of new()
184        let initial_markingset = BTreeSet::from([initial_marking]);
185        markingset2node.insert(initial_markingset.clone(), source_node);
186        let mut queue = VecDeque::new();
187        queue.push_back(initial_markingset);
188
189        while let Some(markingset) = queue.pop_front() {
190            let node = markingset2node[&markingset];
191
192            //gather activity -> marking set (for each activity, the set of markings that can be reached with that activity)
193            let mut activity2markings = HashMap::new();
194            {
195                let mut inner_queue = VecDeque::new();
196                let mut inner_visited = HashSet::new();
197                inner_queue.extend(markingset.clone());
198                inner_visited.extend(markingset);
199
200                //walk through all markings
201                while let Some(marking) = inner_queue.pop_front() {
202                    if is_final_state(&tree, &marking) {
203                        result.set_final_state(node, true);
204                    } else {
205                        for transition in get_enabled_transitions(&tree, &marking) {
206                            let mut target_marking = marking.clone();
207                            execute_transition(&tree, &mut target_marking, transition).unwrap(); //by construction, transition is enabled
208
209                            if let Some(activity) = get_transition_activity(&tree, transition) {
210                                //labelled activity: insert into resulting map
211                                activity2markings
212                                    .entry(translator.translate_activity(&activity))
213                                    .or_insert_with(|| BTreeSet::new())
214                                    .insert(target_marking);
215                            } else {
216                                //silent step: add to queue
217                                if inner_visited.insert(target_marking.clone()) {
218                                    inner_queue.push_back(target_marking);
219                                }
220                            }
221                        }
222                    }
223                }
224            }
225
226            //add the activity -> marking set mapping to the automaton
227            for (activity, markingset) in activity2markings {
228                let target_node = markingset2node
229                    .entry(markingset.clone())
230                    .or_insert_with(|| {
231                        queue.push_back(markingset);
232                        result.add_state()
233                    });
234
235                result.add_transition(node, activity, *target_node).unwrap(); //by construction, this should not fail.
236            }
237        }
238
239        result
240    }
241}
242
243macro_rules! via_tree {
244    ($t:ident) => {
245        impl From<$t> for DeterministicFiniteAutomaton {
246            fn from(value: $t) -> Self {
247                let tree = ProcessTree::from(value);
248                tree.into()
249            }
250        }
251    };
252}
253
254via_tree!(StochasticProcessTree);
255via_tree!(ProcessTreeMarkupLanguage);
256
257impl From<StochasticNondeterministicFiniteAutomaton> for DeterministicFiniteAutomaton {
258    fn from(snfa: StochasticNondeterministicFiniteAutomaton) -> Self {
259        log::info!("convert SNFA to DFA");
260
261        let initial_marking = if let Some(marking) = snfa.initial_state {
262            marking
263        } else {
264            //empty language
265            return Self {
266                activity_key: ActivityKey::new(),
267                initial_state: None,
268                sources: vec![],
269                targets: vec![],
270                activities: vec![],
271                final_states: vec![],
272            };
273        };
274
275        let mut result = DeterministicFiniteAutomaton::new();
276        let translator = ActivityKeyTranslator::new(snfa.activity_key(), result.activity_key_mut());
277
278        let mut markingset2node = HashMap::new();
279
280        //initial state
281        let source_node = result.initial_state.unwrap(); //exists by contract of new()
282        let initial_markingset = BTreeSet::from([initial_marking]);
283        markingset2node.insert(initial_markingset.clone(), source_node);
284        let mut queue = VecDeque::new();
285        queue.push_back(initial_markingset);
286
287        while let Some(markingset) = queue.pop_front() {
288            let node = markingset2node[&markingset];
289
290            //gather activity -> marking set (for each activity, the set of markings that can be reached with that activity)
291            let mut activity2markings = HashMap::new();
292            {
293                let mut inner_queue = VecDeque::new();
294                let mut inner_visited = HashSet::new();
295                inner_queue.extend(markingset.clone());
296                inner_visited.extend(markingset);
297
298                //walk through all markings
299                while let Some(marking) = inner_queue.pop_front() {
300                    if snfa.get_termination_probability(marking).is_positive() {
301                        result.set_final_state(node, true);
302                    } else {
303                        for (_, target_marking, label, _) in snfa.outgoing_edges(marking) {
304                            if let Some(activity) = label {
305                                //labelled activity: insert into resulting map
306                                activity2markings
307                                    .entry(translator.translate_activity(&activity))
308                                    .or_insert_with(|| BTreeSet::new())
309                                    .insert(*target_marking);
310                            } else {
311                                //silent step: add to queue
312                                if inner_visited.insert(target_marking.clone()) {
313                                    inner_queue.push_back(*target_marking);
314                                }
315                            }
316                        }
317                    }
318                }
319            }
320
321            //add the activity -> marking set mapping to the automaton
322            for (activity, markingset) in activity2markings {
323                let target_node = markingset2node
324                    .entry(markingset.clone())
325                    .or_insert_with(|| {
326                        queue.push_back(markingset);
327                        result.add_state()
328                    });
329
330                result.add_transition(node, activity, *target_node).unwrap(); //by construction, this should not fail.
331            }
332        }
333
334        result
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use crate::{
341        DeterministicFiniteAutomaton, ProcessTree, StochasticDirectlyFollowsModel,
342        StochasticNondeterministicFiniteAutomaton, StochasticProcessTree,
343    };
344    use std::fs;
345
346    #[test]
347    fn tree_to_dfa() {
348        let fin = fs::read_to_string("testfiles/seq(a-xor(b-c)).sptree").unwrap();
349        let tree: ProcessTree = fin.parse::<StochasticProcessTree>().unwrap().into();
350
351        let dfa = DeterministicFiniteAutomaton::from(tree);
352        assert_eq!(
353            format!("{}", dfa),
354            "{
355\"initialState\": 0,
356\"transitions\": [
357{\"from\":0,\"to\":1,\"label\":\"a\"},
358{\"from\":1,\"to\":2,\"label\":\"b\"},
359{\"from\":1,\"to\":2,\"label\":\"c\"}
360], \"finalStates\": [
3612
362]}
363"
364        );
365    }
366
367    #[test]
368    fn snfa_to_dfa() {
369        let fin = fs::read_to_string("testfiles/aa-ab-ba.snfa").unwrap();
370        let snfa = fin
371            .parse::<StochasticNondeterministicFiniteAutomaton>()
372            .unwrap();
373
374        let dfa = DeterministicFiniteAutomaton::from(snfa);
375
376        assert_eq!(dfa.number_of_states(), 4);
377        assert_eq!(dfa.get_sources().len(), 5);
378    }
379
380    #[test]
381    fn dfm_to_dfa() {
382        let fin = fs::read_to_string("testfiles/aa-ab-ba.sdfm").unwrap();
383        let sdfm = fin.parse::<StochasticDirectlyFollowsModel>().unwrap();
384
385        let dfa = DeterministicFiniteAutomaton::from(sdfm);
386
387        assert_eq!(dfa.number_of_states(), 3);
388        assert_eq!(dfa.sources.len(), 5);
389    }
390}