1
//! Authors: Maurice Laveaux, Eduardo Costa Martins
2
//!
3
//! Implements the weak bisimulation algorithm by Eduardo Costa Martins.
4
#![forbid(unsafe_code)]
5

            
6
use std::iter;
7

            
8
use bitvec::bitvec;
9
use bitvec::order::Lsb0;
10

            
11
use bitvec::vec::BitVec;
12
use log::info;
13
use log::trace;
14
use merc_collections::BlockIndex;
15
use merc_io::TimeProgress;
16
use merc_lts::IncomingTransitions;
17
use merc_lts::LTS;
18
use merc_lts::LabelIndex;
19
use merc_lts::LabelledTransitionSystem;
20
use merc_lts::StateIndex;
21
use merc_utilities::Timing;
22

            
23
use crate::DivergencePreservingLts;
24
use crate::Equivalence;
25
use crate::MarkedBlockPartition;
26
use crate::reduce_lts;
27
use crate::tau_cycle_elimination_and_reorder;
28

            
29
/// Type alias because we use bitvec for marking states
30
type BitArray = BitVec<u64, Lsb0>;
31

            
32
/// Apply weak bisimulation reduction
33
///
34
/// # Details
35
///
36
/// The `preprocess` flag indicates whether to preprocess the LTS using
37
/// branching bisimulation.
38
///
39
/// The `state` is any state for which we return the equivalent state in the
40
/// preprocessed LTS.
41
800
pub fn weak_bisimulation<L: LTS>(
42
800
    lts: L,
43
800
    state: StateIndex,
44
800
    preprocess: bool,
45
800
    divergence_preserving: bool,
46
800
    timing: &Timing,
47
800
) -> (LabelledTransitionSystem<L::Label>, StateIndex, MarkedBlockPartition) {
48
    // Preprocess the LTS if desired.
49
800
    if preprocess {
50
        let lts = timing.measure("preprocess", || {
51
            if divergence_preserving {
52
                reduce_lts(lts, Equivalence::BranchingBisimDivergencePreserving, true, timing)
53
            } else {
54
                reduce_lts(lts, Equivalence::BranchingBisim, true, timing)
55
            }
56
        });
57
        weak_bisimulation_preprocessed(lts, state, divergence_preserving, timing)
58
    } else {
59
800
        weak_bisimulation_preprocessed(lts, state, divergence_preserving, timing)
60
    }
61
800
}
62

            
63
/// Apply weak bisimulation reduction using the parallel variant of the
64
/// algorithm
65
///
66
/// # Details
67
///
68
/// The `preprocess` flag indicates whether to preprocess the LTS using
69
/// branching bisimulation.
70
///
71
/// The `state` is any state for which we return the equivalent state in the
72
/// preprocessed LTS.
73
700
pub fn weak_bisimulation_parallel<L: LTS>(
74
700
    lts: L,
75
700
    state: StateIndex,
76
700
    preprocess: bool,
77
700
    divergence_preserving: bool,
78
700
    timing: &Timing,
79
700
) -> (LabelledTransitionSystem<L::Label>, StateIndex, MarkedBlockPartition) {
80
    // Preprocess the LTS if desired.
81
700
    if preprocess {
82
        let lts = timing.measure("preprocess", || {
83
            if divergence_preserving {
84
                reduce_lts(lts, Equivalence::BranchingBisimDivergencePreserving, true, timing)
85
            } else {
86
                reduce_lts(lts, Equivalence::BranchingBisim, true, timing)
87
            }
88
        });
89
        weak_bisimulation_parallel_preprocessed(lts, state, divergence_preserving, timing)
90
    } else {
91
700
        weak_bisimulation_parallel_preprocessed(lts, state, divergence_preserving, timing)
92
    }
93
700
}
94

            
95
/// Core weak bisimulation algorithm implementation.
96
800
fn weak_bisimulation_preprocessed<L: LTS>(
97
800
    lts: L,
98
800
    state: StateIndex,
99
800
    divergence_preserving: bool,
100
800
    timing: &Timing,
101
800
) -> (LabelledTransitionSystem<L::Label>, StateIndex, MarkedBlockPartition) {
102
800
    let (tau_loop_free_lts, mapped_state) = timing.measure("preprocess", || {
103
800
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
104
800
    });
105

            
106
800
    timing.measure("reduction", || {
107
800
        let blocks = if divergence_preserving {
108
300
            let divergence_preserving_lts = DivergencePreservingLts::new(&tau_loop_free_lts);
109
300
            weak_bisimulation_impl(&divergence_preserving_lts)
110
        } else {
111
500
            weak_bisimulation_impl(&tau_loop_free_lts)
112
        };
113
800
        (tau_loop_free_lts, mapped_state, blocks)
114
800
    })
115
800
}
116

            
117
/// The implementation of [weak_bisimulation_parallel] that assumes the LTS has already been preprocessed.
118
700
fn weak_bisimulation_parallel_preprocessed<L: LTS>(
119
700
    lts: L,
120
700
    state: StateIndex,
121
700
    divergence_preserving: bool,
122
700
    timing: &Timing,
123
700
) -> (LabelledTransitionSystem<L::Label>, StateIndex, MarkedBlockPartition) {
124
700
    let (tau_loop_free_lts, mapped_state) = timing.measure("preprocess", || {
125
700
        tau_cycle_elimination_and_reorder(lts, state, !divergence_preserving)
126
700
    });
127

            
128
700
    timing.measure("reduction", || {
129
700
        let blocks = if divergence_preserving {
130
300
            let divergence_preserving_lts = DivergencePreservingLts::new(&tau_loop_free_lts);
131
300
            weak_bisimulation_parallel_impl(&divergence_preserving_lts)
132
        } else {
133
400
            weak_bisimulation_parallel_impl(&tau_loop_free_lts)
134
        };
135
700
        (tau_loop_free_lts, mapped_state, blocks)
136
700
    })
137
700
}
138

            
139
/// The implementation of [weak_bisimulation].
140
800
fn weak_bisimulation_impl<L: LTS>(lts: &L) -> MarkedBlockPartition {
141
800
    let mut blocks = MarkedBlockPartition::new(lts.num_of_states());
142

            
143
800
    let mut act_mark = bitvec![u64, Lsb0; 0; lts.num_of_states()];
144
800
    let mut tau_mark = bitvec![u64, Lsb0; 0; lts.num_of_states()];
145

            
146
800
    let incoming = IncomingTransitions::new(lts);
147

            
148
800
    let progress = TimeProgress::new(
149
610
        |num_of_blocks: usize| {
150
610
            info!("Found {} blocks...", num_of_blocks);
151
610
        },
152
        1,
153
    );
154

            
155
    loop {
156
5854
        let mut stable = true;
157
411626
        for block_index in (0usize..blocks.num_of_blocks()).map(BlockIndex::new) {
158
411626
            progress.print(blocks.num_of_blocks());
159
411626
            if *blocks.block(block_index).annotation() {
160
323282
                continue;
161
88344
            }
162

            
163
88344
            trace!("Stabilising block {:?}", block_index);
164
88344
            stable = false;
165
88344
            blocks.mark_block_stable(block_index);
166

            
167
            // tau is the first label.
168
284952
            for label in lts.labels().iter().enumerate().map(|(i, _)| LabelIndex::new(i)) {
169
284952
                compute_weak_act(
170
284952
                    &mut act_mark,
171
284952
                    &mut tau_mark,
172
284952
                    lts,
173
284952
                    &blocks,
174
284952
                    &incoming,
175
284952
                    block_index,
176
284952
                    label,
177
                );
178

            
179
                // Note that we cannot use the block references here, and instead uses indices, because stabilise
180
                // also modifies the blocks structure.
181
38590840
                for block_prime in (0usize..blocks.num_of_blocks()).map(BlockIndex::new) {
182
38590840
                    stabilise(block_prime, &mut act_mark, &mut blocks);
183
38590840
                }
184
            }
185
        }
186

            
187
5854
        if stable {
188
800
            trace!("Partition is stable!");
189
800
            break;
190
5054
        }
191
    }
192

            
193
800
    blocks
194
800
}
195

            
196
/// The implementation of [weak_bisimulation_parallel].
197
700
fn weak_bisimulation_parallel_impl<L: LTS>(lts: &L) -> MarkedBlockPartition {
198
700
    let progress = TimeProgress::new(
199
        |num_of_blocks: usize| {
200
            info!("Found {} blocks...", num_of_blocks);
201
        },
202
        1,
203
    );
204

            
205
700
    let mut blocks = MarkedBlockPartition::new(lts.num_of_states());
206

            
207
    // Represents the s.marked[a] from the pseudocode.
208
700
    let mut marked = Vec::from_iter(iter::repeat_n(
209
700
        bitvec![u64, Lsb0; 0; lts.labels().len()],
210
700
        lts.num_of_states(),
211
    ));
212

            
213
700
    let incoming = IncomingTransitions::new(lts);
214

            
215
    loop {
216
4733
        let mut stable = true;
217
162091
        for block_index in (0usize..blocks.num_of_blocks()).map(BlockIndex::new) {
218
162091
            progress.print(blocks.num_of_blocks());
219
162091
            if *blocks.block(block_index).annotation() {
220
116320
                continue;
221
45771
            }
222

            
223
45771
            trace!("Stabilising block {:?}", block_index);
224
45771
            stable = false;
225
45771
            blocks.mark_block_stable(block_index);
226

            
227
            // marked := 0 for all states and actions
228
6986502
            for act_mark in &mut marked {
229
6986502
                act_mark.fill(false);
230
6986502
            }
231

            
232
45771
            compute_weak_acts(&mut marked, lts, &incoming, &blocks, block_index);
233

            
234
67414
            while let Some(label) = find_act(lts, &blocks, &mut marked) {
235
728005
                for block_index in (0usize..blocks.num_of_blocks()).map(BlockIndex::new) {
236
728005
                    stabilise_act(block_index, label, &mut marked, &mut blocks);
237
728005
                }
238
            }
239
        }
240

            
241
4733
        if stable {
242
700
            trace!("Partition is stable!");
243
700
            break;
244
4033
        }
245
    }
246

            
247
700
    blocks
248
700
}
249

            
250
/// Sets s.act_mark to true iff exists t: S. s =\not{a}=> t
251
/// If a = tau, then also updates s.tau_mark
252
442665
fn compute_weak_act<L: LTS>(
253
442665
    act_mark: &mut BitArray,
254
442665
    tau_mark: &mut BitArray,
255
442665
    lts: &L,
256
442665
    blocks: &MarkedBlockPartition,
257
442665
    incoming: &IncomingTransitions,
258
442665
    block: BlockIndex,
259
442665
    label: LabelIndex,
260
442665
) {
261
177079511
    for s in lts.iter_states() {
262
        // s.act_mark := true iff s in B && a == tau
263
177079511
        act_mark.set(
264
177079511
            *s,
265
272636209
            lts.is_hidden_label(label) && blocks.iter_block(block).any(|state| state == *s),
266
        );
267

            
268
179151925
        for transition in lts.outgoing_transitions(s) {
269
179151925
            if transition.label == label {
270
                // s.act_mark := true iff a != tau && tau_mark[t]
271
57478226
                if !lts.is_hidden_label(transition.label) && tau_mark[*transition.to] {
272
612773
                    act_mark.set(*s, true);
273
56865453
                }
274
121673699
            }
275
        }
276
    }
277

            
278
177079511
    for t in lts.iter_states() {
279
        // t.tau_mark := t.act_mark if a == tau
280
177079511
        if lts.is_hidden_label(label) {
281
56884778
            tau_mark.set(*t, act_mark[*t]);
282
120194733
        }
283

            
284
177079511
        if act_mark[*t] {
285
1700229
            for transition in incoming.incoming_silent_transitions(t) {
286
554876
                act_mark.set(*transition.from, true);
287
554876
            }
288
175379282
        }
289
    }
290
442665
}
291

            
292
/// Computing weak reachability for all actions at once. The `marked` array contains |Act| entries per state.
293
///
294
/// # Details
295
///
296
/// Requires s.tau_mark iff s ->> B.
297
/// For all a in A sets s.marked[a] iff s =[a]> B.
298
///
299
/// Note that `B` is only used for debugging checks, and is not used in the actual algorithm.
300
45771
fn compute_weak_acts<L: LTS>(
301
45771
    marked: &mut [BitArray],
302
45771
    lts: &L,
303
45771
    incoming: &IncomingTransitions<'_>,
304
45771
    blocks: &MarkedBlockPartition,
305
45771
    block: BlockIndex,
306
45771
) {
307
45771
    if cfg!(debug_assertions) {
308
        // Check that compute_weak_act results in the same markings as the optimised compute_weak_acts procedure.
309

            
310
        // Determine the tau_mark first, the act_mark result is ignored.
311
45771
        let mut tau_mark = bitvec![u64, Lsb0; 0; lts.num_of_states()];
312
6986502
        for s in lts.iter_states() {
313
6986502
            tau_mark.set(*s, marked[*s][0]);
314
6986502
        }
315

            
316
        // Determine the act_mark for every label that is not tau
317
45771
        let act_mark = (0..lts.labels().len())
318
157713
            .map(|label| {
319
157713
                let mut act_mark = bitvec![u64, Lsb0; 0; lts.num_of_states()];
320

            
321
24207347
                for s in lts.iter_states() {
322
24207347
                    act_mark.set(*s, marked[*s][label]);
323
24207347
                }
324

            
325
157713
                compute_weak_act(
326
157713
                    &mut act_mark,
327
157713
                    &mut tau_mark,
328
157713
                    lts,
329
157713
                    blocks,
330
157713
                    incoming,
331
157713
                    block,
332
157713
                    LabelIndex::new(label),
333
                );
334
157713
                act_mark
335
157713
            })
336
45771
            .collect::<Vec<_>>();
337

            
338
        // Compute the markings using the optimised procedure.
339
45771
        compute_weak_acts_inner(marked, lts, incoming, blocks, block);
340

            
341
        // Check that the markings are the same for all labels, except tau
342
111942
        for label in 1..lts.labels().len() {
343
            // The act_mark array starts at the first action, because we skip the tau action (index 0).
344
111942
            debug_assert!(
345
17220845
                act_mark[label].iter().zip(marked.iter()).all(|(a, m)| a == m[label]),
346
                "The act mark should be the same as the corresponding column in marked"
347
            );
348
        }
349
    } else {
350
        // No checking for correctness.
351
        compute_weak_acts_inner(marked, lts, incoming, blocks, block);
352
    }
353
45771
}
354

            
355
/// The inner implementation of [compute_weak_acts]. For all action a, sets s.marked[a] iff s =[a]> B, where B is the given block.
356
///
357
/// # Details
358
///
359
/// Requires that marked = 0 for all states.
360
45771
fn compute_weak_acts_inner<L: LTS>(
361
45771
    marked: &mut [BitArray],
362
45771
    lts: &L,
363
45771
    incoming: &IncomingTransitions<'_>,
364
45771
    blocks: &MarkedBlockPartition,
365
45771
    block: BlockIndex,
366
45771
) {
367
45771
    debug_assert!(
368
6986502
        marked.iter().all(|m| m.not_any()),
369
        "The marked array should be empty when calling compute_weak_acts_inner"
370
    );
371

            
372
    // TODO: This should probably not be hardcoded.
373
45771
    let tau_index = LabelIndex::new(0);
374
45771
    debug_assert!(
375
45771
        lts.is_hidden_label(tau_index),
376
        "The first label should be the tau action"
377
    );
378

            
379
    // For t in B: t.marked[tau] := true
380
248025
    for t in blocks.iter_block(block) {
381
248025
        marked[t].set(*tau_index, true);
382
248025
    }
383

            
384
6986502
    for t in lts.iter_states() {
385
6986502
        if marked[*t][*tau_index] {
386
            // For each s -[tau]-> t do
387
283275
            for transition in incoming.incoming_silent_transitions(t) {
388
92469
                // s.marked[tau] := True if t.marked[tau]
389
92469
                marked[transition.from].set(*tau_index, true);
390
92469
            }
391
6703227
        }
392
    }
393

            
394
6986502
    for t in lts.iter_states() {
395
        // For each t -[a]-> u do
396
7263057
        for transition in lts.outgoing_transitions(t) {
397
            // If u.marked[tau] then t.marked[a] := true
398
7263057
            if marked[*transition.to][*tau_index] {
399
293756
                marked[*t].set(*transition.label, true);
400
6969301
            }
401
        }
402

            
403
        // For each s -[tau]-> t do
404
6986502
        for transition in incoming.incoming_silent_transitions(t) {
405
            // Computes s.marked[a] := s.marked[a] | t.marked[a] in place.
406
2308290
            let [marked_s, marked_t] = marked
407
2308290
                .get_disjoint_mut([*transition.from, *t])
408
2308290
                .expect("The indices are disjoint");
409
2308290
            for (i, number) in marked_s.as_raw_mut_slice().iter_mut().enumerate() {
410
2308290
                *number |= marked_t.as_raw_slice()[i];
411
2308290
            }
412
        }
413
    }
414
45771
}
415

            
416
/// Finding an action that can be used to perform a refinement step.
417
67414
fn find_act<L: LTS>(_lts: &L, blocks: &MarkedBlockPartition, marked: &mut [BitArray]) -> Option<LabelIndex> {
418
2596130
    for block in (0..blocks.num_of_blocks()).map(BlockIndex::new) {
419
        // Pick a representative state s from the block
420
2596130
        let s = blocks.iter_block(block).next().expect("Block is non-empty");
421
8799550
        for t in blocks.iter_block(block) {
422
8799550
            if marked[s] != marked[t] {
423
                // Find an action a such that s.marked[a] != t.marked[a]
424
51245
                for (label, marked_s) in marked[s].iter().enumerate() {
425
51245
                    if marked_s != marked[t][label] {
426
21643
                        return Some(LabelIndex::new(label));
427
29602
                    }
428
                }
429
8777907
            }
430
        }
431
    }
432

            
433
45771
    None
434
67414
}
435

            
436
/// Splits the given block according to the given marking.
437
38590840
fn stabilise(block: BlockIndex, act_mark: &mut BitArray, blocks: &mut MarkedBlockPartition) {
438
152872164
    blocks.split_block(block, |state| act_mark[*state]);
439
38590840
}
440

            
441
/// Splits the given block according to the given marking.
442
728005
fn stabilise_act(block: BlockIndex, act: LabelIndex, marked: &mut [BitArray], blocks: &mut MarkedBlockPartition) {
443
3292599
    blocks.split_block(block, |state| marked[*state][*act]);
444
728005
}
445

            
446
#[cfg(test)]
447
mod tests {
448
    use merc_io::DumpFiles;
449
    use merc_lts::LTS;
450
    use merc_lts::random_lts;
451
    use merc_lts::write_aut;
452
    use merc_utilities::Timing;
453
    use merc_utilities::random_test;
454

            
455
    use crate::Equivalence;
456
    use crate::compare_lts;
457
    use crate::reduce_lts;
458
    // use crate::signature_refinement::test_mcrl2_sigref_vs_ltsconvert_impl;
459

            
460
    #[test]
461
    #[cfg_attr(miri, ignore)]
462
1
    fn test_weak_bisimulation() {
463
100
        random_test(100, |rng| {
464
100
            let mut files = DumpFiles::new("test_weak_bisimulation");
465

            
466
100
            let lts = random_lts::<String, _>(rng, 1000, 3);
467
100
            let mut timing = Timing::new();
468
100
            files.dump("input.aut", |f| write_aut(f, &lts)).unwrap();
469

            
470
100
            let result = reduce_lts(lts.clone(), Equivalence::WeakBisim, false, &mut timing);
471
100
            let expected = reduce_lts(lts, Equivalence::WeakBisimSigref, false, &mut timing);
472

            
473
100
            assert_eq!(result.num_of_states(), expected.num_of_states());
474
100
            assert_eq!(result.num_of_transitions(), expected.num_of_transitions());
475

            
476
100
            files.dump("result.aut", |f| write_aut(f, &result)).unwrap();
477
100
            files.dump("expected.aut", |f| write_aut(f, &expected)).unwrap();
478

            
479
100
            assert!(compare_lts(
480
100
                Equivalence::StrongBisim,
481
100
                result,
482
100
                expected,
483
                false,
484
100
                &mut timing
485
            ));
486
100
        })
487
1
    }
488

            
489
    #[test]
490
    #[cfg_attr(miri, ignore)]
491
1
    fn test_weak_bisimulation_parallel() {
492
100
        random_test(100, |rng| {
493
100
            let mut files = DumpFiles::new("test_weak_bisimulation_parallel");
494

            
495
100
            let lts = random_lts::<String, _>(rng, 100, 3);
496
100
            let mut timing = Timing::new();
497
100
            files.dump("input.aut", |f| write_aut(f, &lts)).unwrap();
498

            
499
100
            let result = reduce_lts(lts.clone(), Equivalence::WeakBisim, false, &mut timing);
500
100
            let expected = reduce_lts(lts, Equivalence::WeakBisimParallel, false, &mut timing);
501

            
502
100
            assert_eq!(result.num_of_states(), expected.num_of_states());
503
100
            assert_eq!(result.num_of_transitions(), expected.num_of_transitions());
504

            
505
100
            files.dump("result.aut", |f| write_aut(f, &result)).unwrap();
506
100
            files.dump("expected.aut", |f| write_aut(f, &expected)).unwrap();
507

            
508
100
            assert!(compare_lts(
509
100
                Equivalence::StrongBisim,
510
100
                result,
511
100
                expected,
512
                false,
513
100
                &mut timing
514
            ));
515
100
        })
516
1
    }
517
}