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
27855008
    pub fn new(slice: &'a [(LabelIndex, BlockIndex)]) -> Signature<'a> {
30
27855008
        Signature(slice)
31
27855008
    }
32

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

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

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

            
47
1925890
        while let Some(&o) = other_item {
48
747369
            match self_item {
49
1160166
                Some(&s) if s == o => {
50
412797
                    // Match found, move both iterators forward
51
412797
                    self_item = self_iter.next();
52
412797
                    other_item = other_iter.next();
53
412797
                }
54
747369
                Some(&s) if s < o => {
55
519569
                    // Move only self iterator forward
56
519569
                    self_item = self_iter.next();
57
519569
                }
58
                _ => {
59
                    // No match found in self for o
60
506067
                    return false;
61
                }
62
            }
63
        }
64
        // If we finished self_iter without returning false, self is a subset
65
487457
        true
66
993524
    }
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
8086829
    fn default() -> Self {
73
8086829
        Signature(&[])
74
8086829
    }
75
}
76

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

            
83
impl Hash for Signature<'_> {
84
12019784
    fn hash<H: Hasher>(&self, state: &mut H) {
85
12019784
        self.as_slice().hash(state)
86
12019784
    }
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
4119132
pub fn is_tau_hat<L: LTS>(label: LabelIndex, lts: &L) -> bool {
97
4119132
    label == lts.num_of_labels()
98
4119132
}
99

            
100
/// Returns a special label that is not in the set of labels.
101
661675
fn tau_hat<L: LTS>(lts: &L) -> LabelIndex {
102
661675
    LabelIndex::new(lts.num_of_labels())
103
661675
}
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
2657768
pub fn strong_bisim_signature<L: LTS, P: Partition>(
111
2657768
    state_index: StateIndex,
112
2657768
    lts: &L,
113
2657768
    partition: &P,
114
2657768
    builder: &mut SignatureBuilder,
115
2657768
) {
116
2657768
    builder.clear();
117

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

            
122
    // Compute the flat signature, which has Hash and is more compact.
123
2657768
    builder.sort_unstable();
124
2657768
    builder.dedup();
125
2657768
}
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
3087482
pub fn branching_bisim_signature<L: LTS, P: Partition>(
133
3087482
    state_index: StateIndex,
134
3087482
    lts: &L,
135
3087482
    partition: &P,
136
3087482
    builder: &mut SignatureBuilder,
137
3087482
    visited: &mut FxHashSet<StateIndex>,
138
3087482
    stack: &mut Vec<StateIndex>,
139
3087482
) {
140
    // Clear the builders and the list of visited states.
141
3087482
    builder.clear();
142
3087482
    visited.clear();
143

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

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

            
151
4080894
        for transition in lts.outgoing_transitions(inner_state_index) {
152
4080894
            if lts.is_hidden_label(transition.label) {
153
1331738
                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
779954
                    if !visited.contains(&transition.to) {
156
779400
                        visited.insert(transition.to);
157
779400
                        stack.push(transition.to);
158
779400
                    }
159
551784
                } else {
160
551784
                    //  pi(s) != pi(t)
161
551784
                    builder.push((transition.label, partition.block_number(transition.to)));
162
551784
                }
163
2749156
            } else {
164
2749156
                // (a != tau) This is a visible action only reachable from tau paths with equal signatures.
165
2749156
                builder.push((transition.label, partition.block_number(transition.to)));
166
2749156
            }
167
        }
168
    }
169

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

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

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

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

            
202
    // Compute the flat signature, which has Hash and is more compact.
203
2644359
    builder.sort_unstable();
204
2644359
    builder.dedup();
205
2644359
}
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
443123
pub fn branching_bisim_signature_inductive<L: LTS>(
210
443123
    state_index: StateIndex,
211
443123
    lts: &L,
212
443123
    partition: &BlockPartition,
213
443123
    state_to_key: &[BlockIndex],
214
443123
    builder: &mut SignatureBuilder,
215
443123
) {
216
443123
    builder.clear();
217

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

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

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

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

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

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

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

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

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

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

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

            
338
/// This computes only tau signatures.
339
///
340
/// The input lts must contain no tau-cycles.
341
3190383
pub fn weak_bisim_signature_sorted_taus<L: LTS, P: Partition>(
342
3190383
    state_index: StateIndex,
343
3190383
    lts: &L,
344
3190383
    partition: &P,
345
3190383
    state_to_taus: &[Signature],
346
3190383
    builder: &mut SignatureBuilder,
347
3190383
) {
348
3190383
    builder.clear();
349
3190383
    builder.push((LabelIndex::new(0), partition.block_number(state_index))); // Add the inert tau transition to itself.
350
3238637
    for transition in lts.outgoing_transitions(state_index) {
351
3238637
        if lts.is_hidden_label(transition.label) {
352
1067802
            // Inert tau transition, take signature from the outgoing tau-transition.
353
1067802
            builder.extend(state_to_taus[transition.to].as_slice());
354
2170835
        }
355
    }
356
    // Compute the flat signature, which has Hash and is more compact.
357
3190383
    builder.sort_unstable();
358
3190383
    builder.dedup();
359
3190383
}
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
5119
pub fn tau_cycle_elimination_and_reorder<L: LTS>(
376
5119
    lts: L,
377
5119
    state: StateIndex,
378
5119
    eliminate_tau_selfloops: bool,
379
5119
) -> (LabelledTransitionSystem<L::Label>, StateIndex) {
380
5119
    let scc_partition = tau_scc_decomposition_iterative(&lts);
381
5119
    let tau_loop_free_lts = quotient_lts_naive(&lts, &scc_partition, eliminate_tau_selfloops, eliminate_tau_selfloops);
382
5119
    let mapped_state = StateIndex::new(*scc_partition.block_number(state));
383
5119
    drop(lts);
384
5119
    drop(scc_partition);
385

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

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