ebi_objects/ebi_objects/
directly_follows_model.rs

1use anyhow::{Context, Error, Result, anyhow};
2use ebi_derive::ActivityKey;
3use itertools::Itertools;
4use layout::topo::layout::VisualGraph;
5use std::{
6    cmp::Ordering,
7    fmt::Display,
8    io::{self, Write},
9    str::FromStr,
10};
11
12use crate::{
13    Activity, ActivityKey, ActivityKeyTranslator, Exportable, Graphable, HasActivityKey,
14    Importable, Infoable, TranslateActivityKey, constants::ebi_object::EbiObject,
15    line_reader::LineReader, traits::graphable,
16};
17
18use super::stochastic_directly_follows_model::NodeIndex;
19
20pub const HEADER: &str = "directly follows model";
21
22#[macro_export]
23macro_rules! format_comparison {
24    () => {"
25    
26    The following table gives an overview of several directly follows-based file types and their features:
27    \\begin{center}
28    \\begin{tabularx}{\\linewidth}{Xlll}
29        \\toprule
30        File type & stochastic & multiple nodes with the same label & file syntax \\\\
31        \\midrule
32        \\hyperref[filehandler:directly follows graph]{directly follows graph (.dfg)} & yes & no & JSON \\\\
33        \\hyperref[filehandler:directly follows model]{directly follows model (.dfm)} & no & yes & line-based \\\\
34        \\hyperref[filehandler:stochastic directly follows model]{stochastic directly follows model (.sdfm)} & yes & yes & line-based \\\\
35        \\bottomrule
36    \\end{tabularx}
37    \\end{center}"}
38}
39
40pub const FORMAT_SPECIFICATION: &str = concat!("A directly follows model is a line-based structure. Lines starting with a \\# are ignored.
41    This first line is exactly `directly follows model'.\\
42    The second line is a boolean indicating whether the model supports empty traces.\\
43    The third line is the number of activities in the model.\\
44    The following lines each contain an activity. Duplicated labels are accepted.\\
45    The next line contains the number of start activities, followed by, for each start activity, a line with the index of the start activity.\\
46    The next line contains the number of end activities, followed by, for each end activity, a line with the index of the end activity.\\
47    The next line contains the number of edges, followed by, for each edge, a line with first the index of the source activity, then the `>` symbol, then the index of the target activity.
48    
49    For instance:
50    \\lstinputlisting[language=ebilines, style=boxed]{../testfiles/a-b_star.dfm}", format_comparison!());
51
52#[derive(ActivityKey, Debug, Clone)]
53pub struct DirectlyFollowsModel {
54    pub activity_key: ActivityKey,
55    pub node_2_activity: Vec<Activity>,
56    pub empty_traces: bool,
57    pub sources: Vec<NodeIndex>, //edge -> source of edge
58    pub targets: Vec<NodeIndex>, //edge -> target of edge
59    pub start_nodes: Vec<bool>,  //node -> how often observed
60    pub end_nodes: Vec<bool>,    //node -> how often observed
61}
62
63impl DirectlyFollowsModel {
64    /**
65     * Creates a new directly follows model without any states or edges. This has the empty language, until a state or an empty trace is added.
66     */
67    pub fn new(activity_key: ActivityKey) -> Self {
68        Self {
69            activity_key: activity_key,
70            node_2_activity: vec![],
71            empty_traces: false,
72            sources: vec![],
73            targets: vec![],
74            start_nodes: vec![],
75            end_nodes: vec![],
76        }
77    }
78
79    pub fn has_empty_traces(&self) -> bool {
80        self.empty_traces
81    }
82
83    pub fn can_terminate_in_node(&self, node: NodeIndex) -> bool {
84        self.end_nodes[node]
85    }
86
87    pub fn number_of_start_nodes(&self) -> usize {
88        self.start_nodes
89            .iter()
90            .fold(0, |a, b| if *b { a + 1 } else { a })
91    }
92
93    pub fn number_of_end_nodes(&self) -> usize {
94        self.start_nodes
95            .iter()
96            .fold(0, |a, b| if *b { a + 1 } else { a })
97    }
98
99    pub fn is_start_node(&self, node: NodeIndex) -> bool {
100        self.start_nodes[node]
101    }
102
103    pub fn is_end_node(&self, node: NodeIndex) -> bool {
104        self.end_nodes[node]
105    }
106
107    pub fn can_execute_edge(&self, _edge: usize) -> bool {
108        true
109    }
110
111    pub fn get_max_state(&self) -> usize {
112        self.node_2_activity.len() + 2
113    }
114
115    pub fn add_empty_trace(&mut self) {
116        self.empty_traces = true;
117    }
118
119    pub fn add_node(&mut self, activity: Activity) -> NodeIndex {
120        let index = self.node_2_activity.len();
121        self.node_2_activity.push(activity);
122        self.start_nodes.push(false);
123        self.end_nodes.push(false);
124        index
125    }
126
127    pub fn add_edge(&mut self, source: NodeIndex, target: NodeIndex) {
128        let (found, from) = self.binary_search(source, target);
129        if found {
130            //edge already present
131        } else {
132            //new edge
133            self.sources.insert(from, source);
134            self.targets.insert(from, target);
135        }
136    }
137
138    pub fn binary_search(&self, source: NodeIndex, target: NodeIndex) -> (bool, usize) {
139        if self.sources.is_empty() {
140            return (false, 0);
141        }
142
143        let mut size = self.sources.len();
144        let mut left = 0;
145        let mut right = size;
146        while left < right {
147            let mid = left + size / 2;
148
149            let cmp = Self::compare(source, target, self.sources[mid], self.targets[mid]);
150
151            left = if cmp == Ordering::Less { mid + 1 } else { left };
152            right = if cmp == Ordering::Greater { mid } else { right };
153            if cmp == Ordering::Equal {
154                assert!(mid < self.sources.len());
155                return (true, mid);
156            }
157
158            size = right - left;
159        }
160
161        assert!(left <= self.sources.len());
162        (false, left)
163    }
164
165    fn compare(
166        source1: NodeIndex,
167        target1: NodeIndex,
168        source2: NodeIndex,
169        target2: NodeIndex,
170    ) -> Ordering {
171        if source1 < source2 {
172            return Ordering::Greater;
173        } else if source1 > source2 {
174            return Ordering::Less;
175        } else if target2 > target1 {
176            return Ordering::Greater;
177        } else if target2 < target1 {
178            return Ordering::Less;
179        } else {
180            return Ordering::Equal;
181        }
182    }
183}
184
185impl TranslateActivityKey for DirectlyFollowsModel {
186    fn translate_using_activity_key(&mut self, to_activity_key: &mut ActivityKey) {
187        let translator = ActivityKeyTranslator::new(&self.activity_key, to_activity_key);
188        self.node_2_activity
189            .iter_mut()
190            .for_each(|activity| *activity = translator.translate_activity(&activity));
191        self.activity_key = to_activity_key.clone();
192    }
193}
194
195impl Importable for DirectlyFollowsModel {
196    fn import_as_object(reader: &mut dyn std::io::prelude::BufRead) -> Result<EbiObject> {
197        Ok(EbiObject::DirectlyFollowsModel(Self::import(reader)?))
198    }
199
200    fn import(reader: &mut dyn std::io::prelude::BufRead) -> anyhow::Result<Self>
201    where
202        Self: Sized,
203    {
204        let mut lreader = LineReader::new(reader);
205
206        let head = lreader
207            .next_line_string()
208            .with_context(|| format!("failed to read header, which should be {}", HEADER))?;
209        if head != HEADER {
210            return Err(anyhow!(
211                "first line should be exactly `{}`, but found `{}`",
212                HEADER,
213                lreader.get_last_line()
214            ));
215        }
216
217        //read empty traces
218        let empty_traces = lreader
219            .next_line_bool()
220            .context("could not read whether the model supports empty traces")?;
221
222        //read activities
223        let number_of_nodes = lreader
224            .next_line_index()
225            .context("could not read the number of nodes")?;
226        let mut activity_key = ActivityKey::new();
227        let mut node_2_activity = vec![];
228        for activity in 0..number_of_nodes {
229            let label = lreader
230                .next_line_string()
231                .with_context(|| format!("could not read activity {}", activity))?;
232            let activity = activity_key.process_activity(&label);
233            node_2_activity.push(activity);
234        }
235
236        //read start activities
237        let mut start_nodes = vec![false; number_of_nodes];
238        let number_of_start_nodes = lreader
239            .next_line_index()
240            .context("could not read the number of start nodes")?;
241        for i in 0..number_of_start_nodes {
242            let start_node = lreader
243                .next_line_index()
244                .with_context(|| format!("could not read start node {}", i))?;
245            start_nodes[start_node] = true;
246        }
247
248        //read end activities
249        let mut end_nodes = vec![false; number_of_nodes];
250        let number_of_end_nodes = lreader
251            .next_line_index()
252            .context("could not read the number of end nodes")?;
253        for i in 0..number_of_end_nodes {
254            let end_node = lreader
255                .next_line_index()
256                .with_context(|| format!("could not read end node {}", i))?;
257            end_nodes[end_node] = true;
258        }
259
260        let mut result = Self {
261            activity_key,
262            node_2_activity,
263            empty_traces,
264            sources: vec![],
265            targets: vec![],
266            start_nodes,
267            end_nodes,
268        };
269
270        //read edges
271        let number_of_edges = lreader
272            .next_line_index()
273            .context("could not read number of edges")?;
274        for e in 0..number_of_edges {
275            let edge_line = lreader
276                .next_line_string()
277                .with_context(|| format!("could not read edge {}", e))?;
278
279            let mut arr = edge_line.split('>');
280            if let Some((source, target)) = arr.next_tuple() {
281                let source = source
282                    .parse::<usize>()
283                    .with_context(|| format!("could not read source of edge {}", e))?;
284                let target = target
285                    .parse::<usize>()
286                    .with_context(|| format!("could not read target of edge {}", e))?;
287                result.add_edge(source, target);
288            } else {
289                return Err(anyhow!(
290                    "could not read edge, which must be two numbers separated by >"
291                ));
292            }
293        }
294
295        Ok(result)
296    }
297}
298
299impl FromStr for DirectlyFollowsModel {
300    type Err = Error;
301
302    fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
303        let mut reader = io::Cursor::new(s);
304        Self::import(&mut reader)
305    }
306}
307
308impl Display for DirectlyFollowsModel {
309    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310        writeln!(f, "{}", HEADER)?;
311
312        writeln!(f, "# empty trace\n{}", self.empty_traces)?;
313
314        //activities
315        writeln!(f, "# number of activites\n{}", self.node_2_activity.len())?;
316        for (a, activity) in self.node_2_activity.iter().enumerate() {
317            writeln!(
318                f,
319                "#activity {}\n{}",
320                a,
321                self.activity_key.get_activity_label(activity)
322            )?;
323        }
324
325        //start activities
326        writeln!(f, "# number of start activites\n{}", self.start_nodes.len())?;
327        for (i, start_activity) in self.start_nodes.iter().enumerate() {
328            writeln!(f, "# start activity {}\n{}", i, start_activity)?;
329        }
330
331        //end activities
332        writeln!(f, "# number of end activites\n{}", self.end_nodes.len())?;
333        for (i, end_activity) in self.end_nodes.iter().enumerate() {
334            writeln!(f, "# end activity {}\n{}", i, end_activity)?;
335        }
336
337        //edges
338        writeln!(f, "# number of edges\n{}\n# edges", self.sources.len())?;
339        for (source, target) in self.sources.iter().zip(self.targets.iter()) {
340            writeln!(f, "{}>{}", source, target)?;
341        }
342
343        Ok(write!(f, "")?)
344    }
345}
346
347impl Graphable for DirectlyFollowsModel {
348    fn to_dot(&self) -> Result<layout::topo::layout::VisualGraph> {
349        let mut graph = VisualGraph::new(layout::core::base::Orientation::LeftToRight);
350
351        //source + sink
352        let source = graphable::create_place(&mut graph, "");
353        let sink = graphable::create_place(&mut graph, "");
354
355        //empty traces
356        if self.empty_traces {
357            graphable::create_edge(&mut graph, &source, &sink, "");
358        }
359
360        //nodes
361        let mut nodes = vec![];
362        for n in &self.node_2_activity {
363            nodes.push(graphable::create_transition(
364                &mut graph,
365                self.activity_key.get_activity_label(n),
366                "",
367            ));
368        }
369
370        //start activities
371        for (activity, is) in self.start_nodes.iter().enumerate() {
372            if *is {
373                graphable::create_edge(&mut graph, &source, &nodes[activity], "");
374            }
375        }
376
377        //end activities
378        for (activity, is) in self.end_nodes.iter().enumerate() {
379            if *is {
380                graphable::create_edge(&mut graph, &nodes[activity], &sink, "");
381            }
382        }
383
384        //edges
385        for (source, target) in self.sources.iter().zip(self.targets.iter()) {
386            graphable::create_edge(&mut graph, &nodes[*source], &nodes[*target], "");
387        }
388
389        Ok(graph)
390    }
391}
392
393impl Exportable for DirectlyFollowsModel {
394    fn export_from_object(object: EbiObject, f: &mut dyn Write) -> Result<()> {
395        match object {
396            EbiObject::DirectlyFollowsModel(dfm) => Self::export(&dfm, f),
397            EbiObject::DirectlyFollowsGraph(dfg) => {
398                Self::export(&Into::<DirectlyFollowsModel>::into(dfg), f)
399            }
400            EbiObject::StochasticDirectlyFollowsModel(sdfm) => {
401                Self::export(&Into::<DirectlyFollowsModel>::into(sdfm), f)
402            }
403
404            _ => Err(anyhow!(
405                "cannot export {} {} as a directly follows model",
406                object.get_type().get_article(),
407                object.get_type()
408            )),
409        }
410    }
411
412    fn export(&self, f: &mut dyn std::io::Write) -> Result<()> {
413        Ok(write!(f, "{}", self.to_string())?)
414    }
415}
416
417impl Infoable for DirectlyFollowsModel {
418    fn info(&self, f: &mut impl std::io::Write) -> Result<()> {
419        writeln!(f, "Number of transitions\t{}", self.node_2_activity.len())?;
420        writeln!(
421            f,
422            "Number of activities\t{}",
423            self.activity_key.activity2name.len()
424        )?;
425        writeln!(f, "Number of nodes\t\t{}", self.node_2_activity.len())?;
426        writeln!(f, "Number of edges\t\t{}", self.sources.len())?;
427        writeln!(f, "Number of start nodes\t{}", self.number_of_start_nodes())?;
428        writeln!(f, "Number of end nodes\t{}", self.number_of_end_nodes())?;
429
430        writeln!(f, "")?;
431        self.activity_key().info(f)?;
432
433        Ok(write!(f, "")?)
434    }
435}