rust_rule_engine/backward/
explanation.rs

1// Explanation Generation for Backward Chaining
2//
3// This module provides functionality for generating human-readable
4// explanations of how backward chaining queries are proven.
5//
6// Version: 1.9.0
7
8use super::goal::Goal;
9use super::proof_tree::{ProofNode, ProofNodeType, ProofStats, ProofTree};
10use super::unification::Bindings;
11use std::collections::HashMap;
12
13/// Explanation builder that tracks the proof process
14#[derive(Debug, Clone)]
15pub struct ExplanationBuilder {
16    /// Stack of nodes being built (for hierarchy)
17    node_stack: Vec<ProofNode>,
18
19    /// Statistics tracking
20    goals_explored: usize,
21    rules_evaluated: usize,
22    facts_checked: usize,
23    max_depth: usize,
24
25    /// Enable/disable tracking
26    enabled: bool,
27}
28
29impl ExplanationBuilder {
30    /// Create a new explanation builder
31    pub fn new() -> Self {
32        ExplanationBuilder {
33            node_stack: Vec::new(),
34            goals_explored: 0,
35            rules_evaluated: 0,
36            facts_checked: 0,
37            max_depth: 0,
38            enabled: false,
39        }
40    }
41
42    /// Enable explanation tracking
43    pub fn enable(&mut self) {
44        self.enabled = true;
45    }
46
47    /// Disable explanation tracking
48    pub fn disable(&mut self) {
49        self.enabled = false;
50    }
51
52    /// Check if tracking is enabled
53    pub fn is_enabled(&self) -> bool {
54        self.enabled
55    }
56
57    /// Start tracking a goal
58    pub fn start_goal(&mut self, goal: &Goal) {
59        if !self.enabled {
60            return;
61        }
62
63        self.goals_explored += 1;
64        self.max_depth = self.max_depth.max(goal.depth);
65
66        let node = ProofNode::new(goal.pattern.clone(), goal.depth);
67        self.node_stack.push(node);
68    }
69
70    /// Mark goal as proven by a fact
71    pub fn goal_proven_by_fact(&mut self, _goal: &Goal, bindings: &Bindings) {
72        if !self.enabled || self.node_stack.is_empty() {
73            return;
74        }
75
76        self.facts_checked += 1;
77
78        if let Some(node) = self.node_stack.last_mut() {
79            node.proven = true;
80            node.node_type = ProofNodeType::Fact;
81            node.set_bindings(bindings);
82        }
83    }
84
85    /// Mark goal as proven by a rule
86    pub fn goal_proven_by_rule(&mut self, _goal: &Goal, rule_name: &str, bindings: &Bindings) {
87        if !self.enabled || self.node_stack.is_empty() {
88            return;
89        }
90
91        self.rules_evaluated += 1;
92
93        if let Some(node) = self.node_stack.last_mut() {
94            node.proven = true;
95            node.node_type = ProofNodeType::Rule;
96            node.rule_name = Some(rule_name.to_string());
97            node.set_bindings(bindings);
98        }
99    }
100
101    /// Mark goal as negation
102    pub fn goal_negation(&mut self, _goal: &Goal, proven: bool) {
103        if !self.enabled || self.node_stack.is_empty() {
104            return;
105        }
106
107        if let Some(node) = self.node_stack.last_mut() {
108            node.proven = proven;
109            node.node_type = ProofNodeType::Negation;
110        }
111    }
112
113    /// Mark goal as failed
114    pub fn goal_failed(&mut self) {
115        if !self.enabled || self.node_stack.is_empty() {
116            return;
117        }
118
119        if let Some(node) = self.node_stack.last_mut() {
120            node.proven = false;
121            node.node_type = ProofNodeType::Failed;
122        }
123    }
124
125    /// Finish tracking a goal and add to parent
126    pub fn finish_goal(&mut self) {
127        if !self.enabled || self.node_stack.is_empty() {
128            return;
129        }
130
131        let finished_node = self.node_stack.pop().unwrap();
132
133        // Add as child to parent if there is one
134        if let Some(parent) = self.node_stack.last_mut() {
135            parent.add_child(finished_node);
136        } else {
137            // Root node - push it back
138            self.node_stack.push(finished_node);
139        }
140    }
141
142    /// Build the final proof tree
143    pub fn build(self, query: String) -> Option<ProofTree> {
144        if !self.enabled || self.node_stack.is_empty() {
145            return None;
146        }
147
148        let root = self.node_stack.into_iter().next().unwrap();
149        let mut tree = ProofTree::new(root, query);
150
151        tree.set_stats(ProofStats {
152            goals_explored: self.goals_explored,
153            rules_evaluated: self.rules_evaluated,
154            facts_checked: self.facts_checked,
155            max_depth: self.max_depth,
156            total_nodes: tree.root.node_count(),
157        });
158
159        Some(tree)
160    }
161
162    /// Get current statistics
163    pub fn stats(&self) -> ProofStats {
164        ProofStats {
165            goals_explored: self.goals_explored,
166            rules_evaluated: self.rules_evaluated,
167            facts_checked: self.facts_checked,
168            max_depth: self.max_depth,
169            total_nodes: self.node_stack.len(),
170        }
171    }
172
173    /// Reset the builder
174    pub fn reset(&mut self) {
175        self.node_stack.clear();
176        self.goals_explored = 0;
177        self.rules_evaluated = 0;
178        self.facts_checked = 0;
179        self.max_depth = 0;
180    }
181}
182
183impl Default for ExplanationBuilder {
184    fn default() -> Self {
185        Self::new()
186    }
187}
188
189/// Step-by-step explanation of reasoning
190#[derive(Debug, Clone)]
191pub struct ExplanationStep {
192    /// Step number
193    pub step_number: usize,
194
195    /// Goal being proven
196    pub goal: String,
197
198    /// Rule used (if any)
199    pub rule_name: Option<String>,
200
201    /// Condition evaluated
202    pub condition: String,
203
204    /// Variable bindings at this step
205    pub bindings: HashMap<String, String>,
206
207    /// Result of this step
208    pub result: StepResult,
209
210    /// Depth in reasoning tree
211    pub depth: usize,
212}
213
214/// Result of an explanation step
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum StepResult {
217    /// Step succeeded
218    Success,
219
220    /// Step failed
221    Failed,
222
223    /// Step was skipped
224    Skipped,
225}
226
227impl ExplanationStep {
228    /// Create a new explanation step
229    pub fn new(
230        step_number: usize,
231        goal: String,
232        rule_name: Option<String>,
233        condition: String,
234        bindings: HashMap<String, String>,
235        result: StepResult,
236        depth: usize,
237    ) -> Self {
238        ExplanationStep {
239            step_number,
240            goal,
241            rule_name,
242            condition,
243            bindings,
244            result,
245            depth,
246        }
247    }
248
249    /// Format step as string
250    pub fn format(&self) -> String {
251        let mut s = String::new();
252
253        s.push_str(&format!("Step {}: {}\n", self.step_number, self.goal));
254
255        if let Some(rule) = &self.rule_name {
256            s.push_str(&format!("  Rule: {}\n", rule));
257        }
258
259        s.push_str(&format!("  Condition: {}\n", self.condition));
260
261        if !self.bindings.is_empty() {
262            s.push_str(&format!("  Bindings: {:?}\n", self.bindings));
263        }
264
265        s.push_str(&format!("  Result: {:?}\n", self.result));
266
267        s
268    }
269}
270
271/// Complete explanation with steps
272#[derive(Debug, Clone)]
273pub struct Explanation {
274    /// Original query
275    pub query: String,
276
277    /// Proof tree
278    pub proof_tree: ProofTree,
279
280    /// Step-by-step explanation
281    pub steps: Vec<ExplanationStep>,
282
283    /// Summary
284    pub summary: String,
285}
286
287impl Explanation {
288    /// Create a new explanation
289    pub fn new(query: String, proof_tree: ProofTree) -> Self {
290        let steps = Self::tree_to_steps(&proof_tree.root);
291        let summary = Self::generate_summary(&proof_tree);
292
293        Explanation {
294            query,
295            proof_tree,
296            steps,
297            summary,
298        }
299    }
300
301    /// Convert proof tree to step-by-step explanation
302    fn tree_to_steps(root: &ProofNode) -> Vec<ExplanationStep> {
303        let mut steps = Vec::new();
304        Self::collect_steps(root, &mut steps, 1);
305        steps
306    }
307
308    /// Recursively collect steps from tree
309    fn collect_steps(
310        node: &ProofNode,
311        steps: &mut Vec<ExplanationStep>,
312        mut step_num: usize,
313    ) -> usize {
314        let result = if node.proven {
315            StepResult::Success
316        } else {
317            StepResult::Failed
318        };
319
320        let condition = match &node.node_type {
321            ProofNodeType::Fact => format!("{} [FACT]", node.goal),
322            ProofNodeType::Rule => node.goal.clone(),
323            ProofNodeType::Negation => format!("NOT {}", node.goal),
324            ProofNodeType::Failed => format!("{} [FAILED]", node.goal),
325        };
326
327        let step = ExplanationStep::new(
328            step_num,
329            node.goal.clone(),
330            node.rule_name.clone(),
331            condition,
332            node.bindings.clone(),
333            result,
334            node.depth,
335        );
336
337        steps.push(step);
338        step_num += 1;
339
340        // Process children
341        for child in &node.children {
342            step_num = Self::collect_steps(child, steps, step_num);
343        }
344
345        step_num
346    }
347
348    /// Generate summary text
349    fn generate_summary(tree: &ProofTree) -> String {
350        if tree.success {
351            format!(
352                "Query '{}' was successfully proven using {} rules and {} facts.",
353                tree.query, tree.stats.rules_evaluated, tree.stats.facts_checked
354            )
355        } else {
356            format!(
357                "Query '{}' could not be proven. Explored {} goals, evaluated {} rules, and checked {} facts.",
358                tree.query,
359                tree.stats.goals_explored,
360                tree.stats.rules_evaluated,
361                tree.stats.facts_checked
362            )
363        }
364    }
365
366    /// Print full explanation
367    pub fn print(&self) {
368        println!(
369            "================================================================================"
370        );
371        println!("EXPLANATION");
372        println!(
373            "================================================================================"
374        );
375        println!("\nQuery: {}", self.query);
376        println!(
377            "Result: {}",
378            if self.proof_tree.success {
379                "✓ Proven"
380            } else {
381                "✗ Unprovable"
382            }
383        );
384        println!("\n{}\n", self.summary);
385
386        println!("Step-by-Step Reasoning:");
387        println!("{}", "-".repeat(80));
388        for step in &self.steps {
389            print!("{}", step.format());
390        }
391
392        println!("\n{}", "=".repeat(80));
393        self.proof_tree.print_stats();
394        println!("{}", "=".repeat(80));
395    }
396
397    /// Export to JSON
398    pub fn to_json(&self) -> Result<String, serde_json::Error> {
399        #[derive(serde::Serialize)]
400        struct ExplanationJson<'a> {
401            query: &'a str,
402            success: bool,
403            summary: &'a str,
404            proof_tree: &'a ProofTree,
405            stats: &'a ProofStats,
406        }
407
408        let json = ExplanationJson {
409            query: &self.query,
410            success: self.proof_tree.success,
411            summary: &self.summary,
412            proof_tree: &self.proof_tree,
413            stats: &self.proof_tree.stats,
414        };
415
416        serde_json::to_string_pretty(&json)
417    }
418
419    /// Export to Markdown
420    pub fn to_markdown(&self) -> String {
421        let mut md = String::new();
422
423        md.push_str("# Query Explanation\n\n");
424        md.push_str(&format!("**Query:** `{}`\n\n", self.query));
425        md.push_str(&format!(
426            "**Result:** {}\n\n",
427            if self.proof_tree.success {
428                "✓ Proven"
429            } else {
430                "✗ Unprovable"
431            }
432        ));
433        md.push_str(&format!("**Summary:** {}\n\n", self.summary));
434
435        md.push_str("## Proof Tree\n\n");
436        md.push_str(&self.proof_tree.to_markdown());
437
438        md
439    }
440}
441
442#[cfg(test)]
443mod tests {
444    use super::*;
445
446    #[test]
447    fn test_explanation_builder_creation() {
448        let builder = ExplanationBuilder::new();
449        assert!(!builder.is_enabled());
450        assert_eq!(builder.goals_explored, 0);
451    }
452
453    #[test]
454    fn test_enable_disable() {
455        let mut builder = ExplanationBuilder::new();
456        builder.enable();
457        assert!(builder.is_enabled());
458
459        builder.disable();
460        assert!(!builder.is_enabled());
461    }
462
463    #[test]
464    fn test_tracking_goal() {
465        let mut builder = ExplanationBuilder::new();
466        builder.enable();
467
468        let goal = Goal::new("test".to_string());
469        builder.start_goal(&goal);
470
471        assert_eq!(builder.goals_explored, 1);
472        assert_eq!(builder.node_stack.len(), 1);
473    }
474
475    #[test]
476    fn test_goal_proven_by_fact() {
477        let mut builder = ExplanationBuilder::new();
478        builder.enable();
479
480        let goal = Goal::new("test".to_string());
481        builder.start_goal(&goal);
482
483        let bindings = Bindings::new();
484        builder.goal_proven_by_fact(&goal, &bindings);
485
486        assert_eq!(builder.facts_checked, 1);
487        assert!(builder.node_stack[0].proven);
488        assert_eq!(builder.node_stack[0].node_type, ProofNodeType::Fact);
489    }
490
491    #[test]
492    fn test_build_proof_tree() {
493        let mut builder = ExplanationBuilder::new();
494        builder.enable();
495
496        let goal = Goal::new("test".to_string());
497        builder.start_goal(&goal);
498
499        let bindings = Bindings::new();
500        builder.goal_proven_by_fact(&goal, &bindings);
501
502        let tree = builder.build("test query".to_string());
503        assert!(tree.is_some());
504
505        let tree = tree.unwrap();
506        assert_eq!(tree.query, "test query");
507        assert!(tree.success);
508    }
509
510    #[test]
511    fn test_explanation_step() {
512        let step = ExplanationStep::new(
513            1,
514            "test_goal".to_string(),
515            Some("test_rule".to_string()),
516            "condition".to_string(),
517            HashMap::new(),
518            StepResult::Success,
519            0,
520        );
521
522        assert_eq!(step.step_number, 1);
523        assert_eq!(step.result, StepResult::Success);
524    }
525}