1#[cfg(any(test, feature = "testactivities"))]
2use crate::activity_key::has_activity_key::TestActivityKey;
3use crate::{
4 Activity, ActivityKey, ActivityKeyTranslator, Exportable, Importable, Infoable,
5 TranslateActivityKey,
6 constants::ebi_object::EbiObject,
7 ebi_objects::labelled_petri_net::TransitionIndex,
8 line_reader::LineReader,
9 traits::importable::{ImporterParameter, ImporterParameterValues, from_string},
10};
11use anyhow::{Context, Result, anyhow};
12use ebi_derive::ActivityKey;
13use std::fmt::Display;
14
15pub const HEADER: &str = "language of alignments";
16
17#[derive(ActivityKey, Clone)]
18pub struct LanguageOfAlignments {
19 pub activity_key: ActivityKey,
20 pub alignments: Vec<Vec<Move>>,
21}
22
23impl LanguageOfAlignments {
24 pub fn new(activity_key: ActivityKey) -> Self {
25 Self {
26 activity_key: activity_key,
27 alignments: vec![],
28 }
29 }
30
31 pub fn push(&mut self, alignment: Vec<Move>) {
32 self.alignments.push(alignment);
33 }
34
35 pub fn append(&mut self, alignments: &mut Vec<Vec<Move>>) {
36 self.alignments.append(alignments);
37 }
38
39 pub fn get(&self, index: usize) -> Option<&Vec<Move>> {
40 self.alignments.get(index)
41 }
42
43 pub fn get_activity_key(&self) -> &ActivityKey {
44 &self.activity_key
45 }
46
47 pub fn get_activity_key_mut(&mut self) -> &mut ActivityKey {
48 &mut self.activity_key
49 }
50
51 pub fn sort(&mut self) {
52 self.alignments.sort();
53 }
54}
55
56impl TranslateActivityKey for LanguageOfAlignments {
57 fn translate_using_activity_key(&mut self, to_activity_key: &mut ActivityKey) {
58 let translator = ActivityKeyTranslator::new(&self.activity_key, to_activity_key);
59 self.alignments.iter_mut().for_each(|alignment| {
60 alignment.iter_mut().for_each(|activity| match activity {
61 Move::SynchronousMove(activity, _)
62 | Move::LogMove(activity)
63 | Move::ModelMove(activity, _) => {
64 *activity = translator.translate_activity(&activity)
65 }
66 _ => {}
67 })
68 });
69 self.activity_key = to_activity_key.clone();
70 }
71}
72
73impl Exportable for LanguageOfAlignments {
74 fn export_from_object(object: EbiObject, f: &mut dyn std::io::Write) -> Result<()> {
75 match object {
76 EbiObject::LanguageOfAlignments(alignments) => alignments.export(f),
77 EbiObject::StochasticLanguageOfAlignments(sali) => Into::<Self>::into(sali).export(f),
78 _ => Err(anyhow!("Cannot export as language of alignments.")),
79 }
80 }
81
82 fn export(&self, f: &mut dyn std::io::Write) -> Result<()> {
83 Ok(write!(f, "{}", self)?)
84 }
85}
86
87impl Display for LanguageOfAlignments {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 writeln!(f, "{}", HEADER)?;
90
91 writeln!(f, "# number of alignments\n{}", self.alignments.len())?;
92
93 for (i, moves) in self.alignments.iter().enumerate() {
94 writeln!(f, "# alignment {}", i)?;
95 writeln!(f, "# number of moves\n{}", moves.len())?;
96
97 for (j, movee) in moves.iter().enumerate() {
98 writeln!(f, "# move {}", j)?;
99
100 match movee {
101 Move::LogMove(activity) => {
102 writeln!(f, "log move")?;
103 writeln!(
104 f,
105 "label {}",
106 self.activity_key.get_activity_label(activity)
107 )?;
108 }
109 Move::ModelMove(activity, transition) => {
110 writeln!(f, "model move")?;
111 writeln!(
112 f,
113 "label {}",
114 self.activity_key.get_activity_label(activity)
115 )?;
116 writeln!(f, "{}", transition)?;
117 }
118 Move::SynchronousMove(activity, transition) => {
119 writeln!(f, "synchronous move")?;
120 writeln!(
121 f,
122 "label {}",
123 self.activity_key.get_activity_label(activity)
124 )?;
125 writeln!(f, "{}", transition)?;
126 }
127 Move::SilentMove(transition) => {
128 writeln!(f, "silent move")?;
129 writeln!(f, "{}", transition)?;
130 }
131 };
132 }
133 }
134
135 write!(f, "")
136 }
137}
138
139impl Importable for LanguageOfAlignments {
140 const FILE_FORMAT_SPECIFICATION_LATEX: &str = "A language of alignments is a line-based structure. Lines starting with a \\# are ignored.
141 This first line is exactly `language of alignments'.
142 The second line is the number of alignments in the language.
143 For each alignment, the first line contains the number of moves in the alignment.
144 Then, each move is given as either
145 \\begin{itemize}
146 \\item `synchronous move', followed by a line with the word `label' followed by a space and the activity label, which is followed with a line with the index of the involved transition.
147 \\item `silent move', followed by a line with the index of the silent transition.
148 \\item `log move', followed by a line with the word `label', then a space, and then the activity label.
149 \\item `model move', followed by a line with the word `label' followed by a space and the activity label, which is followed with a line with the index of the involved ransition.
150 \\end{itemize}
151 Note that the Semantics trait of Ebi, which is what most alignment computations use, requires that every final marking is a deadlock.
152 Consequently, an implicit silent transition may be added by the Semantics trait that is not in the model.
153
154 For instance:
155 \\lstinputlisting[language=ebilines, style=boxed]{../testfiles/aa-ab-ba.ali}";
156
157 const IMPORTER_PARAMETERS: &[ImporterParameter] = &[];
158
159 fn import_as_object(
160 reader: &mut dyn std::io::BufRead,
161 parameter_values: &ImporterParameterValues,
162 ) -> Result<EbiObject> {
163 Ok(EbiObject::LanguageOfAlignments(Self::import(
164 reader,
165 parameter_values,
166 )?))
167 }
168
169 fn import(
170 reader: &mut dyn std::io::BufRead,
171 _: &ImporterParameterValues,
172 ) -> anyhow::Result<Self>
173 where
174 Self: Sized,
175 {
176 let mut lreader = LineReader::new(reader);
177 let mut activity_key = ActivityKey::new();
178
179 let head = lreader
180 .next_line_string()
181 .with_context(|| format!("failed to read header, which should be {}", HEADER))?;
182 if head != HEADER {
183 return Err(anyhow!(
184 "first line should be exactly `{}`, but found `{}`",
185 HEADER,
186 lreader.get_last_line()
187 ));
188 }
189
190 let number_of_alignments = lreader
191 .next_line_index()
192 .context("failed to read number of alignments")?;
193
194 let mut alignments = Vec::new();
195 for alignment_number in 0..number_of_alignments {
196 let mut moves = vec![];
197
198 let number_of_moves = lreader.next_line_index().with_context(|| {
199 format!(
200 "failed to read number of moves in alignemnt {}",
201 alignment_number
202 )
203 })?;
204
205 for move_number in 0..number_of_moves {
206 let move_type_line = lreader.next_line_string().with_context(|| {
208 format!(
209 "failed to read type of move {} of alignment {}",
210 move_number, alignment_number
211 )
212 })?;
213 if move_type_line.trim_start().starts_with("log move") {
214 let label_line = lreader.next_line_string().with_context(|| {
216 format!(
217 "failed to read label of log move {} of alignment {}",
218 move_number, alignment_number
219 )
220 })?;
221 if label_line.trim_start().starts_with("label ") {
222 let label = label_line[6..].to_string();
223 let activity = activity_key.process_activity(&label);
224 moves.push(Move::LogMove(activity));
225 } else {
226 return Err(anyhow!("Line must have a label"));
227 }
228 } else if move_type_line.trim_start().starts_with("model move") {
229 let label_line = lreader.next_line_string().with_context(|| {
231 format!(
232 "failed to read label of model move {} of alignment {}",
233 move_number, alignment_number
234 )
235 })?;
236 let activity = if label_line.trim_start().starts_with("label ") {
237 let label = label_line.trim_start()[6..].to_string();
238 let activity = activity_key.process_activity(&label);
239 activity
240 } else {
241 return Err(anyhow!("Line must have a label"));
242 };
243
244 let transition = lreader.next_line_index().with_context(|| {
246 format!(
247 "failed to read transition of move {} in alignemnt {}",
248 move_number, alignment_number
249 )
250 })?;
251
252 moves.push(Move::ModelMove(activity, transition));
253 } else if move_type_line.trim_start().starts_with("synchronous move") {
254 let label_line = lreader.next_line_string().with_context(|| {
256 format!(
257 "failed to read label of synchronous move {} of alignment {}",
258 move_number, alignment_number
259 )
260 })?;
261 if label_line.trim_start().starts_with("label ") {
262 let label = label_line.trim_start()[6..].to_string();
263 let activity = activity_key.process_activity(&label);
264
265 let transition = lreader.next_line_index().with_context(|| {
267 format!(
268 "failed to read transition of move {} in alignemnt {}",
269 move_number, alignment_number
270 )
271 })?;
272
273 moves.push(Move::SynchronousMove(activity, transition));
274 } else {
275 return Err(anyhow!("Line must have a label"));
276 }
277 } else if move_type_line.trim_start().starts_with("silent move") {
278 let transition = lreader.next_line_index().with_context(|| {
280 format!(
281 "failed to read transition of move {} in alignemnt {}",
282 move_number, alignment_number
283 )
284 })?;
285
286 moves.push(Move::SilentMove(transition));
287 } else {
288 return Err(anyhow!(
289 "Type of log move {} of alignment {} is not recognised.",
290 move_number,
291 alignment_number
292 ));
293 }
294 }
295
296 alignments.push(moves);
297 }
298
299 Ok(Self {
300 activity_key: activity_key,
301 alignments: alignments,
302 })
303 }
304}
305from_string!(LanguageOfAlignments);
306
307impl Infoable for LanguageOfAlignments {
308 fn info(&self, f: &mut impl std::io::Write) -> Result<()> {
309 writeln!(f, "Number of alignments\t\t{}", self.alignments.len())?;
310
311 writeln!(f, "")?;
312 self.get_activity_key().info(f)?;
313
314 Ok(writeln!(f, "")?)
315 }
316}
317
318#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone)]
319pub enum Move {
320 LogMove(Activity),
321 ModelMove(Activity, TransitionIndex),
322 SynchronousMove(Activity, TransitionIndex),
323 SilentMove(TransitionIndex),
324}
325
326impl Move {
327 pub fn get_transition(&self) -> Option<TransitionIndex> {
328 match self {
329 Move::LogMove(_) => None,
330 Move::ModelMove(_, transition)
331 | Move::SilentMove(transition)
332 | Move::SynchronousMove(_, transition) => Some(*transition),
333 }
334 }
335}
336
337#[cfg(any(test, feature = "testactivities"))]
338impl TestActivityKey for LanguageOfAlignments {
339 fn test_activity_key(&self) {
340 self.alignments.iter().for_each(|alignment| {
341 alignment.iter().for_each(|activity| match activity {
342 Move::SynchronousMove(activity, _)
343 | Move::LogMove(activity)
344 | Move::ModelMove(activity, _) => {
345 use crate::HasActivityKey;
346
347 self.activity_key().assert_activity_is_of_key(activity)
348 }
349 Move::SilentMove(_) => {}
350 })
351 });
352 }
353}