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
1000
pub fn strong_bisim_sigref<L: LTS>(lts: L, timing: &Timing) -> (L, BlockPartition) {
42
1000
    let incoming = timing.measure("preprocess", || IncomingTransitions::new(&lts));
43

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

            
55
1000
    (lts, partition)
56
1000
}
57

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

            
66
500
    (lts, partition)
67
500
}
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
1103
pub fn branching_bisim_sigref<L: LTS>(
75
1103
    lts: L,
76
1103
    state: StateIndex,
77
1103
    divergence_preserving: bool,
78
1103
    timing: &Timing,
79
1103
) -> (LabelledTransitionSystem<L::Label>, StateIndex, BlockPartition) {
80
1103
    let (preprocessed_lts, mapped_state) = timing.measure("preprocess", || {
81
1103
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
82
1103
    });
83

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

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

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

            
97
1103
    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
1103
    }
101

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

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

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

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

            
142
330833
                    if !is_tau_hat(*label, preprocessed_lts) {
143
277412
                        return None;
144
53421
                    }
145
                }
146

            
147
93748
                None
148
442434
            },
149
        )
150
1103
    })
151
1103
}
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
900
pub fn branching_bisim_sigref_naive<L: LTS>(
160
900
    lts: L,
161
900
    state: StateIndex,
162
900
    divergence_preserving: bool,
163
900
    timing: &Timing,
164
900
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
165
900
    let (preprocessed_lts, mapped_state) = timing.measure("preprocess", || {
166
900
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
167
900
    });
168

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

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

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

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

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

            
202
2648362
                    let signature = Signature::new(builder);
203
2648362
                    debug_assert_eq!(
204
2648362
                        signature.as_slice(),
205
                        expected_result,
206
                        "The sorted and expected signature should be the same"
207
                    );
208
                }
209
2648362
            },
210
        )
211
900
    })
212
900
}
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
800
pub fn weak_bisim_sigref_inductive_naive<L: LTS>(
218
800
    lts: L,
219
800
    state: StateIndex,
220
800
    preprocess: bool,
221
800
    divergence_preserving: bool,
222
800
    timing: &Timing,
223
800
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
224
    // Preprocess the LTS if desired.
225
800
    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
800
        weak_bisim_sigref_inductive_naive_impl(lts, state, divergence_preserving, timing)
235
    }
236
800
}
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
800
pub fn weak_bisim_sigref_inductive_naive_impl<L: LTS>(
242
800
    lts: L,
243
800
    state: StateIndex,
244
800
    divergence_preserving: bool,
245
800
    timing: &Timing,
246
800
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
247
800
    let (preprocessed_lts, mapped_state) = timing.measure("preprocess", || {
248
800
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
249
800
    });
250
800
    let partition = timing.measure("reduction", || {
251
800
        if divergence_preserving {
252
300
            signature_refinement_weak(&DivergencePreservingLts::new(&preprocessed_lts))
253
        } else {
254
500
            signature_refinement_weak(&preprocessed_lts)
255
        }
256
800
    });
257
800
    (preprocessed_lts, mapped_state, partition)
258
800
}
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
800
pub fn weak_bisim_sigref_naive<L: LTS>(
264
800
    lts: L,
265
800
    state: StateIndex,
266
800
    preprocess: bool,
267
800
    divergence_preserving: bool,
268
800
    timing: &Timing,
269
800
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
270
    // Preprocess the LTS if desired.
271
800
    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
800
        weak_bisim_sigref_naive_impl(lts, state, divergence_preserving, timing)
281
    }
282
800
}
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
800
fn weak_bisim_sigref_naive_impl<L: LTS>(
290
800
    lts: L,
291
800
    state: StateIndex,
292
800
    divergence_preserving: bool,
293
800
    timing: &Timing,
294
800
) -> (LabelledTransitionSystem<L::Label>, StateIndex, IndexedPartition) {
295
800
    let (preprocessed_lts, mapped_state) = timing.measure("preprocess", || {
296
800
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
297
800
    });
298
800
    let partition = timing.measure("reduction", || {
299
800
        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
250573
                |state_index, partition, state_to_signature, builder| {
304
250573
                    weak_bisim_signature_sorted(
305
250573
                        state_index,
306
250573
                        &divergence_preserving_lts,
307
250573
                        partition,
308
250573
                        state_to_signature,
309
250573
                        builder,
310
                    )
311
250573
                },
312
            )
313
        } else {
314
500
            signature_refinement_naive::<_, _, true>(
315
500
                &preprocessed_lts,
316
1633452
                |state_index, partition, state_to_signature, builder| {
317
1633452
                    weak_bisim_signature_sorted(state_index, &preprocessed_lts, partition, state_to_signature, builder)
318
1633452
                },
319
            )
320
        }
321
800
    });
322

            
323
800
    (preprocessed_lts, mapped_state, partition)
324
800
}
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
2103
fn signature_refinement<F, G, L, const BRANCHING: bool>(
341
2103
    lts: &L,
342
2103
    incoming: &IncomingTransitions,
343
2103
    mut signature: F,
344
2103
    mut renumber: G,
345
2103
) -> BlockPartition
346
2103
where
347
2103
    F: FnMut(StateIndex, &BlockPartition, &[BlockIndex], &mut SignatureBuilder),
348
2103
    G: FnMut(&[(LabelIndex, BlockIndex)], &Vec<Signature>) -> Option<BlockIndex>,
349
2103
    L: LTS,
350
{
351
    // Avoids reallocations when computing the signature.
352
2103
    let mut arena = Bump::new();
353
2103
    let mut builder = SignatureBuilder::default();
354
2103
    let mut split_builder = BlockPartitionBuilder::default();
355

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

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

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

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

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

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

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

            
386
        // Safety: The current signatures have been removed, so it safe to reuse the memory.
387
168987
        let id: &mut FxHashMap<Signature<'_>, BlockIndex> = unsafe { std::mem::transmute(&mut id) };
388
168987
        let key_to_signature: &'_ mut Vec<Signature<'_>> = unsafe { std::mem::transmute(&mut key_to_signature) };
389

            
390
168987
        arena.reset();
391

            
392
168987
        let block = partition.block(block_index);
393
168987
        debug_assert!(
394
168987
            block.has_marked(),
395
            "Every block in the worklist should have at least one marked state"
396
        );
397

            
398
168987
        if BRANCHING {
399
66983
            partition.mark_backward_closure(block_index, incoming);
400
119922
        }
401

            
402
        // Blocks above this number are new in this iteration.
403
168987
        let num_blocks = partition.num_of_blocks();
404

            
405
        // This is a workaround for a data race in bumpalo for zero-sized slices.
406
168987
        let empty_slice: &[(LabelIndex, BlockIndex)] = &[];
407

            
408
436742
        for new_block_index in
409
1519947
            partition.partition_marked_with(block_index, &mut split_builder, |state_index, partition| {
410
1519947
                signature(state_index, partition, &state_to_key, &mut builder);
411

            
412
                // Compute the signature of a single state
413
1519947
                let index = if let Some(key) = renumber(&builder, key_to_signature) {
414
71274
                    key
415
1448673
                } else if let Some((_, index)) = id.get_key_value(&Signature::new(&builder)) {
416
1107328
                    *index
417
                } else {
418
341345
                    let slice = if builder.is_empty() {
419
1958
                        empty_slice
420
                    } else {
421
339387
                        arena.alloc_slice_copy(&builder)
422
                    };
423
341345
                    let number = BlockIndex::new(key_to_signature.len());
424
341345
                    id.insert(Signature::new(slice), number);
425
341345
                    key_to_signature.push(Signature::new(slice));
426

            
427
341345
                    number
428
                };
429

            
430
                // (branching) Keep track of the signature for every block in the next partition.
431
1519947
                state_to_key[state_index] = index;
432

            
433
1519947
                trace!("State {state_index} signature {builder:?} index {index}");
434
1519947
                index
435
1519947
            })
436
        {
437
436742
            if block_index != new_block_index {
438
                // If this is a new block, mark the incoming states as dirty
439
267755
                states.clear();
440
267755
                states.extend(partition.iter_block(new_block_index));
441

            
442
1005861
                for &state_index in &states {
443
1505855
                    for transition in incoming.incoming_transitions(state_index) {
444
1505855
                        if BRANCHING {
445
                            // Mark incoming states into old blocks, or visible actions.
446
321733
                            if !lts.is_hidden_label(transition.label)
447
102326
                                || partition.block_number(transition.from) < num_blocks
448
                            {
449
241373
                                let other_block = partition.block_number(transition.from);
450

            
451
241373
                                if !partition.block(other_block).has_marked() {
452
65880
                                    // If block was not already marked then add it to the worklist.
453
65880
                                    worklist.push(other_block);
454
175497
                                }
455

            
456
241373
                                partition.mark_element(transition.from);
457
80360
                            }
458
                        } else {
459
                            // In this case mark all incoming states.
460
1184122
                            let other_block = partition.block_number(transition.from);
461

            
462
1184122
                            if !partition.block(other_block).has_marked() {
463
101004
                                // If block was not already marked then add it to the worklist.
464
101004
                                worklist.push(other_block);
465
1083118
                            }
466

            
467
1184122
                            partition.mark_element(transition.from);
468
                        }
469
                    }
470
                }
471
168987
            }
472
        }
473

            
474
168987
        trace!("Iteration {iteration} partition {partition}");
475

            
476
168987
        iteration += 1;
477

            
478
168987
        progress.print((iteration, partition.num_of_blocks()));
479
    }
480

            
481
2103
    trace!("Refinement partition {partition}");
482
2103
    partition
483
2103
}
484

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

            
495
    // Put all the states in the initial partition { S }.
496
800
    let mut id: FxHashMap<Signature<'_>, BlockIndex> = FxHashMap::default();
497

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

            
505
800
    state_to_signature.resize_with(lts.num_of_states(), || None);
506
800
    state_to_taus.resize_with(lts.num_of_states(), Signature::default);
507

            
508
800
    let mut old_count = 1;
509
800
    let mut iteration = 0;
510

            
511
800
    let progress = TimeProgress::new(
512
        |(iteration, blocks)| {
513
            debug!("Iteration {iteration}, found {blocks} blocks...",);
514
        },
515
        5,
516
    );
517

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

            
522
4790
    while old_count != id.len() {
523
3990
        old_count = id.len();
524
3990
        progress.print((iteration, old_count));
525
3990
        swap(&mut partition, &mut next_partition);
526

            
527
        // Clear the current partition to start the next blocks.
528
3990
        id.clear();
529

            
530
        // Remove the current signatures.
531
3990
        arena.reset();
532

            
533
3990
        state_to_signature.clear();
534
3990
        key_to_signature.clear();
535

            
536
3990
        state_to_signature.resize_with(lts.num_of_states(), || None);
537
        // Safety: The current signatures have been removed, so it safe to reuse the memory.
538
3990
        let state_to_signature: &'_ mut Vec<Option<usize>> = unsafe { std::mem::transmute(&mut state_to_signature) };
539
3990
        let id: &'_ mut FxHashMap<Signature<'_>, BlockIndex> = unsafe { std::mem::transmute(&mut id) };
540
3990
        let key_to_signature: &'_ mut Vec<Signature<'_>> = unsafe { std::mem::transmute(&mut key_to_signature) };
541
3990
        let state_to_taus: &'_ mut Vec<Signature<'_>> = unsafe { std::mem::transmute(&mut state_to_taus) };
542

            
543
        // Compute for each state its tau signature. This seems inefficient, but for now it works.
544
3990
        state_to_taus.resize_with(lts.num_of_states(), Signature::default);
545
1601390
        for state in lts.iter_states() {
546
1601390
            weak_bisim_signature_sorted_taus(state, lts, &partition, state_to_taus, &mut builder);
547

            
548
1601390
            let slice = if builder.is_empty() {
549
                empty_slice
550
            } else {
551
1601390
                arena.alloc_slice_copy(&builder)
552
            };
553
1601390
            state_to_taus[state] = Signature::new(slice);
554
        }
555

            
556
1601390
        for state_index in lts.iter_states() {
557
            // Compute the Presignature of a single state
558
1601390
            weak_bisim_presignature_sorted(
559
1601390
                state_index,
560
1601390
                lts,
561
1601390
                &partition,
562
1601390
                state_to_taus,
563
1601390
                state_to_signature,
564
1601390
                &mut builder,
565
            );
566

            
567
            // Inductive step see if presig is a subset of a tau reachable state.
568
1601390
            let mut inductive_key = None;
569
3397141
            for keyvalue in builder.as_slice() {
570
3397141
                if is_tau_hat(keyvalue.0, lts) {
571
523568
                    let tau_sig = &key_to_signature[keyvalue.1.value()];
572
523568
                    let presig = Signature::new(&builder);
573

            
574
523568
                    if tau_sig.is_subset_of(presig.as_slice(), *keyvalue) {
575
218983
                        inductive_key = Some(*keyvalue.1);
576
218983
                        break;
577
304585
                    }
578
2873573
                }
579
            }
580
1601390
            if let Some(inductive_key) = inductive_key {
581
218983
                trace!(
582
                    "State {state_index} with pre {:?} uses inductive key {inductive_key}:{:?}",
583
                    builder.as_slice(),
584
                    key_to_signature[inductive_key].as_slice()
585
                );
586
218983
                state_to_signature[state_index] = Some(inductive_key);
587
218983
                next_partition.set_block(*state_index, BlockIndex::new(inductive_key));
588
            } else {
589
                // If not: expand the signature completely.
590
1382407
                weak_bisim_signature_sorted_full(
591
1382407
                    state_index,
592
1382407
                    lts,
593
1382407
                    &partition,
594
1382407
                    state_to_taus,
595
1382407
                    state_to_signature,
596
1382407
                    key_to_signature,
597
1382407
                    &mut builder,
598
                );
599
1382407
                trace!("State {state_index} final signature {:?}", builder.as_slice());
600

            
601
                // Keep track of the index for every state
602
1382407
                let mut new_id = BlockIndex::new(key_to_signature.len());
603
1382407
                if let Some((_signature, index)) = id.get_key_value(&Signature::new(&builder)) {
604
965020
                    // SAFETY: We know that the signature lives as long as the arena
605
965020
                    state_to_signature[state_index] = Some(index.value());
606
965020
                    new_id = *index;
607
965020
                } else {
608
417387
                    let slice = if builder.is_empty() {
609
                        empty_slice
610
                    } else {
611
417387
                        arena.alloc_slice_copy(&builder)
612
                    };
613
417387
                    id.insert(Signature::new(slice), new_id);
614
417387
                    key_to_signature.push(Signature::new(slice));
615

            
616
417387
                    state_to_signature[state_index] = Some(new_id.value());
617
                }
618

            
619
1382407
                next_partition.set_block(*state_index, new_id);
620
            };
621
        }
622

            
623
3990
        iteration += 1;
624

            
625
3990
        debug_assert!(
626
3990
            iteration <= lts.num_of_states().max(2),
627
            "There can never be more splits than number of states, but at least two iterations for stability"
628
        );
629
    }
630

            
631
800
    trace!("Refinement partition {partition}");
632
800
    partition
633
800
}
634

            
635
/// General signature refinement algorithm that accepts an arbitrary signature
636
///
637
/// The signature function is called for each state and should fill the
638
/// signature builder with the signature of the state. It consists of the
639
/// current partition, the signatures per state for the next partition.
640
2200
fn signature_refinement_naive<F, L: LTS, const WEAK: bool>(lts: &L, mut signature: F) -> IndexedPartition
641
2200
where
642
2200
    F: FnMut(StateIndex, &IndexedPartition, &Vec<Signature<'_>>, &mut SignatureBuilder),
643
{
644
    // Avoids reallocations when computing the signature.
645
2200
    let mut arena = Bump::new();
646
2200
    let mut builder = SignatureBuilder::default();
647

            
648
    // Put all the states in the initial partition { S }.
649
2200
    let mut id: FxHashMap<Signature<'_>, BlockIndex> = FxHashMap::default();
650

            
651
    // Assigns the signature to each state.
652
2200
    let mut partition = IndexedPartition::new(lts.num_of_states());
653
2200
    let mut next_partition = IndexedPartition::new(lts.num_of_states());
654
2200
    let mut state_to_signature: Vec<Signature<'_>> = Vec::new();
655
2200
    state_to_signature.resize_with(lts.num_of_states(), Signature::default);
656

            
657
    // Refine partitions until stable.
658
2200
    let mut old_count = 1;
659
2200
    let mut iteration = 0;
660

            
661
2200
    let progress = TimeProgress::new(
662
        |(iteration, blocks)| {
663
            debug!("Iteration {iteration}, found {blocks} blocks...",);
664
        },
665
        5,
666
    );
667

            
668
    // This is a workaround for a data race in bumpalo for zero-sized slices.
669
2200
    let empty_slice: &[(LabelIndex, BlockIndex)] = &[];
670

            
671
13449
    while old_count != id.len() {
672
11249
        trace!("Iteration {} ({} blocks)", iteration, id.len());
673

            
674
11249
        old_count = id.len();
675
11249
        progress.print((iteration, old_count));
676
11249
        swap(&mut partition, &mut next_partition);
677

            
678
        // Clear the current partition to start the next blocks.
679
11249
        id.clear();
680

            
681
11249
        state_to_signature.clear();
682
11249
        state_to_signature.resize_with(lts.num_of_states(), Signature::default);
683

            
684
        // Safety: The current signatures have been removed, so it safe to reuse the memory.
685
11249
        let id: &'_ mut FxHashMap<Signature<'_>, BlockIndex> = unsafe { std::mem::transmute(&mut id) };
686
11249
        let state_to_signature: &mut Vec<Signature<'_>> = unsafe { std::mem::transmute(&mut state_to_signature) };
687

            
688
        // Remove the current signatures.
689
11249
        arena.reset();
690

            
691
11249
        if WEAK {
692
1595349
            for state_index in lts.iter_states() {
693
1595349
                weak_bisim_signature_sorted_taus(state_index, lts, &partition, state_to_signature, &mut builder);
694

            
695
1595349
                trace!("State {state_index} weak signature {:?}", builder);
696

            
697
                // Keep track of the index for every state, either use the arena to allocate space or simply borrow the value.
698
1595349
                let slice = if builder.is_empty() {
699
                    empty_slice
700
                } else {
701
1595349
                    arena.alloc_slice_copy(&builder)
702
                };
703
1595349
                state_to_signature[state_index] = Signature::new(slice);
704
            }
705
7307
        }
706

            
707
5276912
        for state_index in lts.iter_states() {
708
            // Compute the signature of a single state
709
5276912
            signature(state_index, &partition, state_to_signature, &mut builder);
710

            
711
5276912
            trace!("State {state_index} signature {builder:?}");
712

            
713
            // Keep track of the index for every state, either use the arena to allocate space or simply borrow the value.
714
5276912
            let mut new_id = BlockIndex::new(id.len());
715
5276912
            if let Some((signature, index)) = id.get_key_value(&Signature::new(&builder)) {
716
3816877
                // SAFETY: We know that the signature lives as long as the arena
717
3816877
                state_to_signature[state_index] = unsafe {
718
3816877
                    std::mem::transmute::<Signature<'_>, Signature<'_>>(Signature::new(signature.as_slice()))
719
3816877
                };
720
3816877
                new_id = *index;
721
3816877
            } else {
722
1460035
                let slice = if builder.is_empty() {
723
7307
                    empty_slice
724
                } else {
725
1452728
                    arena.alloc_slice_copy(&builder)
726
                };
727
1460035
                id.insert(Signature::new(slice), new_id);
728

            
729
                // (branching) Keep track of the signature for every block in the next partition.
730
1460035
                state_to_signature[state_index] = Signature::new(slice);
731
            }
732

            
733
5276912
            next_partition.set_block(*state_index, new_id);
734
        }
735

            
736
11249
        iteration += 1;
737

            
738
11249
        debug_assert!(
739
11249
            iteration <= lts.num_of_states().max(2),
740
            "There can never be more splits than number of states, but at least two iterations for stability"
741
        );
742
    }
743

            
744
2200
    trace!("Refinement partition {partition}");
745
2200
    debug_assert!(
746
923146
        is_valid_refinement(lts, &partition, |state_index, partition, builder| signature(
747
923146
            state_index,
748
923146
            partition,
749
923146
            &state_to_signature,
750
923146
            builder
751
        )),
752
        "The resulting partition is not a valid partition."
753
    );
754
2200
    partition
755
2200
}
756

            
757
/// Returns true iff the given partition is a strong bisimulation partition
758
2200
pub fn is_valid_refinement<F, P, L>(lts: &L, partition: &P, mut compute_signature: F) -> bool
759
2200
where
760
2200
    F: FnMut(StateIndex, &P, &mut SignatureBuilder),
761
2200
    P: Partition,
762
2200
    L: LTS,
763
{
764
    // Check that the partition is indeed stable and as such is a quotient of strong bisimulation
765
2200
    let mut block_to_signature: Vec<Option<SignatureBuilder>> = vec![None; partition.num_of_blocks()];
766

            
767
    // Avoids reallocations when computing the signature.
768
2200
    let mut builder = SignatureBuilder::default();
769

            
770
923146
    for state_index in lts.iter_states() {
771
923146
        let block = partition.block_number(state_index);
772

            
773
        // Compute the flat signature, which has Hash and is more compact.
774
923146
        compute_signature(state_index, partition, &mut builder);
775
923146
        let signature: Vec<(LabelIndex, BlockIndex)> = builder.clone();
776

            
777
923146
        if let Some(block_signature) = &block_to_signature[block] {
778
562421
            if signature != *block_signature {
779
                trace!(
780
                    "State {state_index} has a different signature {signature:?} then the block {block} which has signature {block_signature:?}"
781
                );
782
                return false;
783
562421
            }
784
360725
        } else {
785
360725
            block_to_signature[block] = Some(signature);
786
360725
        };
787
    }
788

            
789
    // Check if there are two blocks with the same signature
790
2200
    let mut signature_to_block: FxHashMap<Signature, usize> = FxHashMap::default();
791

            
792
360725
    for (block_index, signature) in block_to_signature
793
2200
        .iter()
794
360725
        .map(|signature: &Option<SignatureBuilder>| signature.as_ref().expect("Signature should be defined"))
795
2200
        .enumerate()
796
    {
797
360725
        if let Some(other_block_index) = signature_to_block.get(&Signature::new(signature)) {
798
            if block_index != *other_block_index {
799
                trace!("Block {block_index} and {other_block_index} have the same signature {signature:?}");
800
                return false;
801
            }
802
360725
        } else {
803
360725
            signature_to_block.insert(Signature::new(signature), block_index);
804
360725
        }
805
    }
806

            
807
2200
    true
808
2200
}
809

            
810
#[cfg(test)]
811
mod tests {
812
    use test_log::test;
813

            
814
    use merc_io::DumpFiles;
815
    use merc_lts::random_lts;
816
    use merc_lts::write_aut;
817
    use merc_utilities::Timing;
818
    use merc_utilities::random_test;
819

            
820
    use super::BlockIndex;
821
    use super::LTS;
822
    use super::Partition;
823
    use super::StateIndex;
824
    use super::branching_bisim_sigref;
825
    use super::branching_bisim_sigref_naive;
826
    use super::strong_bisim_sigref;
827
    use super::strong_bisim_sigref_naive;
828
    use super::weak_bisim_sigref_inductive_naive;
829
    use super::weak_bisim_sigref_naive;
830

            
831
    /// Returns true iff the partitions are equal, runs in O(n^2).
832
300
    fn equal_partitions<P: Partition, Q: Partition>(left: &P, right: &Q) -> bool {
833
        // Check that states in the same block, have a single (unique) number in
834
        // the other partition.
835
117998
        for block_index in (0..left.num_of_blocks()).map(BlockIndex::new) {
836
117998
            let mut other_block_index = None;
837

            
838
299978
            for state_index in (0..left.len())
839
117998
                .map(StateIndex::new)
840
117990045
                .filter(|&state_index| left.block_number(state_index) == block_index)
841
            {
842
299978
                match other_block_index {
843
117998
                    None => other_block_index = Some(right.block_number(state_index)),
844
181980
                    Some(other_block_index) => {
845
181980
                        if right.block_number(state_index) != other_block_index {
846
                            return false;
847
181980
                        }
848
                    }
849
                }
850
            }
851
        }
852

            
853
117998
        for block_index in (0..right.num_of_blocks()).map(BlockIndex::new) {
854
117998
            let mut other_block_index = None;
855

            
856
299978
            for state_index in (0..left.len())
857
117998
                .map(StateIndex::new)
858
117990045
                .filter(|&state_index| right.block_number(state_index) == block_index)
859
            {
860
299978
                match other_block_index {
861
117998
                    None => other_block_index = Some(left.block_number(state_index)),
862
181980
                    Some(other_block_index) => {
863
181980
                        if left.block_number(state_index) != other_block_index {
864
                            return false;
865
181980
                        }
866
                    }
867
                }
868
            }
869
        }
870

            
871
300
        true
872
300
    }
873

            
874
    /// Checks that the strong bisimulation partition is a refinement of the branching bisimulation partition.
875
200
    fn is_refinement<L: LTS, P: Partition, Q: Partition>(lts: &L, strong_partition: &P, branching_partition: &Q) {
876
199975
        for state_index in lts.iter_states() {
877
199950051
            for other_state_index in lts.iter_states() {
878
199950051
                if strong_partition.block_number(state_index) == strong_partition.block_number(other_state_index) {
879
                    // If the states are together according to strong bisimilarity, then they should also be together according to branching bisimilarity.
880
27453453
                    assert_eq!(
881
27453453
                        branching_partition.block_number(state_index),
882
27453453
                        branching_partition.block_number(other_state_index),
883
                        "The strong partition should be a refinement of the branching partition, but states {state_index} and {other_state_index} are in different strong blocks"
884
                    );
885
172496598
                }
886
            }
887
        }
888
200
    }
889

            
890
    #[test]
891
    #[cfg_attr(miri, ignore)] // Miri is too slow
892
    fn test_random_strong_bisim_sigref() {
893
100
        random_test(100, |rng| {
894
100
            let mut files = DumpFiles::new("test_random_strong_bisim_sigref");
895

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

            
899
100
            let mut timing = Timing::new();
900

            
901
100
            let (result_lts, result_partition) = strong_bisim_sigref(lts.clone(), &mut timing);
902
100
            let (expected_lts, expected_partition) = strong_bisim_sigref_naive(lts, &mut timing);
903

            
904
100
            files
905
100
                .dump("result.aut", |writer| write_aut(writer, &result_lts))
906
100
                .unwrap();
907
100
            files
908
100
                .dump("expected.aut", |writer| write_aut(writer, &expected_lts))
909
100
                .unwrap();
910

            
911
            // There is no preprocessing so this works.
912
100
            assert!(equal_partitions(&result_partition, &expected_partition));
913
100
        });
914
    }
915

            
916
    #[test]
917
    #[cfg_attr(miri, ignore)] // Miri is too slow
918
    fn test_random_branching_bisim_sigref() {
919
100
        random_test(100, |rng| {
920
100
            let mut files = DumpFiles::new("test_random_branching_bisim_sigref");
921

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

            
925
100
            let mut timing = Timing::new();
926

            
927
100
            let (result_lts, _, result_partition) =
928
100
                branching_bisim_sigref(lts.clone(), StateIndex::new(0), false, &mut timing);
929
100
            let (expected_lts, _, expected_partition) =
930
100
                branching_bisim_sigref_naive(lts, StateIndex::new(0), false, &mut timing);
931

            
932
100
            files
933
100
                .dump("result.aut", |writer| write_aut(writer, &result_lts))
934
100
                .unwrap();
935
100
            files
936
100
                .dump("expected.aut", |writer| write_aut(writer, &expected_lts))
937
100
                .unwrap();
938

            
939
            // There is no preprocessing so this works.
940
100
            assert!(equal_partitions(&result_partition, &expected_partition));
941
100
        });
942
    }
943

            
944
    #[test]
945
    #[cfg_attr(miri, ignore)] // Miri is too slow
946
    fn test_random_weak_bisim_sigref() {
947
100
        random_test(100, |rng| {
948
100
            let mut files = DumpFiles::new("test_random_weak_bisim_sigref");
949

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

            
953
100
            let mut timing = Timing::new();
954

            
955
100
            let (result_lts, _, result_partition) =
956
100
                weak_bisim_sigref_naive(lts.clone(), StateIndex::new(0), false, false, &mut timing);
957
100
            let (expected_lts, _, expected_partition) =
958
100
                weak_bisim_sigref_inductive_naive(lts, StateIndex::new(0), false, false, &mut timing);
959

            
960
100
            files
961
100
                .dump("result.aut", |writer| write_aut(writer, &result_lts))
962
100
                .unwrap();
963
100
            files
964
100
                .dump("expected.aut", |writer| write_aut(writer, &expected_lts))
965
100
                .unwrap();
966

            
967
            // There is no preprocessing so this works.
968
100
            assert!(equal_partitions(&result_partition, &expected_partition));
969
100
        });
970
    }
971

            
972
    #[test]
973
    #[cfg_attr(miri, ignore)] // Miri is too slow
974
    fn test_random_branching_bisim_sigref_naive() {
975
100
        random_test(100, |rng| {
976
100
            let mut files = DumpFiles::new("test_random_branching_bisim_sigref_naive");
977

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

            
981
100
            let mut timing = Timing::new();
982

            
983
100
            let (preprocessed_lts, _, branching_partition) =
984
100
                branching_bisim_sigref_naive(lts, StateIndex::new(0), false, &mut timing);
985
100
            files
986
100
                .dump("preprocessed.aut", |writer| write_aut(writer, &preprocessed_lts))
987
100
                .unwrap();
988

            
989
100
            let strong_partition = strong_bisim_sigref_naive(preprocessed_lts.clone(), &mut timing).1;
990
100
            is_refinement(&preprocessed_lts, &strong_partition, &branching_partition);
991
100
        });
992
    }
993

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

            
100
            let lts = random_lts::<String, _>(rng, 1000, 3);
100
            files.dump("input.aut", |writer| write_aut(writer, &lts)).unwrap();
100
            let mut timing = Timing::new();
100
            let (preprocessed_lts, _, weak_partition) =
100
                weak_bisim_sigref_naive(lts, StateIndex::new(0), false, false, &mut 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, &mut timing);
100
            is_refinement(&preprocessed_lts, &branching_partition, &weak_partition);
100
        });
    }
}