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
1602
pub fn scc_decomposition<F, G>(graph: &G, filter: F) -> IndexedPartition
12
1602
where
13
1602
    F: Fn(G::VertexIndex, G::LabelIndex, G::VertexIndex) -> bool,
14
1602
    G: Graph,
15
1602
    G::VertexIndex: MercIndex<Target = usize>,
16
{
17
    // We assume that the graph is dense.
18
1602
    debug_assert!(
19
394124
        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
1602
    let mut partition =
24
394124
        IndexedPartition::with_subset(graph.num_of_vertices(), graph.iter_vertices().map(|v| v.index()));
25

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

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

            
32
1602
    let mut smallest_index = 0;
33
1602
    let mut next_block_number = BlockIndex::new(0);
34

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

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

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

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

            
74
5203
    let n = graph.num_of_vertices();
75

            
76
1632713
    let mut partition = IndexedPartition::with_subset(n, graph.iter_vertices().map(|v| v.index()));
77

            
78
    // Sentinel value indicating not yet visited.
79
    const UNVISITED: usize = usize::MAX;
80

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

            
89
    // Work stack: (vertex, edge offset into outgoing_edges).
90
5203
    let mut work: Vec<(G::VertexIndex, usize)> = Vec::new();
91

            
92
1632713
    for root in graph.iter_vertices() {
93
1632713
        let s0 = root.index();
94
1632713
        if low[s0] != UNVISITED {
95
250831
            continue;
96
1381882
        }
97

            
98
1381882
        work.push((root, 0));
99

            
100
3265426
        while let Some((s_vertex, offset)) = work.pop() {
101
1883544
            let s = s_vertex.index();
102

            
103
1883544
            if low[s] == UNVISITED {
104
1632713
                disc[s] = discovery_time;
105
1632713
                low[s] = discovery_time;
106
1632713
                discovery_time += 1;
107
1632713
                scc_stack.push(s);
108
1632713
                on_scc_stack[s] = true;
109
1632713
            }
110

            
111
1883544
            let mut child = None;
112
1883544
            let mut next_offset = offset;
113
1883544
            for (label, to) in graph.outgoing_edges(s_vertex).skip(offset) {
114
1670126
                next_offset += 1;
115
1670126
                if filter(s_vertex, label, to) {
116
549437
                    let v = to.index();
117
549437
                    if disc[v] == UNVISITED {
118
250831
                        disc[v] = 0; // Mark as queued to prevent double-pushing.
119
250831
                        child = Some((to, next_offset));
120
250831
                        break;
121
298606
                    } else if on_scc_stack[v] {
122
2998
                        low[s] = low[s].min(disc[v]);
123
295608
                    }
124
1120689
                }
125
            }
126

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

            
154
5203
    trace!("SCC partition {partition}");
155
5203
    debug!(
156
        "Found {} strongly connected components",
157
        LargeFormatter(partition.num_of_blocks())
158
    );
159
5203
    partition
160
5203
}
161

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

            
168
    /// Keeps track of the lowest state that can be reached on the stack.
169
    lowlink: usize,
170

            
171
    /// Keeps track of whether this state is on the stack.
172
    on_stack: bool,
173
}
174

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

            
199
394124
    state_info[vertex_index.index()] = Some(StateInfo {
200
394124
        index: *smallest_index,
201
394124
        lowlink: *smallest_index,
202
394124
        on_stack: true,
203
394124
    });
204

            
205
394124
    *smallest_index += 1;
206

            
207
    // Start a depth first search from the current state.
208
394124
    stack.push(vertex_index);
209

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

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

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

            
262
394124
            trace!("Added state {} to block {}", index.index(), next_block_number);
263
394124
            partition.set_block(index.index(), *next_block_number);
264

            
265
394124
            if index == vertex_index || stack.is_empty() {
266
393445
                *next_block_number = BlockIndex::new(next_block_number.value() + 1);
267
393445
                break;
268
679
            }
269
        }
270
679
    }
271
394124
}