1
#![forbid(unsafe_code)]
2

            
3
use merc_lts::LTS;
4
use merc_lts::StateIndex;
5

            
6
/// Returns true iff the given state diverges, i.e., it can perform an infinite
7
/// sequence of tau transitions.
8
///
9
/// Uses iterative DFS with 3-color marking to correctly detect cycles in any LTS,
10
/// including tau self-loops and graphs where the same state is reachable via
11
/// multiple paths (diamonds).
12
1199
pub fn diverges<L: LTS>(lts: &L, state: StateIndex) -> bool {
13
1199
    let mut color = vec![DfsColor::White; lts.num_of_states()];
14
    // Each stack entry is (node, backtrack): when backtrack is true the node is being finalised.
15
1199
    let mut stack = vec![(state, false)];
16

            
17
5424
    while let Some((current, backtrack)) = stack.pop() {
18
4229
        if backtrack {
19
            // All descendants have been processed; mark the node as fully done.
20
2110
            color[current] = DfsColor::Black;
21
2110
            continue;
22
2119
        }
23

            
24
2119
        match color[current] {
25
            DfsColor::Gray => return true, // Back-edge: current is still on the DFS path → cycle found.
26
2
            DfsColor::Black => continue,   // Already fully processed; no new information.
27
2117
            DfsColor::White => {}
28
        }
29

            
30
        // Mark gray (on the current DFS path) and schedule finalisation.
31
2117
        color[current] = DfsColor::Gray;
32
2117
        stack.push((current, true));
33

            
34
2894
        for transition in lts.outgoing_transitions(current) {
35
2894
            if lts.is_hidden_label(transition.label) {
36
925
                match color[transition.to] {
37
4
                    DfsColor::Gray => return true, // Back-edge (incl. self-loop): cycle found.
38
920
                    DfsColor::White => stack.push((transition.to, false)),
39
1
                    DfsColor::Black => {}
40
                }
41
1969
            }
42
        }
43
    }
44

            
45
    // All reachable tau-transitions explored without finding a back-edge.
46
1195
    false
47
1199
}
48

            
49
/// DFS node colour used by [`diverges`].
50
#[derive(Clone, Copy, Default, PartialEq, Eq)]
51
enum DfsColor {
52
    /// Not yet visited.
53
    #[default]
54
    White,
55
    /// On the current DFS path (in the recursion stack).
56
    Gray,
57
    /// All descendants fully processed.
58
    Black,
59
}