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
1101
pub fn scc_decomposition<F, G>(graph: &G, filter: F) -> IndexedPartition
12
1101
where
13
1101
    F: Fn(G::VertexIndex, G::LabelIndex, G::VertexIndex) -> bool,
14
1101
    G: Graph,
15
1101
    G::VertexIndex: MercIndex<Target = usize>,
16
{
17
1101
    let mut partition = IndexedPartition::new(graph.num_of_vertices());
18

            
19
    // The stack for the depth first search.
20
1101
    let mut stack = Vec::new();
21

            
22
    // Keep track of already visited states.
23
1101
    let mut state_info: Vec<Option<StateInfo>> = vec![None; graph.num_of_vertices()];
24

            
25
1101
    let mut smallest_index = 0;
26
1101
    let mut next_block_number = BlockIndex::new(0);
27

            
28
    // The outer depth first search used to traverse all the states.
29
10012
    for state_index in graph.iter_vertices() {
30
10012
        if state_info[state_index.index()].is_none() {
31
5669
            trace!("State {}", state_index.index());
32

            
33
5669
            strongly_connect(
34
5669
                state_index,
35
5669
                graph,
36
5669
                &filter,
37
5669
                &mut partition,
38
5669
                &mut smallest_index,
39
5669
                &mut next_block_number,
40
5669
                &mut stack,
41
5669
                &mut state_info,
42
            )
43
4343
        }
44
    }
45

            
46
1101
    trace!("SCC partition {partition}");
47
1101
    debug!(
48
        "Found {} strongly connected components",
49
        LargeFormatter(partition.num_of_blocks())
50
    );
51
1101
    partition
52
1101
}
53

            
54
/// Information about a state during the SCC computation.
55
#[derive(Clone, Debug)]
56
struct StateInfo {
57
    /// A unique index for every state.
58
    index: usize,
59

            
60
    /// Keeps track of the lowest state that can be reached on the stack.
61
    lowlink: usize,
62

            
63
    /// Keeps track of whether this state is on the stack.
64
    on_stack: bool,
65
}
66

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

            
91
10012
    state_info[vertex_index.index()] = Some(StateInfo {
92
10012
        index: *smallest_index,
93
10012
        lowlink: *smallest_index,
94
10012
        on_stack: true,
95
10012
    });
96

            
97
10012
    *smallest_index += 1;
98

            
99
    // Start a depth first search from the current state.
100
10012
    stack.push(vertex_index);
101

            
102
    // Consider successors of the current state.
103
19675
    for (label, to) in lts.outgoing_edges(vertex_index) {
104
19675
        if filter(vertex_index, label, to) {
105
9366
            if let Some(meta) = &mut state_info[to.index()] {
106
5023
                if meta.on_stack {
107
923
                    // Successor w is in stack S and hence in the current SCC
108
923
                    // If w is not on stack, then (v, w) is an edge pointing to an SCC already found and must be ignored
109
923
                    // v.lowlink := min(v.lowlink, w.lowlink);
110
923
                    let w_index = state_info[to.index()]
111
923
                        .as_ref()
112
923
                        .expect("The state must be visited in the recursive call")
113
923
                        .index;
114
923
                    let info = state_info[vertex_index.index()].as_mut().expect("This state was added before");
115
923
                    info.lowlink = info.lowlink.min(w_index);
116
4100
                }
117
4343
            } else {
118
4343
                // Successor w has not yet been visited; recurse on it
119
4343
                strongly_connect(
120
4343
                    to,
121
4343
                    lts,
122
4343
                    filter,
123
4343
                    partition,
124
4343
                    smallest_index,
125
4343
                    next_block_number,
126
4343
                    stack,
127
4343
                    state_info,
128
4343
                );
129
4343

            
130
4343
                // v.lowlink := min(v.lowlink, w.lowlink);
131
4343
                let w_lowlink = state_info[to.index()]
132
4343
                    .as_ref()
133
4343
                    .expect("The state must be visited in the recursive call")
134
4343
                    .lowlink;
135
4343
                let info = state_info[vertex_index.index()].as_mut().expect("This state was added before");
136
4343
                info.lowlink = info.lowlink.min(w_lowlink);
137
4343
            }
138
10309
        }
139
    }
140

            
141
10012
    let info = state_info[vertex_index.index()].as_ref().expect("This state was added before");
142
10012
    if info.lowlink == info.index {
143
        // Start a new strongly connected component.
144
10012
        while let Some(index) = stack.pop() {
145
10012
            let info = state_info[index.index()].as_mut().expect("This state was on the stack");
146
10012
            info.on_stack = false;
147

            
148
10012
            trace!("Added state {} to block {}", index.index(), next_block_number);
149
10012
            partition.set_block(index.index(), *next_block_number);
150

            
151
10012
            if index == vertex_index || stack.is_empty() {
152
9760
                *next_block_number = BlockIndex::new(next_block_number.value() + 1);
153
9760
                break;
154
252
            }
155
        }
156
252
    }
157
10012
}