rust_lstar/eqtest/
w_method.rs1use 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
14pub struct WMethodEQ {
16 knowledge_base: Arc<Mutex<dyn KnowledgeBaseTrait>>,
17 input_letters: Vec<Letter>,
18 pub max_states: usize,
20}
21
22impl WMethodEQ {
23 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}