1use crate::ir::{Constant, Predicate, Term};
37use crate::proof_storage::{ProofFragment, ProofMetadata};
38use crate::reasoning::Proof;
39
40#[cfg(test)]
41use crate::reasoning::ProofRule;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ExplanationStyle {
46 Concise,
48 Detailed,
50 Pedagogical,
52 Formal,
54}
55
56#[derive(Debug, Clone)]
58pub struct ExplanationConfig {
59 pub style: ExplanationStyle,
61 pub include_premises: bool,
63 pub include_substitutions: bool,
65 pub max_depth: usize,
67 pub naturalize_predicates: bool,
69}
70
71impl Default for ExplanationConfig {
72 fn default() -> Self {
73 Self {
74 style: ExplanationStyle::Detailed,
75 include_premises: true,
76 include_substitutions: false,
77 max_depth: 10,
78 naturalize_predicates: true,
79 }
80 }
81}
82
83impl ExplanationConfig {
84 pub fn concise() -> Self {
86 Self {
87 style: ExplanationStyle::Concise,
88 include_premises: false,
89 include_substitutions: false,
90 max_depth: 3,
91 naturalize_predicates: true,
92 }
93 }
94
95 pub fn detailed() -> Self {
97 Self {
98 style: ExplanationStyle::Detailed,
99 include_premises: true,
100 include_substitutions: false,
101 max_depth: 10,
102 naturalize_predicates: true,
103 }
104 }
105
106 pub fn pedagogical() -> Self {
108 Self {
109 style: ExplanationStyle::Pedagogical,
110 include_premises: true,
111 include_substitutions: true,
112 max_depth: 10,
113 naturalize_predicates: true,
114 }
115 }
116
117 pub fn formal() -> Self {
119 Self {
120 style: ExplanationStyle::Formal,
121 include_premises: true,
122 include_substitutions: true,
123 max_depth: 10,
124 naturalize_predicates: false,
125 }
126 }
127}
128
129pub struct ProofExplainer {
131 config: ExplanationConfig,
132}
133
134impl ProofExplainer {
135 pub fn new() -> Self {
137 Self {
138 config: ExplanationConfig::default(),
139 }
140 }
141
142 pub fn with_config(config: ExplanationConfig) -> Self {
144 Self { config }
145 }
146
147 pub fn explain(&self, proof: &Proof, style: ExplanationStyle) -> String {
149 let mut config = self.config.clone();
150 config.style = style;
151 self.explain_with_config(proof, &config, 0)
152 }
153
154 pub fn explain_with_config(
156 &self,
157 proof: &Proof,
158 config: &ExplanationConfig,
159 depth: usize,
160 ) -> String {
161 if depth > config.max_depth {
162 return "[...proof continues...]".to_string();
163 }
164
165 match config.style {
166 ExplanationStyle::Concise => self.explain_concise(proof, depth),
167 ExplanationStyle::Detailed => self.explain_detailed(proof, config, depth),
168 ExplanationStyle::Pedagogical => self.explain_pedagogical(proof, config, depth),
169 ExplanationStyle::Formal => self.explain_formal(proof, config, depth),
170 }
171 }
172
173 fn explain_concise(&self, proof: &Proof, depth: usize) -> String {
175 let indent = " ".repeat(depth);
176 let goal_str = self.predicate_to_string(&proof.goal);
177
178 if let Some(rule) = &proof.rule {
179 if rule.is_fact {
180 format!("{}✓ {} (given fact)", indent, goal_str)
181 } else {
182 format!("{}✓ {} (by rule)", indent, goal_str)
183 }
184 } else {
185 format!("{}✓ {} (assumed)", indent, goal_str)
186 }
187 }
188
189 fn explain_detailed(&self, proof: &Proof, config: &ExplanationConfig, depth: usize) -> String {
191 let indent = " ".repeat(depth);
192 let mut result = String::new();
193
194 let goal_str = self.predicate_to_string(&proof.goal);
195
196 if let Some(rule) = &proof.rule {
197 if rule.is_fact {
198 result.push_str(&format!(
199 "{}We know that {} because it is a given fact.\n",
200 indent, goal_str
201 ));
202 } else {
203 result.push_str(&format!(
204 "{}To prove that {}, we apply a rule:\n",
205 indent, goal_str
206 ));
207
208 if config.include_premises && !proof.subproofs.is_empty() {
209 result.push_str(&format!("{} This requires proving:\n", indent));
210 for (i, subproof) in proof.subproofs.iter().enumerate() {
211 result.push_str(&format!(
212 "{} {}. {}\n",
213 indent,
214 i + 1,
215 self.predicate_to_string(&subproof.goal)
216 ));
217
218 let sub_explanation = self.explain_with_config(subproof, config, depth + 2);
219 result.push_str(&sub_explanation);
220 }
221 }
222
223 result.push_str(&format!("{} Therefore, {} holds.\n", indent, goal_str));
224 }
225 } else {
226 result.push_str(&format!("{}Assume that {}.\n", indent, goal_str));
227 }
228
229 result
230 }
231
232 fn explain_pedagogical(
234 &self,
235 proof: &Proof,
236 config: &ExplanationConfig,
237 depth: usize,
238 ) -> String {
239 let indent = " ".repeat(depth);
240 let mut result = String::new();
241
242 let goal_str = self.predicate_to_string(&proof.goal);
243
244 if let Some(rule) = &proof.rule {
245 if rule.is_fact {
246 result.push_str(&format!(
247 "{}[Base Case] We start with the fact that {}.\n",
248 indent, goal_str
249 ));
250 result.push_str(&format!(
251 "{}This is an axiom or given information in our knowledge base.\n",
252 indent
253 ));
254 } else {
255 result.push_str(&format!(
256 "{}[Deduction Step] Goal: Prove that {}\n",
257 indent, goal_str
258 ));
259 result.push_str(&format!("{}Strategy: Apply a logical rule\n", indent));
260
261 if !rule.body.is_empty() {
262 result.push_str(&format!(
263 "{}This rule states: IF {} THEN {}\n",
264 indent,
265 rule.body
266 .iter()
267 .map(|p| self.predicate_to_string(p))
268 .collect::<Vec<_>>()
269 .join(" AND "),
270 self.predicate_to_string(&rule.head)
271 ));
272 }
273
274 if config.include_premises && !proof.subproofs.is_empty() {
275 result.push_str(&format!(
276 "{}To apply this rule, we must first establish {} condition(s):\n",
277 indent,
278 proof.subproofs.len()
279 ));
280 for (i, subproof) in proof.subproofs.iter().enumerate() {
281 result.push_str(&format!(
282 "{}Condition {}: {}\n",
283 indent,
284 i + 1,
285 self.predicate_to_string(&subproof.goal)
286 ));
287 let sub_explanation = self.explain_with_config(subproof, config, depth + 1);
288 result.push_str(&sub_explanation);
289 }
290 }
291
292 result.push_str(&format!(
293 "{}Since all conditions are satisfied, we conclude that {}.\n",
294 indent, goal_str
295 ));
296 }
297 } else {
298 result.push_str(&format!(
299 "{}[Assumption] We assume that {}.\n",
300 indent, goal_str
301 ));
302 }
303
304 result
305 }
306
307 fn explain_formal(&self, proof: &Proof, config: &ExplanationConfig, depth: usize) -> String {
309 let indent = " ".repeat(depth);
310 let mut result = String::new();
311
312 result.push_str(&format!(
313 "{}⊢ {}\n",
314 indent,
315 self.predicate_to_formal(&proof.goal)
316 ));
317
318 if let Some(rule) = &proof.rule {
319 if rule.is_fact {
320 result.push_str(&format!("{} [Axiom]\n", indent));
321 } else {
322 if config.include_premises && !proof.subproofs.is_empty() {
323 for subproof in &proof.subproofs {
324 let sub_explanation = self.explain_with_config(subproof, config, depth + 1);
325 result.push_str(&sub_explanation);
326 }
327 }
328 result.push_str(&format!("{} [Apply Rule]\n", indent));
329 }
330 } else {
331 result.push_str(&format!("{} [Assume]\n", indent));
332 }
333
334 result
335 }
336
337 fn predicate_to_string(&self, pred: &Predicate) -> String {
339 if self.config.naturalize_predicates {
340 self.naturalize_predicate(pred)
341 } else {
342 self.predicate_to_formal(pred)
343 }
344 }
345
346 fn predicate_to_formal(&self, pred: &Predicate) -> String {
348 if pred.args.is_empty() {
349 pred.name.clone()
350 } else {
351 format!(
352 "{}({})",
353 pred.name,
354 pred.args
355 .iter()
356 .map(|t| self.term_to_string(t))
357 .collect::<Vec<_>>()
358 .join(", ")
359 )
360 }
361 }
362
363 pub fn naturalize_predicate(&self, pred: &Predicate) -> String {
365 match pred.name.as_str() {
367 "mortal" if pred.args.len() == 1 => {
368 format!("{} is mortal", self.term_to_string(&pred.args[0]))
369 }
370 "human" if pred.args.len() == 1 => {
371 format!("{} is a human", self.term_to_string(&pred.args[0]))
372 }
373 "parent" if pred.args.len() == 2 => {
374 format!(
375 "{} is a parent of {}",
376 self.term_to_string(&pred.args[0]),
377 self.term_to_string(&pred.args[1])
378 )
379 }
380 "ancestor" if pred.args.len() == 2 => {
381 format!(
382 "{} is an ancestor of {}",
383 self.term_to_string(&pred.args[0]),
384 self.term_to_string(&pred.args[1])
385 )
386 }
387 "greater_than" | "gt" if pred.args.len() == 2 => {
388 format!(
389 "{} > {}",
390 self.term_to_string(&pred.args[0]),
391 self.term_to_string(&pred.args[1])
392 )
393 }
394 "less_than" | "lt" if pred.args.len() == 2 => {
395 format!(
396 "{} < {}",
397 self.term_to_string(&pred.args[0]),
398 self.term_to_string(&pred.args[1])
399 )
400 }
401 "equal" | "eq" if pred.args.len() == 2 => {
402 format!(
403 "{} = {}",
404 self.term_to_string(&pred.args[0]),
405 self.term_to_string(&pred.args[1])
406 )
407 }
408 _ => {
409 self.predicate_to_formal(pred)
411 }
412 }
413 }
414
415 fn term_to_string(&self, term: &Term) -> String {
417 match term {
418 Term::Var(v) => format!("?{}", v),
419 Term::Const(c) => self.constant_to_string(c),
420 Term::Fun(f, args) => {
421 if args.is_empty() {
422 f.clone()
423 } else {
424 format!(
425 "{}({})",
426 f,
427 args.iter()
428 .map(|t| self.term_to_string(t))
429 .collect::<Vec<_>>()
430 .join(", ")
431 )
432 }
433 }
434 Term::Ref(_) => "[ref]".to_string(),
435 }
436 }
437
438 fn constant_to_string(&self, constant: &Constant) -> String {
440 match constant {
441 Constant::String(s) => s.clone(),
442 Constant::Int(i) => i.to_string(),
443 Constant::Bool(b) => b.to_string(),
444 Constant::Float(f) => f.clone(),
445 }
446 }
447}
448
449impl Default for ProofExplainer {
450 fn default() -> Self {
451 Self::new()
452 }
453}
454
455pub struct ProofExplanationBuilder {
457 proof: Proof,
458 config: ExplanationConfig,
459}
460
461impl ProofExplanationBuilder {
462 pub fn new(proof: Proof) -> Self {
464 Self {
465 proof,
466 config: ExplanationConfig::default(),
467 }
468 }
469
470 pub fn style(mut self, style: ExplanationStyle) -> Self {
472 self.config.style = style;
473 self
474 }
475
476 pub fn with_premises(mut self) -> Self {
478 self.config.include_premises = true;
479 self
480 }
481
482 pub fn with_substitutions(mut self) -> Self {
484 self.config.include_substitutions = true;
485 self
486 }
487
488 pub fn max_depth(mut self, depth: usize) -> Self {
490 self.config.max_depth = depth;
491 self
492 }
493
494 pub fn natural_language(mut self) -> Self {
496 self.config.naturalize_predicates = true;
497 self
498 }
499
500 pub fn formal_notation(mut self) -> Self {
502 self.config.naturalize_predicates = false;
503 self
504 }
505
506 pub fn build(self) -> String {
508 let explainer = ProofExplainer::with_config(self.config.clone());
509 explainer.explain_with_config(&self.proof, &self.config, 0)
510 }
511}
512
513pub struct FragmentProofExplainer {
515 explainer: ProofExplainer,
516}
517
518impl FragmentProofExplainer {
519 pub fn new() -> Self {
521 Self {
522 explainer: ProofExplainer::new(),
523 }
524 }
525
526 pub fn explain_fragment(&self, fragment: &ProofFragment, style: ExplanationStyle) -> String {
528 let mut result = String::new();
529
530 match style {
531 ExplanationStyle::Concise => {
532 result.push_str(&format!(
533 "✓ {}",
534 self.explainer.predicate_to_string(&fragment.conclusion)
535 ));
536 if let Some(rule) = &fragment.rule_applied {
537 result.push_str(&format!(" (by rule '{}')", rule.rule_id));
538 }
539 }
540 ExplanationStyle::Detailed => {
541 result.push_str(&format!(
542 "Proof fragment for: {}\n",
543 self.explainer.predicate_to_string(&fragment.conclusion)
544 ));
545 if let Some(rule) = &fragment.rule_applied {
546 result.push_str(&format!("Applied rule: {}\n", rule.rule_id));
547 }
548 if !fragment.premise_refs.is_empty() {
549 result.push_str(&format!(
550 "Number of premises: {}\n",
551 fragment.premise_refs.len()
552 ));
553 }
554 if !fragment.substitution.is_empty() {
555 result.push_str("Substitutions:\n");
556 for (var, term) in &fragment.substitution {
557 result.push_str(&format!(
558 " {} ← {}\n",
559 var,
560 self.explainer.term_to_string(term)
561 ));
562 }
563 }
564 }
565 ExplanationStyle::Pedagogical => {
566 result.push_str(&format!(
567 "[Proof Step] To establish that {}\n",
568 self.explainer.predicate_to_string(&fragment.conclusion)
569 ));
570 if let Some(rule) = &fragment.rule_applied {
571 result.push_str(&format!("We apply the rule: {}\n", rule.rule_id));
572 }
573 if !fragment.premise_refs.is_empty() {
574 result.push_str(&format!(
575 "This requires {} supporting facts:\n",
576 fragment.premise_refs.len()
577 ));
578 for (i, _premise) in fragment.premise_refs.iter().enumerate() {
579 result.push_str(&format!(" {}. [Referenced proof fragment]\n", i + 1));
580 }
581 }
582 }
583 ExplanationStyle::Formal => {
584 result.push_str(&format!(
585 "⊢ {}\n",
586 self.explainer.predicate_to_formal(&fragment.conclusion)
587 ));
588 if let Some(rule) = &fragment.rule_applied {
589 result.push_str(&format!(" [Apply {}]\n", rule.rule_id));
590 }
591 }
592 }
593
594 result
595 }
596
597 pub fn explain_metadata(&self, metadata: &ProofMetadata) -> String {
599 let mut result = String::new();
600 result.push_str("Proof Metadata:\n");
601 if let Some(created_at) = metadata.created_at {
602 result.push_str(&format!(" Created at: {}\n", created_at));
603 }
604 if let Some(author) = &metadata.created_by {
605 result.push_str(&format!(" Author: {}\n", author));
606 }
607 if let Some(complexity) = metadata.complexity {
608 result.push_str(&format!(" Complexity: {} steps\n", complexity));
609 }
610 result.push_str(&format!(" Depth: {}\n", metadata.depth));
611 if !metadata.custom.is_empty() {
612 result.push_str(" Custom fields:\n");
613 for (key, value) in &metadata.custom {
614 result.push_str(&format!(" {}: {}\n", key, value));
615 }
616 }
617 result
618 }
619}
620
621impl Default for FragmentProofExplainer {
622 fn default() -> Self {
623 Self::new()
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630
631 fn create_simple_fact() -> Proof {
632 Proof {
633 goal: Predicate {
634 name: "mortal".to_string(),
635 args: vec![Term::Const(Constant::String("socrates".to_string()))],
636 },
637 rule: Some(ProofRule {
638 head: Predicate {
639 name: "mortal".to_string(),
640 args: vec![Term::Const(Constant::String("socrates".to_string()))],
641 },
642 body: vec![],
643 is_fact: true,
644 }),
645 subproofs: vec![],
646 }
647 }
648
649 fn create_rule_proof() -> Proof {
650 let premise = Proof {
651 goal: Predicate {
652 name: "human".to_string(),
653 args: vec![Term::Const(Constant::String("socrates".to_string()))],
654 },
655 rule: Some(ProofRule {
656 head: Predicate {
657 name: "human".to_string(),
658 args: vec![Term::Const(Constant::String("socrates".to_string()))],
659 },
660 body: vec![],
661 is_fact: true,
662 }),
663 subproofs: vec![],
664 };
665
666 Proof {
667 goal: Predicate {
668 name: "mortal".to_string(),
669 args: vec![Term::Const(Constant::String("socrates".to_string()))],
670 },
671 rule: Some(ProofRule {
672 head: Predicate {
673 name: "mortal".to_string(),
674 args: vec![Term::Var("X".to_string())],
675 },
676 body: vec![Predicate {
677 name: "human".to_string(),
678 args: vec![Term::Var("X".to_string())],
679 }],
680 is_fact: false,
681 }),
682 subproofs: vec![premise],
683 }
684 }
685
686 #[test]
687 fn test_concise_explanation() {
688 let proof = create_simple_fact();
689 let explainer = ProofExplainer::new();
690 let explanation = explainer.explain(&proof, ExplanationStyle::Concise);
691 assert!(explanation.contains("socrates is mortal"));
692 assert!(explanation.contains("given fact"));
693 }
694
695 #[test]
696 fn test_detailed_explanation() {
697 let proof = create_rule_proof();
698 let explainer = ProofExplainer::new();
699 let explanation = explainer.explain(&proof, ExplanationStyle::Detailed);
700 assert!(explanation.contains("socrates"));
701 }
702
703 #[test]
704 fn test_pedagogical_explanation() {
705 let proof = create_rule_proof();
706 let explainer = ProofExplainer::new();
707 let explanation = explainer.explain(&proof, ExplanationStyle::Pedagogical);
708 assert!(explanation.contains("Goal"));
709 assert!(explanation.contains("Strategy"));
710 }
711
712 #[test]
713 fn test_formal_explanation() {
714 let proof = create_simple_fact();
715 let explainer = ProofExplainer::new();
716 let explanation = explainer.explain(&proof, ExplanationStyle::Formal);
717 assert!(explanation.contains("⊢"));
718 assert!(explanation.contains("Axiom"));
719 }
720
721 #[test]
722 fn test_builder_pattern() {
723 let proof = create_simple_fact();
724 let explanation = ProofExplanationBuilder::new(proof)
725 .style(ExplanationStyle::Concise)
726 .natural_language()
727 .build();
728 assert!(explanation.contains("socrates"));
729 }
730
731 #[test]
732 fn test_predicate_naturalization() {
733 let explainer = ProofExplainer::new();
734 let pred = Predicate {
735 name: "parent".to_string(),
736 args: vec![
737 Term::Const(Constant::String("alice".to_string())),
738 Term::Const(Constant::String("bob".to_string())),
739 ],
740 };
741 let natural = explainer.naturalize_predicate(&pred);
742 assert!(natural.contains("alice is a parent of bob"));
743 }
744
745 #[test]
746 fn test_config_presets() {
747 let concise = ExplanationConfig::concise();
748 assert_eq!(concise.style, ExplanationStyle::Concise);
749 assert!(!concise.include_premises);
750
751 let detailed = ExplanationConfig::detailed();
752 assert_eq!(detailed.style, ExplanationStyle::Detailed);
753 assert!(detailed.include_premises);
754
755 let pedagogical = ExplanationConfig::pedagogical();
756 assert_eq!(pedagogical.style, ExplanationStyle::Pedagogical);
757 assert!(pedagogical.include_substitutions);
758 }
759}