rust_rule_engine/backward/
explanation.rs1use super::goal::{Goal, GoalStatus};
9use super::proof_tree::{ProofNode, ProofNodeType, ProofStats, ProofTree};
10use super::unification::Bindings;
11use std::collections::HashMap;
12
13#[derive(Debug, Clone)]
15pub struct ExplanationBuilder {
16 node_stack: Vec<ProofNode>,
18
19 goals_explored: usize,
21 rules_evaluated: usize,
22 facts_checked: usize,
23 max_depth: usize,
24
25 enabled: bool,
27}
28
29impl ExplanationBuilder {
30 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 pub fn enable(&mut self) {
44 self.enabled = true;
45 }
46
47 pub fn disable(&mut self) {
49 self.enabled = false;
50 }
51
52 pub fn is_enabled(&self) -> bool {
54 self.enabled
55 }
56
57 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 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 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 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 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 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 if let Some(parent) = self.node_stack.last_mut() {
135 parent.add_child(finished_node);
136 } else {
137 self.node_stack.push(finished_node);
139 }
140 }
141
142 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 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 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#[derive(Debug, Clone)]
191pub struct ExplanationStep {
192 pub step_number: usize,
194
195 pub goal: String,
197
198 pub rule_name: Option<String>,
200
201 pub condition: String,
203
204 pub bindings: HashMap<String, String>,
206
207 pub result: StepResult,
209
210 pub depth: usize,
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum StepResult {
217 Success,
219
220 Failed,
222
223 Skipped,
225}
226
227impl ExplanationStep {
228 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 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#[derive(Debug, Clone)]
273pub struct Explanation {
274 pub query: String,
276
277 pub proof_tree: ProofTree,
279
280 pub steps: Vec<ExplanationStep>,
282
283 pub summary: String,
285}
286
287impl Explanation {
288 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 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 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 for child in &node.children {
338 step_num = Self::collect_steps(child, steps, step_num);
339 }
340
341 step_num
342 }
343
344 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 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 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 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}