1use crate::automata::{Automata, State, Transition};
2use crate::knowledge_base::KnowledgeBaseTrait;
3use crate::letter::Letter;
4use crate::query::OutputQuery;
5use crate::word::Word;
6use indexmap::IndexMap;
7use itertools::Itertools;
8use std::collections::{HashMap, HashSet};
9use std::fmt;
10use std::sync::{Arc, Mutex};
11
12#[allow(non_snake_case)]
19pub struct ObservationTable {
20 pub input_letters: Vec<Letter>,
21 knowledge_base: Arc<Mutex<dyn KnowledgeBaseTrait>>,
22 pub D: Vec<Word>,
23 pub S: Vec<Word>,
24 pub SA: Vec<Word>,
25 pub ot_content: HashMap<Word, HashMap<Word, Option<Letter>>>,
26 pub initialized: bool,
27}
28
29type RowSignature = Vec<Option<Letter>>;
30type Inconsistency = (((Word, Word), Word), Word);
31
32#[allow(non_snake_case)]
33impl ObservationTable {
34 pub fn new(
36 input_letters: Vec<Letter>,
37 knowledge_base: Arc<Mutex<dyn KnowledgeBaseTrait>>,
38 ) -> Self {
39 let capacity = input_letters.len() * 4;
40 ObservationTable {
41 input_letters,
42 knowledge_base,
43 D: Vec::with_capacity(capacity),
44 S: Vec::with_capacity(capacity * 2),
45 SA: Vec::with_capacity(capacity * 4),
46 ot_content: HashMap::with_capacity(capacity),
47 initialized: false,
48 }
49 }
50
51 pub fn initialize(&mut self) -> Result<(), String> {
53 if self.initialized {
54 return Err("Observation table already initialized".into());
55 }
56
57 self.initialized = true;
58 self.D.clear();
59 self.S.clear();
60 self.SA.clear();
61 self.ot_content.clear();
62
63 for letter in self.input_letters.clone() {
65 let word = Word::from_letters(vec![letter]);
66 self.add_word_in_D(word)?;
67 }
68
69 let empty_word = Word::new();
71 self.add_word_in_S(empty_word)?;
72
73 Ok(())
74 }
75
76 pub fn serialize(&self) -> String {
78 let mut result = String::new();
79 result.push_str("=== Observation Table ===\n");
80 result.push_str(&format!("D: {:?}\n", self.D));
81 result.push_str(&format!("S: {:?}\n", self.S));
82 result.push_str(&format!("SA: {:?}\n", self.SA));
83 result.push_str(&format!("ot: {:?}\n", self.ot_content));
84 result.push_str("\n\n\n");
85 result
86 }
87
88 pub fn find_inconsistency(&self) -> Option<Inconsistency> {
93 let mut s_with_same_rows: HashMap<RowSignature, Vec<Word>> = HashMap::new();
95 for word_in_s in &self.S {
96 s_with_same_rows
97 .entry(self.get_row(word_in_s))
98 .or_default()
99 .push(word_in_s.clone());
100 }
101
102 for (_k, eq_word_in_s) in s_with_same_rows.iter() {
104 if eq_word_in_s.len() > 1 {
105 for pair_eq_words_in_S in eq_word_in_s
106 .iter()
107 .cloned()
108 .tuple_combinations::<(Word, Word)>()
109 {
110 if let Some((suffix, distinguishing)) =
111 self.is_prefixes_equivalent(pair_eq_words_in_S.clone())
112 {
113 return Some(((pair_eq_words_in_S, suffix), distinguishing));
114 }
115 }
116 }
117 }
118
119 None
120 }
121
122 fn is_prefixes_equivalent(&self, eq_words_in_s: (Word, Word)) -> Option<(Word, Word)> {
126 for input_letter in &self.input_letters {
127 let suffix = Word::from_letters(vec![input_letter.clone()]);
128 let initial_suffixed = eq_words_in_s.0.concatenate(&suffix);
129 let eq_suffixed = eq_words_in_s.1.concatenate(&suffix);
130
131 for word_in_d in &self.D {
133 let cell_map = self.ot_content.get(word_in_d);
134 let v1 = cell_map
135 .and_then(|m| m.get(&initial_suffixed))
136 .and_then(|o| o.as_ref());
137 let v2 = cell_map
138 .and_then(|m| m.get(&eq_suffixed))
139 .and_then(|o| o.as_ref());
140
141 if v1 != v2 {
142 return Some((suffix, word_in_d.clone()));
144 }
145 }
146 }
147
148 None
149 }
150
151 pub fn add_counterexample(
153 &mut self,
154 input_word: &Word,
155 _output_word: &Word,
156 ) -> Result<(), String> {
157 for len_prefix in 1..=input_word.len() {
159 let prefix_input = input_word.prefix(len_prefix);
160 if !self.S.contains(&prefix_input) {
161 if self.SA.contains(&prefix_input) {
162 self.remove_row(prefix_input.clone())?;
163 }
164 self.add_word_in_S(prefix_input)?;
165 }
166 }
167 Ok(())
168 }
169
170 fn remove_row(&mut self, word: Word) -> Result<(), String> {
171 self.S.retain(|w| w != &word);
173 self.SA.retain(|w| w != &word);
175 for (_key, row_map) in self.ot_content.iter_mut() {
177 row_map.remove(&word);
178 }
179 Ok(())
180 }
181
182 pub fn make_consistent(
184 &mut self,
185 inconsistency: Inconsistency,
186 ) -> Result<(), String> {
187 let ((_pair, suffix), inconsistent_suffix) = inconsistency;
188
189 let new_col_word = suffix.concatenate(&inconsistent_suffix);
191 if !self.D.contains(&new_col_word) {
192 self.add_word_in_D(new_col_word)?;
193 }
194
195 Ok(())
196 }
197
198 pub fn is_closed(&self) -> bool {
200 let rows_in_s: HashSet<RowSignature> = self.S.iter().map(|s| self.get_row(s)).collect();
201
202 for sa in &self.SA {
203 if !rows_in_s.contains(&self.get_row(sa)) {
204 return false;
205 }
206 }
207 true
208 }
209
210 pub fn close_table(&mut self) -> Result<(), String> {
212 let mut to_move = Vec::new();
213 let rows_in_s: HashSet<RowSignature> = self.S.iter().map(|s| self.get_row(s)).collect();
214
215 for sa in &self.SA {
216 if !rows_in_s.contains(&self.get_row(sa)) {
217 to_move.push(sa.clone());
218 }
219 }
220
221 for word in to_move {
222 self.SA.retain(|w| w != &word);
223 self.add_word_in_S(word)?;
224 }
225
226 Ok(())
227 }
228
229 fn get_row(&self, row_name: &Word) -> Vec<Option<Letter>> {
231 let mut row = Vec::with_capacity(self.D.len());
232 for word_in_D in &self.D {
233 let value = self
234 .ot_content
235 .get(word_in_D)
236 .and_then(|cell_content| cell_content.get(row_name))
237 .cloned()
238 .unwrap_or(None);
239 row.push(value);
240 }
241 row
242 }
243
244 fn add_word_in_D(&mut self, word: Word) -> Result<(), String> {
245 if self.D.contains(&word) {
246 return Err(format!("Word {:?} already in D", word));
247 }
248
249 if self.ot_content.contains_key(&word) {
250 return Err(format!(
251 "Word {:?} already in content of the observation table",
252 word
253 ));
254 }
255
256 self.D.push(word.clone());
257
258 let words_to_query: Vec<Word> = self.S.iter().chain(self.SA.iter()).cloned().collect();
260 let mut cell_content = HashMap::with_capacity(words_to_query.len());
261
262 for word_in_S_or_SA in &words_to_query {
263 let query_input = word_in_S_or_SA.concatenate(&word);
264 let mut query = OutputQuery::new(query_input);
265 self.execute_query(&mut query)?;
266
267 if let Some(output_word) = query.output_word() {
268 if let Some(output_letter) = output_word.last_letter() {
269 cell_content.insert(word_in_S_or_SA.clone(), Some(output_letter.clone()));
270 }
271 }
272 }
273
274 self.ot_content.insert(word, cell_content);
275 Ok(())
276 }
277
278 fn add_word_in_S(&mut self, word: Word) -> Result<(), String> {
279 if self.S.contains(&word) {
280 return Err(format!("Word {:?} already in S", word));
281 }
282 if self.SA.contains(&word) {
283 return Err(format!("Word {:?} already in SA", word));
284 }
285
286 self.S.push(word.clone());
287
288 let d_words: Vec<Word> = self.D.iter().cloned().collect();
290 for word_in_D in &d_words {
291 let query_input = word.concatenate(word_in_D);
292 let mut query = OutputQuery::new(query_input);
293 self.execute_query(&mut query)?;
294
295 if let Some(output_word) = query.output_word() {
296 if let Some(output_letter) = output_word.last_letter() {
297 self.ot_content
298 .entry(word_in_D.clone())
299 .or_default()
300 .insert(word.clone(), Some(output_letter.clone()));
301 }
302 }
303 }
304
305 let input_letters: Vec<Letter> = self.input_letters.iter().cloned().collect();
307 for input_letter in &input_letters {
308 let new_sa = word.concatenate(&Word::from_letters(vec![input_letter.clone()]));
309 if !self.S.contains(&new_sa) {
310 self.add_word_in_SA(new_sa)?;
311 }
312 }
313
314 Ok(())
315 }
316
317 fn add_word_in_SA(&mut self, word: Word) -> Result<(), String> {
318 if self.SA.contains(&word) {
319 return Err(format!("Word {:?} already in SA", word));
320 }
321 if self.S.contains(&word) {
322 return Err(format!("Word {:?} already in S", word));
323 }
324
325 self.SA.push(word.clone());
326
327 let d_words: Vec<Word> = self.D.iter().cloned().collect();
329 for word_in_D in &d_words {
330 let query_input = word.concatenate(word_in_D);
331 let mut query = OutputQuery::new(query_input);
332 self.execute_query(&mut query)?;
333
334 if let Some(output_word) = query.output_word() {
335 if let Some(output_letter) = output_word.last_letter() {
336 self.ot_content
337 .entry(word_in_D.clone())
338 .or_default()
339 .insert(word.clone(), Some(output_letter.clone()));
340 }
341 }
342 }
343
344 Ok(())
345 }
346
347 fn execute_query(&mut self, query: &mut OutputQuery) -> Result<(), String> {
348 self.knowledge_base.lock().unwrap().resolve_query(query)
349 }
350 pub fn is_consistent(&self) -> bool {
352 self.find_inconsistency().is_none()
353 }
354
355 pub fn build_hypothesis(&self) -> Result<Automata, String> {
357 if !self.is_closed() {
358 return Err("Observation table must be closed".into());
359 }
360 if !self.is_consistent() {
361 return Err("Observation table must be consistent".into());
362 }
363
364 let mut transitions = Vec::new();
365 let mut words_and_states: Vec<(Word, State)> = Vec::new();
366 let mut long_state_name_to_states: HashMap<RowSignature, usize> = HashMap::new();
367
368 let mut unique_rows: IndexMap<RowSignature, Vec<Word>> = IndexMap::new();
370 for word in &self.S {
371 unique_rows
372 .entry(self.get_row(word))
373 .or_default()
374 .push(word.clone());
375 }
376
377 let mut initial_state = None;
379 for (idx, (row_key, words_in_s)) in unique_rows.iter().enumerate() {
380 let state_name = idx.to_string();
381 let state = State::new(state_name.clone());
382
383 if words_in_s.iter().any(|w| w.is_empty()) {
385 if initial_state.is_some() {
386 return Err("Multiple initial states found".into());
387 }
388 initial_state = Some(idx);
389 }
390
391 words_and_states.push((words_in_s[0].clone(), state.clone()));
392 long_state_name_to_states.insert(row_key.clone(), idx);
393 }
394
395 let initial_state_idx = initial_state.ok_or("No initial state found")?;
396
397 let input_words: Vec<(Letter, Word)> = self
399 .input_letters
400 .iter()
401 .cloned()
402 .map(|letter| {
403 let one_letter_word = Word::from_letters(vec![letter.clone()]);
404 (letter, one_letter_word)
405 })
406 .collect();
407 for i in 0..words_and_states.len() {
408 let word = words_and_states[i].0.clone();
409 let source_state_name = words_and_states[i].1.name.clone();
410
411 for (input_letter, input_letter_word) in &input_words {
412 let new_word = word.concatenate(input_letter_word);
413 let output_row = self.get_row(&new_word);
414 let output_state_idx = long_state_name_to_states
415 .get(&output_row)
416 .copied()
417 .ok_or_else(|| format!("Cannot find a state matching row: {:?}", output_row))?;
418
419 let output_letter = self
420 .ot_content
421 .get(input_letter_word)
422 .and_then(|cell| cell.get(&word))
423 .and_then(|opt| opt.as_ref())
424 .cloned()
425 .ok_or_else(|| {
426 format!(
427 "Missing output letter for input '{}' from word '{}'",
428 input_letter, word
429 )
430 })?;
431
432 let transition_name = format!("t{}", transitions.len());
433 let transition = Transition::new_with_source(
434 transition_name,
435 source_state_name.clone(),
436 words_and_states[output_state_idx].1.clone(),
437 input_letter.clone(),
438 output_letter,
439 );
440 words_and_states[i].1.add_transition(transition.clone());
441 transitions.push(transition);
442 }
443 }
444
445 let mut automata = Automata::new(
446 words_and_states[initial_state_idx].1.clone(),
447 "Automata".to_string(),
448 );
449 automata.transitions = transitions;
450
451 Ok(automata)
452 }
453}
454
455impl fmt::Display for ObservationTable {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 let mut matrix: Vec<Vec<String>> = Vec::new();
458
459 let mut header_row = vec![String::new()];
461 header_row.extend(self.D.iter().map(|d| d.to_string()));
462 matrix.push(header_row.clone());
463
464 for row_name in &self.S {
466 let mut row = vec![row_name.to_string()];
467 row.extend(
468 self.get_row(row_name)
469 .iter()
470 .map(|w| w.as_ref().map(|l| l.to_string()).unwrap_or_default()),
471 );
472 matrix.push(row);
473 }
474
475 matrix.push(vec!["~~~".to_string(); header_row.len()]);
477
478 for row_name in &self.SA {
480 let mut row = vec![row_name.to_string()];
481 row.extend(
482 self.get_row(row_name)
483 .iter()
484 .map(|w| w.as_ref().map(|l| l.to_string()).unwrap_or_default()),
485 );
486 matrix.push(row);
487 }
488
489 let col_count = matrix[0].len();
491 let mut col_widths = vec![0; col_count];
492 for row in &matrix {
493 for (i, cell) in row.iter().enumerate() {
494 col_widths[i] = col_widths[i].max(cell.len());
495 }
496 }
497
498 for row in &matrix {
500 for (i, cell) in row.iter().enumerate() {
501 if i > 0 {
502 write!(f, " | ")?;
503 }
504 write!(f, "{:width$}", cell, width = col_widths[i])?;
505 }
506 writeln!(f)?;
507 }
508
509 Ok(())
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 use crate::knowledge_base::KnowledgeBase;
517 use std::sync::{Arc, Mutex};
518
519 #[test]
520 fn test_observation_table_creation() {
521 let kb: Arc<Mutex<dyn crate::knowledge_base::KnowledgeBaseTrait>> =
522 Arc::new(Mutex::new(KnowledgeBase::new()));
523 let letters = vec![Letter::new("a"), Letter::new("b")];
524 let ot = ObservationTable::new(letters, kb);
525 assert!(!ot.initialized);
526 }
527}