1use crate::{
2 Activity, ActivityKey, ActivityKeyTranslator, EbiObject, Exportable, Graphable, HasActivityKey,
3 Importable, Infoable, TranslateActivityKey, json,
4 traits::{
5 graphable,
6 importable::{ImporterParameter, ImporterParameterValues, from_string},
7 },
8};
9use anyhow::{Context, Result, anyhow};
10use ebi_arithmetic::{Fraction, One, Signed};
11use ebi_derive::ActivityKey;
12use layout::topo::layout::VisualGraph;
13use serde_json::Value;
14use std::{
15 cmp::{Ordering, max},
16 collections::HashMap,
17 fmt,
18 io::BufRead,
19};
20
21#[derive(Debug, ActivityKey, Clone)]
22pub struct StochasticDeterministicFiniteAutomaton {
23 pub activity_key: ActivityKey,
24 pub initial_state: Option<usize>,
25 pub max_state: usize,
26 pub sources: Vec<usize>, pub targets: Vec<usize>, pub activities: Vec<Activity>, pub probabilities: Vec<Fraction>, pub terminating_probabilities: Vec<Fraction>, }
32
33impl StochasticDeterministicFiniteAutomaton {
34 pub fn new() -> Self {
38 Self {
39 activity_key: ActivityKey::new(),
40 max_state: 0,
41 initial_state: Some(0),
42 sources: vec![],
43 targets: vec![],
44 activities: vec![],
45 probabilities: vec![],
46 terminating_probabilities: vec![Fraction::one()],
47 }
48 }
49
50 pub fn get_sources(&self) -> &Vec<usize> {
51 &self.sources
52 }
53
54 pub fn get_targets(&self) -> &Vec<usize> {
55 &self.targets
56 }
57
58 pub fn get_activities(&self) -> &Vec<Activity> {
59 &self.activities
60 }
61
62 pub fn get_probabilities(&self) -> &Vec<Fraction> {
63 &self.probabilities
64 }
65
66 pub fn set_initial_state(&mut self, state: Option<usize>) {
67 if let Some(state) = state {
68 self.ensure_states(state);
69 }
70 self.initial_state = state;
71 }
72
73 pub fn can_terminate_in_state(&self, state: usize) -> bool {
74 self.get_termination_probability(state).is_positive()
75 }
76
77 pub fn can_execute_transition(&self, transition: usize) -> bool {
81 self.probabilities[transition].is_positive()
82 }
83
84 fn ensure_states(&mut self, new_max_state: usize) {
85 if new_max_state > self.max_state {
86 self.terminating_probabilities
87 .extend(vec![Fraction::one(); new_max_state - self.max_state]);
88 self.max_state = new_max_state;
89
90 assert!(self.terminating_probabilities.len() == self.max_state + 1)
91 }
92 }
93
94 pub fn add_transition(
95 &mut self,
96 source: usize,
97 activity: Activity,
98 target: usize,
99 probability: Fraction,
100 ) -> Result<()> {
101 self.ensure_states(max(source, target));
102
103 let (found, from) =
104 self.binary_search(source, self.activity_key.get_id_from_activity(activity));
105 if found {
106 Err(anyhow!(
108 "tried to insert edge from {} to {}, which would violate the determinism of the SDFA",
109 source,
110 target
111 ))
112 } else {
113 self.sources.insert(from, source);
114 self.targets.insert(from, target);
115 self.activities.insert(from, activity);
116 self.terminating_probabilities[source] -= &probability;
117 self.probabilities.insert(from, probability);
118
119 if self.terminating_probabilities[source].is_negative() {
120 Err(anyhow!(
121 "tried to insert edge from {} to {}, which brings the sum outgoing probability of the source state (1-{}) above 1",
122 source,
123 target,
124 self.terminating_probabilities[source]
125 ))
126 } else {
127 Ok(())
128 }
129 }
130 }
131
132 pub fn get_number_of_transitions(&self) -> usize {
133 self.sources.len()
134 }
135
136 pub fn take_or_add_transition(
140 &mut self,
141 source_state: usize,
142 activity: Activity,
143 probability: Fraction,
144 ) -> usize {
145 self.terminating_probabilities[source_state] -= &probability;
146
147 let (found, transition) = self.binary_search(
148 source_state,
149 self.activity_key.get_id_from_activity(activity),
150 );
151 if found {
152 self.probabilities[transition] += &probability;
153 return self.targets[transition];
154 } else {
155 let target = self.add_state();
156 self.sources.insert(transition, source_state);
157 self.targets.insert(transition, target);
158 self.activities.insert(transition, activity);
159 self.probabilities.insert(transition, probability);
160 return target;
161 }
162 }
163
164 pub fn scale_outgoing_probabilities(&mut self, state2scale: HashMap<usize, Fraction>) {
168 let mut new_terminating_probabilities =
169 vec![Fraction::one(); self.terminating_probabilities.len()];
170 for (state, _, _, outgoing_probability) in &mut self.into_iter() {
171 if let Some(factor) = state2scale.get(state) {
172 *outgoing_probability /= factor;
173 }
174 new_terminating_probabilities[*state] -= outgoing_probability;
175 }
176 self.terminating_probabilities = new_terminating_probabilities;
177 }
178
179 pub fn get_initial_state(&self) -> Option<usize> {
180 self.initial_state
181 }
182
183 pub fn get_termination_probability(&self, state: usize) -> &Fraction {
184 &self.terminating_probabilities[state]
185 }
186
187 pub fn add_state(&mut self) -> usize {
188 self.max_state += 1;
189 self.terminating_probabilities.push(Fraction::one());
190 return self.max_state;
191 }
192
193 pub fn get_max_state(&self) -> usize {
194 self.max_state
195 }
196
197 fn compare(source1: usize, activity1: usize, source2: usize, activity2: Activity) -> Ordering {
198 if source1 < source2 {
199 return Ordering::Greater;
200 } else if source1 > source2 {
201 return Ordering::Less;
202 } else if activity2 > activity1 {
203 return Ordering::Greater;
204 } else if activity2 < activity1 {
205 return Ordering::Less;
206 } else {
207 return Ordering::Equal;
208 }
209 }
210
211 pub fn binary_search(&self, source: usize, activity: usize) -> (bool, usize) {
212 if self.sources.is_empty() {
213 return (false, 0);
214 }
215
216 let mut size = self.sources.len();
217 let mut left = 0;
218 let mut right = size;
219 while left < right {
220 let mid = left + size / 2;
221
222 let cmp = Self::compare(source, activity, self.sources[mid], self.activities[mid]);
223
224 left = if cmp == Ordering::Less { mid + 1 } else { left };
225 right = if cmp == Ordering::Greater { mid } else { right };
226 if cmp == Ordering::Equal {
227 assert!(mid < self.sources.len());
228 return (true, mid);
229 }
230
231 size = right - left;
232 }
233
234 assert!(left <= self.sources.len());
235 (false, left)
236 }
237
238 pub fn set_activity_key(&mut self, activity_key: &ActivityKey) {
239 self.activity_key = activity_key.clone();
240 }
241}
242
243impl TranslateActivityKey for StochasticDeterministicFiniteAutomaton {
244 fn translate_using_activity_key(&mut self, to_activity_key: &mut ActivityKey) {
245 let translator = ActivityKeyTranslator::new(&self.activity_key, to_activity_key);
246 self.activities
247 .iter_mut()
248 .for_each(|activity| *activity = translator.translate_activity(&activity));
249 self.activity_key = to_activity_key.clone();
250 }
251}
252
253impl Importable for StochasticDeterministicFiniteAutomaton {
254 const FILE_FORMAT_SPECIFICATION_LATEX: &str = "A stochastic deterministic finite automaton is a JSON structure with the top level being an object.
255 This object contains the following key-value pairs:
256 \\begin{itemize}
257 \\item \\texttt{initialState} being the index of the initial state. This field is optional: if omitted, the SDFA has an empty stochastic language.
258 \\item \\texttt{transitions} being a list of transitions.
259 Each transition is an object with \\texttt{from} being the source state index of the transition,
260 \\texttt{to} being the target state index of the transition,
261 \\texttt{label} being the activity of the transition, and
262 \\texttt{prob} being the probability of the transition (may be given as a fraction in a string or a float value. Must be $\\leq 1$).
263 Silent transitions are not supported.
264 The file format supports deadlocks and livelocks.
265 The probability that a trace terminates in a state is 1 - the sum probability of the outgoing transitions of the state.
266 \\end{itemize}
267 For instance:
268 \\lstinputlisting[language=json, style=boxed]{../testfiles/aa-ab-ba.sdfa}";
269
270 const IMPORTER_PARAMETERS: &[ImporterParameter] = &[];
271
272 fn import_as_object(
273 reader: &mut dyn BufRead,
274 parameter_values: &ImporterParameterValues,
275 ) -> Result<EbiObject> {
276 Ok(EbiObject::StochasticDeterministicFiniteAutomaton(
277 Self::import(reader, parameter_values)?,
278 ))
279 }
280
281 fn import(reader: &mut dyn BufRead, _: &ImporterParameterValues) -> Result<Self>
282 where
283 Self: Sized,
284 {
285 let json: Value = serde_json::from_reader(reader)?;
286
287 let mut result = StochasticDeterministicFiniteAutomaton::new();
288
289 result.set_initial_state(json::read_field_number(&json, "initialState").ok());
290 let jtrans = json::read_field_list(&json, "transitions")
291 .context("failed to read list of transitions")?;
292 for (i, jtransition) in jtrans.iter().enumerate() {
293 let from = json::read_field_number(jtransition, "from")
294 .with_context(|| format!("could not read source of transition {}", i))?;
295 let to = json::read_field_number(jtransition, "to")
296 .with_context(|| format!("could not read destination of transition {}", i))?;
297 let label = json::read_field_string(jtransition, "label")
298 .with_context(|| format!("could not read label of transition {}", i))?;
299 let activity = result.activity_key.process_activity(label.as_str());
300 let probability = json::read_field_fraction(jtransition, "prob")
301 .with_context(|| format!("could not read probability on transition {}", i))?;
302
303 result.add_transition(from, activity, to, probability)?;
304 }
305
306 return Ok(result);
307 }
308}
309from_string!(StochasticDeterministicFiniteAutomaton);
310
311impl Exportable for StochasticDeterministicFiniteAutomaton {
312 fn export_from_object(object: EbiObject, f: &mut dyn std::io::Write) -> Result<()> {
313 match object {
314 EbiObject::StochasticDeterministicFiniteAutomaton(sdfa) => sdfa.export(f),
315 EbiObject::FiniteStochasticLanguage(slang) => Into::<Self>::into(slang).export(f),
316 EbiObject::EventLog(log) => Into::<Self>::into(log).export(f),
317 EbiObject::EventLogTraceAttributes(log) => Into::<Self>::into(log).export(f),
318 EbiObject::EventLogXes(log) => Into::<Self>::into(log).export(f),
319 EbiObject::EventLogCsv(log) => Into::<Self>::into(log).export(f),
320 _ => Err(anyhow!(
321 "Cannot export {} {} as an SDFA.",
322 object.get_type().get_article(),
323 object.get_type()
324 )),
325 }
326 }
327
328 fn export(&self, f: &mut dyn std::io::Write) -> Result<()> {
329 Ok(write!(f, "{}", self)?)
330 }
331}
332
333impl Infoable for StochasticDeterministicFiniteAutomaton {
334 fn info(&self, f: &mut impl std::io::Write) -> Result<()> {
335 writeln!(f, "Number of states\t{}", self.max_state)?;
336 writeln!(f, "Number of transitions\t{}", self.sources.len())?;
337 writeln!(
338 f,
339 "Number of activities\t{}",
340 self.activity_key.get_number_of_activities()
341 )?;
342
343 writeln!(f, "")?;
344 self.activity_key().info(f)?;
345
346 Ok(writeln!(f, "")?)
347 }
348}
349
350impl fmt::Display for StochasticDeterministicFiniteAutomaton {
351 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352 writeln!(f, "{{")?;
353 if let Some(state) = self.get_initial_state() {
354 writeln!(f, "\"initialState\": {},", state)?;
355 }
356 writeln!(f, "\"transitions\": [")?;
357 for pos in 0..self.sources.len() {
358 write!(
359 f,
360 "{{\"from\":{},\"to\":{},\"label\":\"{}\",\"prob\":\"{}\"}}",
361 self.sources[pos],
362 self.targets[pos],
363 self.activity_key.get_activity_label(&self.activities[pos]),
364 self.probabilities[pos]
365 )?;
366 if pos + 1 == self.sources.len() {
367 writeln!(f, "")?;
368 } else {
369 writeln!(f, ",")?;
370 }
371 }
372 writeln!(f, "]}}")?;
373 Ok(())
374 }
375}
376
377impl Graphable for StochasticDeterministicFiniteAutomaton {
378 fn to_dot(&self) -> Result<layout::topo::layout::VisualGraph> {
379 log::info!("to_dot for StochasticDeterministicFiniteAutomaton");
380 let mut graph = VisualGraph::new(layout::core::base::Orientation::LeftToRight);
381
382 let mut places = vec![];
383 for state in 0..=self.max_state {
384 places.push(graphable::create_place(
385 &mut graph,
386 &format!("{}", self.terminating_probabilities[state]),
387 ));
388 }
390
391 for pos in 0..self.sources.len() {
392 let source = places[self.sources[pos]];
393 let target = places[self.targets[pos]];
394 let probability = &self.probabilities[pos];
395 let activity = self.activity_key.get_activity_label(&self.activities[pos]);
396
397 graphable::create_edge(
398 &mut graph,
399 &source,
400 &target,
401 &format!("{}, {}", activity, probability.to_string()),
402 );
403 }
404
405 Ok(graph)
406 }
407}
408
409impl<'a> IntoIterator for &'a StochasticDeterministicFiniteAutomaton {
410 type Item = (&'a usize, &'a usize, &'a Activity, &'a Fraction);
411
412 type IntoIter = StochasticDeterministicFiniteAutomatonIterator<'a>;
413
414 fn into_iter(self) -> Self::IntoIter {
415 Self::IntoIter {
416 it_sources: self.get_sources().iter(),
417 it_targets: self.get_targets().iter(),
418 it_activities: self.get_activities().iter(),
419 it_probabilities: self.get_probabilities().iter(),
420 }
421 }
422}
423
424pub struct StochasticDeterministicFiniteAutomatonIterator<'a> {
425 it_sources: std::slice::Iter<'a, usize>,
426 it_targets: std::slice::Iter<'a, usize>,
427 it_activities: std::slice::Iter<'a, Activity>,
428 it_probabilities: std::slice::Iter<'a, Fraction>,
429}
430
431impl<'a> Iterator for StochasticDeterministicFiniteAutomatonIterator<'a> {
432 type Item = (&'a usize, &'a usize, &'a Activity, &'a Fraction);
433
434 fn next(&mut self) -> Option<Self::Item> {
435 if let Some(source) = self.it_sources.next() {
436 let target = self.it_targets.next().unwrap();
437 let activity = self.it_activities.next().unwrap();
438 let probability = self.it_probabilities.next().unwrap();
439 let result = Some((source, target, activity, probability));
440 result
441 } else {
442 None
443 }
444 }
445}
446
447impl<'a> IntoIterator for &'a mut StochasticDeterministicFiniteAutomaton {
448 type Item = (&'a usize, &'a usize, &'a Activity, &'a mut Fraction);
449
450 type IntoIter = StochasticDeterministicFiniteAutomatonMutIterator<'a>;
451
452 fn into_iter(self) -> Self::IntoIter {
453 Self::IntoIter {
454 it_sources: self.sources.iter(),
455 it_targets: self.targets.iter(),
456 it_activities: self.activities.iter(),
457 it_probabilities: self.probabilities.iter_mut(),
458 }
459 }
460}
461
462pub struct StochasticDeterministicFiniteAutomatonMutIterator<'a> {
463 it_sources: std::slice::Iter<'a, usize>,
464 it_targets: std::slice::Iter<'a, usize>,
465 it_activities: std::slice::Iter<'a, Activity>,
466 it_probabilities: std::slice::IterMut<'a, Fraction>,
467}
468
469impl<'a> Iterator for StochasticDeterministicFiniteAutomatonMutIterator<'a> {
470 type Item = (&'a usize, &'a usize, &'a Activity, &'a mut Fraction);
471
472 fn next(&mut self) -> Option<Self::Item> {
473 if let Some(source) = self.it_sources.next() {
474 let target = self.it_targets.next().unwrap();
475 let activity = self.it_activities.next().unwrap();
476 let probability = self.it_probabilities.next().unwrap();
477 let result = Some((source, target, activity, probability));
478 result
479 } else {
480 None
481 }
482 }
483}