1use crate::proof::{Proof, ProofNode, ProofNodeId, ProofStep};
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Verbosity {
12 Minimal,
14 Concise,
16 Detailed,
18 Verbose,
20}
21
22#[derive(Debug, Clone)]
24pub struct ExplainedStep {
25 pub node_id: ProofNodeId,
27 pub explanation: String,
29 pub depth: u32,
31}
32
33impl fmt::Display for ExplainedStep {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 write!(f, "{}: {}", self.node_id, self.explanation)
36 }
37}
38
39pub struct ProofExplainer {
41 verbosity: Verbosity,
42}
43
44impl ProofExplainer {
45 #[must_use]
47 pub fn new() -> Self {
48 Self {
49 verbosity: Verbosity::Concise,
50 }
51 }
52
53 #[must_use]
55 pub fn with_verbosity(verbosity: Verbosity) -> Self {
56 Self { verbosity }
57 }
58
59 #[must_use]
61 pub fn explain_node(&self, proof: &Proof, node: &ProofNode) -> ExplainedStep {
62 let explanation = match &node.step {
63 ProofStep::Axiom { conclusion } => self.explain_axiom(conclusion),
64 ProofStep::Inference {
65 rule,
66 premises,
67 conclusion,
68 args,
69 } => self.explain_inference(proof, rule, premises, conclusion, args),
70 };
71
72 ExplainedStep {
73 node_id: node.id,
74 explanation,
75 depth: node.depth,
76 }
77 }
78
79 fn explain_axiom(&self, conclusion: &str) -> String {
81 match self.verbosity {
82 Verbosity::Minimal => "Axiom".to_string(),
83 Verbosity::Concise => "Assumed as axiom".to_string(),
84 Verbosity::Detailed | Verbosity::Verbose => {
85 format!("Assumed '{}' as an axiom", conclusion)
86 }
87 }
88 }
89
90 fn explain_inference(
92 &self,
93 proof: &Proof,
94 rule: &str,
95 premises: &[ProofNodeId],
96 conclusion: &str,
97 args: &[String],
98 ) -> String {
99 let rule_desc = self.describe_rule(rule);
100
101 match self.verbosity {
102 Verbosity::Minimal => rule_desc.to_string(),
103 Verbosity::Concise => {
104 if premises.is_empty() {
105 format!("Applied {}", rule_desc)
106 } else {
107 format!("Applied {} using {} premise(s)", rule_desc, premises.len())
108 }
109 }
110 Verbosity::Detailed => {
111 if premises.is_empty() {
112 format!("Applied {}", rule_desc)
113 } else {
114 let premise_ids: Vec<String> =
115 premises.iter().map(|id| id.to_string()).collect();
116 format!(
117 "Applied {} using premises [{}]",
118 rule_desc,
119 premise_ids.join(", ")
120 )
121 }
122 }
123 Verbosity::Verbose => {
124 let mut parts = vec![format!("Applied {}", rule_desc)];
125
126 if !premises.is_empty() {
127 let premise_descs: Vec<String> = premises
128 .iter()
129 .filter_map(|&id| proof.get_node(id).map(|n| (id, n)))
130 .map(|(id, n)| match &n.step {
131 ProofStep::Axiom { conclusion } => format!("{} ({})", id, conclusion),
132 ProofStep::Inference { conclusion, .. } => {
133 format!("{} ({})", id, conclusion)
134 }
135 })
136 .collect();
137 parts.push(format!("from [{}]", premise_descs.join(", ")));
138 }
139
140 if !args.is_empty() {
141 parts.push(format!("with arguments [{}]", args.join(", ")));
142 }
143
144 parts.push(format!("to derive '{}'", conclusion));
145 parts.join(" ")
146 }
147 }
148 }
149
150 fn describe_rule<'a>(&self, rule: &'a str) -> &'a str {
152 match rule {
153 "and" => "conjunction introduction",
154 "or" => "disjunction introduction",
155 "not" => "negation introduction",
156 "implies" => "implication introduction",
157 "resolution" => "resolution",
158 "unit_propagation" => "unit propagation",
159 "modus_ponens" => "modus ponens",
160 "transitivity" => "transitivity",
161 "symmetry" => "symmetry",
162 "reflexivity" => "reflexivity",
163 "congruence" => "congruence",
164 "farkas" => "Farkas lemma",
165 "split" => "case split",
166 "instantiate" => "quantifier instantiation",
167 "skolemize" => "skolemization",
168 _ => rule,
169 }
170 }
171
172 #[must_use]
174 pub fn explain_proof(&self, proof: &Proof) -> Vec<ExplainedStep> {
175 proof
176 .nodes()
177 .iter()
178 .map(|node| self.explain_node(proof, node))
179 .collect()
180 }
181
182 #[must_use]
184 pub fn summarize_proof(&self, proof: &Proof) -> String {
185 let explanations = self.explain_proof(proof);
186
187 if explanations.is_empty() {
188 return "Empty proof".to_string();
189 }
190
191 let mut summary = String::new();
192 summary.push_str(&format!("Proof Summary ({} steps):\n", explanations.len()));
193 summary.push_str("─".repeat(60).as_str());
194 summary.push('\n');
195
196 for (i, step) in explanations.iter().enumerate() {
197 let indent = " ".repeat(step.depth as usize);
198 summary.push_str(&format!(
199 "{}{}. {} (depth {})\n",
200 indent,
201 i + 1,
202 step.explanation,
203 step.depth
204 ));
205 }
206
207 summary
208 }
209
210 #[must_use]
212 pub fn critical_path(&self, proof: &Proof) -> Vec<ProofNodeId> {
213 if proof.is_empty() {
214 return Vec::new();
215 }
216
217 let max_depth_node = proof
219 .nodes()
220 .iter()
221 .max_by_key(|n| n.depth)
222 .expect("proof has at least one node");
223
224 let mut path = vec![max_depth_node.id];
226 let mut current = max_depth_node;
227
228 while let ProofStep::Inference { premises, .. } = ¤t.step {
229 if premises.is_empty() {
230 break;
231 }
232
233 let max_premise = premises
235 .iter()
236 .filter_map(|&id| proof.get_node(id))
237 .max_by_key(|n| n.depth);
238
239 if let Some(premise) = max_premise {
240 path.push(premise.id);
241 current = premise;
242 } else {
243 break;
244 }
245 }
246
247 path.reverse();
248 path
249 }
250
251 #[must_use]
253 pub fn analyze_complexity(&self, proof: &Proof) -> ProofComplexity {
254 let total_steps = proof.len();
255 let max_depth = proof.depth();
256
257 let axiom_count = proof
258 .nodes()
259 .iter()
260 .filter(|n| matches!(n.step, ProofStep::Axiom { .. }))
261 .count();
262
263 let inference_count = total_steps - axiom_count;
264
265 let avg_premises = if inference_count > 0 {
266 proof
267 .nodes()
268 .iter()
269 .filter_map(|n| match &n.step {
270 ProofStep::Inference { premises, .. } => Some(premises.len()),
271 _ => None,
272 })
273 .sum::<usize>() as f64
274 / inference_count as f64
275 } else {
276 0.0
277 };
278
279 let branching_factor = if max_depth > 0 {
280 total_steps as f64 / max_depth as f64
281 } else {
282 0.0
283 };
284
285 ProofComplexity {
286 total_steps,
287 max_depth,
288 axiom_count,
289 inference_count,
290 avg_premises,
291 branching_factor,
292 }
293 }
294}
295
296impl Default for ProofExplainer {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302#[derive(Debug, Clone)]
304pub struct ProofComplexity {
305 pub total_steps: usize,
307 pub max_depth: u32,
309 pub axiom_count: usize,
311 pub inference_count: usize,
313 pub avg_premises: f64,
315 pub branching_factor: f64,
317}
318
319impl fmt::Display for ProofComplexity {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 writeln!(f, "Proof Complexity Analysis:")?;
322 writeln!(f, " Total steps: {}", self.total_steps)?;
323 writeln!(f, " Maximum depth: {}", self.max_depth)?;
324 writeln!(f, " Axioms: {}", self.axiom_count)?;
325 writeln!(f, " Inferences: {}", self.inference_count)?;
326 writeln!(f, " Avg premises per inference: {:.2}", self.avg_premises)?;
327 writeln!(f, " Branching factor: {:.2}", self.branching_factor)?;
328 Ok(())
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn test_explain_axiom() {
338 let explainer = ProofExplainer::new();
339 let mut proof = Proof::new();
340 let id = proof.add_axiom("p");
341 let node = proof.get_node(id).expect("test operation should succeed");
342
343 let explained = explainer.explain_node(&proof, node);
344 assert!(explained.explanation.contains("Assumed"));
345 }
346
347 #[test]
348 fn test_explain_inference() {
349 let explainer = ProofExplainer::new();
350 let mut proof = Proof::new();
351 let a1 = proof.add_axiom("p");
352 let a2 = proof.add_axiom("q");
353 let i1 = proof.add_inference("and", vec![a1, a2], "p /\\ q");
354 let node = proof.get_node(i1).expect("test operation should succeed");
355
356 let explained = explainer.explain_node(&proof, node);
357 assert!(explained.explanation.contains("conjunction"));
358 }
359
360 #[test]
361 fn test_verbosity_levels() {
362 let mut proof = Proof::new();
363 let a1 = proof.add_axiom("p");
364 let a2 = proof.add_axiom("q");
365 let i1 = proof.add_inference("and", vec![a1, a2], "p /\\ q");
366 let node = proof.get_node(i1).expect("test operation should succeed");
367
368 let minimal = ProofExplainer::with_verbosity(Verbosity::Minimal);
369 let concise = ProofExplainer::with_verbosity(Verbosity::Concise);
370 let detailed = ProofExplainer::with_verbosity(Verbosity::Detailed);
371 let verbose = ProofExplainer::with_verbosity(Verbosity::Verbose);
372
373 let exp_min = minimal.explain_node(&proof, node);
374 let exp_con = concise.explain_node(&proof, node);
375 let exp_det = detailed.explain_node(&proof, node);
376 let exp_ver = verbose.explain_node(&proof, node);
377
378 assert!(exp_min.explanation.len() < exp_con.explanation.len());
380 assert!(exp_det.explanation.len() > exp_con.explanation.len());
381 assert!(exp_ver.explanation.len() > exp_det.explanation.len());
382 }
383
384 #[test]
385 fn test_explain_proof() {
386 let explainer = ProofExplainer::new();
387 let mut proof = Proof::new();
388 proof.add_axiom("p");
389 proof.add_axiom("q");
390
391 let explanations = explainer.explain_proof(&proof);
392 assert_eq!(explanations.len(), 2);
393 }
394
395 #[test]
396 fn test_summarize_proof() {
397 let explainer = ProofExplainer::new();
398 let mut proof = Proof::new();
399 proof.add_axiom("p");
400 proof.add_axiom("q");
401
402 let summary = explainer.summarize_proof(&proof);
403 assert!(summary.contains("Proof Summary"));
404 assert!(summary.contains("2 steps"));
405 }
406
407 #[test]
408 fn test_critical_path() {
409 let explainer = ProofExplainer::new();
410 let mut proof = Proof::new();
411 let a1 = proof.add_axiom("p");
412 let a2 = proof.add_axiom("q");
413 let i1 = proof.add_inference("and", vec![a1, a2], "p /\\ q");
414 let i2 = proof.add_inference("not", vec![i1], "!(p /\\ q)");
415
416 let path = explainer.critical_path(&proof);
417 assert!(path.len() >= 2);
418 assert_eq!(*path.last().expect("collection should not be empty"), i2);
419 }
420
421 #[test]
422 fn test_analyze_complexity() {
423 let explainer = ProofExplainer::new();
424 let mut proof = Proof::new();
425 let a1 = proof.add_axiom("p");
426 let a2 = proof.add_axiom("q");
427 proof.add_inference("and", vec![a1, a2], "p /\\ q");
428
429 let complexity = explainer.analyze_complexity(&proof);
430 assert_eq!(complexity.total_steps, 3);
431 assert_eq!(complexity.axiom_count, 2);
432 assert_eq!(complexity.inference_count, 1);
433 assert_eq!(complexity.max_depth, 1);
434 }
435
436 #[test]
437 fn test_empty_proof_complexity() {
438 let explainer = ProofExplainer::new();
439 let proof = Proof::new();
440
441 let complexity = explainer.analyze_complexity(&proof);
442 assert_eq!(complexity.total_steps, 0);
443 assert_eq!(complexity.max_depth, 0);
444 }
445
446 #[test]
447 fn test_rule_descriptions() {
448 let explainer = ProofExplainer::new();
449
450 assert_eq!(explainer.describe_rule("and"), "conjunction introduction");
451 assert_eq!(explainer.describe_rule("resolution"), "resolution");
452 assert_eq!(explainer.describe_rule("farkas"), "Farkas lemma");
453 assert_eq!(explainer.describe_rule("unknown"), "unknown");
454 }
455
456 #[test]
457 fn test_complexity_display() {
458 let complexity = ProofComplexity {
459 total_steps: 10,
460 max_depth: 5,
461 axiom_count: 3,
462 inference_count: 7,
463 avg_premises: 2.5,
464 branching_factor: 2.0,
465 };
466
467 let display = complexity.to_string();
468 assert!(display.contains("Total steps: 10"));
469 assert!(display.contains("Maximum depth: 5"));
470 }
471}