1
use log::debug;
2
use log::info;
3
use log::trace;
4
use oxidd::ldd::LDDManagerRef;
5
use rustc_hash::FxBuildHasher;
6
use rustc_hash::FxHashSet;
7
use streaming_iterator::StreamingIterator;
8

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

            
16
use crate::SymbolicLTS;
17
use crate::TransitionGroup;
18
use crate::height;
19
use crate::iter;
20

            
21
/// Converts a symbolic LDD LTS to an explicit LTS.
22
///
23
/// # Details
24
///
25
/// This basically applies the symbolic transitions to every state in the state
26
/// space, and constructs the explicit LTS.
27
201
pub fn convert_symbolic_lts<B: LtsBuilder<String>, L: SymbolicLTS>(
28
201
    manager: &LDDManagerRef,
29
201
    output: &mut B,
30
201
    lts: &L,
31
201
) -> Result<B::LTS, MercError> {
32
    // Compute for every read and write index its position in the transition vector.
33
201
    let mut read_positions = Vec::new();
34
201
    let mut write_positions = Vec::new();
35

            
36
1010
    for group in lts.transition_groups() {
37
1010
        let (rpos, wpos) = compute_positions(group);
38
1010

            
39
1010
        read_positions.push(rpos);
40
1010
        write_positions.push(wpos);
41
1010
    }
42

            
43
1010
    for (group_index, group) in lts.transition_groups().iter().enumerate() {
44
1010
        debug!("Transition group {}: {:?}", group_index, group,);
45

            
46
1010
        debug!("  Read indices: {:?}", read_positions[group_index]);
47
1010
        debug!("  Write indices: {:?}", write_positions[group_index]);
48
    }
49

            
50
    // Total number of states for progress reporting.
51
201
    let total_number_of_states = lts.states().len();
52
201
    info!(
53
        "Converting symbolic LTS to explicit LTS with {} states",
54
        LargeFormatter(total_number_of_states)
55
    );
56

            
57
    // Use floating-point division so progress reporting never divides by zero (mirrors the BDD variant).
58
201
    let total_states_f64 = total_number_of_states as f64;
59
201
    let state_progress = TimeProgress::new(
60
        move |number_of_states| {
61
            info!(
62
                "Added {} states to discovered ({}%)",
63
                LargeFormatter(number_of_states),
64
                (number_of_states as f64 * 100.0 / total_states_f64) as usize
65
            );
66
        },
67
        1,
68
    );
69

            
70
    // All states have been explored, so add them to the discovered set immediately.
71
201
    let mut discovered: IndexedSet<Vec<u32>, FxBuildHasher> = IndexedSet::new();
72
201
    let mut all_states_iter = iter(lts.states());
73
27249
    while let Some(state) = all_states_iter.next() {
74
27048
        let (_, inserted) = discovered.insert(state.clone());
75
27048
        debug_assert!(inserted, "State space contains duplicate states");
76
27048
        state_progress.print(discovered.len())
77
    }
78

            
79
    // Total number of states for progress reporting.
80
201
    let progress = TimeProgress::new(
81
45
        move |(number_of_states, number_of_transitions)| {
82
45
            info!(
83
                "Explored {} states and {} transitions ({}%)",
84
                LargeFormatter(number_of_states),
85
                LargeFormatter(number_of_transitions),
86
                (number_of_states as f64 * 100.0 / total_states_f64) as usize
87
            );
88
45
        },
89
        1,
90
    );
91

            
92
    // Keep track of outgoing transitions to avoid duplicates.
93
201
    let mut outgoing = FxHashSet::default();
94

            
95
    // Avoid reallocations.
96
201
    let mut target = vec![0u32; height(manager, lts.states())];
97

            
98
201
    let mut state_iter = iter(lts.states());
99
201
    let mut pos: usize = 0;
100

            
101
27249
    while let Some(state) = state_iter.next() {
102
        // Find the index of this state, it was already added before.
103
27048
        let state_index = discovered
104
27048
            .index(state)
105
27048
            .ok_or("Found state that was not in the state set")?;
106

            
107
        // Apply every transition group to this state.
108
135610
        for (group_index, group) in lts.transition_groups().iter().enumerate() {
109
135610
            let mut iter_transitions = iter(group.relation());
110
1477482
            'skip: while let Some(transition) = iter_transitions.next() {
111
                // Try to match the read parameters of this vector.
112
1341872
                for (index, i) in group.read_indices().iter().enumerate() {
113
995102
                    if state[*i as usize] != transition[read_positions[group_index][index]] {
114
                        // If they do not match, skip this transition.
115
804489
                        continue 'skip;
116
190613
                    }
117
                }
118

            
119
                // Apply the transition writes to the state vector.
120
537383
                target.clone_from_slice(state);
121
537383
                trace!("transition {:?}", transition);
122
2899132
                for (index, i) in group.write_indices().iter().enumerate() {
123
2899132
                    target[*i as usize] = transition[write_positions[group_index][index]];
124
2899132
                }
125

            
126
                // Find the action label.
127
537383
                let action_value = transition[group
128
537383
                    .action_label_index()
129
537383
                    .ok_or("Transition vector should at least have the action label")?];
130
537383
                let label = &lts.action_labels()[action_value as usize];
131

            
132
                // Find the target state index.
133
537383
                let target_index = discovered
134
537383
                    .index(&target)
135
537383
                    .ok_or("Found state that was not in the state set")?;
136
                // Include the action label in the dedup key: the same source/target pair can be
137
                // connected by transitions with different labels, and dropping any of them would
138
                // silently lose behavior.
139
537383
                if outgoing.insert((*state_index, action_value, *target_index)) {
140
536019
                    trace!(
141
                        " Found transition in {group_index} from {:?} to {:?} with label {:?}",
142
                        state, target, label
143
                    );
144

            
145
536019
                    output.add_transition(StateIndex::new(*state_index), label, StateIndex::new(*target_index))?;
146
1364
                }
147
            }
148
        }
149

            
150
27048
        progress.print((pos, output.num_of_transitions()));
151
27048
        pos += 1;
152

            
153
        // Clear the outgoing set for the next state.
154
27048
        outgoing.clear();
155
    }
156

            
157
    // Find the initial state.
158
201
    let mut state_iter = iter(lts.initial_state());
159
201
    let initial_state = state_iter.next().ok_or("Symbolic LTS has no initial state")?;
160

            
161
201
    let initial_state_index = discovered
162
201
        .index(initial_state)
163
201
        .ok_or("Initial state was not found in the discovered state set")?;
164

            
165
201
    output.finish(StateIndex::new(*initial_state_index))
166
201
}
167

            
168
/// Computes the positions of the read and write indices in the transition vector.
169
1010
fn compute_positions<G: TransitionGroup>(group: &G) -> (Vec<usize>, Vec<usize>) {
170
    // Ensure indices are non-decreasing; merge relies on sorted inputs.
171
1010
    debug_assert!(group.read_indices().is_sorted(), "read_indices must be sorted");
172
1010
    debug_assert!(group.write_indices().is_sorted(), "write_indices must be sorted");
173

            
174
1010
    let mut rpos = Vec::with_capacity(group.read_indices().len());
175
1010
    let mut wpos = Vec::with_capacity(group.write_indices().len());
176

            
177
1010
    let mut ri = 0usize;
178
1010
    let mut wi = 0usize;
179
1010
    let mut index = 0usize;
180

            
181
7642
    while ri < group.read_indices().len() && wi < group.write_indices().len() {
182
6632
        if group.read_indices()[ri] <= group.write_indices()[wi] {
183
3487
            rpos.push(index);
184
3487
            ri += 1;
185
3487
        } else {
186
3145
            wpos.push(index);
187
3145
            wi += 1;
188
3145
        }
189
6632
        index += 1;
190
    }
191

            
192
2005
    while ri < group.read_indices().len() {
193
995
        rpos.push(index);
194
995
        ri += 1;
195
995
        index += 1;
196
995
    }
197

            
198
2498
    while wi < group.write_indices().len() {
199
1488
        wpos.push(index);
200
1488
        wi += 1;
201
1488
        index += 1;
202
1488
    }
203
1010
    (rpos, wpos)
204
1010
}
205

            
206
#[cfg(test)]
207
mod tests {
208
    use merc_lts::LTS;
209
    use merc_lts::LtsBuilderMem;
210
    use merc_utilities::test_logger;
211

            
212
    use crate::read_symbolic_lts;
213

            
214
    use super::convert_symbolic_lts;
215

            
216
    #[test]
217
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
218
1
    fn test_convert_symbolic_lts() {
219
1
        test_logger();
220

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

            
223
1
        let manager = oxidd::ldd::new_manager(2048, 1024, 1);
224
1
        let symbolic_lts = read_symbolic_lts(&manager, &input[..]).unwrap();
225

            
226
1
        let mut builder = LtsBuilderMem::new(Vec::new(), Vec::new());
227
1
        let lts = convert_symbolic_lts(&manager, &mut builder, &symbolic_lts).unwrap();
228

            
229
1
        debug_assert_eq!(lts.num_of_states(), 74);
230
1
        debug_assert_eq!(lts.num_of_transitions(), 92);
231
1
    }
232
}