1use crate::proof::{Proof, ProofStep};
7use crate::theory::{TheoryProof, TheoryRule};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
12pub struct DetailedProofStats {
13 pub total_steps: usize,
15 pub axioms: usize,
17 pub inferences: usize,
19 pub max_depth: usize,
21 pub avg_leaf_depth: f64,
23 pub total_premises: usize,
25 pub avg_premises: f64,
27 pub unique_conclusions: usize,
29 pub rule_usage: HashMap<String, usize>,
31 pub complexity_score: f64,
33}
34
35impl DetailedProofStats {
36 #[must_use]
38 pub fn compute(proof: &Proof) -> Self {
39 let total_steps = proof.node_count();
40 let max_depth = proof.depth();
41 let leaf_nodes = proof.leaf_nodes();
42 let axioms = leaf_nodes.len();
43 let inferences = total_steps - axioms;
44
45 let mut total_premises = 0;
46 let mut unique_conclusions = std::collections::HashSet::new();
47 let mut rule_usage: HashMap<String, usize> = HashMap::new();
48
49 for node in proof.nodes() {
50 match &node.step {
51 ProofStep::Axiom { conclusion } => {
52 unique_conclusions.insert(conclusion.clone());
53 }
54 ProofStep::Inference {
55 rule,
56 premises,
57 conclusion,
58 ..
59 } => {
60 total_premises += premises.len();
61 unique_conclusions.insert(conclusion.clone());
62 *rule_usage.entry(rule.clone()).or_insert(0) += 1;
63 }
64 }
65 }
66
67 let avg_premises = if inferences > 0 {
68 total_premises as f64 / inferences as f64
69 } else {
70 0.0
71 };
72
73 let mut total_leaf_depth = 0;
75 if let Some(root) = proof.root() {
76 for &leaf in &leaf_nodes {
77 total_leaf_depth += compute_depth_to_node(proof, root, leaf);
78 }
79 }
80 let avg_leaf_depth = if !leaf_nodes.is_empty() {
81 total_leaf_depth as f64 / leaf_nodes.len() as f64
82 } else {
83 0.0
84 };
85
86 let depth_factor = max_depth as f64;
89 let branching_factor = avg_premises;
90 let rule_diversity = rule_usage.len() as f64;
91 let complexity_score = (depth_factor * branching_factor * rule_diversity).sqrt();
92
93 Self {
94 total_steps,
95 axioms,
96 inferences,
97 max_depth: max_depth as usize,
98 avg_leaf_depth,
99 total_premises,
100 avg_premises,
101 unique_conclusions: unique_conclusions.len(),
102 rule_usage,
103 complexity_score,
104 }
105 }
106
107 #[must_use]
109 pub fn compute_theory(proof: &TheoryProof) -> TheoryProofStats {
110 let total_steps = proof.len();
111 let mut axioms = 0;
112 let mut inferences = 0;
113 let mut total_premises = 0;
114 let mut rule_usage: HashMap<String, usize> = HashMap::new();
115 let mut theory_usage: HashMap<String, usize> = HashMap::new();
116
117 for step in proof.steps() {
118 if step.premises.is_empty() {
119 axioms += 1;
120 } else {
121 inferences += 1;
122 total_premises += step.premises.len();
123 }
124
125 let rule_name = format!("{}", step.rule);
126 *rule_usage.entry(rule_name).or_insert(0) += 1;
127
128 let theory = categorize_theory_rule(&step.rule);
130 *theory_usage.entry(theory).or_insert(0) += 1;
131 }
132
133 let avg_premises = if inferences > 0 {
134 total_premises as f64 / inferences as f64
135 } else {
136 0.0
137 };
138
139 TheoryProofStats {
140 total_steps,
141 axioms,
142 inferences,
143 total_premises,
144 avg_premises,
145 rule_usage,
146 theory_usage,
147 }
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct TheoryProofStats {
154 pub total_steps: usize,
156 pub axioms: usize,
158 pub inferences: usize,
160 pub total_premises: usize,
162 pub avg_premises: f64,
164 pub rule_usage: HashMap<String, usize>,
166 pub theory_usage: HashMap<String, usize>,
168}
169
170fn compute_depth_to_node(
172 proof: &Proof,
173 current: crate::proof::ProofNodeId,
174 target: crate::proof::ProofNodeId,
175) -> usize {
176 if current == target {
177 return 0;
178 }
179
180 if let Some(premises) = proof.premises(current) {
181 for &premise in premises {
182 let depth = compute_depth_to_node(proof, premise, target);
183 if depth > 0 || premise == target {
184 return depth + 1;
185 }
186 }
187 }
188
189 0
190}
191
192fn categorize_theory_rule(rule: &TheoryRule) -> String {
194 match rule {
195 TheoryRule::Refl
196 | TheoryRule::Symm
197 | TheoryRule::Trans
198 | TheoryRule::Cong
199 | TheoryRule::FuncEq
200 | TheoryRule::Distinct => "EUF".to_string(),
201
202 TheoryRule::LaGeneric
203 | TheoryRule::LaMult
204 | TheoryRule::LaAdd
205 | TheoryRule::LaTighten
206 | TheoryRule::LaDiv
207 | TheoryRule::LaTotality
208 | TheoryRule::LaDiseq => "Arithmetic".to_string(),
209
210 TheoryRule::BvBlastEq
211 | TheoryRule::BvExtract
212 | TheoryRule::BvConcat
213 | TheoryRule::BvZeroExtend
214 | TheoryRule::BvSignExtend
215 | TheoryRule::BvBitwise
216 | TheoryRule::BvArith
217 | TheoryRule::BvCompare
218 | TheoryRule::BvOverflow => "BitVector".to_string(),
219
220 TheoryRule::ArrReadWrite1
221 | TheoryRule::ArrReadWrite2
222 | TheoryRule::ArrExt
223 | TheoryRule::ArrConst => "Array".to_string(),
224
225 TheoryRule::ForallElim
226 | TheoryRule::ExistsIntro
227 | TheoryRule::Skolemize
228 | TheoryRule::QuantInst
229 | TheoryRule::AlphaEquiv => "Quantifier".to_string(),
230
231 TheoryRule::TheoryConflict | TheoryRule::TheoryProp => "Theory".to_string(),
232
233 TheoryRule::IteElim | TheoryRule::BoolSimp => "Boolean".to_string(),
234
235 TheoryRule::Custom(name) => format!("Custom({})", name),
236 }
237}
238
239#[derive(Debug, Clone)]
241pub struct ProofQuality {
242 pub redundancy: f64,
244 pub efficiency: f64,
246 pub compactness: f64,
248 pub overall_score: f64,
250}
251
252impl ProofQuality {
253 #[must_use]
255 pub fn compute(stats: &DetailedProofStats) -> Self {
256 let redundancy = if stats.total_steps > 0 {
258 1.0 - (stats.unique_conclusions as f64 / stats.total_steps as f64)
259 } else {
260 0.0
261 };
262
263 let efficiency = if stats.avg_premises > 0.0 {
265 1.0 / (1.0 + stats.avg_premises)
266 } else {
267 1.0
268 };
269
270 let compactness = if stats.total_steps > 0 && stats.max_depth > 0 {
272 stats.max_depth as f64 / stats.total_steps as f64
273 } else {
274 1.0
275 };
276
277 let overall_score =
279 ((1.0 - redundancy) * 0.3 + efficiency * 0.4 + compactness * 0.3) * 100.0;
280
281 Self {
282 redundancy,
283 efficiency,
284 compactness,
285 overall_score,
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_detailed_proof_stats() {
296 let mut proof = Proof::new();
297 let p1 = proof.add_axiom("p");
298 let p2 = proof.add_axiom("q");
299 let _p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
300
301 let stats = DetailedProofStats::compute(&proof);
302
303 assert_eq!(stats.total_steps, 3);
304 assert_eq!(stats.axioms, 2);
305 assert_eq!(stats.inferences, 1);
306 assert_eq!(stats.unique_conclusions, 3);
307 assert_eq!(stats.rule_usage.get("and"), Some(&1));
308 }
309
310 #[test]
311 fn test_theory_proof_stats() {
312 let mut proof = TheoryProof::new();
313
314 let s1 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= a b)");
315 let s2 = proof.add_axiom(TheoryRule::Custom("assert".into()), "(= b c)");
316 proof.trans(s1, s2, "a", "c");
317
318 let stats = DetailedProofStats::compute_theory(&proof);
319
320 assert_eq!(stats.total_steps, 3);
321 assert_eq!(stats.axioms, 2);
322 assert_eq!(stats.inferences, 1);
323 assert_eq!(stats.theory_usage.get("EUF"), Some(&1));
324 assert_eq!(stats.theory_usage.get("Custom(assert)"), Some(&2));
325 }
326
327 #[test]
328 fn test_categorize_theory_rule() {
329 assert_eq!(categorize_theory_rule(&TheoryRule::Refl), "EUF");
330 assert_eq!(categorize_theory_rule(&TheoryRule::LaGeneric), "Arithmetic");
331 assert_eq!(categorize_theory_rule(&TheoryRule::BvBlastEq), "BitVector");
332 assert_eq!(categorize_theory_rule(&TheoryRule::ArrReadWrite1), "Array");
333 }
334
335 #[test]
336 fn test_proof_quality() {
337 let mut proof = Proof::new();
338 let p1 = proof.add_axiom("p");
339 let p2 = proof.add_axiom("q");
340 let _p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
341
342 let stats = DetailedProofStats::compute(&proof);
343 let quality = ProofQuality::compute(&stats);
344
345 assert!(quality.redundancy >= 0.0 && quality.redundancy <= 1.0);
346 assert!(quality.efficiency > 0.0);
347 assert!(quality.compactness > 0.0);
348 assert!(quality.overall_score >= 0.0 && quality.overall_score <= 100.0);
349 }
350
351 #[test]
352 fn test_complexity_score() {
353 let mut proof = Proof::new();
354 let p1 = proof.add_axiom("p");
355 let p2 = proof.add_axiom("q");
356 let _p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
357
358 let stats = DetailedProofStats::compute(&proof);
359
360 assert!(stats.complexity_score > 0.0);
361 }
362
363 #[test]
364 fn test_avg_leaf_depth() {
365 let mut proof = Proof::new();
366 let p1 = proof.add_axiom("p");
367 let p2 = proof.add_axiom("q");
368 let p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
369 let _p4 = proof.add_inference("or", vec![p3], "(or (and p q))");
370
371 let stats = DetailedProofStats::compute(&proof);
372
373 assert!(stats.avg_leaf_depth > 0.0);
374 }
375}