rust_rule_engine/backward/
proof_tree.rs

1// Proof Tree for Backward Chaining Explanations
2//
3// This module provides data structures for capturing and visualizing
4// the reasoning process in backward chaining queries.
5//
6// Version: 1.9.0
7
8use super::unification::Bindings;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Represents a single node in the proof tree
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ProofNode {
15    /// The goal that was proven at this node
16    pub goal: String,
17
18    /// Name of the rule that was used (if any)
19    pub rule_name: Option<String>,
20
21    /// Variable bindings at this node
22    #[serde(skip_serializing_if = "HashMap::is_empty")]
23    pub bindings: HashMap<String, String>,
24
25    /// Child nodes (sub-goals that were proven)
26    #[serde(skip_serializing_if = "Vec::is_empty")]
27    pub children: Vec<ProofNode>,
28
29    /// Depth in the proof tree
30    pub depth: usize,
31
32    /// Whether this goal was proven successfully
33    pub proven: bool,
34
35    /// Type of proof node
36    pub node_type: ProofNodeType,
37}
38
39/// Type of proof node
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub enum ProofNodeType {
42    /// Goal proven by a fact
43    Fact,
44
45    /// Goal proven by a rule
46    Rule,
47
48    /// Negated goal (NOT)
49    Negation,
50
51    /// Goal failed to prove
52    Failed,
53}
54
55impl ProofNode {
56    /// Create a new proof node
57    pub fn new(goal: String, depth: usize) -> Self {
58        ProofNode {
59            goal,
60            rule_name: None,
61            bindings: HashMap::new(),
62            children: Vec::new(),
63            depth,
64            proven: false,
65            node_type: ProofNodeType::Failed,
66        }
67    }
68
69    /// Create a fact node
70    pub fn fact(goal: String, depth: usize) -> Self {
71        ProofNode {
72            goal,
73            rule_name: None,
74            bindings: HashMap::new(),
75            children: Vec::new(),
76            depth,
77            proven: true,
78            node_type: ProofNodeType::Fact,
79        }
80    }
81
82    /// Create a rule node
83    pub fn rule(goal: String, rule_name: String, depth: usize) -> Self {
84        ProofNode {
85            goal,
86            rule_name: Some(rule_name),
87            bindings: HashMap::new(),
88            children: Vec::new(),
89            depth,
90            proven: true,
91            node_type: ProofNodeType::Rule,
92        }
93    }
94
95    /// Create a negation node
96    pub fn negation(goal: String, depth: usize, proven: bool) -> Self {
97        ProofNode {
98            goal,
99            rule_name: None,
100            bindings: HashMap::new(),
101            children: Vec::new(),
102            depth,
103            proven,
104            node_type: ProofNodeType::Negation,
105        }
106    }
107
108    /// Add a child node
109    pub fn add_child(&mut self, child: ProofNode) {
110        self.children.push(child);
111    }
112
113    /// Set bindings from Bindings object
114    pub fn set_bindings(&mut self, bindings: &Bindings) {
115        // Convert Bindings to HashMap using to_map() method
116        let binding_map = bindings.to_map();
117        self.bindings = binding_map
118            .iter()
119            .map(|(k, v)| (k.clone(), format!("{:?}", v)))
120            .collect();
121    }
122
123    /// Set bindings from HashMap
124    pub fn set_bindings_map(&mut self, bindings: HashMap<String, String>) {
125        self.bindings = bindings;
126    }
127
128    /// Check if this is a leaf node
129    pub fn is_leaf(&self) -> bool {
130        self.children.is_empty()
131    }
132
133    /// Print the proof tree
134    pub fn print_tree(&self, indent: usize) {
135        let prefix = "  ".repeat(indent);
136        let status = if self.proven { "✓" } else { "✗" };
137
138        println!("{}{} {}", prefix, status, self.goal);
139
140        if let Some(rule) = &self.rule_name {
141            println!("{}  [Rule: {}]", prefix, rule);
142        }
143
144        match self.node_type {
145            ProofNodeType::Fact => println!("{}  [FACT]", prefix),
146            ProofNodeType::Negation => println!("{}  [NEGATION]", prefix),
147            _ => {}
148        }
149
150        if !self.bindings.is_empty() {
151            println!("{}  Bindings: {:?}", prefix, self.bindings);
152        }
153
154        for child in &self.children {
155            child.print_tree(indent + 1);
156        }
157    }
158
159    /// Get tree height
160    pub fn height(&self) -> usize {
161        if self.children.is_empty() {
162            1
163        } else {
164            1 + self.children.iter().map(|c| c.height()).max().unwrap_or(0)
165        }
166    }
167
168    /// Count total nodes
169    pub fn node_count(&self) -> usize {
170        1 + self.children.iter().map(|c| c.node_count()).sum::<usize>()
171    }
172}
173
174/// Complete proof tree with metadata
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct ProofTree {
177    /// Root node of the proof tree
178    pub root: ProofNode,
179
180    /// Whether the query was proven
181    pub success: bool,
182
183    /// Original query string
184    pub query: String,
185
186    /// Statistics
187    pub stats: ProofStats,
188}
189
190/// Statistics about the proof
191#[derive(Debug, Clone, Default, Serialize, Deserialize)]
192pub struct ProofStats {
193    /// Total number of goals explored
194    pub goals_explored: usize,
195
196    /// Total number of rules evaluated
197    pub rules_evaluated: usize,
198
199    /// Total number of facts checked
200    pub facts_checked: usize,
201
202    /// Maximum depth reached
203    pub max_depth: usize,
204
205    /// Total nodes in proof tree
206    pub total_nodes: usize,
207}
208
209impl ProofTree {
210    /// Create a new proof tree
211    pub fn new(root: ProofNode, query: String) -> Self {
212        let success = root.proven;
213        let total_nodes = root.node_count();
214        let max_depth = root.height();
215
216        ProofTree {
217            root,
218            success,
219            query,
220            stats: ProofStats {
221                goals_explored: 0,
222                rules_evaluated: 0,
223                facts_checked: 0,
224                max_depth,
225                total_nodes,
226            },
227        }
228    }
229
230    /// Set statistics
231    pub fn set_stats(&mut self, stats: ProofStats) {
232        self.stats = stats;
233    }
234
235    /// Print the entire tree
236    pub fn print(&self) {
237        println!("Query: {}", self.query);
238        println!(
239            "Result: {}",
240            if self.success {
241                "✓ Proven"
242            } else {
243                "✗ Unprovable"
244            }
245        );
246        println!("\nProof Tree:");
247        println!("{}", "=".repeat(80));
248        self.root.print_tree(0);
249        println!("{}", "=".repeat(80));
250        self.print_stats();
251    }
252
253    /// Print statistics
254    pub fn print_stats(&self) {
255        println!("\nStatistics:");
256        println!("  Goals explored: {}", self.stats.goals_explored);
257        println!("  Rules evaluated: {}", self.stats.rules_evaluated);
258        println!("  Facts checked: {}", self.stats.facts_checked);
259        println!("  Max depth: {}", self.stats.max_depth);
260        println!("  Total nodes: {}", self.stats.total_nodes);
261    }
262
263    /// Convert to JSON string
264    pub fn to_json(&self) -> Result<String, serde_json::Error> {
265        serde_json::to_string_pretty(self)
266    }
267
268    /// Convert to Markdown
269    pub fn to_markdown(&self) -> String {
270        let mut md = String::new();
271
272        md.push_str("# Proof Explanation\n\n");
273        md.push_str(&format!("**Query:** `{}`\n\n", self.query));
274        md.push_str(&format!(
275            "**Result:** {}\n\n",
276            if self.success {
277                "✓ Proven"
278            } else {
279                "✗ Unprovable"
280            }
281        ));
282
283        md.push_str("## Proof Tree\n\n");
284        Self::node_to_markdown(&self.root, &mut md, 0);
285
286        md.push_str("\n## Statistics\n\n");
287        md.push_str(&format!(
288            "- **Goals explored:** {}\n",
289            self.stats.goals_explored
290        ));
291        md.push_str(&format!(
292            "- **Rules evaluated:** {}\n",
293            self.stats.rules_evaluated
294        ));
295        md.push_str(&format!(
296            "- **Facts checked:** {}\n",
297            self.stats.facts_checked
298        ));
299        md.push_str(&format!("- **Max depth:** {}\n", self.stats.max_depth));
300        md.push_str(&format!("- **Total nodes:** {}\n", self.stats.total_nodes));
301
302        md
303    }
304
305    /// Convert node to markdown recursively
306    fn node_to_markdown(node: &ProofNode, md: &mut String, depth: usize) {
307        let prefix = "  ".repeat(depth);
308        let status = if node.proven { "✓" } else { "✗" };
309
310        md.push_str(&format!("{}* {} `{}`", prefix, status, node.goal));
311
312        if let Some(rule) = &node.rule_name {
313            md.push_str(&format!(" **[Rule: {}]**", rule));
314        }
315
316        match node.node_type {
317            ProofNodeType::Fact => md.push_str(" *[FACT]*"),
318            ProofNodeType::Negation => md.push_str(" *[NEGATION]*"),
319            _ => {}
320        }
321
322        md.push('\n');
323
324        if !node.bindings.is_empty() {
325            md.push_str(&format!("{}  * Bindings: `{:?}`\n", prefix, node.bindings));
326        }
327
328        for child in &node.children {
329            Self::node_to_markdown(child, md, depth + 1);
330        }
331    }
332
333    /// Convert to HTML
334    pub fn to_html(&self) -> String {
335        let mut html = String::new();
336
337        html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
338        html.push_str("  <title>Proof Explanation</title>\n");
339        html.push_str("  <style>\n");
340        html.push_str("    body { font-family: 'Courier New', monospace; margin: 20px; }\n");
341        html.push_str("    .proven { color: green; }\n");
342        html.push_str("    .failed { color: red; }\n");
343        html.push_str("    .node { margin-left: 20px; }\n");
344        html.push_str("    .rule { color: blue; font-style: italic; }\n");
345        html.push_str("    .bindings { color: gray; font-size: 0.9em; }\n");
346        html.push_str("    .stats { margin-top: 20px; padding: 10px; background: #f0f0f0; }\n");
347        html.push_str("  </style>\n");
348        html.push_str("</head>\n<body>\n");
349
350        html.push_str("<h1>Proof Explanation</h1>\n");
351        html.push_str(&format!(
352            "<p><strong>Query:</strong> <code>{}</code></p>\n",
353            self.query
354        ));
355        html.push_str(&format!(
356            "<p><strong>Result:</strong> <span class=\"{}\">{}</span></p>\n",
357            if self.success { "proven" } else { "failed" },
358            if self.success {
359                "✓ Proven"
360            } else {
361                "✗ Unprovable"
362            }
363        ));
364
365        html.push_str("<h2>Proof Tree</h2>\n");
366        Self::node_to_html(&self.root, &mut html);
367
368        html.push_str("<div class=\"stats\">\n");
369        html.push_str("<h2>Statistics</h2>\n");
370        html.push_str(&format!(
371            "<p>Goals explored: {}</p>\n",
372            self.stats.goals_explored
373        ));
374        html.push_str(&format!(
375            "<p>Rules evaluated: {}</p>\n",
376            self.stats.rules_evaluated
377        ));
378        html.push_str(&format!(
379            "<p>Facts checked: {}</p>\n",
380            self.stats.facts_checked
381        ));
382        html.push_str(&format!("<p>Max depth: {}</p>\n", self.stats.max_depth));
383        html.push_str(&format!("<p>Total nodes: {}</p>\n", self.stats.total_nodes));
384        html.push_str("</div>\n");
385
386        html.push_str("</body>\n</html>");
387        html
388    }
389
390    /// Convert node to HTML recursively
391    fn node_to_html(node: &ProofNode, html: &mut String) {
392        let status = if node.proven { "✓" } else { "✗" };
393        let class = if node.proven { "proven" } else { "failed" };
394
395        html.push_str("<div class=\"node\">\n");
396        html.push_str(&format!(
397            "  <span class=\"{}\">{} {}</span>",
398            class, status, node.goal
399        ));
400
401        if let Some(rule) = &node.rule_name {
402            html.push_str(&format!(" <span class=\"rule\">[Rule: {}]</span>", rule));
403        }
404
405        match node.node_type {
406            ProofNodeType::Fact => html.push_str(" <em>[FACT]</em>"),
407            ProofNodeType::Negation => html.push_str(" <em>[NEGATION]</em>"),
408            _ => {}
409        }
410
411        if !node.bindings.is_empty() {
412            html.push_str(&format!(
413                "<br><span class=\"bindings\">Bindings: {:?}</span>",
414                node.bindings
415            ));
416        }
417
418        html.push('\n');
419
420        for child in &node.children {
421            Self::node_to_html(child, html);
422        }
423
424        html.push_str("</div>\n");
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn test_proof_node_creation() {
434        let node = ProofNode::new("test_goal".to_string(), 0);
435        assert_eq!(node.goal, "test_goal");
436        assert_eq!(node.depth, 0);
437        assert!(!node.proven);
438        assert_eq!(node.node_type, ProofNodeType::Failed);
439    }
440
441    #[test]
442    fn test_fact_node() {
443        let node = ProofNode::fact("fact_goal".to_string(), 1);
444        assert!(node.proven);
445        assert_eq!(node.node_type, ProofNodeType::Fact);
446        assert!(node.is_leaf());
447    }
448
449    #[test]
450    fn test_rule_node() {
451        let node = ProofNode::rule("rule_goal".to_string(), "test_rule".to_string(), 2);
452        assert!(node.proven);
453        assert_eq!(node.node_type, ProofNodeType::Rule);
454        assert_eq!(node.rule_name, Some("test_rule".to_string()));
455    }
456
457    #[test]
458    fn test_add_child() {
459        let mut parent = ProofNode::rule("parent".to_string(), "rule1".to_string(), 0);
460        let child = ProofNode::fact("child".to_string(), 1);
461
462        parent.add_child(child);
463        assert_eq!(parent.children.len(), 1);
464        assert!(!parent.is_leaf());
465    }
466
467    #[test]
468    fn test_tree_height() {
469        let mut root = ProofNode::rule("root".to_string(), "rule1".to_string(), 0);
470        let mut child1 = ProofNode::rule("child1".to_string(), "rule2".to_string(), 1);
471        let child2 = ProofNode::fact("child2".to_string(), 2);
472
473        child1.add_child(child2);
474        root.add_child(child1);
475
476        assert_eq!(root.height(), 3);
477    }
478
479    #[test]
480    fn test_node_count() {
481        let mut root = ProofNode::rule("root".to_string(), "rule1".to_string(), 0);
482        let child1 = ProofNode::fact("child1".to_string(), 1);
483        let child2 = ProofNode::fact("child2".to_string(), 1);
484
485        root.add_child(child1);
486        root.add_child(child2);
487
488        assert_eq!(root.node_count(), 3);
489    }
490
491    #[test]
492    fn test_proof_tree_creation() {
493        let root = ProofNode::fact("test".to_string(), 0);
494        let tree = ProofTree::new(root, "test query".to_string());
495
496        assert!(tree.success);
497        assert_eq!(tree.query, "test query");
498        assert_eq!(tree.stats.total_nodes, 1);
499    }
500
501    #[test]
502    fn test_json_serialization() {
503        let root = ProofNode::fact("test".to_string(), 0);
504        let tree = ProofTree::new(root, "test query".to_string());
505
506        let json = tree.to_json().unwrap();
507        assert!(json.contains("test query"));
508        assert!(json.contains("Fact"));
509    }
510
511    #[test]
512    fn test_markdown_generation() {
513        let root = ProofNode::fact("test".to_string(), 0);
514        let tree = ProofTree::new(root, "test query".to_string());
515
516        let md = tree.to_markdown();
517        assert!(md.contains("# Proof Explanation"));
518        assert!(md.contains("test query"));
519        assert!(md.contains("✓"));
520    }
521
522    #[test]
523    fn test_html_generation() {
524        let root = ProofNode::fact("test".to_string(), 0);
525        let tree = ProofTree::new(root, "test query".to_string());
526
527        let html = tree.to_html();
528        assert!(html.contains("<!DOCTYPE html>"));
529        assert!(html.contains("test query"));
530        assert!(html.contains("✓"));
531    }
532}