1use crate::{
2 CompressedEventLog, CompressedEventLogXes, DirectlyFollowsGraph, EventLogCsv,
3 EventLogTraceAttributes, EventLogXes, HasActivityKey, NumberOfTraces,
4 ebi_objects::{
5 compressed_event_log_trace_attributes::CompressedEventLogTraceAttributes,
6 event_log::EventLog, event_log_python::EventLogPython,
7 finite_stochastic_language::FiniteStochasticLanguage,
8 stochastic_deterministic_finite_automaton::StochasticDeterministicFiniteAutomaton,
9 },
10 traits::trace_iterators::{IntoRefTraceIterator, IntoTraceIterator},
11};
12use ebi_arithmetic::{Fraction, One, Signed, Zero};
13use std::collections::{HashMap, hash_map::Entry};
14
15impl From<FiniteStochasticLanguage> for StochasticDeterministicFiniteAutomaton {
16 fn from(value: FiniteStochasticLanguage) -> Self {
17 log::info!("convert finite stochastic language to SDFA");
18
19 let mut result = StochasticDeterministicFiniteAutomaton::new();
20 if value.number_of_traces().is_zero() {
21 result.set_initial_state(None);
22 } else {
23 let mut final_states = HashMap::new();
24 result.activity_key = value.activity_key.clone();
25
26 for (trace, probability) in &value.traces {
28 let mut state = result.get_initial_state().unwrap();
29 for activity in trace {
30 state = result.take_or_add_transition(state, *activity, probability.clone());
31 }
32
33 match final_states.entry(state) {
34 Entry::Occupied(mut e) => *e.get_mut() += probability,
35 Entry::Vacant(e) => {
36 e.insert(probability.clone());
37 }
38 }
39 }
40
41 let mut sums = final_states;
43 for (source, _, _, probability) in &result {
44 match sums.entry(*source) {
45 Entry::Occupied(mut e) => *e.get_mut() += probability,
46 Entry::Vacant(e) => {
47 e.insert(probability.clone());
48 }
49 }
50 }
51
52 result.scale_outgoing_probabilities(sums);
54 }
55 result
56 }
57}
58
59impl From<DirectlyFollowsGraph> for StochasticDeterministicFiniteAutomaton {
60 fn from(value: DirectlyFollowsGraph) -> Self {
61 let mut result = Self::new();
62 let initial_state = 0;
63
64 for _ in value.activity_key().get_activities() {
66 result.add_state(); }
68
69 {
71 let mut sum_of_start_activities = value
72 .start_activities
73 .iter()
74 .map(|(_, f)| f)
75 .sum::<Fraction>();
76 sum_of_start_activities += &value.empty_traces_weight;
77 for (activity, weight) in &value.start_activities {
78 result
79 .add_transition(
80 initial_state,
81 *activity,
82 activity.id + 1,
83 weight / &sum_of_start_activities,
84 )
85 .unwrap(); }
87
88 }
90
91 {
93 for activity in value.activity_key().get_activities() {
94 let mut sum = Fraction::zero();
96 {
97 let (_, mut i) =
98 value.binary_search(*activity, value.activity_key().get_activity_by_id(0));
99 while i < value.sources.len() && &value.sources[i] == activity {
100 if value.weights[i].is_positive() {
101 sum += &value.weights[i];
102 }
103 i += 1;
104 }
105
106 if let Some(w) = value.end_activities.get(activity)
107 && w.is_positive()
108 {
109 sum += w;
110 }
111 }
112
113 let (_, mut i) =
115 value.binary_search(*activity, value.activity_key().get_activity_by_id(0));
116 while i < value.sources.len() && &value.sources[i] == activity {
117 if value.weights[i].is_positive() {
118 result
119 .add_transition(
120 activity.id + 1,
121 value.targets[i],
122 value.targets[i].id + 1,
123 &value.weights[i] / &sum,
124 )
125 .unwrap(); }
127 i += 1;
128 }
129
130 }
132 }
133
134 result.activity_key = value.activity_key;
135
136 result
137 }
138}
139
140macro_rules! log {
141 ($t: ident) => {
142 impl From<$t> for StochasticDeterministicFiniteAutomaton {
143 fn from(value: $t) -> Self {
144 log::info!("convert event log to SDFA");
145
146 let mut result = StochasticDeterministicFiniteAutomaton::new();
147 result.activity_key = value.activity_key().clone();
148
149 if value.number_of_traces().is_zero() {
150 result.set_initial_state(None);
151 } else {
152 let mut final_states = HashMap::new();
153
154 for trace in value.iter_traces() {
156 let mut state = result.get_initial_state().unwrap();
157
158 for activity in trace {
159 state = result.take_or_add_transition(
160 state,
161 activity.clone(),
162 Fraction::one(),
163 );
164 }
165
166 match final_states.entry(state) {
167 std::collections::hash_map::Entry::Occupied(mut e) => {
168 *e.get_mut() += Fraction::one()
169 }
170 std::collections::hash_map::Entry::Vacant(e) => {
171 e.insert(Fraction::one());
172 }
173 }
174 }
175
176 let mut sums = final_states;
178 for (source, _, _, probability) in &result {
179 match sums.entry(*source) {
180 std::collections::hash_map::Entry::Occupied(mut e) => {
181 *e.get_mut() += probability
182 }
183 std::collections::hash_map::Entry::Vacant(e) => {
184 e.insert(probability.clone());
185 }
186 }
187 }
188
189 result.scale_outgoing_probabilities(sums);
191 }
192
193 result
194 }
195 }
196 };
197}
198
199macro_rules! from_via_log {
200 ($t:ident) => {
201 impl From<$t> for StochasticDeterministicFiniteAutomaton {
202 fn from(value: $t) -> Self {
203 let log: EventLog = value.into();
204 log.into()
205 }
206 }
207 };
208}
209
210log!(EventLog);
211log!(EventLogTraceAttributes);
212log!(EventLogXes);
213log!(EventLogCsv);
214from_via_log!(EventLogPython);
215from_via_log!(CompressedEventLogXes);
216from_via_log!(CompressedEventLog);
217from_via_log!(CompressedEventLogTraceAttributes);
218
219#[cfg(test)]
220mod tests {
221 use ebi_arithmetic::{Fraction, Zero, f0};
222
223 use crate::{
224 DirectlyFollowsGraph, FiniteStochasticLanguage, HasActivityKey, NumberOfTraces,
225 StochasticDeterministicFiniteAutomaton,
226 };
227 use std::fs;
228
229 #[test]
230 fn slang_minprob_zero_through_sdfa() {
231 let fin = fs::read_to_string("testfiles/aa-ab-ba.slang").unwrap();
232 let slang = fin.parse::<FiniteStochasticLanguage>().unwrap();
233 assert_eq!(slang.number_of_traces(), 3);
234 let sdfa: StochasticDeterministicFiniteAutomaton = slang.into();
235 assert_eq!(sdfa.number_of_states(), 6);
236 assert_eq!(sdfa.number_of_transitions(), 5);
237 }
238
239 #[test]
240 fn dfg_to_sdfa() {
241 let fin1 = fs::read_to_string("testfiles/aa-ab-ba.dfg").unwrap();
242 let mut dfg: DirectlyFollowsGraph = fin1.parse::<DirectlyFollowsGraph>().unwrap();
243
244 let a = dfg.activity_key_mut().process_activity("a");
245 let b = dfg.activity_key_mut().process_activity("b");
246 assert_eq!(dfg.start_activities[&a], Fraction::from((2, 5)));
247 assert_eq!(dfg.start_activities[&b], Fraction::from((3, 5)));
248
249 let snfa: StochasticDeterministicFiniteAutomaton = dfg.into();
250 assert_eq!(snfa.number_of_states(), 3);
251 assert_eq!(snfa.number_of_transitions(), 5);
252 assert_eq!(snfa.sources, [0, 0, 1, 1, 2]);
253 assert_eq!(snfa.targets, [1, 2, 1, 2, 1]);
254 assert_eq!(
255 snfa.probabilities,
256 [
257 Fraction::from((2, 5)),
258 Fraction::from((3, 5)),
259 Fraction::from((1, 6)),
260 Fraction::from((1, 6)),
261 Fraction::from((3, 4))
262 ]
263 );
264 assert_eq!(
265 snfa.terminating_probabilities,
266 [f0!(), Fraction::from((2, 3)), Fraction::from((1, 4))]
267 );
268 }
269
270 #[test]
271 fn log_to_sdfa() {
272 let fin1 = fs::read_to_string("testfiles/acb-abc-ad-aded-adeded-adededed.slang").unwrap();
273 let slang = fin1.parse::<FiniteStochasticLanguage>().unwrap();
274
275 let sdfa = StochasticDeterministicFiniteAutomaton::from(slang);
276
277 assert!(
278 sdfa.terminating_probabilities
279 .contains(&Fraction::from((8, 15)))
280 );
281 assert!(
282 sdfa.terminating_probabilities
283 .contains(&Fraction::from((2, 3)))
284 );
285 assert!(
286 sdfa.terminating_probabilities
287 .contains(&Fraction::from((4, 7)))
288 );
289 }
290}