1
#![forbid(unsafe_code)]
2

            
3
use std::fmt;
4
use std::hash::Hash;
5
use std::hash::Hasher;
6

            
7
use merc_collections::BlockIndex;
8
use merc_lts::LTS;
9
use merc_lts::LabelIndex;
10
use merc_lts::LabelledTransitionSystem;
11
use merc_lts::StateIndex;
12
use rustc_hash::FxHashSet;
13

            
14
use super::BlockPartition;
15
use super::sort_topological;
16
use crate::Partition;
17
use crate::quotient_lts_naive;
18
use crate::tau_scc_decomposition_iterative;
19

            
20
/// The builder used to construct the signature.
21
pub type SignatureBuilder = Vec<(LabelIndex, BlockIndex)>;
22

            
23
/// The type of a signature. We use sorted vectors to avoid the overhead of hash
24
/// sets that might have unused values.
25
#[derive(Eq)]
26
pub struct Signature<'a>(&'a [(LabelIndex, BlockIndex)]);
27

            
28
impl<'a> Signature<'a> {
29
26615656
    pub fn new(slice: &'a [(LabelIndex, BlockIndex)]) -> Signature<'a> {
30
26615656
        Signature(slice)
31
26615656
    }
32

            
33
43745716
    pub fn as_slice(&self) -> &[(LabelIndex, BlockIndex)] {
34
43745716
        self.0
35
43745716
    }
36
}
37

            
38
impl Signature<'_> {
39
    // Check if other is a subset of self, excluding a specific element
40
844978
    pub fn is_subset_of(&self, other: &[(LabelIndex, BlockIndex)], exclude: (LabelIndex, BlockIndex)) -> bool {
41
844978
        let mut self_iter = self.as_slice().iter();
42
1258164
        let mut other_iter = other.iter().filter(|&&x| x != exclude);
43

            
44
844978
        let mut self_item = self_iter.next();
45
844978
        let mut other_item = other_iter.next();
46

            
47
1711614
        while let Some(&o) = other_item {
48
688247
            match self_item {
49
1072821
                Some(&s) if s == o => {
50
384574
                    // Match found, move both iterators forward
51
384574
                    self_item = self_iter.next();
52
384574
                    other_item = other_iter.next();
53
384574
                }
54
688247
                Some(&s) if s < o => {
55
482062
                    // Move only self iterator forward
56
482062
                    self_item = self_iter.next();
57
482062
                }
58
                _ => {
59
                    // No match found in self for o
60
443631
                    return false;
61
                }
62
            }
63
        }
64
        // If we finished self_iter without returning false, self is a subset
65
401347
        true
66
844978
    }
67
}
68

            
69
// This default implementation is actually different from the one generated by derive(Default).
70
#[allow(clippy::derivable_impls)]
71
impl Default for Signature<'_> {
72
6488759
    fn default() -> Self {
73
6488759
        Signature(&[])
74
6488759
    }
75
}
76

            
77
impl PartialEq for Signature<'_> {
78
7522886
    fn eq(&self, other: &Self) -> bool {
79
7522886
        self.as_slice() == other.as_slice()
80
7522886
    }
81
}
82

            
83
impl Hash for Signature<'_> {
84
12099302
    fn hash<H: Hasher>(&self, state: &mut H) {
85
12099302
        self.as_slice().hash(state)
86
12099302
    }
87
}
88

            
89
impl fmt::Debug for Signature<'_> {
90
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91
        f.debug_list().entries(self.as_slice().iter()).finish()
92
    }
93
}
94

            
95
/// Returns true if the label is the special tau_hat label for the given LTS.
96
4130081
pub fn is_tau_hat<L: LTS>(label: LabelIndex, lts: &L) -> bool {
97
4130081
    label == lts.num_of_labels()
98
4130081
}
99

            
100
/// Returns a special label that is not in the set of labels.
101
661904
fn tau_hat<L: LTS>(lts: &L) -> LabelIndex {
102
661904
    LabelIndex::new(lts.num_of_labels())
103
661904
}
104

            
105
/// Returns the signature for strong bisimulation.
106
///
107
/// ```plain
108
///     sig(s, pi) = { (a, pi(t)) | s -a-> t in T }
109
/// ```
110
2745184
pub fn strong_bisim_signature<L: LTS, P: Partition>(
111
2745184
    state_index: StateIndex,
112
2745184
    lts: &L,
113
2745184
    partition: &P,
114
2745184
    builder: &mut SignatureBuilder,
115
2745184
) {
116
2745184
    builder.clear();
117

            
118
5096894
    for transition in lts.outgoing_transitions(state_index) {
119
5096894
        builder.push((transition.label, partition.block_number(transition.to)));
120
5096894
    }
121

            
122
    // Compute the flat signature, which has Hash and is more compact.
123
2745184
    builder.sort_unstable();
124
2745184
    builder.dedup();
125
2745184
}
126

            
127
/// Returns the branching bisimulation signature for branching bisimulation.
128
///
129
/// ```plain
130
///     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)) }
131
/// ```
132
3090796
pub fn branching_bisim_signature<L: LTS, P: Partition>(
133
3090796
    state_index: StateIndex,
134
3090796
    lts: &L,
135
3090796
    partition: &P,
136
3090796
    builder: &mut SignatureBuilder,
137
3090796
    visited: &mut FxHashSet<StateIndex>,
138
3090796
    stack: &mut Vec<StateIndex>,
139
3090796
) {
140
    // Clear the builders and the list of visited states.
141
3090796
    builder.clear();
142
3090796
    visited.clear();
143

            
144
    // A stack used for depth first search of tau paths.
145
3090796
    debug_assert!(stack.is_empty(), "The stack should be empty");
146
3090796
    stack.push(state_index);
147

            
148
6957007
    while let Some(inner_state_index) = stack.pop() {
149
3866211
        visited.insert(inner_state_index);
150

            
151
4080323
        for transition in lts.outgoing_transitions(inner_state_index) {
152
4080323
            if lts.is_hidden_label(transition.label) {
153
1323714
                if partition.block_number(state_index) == partition.block_number(transition.to) {
154
                    // Explore the outgoing state as well, still tau path in same block
155
776063
                    if !visited.contains(&transition.to) {
156
775415
                        visited.insert(transition.to);
157
775415
                        stack.push(transition.to);
158
775415
                    }
159
547651
                } else {
160
547651
                    //  pi(s) != pi(t)
161
547651
                    builder.push((transition.label, partition.block_number(transition.to)));
162
547651
                }
163
2756609
            } else {
164
2756609
                // (a != tau) This is a visible action only reachable from tau paths with equal signatures.
165
2756609
                builder.push((transition.label, partition.block_number(transition.to)));
166
2756609
            }
167
        }
168
    }
169

            
170
    // Compute the flat signature, which has Hash and is more compact.
171
3090796
    builder.sort_unstable();
172
3090796
    builder.dedup();
173
3090796
}
174

            
175
/// The same as [branching_bisim_signature], but assuming that the input LTS is
176
/// topological sorted, and contains no tau-cycles.
177
2648362
pub fn branching_bisim_signature_sorted<L: LTS, P: Partition>(
178
2648362
    state_index: StateIndex,
179
2648362
    lts: &L,
180
2648362
    partition: &P,
181
2648362
    state_to_signature: &[Signature],
182
2648362
    builder: &mut SignatureBuilder,
183
2648362
) {
184
2648362
    builder.clear();
185

            
186
2675722
    for transition in lts.outgoing_transitions(state_index) {
187
2675722
        let to_block = partition.block_number(transition.to);
188

            
189
2675722
        if partition.block_number(state_index) == to_block {
190
789355
            if lts.is_hidden_label(transition.label) {
191
458723
                // Inert tau transition, take signature from the outgoing tau-transition.
192
458723
                builder.extend(state_to_signature[transition.to].as_slice());
193
458723
            } else {
194
330632
                builder.push((transition.label, to_block));
195
330632
            }
196
1886367
        } else {
197
1886367
            // Visible action, add to the signature.
198
1886367
            builder.push((transition.label, to_block));
199
1886367
        }
200
    }
201

            
202
    // Compute the flat signature, which has Hash and is more compact.
203
2648362
    builder.sort_unstable();
204
2648362
    builder.dedup();
205
2648362
}
206

            
207
/// The inductive version of [branching_bisim_signature_sorted]. Assumes that
208
/// the input LTS has no tau-cycles, and is topologically sorted.
209
442434
pub fn branching_bisim_signature_inductive<L: LTS>(
210
442434
    state_index: StateIndex,
211
442434
    lts: &L,
212
442434
    partition: &BlockPartition,
213
442434
    state_to_key: &[BlockIndex],
214
442434
    builder: &mut SignatureBuilder,
215
442434
) {
216
442434
    builder.clear();
217

            
218
555086
    for transition in lts.outgoing_transitions(state_index) {
219
555086
        let to_block = partition.block_number(transition.to);
220

            
221
555086
        if partition.block_number(state_index) == to_block {
222
349243
            if lts.is_hidden_label(transition.label) && partition.is_element_marked(transition.to) {
223
128077
                // Inert tau transition, take signature from the outgoing tau-transition.
224
128077
                builder.push((tau_hat(lts), state_to_key[transition.to]));
225
221166
            } else {
226
221166
                builder.push((transition.label, to_block));
227
221166
            }
228
205843
        } else {
229
205843
            // Visible action, add to the signature.
230
205843
            builder.push((transition.label, to_block));
231
205843
        }
232
    }
233

            
234
    // Compute the flat signature, which has Hash and is more compact.
235
442434
    builder.sort_unstable();
236
442434
    builder.dedup();
237
442434
}
238

            
239
/// Computes the weak bisimulation presignature.
240
///
241
/// The input lts must contain no tau-cycles.
242
1601390
pub fn weak_bisim_presignature_sorted<L: LTS, P: Partition>(
243
1601390
    state_index: StateIndex,
244
1601390
    lts: &L,
245
1601390
    partition: &P,
246
1601390
    state_to_taus: &[Signature],
247
1601390
    state_to_key: &[Option<usize>],
248
1601390
    builder: &mut SignatureBuilder,
249
1601390
) {
250
1601390
    builder.clear();
251
1601390
    builder.push((LabelIndex::new(0), partition.block_number(state_index))); // Add the inert tau transition to itself.
252
1625406
    for transition in lts.outgoing_transitions(state_index) {
253
1625406
        if lts.is_hidden_label(transition.label) {
254
533827
            // Inert tau transition, take signature from the outgoing tau-transition.
255
533827
            builder.push((tau_hat(lts), BlockIndex::new(state_to_key[transition.to].unwrap())));
256
533827
        } else {
257
1320906
            for (label_after, color) in state_to_taus[transition.to].as_slice() {
258
1320906
                if lts.is_hidden_label(*label_after) {
259
1320906
                    builder.push((transition.label, *color));
260
1320906
                }
261
            }
262
        }
263
    }
264

            
265
    // Compute the flat signature, which has Hash and is more compact.
266
1601390
    builder.sort_unstable();
267
1601390
    builder.dedup();
268
1601390
}
269

            
270
/// Computes the weak bisimulation signature.
271
///
272
/// The input lts must contain no tau-cycles.
273
1382407
pub fn weak_bisim_signature_sorted_full<L: LTS, P: Partition>(
274
1382407
    state_index: StateIndex,
275
1382407
    lts: &L,
276
1382407
    partition: &P,
277
1382407
    state_to_taus: &[Signature],
278
1382407
    state_to_signature: &[Option<usize>],
279
1382407
    key_to_signature: &[Signature],
280
1382407
    builder: &mut SignatureBuilder,
281
1382407
) {
282
1382407
    builder.clear();
283
1382407
    builder.push((LabelIndex::new(0), partition.block_number(state_index))); // Add the inert tau transition to itself.
284
1382407
    for transition in lts.outgoing_transitions(state_index) {
285
1359985
        let to_block = partition.block_number(transition.to);
286

            
287
1359985
        if lts.is_hidden_label(transition.label) {
288
304585
            // Inert tau transition, take signature from the outgoing tau-transition.
289
304585
            builder.extend(key_to_signature[state_to_signature[transition.to].unwrap()].as_slice());
290
304585
        } else {
291
1055400
            builder.push((transition.label, to_block));
292
1284297
            for (label_after, color) in state_to_taus[transition.to].as_slice() {
293
1284297
                if lts.is_hidden_label(*label_after) {
294
1284297
                    builder.push((transition.label, *color));
295
1284297
                }
296
            }
297
        }
298
    }
299

            
300
    // Compute the flat signature, which has Hash and is more compact.
301
1382407
    builder.sort_unstable();
302
1382407
    builder.dedup();
303
1382407
}
304

            
305
/// Computes the weak bisimulation signature.
306
///
307
/// The input lts must contain no tau-cycles.
308
1884025
pub fn weak_bisim_signature_sorted<L: LTS, P: Partition>(
309
1884025
    state_index: StateIndex,
310
1884025
    lts: &L,
311
1884025
    partition: &P,
312
1884025
    state_to_signature: &[Signature],
313
1884025
    builder: &mut SignatureBuilder,
314
1884025
) {
315
1884025
    builder.clear();
316
1884025
    builder.push((LabelIndex::new(0), partition.block_number(state_index))); // Add the inert tau transition to itself.
317
1909022
    for transition in lts.outgoing_transitions(state_index) {
318
1909022
        let to_block = partition.block_number(transition.to);
319

            
320
1909022
        if lts.is_hidden_label(transition.label) {
321
628048
            // Inert tau transition, take signature from the outgoing tau-transition.
322
628048
            builder.extend(state_to_signature[transition.to].as_slice());
323
628048
        } else {
324
1280974
            builder.push((transition.label, to_block));
325
2423382
            for (label_after, color) in state_to_signature[transition.to].as_slice() {
326
2423382
                if lts.is_hidden_label(*label_after) {
327
1566811
                    builder.push((transition.label, *color));
328
1566811
                }
329
            }
330
        }
331
    }
332

            
333
    // Compute the flat signature, which has Hash and is more compact.
334
1884025
    builder.sort_unstable();
335
1884025
    builder.dedup();
336
1884025
}
337

            
338
/// This computes only tau signatures.
339
///
340
/// The input lts must contain no tau-cycles.
341
3196739
pub fn weak_bisim_signature_sorted_taus<L: LTS, P: Partition>(
342
3196739
    state_index: StateIndex,
343
3196739
    lts: &L,
344
3196739
    partition: &P,
345
3196739
    state_to_taus: &[Signature],
346
3196739
    builder: &mut SignatureBuilder,
347
3196739
) {
348
3196739
    builder.clear();
349
3196739
    builder.push((LabelIndex::new(0), partition.block_number(state_index))); // Add the inert tau transition to itself.
350
3241067
    for transition in lts.outgoing_transitions(state_index) {
351
3241067
        if lts.is_hidden_label(transition.label) {
352
1065494
            // Inert tau transition, take signature from the outgoing tau-transition.
353
1065494
            builder.extend(state_to_taus[transition.to].as_slice());
354
2175573
        }
355
    }
356
    // Compute the flat signature, which has Hash and is more compact.
357
3196739
    builder.sort_unstable();
358
3196739
    builder.dedup();
359
3196739
}
360

            
361
/// Perform the preprocessing necessary for branching bisimulation with the
362
/// sorted signature [branching_bisim_signature_sorted] and
363
/// [branching_bisim_signature_inductive].
364
///
365
/// # Details
366
///
367
/// Computes the tau-SCC decomposition of the LTS, quotients the LTS modulo the
368
/// tau-SCCs, and then sorts the states according to a reverse topological order
369
/// of the tau transitions, i.e., if there is a tau-transition from state s to
370
/// state t, then t appears before s in the ordering.
371
///
372
/// Returns the state of the preprocessed LTS corresponding to the given state.
373
/// If `eliminate_tau_selfloops` is true, then the tau self-loops are removed
374
/// after quotienting.
375
5103
pub fn tau_cycle_elimination_and_reorder<L: LTS>(
376
5103
    lts: L,
377
5103
    state: StateIndex,
378
5103
    eliminate_tau_selfloops: bool,
379
5103
) -> (LabelledTransitionSystem<L::Label>, StateIndex) {
380
5103
    let scc_partition = tau_scc_decomposition_iterative(&lts);
381
5103
    let tau_loop_free_lts = quotient_lts_naive(&lts, &scc_partition, eliminate_tau_selfloops, eliminate_tau_selfloops);
382
5103
    let mapped_state = StateIndex::new(*scc_partition.block_number(state));
383
5103
    drop(lts);
384
5103
    drop(scc_partition);
385

            
386
    // Sort the states according to the topological order of the tau transitions.
387
5103
    let topological_permutation = sort_topological(
388
5103
        &tau_loop_free_lts,
389
3121800
        |label_index, _| tau_loop_free_lts.is_hidden_label(label_index),
390
        true,
391
    )
392
5103
    .expect("After quotienting, the LTS should not contain cycles");
393

            
394
    (
395
3104864
        LabelledTransitionSystem::new_from_permutation(tau_loop_free_lts, |i| topological_permutation[i]),
396
5103
        topological_permutation[mapped_state],
397
    )
398
5103
}