Skip to main content

rust_lstar/eqtest/
w_method.rs

1//! W-method equivalence-test implementation.
2//!
3//! Generates test suites from transition-cover and characterization sets.
4
5use crate::automata::{Automata, State};
6use crate::eqtest::{Counterexample, EquivalenceTest};
7use crate::knowledge_base::KnowledgeBaseTrait;
8use crate::letter::Letter;
9use crate::query::OutputQuery;
10use crate::word::Word;
11use std::collections::{HashSet, VecDeque};
12use std::sync::{Arc, Mutex};
13
14/// W-method equivalence-test strategy.
15pub struct WMethodEQ {
16    knowledge_base: Arc<Mutex<dyn KnowledgeBaseTrait>>,
17    input_letters: Vec<Letter>,
18    /// Upper bound on the number of states in the target system.
19    pub max_states: usize,
20}
21
22impl WMethodEQ {
23    /// Create a new W-method equivalence tester.
24    pub fn new(
25        knowledge_base: Arc<Mutex<dyn KnowledgeBaseTrait>>,
26        input_letters: Vec<Letter>,
27        max_states: usize,
28    ) -> Self {
29        WMethodEQ {
30            knowledge_base,
31            input_letters,
32            max_states,
33        }
34    }
35
36    fn compute_distinguishable_string(
37        &self,
38        hypothesis: &mut Automata,
39        couple: (&State, &State),
40    ) -> OutputQuery {
41        let mut queries_to_test = VecDeque::new();
42        let empty_word = Word::new();
43        let z_query = OutputQuery::new(empty_word.clone());
44
45        for letter in &self.input_letters {
46            let new_word = empty_word.concatenate(&Word::from_letters(vec![letter.clone()]));
47            queries_to_test.push_back(OutputQuery::new(new_word));
48        }
49
50        let mut distinguishable_query = z_query;
51        let mut i = 0;
52
53        while let Some(query) = queries_to_test.pop_front() {
54            if i > self.max_states * self.max_states {
55                break;
56            }
57
58            if self.is_distinguishable_states(hypothesis, &query, couple) {
59                distinguishable_query = query;
60                break;
61            } else {
62                for letter in &self.input_letters {
63                    let new_query = OutputQuery::new(
64                        query
65                            .input_word
66                            .concatenate(&Word::from_letters(vec![letter.clone()])),
67                    );
68                    queries_to_test.push_back(new_query);
69                }
70            }
71            i += 1;
72        }
73
74        distinguishable_query
75    }
76
77    fn is_distinguishable_states(
78        &self,
79        hypothesis: &mut Automata,
80        query: &OutputQuery,
81        couple: (&State, &State),
82    ) -> bool {
83        let output0 = hypothesis.play_word(&query.input_word, Some(couple.0));
84        let output1 = hypothesis.play_word(&query.input_word, Some(couple.1));
85
86        output0 != output1
87    }
88
89    fn compute_p(&self, hypothesis: &mut Automata) -> Vec<OutputQuery> {
90        let mut p = Vec::new();
91        let empty_word = Word::new();
92        let current_query = OutputQuery::new(empty_word);
93        p.push(current_query.clone());
94
95        let mut open_queries = VecDeque::new();
96        open_queries.push_back(current_query);
97        let mut close_queries = Vec::new();
98        let mut seen_states = HashSet::new();
99        seen_states.insert(hypothesis.initial_state.name.clone());
100
101        while let Some(query) = open_queries.pop_front() {
102            let mut tmp_seen_states = HashSet::new();
103
104            for letter in &self.input_letters {
105                let new_word = query
106                    .input_word
107                    .concatenate(&Word::from_letters(vec![letter.clone()]));
108                let query_z = OutputQuery::new(new_word);
109
110                if let Ok((_, visited_states)) = hypothesis.play_query(&query_z) {
111                    close_queries.push(query_z.clone());
112
113                    if let Some(last_state) = visited_states.last() {
114                        if !seen_states.contains(&last_state.name) {
115                            tmp_seen_states.insert(last_state.name.clone());
116                            open_queries.push_back(query_z);
117                        }
118                    }
119                }
120            }
121
122            seen_states.extend(tmp_seen_states);
123        }
124
125        p.extend(close_queries);
126        p
127    }
128
129    fn compute_z(&self, hypothesis: &Automata, w: &[OutputQuery]) -> Vec<OutputQuery> {
130        let mut z = Vec::new();
131        let mut z_set = HashSet::new();
132        
133        for query in w {
134            z_set.insert(query.input_word.clone());
135            z.push(query.clone());
136        }
137
138        let states = hypothesis.get_states();
139        let v = if self.max_states > states.len() {
140            self.max_states - states.len()
141        } else {
142            0
143        };
144
145        let mut output_queries = Vec::new();
146        for input_letter in &self.input_letters {
147            output_queries.push(OutputQuery::new(Word::from_letters(vec![
148                input_letter.clone()
149            ])));
150        }
151
152        let mut x = vec![w.to_vec()];
153        for i in 1..=v {
154            let mut xi = Vec::new();
155            let previous_x = &x[i - 1];
156
157            for x_elem in previous_x {
158                for oq in &output_queries {
159                    let new_word = x_elem.input_word.concatenate(&oq.input_word);
160                    xi.push(OutputQuery::new(new_word));
161                }
162            }
163
164            for xi_elem in &xi {
165                if z_set.insert(xi_elem.input_word.clone()) {
166                    z.push(xi_elem.clone());
167                }
168            }
169
170            x.push(xi);
171        }
172
173        z
174    }
175}
176
177impl EquivalenceTest for WMethodEQ {
178    fn find_counterexample(&self, hypothesis: &mut Automata) -> Option<Counterexample> {
179        let states = hypothesis.get_states();
180        let mut w = Vec::new();
181
182        for i in 0..states.len() {
183            for j in (i + 1)..states.len() {
184                let couple = (&states[i], &states[j]);
185                w.push(self.compute_distinguishable_string(hypothesis, couple));
186            }
187        }
188
189        let p = self.compute_p(hypothesis);
190        let z = self.compute_z(hypothesis, &w);
191        let mut t = p;
192        t.extend(z);
193
194        for testcase_query in t.iter().skip(1) {
195            if let Ok((hypothesis_output_word, _)) = hypothesis.play_query(testcase_query) {
196                let mut query = testcase_query.clone();
197                if self
198                    .knowledge_base
199                    .lock()
200                    .unwrap()
201                    .resolve_query(&mut query)
202                    .is_ok()
203                {
204                    if let Some(real_output_word) = query.output_word() {
205                        if real_output_word != &hypothesis_output_word {
206                            return Some(Counterexample {
207                                input_word: testcase_query.input_word.clone(),
208                                output_word: real_output_word.clone(),
209                            });
210                        }
211                    }
212                }
213            }
214        }
215
216        None
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223    use crate::automata::{Automata, State, Transition};
224    use crate::knowledge_base::KnowledgeBaseTrait;
225    use std::sync::{Arc, Mutex};
226
227    struct ConstantOutputKb {
228        output_symbol: String,
229    }
230
231    impl ConstantOutputKb {
232        fn new(output_symbol: &str) -> Self {
233            Self {
234                output_symbol: output_symbol.to_string(),
235            }
236        }
237    }
238
239    impl KnowledgeBaseTrait for ConstantOutputKb {
240        fn resolve_query(&mut self, query: &mut OutputQuery) -> Result<(), String> {
241            let outputs = query
242                .input_word
243                .letters()
244                .iter()
245                .map(|_| Letter::new(self.output_symbol.clone()))
246                .collect::<Vec<_>>();
247            query.set_result(Word::from_letters(outputs));
248            Ok(())
249        }
250
251        fn add_word(&mut self, _input_word: &Word, _output_word: &Word) -> Result<(), String> {
252            Ok(())
253        }
254    }
255
256    fn build_single_state_hypothesis(output_symbol: &str) -> Automata {
257        let mut s0 = State::new("S0".to_string());
258        s0.add_transition(Transition::new(
259            "t0".to_string(),
260            State::new("S0".to_string()),
261            Letter::new("a"),
262            Letter::new(output_symbol),
263        ));
264        Automata::new(s0, "H".to_string())
265    }
266
267    #[test]
268    fn wmethod_finds_counterexample_for_wrong_hypothesis() {
269        let kb: Arc<Mutex<dyn KnowledgeBaseTrait>> =
270            Arc::new(Mutex::new(ConstantOutputKb::new("1")));
271        let eq = WMethodEQ::new(kb, vec![Letter::new("a")], 2);
272        let mut hypothesis = build_single_state_hypothesis("0");
273
274        let ce = eq.find_counterexample(&mut hypothesis);
275        assert!(ce.is_some());
276    }
277
278    #[test]
279    fn wmethod_returns_none_for_equivalent_hypothesis() {
280        let kb: Arc<Mutex<dyn KnowledgeBaseTrait>> =
281            Arc::new(Mutex::new(ConstantOutputKb::new("0")));
282        let eq = WMethodEQ::new(kb, vec![Letter::new("a")], 2);
283        let mut hypothesis = build_single_state_hypothesis("0");
284
285        let ce = eq.find_counterexample(&mut hypothesis);
286        assert!(ce.is_none());
287    }
288}