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

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

            
17
use crate::CubeIterAll;
18
use crate::SatCountCache;
19
use crate::SymbolicLtsBdd;
20
use crate::approx_satcount;
21
use crate::to_value;
22

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

            
39
201
    let state_variable_indices = state_variables
40
201
        .iter()
41
201
        .enumerate()
42
3398
        .map(|(index, &var)| (var, index))
43
201
        .collect::<FxHashMap<VarNo, usize>>();
44
201
    let next_state_variable_indices = next_state_variables
45
201
        .iter()
46
201
        .enumerate()
47
3398
        .map(|(index, &var)| (var, index))
48
201
        .collect::<FxHashMap<VarNo, usize>>();
49

            
50
201
    let state_group_offsets = {
51
201
        let mut offsets = Vec::with_capacity(lts.state_variable_num_of_bits().len());
52
201
        let mut offset = 0usize;
53

            
54
1111
        for &num_of_bits in lts.state_variable_num_of_bits() {
55
1111
            offsets.push(offset);
56
1111
            offset += num_of_bits as usize;
57
1111
        }
58

            
59
201
        offsets
60
    };
61

            
62
201
    let mut read_positions = Vec::new();
63
201
    let mut write_positions = Vec::new();
64
201
    let mut transition_variables = Vec::new();
65
201
    let mut action_positions = Vec::new();
66

            
67
1010
    for group in lts.transition_groups() {
68
1010
        let read_group: FxHashSet<usize> = group
69
1010
            .read_variables()
70
1010
            .iter()
71
8554
            .map(|var| {
72
8554
                *state_variable_indices
73
8554
                    .get(var)
74
8554
                    .expect("Read variable was not found in state variables")
75
8554
            })
76
1010
            .collect();
77

            
78
1010
        let write_group: FxHashSet<usize> = group
79
1010
            .write_variables()
80
1010
            .iter()
81
8832
            .map(|var| {
82
8832
                *next_state_variable_indices
83
8832
                    .get(var)
84
8832
                    .expect("Write variable was not found in next-state variables")
85
8832
            })
86
1010
            .collect();
87

            
88
1010
        let mut variables = Vec::new();
89

            
90
1010
        let (rpos, wpos) = compute_positions(
91
1010
            &state_variables,
92
1010
            &next_state_variables,
93
1010
            &action_variables,
94
1010
            &state_group_offsets,
95
1010
            &read_group,
96
1010
            &write_group,
97
1010
            &mut variables,
98
1010
        );
99

            
100
1010
        let action_start = variables.len() - action_variables.len();
101
1010
        action_positions.push(action_start);
102
1010
        transition_variables.push(variables);
103

            
104
1010
        read_positions.push(rpos);
105
1010
        write_positions.push(wpos);
106
    }
107

            
108
    // Total number of states for progress reporting.
109
201
    let mut satcount_cache = SatCountCache::new();
110
201
    let total_number_of_states =
111
201
        approx_satcount(lts.states(), lts.state_variables().len() as VarNo, &mut satcount_cache);
112
201
    info!(
113
        "Converting symbolic LTS to explicit LTS with {} states",
114
        total_number_of_states
115
    );
116

            
117
201
    let total_states_f64 = total_number_of_states.as_f64();
118
201
    let state_progress = TimeProgress::new(
119
        move |number_of_states| {
120
            info!(
121
                "Added {} states to discovered ({}%)",
122
                LargeFormatter(number_of_states),
123
                (number_of_states as f64 * 100.0 / total_states_f64) as usize
124
            );
125
        },
126
        1,
127
    );
128

            
129
    // All states have been explored, so add them to the discovered set immediately.
130
201
    let mut discovered: IndexedSet<Vec<OptBool>, FxBuildHasher> = IndexedSet::new();
131
11328
    for cube in CubeIterAll::with_variables(lts.states(), &state_variables) {
132
11328
        let mut cube = cube?;
133
11328
        concretize_cube(&mut cube);
134

            
135
11328
        let (_, inserted) = discovered.insert(cube);
136
11328
        debug_assert!(inserted, "State space contains duplicate states");
137
11328
        state_progress.print(discovered.len())
138
    }
139

            
140
    // Total number of states for progress reporting.
141
201
    let progress = TimeProgress::new(
142
67
        move |(number_of_states, number_of_transitions)| {
143
67
            info!(
144
                "Explored {} states and {} transitions ({}%)",
145
                LargeFormatter(number_of_states),
146
                LargeFormatter(number_of_transitions),
147
                (number_of_states as f64 * 100.0 / total_states_f64) as usize
148
            );
149
67
        },
150
        1,
151
    );
152

            
153
    // Keep track of outgoing transitions to avoid duplicates.
154
201
    let mut outgoing = FxHashSet::default();
155

            
156
    // Avoid reallocations.
157
201
    let mut target = vec![OptBool::False; state_variables.len()];
158

            
159
201
    let mut index = 0usize;
160
11328
    for cube in CubeIterAll::with_variables(lts.states(), &state_variables) {
161
11328
        let mut cube = cube?;
162
11328
        concretize_cube(&mut cube);
163

            
164
        // Find the index of this state, it was already added before.
165
11328
        let state_index = discovered
166
11328
            .index(&cube)
167
11328
            .ok_or("Found state that was not in the state set")?;
168

            
169
        // Apply every transition group to this state.
170
57010
        for (group_index, group) in lts.transition_groups().iter().enumerate() {
171
970684
            for transition in CubeIterAll::with_variables(group.relation(), &transition_variables[group_index]) {
172
970684
                let mut transition = transition?;
173
970684
                concretize_cube(&mut transition);
174

            
175
                // Try to match the read parameters of this vector.
176
970684
                let mut matches = true;
177
1730210
                for (&read_variable, &read_position) in
178
970684
                    group.read_variables().iter().zip(read_positions[group_index].iter())
179
                {
180
1730210
                    let state_pos = *state_variable_indices
181
1730210
                        .get(&read_variable)
182
1730210
                        .expect("Read variable was not found in state variables");
183
1730210
                    if cube[state_pos] != transition[read_position] {
184
765772
                        matches = false;
185
765772
                        break;
186
964438
                    }
187
                }
188

            
189
970684
                if !matches {
190
765772
                    continue;
191
204912
                }
192

            
193
                // Apply the transition writes to the state vector.
194
204912
                target.clone_from_slice(&cube);
195
204912
                trace!("transition {:?}", transition);
196
3701163
                for (&write_variable, &write_position) in
197
204912
                    group.write_variables().iter().zip(write_positions[group_index].iter())
198
3701163
                {
199
3701163
                    let state_pos = *next_state_variable_indices
200
3701163
                        .get(&write_variable)
201
3701163
                        .expect("Write variable was not found in next-state variables");
202
3701163
                    target[state_pos] = transition[write_position];
203
3701163
                }
204

            
205
                // Find the action label.
206
204912
                let action_bits = &transition[action_positions[group_index]..];
207
204912
                let action_value = to_value(action_bits);
208
204912
                let label = lts
209
204912
                    .action_labels()
210
204912
                    .get(action_value as usize)
211
204912
                    .ok_or("Found transition with unknown action label")?;
212

            
213
                // Find the target state index.
214
204912
                let target_index = discovered
215
204912
                    .index(&target)
216
204912
                    .ok_or("Found state that was not in the state set")?;
217
                // Include the action label in the dedup key: the same source/target pair can be
218
                // connected by transitions with different labels, and dropping any of them would
219
                // silently lose behavior.
220
204912
                if outgoing.insert((*state_index, action_value, *target_index)) {
221
204239
                    trace!(
222
                        " Found transition in {group_index} from {:?} to {:?} with label {:?}",
223
                        cube, target, label
224
                    );
225

            
226
204239
                    output.add_transition(StateIndex::new(*state_index), label, StateIndex::new(*target_index))?;
227
673
                }
228
            }
229
        }
230

            
231
11328
        progress.print((index, output.num_of_transitions()));
232
11328
        index += 1;
233

            
234
        // Clear the outgoing set for the next state.
235
11328
        outgoing.clear();
236
    }
237

            
238
    // Find the initial state.
239
201
    let mut initial_state = CubeIterAll::with_variables(lts.initial_state(), &state_variables);
240
201
    let mut initial_state = initial_state.next().ok_or("Symbolic LTS has no initial state")??;
241
201
    concretize_cube(&mut initial_state);
242

            
243
201
    let initial_state_index = discovered
244
201
        .index(&initial_state)
245
201
        .ok_or("Initial state was not found in the discovered state set")?;
246

            
247
201
    output.finish(StateIndex::new(*initial_state_index))
248
201
}
249

            
250
/// Replace all don't-care bits in the cube with false, to get a concrete state vector.
251
993541
fn concretize_cube(cube: &mut [OptBool]) {
252
24698613
    for bit in cube {
253
24698613
        if *bit == OptBool::None {
254
            *bit = OptBool::False;
255
24698613
        }
256
    }
257
993541
}
258

            
259
/// Computes the positions of the read and write indices in the transition vector.
260
1010
fn compute_positions(
261
1010
    state_variables: &[VarNo],
262
1010
    next_state_variables: &[VarNo],
263
1010
    action_variables: &[VarNo],
264
1010
    state_group_offsets: &[usize],
265
1010
    read_group: &FxHashSet<usize>,
266
1010
    write_group: &FxHashSet<usize>,
267
1010
    transition_variables: &mut Vec<VarNo>,
268
1010
) -> (Vec<usize>, Vec<usize>) {
269
1010
    let mut rpos = Vec::new();
270
1010
    let mut wpos = Vec::new();
271

            
272
5610
    for (state_group, &offset) in state_group_offsets.iter().enumerate() {
273
5610
        let end = state_group_offsets
274
5610
            .get(state_group + 1)
275
5610
            .copied()
276
5610
            .unwrap_or(state_variables.len());
277

            
278
5610
        if read_group.contains(&offset) {
279
8554
            for bit in state_variables.iter().take(end).skip(offset) {
280
8554
                rpos.push(transition_variables.len());
281
8554
                transition_variables.push(*bit);
282
8554
            }
283
2899
        }
284

            
285
5610
        if write_group.contains(&offset) {
286
8832
            for bit in next_state_variables.iter().take(end).skip(offset) {
287
8832
                wpos.push(transition_variables.len());
288
8832
                transition_variables.push(*bit);
289
8832
            }
290
2802
        }
291
    }
292

            
293
1010
    transition_variables.extend(action_variables.iter().copied());
294

            
295
1010
    (rpos, wpos)
296
1010
}
297

            
298
#[cfg(test)]
299
mod tests {
300
    use merc_lts::LTS;
301
    use merc_lts::LtsBuilderMem;
302
    use merc_reduction::Equivalence;
303
    use merc_reduction::compare_lts;
304
    use merc_utilities::Timing;
305
    use merc_utilities::random_test;
306
    use merc_utilities::test_logger;
307

            
308
    use crate::SymbolicLtsBdd;
309
    use crate::convert_symbolic_lts;
310
    use crate::convert_symbolic_lts_bdd;
311
    use crate::random_symbolic_lts;
312
    use crate::read_symbolic_lts;
313

            
314
    #[test]
315
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
316
1
    fn test_convert_symbolic_lts_bdd_abp() {
317
1
        test_logger();
318

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

            
321
1
        let ldd_manager = oxidd::ldd::new_manager(2048, 1024, 1);
322
1
        let bdd_manager = oxidd::bdd::new_manager(2048, 1024, 1);
323
1
        let symbolic_lts = read_symbolic_lts(&ldd_manager, &input[..]).unwrap();
324
1
        let symbolic_lts_bdd = SymbolicLtsBdd::from_symbolic_lts(&ldd_manager, &bdd_manager, &symbolic_lts).unwrap();
325

            
326
1
        let mut builder = LtsBuilderMem::new(Vec::new(), Vec::new());
327
1
        let lts = convert_symbolic_lts_bdd(&bdd_manager, &mut builder, &symbolic_lts_bdd).unwrap();
328

            
329
1
        assert_eq!(lts.num_of_states(), 74);
330
1
        assert_eq!(lts.num_of_transitions(), 92);
331
1
    }
332

            
333
    #[test]
334
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
335
1
    fn test_random_convert_symbolic_lts_bdd() {
336
100
        random_test(100, |rng| {
337
100
            let ldd_manager = oxidd::ldd::new_manager(2048, 1024, 1);
338

            
339
100
            let lts = random_symbolic_lts(rng, &ldd_manager, 10, 5).unwrap();
340
100
            let mut builder = LtsBuilderMem::new(Vec::new(), Vec::new());
341
100
            let explicit_lts = convert_symbolic_lts(&ldd_manager, &mut builder, &lts).unwrap();
342

            
343
100
            let bdd_manager = oxidd::bdd::new_manager(2028, 2028, 1);
344
100
            let lts_bdd = SymbolicLtsBdd::from_symbolic_lts(&ldd_manager, &bdd_manager, &lts).unwrap();
345

            
346
100
            let mut builder = LtsBuilderMem::new(Vec::new(), Vec::new());
347
100
            let explicit_lts_bdd = convert_symbolic_lts_bdd(&bdd_manager, &mut builder, &lts_bdd).unwrap();
348

            
349
100
            assert!(
350
100
                compare_lts(
351
100
                    Equivalence::StrongBisim,
352
100
                    explicit_lts,
353
100
                    explicit_lts_bdd,
354
                    false,
355
100
                    &Timing::new()
356
                ),
357
                "Both the explicit LTS and the one converted from the symbolic LTS should be bisimilar"
358
            );
359
100
        });
360
1
    }
361
}