1
use std::time::Instant;
2

            
3
use log::debug;
4
use log::trace;
5

            
6
use crate::quotient_lts;
7
use crate::reduction::sort_topological;
8
use crate::IndexedPartition;
9
use crate::LabelledTransitionSystem;
10
use crate::Partition;
11

            
12
/// Computes the strongly connected tau component partitioning of the given LTS.
13
59
pub fn tau_scc_decomposition(lts: &LabelledTransitionSystem) -> IndexedPartition {
14
490507
    let partition = scc_decomposition(lts, &|_, label_index, _| lts.is_hidden_label(label_index));
15
59
    if cfg!(debug_assertions) {
16
59
        let quotient_lts = quotient_lts(lts, &partition, true);
17
59
        debug_assert!(!has_tau_loop(&quotient_lts), "The SCC decomposition contains tau-loops");
18
    }
19
59
    partition
20
59
}
21

            
22
/// Computes the strongly connected component partitioning of the given LTS.
23
59
pub fn scc_decomposition<F>(lts: &LabelledTransitionSystem, filter: &F) -> IndexedPartition
24
59
where
25
59
    F: Fn(usize, usize, usize) -> bool,
26
59
{
27
59
    let start = Instant::now();
28
59
    trace!("{:?}", lts);
29

            
30
59
    let mut partition = IndexedPartition::new(lts.num_of_states());
31
59

            
32
59
    // The stack for the depth first search.
33
59
    let mut stack = Vec::new();
34
59

            
35
59
    // Keep track of already visited states.
36
59
    let mut state_info: Vec<Option<StateInfo>> = vec![None; lts.num_of_states()];
37
59

            
38
59
    let mut smallest_index = 0;
39
59
    let mut next_block_number = 0;
40

            
41
    // The outer depth first search used to traverse all the states.
42
282513
    for state_index in lts.iter_states() {
43
282513
        if state_info[state_index].is_none() {
44
205178
            trace!("State {state_index}");
45

            
46
205178
            strongly_connect(
47
205178
                state_index,
48
205178
                lts,
49
205178
                filter,
50
205178
                &mut partition,
51
205178
                &mut smallest_index,
52
205178
                &mut next_block_number,
53
205178
                &mut stack,
54
205178
                &mut state_info,
55
205178
            )
56
77335
        }
57
    }
58

            
59
59
    trace!("SCC partition {partition}");
60
59
    debug!("Found {} strongly connected components", partition.num_of_blocks());
61
59
    debug!("Time scc_decomposition: {:.3}s", start.elapsed().as_secs_f64());
62
59
    partition
63
59
}
64

            
65
#[derive(Clone, Debug)]
66
struct StateInfo {
67
    /// A unique index for every state.
68
    index: usize,
69

            
70
    /// Keeps track of the lowest state that can be reached on the stack.
71
    lowlink: usize,
72

            
73
    /// Keeps track of whether this state is on the stack.
74
    on_stack: bool,
75
}
76

            
77
/// Tarjan's strongly connected components algorithm.
78
///
79
/// The `filter` can be used to determine which (from, label, to) edges should
80
/// to be connected.
81
///
82
/// The `smallest_index`, `stack` and `indices` are updated in each recursive
83
/// call to keep track of the current SCC.
84
282513
fn strongly_connect<F>(
85
282513
    state_index: usize,
86
282513
    lts: &LabelledTransitionSystem,
87
282513
    filter: &F,
88
282513
    partition: &mut IndexedPartition,
89
282513
    smallest_index: &mut usize,
90
282513
    next_block_number: &mut usize,
91
282513
    stack: &mut Vec<usize>,
92
282513
    state_info: &mut Vec<Option<StateInfo>>,
93
282513
) where
94
282513
    F: Fn(usize, usize, usize) -> bool,
95
282513
{
96
282513
    trace!("Visiting state {state_index}");
97

            
98
282513
    state_info[state_index] = Some(StateInfo {
99
282513
        index: *smallest_index,
100
282513
        lowlink: *smallest_index,
101
282513
        on_stack: true,
102
282513
    });
103
282513

            
104
282513
    *smallest_index += 1;
105
282513

            
106
282513
    // Start a depth first search from the current state.
107
282513
    stack.push(state_index);
108

            
109
    // Consider successors of the current state.
110
490507
    for (label_index, to_index) in lts.outgoing_transitions(state_index) {
111
490507
        if filter(state_index, *label_index, *to_index) {
112
171851
            if let Some(meta) = &mut state_info[*to_index] {
113
94516
                if meta.on_stack {
114
4
                    // Successor w is in stack S and hence in the current SCC
115
4
                    // If w is not on stack, then (v, w) is an edge pointing to an SCC already found and must be ignored
116
4
                    // v.lowlink := min(v.lowlink, w.lowlink);
117
4
                    let w_index = state_info[*to_index]
118
4
                        .as_ref()
119
4
                        .expect("The state must be visited in the recursive call")
120
4
                        .index;
121
4
                    let info = state_info[state_index].as_mut().expect("This state was added before");
122
4
                    info.lowlink = info.lowlink.min(w_index);
123
94512
                }
124
77335
            } else {
125
77335
                // Successor w has not yet been visited; recurse on it
126
77335
                strongly_connect(
127
77335
                    *to_index,
128
77335
                    lts,
129
77335
                    filter,
130
77335
                    partition,
131
77335
                    smallest_index,
132
77335
                    next_block_number,
133
77335
                    stack,
134
77335
                    state_info,
135
77335
                );
136
77335

            
137
77335
                // v.lowlink := min(v.lowlink, w.lowlink);
138
77335
                let w_lowlink = state_info[*to_index]
139
77335
                    .as_ref()
140
77335
                    .expect("The state must be visited in the recursive call")
141
77335
                    .lowlink;
142
77335
                let info = state_info[state_index].as_mut().expect("This state was added before");
143
77335
                info.lowlink = info.lowlink.min(w_lowlink);
144
77335
            }
145
318656
        }
146
    }
147

            
148
282513
    let info = state_info[state_index].as_ref().expect("This state was added before");
149
282513
    if info.lowlink == info.index {
150
        // Start a new strongly connected component.
151
282513
        while let Some(index) = stack.pop() {
152
282513
            let info = state_info[index].as_mut().expect("This state was on the stack");
153
282513
            info.on_stack = false;
154
282513

            
155
282513
            trace!("Added state {index} to block {}", next_block_number);
156
282513
            partition.set_block(index, *next_block_number);
157
282513

            
158
282513
            if index == state_index || stack.is_empty() {
159
282510
                *next_block_number += 1;
160
282510
                break;
161
3
            }
162
        }
163
3
    }
164
282513
}
165

            
166
/// Returns true iff the labelled transition system has tau-loops.
167
59
pub fn has_tau_loop(lts: &LabelledTransitionSystem) -> bool {
168
981000
    sort_topological(lts, |label_index, _| lts.is_hidden_label(label_index), false).is_err()
169
59
}
170

            
171
#[cfg(test)]
172
mod tests {
173
    use test_log::test;
174

            
175
    use crate::quotient_lts;
176
    use crate::random_lts;
177
    use crate::Partition;
178

            
179
    use super::*;
180

            
181
    /// Returns the reachable states from the given state index.
182
10
    fn reachable_states(
183
10
        lts: &LabelledTransitionSystem,
184
10
        state_index: usize,
185
10
        filter: &impl Fn(usize, usize, usize) -> bool,
186
10
    ) -> Vec<usize> {
187
10
        let mut stack = vec![state_index];
188
10
        let mut visited = vec![false; lts.num_of_states()];
189

            
190
        // Depth first search to find all reachable states.
191
57
        while let Some(inner_state_index) = stack.pop() {
192
69
            for (_, to_index) in lts.outgoing_transitions(inner_state_index) {
193
69
                if filter(inner_state_index, 0, *to_index) && !visited[*to_index] {
194
37
                    visited[*to_index] = true;
195
37
                    stack.push(*to_index);
196
37
                }
197
            }
198
        }
199

            
200
        // All the states that were visited are reachable.
201
10
        visited
202
10
            .into_iter()
203
10
            .enumerate()
204
100
            .filter_map(|(index, visited)| if visited { Some(index) } else { None })
205
10
            .collect()
206
10
    }
207

            
208
1
    #[test]
209
    fn test_random_tau_scc_decomposition() {
210
        let lts = random_lts(10, 3, 3);
211
        let partitioning = tau_scc_decomposition(&lts);
212
        let reduction = quotient_lts(&lts, &partitioning, true);
213

            
214
        // Check that states in a strongly connected component are reachable from each other.
215
        for state_index in lts.iter_states() {
216
69
            let reachable = reachable_states(&lts, state_index, &|_, label, _| lts.is_hidden_label(label));
217

            
218
            // All other states in the same block should be reachable.
219
            let block = partitioning.block_number(state_index);
220

            
221
            for other_state_index in lts
222
                .iter_states()
223
100
                .filter(|index| state_index != *index && partitioning.block_number(*index) == block)
224
            {
225
                assert!(
226
                    reachable.contains(&other_state_index),
227
                    "State {state_index} and {other_state_index} should be connected"
228
                );
229
            }
230
        }
231

            
232
        assert!(
233
            reduction.num_of_states() == tau_scc_decomposition(&reduction).num_of_blocks(),
234
            "Applying SCC decomposition again should yield the same number of SCC after second application"
235
        );
236
    }
237

            
238
1
    #[test]
239
    fn test_cycles() {
240
        let transitions = vec![(0, 0, 2), (0, 0, 4), (1, 0, 0), (2, 0, 1), (2, 0, 0)];
241

            
242
        let lts = LabelledTransitionSystem::new(
243
            0,
244
            None,
245
2
            || transitions.iter().cloned(),
246
            vec!["tau".into(), "a".into()],
247
            vec!["tau".into()]
248
        );
249

            
250
        let _ = tau_scc_decomposition(&lts);
251
    }
252
}