1
use std::mem::swap;
2

            
3
use bumpalo::Bump;
4
use log::debug;
5
use log::trace;
6
use rustc_hash::FxHashMap;
7
use rustc_hash::FxHashSet;
8
use utilities::Timing;
9

            
10
use crate::branching_bisim_signature;
11
use crate::branching_bisim_signature_inductive;
12
use crate::branching_bisim_signature_sorted;
13
use crate::combine_partition;
14
use crate::preprocess_branching;
15
use crate::strong_bisim_signature;
16
use crate::BlockPartition;
17
use crate::BlockPartitionBuilder;
18
use crate::IncomingTransitions;
19
use crate::IndexedPartition;
20
use crate::LabelledTransitionSystem;
21
use crate::Partition;
22
use crate::Signature;
23
use crate::SignatureBuilder;
24

            
25
/// Computes a strong bisimulation partitioning using signature refinement
26
29
pub fn strong_bisim_sigref(lts: &LabelledTransitionSystem, timing: &mut Timing) -> IndexedPartition {
27
29
    let mut timepre = timing.start("preprocess");
28
29
    let incoming = IncomingTransitions::new(lts);
29
29
    timepre.finish();
30
29

            
31
29
    let mut time = timing.start("reduction");
32
243956
    let partition = signature_refinement::<_, _, false>(lts, &incoming, |state_index, partition, _, builder| {
33
243956
        strong_bisim_signature(state_index, lts, partition, builder);
34
243956
    }, |_, _| { None });
35
29

            
36
29
    debug_assert_eq!(
37
        partition,
38
29
        strong_bisim_sigref_naive(lts, timing),
39
        "The resulting partition is not a valid strong bisimulation partition."
40
    );
41

            
42
29
    time.finish();
43
29
    partition.into()
44
29
}
45

            
46
/// Computes a strong bisimulation partitioning using signature refinement
47
57
pub fn strong_bisim_sigref_naive(lts: &LabelledTransitionSystem, timing: &mut Timing) -> IndexedPartition {
48
57
    let mut time = timing.start("reduction");
49
3335412
    let partition = signature_refinement_naive(lts, |state_index, partition, _, builder| {
50
3335412
        strong_bisim_signature(state_index, lts, partition, builder);
51
3335412
    });
52
57

            
53
57
    time.finish();
54
57
    partition
55
57
}
56

            
57
/// Computes a branching bisimulation partitioning using signature refinement
58
28
pub fn branching_bisim_sigref(lts: &LabelledTransitionSystem, timing: &mut Timing) -> IndexedPartition {
59
28
    let mut timepre = timing.start("preprocess");
60
28
    let (preprocessed_lts, preprocess_partition) = preprocess_branching(lts);
61
28
    let incoming = IncomingTransitions::new(&preprocessed_lts);
62
28
    timepre.finish();
63
28

            
64
28
    let mut time = timing.start("reduction");
65
28
    let mut expected_builder = SignatureBuilder::default();
66
28
    let mut visited = FxHashSet::default();
67
28
    let mut stack = Vec::new();
68
28

            
69
28
    let partition =
70
220118
        signature_refinement::<_, _, true>(&preprocessed_lts, &incoming, |state_index, partition, state_to_key, builder| {
71
220118
            branching_bisim_signature_inductive(state_index, &preprocessed_lts, partition, state_to_key, builder);
72
220118

            
73
220118
            // Compute the expected signature, only used in debugging.
74
220118
            if cfg!(debug_assertions) {
75
220118
                branching_bisim_signature(
76
220118
                    state_index,
77
220118
                    &preprocessed_lts,
78
220118
                    partition,
79
220118
                    &mut expected_builder,
80
220118
                    &mut visited,
81
220118
                    &mut stack,
82
220118
                );
83
220118
                let expected_result = builder.clone();
84
220118

            
85
220118
                let signature = Signature::new(builder);
86
220118
                debug_assert_eq!(
87
220118
                    signature.as_slice(),
88
                    expected_result,
89
                    "The sorted and expected signature should be the same"
90
                );
91
            }
92
220118
        },
93
79530
            |signature, key_to_signature| {                
94
                // Inductive signatures.
95
79713
                for (label, key) in signature.iter().rev() {
96
79713
                    if *label == lts.num_of_labels() && key_to_signature[*key].is_subset_of(signature, (*label, *key)) {
97
1527
                        return Some(*key);
98
78186
                    }
99
78186
                    
100
78186
                    if *label != lts.num_of_labels() {
101
77945
                        return None;
102
241
                    }
103
                }
104

            
105
58
                None
106
79530
        });
107
28

            
108
28
    debug_assert_eq!(
109
        partition,
110
724837
        signature_refinement_naive(&preprocessed_lts, |state_index, partition, _, builder| {
111
724837
            branching_bisim_signature(
112
724837
                state_index,
113
724837
                &preprocessed_lts,
114
724837
                partition,
115
724837
                builder,
116
724837
                &mut visited,
117
724837
                &mut stack,
118
724837
            );
119
724837
        }),
120
        "The resulting partition is not a branching bisimulation partition."
121
    );
122
28
    time.finish();
123
28

            
124
28
    // Combine the SCC partition with the branching bisimulation partition.
125
28
    let combined_partition = combine_partition(preprocess_partition, &partition);
126
28

            
127
28
    trace!("Final partition {combined_partition}");
128
28
    combined_partition
129
28
}
130

            
131
/// Computes a branching bisimulation partitioning using signature refinement without dirty blocks.
132
28
pub fn branching_bisim_sigref_naive(lts: &LabelledTransitionSystem, timing: &mut Timing) -> IndexedPartition {
133
28
    let mut timepre = timing.start("preprocess");
134
28
    let (preprocessed_lts, preprocess_partition) = preprocess_branching(lts);
135
28
    timepre.finish();
136
28

            
137
28
    let mut time = timing.start("reduction");
138
28
    let mut expected_builder = SignatureBuilder::default();
139
28
    let mut visited = FxHashSet::default();
140
28
    let mut stack = Vec::new();
141
28

            
142
28
    let partition = signature_refinement_naive(
143
28
        &preprocessed_lts,
144
724824
        |state_index, partition, state_to_signature, builder| {
145
724824
            branching_bisim_signature_sorted(state_index, &preprocessed_lts, partition, state_to_signature, builder);
146
724824

            
147
724824
            // Compute the expected signature, only used in debugging.
148
724824
            if cfg!(debug_assertions) {
149
724824
                branching_bisim_signature(
150
724824
                    state_index,
151
724824
                    &preprocessed_lts,
152
724824
                    partition,
153
724824
                    &mut expected_builder,
154
724824
                    &mut visited,
155
724824
                    &mut stack,
156
724824
                );
157
724824
                let expected_result = builder.clone();
158
724824

            
159
724824
                let signature = Signature::new(builder);
160
724824
                debug_assert_eq!(
161
724824
                    signature.as_slice(),
162
                    expected_result,
163
                    "The sorted and expected signature should be the same"
164
                );
165
            }
166
724824
        },
167
28
    );
168
28
    time.finish();
169
28

            
170
28
    // Combine the SCC partition with the branching bisimulation partition.
171
28
    let combined_partition = combine_partition(preprocess_partition, &partition);
172
28

            
173
28
    trace!("Final partition {combined_partition}");
174
28
    combined_partition
175
28
}
176

            
177
/// General signature refinement algorithm that accepts an arbitrary signature
178
///
179
/// The signature function is called for each state and should fill the
180
/// signature builder with the signature of the state. It consists of the
181
/// current partition, the signatures per state for the next partition.
182
57
fn signature_refinement<F, G, const BRANCHING: bool>(lts: &LabelledTransitionSystem, incoming: &IncomingTransitions, 
183
57
    mut signature: F,
184
57
    mut renumber: G) -> BlockPartition
185
57
where
186
57
    F: FnMut(usize, &BlockPartition, &[usize], &mut SignatureBuilder),
187
57
    G: FnMut(&[(usize, usize)], &Vec<Signature>) -> Option<usize>
188
57
{
189
57
    trace!("{:?}", lts);
190

            
191
    // Avoids reallocations when computing the signature.
192
57
    let mut arena = Bump::new();
193
57
    let mut builder = SignatureBuilder::default();
194
57
    let mut split_builder = BlockPartitionBuilder::default();
195
57

            
196
57
    // Put all the states in the initial partition { S }.
197
57
    let mut id: FxHashMap<Signature, usize> = FxHashMap::default();
198
57

            
199
57
    // Assigns the signature to each state.
200
57
    let mut partition = BlockPartition::new(lts.num_of_states());
201
57
    let mut state_to_key: Vec<usize> = Vec::new();
202
57
    state_to_key.resize_with(lts.num_of_states(), usize::default);
203
57
    let mut key_to_signature: Vec<Signature> = Vec::new();
204
57

            
205
57
    // Refine partitions until stable.
206
57
    let mut iteration = 0usize;
207
57
    let mut num_of_blocks;
208
57
    let mut states = Vec::new();
209
57

            
210
57
    // Used to keep track of dirty blocks.
211
57
    let mut worklist = vec![0];
212

            
213
156759
    while let Some(block_index) = worklist.pop() {
214
        // Clear the current partition to start the next blocks.
215
156702
        id.clear();
216
156702

            
217
156702
        // Removes the existing signatures.
218
156702
        key_to_signature.clear();
219
156702
        arena.reset();
220
156702

            
221
156702
        num_of_blocks = partition.num_of_blocks();
222
156702
        let block = partition.block(block_index);
223
156702
        debug_assert!(
224
156702
            block.has_marked(),
225
            "Every block in the worklist should have at least one marked state"
226
        );
227

            
228
156702
        if BRANCHING {
229
76892
            partition.mark_backward_closure(block_index, incoming);
230
79810
        }
231

            
232
314856
        for new_block_index in
233
464074
            partition.partition_marked_with(block_index, &mut split_builder, |state_index, partition| {
234
464074
                signature(state_index, partition, &state_to_key, &mut builder);
235

            
236
                // Compute the signature of a single state
237
464074
                let index = if let Some((_, index)) = id.get_key_value(&Signature::new(&builder)) {
238
                    // Keep track of the index for every signature, either use the arena to allocate space or simply borrow the value.
239
300916
                    state_to_key[state_index] = *index;
240
300916
                    *index
241
                } else {
242
163158
                    let slice = arena.alloc_slice_copy(&builder);
243

            
244
163158
                    let number = if let Some(key) = renumber(&builder, &key_to_signature) {
245
1527
                        key
246
                    } else {
247
161631
                        let result = key_to_signature.len();
248
161631
                        key_to_signature.push(Signature::new(slice));
249
161631
                        result
250
                    };
251

            
252
163158
                    id.insert(Signature::new(slice), number);
253
163158
                    
254
163158
                    // (branching)  Keep track of the signature for every block in the next partition.
255
163158
                    state_to_key[state_index] = number;
256
163158

            
257
163158
                    number
258
                };
259

            
260
464074
                trace!("State {state_index} signature {:?} index {index}", builder);
261
464074
                index
262
464074
            })
263
        {
264
314856
            if block_index != new_block_index {
265
                // If this is a new block, mark the incoming states as dirty
266
158154
                states.clear();
267
158154
                states.extend(partition.iter_block(new_block_index));
268

            
269
456669
                for &state_index in &states {
270
510609
                    for &(label_index, incoming_state) in incoming.incoming_transitions(state_index) {
271
510609
                        if BRANCHING {
272
                            // Mark incoming states in other blocks, or visible actions.
273
218230
                            if !lts.is_hidden_label(label_index)
274
51945
                                || partition.block_number(incoming_state) != partition.block_number(state_index)
275
                            {
276
172715
                                let other_block = partition.block_number(incoming_state);
277
172715

            
278
172715
                                if !partition.block(other_block).has_marked() {
279
76864
                                    // If block was not already marked then add it to the worklist.
280
76864
                                    worklist.push(other_block);
281
95851
                                }
282
    
283
172715
                                partition.mark_element(incoming_state);
284
45515
                            }
285
                        } else {
286
                            // In this case mark all incoming states.
287
292379
                            let other_block = partition.block_number(incoming_state);
288
292379

            
289
292379
                            if !partition.block(other_block).has_marked() {
290
79781
                                // If block was not already marked then add it to the worklist.
291
79781
                                worklist.push(other_block);
292
212600
                            }
293

            
294
292379
                            partition.mark_element(incoming_state);
295
                        }
296
                    }
297
                }
298
156702
            }
299
        }
300

            
301
156702
        trace!("Iteration {iteration} partition {partition}");
302

            
303
156702
        iteration += 1;
304
156702
        if num_of_blocks != partition.num_of_blocks() {
305
            // Only print a message when new blocks have been found.
306
2163
            debug!("Iteration {iteration}, found {} blocks", partition.num_of_blocks());
307
154539
        }
308
    }
309

            
310
57
    trace!("Refinement partition {partition}");
311
57
    partition
312
57
}
313

            
314
/// General signature refinement algorithm that accepts an arbitrary signature
315
///
316
/// The signature function is called for each state and should fill the
317
/// signature builder with the signature of the state. It consists of the
318
/// current partition, the signatures per state for the next partition.
319
113
fn signature_refinement_naive<F>(lts: &LabelledTransitionSystem, mut signature: F) -> IndexedPartition
320
113
where
321
113
    F: FnMut(usize, &IndexedPartition, &Vec<Signature>, &mut SignatureBuilder),
322
113
{
323
113
    trace!("{:?}", lts);
324

            
325
    // Avoids reallocations when computing the signature.
326
113
    let mut arena = Bump::new();
327
113
    let mut builder = SignatureBuilder::default();
328
113

            
329
113
    // Put all the states in the initial partition { S }.
330
113
    let mut id: FxHashMap<Signature, usize> = FxHashMap::default();
331
113

            
332
113
    // Assigns the signature to each state.
333
113
    let mut partition = IndexedPartition::new(lts.num_of_states());
334
113
    let mut next_partition = IndexedPartition::new(lts.num_of_states());
335
113
    let mut state_to_signature: Vec<Signature> = Vec::new();
336
113
    state_to_signature.resize_with(lts.num_of_states(), Signature::default);
337
113

            
338
113
    // Refine partitions until stable.
339
113
    let mut old_count = 1;
340
113
    let mut iteration = 0;
341

            
342
1153
    while old_count != id.len() {
343
1040
        old_count = id.len();
344
1040
        debug!("Iteration {iteration}, found {old_count} blocks");
345
1040
        swap(&mut partition, &mut next_partition);
346
1040

            
347
1040
        // Clear the current partition to start the next blocks.
348
1040
        id.clear();
349
1040

            
350
1040
        // Remove the current signatures.
351
1040
        arena.reset();
352

            
353
4220088
        for state_index in lts.iter_states() {
354
            // Compute the signature of a single state
355
4220088
            signature(state_index, &partition, &state_to_signature, &mut builder);
356
4220088

            
357
4220088
            trace!("State {state_index} signature {:?}", builder);
358

            
359
            // Keep track of the index for every state, either use the arena to allocate space or simply borrow the value.
360
4220088
            let mut new_id = id.len();
361
4220088
            if let Some((signature, index)) = id.get_key_value(&Signature::new(&builder)) {
362
3461308
                state_to_signature[state_index] = Signature::new(signature.as_slice());
363
3461308
                new_id = *index;
364
3461330
            } else {
365
758780
                let slice = arena.alloc_slice_copy(&builder);
366
758780
                id.insert(Signature::new(slice), new_id);
367
758780

            
368
758780
                // (branching) Keep track of the signature for every block in the next partition.
369
758780
                state_to_signature[state_index] = Signature::new(slice);
370
758780
            }
371

            
372
4220088
            next_partition.set_block(state_index, new_id);
373
        }
374

            
375
1040
        iteration += 1;
376
1040

            
377
1040
        debug_assert!(
378
1040
            iteration <= lts.num_of_states().max(2),
379
            "There can never be more splits than number of states, but at least two iterations for stability"
380
        );
381
    }
382

            
383
113
    trace!("Refinement partition {partition}");
384
113
    debug_assert!(
385
564985
        is_valid_refinement(lts, &partition, |state_index, partition, builder| signature(
386
564985
            state_index,
387
564985
            partition,
388
564985
            &state_to_signature,
389
564985
            builder
390
564985
        )),
391
        "The resulting partition is not a valid partition."
392
    );
393
113
    partition
394
113
}
395

            
396
/// Returns true iff the given partition is a strong bisimulation partition
397
113
pub fn is_valid_refinement<F, P>(lts: &LabelledTransitionSystem, partition: &P, mut compute_signature: F) -> bool
398
113
where
399
113
    F: FnMut(usize, &P, &mut SignatureBuilder),
400
113
    P: Partition,
401
113
{
402
113
    // Check that the partition is indeed stable and as such is a quotient of strong bisimulation
403
113
    let mut block_to_signature: Vec<Option<SignatureBuilder>> = vec![None; partition.num_of_blocks()];
404
113

            
405
113
    // Avoids reallocations when computing the signature.
406
113
    let mut builder = SignatureBuilder::default();
407

            
408
564985
    for state_index in lts.iter_states() {
409
564985
        let block = partition.block_number(state_index);
410
564985

            
411
564985
        // Compute the flat signature, which has Hash and is more compact.
412
564985
        compute_signature(state_index, partition, &mut builder);
413
564985
        let signature: Vec<(usize, usize)> = builder.clone();
414

            
415
564985
        if let Some(block_signature) = &block_to_signature[block] {
416
248574
            if signature != *block_signature {
417
                trace!("State {state_index} has a different signature {signature:?} then the block {block} which has signature {block_signature:?}");
418
                return false;
419
248574
            }
420
316411
        } else {
421
316411
            block_to_signature[block] = Some(signature);
422
316411
        };
423
    }
424

            
425
    // Check if there are two blocks with the same signature
426
113
    let mut signature_to_block: FxHashMap<Signature, usize> = FxHashMap::default();
427

            
428
316411
    for (block_index, signature) in block_to_signature
429
113
        .iter()
430
316411
        .map(|signature| signature.as_ref().unwrap())
431
113
        .enumerate()
432
    {
433
316411
        if let Some(other_block_index) = signature_to_block.get(&Signature::new(signature)) {
434
            if block_index != *other_block_index {
435
                trace!("Block {block_index} and {other_block_index} have the same signature {signature:?}");
436
                return false;
437
            }
438
316411
        } else {
439
316411
            signature_to_block.insert(Signature::new(signature), block_index);
440
316411
        }
441
    }
442

            
443
113
    true
444
113
}
445

            
446
#[cfg(test)]
447
mod tests {
448
    use test_log::test;
449
    use utilities::Timing;
450

            
451
    use crate::random_lts;
452

            
453
    use super::*;
454

            
455
1
    #[test]
456
    fn test_random_strong_bisim_sigref() {
457
        let lts = random_lts(10, 3, 3);
458
        let mut timing = Timing::new();
459

            
460
        strong_bisim_sigref(&lts, &mut timing);
461
    }
462

            
463
2
    fn is_refinement(
464
2
        lts: &LabelledTransitionSystem,
465
2
        strong_partition: &impl Partition,
466
2
        branching_partition: &impl Partition,
467
2
    ) {
468
20
        for state_index in lts.iter_states() {
469
200
            for other_state_index in lts.iter_states() {
470
200
                if strong_partition.block_number(state_index) == strong_partition.block_number(other_state_index) {
471
                    // If the states are together according to branching bisimilarity, then they should also be together according to strong bisimilarity.
472
36
                    assert_eq!(
473
36
                        branching_partition.block_number(state_index),
474
36
                        branching_partition.block_number(other_state_index),
475
                        "The branching partition should be a refinement of the strong partition, 
476
                        but states {state_index} and {other_state_index} are in different blocks"
477
                    );
478
164
                }
479
            }
480
        }
481
2
    }
482

            
483
1
    #[test]
484
    fn test_random_branching_bisim_sigref() {
485
        let lts = random_lts(10, 3, 3);
486
        let mut timing = Timing::new();
487

            
488
        let strong_partition = strong_bisim_sigref(&lts, &mut timing);
489
        let branching_partition = branching_bisim_sigref(&lts, &mut timing);
490
        is_refinement(&lts, &strong_partition, &branching_partition);
491
    }
492

            
493
1
    #[test]
494
    fn test_random_branching_bisim_sigref_naive() {
495
        let lts = random_lts(10, 3, 3);
496
        let mut timing = Timing::new();
497

            
498
        let strong_partition = strong_bisim_sigref_naive(&lts, &mut timing);
499
        let branching_partition = branching_bisim_sigref_naive(&lts, &mut timing);
500
        is_refinement(&lts, &strong_partition, &branching_partition);
501
    }
502
}