1
use std::collections::HashMap;
2
use std::io::BufReader;
3
use std::io::BufWriter;
4
use std::io::Read;
5
use std::io::Write;
6

            
7
use log::info;
8
use merc_aterm::ATerm;
9
use merc_aterm::ATermInt;
10
use merc_aterm::ATermList;
11
use merc_aterm::ATermRead;
12
use merc_aterm::ATermStreamable;
13
use merc_aterm::ATermWrite;
14
use merc_aterm::BinaryATermReader;
15
use merc_aterm::BinaryATermWriter;
16
use merc_aterm::Symbol;
17
use merc_aterm::is_list_term;
18
use merc_data::DataSpecification;
19
use merc_io::TimeProgress;
20
use merc_utilities::MercError;
21

            
22
use crate::LTS;
23
use crate::LabelledTransitionSystem;
24
use crate::LtsBuilder;
25
use crate::MultiAction;
26
use crate::StateIndex;
27

            
28
/// Loads a labelled transition system from the binary 'lts' format of the mCRL2 toolset.
29
101
pub fn read_lts(
30
101
    reader: impl Read,
31
101
    hidden_labels: Vec<String>,
32
101
) -> Result<LabelledTransitionSystem<MultiAction>, MercError> {
33
101
    info!("Reading LTS in .lts format...");
34

            
35
101
    let mut reader = BinaryATermReader::new(BufReader::new(reader))?;
36

            
37
101
    if reader.read_aterm()? != Some(lts_marker()) {
38
        return Err("Stream does not contain a labelled transition system (LTS).".into());
39
101
    }
40

            
41
    // Read the data specification, parameters, and actions.
42
101
    let _data_spec = DataSpecification::read(&mut reader)?;
43
101
    let _parameters = reader.read_aterm()?;
44
101
    let _actions = reader.read_aterm()?;
45

            
46
    // Use a cache to avoid translating the same multi-action multiple times.
47
101
    let mut multi_actions: HashMap<ATerm, MultiAction> = HashMap::new();
48

            
49
    // The initial state is not known yet.
50
101
    let mut initial_state: Option<StateIndex> = None;
51
101
    let mut builder = LtsBuilder::new(Vec::new(), hidden_labels);
52

            
53
101
    let progress = TimeProgress::new(
54
        |num_of_transitions| {
55
            info!("Read {num_of_transitions} transitions...");
56
        },
57
        1,
58
    );
59

            
60
    loop {
61
93551
        let term = reader.read_aterm()?;
62
93551
        match term {
63
93450
            Some(t) => {
64
93450
                if t == transition_marker() {
65
93274
                    let from: ATermInt = reader.read_aterm()?.ok_or("Missing from state")?.into();
66
93274
                    let label = reader.read_aterm()?.ok_or("Missing transition label")?;
67
93274
                    let to: ATermInt = reader.read_aterm()?.ok_or("Missing to state")?.into();
68

            
69
93274
                    if let Some(multi_action) = multi_actions.get(&label) {
70
92955
                        // Multi-action already exists in the cache.
71
92955
                        builder.add_transition(
72
92955
                            StateIndex::new(from.value()),
73
92955
                            multi_action,
74
92955
                            StateIndex::new(to.value()),
75
92955
                        );
76
92955
                    } else {
77
                        // New multi-action found, add it to the builder.
78
319
                        let multi_action = MultiAction::from_mcrl2_aterm(label.clone())?;
79
319
                        multi_actions.insert(label.clone(), multi_action.clone());
80
319
                        builder.add_transition(
81
319
                            StateIndex::new(from.value()),
82
319
                            &multi_action,
83
319
                            StateIndex::new(to.value()),
84
                        );
85
                    }
86

            
87
93274
                    progress.print(builder.num_of_transitions());
88
176
                } else if t == probabilistic_transition_mark() {
89
                    return Err("Probabilistic transitions are not supported yet.".into());
90
176
                } else if is_list_term(&t) {
91
74
                    // State labels can be ignored for the reduction algorithm.
92
102
                } else if t == initial_state_marker() {
93
101
                    initial_state = Some(StateIndex::new(
94
101
                        ATermInt::from(reader.read_aterm()?.ok_or("Missing initial state")?).value(),
95
                    ));
96
1
                }
97
            }
98
101
            None => break, // The default constructed term indicates the end of the stream.
99
        }
100
    }
101
101
    info!("Finished reading LTS");
102

            
103
101
    Ok(builder.finish(initial_state.ok_or("Missing initial state")?))
104
101
}
105

            
106
/// Write a labelled transition system in binary 'lts' format to the given
107
/// writer. Requires that the labels are ATerm streamable. Note that the writer
108
/// is buffered internally using a `BufWriter`.
109
///
110
/// # Details
111
///
112
/// This format is built on top the ATerm binary format. The structure is as
113
/// follows:
114
///
115
/// ```plain
116
///     lts_marker: ATerm
117
///     data_spec: see [`merc_data::DataSpecification::write`]
118
///     parameters: ATermList
119
///     action_labels: ATermList
120
/// ```
121
/// 
122
/// Afterwards we can write the following elements in any order:
123
///
124
/// ```plain
125
/// initial state:
126
///    initial_state_marker: ATerm
127
///    state: ATermInt
128
///
129
/// transition:
130
///     transition_marker: ATerm
131
///     from: ATermInt
132
///     label: ATerm (the multi_action)
133
///     to: ATermInt
134
/// ```
135
///
136
/// state_label (index derived from order of appearance):
137
///    state_label: ATermList::<DataExpression>
138
100
pub fn write_lts<L>(writer: &mut impl Write, lts: &L) -> Result<(), MercError>
139
100
where
140
100
    L: LTS<Label = MultiAction>,
141
{
142
100
    info!("Writing LTS in .lts format...");
143

            
144
100
    let mut writer = BinaryATermWriter::new(BufWriter::new(writer))?;
145

            
146
100
    writer.write_aterm(&lts_marker())?;
147

            
148
    // Write the data specification, parameters, and actions.
149
100
    DataSpecification::default().write(&mut writer)?;
150
100
    writer.write_aterm(&ATermList::<ATerm>::empty().into())?; // Empty parameters
151
100
    writer.write_aterm(&ATermList::<ATerm>::empty().into())?; // Empty action labels
152

            
153
    // Convert the internal multi-actions to the ATerm representation that mCRL2 expects.
154
100
    let label_terms = lts
155
100
        .labels()
156
100
        .iter()
157
300
        .map(|label| label.to_mcrl2_aterm())
158
100
        .collect::<Result<Vec<ATerm>, MercError>>()?;
159

            
160
    // Write the initial state.
161
100
    writer.write_aterm(&initial_state_marker())?;
162
100
    writer.write_aterm(&ATermInt::new(*lts.initial_state_index()))?;
163

            
164
10000
    for state in lts.iter_states() {
165
93182
        for transition in lts.outgoing_transitions(state) {
166
93182
            writer.write_aterm(&transition_marker())?;
167
93182
            writer.write_aterm(&ATermInt::new(*state))?;
168
93182
            writer.write_aterm(&label_terms[transition.label.value()])?;
169
93182
            writer.write_aterm(&ATermInt::new(*transition.to))?;
170
        }
171
    }
172

            
173
100
    Ok(())
174
100
}
175

            
176
/// Returns the ATerm marker for a labelled transition system.
177
201
fn lts_marker() -> ATerm {
178
201
    ATerm::constant(&Symbol::new("labelled_transition_system", 0))
179
201
}
180

            
181
/// Returns the ATerm marker for a transition.
182
186632
fn transition_marker() -> ATerm {
183
186632
    ATerm::constant(&Symbol::new("transition", 0))
184
186632
}
185

            
186
/// Returns the ATerm marker for the initial state.
187
202
fn initial_state_marker() -> ATerm {
188
202
    ATerm::constant(&Symbol::new("initial_state", 0))
189
202
}
190

            
191
/// Returns the ATerm marker for the probabilistic transition.
192
176
fn probabilistic_transition_mark() -> ATerm {
193
176
    ATerm::constant(&Symbol::new("probabilistic_transition", 0))
194
176
}
195

            
196
#[cfg(test)]
197
mod tests {
198
    use super::*;
199

            
200
    use merc_utilities::random_test;
201

            
202
    use crate::LTS;
203
    use crate::random_lts_monolithic;
204

            
205
    #[test]
206
    #[cfg_attr(miri, ignore)] // Tests are too slow under miri.
207
1
    fn test_read_lts() {
208
1
        let lts = read_lts(include_bytes!("../../../examples/lts/abp.lts").as_ref(), vec![]).unwrap();
209

            
210
1
        assert_eq!(lts.num_of_states(), 74);
211
1
        assert_eq!(lts.num_of_transitions(), 92);
212
1
    }
213

            
214
    #[test]
215
    #[cfg_attr(miri, ignore)]
216
1
    fn test_random_lts_io() {
217
100
        random_test(100, |rng| {
218
100
            let lts = random_lts_monolithic::<MultiAction>(rng, 100, 3, 20);
219

            
220
100
            let mut buffer: Vec<u8> = Vec::new();
221
100
            write_lts(&mut buffer, &lts).unwrap();
222

            
223
100
            let lts_read = read_lts(&buffer[0..], vec![]).unwrap();
224

            
225
            // If labels are not used, the number of labels may be less. So find a remapping of old labels to new labels.
226
100
            let mapping = lts
227
100
                .labels()
228
100
                .iter()
229
100
                .enumerate()
230
600
                .filter_map(|(_i, label)| lts_read.labels().iter().position(|l| l == label))
231
100
                .collect::<Vec<_>>();
232

            
233
100
            assert!(lts.num_of_states() == lts_read.num_of_states());
234
100
            assert!(lts.num_of_transitions() == lts_read.num_of_transitions());
235

            
236
            // Check that all the outgoing transitions are the same.
237
10000
            for state_index in lts.iter_states() {
238
                // The labels
239
10000
                let transitions: Vec<_> = lts.outgoing_transitions(state_index).collect();
240
10000
                let transitions_read: Vec<_> = lts_read.outgoing_transitions(state_index).collect();
241

            
242
                // Check that transitions are the same, modulo label remapping.
243
93182
                transitions.iter().for_each(|t| {
244
93182
                    let mapped_label = mapping[t.label.value()];
245
93182
                    assert!(
246
93182
                        transitions_read
247
93182
                            .iter()
248
638822
                            .any(|tr| tr.to == t.to && tr.label.value() == mapped_label)
249
                    );
250
93182
                });
251
            }
252
100
        })
253
1
    }
254
}