rust_lstar/eqtest/
bdist_method.rs1use crate::automata::Automata;
7use crate::eqtest::{Counterexample, EquivalenceTest};
8use crate::knowledge_base::KnowledgeBaseTrait;
9use crate::letter::Letter;
10use crate::query::OutputQuery;
11use crate::word::Word;
12use std::collections::{HashMap, VecDeque};
13use std::sync::{Arc, Mutex};
14
15pub struct BDistMethod {
17 knowledge_base: Arc<Mutex<dyn KnowledgeBaseTrait>>,
18 input_letters: Vec<Letter>,
19 bdist: usize,
20}
21
22impl BDistMethod {
23 pub fn new(
27 knowledge_base: Arc<Mutex<dyn KnowledgeBaseTrait>>,
28 input_letters: Vec<Letter>,
29 bdist: usize,
30 ) -> Self {
31 Self {
32 knowledge_base,
33 input_letters,
34 bdist,
35 }
36 }
37
38 fn can_use_flat_transitions(hypothesis: &Automata) -> bool {
39 !hypothesis.transitions.is_empty()
40 && hypothesis
41 .transitions
42 .iter()
43 .all(|transition| !transition.source_state.is_empty())
44 }
45
46 fn outgoing_inputs(&self, hypothesis: &Automata, state_name: &str) -> Vec<(Letter, String)> {
47 if Self::can_use_flat_transitions(hypothesis) {
48 return hypothesis
49 .transitions
50 .iter()
51 .filter(|t| t.source_state == state_name)
52 .map(|t| (t.input_letter.clone(), t.output_state.name.clone()))
53 .collect();
54 }
55
56 let states = hypothesis.get_states();
57 if let Some(state) = states.iter().find(|s| s.name == state_name) {
58 return state
59 .transitions
60 .iter()
61 .map(|t| (t.input_letter.clone(), t.output_state.name.clone()))
62 .collect();
63 }
64 Vec::new()
65 }
66
67 fn compute_representatives(&self, hypothesis: &Automata) -> HashMap<String, Word> {
68 let mut representatives = HashMap::new();
69 let mut queue = VecDeque::new();
70 let initial = hypothesis.initial_state.name.clone();
71 representatives.insert(initial.clone(), Word::new());
72 queue.push_back(initial);
73
74 while let Some(state_name) = queue.pop_front() {
75 let rep_word = match representatives.get(&state_name) {
76 Some(w) => w.clone(),
77 None => continue,
78 };
79
80 for (input_letter, next_state_name) in self.outgoing_inputs(hypothesis, &state_name) {
81 if next_state_name == state_name || representatives.contains_key(&next_state_name) {
82 continue;
83 }
84 let candidate = rep_word.concatenate(&Word::from_letters(vec![input_letter]));
85 representatives.insert(next_state_name.clone(), candidate);
86 queue.push_back(next_state_name);
87 }
88 }
89
90 representatives
91 }
92
93 fn generate_suffixes(&self) -> Vec<Word> {
94 fn generate_len(
95 letters: &[Letter],
96 len: usize,
97 current: &mut Vec<Letter>,
98 acc: &mut Vec<Word>,
99 ) {
100 if current.len() == len {
101 acc.push(Word::from_letters(current.clone()));
102 return;
103 }
104 for letter in letters {
105 current.push(letter.clone());
106 generate_len(letters, len, current, acc);
107 current.pop();
108 }
109 }
110
111 let mut suffixes = Vec::new();
112 for len in 1..=self.bdist {
113 let mut current = Vec::new();
114 generate_len(&self.input_letters, len, &mut current, &mut suffixes);
115 }
116 suffixes
117 }
118
119 fn check_equivalence(
120 &self,
121 w_i: &Word,
122 w_i_prime: &Word,
123 suffixes: &[Word],
124 ) -> Option<(Word, OutputQuery, OutputQuery)> {
125 for suffix in suffixes {
126 let mut query_i = OutputQuery::new(w_i.concatenate(suffix));
127 let mut query_i_prime = OutputQuery::new(w_i_prime.concatenate(suffix));
128
129 if self
130 .knowledge_base
131 .lock()
132 .unwrap()
133 .resolve_query(&mut query_i)
134 .is_err()
135 {
136 continue;
137 }
138 if self
139 .knowledge_base
140 .lock()
141 .unwrap()
142 .resolve_query(&mut query_i_prime)
143 .is_err()
144 {
145 continue;
146 }
147
148 let last_i = query_i.output_word().and_then(|w| w.last_letter()).cloned();
149 let last_i_prime = query_i_prime
150 .output_word()
151 .and_then(|w| w.last_letter())
152 .cloned();
153
154 if last_i != last_i_prime {
155 return Some((suffix.clone(), query_i, query_i_prime));
156 }
157 }
158 None
159 }
160}
161
162impl EquivalenceTest for BDistMethod {
163 fn find_counterexample(&self, hypothesis: &mut Automata) -> Option<Counterexample> {
164 let representatives = self.compute_representatives(hypothesis);
165 let suffixes = self.generate_suffixes();
166 let states = hypothesis.get_states();
167
168 for state in &states {
169 for letter in &self.input_letters {
170 let rep = match representatives.get(&state.name) {
171 Some(w) => w.clone(),
172 None => continue,
173 };
174 let w_i = rep.concatenate(&Word::from_letters(vec![letter.clone()]));
175 let one_letter_word = Word::from_letters(vec![letter.clone()]);
176
177 let (hyp_output_word, visited_states) =
178 match hypothesis.play_word(&one_letter_word, Some(state)) {
179 Ok(v) => v,
180 Err(_) => continue,
181 };
182
183 let mut query = OutputQuery::new(w_i.clone());
184 if self
185 .knowledge_base
186 .lock()
187 .unwrap()
188 .resolve_query(&mut query)
189 .is_err()
190 {
191 continue;
192 }
193
194 let hyp_last = hyp_output_word.last_letter().cloned();
195 let real_last = query.output_word().and_then(|w| w.last_letter()).cloned();
196 if hyp_last != real_last {
197 return query
198 .output_word()
199 .cloned()
200 .map(|output_word| Counterexample {
201 input_word: query.input_word.clone(),
202 output_word,
203 });
204 }
205
206 let q_prime_name = match visited_states.last() {
207 Some(s) => s.name.clone(),
208 None => continue,
209 };
210 let w_i_prime = match representatives.get(&q_prime_name) {
211 Some(w) => w,
212 None => continue,
213 };
214 if &w_i == w_i_prime {
215 continue;
216 }
217
218 if let Some((suffix, query_i, query_i_prime)) =
219 self.check_equivalence(&w_i, w_i_prime, &suffixes)
220 {
221 let w_i_suffix = w_i.concatenate(&suffix);
222 let expected_i = match hypothesis.play_word(&w_i_suffix, None) {
223 Ok((w, _)) => w,
224 Err(_) => continue,
225 };
226 let observed_i = match query_i.output_word() {
227 Some(w) => w.clone(),
228 None => continue,
229 };
230
231 let chosen_query = if expected_i != observed_i {
232 query_i
233 } else {
234 query_i_prime
235 };
236
237 if let Some(output_word) = chosen_query.output_word().cloned() {
238 return Some(Counterexample {
239 input_word: chosen_query.input_word.clone(),
240 output_word,
241 });
242 }
243 }
244 }
245 }
246
247 None
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::automata::{State, Transition};
255
256 struct ConstantOutputKb {
257 output_symbol: String,
258 }
259
260 impl ConstantOutputKb {
261 fn new(output_symbol: &str) -> Self {
262 Self {
263 output_symbol: output_symbol.to_string(),
264 }
265 }
266 }
267
268 impl KnowledgeBaseTrait for ConstantOutputKb {
269 fn resolve_query(&mut self, query: &mut OutputQuery) -> Result<(), String> {
270 let outputs = query
271 .input_word
272 .letters()
273 .iter()
274 .map(|_| Letter::new(self.output_symbol.clone()))
275 .collect::<Vec<_>>();
276 query.set_result(Word::from_letters(outputs));
277 Ok(())
278 }
279
280 fn add_word(&mut self, _input_word: &Word, _output_word: &Word) -> Result<(), String> {
281 Ok(())
282 }
283 }
284
285 fn build_single_state_hypothesis(output_symbol: &str) -> Automata {
286 let mut s0 = State::new("S0".to_string());
287 s0.add_transition(Transition::new(
288 "t0".to_string(),
289 State::new("S0".to_string()),
290 Letter::new("a"),
291 Letter::new(output_symbol),
292 ));
293 Automata::new(s0, "H".to_string())
294 }
295
296 #[test]
297 fn bdist_finds_counterexample_for_wrong_hypothesis() {
298 let kb: Arc<Mutex<dyn KnowledgeBaseTrait>> =
299 Arc::new(Mutex::new(ConstantOutputKb::new("1")));
300 let eq = BDistMethod::new(kb, vec![Letter::new("a")], 2);
301 let mut hypothesis = build_single_state_hypothesis("0");
302 assert!(eq.find_counterexample(&mut hypothesis).is_some());
303 }
304
305 #[test]
306 fn bdist_returns_none_for_equivalent_hypothesis() {
307 let kb: Arc<Mutex<dyn KnowledgeBaseTrait>> =
308 Arc::new(Mutex::new(ConstantOutputKb::new("0")));
309 let eq = BDistMethod::new(kb, vec![Letter::new("a")], 2);
310 let mut hypothesis = build_single_state_hypothesis("0");
311 assert!(eq.find_counterexample(&mut hypothesis).is_none());
312 }
313}