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 graph.
11
///
12
/// Uses recursive Tarjan's algorithm. For large graphs that may cause a stack overflow,
13
/// prefer [`scc_decomposition_iterative`] instead.
14
1605
pub fn scc_decomposition<F, G>(graph: &G, filter: F) -> IndexedPartition
15
1605
where
16
1605
    F: Fn(G::VertexIndex, G::LabelIndex, G::VertexIndex) -> bool,
17
1605
    G: Graph,
18
1605
    G::VertexIndex: MercIndex<Target = usize>,
19
{
20
    // We assume that the graph is dense.
21
1605
    debug_assert!(
22
394014
        graph.iter_vertices().all(|v| v.index() < graph.num_of_vertices()),
23
        "The graph contains vertices with indices larger than the number of vertices"
24
    );
25

            
26
1605
    let mut partition =
27
394014
        IndexedPartition::with_subset(graph.num_of_vertices(), graph.iter_vertices().map(|v| v.index()));
28

            
29
    // The stack for the depth first search.
30
1605
    let mut stack = Vec::new();
31

            
32
    // Keep track of already visited states.
33
1605
    let mut state_info: Vec<Option<StateInfo>> = vec![None; graph.num_of_vertices()];
34

            
35
1605
    let mut smallest_index = 0;
36
1605
    let mut next_block_number = BlockIndex::new(0);
37

            
38
    // The outer depth first search used to traverse all the states.
39
394014
    for state_index in graph.iter_vertices() {
40
394014
        if state_info[state_index.index()].is_none() {
41
336403
            trace!("State {}", state_index.index());
42

            
43
336403
            strongly_connect(
44
336403
                state_index,
45
336403
                graph,
46
336403
                &filter,
47
336403
                &mut partition,
48
336403
                &mut smallest_index,
49
336403
                &mut next_block_number,
50
336403
                &mut stack,
51
336403
                &mut state_info,
52
            )
53
57611
        }
54
    }
55

            
56
1605
    trace!("SCC partition {partition}");
57
1605
    debug!(
58
        "Found {} strongly connected components",
59
        LargeFormatter(partition.num_of_blocks())
60
    );
61
1605
    partition
62
1605
}
63

            
64
/// Computes the strongly connected component partitioning of the given LTS using an iterative
65
/// algorithm, based on the iterative Tarjan's SCC algorithm from the mCRL2 toolset (Wouter Schols).
66
5219
pub fn scc_decomposition_iterative<F, G>(graph: &G, filter: F) -> IndexedPartition
67
5219
where
68
5219
    F: Fn(G::VertexIndex, G::LabelIndex, G::VertexIndex) -> bool,
69
5219
    G: Graph,
70
5219
    G::VertexIndex: MercIndex<Target = usize>,
71
{
72
5219
    debug_assert!(
73
1632549
        graph.iter_vertices().all(|v| v.index() < graph.num_of_vertices()),
74
        "The graph contains vertices with indices larger than the number of vertices"
75
    );
76

            
77
5219
    let n = graph.num_of_vertices();
78

            
79
1632549
    let mut partition = IndexedPartition::with_subset(n, graph.iter_vertices().map(|v| v.index()));
80

            
81
    // Sentinel value indicating not yet visited.
82
    const UNVISITED: usize = usize::MAX;
83

            
84
5219
    let mut low = vec![UNVISITED; n];
85
    // disc[v] == UNVISITED means never queued; disc[v] == 0 means queued but not yet initialized.
86
5219
    let mut disc = vec![UNVISITED; n];
87
5219
    let mut on_scc_stack = vec![false; n];
88
5219
    let mut scc_stack: Vec<usize> = Vec::new();
89
5219
    let mut discovery_time = 0usize;
90
5219
    let mut eq_class = 0usize;
91

            
92
    // Work stack: (vertex, edge offset into outgoing_edges).
93
5219
    let mut work: Vec<(G::VertexIndex, usize)> = Vec::new();
94

            
95
1632549
    for root in graph.iter_vertices() {
96
1632549
        let s0 = root.index();
97
1632549
        if low[s0] != UNVISITED {
98
253378
            continue;
99
1379171
        }
100

            
101
1379171
        work.push((root, 0));
102

            
103
3265098
        while let Some((s_vertex, offset)) = work.pop() {
104
1885927
            let s = s_vertex.index();
105

            
106
1885927
            if low[s] == UNVISITED {
107
1632549
                disc[s] = discovery_time;
108
1632549
                low[s] = discovery_time;
109
1632549
                discovery_time += 1;
110
1632549
                scc_stack.push(s);
111
1632549
                on_scc_stack[s] = true;
112
1632549
            }
113

            
114
1885927
            let mut child = None;
115
1885927
            let mut next_offset = offset;
116
1885927
            for (label, to) in graph.outgoing_edges(s_vertex).skip(offset) {
117
1671985
                next_offset += 1;
118
1671985
                if filter(s_vertex, label, to) {
119
552183
                    let v = to.index();
120
552183
                    if disc[v] == UNVISITED {
121
253378
                        disc[v] = 0; // Mark as queued to prevent double-pushing.
122
253378
                        child = Some((to, next_offset));
123
253378
                        break;
124
298805
                    } else if on_scc_stack[v] {
125
3109
                        low[s] = low[s].min(disc[v]);
126
295696
                    }
127
1119802
                }
128
            }
129

            
130
1885927
            if let Some((child_vertex, resume_offset)) = child {
131
253378
                // Push current state continuation, then recurse on child_vertex.
132
253378
                work.push((s_vertex, resume_offset));
133
253378
                work.push((child_vertex, 0));
134
253378
            } else {
135
1632549
                if disc[s] == low[s] {
136
                    // s is the root of an SCC; pop all members off the SCC stack.
137
                    loop {
138
1632549
                        let u = scc_stack.pop().expect("scc_stack must not be empty");
139
1632549
                        on_scc_stack[u] = false;
140
1632549
                        trace!("Added state {} to block {}", u, eq_class);
141
1632549
                        partition.set_block(u, BlockIndex::new(eq_class));
142
1632549
                        if u == s {
143
1631806
                            break;
144
743
                        }
145
                    }
146
1631806
                    eq_class += 1;
147
743
                }
148
                // Propagate lowlink to parent.
149
1632549
                if let Some((parent_vertex, _)) = work.last() {
150
253378
                    let p = parent_vertex.index();
151
253378
                    low[p] = low[p].min(low[s]);
152
1379171
                }
153
            }
154
        }
155
    }
156

            
157
5219
    trace!("SCC partition {partition}");
158
5219
    debug!(
159
        "Found {} strongly connected components",
160
        LargeFormatter(partition.num_of_blocks())
161
    );
162
5219
    partition
163
5219
}
164

            
165
/// Information about a state during the SCC computation.
166
#[derive(Clone, Debug)]
167
struct StateInfo {
168
    /// A unique index for every state.
169
    index: usize,
170

            
171
    /// Keeps track of the lowest state that can be reached on the stack.
172
    lowlink: usize,
173

            
174
    /// Keeps track of whether this state is on the stack.
175
    on_stack: bool,
176
}
177

            
178
/// Tarjan's strongly connected components algorithm.
179
///
180
/// The `filter` can be used to determine which (from, label, to) edges should
181
/// to be connected.
182
///
183
/// The `smallest_index`, `stack` and `indices` are updated in each recursive
184
/// call to keep track of the current SCC.
185
#[allow(clippy::too_many_arguments)]
186
394014
fn strongly_connect<F, G>(
187
394014
    vertex_index: G::VertexIndex,
188
394014
    lts: &G,
189
394014
    filter: &F,
190
394014
    partition: &mut IndexedPartition,
191
394014
    smallest_index: &mut usize,
192
394014
    next_block_number: &mut BlockIndex,
193
394014
    stack: &mut Vec<G::VertexIndex>,
194
394014
    state_info: &mut Vec<Option<StateInfo>>,
195
394014
) where
196
394014
    F: Fn(G::VertexIndex, G::LabelIndex, G::VertexIndex) -> bool,
197
394014
    G: Graph,
198
394014
    G::VertexIndex: MercIndex<Target = usize>,
199
{
200
394014
    trace!("Visiting state {}", vertex_index.index());
201

            
202
394014
    state_info[vertex_index.index()] = Some(StateInfo {
203
394014
        index: *smallest_index,
204
394014
        lowlink: *smallest_index,
205
394014
        on_stack: true,
206
394014
    });
207

            
208
394014
    *smallest_index += 1;
209

            
210
    // Start a depth first search from the current state.
211
394014
    stack.push(vertex_index);
212

            
213
    // Consider successors of the current state.
214
395928
    for (label, to) in lts.outgoing_edges(vertex_index) {
215
375449
        if filter(vertex_index, label, to) {
216
167489
            if let Some(meta) = &mut state_info[to.index()] {
217
109878
                if meta.on_stack {
218
19228
                    // Successor w is in stack S and hence in the current SCC
219
19228
                    // If w is not on stack, then (v, w) is an edge pointing to an SCC already found and must be ignored
220
19228
                    // v.lowlink := min(v.lowlink, w.lowlink);
221
19228
                    let w_index = state_info[to.index()]
222
19228
                        .as_ref()
223
19228
                        .expect("The state must be visited in the recursive call")
224
19228
                        .index;
225
19228
                    let info = state_info[vertex_index.index()]
226
19228
                        .as_mut()
227
19228
                        .expect("This state was added before");
228
19228
                    info.lowlink = info.lowlink.min(w_index);
229
90651
                }
230
57611
            } else {
231
57611
                // Successor w has not yet been visited; recurse on it
232
57611
                strongly_connect(
233
57611
                    to,
234
57611
                    lts,
235
57611
                    filter,
236
57611
                    partition,
237
57611
                    smallest_index,
238
57611
                    next_block_number,
239
57611
                    stack,
240
57611
                    state_info,
241
57611
                );
242
57611

            
243
57611
                // v.lowlink := min(v.lowlink, w.lowlink);
244
57611
                let w_lowlink = state_info[to.index()]
245
57611
                    .as_ref()
246
57611
                    .expect("The state must be visited in the recursive call")
247
57611
                    .lowlink;
248
57611
                let info = state_info[vertex_index.index()]
249
57611
                    .as_mut()
250
57611
                    .expect("This state was added before");
251
57611
                info.lowlink = info.lowlink.min(w_lowlink);
252
57611
            }
253
207960
        }
254
    }
255

            
256
394014
    let info = state_info[vertex_index.index()]
257
394014
        .as_ref()
258
394014
        .expect("This state was added before");
259
394014
    if info.lowlink == info.index {
260
        // Start a new strongly connected component.
261
394014
        while let Some(index) = stack.pop() {
262
394014
            let info = state_info[index.index()].as_mut().expect("This state was on the stack");
263
394014
            info.on_stack = false;
264

            
265
394014
            trace!("Added state {} to block {}", index.index(), next_block_number);
266
394014
            partition.set_block(index.index(), *next_block_number);
267

            
268
394014
            if index == vertex_index || stack.is_empty() {
269
393213
                *next_block_number = BlockIndex::new(next_block_number.value() + 1);
270
393213
                break;
271
801
            }
272
        }
273
801
    }
274
394014
}