Skip to main content

vending_machine/
vending_machine.rs

1/// Example: Simple Vending Machine Learning
2use rust_lstar::knowledge_base::{KnowledgeBaseStats, KnowledgeBaseTrait};
3use rust_lstar::query::OutputQuery;
4use rust_lstar::*;
5use std::sync::{Arc, Mutex};
6
7fn main() -> Result<(), Box<dyn std::error::Error>> {
8    println!("=== Vending Machine Learning Example ===\n");
9
10    // Create a vending machine knowledge base (SUL)
11    let kb = Arc::new(Mutex::new(VendingMachineKB::new()));
12
13    // Define input vocabulary
14    let vocabulary = vec![
15        "INSERT_COIN".to_string(),
16        "PRESS_A".to_string(),
17        "PRESS_B".to_string(),
18    ];
19
20    // Run L* learner
21    let mut learner = LSTAR::new(vocabulary, kb.clone(), 4, None, None);
22    match learner.learn() {
23        Ok(automata) => {
24            println!("\n=== Learned Vending Machine ===\n");
25            println!("{}", automata.build_dot_code());
26        }
27        Err(e) => eprintln!("Learning error: {}", e),
28    }
29
30    let kb_guard = kb.lock().unwrap();
31    println!("\nKnowledge Base Statistics:\n{}", kb_guard.stats);
32
33    Ok(())
34}
35
36/// Knowledge base that simulates the vending machine SUL
37struct VendingMachineKB {
38    state: VendingState,
39    stats: KnowledgeBaseStats,
40}
41
42#[derive(Clone, Copy)]
43enum VendingState {
44    Idle,
45    HasCoin,
46}
47
48impl VendingMachineKB {
49    fn new() -> Self {
50        VendingMachineKB {
51            state: VendingState::Idle,
52            stats: KnowledgeBaseStats::new(),
53        }
54    }
55
56    fn process_input(&self, input: &str, current: VendingState) -> (VendingState, &'static str) {
57        match current {
58            VendingState::Idle => match input {
59                "INSERT_COIN" => (VendingState::HasCoin, "BEEP"),
60                "PRESS_A" => (VendingState::Idle, "ERROR"),
61                "PRESS_B" => (VendingState::Idle, "ERROR"),
62                _ => (VendingState::Idle, "ERROR"),
63            },
64            VendingState::HasCoin => match input {
65                "PRESS_A" => (VendingState::Idle, "DISPENSE_A"),
66                "PRESS_B" => (VendingState::Idle, "DISPENSE_B"),
67                "INSERT_COIN" => (VendingState::HasCoin, "BEEP"),
68                _ => (VendingState::HasCoin, "ERROR"),
69            },
70        }
71    }
72}
73
74impl KnowledgeBaseTrait for VendingMachineKB {
75    fn resolve_query(&mut self, query: &mut OutputQuery) -> Result<(), String> {
76        self.stats.increment_nb_query();
77        self.stats.add_nb_letter(query.input_word.len());
78        self.stats.increment_nb_submitted_query();
79        self.stats.add_nb_submitted_letter(query.input_word.len());
80
81        // Reset to initial state for each query
82        self.state = VendingState::Idle;
83
84        let mut outputs = Vec::new();
85        for input_letter in query.input_word.letters() {
86            let command_string = input_letter.symbols();
87            let command = command_string.as_str();
88
89            let (next, resp) = self.process_input(command, self.state);
90            self.state = next;
91            outputs.push(Letter::new(resp));
92        }
93
94        query.set_result(Word::from_letters(outputs));
95        Ok(())
96    }
97
98    fn add_word(&mut self, _input: &Word, _output: &Word) -> Result<(), String> {
99        Ok(())
100    }
101}