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>, pub targets: Vec<NodeIndex>, pub start_nodes: Vec<bool>, pub end_nodes: Vec<bool>, }
62
63impl DirectlyFollowsModel {
64 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 } else {
132 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 let empty_traces = lreader
219 .next_line_bool()
220 .context("could not read whether the model supports empty traces")?;
221
222 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 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 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 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 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 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 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 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 let source = graphable::create_place(&mut graph, "");
353 let sink = graphable::create_place(&mut graph, "");
354
355 if self.empty_traces {
357 graphable::create_edge(&mut graph, &source, &sink, "");
358 }
359
360 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 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 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 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}