1
use std::mem::swap;
2

            
3
use bumpalo::Bump;
4
use log::debug;
5
use log::info;
6
use log::log_enabled;
7
use log::trace;
8
use merc_io::TimeProgress;
9
use merc_lts::IncomingTransitions;
10
use merc_lts::LTS;
11
use merc_lts::LabelIndex;
12
use merc_lts::LabelledTransitionSystem;
13
use merc_lts::StateIndex;
14
use rustc_hash::FxHashMap;
15
use rustc_hash::FxHashSet;
16

            
17
use merc_collections::BlockIndex;
18
use merc_collections::IndexedPartition;
19
use merc_utilities::Timing;
20

            
21
use crate::BlockPartition;
22
use crate::BlockPartitionBuilder;
23
use crate::DivergencePreservingLts;
24
use crate::Partition;
25
use crate::Signature;
26
use crate::SignatureBuilder;
27
use crate::branching_bisim_signature;
28
use crate::branching_bisim_signature_inductive;
29
use crate::branching_bisim_signature_sorted;
30
use crate::is_tau_hat;
31
use crate::longest_tau_path;
32
use crate::quotient_lts_block;
33
use crate::strong_bisim_signature;
34
use crate::tau_cycle_elimination_and_reorder;
35
use crate::weak_bisim_presignature_sorted;
36
use crate::weak_bisim_signature_sorted;
37
use crate::weak_bisim_signature_sorted_full;
38
use crate::weak_bisim_signature_sorted_taus;
39

            
40
/// Computes a strong bisimulation partitioning using signature refinement
41
1104
pub fn strong_bisim_sigref<L: LTS>(lts: L, timing: &Timing) -> (L, BlockPartition) {
42
1104
    let incoming = timing.measure("preprocess", || IncomingTransitions::new(&lts));
43

            
44
1104
    let partition = timing.measure("reduction", || {
45
1104
        signature_refinement::<_, _, _, false>(
46
1104
            &lts,
47
1104
            &incoming,
48
982890
            |state_index, partition, _, builder| {
49
982890
                strong_bisim_signature(state_index, &lts, partition, builder);
50
982890
            },
51
            |_, _| None,
52
        )
53
1104
    });
54

            
55
1104
    (lts, partition)
56
1104
}
57

            
58
/// Computes a strong bisimulation partitioning using signature refinement
59
503
pub fn strong_bisim_sigref_naive<L: LTS>(lts: L, timing: &Timing) -> (L, IndexedPartition) {
60
503
    let partition = timing.measure("reduction", || {
61
1674878
        signature_refinement_naive::<_, _, false>(&lts, |state_index, partition, _, builder| {
62
1674878
            strong_bisim_signature(state_index, &lts, partition, builder);
63
1674878
        })
64
503
    });
65

            
66
503
    (lts, partition)
67
503
}
68

            
69
/// Computes a branching bisimulation partitioning using signature refinement.
70
///
71
/// The `state` is any state for which we return the equivalent state in the
72
/// preprocessed LTS. And if `divergence_preserving` is true, we compute
73
/// divergence preserving branching bisimulation instead.
74
1109
pub fn branching_bisim_sigref<L: LTS>(
75
1109
    lts: L,
76
1109
    state: StateIndex,
77
1109
    divergence_preserving: bool,
78
1109
    timing: &Timing,
79
1109
) -> (LabelledTransitionSystem<L::Label>, StateIndex, BlockPartition) {
80
1109
    let (preprocessed_lts, mapped_state) = timing.measure("preprocess", || {
81
1109
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
82
1109
    });
83

            
84
1109
    let partition = if divergence_preserving {
85
503
        branching_bisim_sigref_impl(&DivergencePreservingLts::new(&preprocessed_lts), timing)
86
    } else {
87
606
        branching_bisim_sigref_impl(&preprocessed_lts, timing)
88
    };
89

            
90
1109
    (preprocessed_lts, mapped_state, partition)
91
1109
}
92

            
93
/// Implementation of [branching_bisim_sigref].
94
1109
fn branching_bisim_sigref_impl<L: LTS>(preprocessed_lts: &L, timing: &Timing) -> BlockPartition {
95
1109
    let incoming = timing.measure("preprocess", || IncomingTransitions::new(preprocessed_lts));
96

            
97
1109
    if log_enabled!(log::Level::Debug) {
98
        let path = longest_tau_path(preprocessed_lts);
99
        debug!("longest_tau_path" = path.len(); "The longest tau path is {:?}", path);
100
1109
    }
101

            
102
1109
    let mut expected_builder = SignatureBuilder::default();
103
1109
    let mut visited = FxHashSet::default();
104
1109
    let mut stack = Vec::new();
105

            
106
1109
    timing.measure("reduction", || {
107
1109
        signature_refinement::<_, _, _, true>(
108
1109
            preprocessed_lts,
109
1109
            &incoming,
110
443123
            |state_index, partition, state_to_key, builder| {
111
443123
                branching_bisim_signature_inductive(state_index, preprocessed_lts, partition, state_to_key, builder);
112

            
113
                // Compute the expected signature, only used in debugging.
114
443123
                if cfg!(debug_assertions) {
115
443123
                    branching_bisim_signature(
116
443123
                        state_index,
117
443123
                        preprocessed_lts,
118
443123
                        partition,
119
443123
                        &mut expected_builder,
120
443123
                        &mut visited,
121
443123
                        &mut stack,
122
                    );
123
443123
                    let expected_result = builder.clone();
124

            
125
443123
                    let signature = Signature::new(builder);
126
443123
                    debug_assert_eq!(
127
443123
                        signature.as_slice(),
128
                        expected_result,
129
                        "The sorted and expected signature should be the same"
130
                    );
131
                }
132
443123
            },
133
443123
            |signature, key_to_signature| {
134
                // Inductive signatures.
135
443130
                for (label, key) in signature.iter().rev() {
136
402724
                    if is_tau_hat(*label, preprocessed_lts)
137
124861
                        && key_to_signature[*key].is_subset_of(signature, (*label, *key))
138
                    {
139
71104
                        return Some(*key);
140
331620
                    }
141

            
142
331620
                    if !is_tau_hat(*label, preprocessed_lts) {
143
277863
                        return None;
144
53757
                    }
145
                }
146

            
147
94156
                None
148
443123
            },
149
        )
150
1109
    })
151
1109
}
152

            
153
/// Computes a branching bisimulation partitioning using signature refinement
154
/// without dirty blocks.
155
///
156
/// The `state` is any state for which we return the equivalent state in the
157
/// preprocessed LTS. And if `divergence_preserving` is true, we compute
158
/// divergence preserving branching bisimulation instead.
159
903
pub fn branching_bisim_sigref_naive<L: LTS>(
160
903
    lts: L,
161
903
    state: StateIndex,
162
903
    divergence_preserving: bool,
163
903
    timing: &Timing,
164
903
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
165
903
    let (preprocessed_lts, mapped_state) = timing.measure("preprocess", || {
166
903
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
167
903
    });
168

            
169
903
    let partition = if divergence_preserving {
170
300
        branching_bisim_sigref_naive_impl(&DivergencePreservingLts::new(&preprocessed_lts), timing)
171
    } else {
172
603
        branching_bisim_sigref_naive_impl(&preprocessed_lts, timing)
173
    };
174

            
175
903
    (preprocessed_lts, mapped_state, partition)
176
903
}
177

            
178
/// Implementation of [branching_bisim_sigref_naive].
179
903
fn branching_bisim_sigref_naive_impl<L: LTS>(preprocessed_lts: &L, timing: &Timing) -> IndexedPartition {
180
903
    timing.measure("reduction", || {
181
903
        let mut expected_builder = SignatureBuilder::default();
182
903
        let mut visited = FxHashSet::default();
183
903
        let mut stack = Vec::new();
184

            
185
903
        signature_refinement_naive::<_, _, false>(
186
903
            preprocessed_lts,
187
2644359
            |state_index, partition, state_to_signature, builder| {
188
2644359
                branching_bisim_signature_sorted(state_index, preprocessed_lts, partition, state_to_signature, builder);
189

            
190
                // Compute the expected signature, only used in debugging.
191
2644359
                if cfg!(debug_assertions) {
192
2644359
                    branching_bisim_signature(
193
2644359
                        state_index,
194
2644359
                        preprocessed_lts,
195
2644359
                        partition,
196
2644359
                        &mut expected_builder,
197
2644359
                        &mut visited,
198
2644359
                        &mut stack,
199
                    );
200
2644359
                    let expected_result = builder.clone();
201

            
202
2644359
                    let signature = Signature::new(builder);
203
2644359
                    debug_assert_eq!(
204
2644359
                        signature.as_slice(),
205
                        expected_result,
206
                        "The sorted and expected signature should be the same"
207
                    );
208
                }
209
2644359
            },
210
        )
211
903
    })
212
903
}
213

            
214
/// Computes a branching bisimulation partitioning using signature refinement without dirty blocks.
215
///
216
/// The `state` is any state for which we return the equivalent state in the preprocessed LTS.
217
804
pub fn weak_bisim_sigref_inductive_naive<L: LTS>(
218
804
    lts: L,
219
804
    state: StateIndex,
220
804
    preprocess: bool,
221
804
    divergence_preserving: bool,
222
804
    timing: &Timing,
223
804
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
224
    // Preprocess the LTS if desired.
225
804
    if preprocess {
226
        let (preprocessed_lts, mapped_state, partition) =
227
            branching_bisim_sigref(lts, state, divergence_preserving, timing);
228
        let quotiented_state = StateIndex::new(*partition.block_number(mapped_state));
229
        let lts = timing.measure("quotient", || {
230
            quotient_lts_block::<_, true>(&preprocessed_lts, &partition, !divergence_preserving)
231
        });
232
        weak_bisim_sigref_inductive_naive_impl(lts, quotiented_state, divergence_preserving, timing)
233
    } else {
234
804
        weak_bisim_sigref_inductive_naive_impl(lts, state, divergence_preserving, timing)
235
    }
236
804
}
237

            
238
/// Implementation of [weak_bisim_sigref_inductive_naive] that deals with both preprocessed and regular LTSs.
239
///
240
/// The `state` is any state for which we return the equivalent state in the preprocessed LTS.
241
804
pub fn weak_bisim_sigref_inductive_naive_impl<L: LTS>(
242
804
    lts: L,
243
804
    state: StateIndex,
244
804
    divergence_preserving: bool,
245
804
    timing: &Timing,
246
804
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
247
804
    let (preprocessed_lts, mapped_state) = timing.measure("preprocess", || {
248
804
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
249
804
    });
250
804
    let partition = timing.measure("reduction", || {
251
804
        if divergence_preserving {
252
300
            signature_refinement_weak(&DivergencePreservingLts::new(&preprocessed_lts))
253
        } else {
254
504
            signature_refinement_weak(&preprocessed_lts)
255
        }
256
804
    });
257
804
    (preprocessed_lts, mapped_state, partition)
258
804
}
259

            
260
/// Computes a branching bisimulation partitioning using signature refinement without dirty blocks.
261
///
262
/// The `state` is any state for which we return the equivalent state in the preprocessed LTS.
263
803
pub fn weak_bisim_sigref_naive<L: LTS>(
264
803
    lts: L,
265
803
    state: StateIndex,
266
803
    preprocess: bool,
267
803
    divergence_preserving: bool,
268
803
    timing: &Timing,
269
803
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
270
    // Preprocess the LTS if desired.
271
803
    if preprocess {
272
        let (preprocessed_lts, mapped_state, partition) =
273
            branching_bisim_sigref(lts, state, divergence_preserving, timing);
274
        let quotiented_state = StateIndex::new(*partition.block_number(mapped_state));
275
        let lts = timing.measure("quotient", || {
276
            quotient_lts_block::<_, true>(&preprocessed_lts, &partition, !divergence_preserving)
277
        });
278
        weak_bisim_sigref_naive_impl(lts, quotiented_state, divergence_preserving, timing)
279
    } else {
280
803
        weak_bisim_sigref_naive_impl(lts, state, divergence_preserving, timing)
281
    }
282
803
}
283

            
284
/// Implementation of [weak_bisim_sigref_naive] that deals with both
285
/// preprocessed and regular LTSs.
286
///
287
/// The `state` is any state for which we return the equivalent state in the
288
/// preprocessed LTS.
289
803
fn weak_bisim_sigref_naive_impl<L: LTS>(
290
803
    lts: L,
291
803
    state: StateIndex,
292
803
    divergence_preserving: bool,
293
803
    timing: &Timing,
294
803
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
295
803
    let (preprocessed_lts, mapped_state) = timing.measure("preprocess", || {
296
803
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
297
803
    });
298
803
    let partition = timing.measure("reduction", || {
299
803
        if divergence_preserving {
300
300
            let divergence_preserving_lts = DivergencePreservingLts::new(&preprocessed_lts);
301
300
            signature_refinement_naive::<_, _, true>(
302
300
                &divergence_preserving_lts,
303
251862
                |state_index, partition, state_to_signature, builder| {
304
251862
                    weak_bisim_signature_sorted(
305
251862
                        state_index,
306
251862
                        &divergence_preserving_lts,
307
251862
                        partition,
308
251862
                        state_to_signature,
309
251862
                        builder,
310
                    )
311
251862
                },
312
            )
313
        } else {
314
503
            signature_refinement_naive::<_, _, true>(
315
503
                &preprocessed_lts,
316
1632292
                |state_index, partition, state_to_signature, builder| {
317
1632292
                    weak_bisim_signature_sorted(state_index, &preprocessed_lts, partition, state_to_signature, builder)
318
1632292
                },
319
            )
320
        }
321
803
    });
322

            
323
803
    (preprocessed_lts, mapped_state, partition)
324
803
}
325

            
326
/// Signature refinement algorithm that accepts an arbitrary signature and uses
327
/// process-the-smaller-half optimisation by marking dirty states.
328
///
329
/// The `signature` function is called for each state and should fill the
330
/// signature builder with the signature of the state.
331
///
332
/// The `renumber` function can be used to renumber the signatures, which is
333
/// used in inductive signatures.
334
///
335
/// If `BRANCHING` then incoming tau-paths are considered for marking the
336
/// incoming blocks. Furthermore, the signature function receives the
337
/// `state_to_key` mapping that contains the signature index for every state,
338
/// required for inductive signatures. And the signatures are computed in the
339
/// order of the given `lts`.
340
2213
fn signature_refinement<F, G, L, const BRANCHING: bool>(
341
2213
    lts: &L,
342
2213
    incoming: &IncomingTransitions,
343
2213
    mut signature: F,
344
2213
    mut renumber: G,
345
2213
) -> BlockPartition
346
2213
where
347
2213
    F: FnMut(StateIndex, &BlockPartition, &[BlockIndex], &mut SignatureBuilder),
348
2213
    G: FnMut(&[(LabelIndex, BlockIndex)], &Vec<Signature>) -> Option<BlockIndex>,
349
2213
    L: LTS,
350
{
351
    // Avoids reallocations when computing the signature.
352
2213
    let mut arena = Bump::new();
353
2213
    let mut builder = SignatureBuilder::default();
354
2213
    let mut split_builder = BlockPartitionBuilder::default();
355

            
356
    // Put all the states in the initial partition { S }.
357
2213
    let mut id: FxHashMap<Signature<'_>, BlockIndex> = FxHashMap::default();
358

            
359
    // Assigns the signature to each state.
360
2213
    let mut partition = BlockPartition::new(lts.num_of_states());
361
2213
    let mut state_to_key: Vec<BlockIndex> = Vec::new();
362
753214
    state_to_key.resize_with(lts.num_of_states(), || BlockIndex::new(0));
363
2213
    let mut key_to_signature: Vec<Signature> = Vec::new();
364

            
365
    // Refine partitions until stable.
366
2213
    let mut iteration = 0usize;
367
2213
    let mut states = Vec::new();
368

            
369
    // Used to keep track of dirty blocks.
370
2213
    let mut worklist = vec![BlockIndex::new(0)];
371

            
372
2213
    let progress = TimeProgress::new(
373
18
        |(iteration, blocks)| {
374
18
            info!("Iteration {iteration}, found {blocks} blocks...");
375
18
        },
376
        5,
377
    );
378

            
379
173019
    while let Some(block_index) = worklist.pop() {
380
        // Clear the current partition to start the next blocks.
381
170806
        id.clear();
382

            
383
        // Removes the existing signatures.
384
170806
        key_to_signature.clear();
385

            
386
        // SAFETY: `id` was cleared above, so it holds no `Signature` borrowing
387
        // from the arena; the transmute only relaxes that borrow's lifetime so the
388
        // map can be refilled with slices allocated after the `arena.reset()`
389
        // below.
390
170806
        let id: &mut FxHashMap<Signature<'_>, BlockIndex> = unsafe { std::mem::transmute(&mut id) };
391
        // SAFETY: see above; `key_to_signature` was cleared.
392
170806
        let key_to_signature: &'_ mut Vec<Signature<'_>> = unsafe { std::mem::transmute(&mut key_to_signature) };
393

            
394
170806
        arena.reset();
395

            
396
170806
        let block = partition.block(block_index);
397
170806
        debug_assert!(
398
170806
            block.has_marked(),
399
            "Every block in the worklist should have at least one marked state"
400
        );
401

            
402
170806
        if BRANCHING {
403
66969
            partition.mark_backward_closure(block_index, incoming);
404
121246
        }
405

            
406
        // Blocks above this number are new in this iteration.
407
170806
        let num_blocks = partition.num_of_blocks();
408

            
409
        // This is a workaround for a data race in bumpalo for zero-sized slices.
410
170806
        let empty_slice: &[(LabelIndex, BlockIndex)] = &[];
411

            
412
440342
        for new_block_index in
413
1426013
            partition.partition_marked_with(block_index, &mut split_builder, |state_index, partition| {
414
1426013
                signature(state_index, partition, &state_to_key, &mut builder);
415

            
416
                // Compute the signature of a single state
417
1426013
                let index = if let Some(key) = renumber(&builder, key_to_signature) {
418
71104
                    key
419
1354909
                } else if let Some((_, index)) = id.get_key_value(&Signature::new(&builder)) {
420
1010975
                    *index
421
                } else {
422
343934
                    let slice = if builder.is_empty() {
423
1974
                        empty_slice
424
                    } else {
425
341960
                        arena.alloc_slice_copy(&builder)
426
                    };
427
343934
                    let number = BlockIndex::new(key_to_signature.len());
428
343934
                    id.insert(Signature::new(slice), number);
429
343934
                    key_to_signature.push(Signature::new(slice));
430

            
431
343934
                    number
432
                };
433

            
434
                // (branching) Keep track of the signature for every block in the next partition.
435
1426013
                state_to_key[state_index] = index;
436

            
437
1426013
                trace!("State {state_index} signature {builder:?} index {index}");
438
1426013
                index
439
1426013
            })
440
        {
441
440342
            if block_index != new_block_index {
442
                // If this is a new block, mark the incoming states as dirty
443
269536
                states.clear();
444
269536
                states.extend(partition.iter_block(new_block_index));
445

            
446
979138
                for &state_index in &states {
447
1188496
                    for transition in incoming.incoming_transitions(state_index) {
448
1188496
                        if BRANCHING {
449
                            // Mark incoming states into old blocks, or visible actions.
450
321636
                            if !lts.is_hidden_label(transition.label)
451
102530
                                || partition.block_number(transition.from) < num_blocks
452
                            {
453
241200
                                let other_block = partition.block_number(transition.from);
454

            
455
241200
                                if !partition.block(other_block).has_marked() {
456
65860
                                    // If block was not already marked then add it to the worklist.
457
65860
                                    worklist.push(other_block);
458
175344
                                }
459

            
460
241200
                                partition.mark_element(transition.from);
461
80436
                            }
462
                        } else {
463
                            // In this case mark all incoming states.
464
866860
                            let other_block = partition.block_number(transition.from);
465

            
466
866860
                            if !partition.block(other_block).has_marked() {
467
102733
                                // If block was not already marked then add it to the worklist.
468
102733
                                worklist.push(other_block);
469
764127
                            }
470

            
471
866860
                            partition.mark_element(transition.from);
472
                        }
473
                    }
474
                }
475
170806
            }
476
        }
477

            
478
170806
        trace!("Iteration {iteration} partition {partition}");
479

            
480
170806
        iteration += 1;
481

            
482
170806
        progress.print((iteration, partition.num_of_blocks()));
483
    }
484

            
485
2213
    trace!("Refinement partition {partition}");
486
2213
    partition
487
2213
}
488

            
489
/// Weak signature refinement algorithm, doing inductive signatures naively.
490
///
491
/// The signature function is called for each state and should fill the
492
/// signature builder with the pre_signature of the state.
493
804
fn signature_refinement_weak<L: LTS>(lts: &L) -> IndexedPartition {
494
    // Avoids reallocations when computing the signature.
495
804
    let mut arena = Bump::new();
496
804
    let mut builder = SignatureBuilder::default();
497

            
498
    // Put all the states in the initial partition { S }.
499
804
    let mut id: FxHashMap<Signature<'_>, BlockIndex> = FxHashMap::default();
500

            
501
    // Assigns the signature to each state.
502
804
    let mut partition = IndexedPartition::new(lts.num_of_states());
503
804
    let mut next_partition = IndexedPartition::new(lts.num_of_states());
504
804
    let mut state_to_signature: Vec<Option<usize>> = Vec::new();
505
804
    let mut key_to_signature: Vec<Signature> = Vec::new();
506
804
    let mut state_to_taus: Vec<Signature> = Vec::new();
507

            
508
804
    state_to_signature.resize_with(lts.num_of_states(), || None);
509
804
    state_to_taus.resize_with(lts.num_of_states(), Signature::default);
510

            
511
804
    let mut old_count = 1;
512
804
    let mut iteration = 0;
513

            
514
804
    let progress = TimeProgress::new(
515
        |(iteration, blocks)| {
516
            debug!("Iteration {iteration}, found {blocks} blocks...",);
517
        },
518
        5,
519
    );
520

            
521
    // This is a workaround for a data race in bumpalo for zero-sized slices.
522
804
    let empty_slice: &[(LabelIndex, BlockIndex)] = &[];
523
    // Refine partitions until stable.
524

            
525
4787
    while old_count != id.len() {
526
3983
        old_count = id.len();
527
3983
        progress.print((iteration, old_count));
528
3983
        swap(&mut partition, &mut next_partition);
529

            
530
        // Clear every collection that borrows from the arena *before* resetting
531
        // it, so that no `Signature` borrow can outlive the storage it points
532
        // into.
533
3983
        id.clear();
534
3983
        state_to_signature.clear();
535
3983
        key_to_signature.clear();
536
3983
        state_to_taus.clear();
537

            
538
        // Remove the current signatures; safe now that every borrowing collection
539
        // has been cleared above.
540
3983
        arena.reset();
541

            
542
3983
        state_to_signature.resize_with(lts.num_of_states(), || None);
543

            
544
        // SAFETY: `id` was cleared above, so it holds no `Signature` borrowing
545
        // from the arena; the transmute only relaxes that borrow's lifetime so the
546
        // map can be refilled with slices allocated from the freshly reset arena
547
        // during this iteration.
548
3983
        let id: &'_ mut FxHashMap<Signature<'_>, BlockIndex> = unsafe { std::mem::transmute(&mut id) };
549
        // SAFETY: see above; `key_to_signature` was cleared.
550
3983
        let key_to_signature: &'_ mut Vec<Signature<'_>> = unsafe { std::mem::transmute(&mut key_to_signature) };
551
        // SAFETY: see above; `state_to_taus` was cleared.
552
3983
        let state_to_taus: &'_ mut Vec<Signature<'_>> = unsafe { std::mem::transmute(&mut state_to_taus) };
553

            
554
        // Compute for each state its tau signature. This seems inefficient, but for now it works.
555
3983
        state_to_taus.resize_with(lts.num_of_states(), Signature::default);
556
1594819
        for state in lts.iter_states() {
557
1594819
            weak_bisim_signature_sorted_taus(state, lts, &partition, state_to_taus, &mut builder);
558

            
559
1594819
            let slice = if builder.is_empty() {
560
                empty_slice
561
            } else {
562
1594819
                arena.alloc_slice_copy(&builder)
563
            };
564
1594819
            state_to_taus[state] = Signature::new(slice);
565
        }
566

            
567
1594819
        for state_index in lts.iter_states() {
568
            // Compute the Presignature of a single state
569
1594819
            weak_bisim_presignature_sorted(
570
1594819
                state_index,
571
1594819
                lts,
572
1594819
                &partition,
573
1594819
                state_to_taus,
574
1594819
                &state_to_signature,
575
1594819
                &mut builder,
576
            );
577

            
578
            // Inductive step see if presig is a subset of a tau reachable state.
579
1594819
            let mut inductive_key = None;
580
3384788
            for keyvalue in builder.as_slice() {
581
3384788
                if is_tau_hat(keyvalue.0, lts) {
582
523315
                    let tau_sig = &key_to_signature[keyvalue.1.value()];
583
523315
                    let presig = Signature::new(&builder);
584

            
585
523315
                    if tau_sig.is_subset_of(presig.as_slice(), *keyvalue) {
586
219712
                        inductive_key = Some(*keyvalue.1);
587
219712
                        break;
588
303603
                    }
589
2861473
                }
590
            }
591
1594819
            if let Some(inductive_key) = inductive_key {
592
219712
                trace!(
593
                    "State {state_index} with pre {:?} uses inductive key {inductive_key}:{:?}",
594
                    builder.as_slice(),
595
                    key_to_signature[inductive_key].as_slice()
596
                );
597
219712
                state_to_signature[state_index] = Some(inductive_key);
598
219712
                next_partition.set_block(*state_index, BlockIndex::new(inductive_key));
599
            } else {
600
                // If not: expand the signature completely.
601
1375107
                weak_bisim_signature_sorted_full(
602
1375107
                    state_index,
603
1375107
                    lts,
604
1375107
                    &partition,
605
1375107
                    state_to_taus,
606
1375107
                    &state_to_signature,
607
1375107
                    key_to_signature,
608
1375107
                    &mut builder,
609
                );
610
1375107
                trace!("State {state_index} final signature {:?}", builder.as_slice());
611

            
612
                // Keep track of the index for every state
613
1375107
                let mut new_id = BlockIndex::new(key_to_signature.len());
614
1375107
                if let Some((_signature, index)) = id.get_key_value(&Signature::new(&builder)) {
615
961145
                    state_to_signature[state_index] = Some(index.value());
616
961145
                    new_id = *index;
617
961145
                } else {
618
413962
                    let slice = if builder.is_empty() {
619
                        empty_slice
620
                    } else {
621
413962
                        arena.alloc_slice_copy(&builder)
622
                    };
623
413962
                    id.insert(Signature::new(slice), new_id);
624
413962
                    key_to_signature.push(Signature::new(slice));
625

            
626
413962
                    state_to_signature[state_index] = Some(new_id.value());
627
                }
628

            
629
1375107
                next_partition.set_block(*state_index, new_id);
630
            };
631
        }
632

            
633
3983
        iteration += 1;
634

            
635
3983
        debug_assert!(
636
3983
            iteration <= lts.num_of_states().max(2),
637
            "There can never be more splits than number of states, but at least two iterations for stability"
638
        );
639
    }
640

            
641
804
    trace!("Refinement partition {partition}");
642
804
    partition
643
804
}
644

            
645
/// General signature refinement algorithm that accepts an arbitrary signature
646
///
647
/// The signature function is called for each state and should fill the
648
/// signature builder with the signature of the state. It consists of the
649
/// current partition, the signatures per state for the next partition.
650
2209
fn signature_refinement_naive<F, L: LTS, const WEAK: bool>(lts: &L, mut signature: F) -> IndexedPartition
651
2209
where
652
2209
    F: FnMut(StateIndex, &IndexedPartition, &Vec<Signature<'_>>, &mut SignatureBuilder),
653
{
654
    // Avoids reallocations when computing the signature.
655
2209
    let mut arena = Bump::new();
656
2209
    let mut builder = SignatureBuilder::default();
657

            
658
    // Put all the states in the initial partition { S }.
659
2209
    let mut id: FxHashMap<Signature<'_>, BlockIndex> = FxHashMap::default();
660

            
661
    // Assigns the signature to each state.
662
2209
    let mut partition = IndexedPartition::new(lts.num_of_states());
663
2209
    let mut next_partition = IndexedPartition::new(lts.num_of_states());
664
2209
    let mut state_to_signature: Vec<Signature<'_>> = Vec::new();
665
2209
    state_to_signature.resize_with(lts.num_of_states(), Signature::default);
666

            
667
    // Refine partitions until stable.
668
2209
    let mut old_count = 1;
669
2209
    let mut iteration = 0;
670

            
671
2209
    let progress = TimeProgress::new(
672
        |(iteration, blocks)| {
673
            debug!("Iteration {iteration}, found {blocks} blocks...",);
674
        },
675
        5,
676
    );
677

            
678
    // This is a workaround for a data race in bumpalo for zero-sized slices.
679
2209
    let empty_slice: &[(LabelIndex, BlockIndex)] = &[];
680

            
681
13515
    while old_count != id.len() {
682
11306
        trace!("Iteration {} ({} blocks)", iteration, id.len());
683

            
684
11306
        old_count = id.len();
685
11306
        progress.print((iteration, old_count));
686
11306
        swap(&mut partition, &mut next_partition);
687

            
688
        // Clear the current partition to start the next blocks.
689
11306
        id.clear();
690

            
691
11306
        state_to_signature.clear();
692
11306
        state_to_signature.resize_with(lts.num_of_states(), Signature::default);
693

            
694
        // SAFETY: `id` was cleared above, so it holds no `Signature` borrowing
695
        // from the arena; the transmute only relaxes that borrow's lifetime so it
696
        // can be refilled with slices allocated after the `arena.reset()` below.
697
11306
        let id: &'_ mut FxHashMap<Signature<'_>, BlockIndex> = unsafe { std::mem::transmute(&mut id) };
698
        // SAFETY: see above; `state_to_signature` was cleared (it now holds only
699
        // the empty default signatures, which borrow no arena storage).
700
11306
        let state_to_signature: &mut Vec<Signature<'_>> = unsafe { std::mem::transmute(&mut state_to_signature) };
701

            
702
        // Remove the current signatures.
703
11306
        arena.reset();
704

            
705
11306
        if WEAK {
706
1595564
            for state_index in lts.iter_states() {
707
1595564
                weak_bisim_signature_sorted_taus(state_index, lts, &partition, state_to_signature, &mut builder);
708

            
709
1595564
                trace!("State {state_index} weak signature {:?}", builder);
710

            
711
                // Keep track of the index for every state, either use the arena to allocate space or simply borrow the value.
712
1595564
                let slice = if builder.is_empty() {
713
                    empty_slice
714
                } else {
715
1595564
                    arena.alloc_slice_copy(&builder)
716
                };
717
1595564
                state_to_signature[state_index] = Signature::new(slice);
718
            }
719
7347
        }
720

            
721
5280743
        for state_index in lts.iter_states() {
722
            // Compute the signature of a single state
723
5280743
            signature(state_index, &partition, state_to_signature, &mut builder);
724

            
725
5280743
            trace!("State {state_index} signature {builder:?}");
726

            
727
            // Keep track of the index for every state, either use the arena to allocate space or simply borrow the value.
728
5280743
            let mut new_id = BlockIndex::new(id.len());
729
5280743
            if let Some((signature, index)) = id.get_key_value(&Signature::new(&builder)) {
730
3810865
                // SAFETY: `signature` borrows from the arena, which outlives this
731
3810865
                // iteration; the transmute only re-labels that borrow with the
732
3810865
                // lifetime expected by `state_to_signature`.
733
3810865
                state_to_signature[state_index] = unsafe {
734
3810865
                    std::mem::transmute::<Signature<'_>, Signature<'_>>(Signature::new(signature.as_slice()))
735
3810865
                };
736
3810865
                new_id = *index;
737
3810865
            } else {
738
1469878
                let slice = if builder.is_empty() {
739
7342
                    empty_slice
740
                } else {
741
1462536
                    arena.alloc_slice_copy(&builder)
742
                };
743
1469878
                id.insert(Signature::new(slice), new_id);
744

            
745
                // (branching) Keep track of the signature for every block in the next partition.
746
1469878
                state_to_signature[state_index] = Signature::new(slice);
747
            }
748

            
749
5280743
            next_partition.set_block(*state_index, new_id);
750
        }
751

            
752
11306
        iteration += 1;
753

            
754
11306
        debug_assert!(
755
11306
            iteration <= lts.num_of_states().max(2),
756
            "There can never be more splits than number of states, but at least two iterations for stability"
757
        );
758
    }
759

            
760
2209
    trace!("Refinement partition {partition}");
761
2209
    debug_assert!(
762
922648
        is_valid_refinement(lts, &partition, |state_index, partition, builder| signature(
763
922648
            state_index,
764
922648
            partition,
765
922648
            &state_to_signature,
766
922648
            builder
767
        )),
768
        "The resulting partition is not a valid partition."
769
    );
770
2209
    partition
771
2209
}
772

            
773
/// Returns true iff the given partition is a strong bisimulation partition
774
2209
pub fn is_valid_refinement<F, P, L>(lts: &L, partition: &P, mut compute_signature: F) -> bool
775
2209
where
776
2209
    F: FnMut(StateIndex, &P, &mut SignatureBuilder),
777
2209
    P: Partition,
778
2209
    L: LTS,
779
{
780
    // Check that the partition is indeed stable and as such is a quotient of strong bisimulation
781
2209
    let mut block_to_signature: Vec<Option<SignatureBuilder>> = vec![None; partition.num_of_blocks()];
782

            
783
    // Avoids reallocations when computing the signature.
784
2209
    let mut builder = SignatureBuilder::default();
785

            
786
922648
    for state_index in lts.iter_states() {
787
922648
        let block = partition.block_number(state_index);
788

            
789
        // Compute the flat signature, which has Hash and is more compact.
790
922648
        compute_signature(state_index, partition, &mut builder);
791
922648
        let signature: Vec<(LabelIndex, BlockIndex)> = builder.clone();
792

            
793
922648
        if let Some(block_signature) = &block_to_signature[block] {
794
559995
            if signature != *block_signature {
795
                trace!(
796
                    "State {state_index} has a different signature {signature:?} then the block {block} which has signature {block_signature:?}"
797
                );
798
                return false;
799
559995
            }
800
362653
        } else {
801
362653
            block_to_signature[block] = Some(signature);
802
362653
        };
803
    }
804

            
805
    // Check if there are two blocks with the same signature
806
2209
    let mut signature_to_block: FxHashMap<Signature, usize> = FxHashMap::default();
807

            
808
362653
    for (block_index, signature) in block_to_signature
809
2209
        .iter()
810
362653
        .map(|signature: &Option<SignatureBuilder>| signature.as_ref().expect("Signature should be defined"))
811
2209
        .enumerate()
812
    {
813
362653
        if let Some(other_block_index) = signature_to_block.get(&Signature::new(signature)) {
814
            if block_index != *other_block_index {
815
                trace!("Block {block_index} and {other_block_index} have the same signature {signature:?}");
816
                return false;
817
            }
818
362653
        } else {
819
362653
            signature_to_block.insert(Signature::new(signature), block_index);
820
362653
        }
821
    }
822

            
823
2209
    true
824
2209
}
825

            
826
#[cfg(test)]
827
mod tests {
828
    use test_log::test;
829

            
830
    use merc_io::DumpFiles;
831
    use merc_lts::random_lts;
832
    use merc_lts::write_aut;
833
    use merc_utilities::Timing;
834
    use merc_utilities::random_test;
835

            
836
    use super::BlockIndex;
837
    use super::LTS;
838
    use super::Partition;
839
    use super::StateIndex;
840
    use super::branching_bisim_sigref;
841
    use super::branching_bisim_sigref_naive;
842
    use super::strong_bisim_sigref;
843
    use super::strong_bisim_sigref_naive;
844
    use super::weak_bisim_sigref_inductive_naive;
845
    use super::weak_bisim_sigref_naive;
846

            
847
    use merc_lts::LabelIndex;
848
    use merc_lts::LabelledTransitionSystem;
849
    use merc_lts::TransitionLabel;
850

            
851
    /// A tiny deterministic instance that exercises the `unsafe` arena-reuse path
852
    /// in [`signature_refinement`] under miri; the randomized tests below build
853
    /// 1000-state systems and are skipped under miri because they are too slow.
854
    #[test]
855
1
    fn test_strong_bisim_sigref_small() {
856
        // 0 -a-> 1, 0 -a-> 2, 1 -a-> 3, 2 -a-> 3 (label index 1 is "a", 0 is tau).
857
1
        let transitions = [(0, 1, 1), (0, 1, 2), (1, 1, 3), (2, 1, 3)]
858
4
            .map(|(from, label, to)| (StateIndex::new(from), LabelIndex::new(label), StateIndex::new(to)));
859

            
860
1
        let lts = LabelledTransitionSystem::new(
861
1
            StateIndex::new(0),
862
1
            None,
863
2
            || transitions.iter().cloned(),
864
1
            vec![String::tau_label(), "a".to_string()],
865
        );
866

            
867
1
        let timing = Timing::new();
868
1
        let (_, partition) = strong_bisim_sigref(lts, &timing);
869

            
870
        // States 1 and 2 are strongly bisimilar (both only do a -> 3), so the four
871
        // states collapse into the three blocks {0}, {1, 2}, {3}.
872
1
        assert_eq!(partition.num_of_blocks(), 3);
873
1
        assert_eq!(
874
1
            partition.block_number(StateIndex::new(1)),
875
1
            partition.block_number(StateIndex::new(2))
876
        );
877
1
    }
878

            
879
    /// Exercises the weak signature-refinement arena path
880
    /// ([`signature_refinement_weak`], where `arena.reset()` is interleaved with
881
    /// clearing the borrowing collections) under miri, for the same reason as
882
    /// [`test_strong_bisim_sigref_small`].
883
    #[test]
884
1
    fn test_weak_bisim_sigref_small() {
885
        // 0 -tau-> 1, 1 -a-> 2, 0 -a-> 2 (label index 1 is "a", 0 is tau).
886
1
        let transitions = [(0, 0, 1), (1, 1, 2), (0, 1, 2)]
887
3
            .map(|(from, label, to)| (StateIndex::new(from), LabelIndex::new(label), StateIndex::new(to)));
888

            
889
1
        let lts = LabelledTransitionSystem::new(
890
1
            StateIndex::new(0),
891
1
            None,
892
2
            || transitions.iter().cloned(),
893
1
            vec![String::tau_label(), "a".to_string()],
894
        );
895

            
896
1
        let timing = Timing::new();
897
1
        let (_, _, partition) = weak_bisim_sigref_inductive_naive(lts, StateIndex::new(0), false, false, &timing);
898

            
899
        // State 2 is a deadlock while state 0 can still perform a, so they must end
900
        // up in different blocks.
901
1
        assert_ne!(
902
1
            partition.block_number(StateIndex::new(0)),
903
1
            partition.block_number(StateIndex::new(2))
904
        );
905
1
    }
906

            
907
    /// Returns true iff the partitions are equal, runs in O(n^2).
908
300
    fn equal_partitions<P: Partition, Q: Partition>(left: &P, right: &Q) -> bool {
909
        // Check that states in the same block, have a single (unique) number in
910
        // the other partition.
911
118140
        for block_index in (0..left.num_of_blocks()).map(BlockIndex::new) {
912
118140
            let mut other_block_index = None;
913

            
914
299973
            for state_index in (0..left.len())
915
118140
                .map(StateIndex::new)
916
118129992
                .filter(|&state_index| left.block_number(state_index) == block_index)
917
            {
918
299973
                match other_block_index {
919
118140
                    None => other_block_index = Some(right.block_number(state_index)),
920
181833
                    Some(other_block_index) => {
921
181833
                        if right.block_number(state_index) != other_block_index {
922
                            return false;
923
181833
                        }
924
                    }
925
                }
926
            }
927
        }
928

            
929
118140
        for block_index in (0..right.num_of_blocks()).map(BlockIndex::new) {
930
118140
            let mut other_block_index = None;
931

            
932
299973
            for state_index in (0..left.len())
933
118140
                .map(StateIndex::new)
934
118129992
                .filter(|&state_index| right.block_number(state_index) == block_index)
935
            {
936
299973
                match other_block_index {
937
118140
                    None => other_block_index = Some(left.block_number(state_index)),
938
181833
                    Some(other_block_index) => {
939
181833
                        if left.block_number(state_index) != other_block_index {
940
                            return false;
941
181833
                        }
942
                    }
943
                }
944
            }
945
        }
946

            
947
300
        true
948
300
    }
949

            
950
    /// Checks that the strong bisimulation partition is a refinement of the branching bisimulation partition.
951
200
    fn is_refinement<L: LTS, P: Partition, Q: Partition>(lts: &L, strong_partition: &P, branching_partition: &Q) {
952
199972
        for state_index in lts.iter_states() {
953
199944060
            for other_state_index in lts.iter_states() {
954
199944060
                if strong_partition.block_number(state_index) == strong_partition.block_number(other_state_index) {
955
                    // If the states are together according to strong bisimilarity, then they should also be together according to branching bisimilarity.
956
27055510
                    assert_eq!(
957
27055510
                        branching_partition.block_number(state_index),
958
27055510
                        branching_partition.block_number(other_state_index),
959
                        "The strong partition should be a refinement of the branching partition, but states {state_index} and {other_state_index} are in different strong blocks"
960
                    );
961
172888550
                }
962
            }
963
        }
964
200
    }
965

            
966
    #[test]
967
    #[cfg_attr(miri, ignore)] // Miri is too slow
968
1
    fn test_random_strong_bisim_sigref() {
969
100
        random_test(100, |rng| {
970
100
            let files = DumpFiles::new("test_random_strong_bisim_sigref");
971

            
972
100
            let lts = random_lts::<String, _>(rng, 1000, 3);
973
100
            files.dump("input.aut", |writer| write_aut(writer, &lts)).unwrap();
974

            
975
100
            let timing = Timing::new();
976

            
977
100
            let (result_lts, result_partition) = strong_bisim_sigref(lts.clone(), &timing);
978
100
            let (expected_lts, expected_partition) = strong_bisim_sigref_naive(lts, &timing);
979

            
980
100
            files
981
100
                .dump("result.aut", |writer| write_aut(writer, &result_lts))
982
100
                .unwrap();
983
100
            files
984
100
                .dump("expected.aut", |writer| write_aut(writer, &expected_lts))
985
100
                .unwrap();
986

            
987
            // There is no preprocessing so this works.
988
100
            assert!(equal_partitions(&result_partition, &expected_partition));
989
100
        });
990
1
    }
991

            
992
    #[test]
993
    #[cfg_attr(miri, ignore)] // Miri is too slow
994
1
    fn test_random_branching_bisim_sigref() {
995
100
        random_test(100, |rng| {
996
100
            let files = DumpFiles::new("test_random_branching_bisim_sigref");
997

            
998
100
            let lts = random_lts::<String, _>(rng, 1000, 3);
999
100
            files.dump("input.aut", |writer| write_aut(writer, &lts)).unwrap();
100
            let timing = Timing::new();
100
            let (result_lts, _, result_partition) =
100
                branching_bisim_sigref(lts.clone(), StateIndex::new(0), false, &timing);
100
            let (expected_lts, _, expected_partition) =
100
                branching_bisim_sigref_naive(lts, StateIndex::new(0), false, &timing);
100
            files
100
                .dump("result.aut", |writer| write_aut(writer, &result_lts))
100
                .unwrap();
100
            files
100
                .dump("expected.aut", |writer| write_aut(writer, &expected_lts))
100
                .unwrap();
            // There is no preprocessing so this works.
100
            assert!(equal_partitions(&result_partition, &expected_partition));
100
        });
1
    }
    #[test]
    #[cfg_attr(miri, ignore)] // Miri is too slow
1
    fn test_random_weak_bisim_sigref() {
100
        random_test(100, |rng| {
100
            let files = DumpFiles::new("test_random_weak_bisim_sigref");
100
            let lts = random_lts::<String, _>(rng, 1000, 3);
100
            files.dump("input.aut", |writer| write_aut(writer, &lts)).unwrap();
100
            let timing = Timing::new();
100
            let (result_lts, _, result_partition) =
100
                weak_bisim_sigref_naive(lts.clone(), StateIndex::new(0), false, false, &timing);
100
            let (expected_lts, _, expected_partition) =
100
                weak_bisim_sigref_inductive_naive(lts, StateIndex::new(0), false, false, &timing);
100
            files
100
                .dump("result.aut", |writer| write_aut(writer, &result_lts))
100
                .unwrap();
100
            files
100
                .dump("expected.aut", |writer| write_aut(writer, &expected_lts))
100
                .unwrap();
            // There is no preprocessing so this works.
100
            assert!(equal_partitions(&result_partition, &expected_partition));
100
        });
1
    }
    #[test]
    #[cfg_attr(miri, ignore)] // Miri is too slow
1
    fn test_random_branching_bisim_sigref_naive() {
100
        random_test(100, |rng| {
100
            let files = DumpFiles::new("test_random_branching_bisim_sigref_naive");
100
            let lts = random_lts::<String, _>(rng, 1000, 3);
100
            files.dump("input.aut", |writer| write_aut(writer, &lts)).unwrap();
100
            let timing = Timing::new();
100
            let (preprocessed_lts, _, branching_partition) =
100
                branching_bisim_sigref_naive(lts, StateIndex::new(0), false, &timing);
100
            files
100
                .dump("preprocessed.aut", |writer| write_aut(writer, &preprocessed_lts))
100
                .unwrap();
100
            let strong_partition = strong_bisim_sigref_naive(preprocessed_lts.clone(), &timing).1;
100
            is_refinement(&preprocessed_lts, &strong_partition, &branching_partition);
100
        });
1
    }
    #[test]
    #[cfg_attr(miri, ignore)] // Miri is too slow
1
    fn test_random_weak_bisim_sigref_naive() {
100
        random_test(100, |rng| {
100
            let files = DumpFiles::new("test_random_weak_bisim_sigref_naive");
100
            let lts = random_lts::<String, _>(rng, 1000, 3);
100
            files.dump("input.aut", |writer| write_aut(writer, &lts)).unwrap();
100
            let timing = Timing::new();
100
            let (preprocessed_lts, _, weak_partition) =
100
                weak_bisim_sigref_naive(lts, StateIndex::new(0), false, false, &timing);
100
            files
100
                .dump("preprocessed.aut", |writer| write_aut(writer, &preprocessed_lts))
100
                .unwrap();
100
            let (_, _, branching_partition) =
100
                branching_bisim_sigref_naive(preprocessed_lts.clone(), StateIndex::new(0), false, &timing);
100
            is_refinement(&preprocessed_lts, &branching_partition, &weak_partition);
100
        });
1
    }
    /// Exercises the `unsafe` arena/lifetime-reuse paths in the signature
    /// refinement implementations on small inputs.
    #[test]
1
    fn test_miri_sigref_unsafe_paths() {
3
        random_test(3, |rng| {
3
            let lts = random_lts::<String, _>(rng, 6, 3);
3
            let timing = Timing::new();
            // signature_refinement (strong + branching with inductive renumbering).
3
            let _ = strong_bisim_sigref(lts.clone(), &timing);
3
            let _ = branching_bisim_sigref(lts.clone(), StateIndex::new(0), false, &timing);
3
            let _ = branching_bisim_sigref(lts.clone(), StateIndex::new(0), true, &timing);
            // signature_refinement_naive (strong, branching and weak signatures).
3
            let _ = strong_bisim_sigref_naive(lts.clone(), &timing);
3
            let _ = branching_bisim_sigref_naive(lts.clone(), StateIndex::new(0), false, &timing);
3
            let _ = weak_bisim_sigref_naive(lts.clone(), StateIndex::new(0), false, false, &timing);
            // signature_refinement_weak (inductive weak signatures).
3
            let _ = weak_bisim_sigref_inductive_naive(lts, StateIndex::new(0), false, false, &timing);
3
        });
1
    }
}