1
use log::debug;
2
use log::trace;
3
use merc_utilities::MercIndex;
4

            
5
use crate::BlockIndex;
6
use crate::Graph;
7
use crate::IndexedPartition;
8
use merc_io::LargeFormatter;
9

            
10
/// Computes the strongly connected component partitioning of the given LTS.
11
3501
pub fn scc_decomposition<F, G>(graph: &G, filter: F) -> IndexedPartition
12
3501
where
13
3501
    F: Fn(G::VertexIndex, G::LabelIndex, G::VertexIndex) -> bool,
14
3501
    G: Graph,
15
3501
    G::VertexIndex: MercIndex<Target = usize>,
16
{
17
    // We assume that the graph is dense.
18
3501
    debug_assert!(
19
581547
        graph.iter_vertices().all(|v| v.index() < graph.num_of_vertices()),
20
        "The graph contains vertices with indices larger than the number of vertices"
21
    );
22

            
23
3501
    let mut partition = IndexedPartition::new(graph.num_of_vertices());
24

            
25
    // The stack for the depth first search.
26
3501
    let mut stack = Vec::new();
27

            
28
    // Keep track of already visited states.
29
3501
    let mut state_info: Vec<Option<StateInfo>> = vec![None; graph.num_of_vertices()];
30

            
31
3501
    let mut smallest_index = 0;
32
3501
    let mut next_block_number = BlockIndex::new(0);
33

            
34
    // The outer depth first search used to traverse all the states.
35
581547
    for state_index in graph.iter_vertices() {
36
581547
        if state_info[state_index.index()].is_none() {
37
507047
            trace!("State {}", state_index.index());
38

            
39
507047
            strongly_connect(
40
507047
                state_index,
41
507047
                graph,
42
507047
                &filter,
43
507047
                &mut partition,
44
507047
                &mut smallest_index,
45
507047
                &mut next_block_number,
46
507047
                &mut stack,
47
507047
                &mut state_info,
48
            )
49
74500
        }
50
    }
51

            
52
3501
    trace!("SCC partition {partition}");
53
3501
    debug!(
54
        "Found {} strongly connected components",
55
        LargeFormatter(partition.num_of_blocks())
56
    );
57
3501
    partition
58
3501
}
59

            
60
/// Information about a state during the SCC computation.
61
#[derive(Clone, Debug)]
62
struct StateInfo {
63
    /// A unique index for every state.
64
    index: usize,
65

            
66
    /// Keeps track of the lowest state that can be reached on the stack.
67
    lowlink: usize,
68

            
69
    /// Keeps track of whether this state is on the stack.
70
    on_stack: bool,
71
}
72

            
73
/// Tarjan's strongly connected components algorithm.
74
///
75
/// The `filter` can be used to determine which (from, label, to) edges should
76
/// to be connected.
77
///
78
/// The `smallest_index`, `stack` and `indices` are updated in each recursive
79
/// call to keep track of the current SCC.
80
#[allow(clippy::too_many_arguments)]
81
581547
fn strongly_connect<F, G>(
82
581547
    vertex_index: G::VertexIndex,
83
581547
    lts: &G,
84
581547
    filter: &F,
85
581547
    partition: &mut IndexedPartition,
86
581547
    smallest_index: &mut usize,
87
581547
    next_block_number: &mut BlockIndex,
88
581547
    stack: &mut Vec<G::VertexIndex>,
89
581547
    state_info: &mut Vec<Option<StateInfo>>,
90
581547
) where
91
581547
    F: Fn(G::VertexIndex, G::LabelIndex, G::VertexIndex) -> bool,
92
581547
    G: Graph,
93
581547
    G::VertexIndex: MercIndex<Target = usize>,
94
{
95
581547
    trace!("Visiting state {}", vertex_index.index());
96

            
97
581547
    state_info[vertex_index.index()] = Some(StateInfo {
98
581547
        index: *smallest_index,
99
581547
        lowlink: *smallest_index,
100
581547
        on_stack: true,
101
581547
    });
102

            
103
581547
    *smallest_index += 1;
104

            
105
    // Start a depth first search from the current state.
106
581547
    stack.push(vertex_index);
107

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

            
138
74500
                // v.lowlink := min(v.lowlink, w.lowlink);
139
74500
                let w_lowlink = state_info[to.index()]
140
74500
                    .as_ref()
141
74500
                    .expect("The state must be visited in the recursive call")
142
74500
                    .lowlink;
143
74500
                let info = state_info[vertex_index.index()]
144
74500
                    .as_mut()
145
74500
                    .expect("This state was added before");
146
74500
                info.lowlink = info.lowlink.min(w_lowlink);
147
74500
            }
148
446384
        }
149
    }
150

            
151
581547
    let info = state_info[vertex_index.index()]
152
581547
        .as_ref()
153
581547
        .expect("This state was added before");
154
581547
    if info.lowlink == info.index {
155
        // Start a new strongly connected component.
156
581547
        while let Some(index) = stack.pop() {
157
581547
            let info = state_info[index.index()].as_mut().expect("This state was on the stack");
158
581547
            info.on_stack = false;
159

            
160
581547
            trace!("Added state {} to block {}", index.index(), next_block_number);
161
581547
            partition.set_block(index.index(), *next_block_number);
162

            
163
581547
            if index == vertex_index || stack.is_empty() {
164
580720
                *next_block_number = BlockIndex::new(next_block_number.value() + 1);
165
580720
                break;
166
827
            }
167
        }
168
827
    }
169
581547
}