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, GoalStatus};
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(node: &ProofNode, steps: &mut Vec<ExplanationStep>, mut step_num: usize) -> usize {
310        let result = if node.proven {
311            StepResult::Success
312        } else {
313            StepResult::Failed
314        };
315
316        let condition = match &node.node_type {
317            ProofNodeType::Fact => format!("{} [FACT]", node.goal),
318            ProofNodeType::Rule => node.goal.clone(),
319            ProofNodeType::Negation => format!("NOT {}", node.goal),
320            ProofNodeType::Failed => format!("{} [FAILED]", node.goal),
321        };
322
323        let step = ExplanationStep::new(
324            step_num,
325            node.goal.clone(),
326            node.rule_name.clone(),
327            condition,
328            node.bindings.clone(),
329            result,
330            node.depth,
331        );
332
333        steps.push(step);
334        step_num += 1;
335
336        // Process children
337        for child in &node.children {
338            step_num = Self::collect_steps(child, steps, step_num);
339        }
340
341        step_num
342    }
343
344    /// Generate summary text
345    fn generate_summary(tree: &ProofTree) -> String {
346        if tree.success {
347            format!(
348                "Query '{}' was successfully proven using {} rules and {} facts.",
349                tree.query,
350                tree.stats.rules_evaluated,
351                tree.stats.facts_checked
352            )
353        } else {
354            format!(
355                "Query '{}' could not be proven. Explored {} goals, evaluated {} rules, and checked {} facts.",
356                tree.query,
357                tree.stats.goals_explored,
358                tree.stats.rules_evaluated,
359                tree.stats.facts_checked
360            )
361        }
362    }
363
364    /// Print full explanation
365    pub fn print(&self) {
366        println!("================================================================================");
367        println!("EXPLANATION");
368        println!("================================================================================");
369        println!("\nQuery: {}", self.query);
370        println!("Result: {}", if self.proof_tree.success { "✓ Proven" } else { "✗ Unprovable" });
371        println!("\n{}\n", self.summary);
372
373        println!("Step-by-Step Reasoning:");
374        println!("{}", "-".repeat(80));
375        for step in &self.steps {
376            print!("{}", step.format());
377        }
378
379        println!("\n{}", "=".repeat(80));
380        self.proof_tree.print_stats();
381        println!("{}", "=".repeat(80));
382    }
383
384    /// Export to JSON
385    pub fn to_json(&self) -> Result<String, serde_json::Error> {
386        #[derive(serde::Serialize)]
387        struct ExplanationJson<'a> {
388            query: &'a str,
389            success: bool,
390            summary: &'a str,
391            proof_tree: &'a ProofTree,
392            stats: &'a ProofStats,
393        }
394
395        let json = ExplanationJson {
396            query: &self.query,
397            success: self.proof_tree.success,
398            summary: &self.summary,
399            proof_tree: &self.proof_tree,
400            stats: &self.proof_tree.stats,
401        };
402
403        serde_json::to_string_pretty(&json)
404    }
405
406    /// Export to Markdown
407    pub fn to_markdown(&self) -> String {
408        let mut md = String::new();
409
410        md.push_str("# Query Explanation\n\n");
411        md.push_str(&format!("**Query:** `{}`\n\n", self.query));
412        md.push_str(&format!(
413            "**Result:** {}\n\n",
414            if self.proof_tree.success { "✓ Proven" } else { "✗ Unprovable" }
415        ));
416        md.push_str(&format!("**Summary:** {}\n\n", self.summary));
417
418        md.push_str("## Proof Tree\n\n");
419        md.push_str(&self.proof_tree.to_markdown());
420
421        md
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428
429    #[test]
430    fn test_explanation_builder_creation() {
431        let builder = ExplanationBuilder::new();
432        assert!(!builder.is_enabled());
433        assert_eq!(builder.goals_explored, 0);
434    }
435
436    #[test]
437    fn test_enable_disable() {
438        let mut builder = ExplanationBuilder::new();
439        builder.enable();
440        assert!(builder.is_enabled());
441
442        builder.disable();
443        assert!(!builder.is_enabled());
444    }
445
446    #[test]
447    fn test_tracking_goal() {
448        let mut builder = ExplanationBuilder::new();
449        builder.enable();
450
451        let goal = Goal::new("test".to_string());
452        builder.start_goal(&goal);
453
454        assert_eq!(builder.goals_explored, 1);
455        assert_eq!(builder.node_stack.len(), 1);
456    }
457
458    #[test]
459    fn test_goal_proven_by_fact() {
460        let mut builder = ExplanationBuilder::new();
461        builder.enable();
462
463        let goal = Goal::new("test".to_string());
464        builder.start_goal(&goal);
465
466        let bindings = Bindings::new();
467        builder.goal_proven_by_fact(&goal, &bindings);
468
469        assert_eq!(builder.facts_checked, 1);
470        assert!(builder.node_stack[0].proven);
471        assert_eq!(builder.node_stack[0].node_type, ProofNodeType::Fact);
472    }
473
474    #[test]
475    fn test_build_proof_tree() {
476        let mut builder = ExplanationBuilder::new();
477        builder.enable();
478
479        let goal = Goal::new("test".to_string());
480        builder.start_goal(&goal);
481
482        let bindings = Bindings::new();
483        builder.goal_proven_by_fact(&goal, &bindings);
484
485        let tree = builder.build("test query".to_string());
486        assert!(tree.is_some());
487
488        let tree = tree.unwrap();
489        assert_eq!(tree.query, "test query");
490        assert!(tree.success);
491    }
492
493    #[test]
494    fn test_explanation_step() {
495        let step = ExplanationStep::new(
496            1,
497            "test_goal".to_string(),
498            Some("test_rule".to_string()),
499            "condition".to_string(),
500            HashMap::new(),
501            StepResult::Success,
502            0,
503        );
504
505        assert_eq!(step.step_number, 1);
506        assert_eq!(step.result, StepResult::Success);
507    }
508}