1
use log::info;
2
use log::trace;
3
use oxidd::BooleanFunction;
4
use oxidd::VarNo;
5
use oxidd::bdd::BDDManagerRef;
6
use oxidd::util::OptBool;
7
use oxidd::util::SatCountCache;
8
use rustc_hash::FxBuildHasher;
9
use rustc_hash::FxHashMap;
10
use rustc_hash::FxHashSet;
11

            
12
use merc_collections::IndexedSet;
13
use merc_io::LargeFormatter;
14
use merc_io::TimeProgress;
15
use merc_lts::LtsBuilder;
16
use merc_lts::StateIndex;
17
use merc_utilities::MercError;
18

            
19
use crate::CubeIterAll;
20
use crate::SymbolicLtsBdd;
21
use crate::to_value;
22

            
23
988688
fn concretize_cube(cube: &mut [OptBool]) {
24
24685445
    for bit in cube {
25
24685445
        if *bit == OptBool::None {
26
36522
            *bit = OptBool::False;
27
24648923
        }
28
    }
29
988688
}
30

            
31
/// Converts a symbolic LTS to an explicit LTS.
32
///
33
/// # Details
34
///
35
/// This basically applies the symbolic transitions to every state in the state
36
/// space, and constructs the explicit LTS.
37
201
pub fn convert_symbolic_lts_bdd<B: LtsBuilder<String>>(
38
201
    _manager_ref: &BDDManagerRef,
39
201
    output: &mut B,
40
201
    lts: &SymbolicLtsBdd,
41
201
) -> Result<B::LTS, MercError> {
42
    // Compute for every read and write index its position in the transition vector.
43
201
    let state_variables = lts.state_variables().to_vec();
44
201
    let next_state_variables = lts.next_state_variables().to_vec();
45
201
    let action_variables = lts.action_variables().to_vec();
46

            
47
201
    let state_variable_indices = state_variables
48
201
        .iter()
49
201
        .enumerate()
50
3382
        .map(|(index, &var)| (var, index))
51
201
        .collect::<FxHashMap<VarNo, usize>>();
52
201
    let next_state_variable_indices = next_state_variables
53
201
        .iter()
54
201
        .enumerate()
55
3382
        .map(|(index, &var)| (var, index))
56
201
        .collect::<FxHashMap<VarNo, usize>>();
57

            
58
201
    let state_group_offsets = {
59
201
        let mut offsets = Vec::with_capacity(lts.state_variable_num_of_bits().len());
60
201
        let mut offset = 0usize;
61

            
62
1111
        for &num_of_bits in lts.state_variable_num_of_bits() {
63
1111
            offsets.push(offset);
64
1111
            offset += num_of_bits as usize;
65
1111
        }
66

            
67
201
        offsets
68
    };
69

            
70
201
    let read_groups = lts
71
201
        .transition_groups()
72
201
        .iter()
73
1010
        .map(|group| {
74
1010
            group
75
1010
                .read_variables()
76
1010
                .iter()
77
8291
                .map(|var| {
78
8291
                    *state_variable_indices
79
8291
                        .get(var)
80
8291
                        .expect("Read variable was not found in state variables")
81
8291
                })
82
1010
                .collect::<FxHashSet<usize>>()
83
1010
        })
84
201
        .collect::<Vec<_>>();
85

            
86
201
    let write_groups = lts
87
201
        .transition_groups()
88
201
        .iter()
89
1010
        .map(|group| {
90
1010
            group
91
1010
                .write_variables()
92
1010
                .iter()
93
8540
                .map(|var| {
94
8540
                    *next_state_variable_indices
95
8540
                        .get(var)
96
8540
                        .expect("Write variable was not found in next-state variables")
97
8540
                })
98
1010
                .collect::<FxHashSet<usize>>()
99
1010
        })
100
201
        .collect::<Vec<_>>();
101

            
102
201
    let mut read_positions = Vec::new();
103
201
    let mut write_positions = Vec::new();
104
201
    let mut transition_variables = Vec::new();
105
201
    let mut action_positions = Vec::new();
106

            
107
1010
    for (group_index, _group) in lts.transition_groups().iter().enumerate() {
108
1010
        let mut variables = Vec::new();
109
1010

            
110
1010
        let (rpos, wpos) = compute_positions(
111
1010
            &state_variables,
112
1010
            &next_state_variables,
113
1010
            &action_variables,
114
1010
            &state_group_offsets,
115
1010
            &read_groups[group_index],
116
1010
            &write_groups[group_index],
117
1010
            &mut variables,
118
1010
        );
119
1010

            
120
1010
        let action_start = variables.len() - action_variables.len();
121
1010
        action_positions.push(action_start);
122
1010
        transition_variables.push(variables);
123
1010

            
124
1010
        read_positions.push(rpos);
125
1010
        write_positions.push(wpos);
126
1010
    }
127

            
128
    // Total number of states for progress reporting.
129
201
    let total_number_of_states: u64 = lts.states().sat_count(
130
201
        lts.state_variables().len() as VarNo,
131
201
        &mut SatCountCache::<u64, FxBuildHasher>::default(),
132
    );
133
201
    info!(
134
        "Converting symbolic LTS to explicit LTS with {} states",
135
        LargeFormatter(total_number_of_states)
136
    );
137

            
138
201
    let state_progress = TimeProgress::new(
139
3
        move |number_of_states| {
140
3
            info!(
141
                "Added {} states to discovered ({}%)",
142
                LargeFormatter(number_of_states),
143
                number_of_states * 100 / total_number_of_states as usize
144
            );
145
3
        },
146
        1,
147
    );
148

            
149
    // All states have been explored, so add them to the discovered set immediately.
150
201
    let mut discovered: IndexedSet<Vec<OptBool>, FxBuildHasher> = IndexedSet::new();
151
19679
    for cube in CubeIterAll::with_variables(lts.states(), &state_variables) {
152
19679
        let mut cube = cube?;
153
19679
        concretize_cube(&mut cube);
154

            
155
19679
        let (_, inserted) = discovered.insert(cube);
156
19679
        debug_assert!(inserted, "State space contains duplicate states");
157
19679
        state_progress.print(discovered.len())
158
    }
159

            
160
    // Total number of states for progress reporting.
161
201
    let progress = TimeProgress::new(
162
141
        move |(number_of_states, number_of_transitions)| {
163
141
            info!(
164
                "Explored {} states and {} transitions ({}%)",
165
                LargeFormatter(number_of_states),
166
                LargeFormatter(number_of_transitions),
167
                number_of_states * 100 / total_number_of_states as usize
168
            );
169
141
        },
170
        1,
171
    );
172

            
173
    // Keep track of outgoing transitions to avoid duplicates.
174
201
    let mut outgoing = FxHashSet::default();
175

            
176
    // Avoid reallocations.
177
201
    let mut target = vec![OptBool::False; state_variables.len()];
178

            
179
201
    let mut index = 0usize;
180
19679
    for cube in CubeIterAll::with_variables(lts.states(), &state_variables) {
181
19679
        let mut cube = cube?;
182
19679
        concretize_cube(&mut cube);
183

            
184
        // Find the index of this state, it was already added before.
185
19679
        let state_index = discovered
186
19679
            .index(&cube)
187
19679
            .ok_or("Found state that was not in the state set")?;
188

            
189
        // Apply every transition group to this state.
190
98765
        for (group_index, group) in lts.transition_groups().iter().enumerate() {
191
949129
            for transition in CubeIterAll::with_variables(group.relation(), &transition_variables[group_index]) {
192
949129
                let mut transition = transition?;
193
949129
                concretize_cube(&mut transition);
194

            
195
                // Try to match the read parameters of this vector.
196
949129
                let mut matches = true;
197
1673521
                for (&read_variable, &read_position) in
198
949129
                    group.read_variables().iter().zip(read_positions[group_index].iter())
199
                {
200
1673521
                    let state_pos = *state_variable_indices
201
1673521
                        .get(&read_variable)
202
1673521
                        .expect("Read variable was not found in state variables");
203
1673521
                    if cube[state_pos] != transition[read_position] {
204
653292
                        matches = false;
205
653292
                        break;
206
1020229
                    }
207
                }
208

            
209
949129
                if !matches {
210
653292
                    continue;
211
295837
                }
212

            
213
                // Apply the transition writes to the state vector.
214
295837
                target.clone_from_slice(&cube);
215
295837
                trace!("transition {:?}", transition);
216
4519253
                for (&write_variable, &write_position) in
217
295837
                    group.write_variables().iter().zip(write_positions[group_index].iter())
218
4519253
                {
219
4519253
                    let state_pos = *next_state_variable_indices
220
4519253
                        .get(&write_variable)
221
4519253
                        .expect("Write variable was not found in next-state variables");
222
4519253
                    target[state_pos] = transition[write_position];
223
4519253
                }
224

            
225
                // Find the action label.
226
295837
                let action_bits = &transition[action_positions[group_index]..];
227
295837
                let action_value = to_value(action_bits);
228
295837
                let label = lts
229
295837
                    .action_labels()
230
295837
                    .get(action_value as usize)
231
295837
                    .ok_or("Found transition with unknown action label")?;
232

            
233
                // Find the target state index.
234
295837
                let target_index = discovered
235
295837
                    .index(&target)
236
295837
                    .ok_or("Found state that was not in the state set")?;
237
                // Include the action label in the dedup key: the same source/target pair can be
238
                // connected by transitions with different labels, and dropping any of them would
239
                // silently lose behavior.
240
295837
                if outgoing.insert((*state_index, action_value, *target_index)) {
241
295368
                    trace!(
242
                        " Found transition in {group_index} from {:?} to {:?} with label {:?}",
243
                        cube, target, label
244
                    );
245

            
246
295368
                    output.add_transition(StateIndex::new(*state_index), label, StateIndex::new(*target_index))?;
247
469
                }
248
            }
249
        }
250

            
251
19679
        progress.print((index, output.num_of_transitions()));
252
19679
        index += 1;
253

            
254
        // Clear the outgoing set for the next state.
255
19679
        outgoing.clear();
256
    }
257

            
258
    // Find the initial state.
259
201
    let mut initial_state = CubeIterAll::with_variables(lts.initial_state(), &state_variables);
260
201
    let mut initial_state = initial_state.next().ok_or("Symbolic LTS has no initial state")??;
261
201
    concretize_cube(&mut initial_state);
262

            
263
201
    let initial_state_index = discovered
264
201
        .index(&initial_state)
265
201
        .ok_or("Initial state was not found in the discovered state set")?;
266

            
267
201
    output.finish(StateIndex::new(*initial_state_index))
268
201
}
269

            
270
/// Computes the positions of the read and write indices in the transition vector.
271
1010
fn compute_positions(
272
1010
    state_variables: &[VarNo],
273
1010
    next_state_variables: &[VarNo],
274
1010
    action_variables: &[VarNo],
275
1010
    state_group_offsets: &[usize],
276
1010
    read_group: &FxHashSet<usize>,
277
1010
    write_group: &FxHashSet<usize>,
278
1010
    transition_variables: &mut Vec<VarNo>,
279
1010
) -> (Vec<usize>, Vec<usize>) {
280
1010
    let mut rpos = Vec::new();
281
1010
    let mut wpos = Vec::new();
282

            
283
5610
    for (state_group, &offset) in state_group_offsets.iter().enumerate() {
284
5610
        let end = state_group_offsets
285
5610
            .get(state_group + 1)
286
5610
            .copied()
287
5610
            .unwrap_or(state_variables.len());
288

            
289
5610
        if read_group.contains(&offset) {
290
8291
            for bit in state_variables.iter().take(end).skip(offset) {
291
8291
                rpos.push(transition_variables.len());
292
8291
                transition_variables.push(*bit);
293
8291
            }
294
2953
        }
295

            
296
5610
        if write_group.contains(&offset) {
297
8540
            for bit in next_state_variables.iter().take(end).skip(offset) {
298
8540
                wpos.push(transition_variables.len());
299
8540
                transition_variables.push(*bit);
300
8540
            }
301
2866
        }
302
    }
303

            
304
1010
    transition_variables.extend(action_variables.iter().copied());
305

            
306
1010
    (rpos, wpos)
307
1010
}
308

            
309
#[cfg(test)]
310
mod tests {
311
    use merc_ldd::Storage;
312
    use merc_lts::LTS;
313
    use merc_lts::LtsBuilderMem;
314
    use merc_reduction::Equivalence;
315
    use merc_reduction::compare_lts;
316
    use merc_utilities::Timing;
317
    use merc_utilities::random_test;
318
    use merc_utilities::test_logger;
319

            
320
    use crate::SymbolicLtsBdd;
321
    use crate::convert_symbolic_lts;
322
    use crate::convert_symbolic_lts_bdd;
323
    use crate::random_symbolic_lts;
324
    use crate::read_symbolic_lts;
325

            
326
    #[test]
327
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
328
1
    fn test_convert_symbolic_lts_bdd_abp() {
329
1
        test_logger();
330

            
331
1
        let input = include_bytes!("../../../examples/lts/abp.sym");
332

            
333
1
        let mut storage = Storage::new();
334
1
        let manager_ref = oxidd::bdd::new_manager(2048, 1024, 1);
335
1
        let symbolic_lts = read_symbolic_lts(&mut storage, &input[..]).unwrap();
336
1
        let symbolic_lts_bdd = SymbolicLtsBdd::from_symbolic_lts(&mut storage, &manager_ref, &symbolic_lts).unwrap();
337

            
338
1
        let mut builder = LtsBuilderMem::new(Vec::new(), Vec::new());
339
1
        let lts = convert_symbolic_lts_bdd(&manager_ref, &mut builder, &symbolic_lts_bdd).unwrap();
340

            
341
1
        assert_eq!(lts.num_of_states(), 74);
342
1
        assert_eq!(lts.num_of_transitions(), 92);
343
1
    }
344

            
345
    #[test]
346
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
347
1
    fn test_random_convert_symbolic_lts_bdd() {
348
100
        random_test(100, |rng| {
349
100
            let mut storage = Storage::new();
350

            
351
100
            let lts = random_symbolic_lts(rng, &mut storage, 10, 5).unwrap();
352
100
            let mut builder = LtsBuilderMem::new(Vec::new(), Vec::new());
353
100
            let explicit_lts = convert_symbolic_lts(&mut storage, &mut builder, &lts).unwrap();
354

            
355
100
            let manager_ref = oxidd::bdd::new_manager(2028, 2028, 1);
356
100
            let lts_bdd = SymbolicLtsBdd::from_symbolic_lts(&mut storage, &manager_ref, &lts).unwrap();
357

            
358
100
            let mut builder = LtsBuilderMem::new(Vec::new(), Vec::new());
359
100
            let explicit_lts_bdd = convert_symbolic_lts_bdd(&manager_ref, &mut builder, &lts_bdd).unwrap();
360

            
361
100
            assert!(
362
100
                compare_lts(
363
100
                    Equivalence::StrongBisim,
364
100
                    explicit_lts,
365
100
                    explicit_lts_bdd,
366
                    false,
367
100
                    &mut Timing::new()
368
                ),
369
                "Both the explicit LTS and the one converted from the symbolic LTS should be bisimilar"
370
            );
371
100
        });
372
1
    }
373
}