ipfrs_tensorlogic/
proof_explanation.rs

1//! Automatic Proof Explanation
2//!
3//! This module provides automatic generation of natural language explanations
4//! for proofs, making them more interpretable and understandable.
5//!
6//! ## Features
7//!
8//! - Natural language proof explanations
9//! - Multiple explanation styles (concise, detailed, pedagogical)
10//! - Template-based explanation generation
11//! - Custom explanation templates
12//! - Proof step highlighting
13//! - Interactive explanation navigation
14//!
15//! ## Examples
16//!
17//! ```
18//! use ipfrs_tensorlogic::{ProofExplainer, ExplanationStyle, Proof, Predicate, Term, Constant};
19//!
20//! // Create a simple proof
21//! let goal = Predicate {
22//!     name: "mortal".to_string(),
23//!     args: vec![Term::Const(Constant::String("socrates".to_string()))],
24//! };
25//! let proof = Proof {
26//!     goal,
27//!     rule: None,
28//!     subproofs: vec![],
29//! };
30//!
31//! let explainer = ProofExplainer::new();
32//! let explanation = explainer.explain(&proof, ExplanationStyle::Detailed);
33//! println!("{}", explanation);
34//! ```
35
36use 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/// Explanation style for proof explanations
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ExplanationStyle {
46    /// Concise one-liner explanations
47    Concise,
48    /// Detailed step-by-step explanations
49    Detailed,
50    /// Pedagogical explanations with reasoning context
51    Pedagogical,
52    /// Formal logical notation
53    Formal,
54}
55
56/// Proof explanation configuration
57#[derive(Debug, Clone)]
58pub struct ExplanationConfig {
59    /// Explanation style
60    pub style: ExplanationStyle,
61    /// Include premise details
62    pub include_premises: bool,
63    /// Include substitution details
64    pub include_substitutions: bool,
65    /// Maximum depth for nested explanations
66    pub max_depth: usize,
67    /// Use natural language for predicates
68    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    /// Create a concise explanation config
85    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    /// Create a detailed explanation config
96    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    /// Create a pedagogical explanation config
107    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    /// Create a formal explanation config
118    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
129/// Natural language proof explainer
130pub struct ProofExplainer {
131    config: ExplanationConfig,
132}
133
134impl ProofExplainer {
135    /// Create a new proof explainer with default config
136    pub fn new() -> Self {
137        Self {
138            config: ExplanationConfig::default(),
139        }
140    }
141
142    /// Create a new proof explainer with custom config
143    pub fn with_config(config: ExplanationConfig) -> Self {
144        Self { config }
145    }
146
147    /// Generate explanation for a proof
148    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    /// Generate explanation with custom config
155    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    /// Generate concise explanation
174    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    /// Generate detailed explanation
190    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    /// Generate pedagogical explanation
233    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    /// Generate formal explanation
308    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    /// Convert predicate to natural language string
338    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    /// Convert predicate to formal notation
347    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    /// Convert predicate to natural language
364    pub fn naturalize_predicate(&self, pred: &Predicate) -> String {
365        // Try to convert common predicates to natural language
366        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                // Default to formal notation
410                self.predicate_to_formal(pred)
411            }
412        }
413    }
414
415    /// Convert term to string
416    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    /// Convert constant to string
439    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
455/// Proof explanation builder for fluent API
456pub struct ProofExplanationBuilder {
457    proof: Proof,
458    config: ExplanationConfig,
459}
460
461impl ProofExplanationBuilder {
462    /// Create a new builder for a proof
463    pub fn new(proof: Proof) -> Self {
464        Self {
465            proof,
466            config: ExplanationConfig::default(),
467        }
468    }
469
470    /// Set explanation style
471    pub fn style(mut self, style: ExplanationStyle) -> Self {
472        self.config.style = style;
473        self
474    }
475
476    /// Include premise details
477    pub fn with_premises(mut self) -> Self {
478        self.config.include_premises = true;
479        self
480    }
481
482    /// Include substitution details
483    pub fn with_substitutions(mut self) -> Self {
484        self.config.include_substitutions = true;
485        self
486    }
487
488    /// Set maximum depth
489    pub fn max_depth(mut self, depth: usize) -> Self {
490        self.config.max_depth = depth;
491        self
492    }
493
494    /// Use natural language
495    pub fn natural_language(mut self) -> Self {
496        self.config.naturalize_predicates = true;
497        self
498    }
499
500    /// Use formal notation
501    pub fn formal_notation(mut self) -> Self {
502        self.config.naturalize_predicates = false;
503        self
504    }
505
506    /// Build the explanation
507    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
513/// Fragment-based proof explainer for content-addressed proofs
514pub struct FragmentProofExplainer {
515    explainer: ProofExplainer,
516}
517
518impl FragmentProofExplainer {
519    /// Create a new fragment proof explainer
520    pub fn new() -> Self {
521        Self {
522            explainer: ProofExplainer::new(),
523        }
524    }
525
526    /// Explain a proof fragment
527    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    /// Explain proof metadata
598    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}