1
use std::fmt::Debug;
2
use std::hash::Hash;
3

            
4
use rustc_hash::FxHashSet;
5

            
6
use crate::LabelledTransitionSystem;
7
use crate::Partition;
8
use crate::StateIndex;
9

            
10
use super::quotient_lts;
11
use super::reorder_partition;
12
use super::reorder_states;
13
use super::sort_topological;
14
use super::tau_scc_decomposition;
15
use super::BlockPartition;
16
use super::IndexedPartition;
17

            
18
/// The builder used to construct the signature.
19
pub type SignatureBuilder = Vec<(usize, usize)>;
20

            
21
/// The type of a signature. We use sorted vectors to avoid the overhead of hash
22
/// sets that might have unused values.
23
#[derive(Eq)]
24
pub struct Signature(*const [(usize, usize)]);
25

            
26
impl Signature {
27
11565583
    pub fn new(slice: &[(usize, usize)]) -> Signature {
28
11565583
        Signature(slice)
29
11565583
    }
30

            
31
12461584
    pub fn as_slice(&self) -> &[(usize, usize)] {
32
12461584
        unsafe { &*self.0 }
33
12461584
    }
34
}
35

            
36
impl Signature {
37
    // Check if target is a subset of self, excluding a specific element
38
1768
    pub fn is_subset_of(&self, other: &[(usize, usize)], exclude: (usize, usize)) -> bool {
39
1768
        let mut self_iter = self.as_slice().iter();
40
4015
        let mut other_iter = other.iter().filter(|&&x| x != exclude);
41
1768

            
42
1768
        let mut self_item: Option<&(usize, usize)> = self_iter.next();
43
1768
        let mut other_item = other_iter.next();
44

            
45
4648
        while let Some(&o) = other_item {
46
822
            match self_item {
47
3021
                Some(&s) if s == o => {
48
2199
                    // Match found, move both iterators forward
49
2199
                    self_item = self_iter.next();
50
2199
                    other_item = other_iter.next();
51
2199
                }
52
822
                Some(&s) if s < o => {
53
681
                    // Move only self iterator forward
54
681
                    self_item = self_iter.next();
55
681
                }
56
                _ => {
57
                    // No match found in self for o
58
241
                    return false;
59
                }
60
            }
61
        }
62
        // If we finished self_iter without returning false, self is a subset
63
1527
        true
64
1768
    }
65
}
66

            
67
impl Default for Signature {
68
564985
    fn default() -> Self {
69
564985
        Signature(&[])
70
564985
    }
71
}
72

            
73
impl PartialEq for Signature {
74
3795016
    fn eq(&self, other: &Self) -> bool {
75
3795016
        self.as_slice() == other.as_slice()
76
3795016
    }
77
}
78

            
79
impl Hash for Signature {
80
7134860
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
81
7134860
        unsafe { (*self.0).hash(state) }
82
7134860
    }
83
}
84

            
85
impl Debug for Signature {
86
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87
        f.debug_list().entries(self.as_slice().iter()).finish()
88
    }
89
}
90

            
91
/// Returns the signature for strong bisimulation sig(s, pi) = { (a, pi(t)) | s -a-> t in T }
92
3579368
pub fn strong_bisim_signature(
93
3579368
    state_index: StateIndex,
94
3579368
    lts: &LabelledTransitionSystem,
95
3579368
    partition: &impl Partition,
96
3579368
    builder: &mut SignatureBuilder,
97
3579368
) {
98
3579368
    builder.clear();
99

            
100
9588383
    for (label, to) in lts.outgoing_transitions(state_index) {
101
9588379
        builder.push((*label, partition.block_number(*to)));
102
9588379
    }
103

            
104
    // Compute the flat signature, which has Hash and is more compact.
105
3579368
    builder.sort_unstable();
106
3579368
    builder.dedup();
107
3579368
}
108

            
109
/// Returns the branching bisimulation signature for branching bisimulation
110
/// sig(s, pi) = { (a, pi(t)) | s -[tau]-> s1 -> ... s_n -[a]-> t in T && pi(s) = pi(s_i) && ((a != tau) || pi(s) != pi(t)) }
111
1669779
pub fn branching_bisim_signature(
112
1669779
    state_index: StateIndex,
113
1669779
    lts: &LabelledTransitionSystem,
114
1669779
    partition: &impl Partition,
115
1669779
    builder: &mut SignatureBuilder,
116
1669779
    visited: &mut FxHashSet<usize>,
117
1669779
    stack: &mut Vec<usize>,
118
1669779
) {
119
1669779
    // Clear the builders and the list of visited states.
120
1669779
    builder.clear();
121
1669779
    visited.clear();
122
1669779

            
123
1669779
    // A stack used for depth first search of tau paths.
124
1669779
    debug_assert!(stack.is_empty(), "The stack should be empty");
125
1669779
    stack.push(state_index);
126

            
127
78604543
    while let Some(inner_state_index) = stack.pop() {
128
76934764
        visited.insert(inner_state_index);
129

            
130
226490764
        for (label_index, to_index) in lts.outgoing_transitions(inner_state_index) {
131
226490760
            if lts.is_hidden_label(*label_index) {
132
222015914
                if partition.block_number(state_index) == partition.block_number(*to_index) {
133
                    // Explore the outgoing state as well, still tau path in same block
134
221312365
                    if !visited.contains(to_index) {
135
75264985
                        visited.insert(*to_index);
136
75264985
                        stack.push(*to_index);
137
146047399
                    }
138
703549
                } else {
139
703549
                    //  pi(s) != pi(t)
140
703549
                    builder.push((*label_index, partition.block_number(*to_index)));
141
703549
                }
142
4474846
            } else {
143
4474846
                // (a != tau) This is a visible action only reachable from tau paths with equal signatures.
144
4474846
                builder.push((*label_index, partition.block_number(*to_index)));
145
4474846
            }
146
        }
147
    }
148

            
149
    // Compute the flat signature, which has Hash and is more compact.
150
1669779
    builder.sort_unstable();
151
1669779
    builder.dedup();
152
1669779
}
153

            
154
/// The input lts must contain no tau-cycles.
155
724824
pub fn branching_bisim_signature_sorted(
156
724824
    state_index: StateIndex,
157
724824
    lts: &LabelledTransitionSystem,
158
724824
    partition: &impl Partition,
159
724824
    state_to_signature: &[Signature],
160
724824
    builder: &mut SignatureBuilder,
161
724824
) {
162
724824
    builder.clear();
163

            
164
1461192
    for &(label_index, to) in lts.outgoing_transitions(state_index) {
165
1461180
        let to_block = partition.block_number(to);
166
1461180

            
167
1461180
        if partition.block_number(state_index) == to_block {
168
626205
            if lts.is_hidden_label(label_index) {
169
463534
                // Inert tau transition, take signature from the outgoing tau-transition.
170
463534
                builder.extend(state_to_signature[to].as_slice());
171
463535
            } else {
172
162671
                builder.push((label_index, to_block));
173
162671
            }
174
834975
        } else {
175
834975
            // Visible action, add to the signature.
176
834975
            builder.push((label_index, to_block));
177
834975
        }
178
    }
179

            
180
    // Compute the flat signature, which has Hash and is more compact.
181
724824
    builder.sort_unstable();
182
724824
    builder.dedup();
183
724824
}
184

            
185
/// The input lts must contain no tau-cycles.
186
220118
pub fn branching_bisim_signature_inductive(
187
220118
    state_index: StateIndex,
188
220118
    lts: &LabelledTransitionSystem,
189
220118
    partition: &BlockPartition,
190
220118
    state_to_key: &[usize],
191
220118
    builder: &mut SignatureBuilder,
192
220118
) {
193
220118
    builder.clear();
194
220118

            
195
220118
    let num_act: usize = lts.num_of_labels(); //this label index does not occur.
196
454916
    for &(label_index, to) in lts.outgoing_transitions(state_index) {
197
454916
        let to_block = partition.block_number(to);
198
454916

            
199
454916
        if partition.block_number(state_index) == to_block {
200
343139
            if lts.is_hidden_label(label_index) && partition.is_element_marked(to) {
201
181162
                // Inert tau transition, take signature from the outgoing tau-transition.
202
181162
                builder.push((num_act, state_to_key[to]));
203
181168
            } else {
204
161977
                builder.push((label_index, to_block));
205
161977
            }
206
111777
        } else {
207
111777
            // Visible action, add to the signature.
208
111777
            builder.push((label_index, to_block));
209
111777
        }
210
    }
211

            
212
    // Compute the flat signature, which has Hash and is more compact.
213
220118
    builder.sort_unstable();
214
220118
    builder.dedup();
215
220118
}
216

            
217
/// Perform the preprocessing necessary for branching bisimulation with the
218
/// sorted signature see `branching_bisim_signature_sorted`.
219
56
pub fn preprocess_branching(lts: &LabelledTransitionSystem) -> (LabelledTransitionSystem, IndexedPartition) {
220
56
    let scc_partition = tau_scc_decomposition(lts);
221
56
    let tau_loop_free_lts = quotient_lts(lts, &scc_partition, true);
222
56

            
223
56
    // Sort the states according to the topological order of the tau transitions.
224
56
    let topological_permutation = sort_topological(
225
56
        &tau_loop_free_lts,
226
980950
        |label_index, _| tau_loop_free_lts.is_hidden_label(label_index),
227
56
        true,
228
56
    )
229
56
    .expect("After quotienting, the LTS should not contain cycles");
230
56

            
231
56
    (
232
773018
        reorder_states(&tau_loop_free_lts, |i| topological_permutation[i]),
233
282488
        reorder_partition(scc_partition, |i| topological_permutation[i]),
234
56
    )
235
56
}