1
//! Authors: Jan Friso Groote, Maurice Laveaux, Wieger Wesselink and Tim A.C. Willemse
2
//! This file contains an implementation of
3
//! M. Laveaux, J.F. Groote and T.A.C. Willemse
4
//! Correct and Efficient Antichain Algorithms for Refinement Checking. Logical Methods in Computer Science 17(1) 2021
5
//!
6
//! There are six algorithms. One for trace inclusion, one for failures inclusion and one for failures-divergence
7
//! inclusion. All algorithms come in a variant with and without internal steps. It is possible to generate a counter
8
//! transition system in case the inclusion is answered by no.
9

            
10
use log::trace;
11
use merc_collections::VecSet;
12
use merc_lts::LTS;
13
use merc_lts::StateIndex;
14
use merc_reduction::Equivalence;
15
use merc_reduction::Partition;
16
use merc_reduction::quotient_lts_block;
17
use merc_reduction::reduce_lts;
18
use merc_reduction::strong_bisim_sigref;
19
use merc_utilities::Timing;
20

            
21
use crate::Antichain;
22
use crate::RefinementType;
23

            
24
/// Sets the exploration strategy for the failures refinement algorithm.
25
pub enum ExplorationStrategy {
26
    BFS,
27
    DFS,
28
}
29

            
30
/// This function checks using algorithms in the paper mentioned above
31
/// whether transition system l1 is included in transition system l2, in the
32
/// sense of trace inclusions, failures inclusion and divergence failures
33
/// inclusion.
34
100
pub fn is_failures_refinement<L: LTS, const COUNTER_EXAMPLE: bool>(
35
100
    impl_lts: L,
36
100
    spec_lts: L,
37
100
    refinement: RefinementType,
38
100
    _strategy: ExplorationStrategy,
39
100
    preprocess: bool,
40
100
    timing: &mut Timing,
41
100
) -> bool {
42
100
    let reduction = match refinement {
43
100
        RefinementType::Trace => Equivalence::StrongBisim,
44
    };
45

            
46
    // For the preprocessing/quotienting step it makes sense to merge both LTSs
47
    // together in case that some states are equivalent. So we do this in all branches.
48
100
    let (merged_lts, initial_spec) = if preprocess {
49
        if COUNTER_EXAMPLE {
50
            // If a counter example is to be generated, we only reduce the
51
            // specification LTS such that the trace remains valid.
52
            let reduced_spec = reduce_lts(spec_lts, reduction, timing);
53
            impl_lts.merge_disjoint(&reduced_spec)
54
        } else {
55
            let (merged_lts, initial_spec) = impl_lts.merge_disjoint(&spec_lts);
56

            
57
            // Reduce all states in the merged LTS.
58
            match reduction {
59
                Equivalence::StrongBisim => {
60
                    let (preprocess_lts, partition) = strong_bisim_sigref(merged_lts, timing);
61

            
62
                    let initial_spec = partition.block_number(initial_spec);
63
                    let reduced_lts = quotient_lts_block::<_, false>(&preprocess_lts, &partition);
64

            
65
                    // After partitioning the block becomes the state in the reduced_lts.
66
                    (reduced_lts, StateIndex::new(*initial_spec))
67
                }
68
                _ => unimplemented!(),
69
            }
70
        }
71
    } else {
72
100
        impl_lts.merge_disjoint(&spec_lts)
73
    };
74

            
75
100
    let mut working = vec![(merged_lts.initial_state_index(), VecSet::singleton(initial_spec))];
76

            
77
    // The antichain data structure is used for storing explored states. However, as opposed to a discovered set it
78
    // allows for pruning additional pairs based on the `antichain` property.
79
100
    let mut antichain = Antichain::new();
80

            
81
26579
    while let Some((impl_state, spec)) = working.pop() {
82
26479
        trace!("Checking ({:?}, {:?})", impl_state, spec);
83
        // pop (impl,spec) from working;
84

            
85
153710
        for impl_transition in merged_lts.outgoing_transitions(impl_state) {
86
            // spec' := {s' | exists s in spec. s-e->s'};
87
153710
            let mut spec_prime = VecSet::new();
88
298308
            for s in &spec {
89
2006702
                for spec_transition in merged_lts.outgoing_transitions(*s) {
90
2006702
                    if impl_transition.label == spec_transition.label {
91
319021
                        spec_prime.insert(spec_transition.to);
92
1687681
                    }
93
                }
94
            }
95

            
96
153710
            trace!("spec' = {:?}", spec_prime);
97
153710
            if spec_prime.is_empty() {
98
                // if spec' = {} then
99
                return false; //    return false;
100
153710
            }
101

            
102
153710
            if antichain.insert(impl_transition.to, spec_prime.clone()) {
103
26379
                // if antichain_insert(impl,spec') then
104
26379
                working.push((impl_transition.to, spec_prime));
105
127331
            }
106
        }
107
    }
108

            
109
100
    true
110
100
}
111

            
112
#[cfg(test)]
113
mod tests {
114
    use merc_io::DumpFiles;
115
    use merc_lts::random_lts;
116
    use merc_lts::write_aut;
117
    use merc_reduction::Equivalence;
118
    use merc_reduction::reduce_lts;
119
    use merc_utilities::Timing;
120
    use merc_utilities::random_test;
121

            
122
    use crate::ExplorationStrategy;
123
    use crate::RefinementType;
124
    use crate::is_failures_refinement;
125

            
126
    #[test]
127
    #[cfg_attr(miri, ignore)] // Tests are too slow under miri.
128
1
    fn test_random_trace_refinement() {
129

            
130
100
        random_test(100, |rng| {
131
100
            let mut files = DumpFiles::new("test_random_trace_refinement");
132

            
133
100
            let spec_lts = random_lts(rng, 10, 20, 5);
134

            
135
100
            let mut timing = Timing::default();
136
100
            let impl_lts = reduce_lts(spec_lts.clone(), Equivalence::StrongBisim, &mut timing);
137

            
138
100
            files.dump("spec.aut", |w| write_aut(w, &spec_lts)).unwrap();
139
100
            files.dump("impl.aut", |w| write_aut(w, &impl_lts)).unwrap();
140

            
141
100
            assert!(
142
100
                is_failures_refinement::<_, false>(
143
100
                    impl_lts,
144
100
                    spec_lts,
145
100
                    RefinementType::Trace,
146
100
                    ExplorationStrategy::BFS,
147
                    false,
148
100
                    &mut timing
149
                ),
150
                "Strong bisimulation implies trace refinement."
151
            );
152
100
        });
153
1
    }
154
}