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 bitvec::bitvec;
7
use bitvec::order::Lsb0;
8

            
9
use bitvec::vec::BitVec;
10
use log::info;
11
use log::trace;
12
use merc_io::TimeProgress;
13
use merc_lts::IncomingTransitions;
14
use merc_lts::LTS;
15
use merc_lts::LabelIndex;
16
use merc_lts::LabelledTransitionSystem;
17
use merc_utilities::Timing;
18

            
19
use crate::BlockIndex;
20
use crate::SimpleBlockPartition;
21
use crate::preprocess_branching;
22

            
23
/// Type alias because we use bitvec for marking states
24
type BitArray = BitVec<u64, Lsb0>;
25

            
26
/// Apply weak bisimulation reduction
27
100
pub fn weak_bisimulation<L: LTS>(
28
100
    lts: L,
29
100
    timing: &mut Timing,
30
100
) -> (LabelledTransitionSystem<L::Label>, SimpleBlockPartition) {
31
100
    let mut time_pre = timing.start("preprocessing");
32
100
    let tau_loop_free_lts = preprocess_branching(lts);
33
100
    time_pre.finish();
34

            
35
100
    let mut time_reduction = timing.start("reduction");
36
100
    let mut blocks = SimpleBlockPartition::new(tau_loop_free_lts.num_of_states());
37

            
38
100
    let mut act_mark = bitvec![u64, Lsb0; 0; tau_loop_free_lts.num_of_states()];
39
100
    let mut tau_mark = bitvec![u64, Lsb0; 0; tau_loop_free_lts.num_of_states()];
40

            
41
100
    let incoming = IncomingTransitions::new(&tau_loop_free_lts);
42

            
43
100
    let progress = TimeProgress::new(
44
        |num_of_blocks: usize| {
45
            info!("Found {} blocks...", num_of_blocks);
46
        },
47
        1,
48
    );
49

            
50
    loop {
51
268
        let mut stable = true;
52
525
        for block_index in (0usize..blocks.num_of_blocks()).map(BlockIndex::new) {
53
525
            progress.print(blocks.num_of_blocks());
54
525
            if blocks.block(block_index).is_stable() {
55
237
                continue;
56
288
            }
57

            
58
288
            trace!("Stabilising block {:?}", block_index);
59
288
            stable = false;
60
288
            blocks.mark_block_stable(block_index);
61

            
62
            // tau is the first label.
63
2880
            for label in tau_loop_free_lts
64
288
                .labels()
65
288
                .iter()
66
288
                .enumerate()
67
2880
                .map(|(i, _)| LabelIndex::new(i))
68
            {
69
2880
                compute_weak_act(
70
2880
                    &mut act_mark,
71
2880
                    &mut tau_mark,
72
2880
                    &tau_loop_free_lts,
73
2880
                    &blocks,
74
2880
                    &incoming,
75
2880
                    block_index,
76
2880
                    label,
77
                );
78

            
79
                // Note that we cannot use the block references here, and instead uses indices, because stabilise
80
                // also modifies the blocks structure.
81
8448
                for block_prime in (0usize..blocks.num_of_blocks()).map(BlockIndex::new) {
82
8448
                    stabilise(block_prime, &mut act_mark, &mut blocks);
83
8448
                }
84
            }
85
        }
86

            
87
268
        if stable {
88
            // Quit the outer loop.
89
100
            trace!("Partition is stable!");
90
100
            break;
91
168
        }
92
    }
93

            
94
100
    time_reduction.finish();
95
100
    (tau_loop_free_lts, blocks)
96
100
}
97

            
98
/// Sets s.act_mark to true iff exists t: S. s =\not{a}=> t
99
/// If a = tau, then also updates s.tau_mark
100
2880
fn compute_weak_act(
101
2880
    act_mark: &mut BitArray,
102
2880
    tau_mark: &mut BitArray,
103
2880
    lts: &impl LTS,
104
2880
    blocks: &SimpleBlockPartition,
105
2880
    incoming: &IncomingTransitions,
106
2880
    block: BlockIndex,
107
2880
    label: LabelIndex,
108
2880
) {
109
9900
    for s in lts.iter_states() {
110
        // s.act_mark := true iff s in B && a == tau
111
9900
        act_mark.set(
112
9900
            *s,
113
9900
            lts.is_hidden_label(label) && blocks.iter_block(block).any(|state| state == s),
114
        );
115

            
116
23640
        for transition in lts.outgoing_transitions(s) {
117
23640
            if transition.label == label {
118
                // s.act_mark := true iff a != tau && tau_mark[t]
119
2364
                if !lts.is_hidden_label(transition.label) && tau_mark[*transition.to] {
120
980
                    act_mark.set(*s, true);
121
1384
                }
122
21276
            }
123
        }
124
    }
125

            
126
9900
    for t in lts.iter_states() {
127
        // t.tau_mark := t.act_mark if a == tau
128
9900
        if lts.is_hidden_label(label) {
129
990
            tau_mark.set(*t, act_mark[*t]);
130
8910
        }
131

            
132
9900
        if act_mark[*t] {
133
1452
            for transition in incoming.incoming_silent_transitions(t) {
134
257
                act_mark.set(*transition.to, true);
135
257
            }
136
8448
        }
137
    }
138
2880
}
139

            
140
/// Splits the given block according to the given marking.
141
8448
fn stabilise(block: BlockIndex, act_mark: &mut BitArray, blocks: &mut SimpleBlockPartition) {
142
9900
    blocks.split_block(block, |state| act_mark[*state]);
143
8448
}
144

            
145
#[cfg(test)]
146
mod tests {
147
    use merc_io::DumpFiles;
148
    use merc_lts::LTS;
149
    use merc_lts::random_lts;
150
    use merc_lts::write_aut;
151
    use merc_utilities::Timing;
152
    use merc_utilities::random_test;
153

            
154
    use crate::Equivalence;
155
    use crate::compare_lts;
156
    use crate::reduce_lts;
157

            
158
    #[test]
159
    #[cfg_attr(miri, ignore)]
160
1
    fn test_weak_bisimulation() {
161
100
        random_test(100, |rng| {
162
100
            let mut files = DumpFiles::new("test_weak_bisimulation");
163

            
164
100
            let lts = random_lts(rng, 2, 10, 3);
165
100
            let mut timing = Timing::new();
166
100
            files.dump("input.aut", |f| write_aut(f, &lts)).unwrap();
167

            
168

            
169
100
            let result = reduce_lts(lts.clone(), Equivalence::WeakBisim, &mut timing);
170
100
            let expected = reduce_lts(lts, Equivalence::WeakBisimSigref, &mut timing);
171

            
172
100
            assert_eq!(result.num_of_states(), expected.num_of_states());
173
100
            assert_eq!(result.num_of_transitions(), expected.num_of_transitions());
174

            
175
100
            files.dump("reduced.aut", |f| write_aut(f, &result)).unwrap();
176
100
            files.dump("expected.aut", |f| write_aut(f, &expected)).unwrap();
177

            
178
100
            assert!(compare_lts(Equivalence::StrongBisim, result, expected, &mut timing));
179
100
        })
180
1
    }
181
}