1use crate::proof::Proof;
9
10#[derive(Debug, Clone)]
12pub struct SimplificationConfig {
13 pub apply_demorgan: bool,
15 pub simplify_double_negation: bool,
17 pub remove_identities: bool,
19 pub combine_inferences: bool,
21 pub simplify_tautologies: bool,
23 pub max_passes: usize,
25}
26
27impl Default for SimplificationConfig {
28 fn default() -> Self {
29 Self {
30 apply_demorgan: true,
31 simplify_double_negation: true,
32 remove_identities: true,
33 combine_inferences: true,
34 simplify_tautologies: true,
35 max_passes: 5,
36 }
37 }
38}
39
40impl SimplificationConfig {
41 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn without_demorgan(mut self) -> Self {
48 self.apply_demorgan = false;
49 self
50 }
51
52 pub fn with_max_passes(mut self, passes: usize) -> Self {
54 self.max_passes = passes;
55 self
56 }
57}
58
59#[derive(Debug, Clone, Default)]
61pub struct SimplificationStats {
62 pub passes: usize,
64 pub double_negations: usize,
66 pub demorgan_applications: usize,
68 pub identities_removed: usize,
70 pub inferences_combined: usize,
72 pub tautologies_simplified: usize,
74 pub nodes_before: usize,
76 pub nodes_after: usize,
78}
79
80impl SimplificationStats {
81 pub fn total_simplifications(&self) -> usize {
83 self.double_negations
84 + self.demorgan_applications
85 + self.identities_removed
86 + self.inferences_combined
87 + self.tautologies_simplified
88 }
89
90 pub fn reduction_ratio(&self) -> f64 {
92 if self.nodes_before == 0 {
93 0.0
94 } else {
95 1.0 - (self.nodes_after as f64 / self.nodes_before as f64)
96 }
97 }
98}
99
100pub struct ProofSimplifier {
102 config: SimplificationConfig,
103}
104
105impl ProofSimplifier {
106 pub fn new() -> Self {
108 Self {
109 config: SimplificationConfig::default(),
110 }
111 }
112
113 pub fn with_config(config: SimplificationConfig) -> Self {
115 Self { config }
116 }
117
118 pub fn simplify(&self, proof: &mut Proof) -> SimplificationStats {
120 let mut stats = SimplificationStats {
121 nodes_before: proof.node_count(),
122 ..Default::default()
123 };
124
125 for pass in 0..self.config.max_passes {
126 let mut changed = false;
127
128 if self.config.simplify_double_negation {
129 let count = self.simplify_double_negations(proof);
130 stats.double_negations += count;
131 changed |= count > 0;
132 }
133
134 if self.config.apply_demorgan {
135 let count = self.apply_demorgan_laws(proof);
136 stats.demorgan_applications += count;
137 changed |= count > 0;
138 }
139
140 if self.config.remove_identities {
141 let count = self.remove_identity_operations(proof);
142 stats.identities_removed += count;
143 changed |= count > 0;
144 }
145
146 if self.config.simplify_tautologies {
147 let count = self.simplify_tautology_steps(proof);
148 stats.tautologies_simplified += count;
149 changed |= count > 0;
150 }
151
152 if self.config.combine_inferences {
153 let count = self.combine_inference_chains(proof);
154 stats.inferences_combined += count;
155 changed |= count > 0;
156 }
157
158 stats.passes = pass + 1;
159
160 if !changed {
161 break;
162 }
163 }
164
165 stats.nodes_after = proof.node_count();
166 stats
167 }
168
169 fn simplify_double_negations(&self, proof: &mut Proof) -> usize {
171 let mut count = 0;
172 let node_ids: Vec<_> = proof.nodes().iter().map(|node| node.id).collect();
173
174 for id in node_ids {
175 if let Some(node) = proof.get_node(id) {
176 let conclusion = node.conclusion().to_string();
177 if let Some(simplified) = self.simplify_conclusion_double_neg(&conclusion)
178 && simplified != conclusion
179 {
180 if proof.update_conclusion(id, simplified) {
182 count += 1;
183 }
184 }
185 }
186 }
187
188 count
189 }
190
191 fn apply_demorgan_laws(&self, proof: &mut Proof) -> usize {
193 let mut count = 0;
194 let node_ids: Vec<_> = proof.nodes().iter().map(|node| node.id).collect();
195
196 for id in node_ids {
197 if let Some(node) = proof.get_node(id) {
198 let conclusion = node.conclusion().to_string();
199 if let Some(simplified) = self.apply_demorgan_to_conclusion(&conclusion)
200 && simplified != conclusion
201 && proof.update_conclusion(id, simplified)
202 {
203 count += 1;
204 }
205 }
206 }
207
208 count
209 }
210
211 fn remove_identity_operations(&self, proof: &mut Proof) -> usize {
213 let mut count = 0;
214 let node_ids: Vec<_> = proof.nodes().iter().map(|node| node.id).collect();
215
216 for id in node_ids {
217 if let Some(node) = proof.get_node(id) {
218 let conclusion = node.conclusion().to_string();
219 if let Some(simplified) = self.remove_identities_from_conclusion(&conclusion)
220 && simplified != conclusion
221 && proof.update_conclusion(id, simplified)
222 {
223 count += 1;
224 }
225 }
226 }
227
228 count
229 }
230
231 fn simplify_tautology_steps(&self, proof: &mut Proof) -> usize {
233 let mut count = 0;
234 let node_ids: Vec<_> = proof.nodes().iter().map(|node| node.id).collect();
235
236 for id in node_ids {
237 if let Some(node) = proof.get_node(id) {
238 let conclusion = node.conclusion().to_string();
239 if self.is_tautology(&conclusion) && proof.update_conclusion(id, "true") {
240 count += 1;
241 }
242 }
243 }
244
245 count
246 }
247
248 fn combine_inference_chains(&self, _proof: &mut Proof) -> usize {
250 0
253 }
254
255 fn simplify_conclusion_double_neg(&self, conclusion: &str) -> Option<String> {
258 let s = conclusion.trim();
259
260 if s.starts_with("(not (not ") && s.ends_with("))") {
262 let inner = &s[10..s.len() - 2];
263 return Some(inner.trim().to_string());
264 }
265
266 if s.starts_with("¬") {
268 let chars: Vec<char> = s.chars().collect();
269 if chars.len() >= 3 && chars[0] == '¬' && chars[1] == '¬' {
270 let result: String = chars[2..].iter().collect();
271 return Some(result.trim().to_string());
272 }
273 }
274
275 None
276 }
277
278 fn apply_demorgan_to_conclusion(&self, conclusion: &str) -> Option<String> {
279 let s = conclusion.trim();
280
281 if s.starts_with("(not (and ") && s.ends_with("))") {
283 let inner_content = &s[10..s.len() - 2];
284 if let Some(inner) = self.extract_binary_args(&format!("{})", inner_content)) {
285 return Some(format!("(or (not {}) (not {}))", inner.0, inner.1));
286 }
287 }
288
289 if s.starts_with("(not (or ") && s.ends_with("))") {
291 let inner_content = &s[9..s.len() - 2];
292 if let Some(inner) = self.extract_binary_args(&format!("{})", inner_content)) {
293 return Some(format!("(and (not {}) (not {}))", inner.0, inner.1));
294 }
295 }
296
297 None
298 }
299
300 fn remove_identities_from_conclusion(&self, conclusion: &str) -> Option<String> {
301 let s = conclusion.trim();
302
303 if (s.contains(" true)") || s.contains(" true ))"))
305 && s.starts_with("(and ")
306 && let Some((left, right)) = self.extract_binary_args(&s[5..])
307 {
308 if right.trim() == "true" {
309 return Some(left.to_string());
310 }
311 if left.trim() == "true" {
312 return Some(right.to_string());
313 }
314 }
315
316 if (s.contains(" false)") || s.contains(" false ))"))
318 && s.starts_with("(or ")
319 && let Some((left, right)) = self.extract_binary_args(&s[4..])
320 {
321 if right.trim() == "false" {
322 return Some(left.to_string());
323 }
324 if left.trim() == "false" {
325 return Some(right.to_string());
326 }
327 }
328
329 None
330 }
331
332 fn is_tautology(&self, conclusion: &str) -> bool {
333 let s = conclusion.trim();
334
335 if let Some(stripped) = s.strip_prefix("(or ")
337 && let Some((left, right)) = self.extract_binary_args(stripped)
338 {
339 if right.trim() == format!("(not {})", left.trim()) {
341 return true;
342 }
343 if left.trim() == format!("(not {})", right.trim()) {
344 return true;
345 }
346 }
347
348 false
349 }
350
351 fn extract_binary_args(&self, s: &str) -> Option<(String, String)> {
352 let trimmed = s.trim();
355 if !trimmed.ends_with(')') {
356 return None;
357 }
358
359 let content = &trimmed[..trimmed.len() - 1];
360 let mut depth = 0;
361 let mut split_pos = None;
362
363 for (i, ch) in content.chars().enumerate() {
364 match ch {
365 '(' => depth += 1,
366 ')' => depth -= 1,
367 ' ' if depth == 0 => {
368 split_pos = Some(i);
369 break;
370 }
371 _ => {}
372 }
373 }
374
375 if let Some(pos) = split_pos {
376 let left = content[..pos].trim().to_string();
377 let right = content[pos + 1..].trim().to_string();
378 Some((left, right))
379 } else {
380 None
381 }
382 }
383}
384
385impl Default for ProofSimplifier {
386 fn default() -> Self {
387 Self::new()
388 }
389}
390
391pub fn simplify_proof(proof: &mut Proof) -> SimplificationStats {
393 ProofSimplifier::new().simplify(proof)
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399 use crate::proof::ProofNodeId;
400
401 #[test]
402 fn test_simplification_config_default() {
403 let config = SimplificationConfig::default();
404 assert!(config.apply_demorgan);
405 assert!(config.simplify_double_negation);
406 assert_eq!(config.max_passes, 5);
407 }
408
409 #[test]
410 fn test_simplification_config_builder() {
411 let config = SimplificationConfig::new()
412 .without_demorgan()
413 .with_max_passes(3);
414 assert!(!config.apply_demorgan);
415 assert_eq!(config.max_passes, 3);
416 }
417
418 #[test]
419 fn test_simplification_stats_total() {
420 let stats = SimplificationStats {
421 double_negations: 2,
422 demorgan_applications: 3,
423 identities_removed: 1,
424 ..Default::default()
425 };
426 assert_eq!(stats.total_simplifications(), 6);
427 }
428
429 #[test]
430 fn test_simplification_stats_reduction_ratio() {
431 let stats = SimplificationStats {
432 nodes_before: 100,
433 nodes_after: 80,
434 ..Default::default()
435 };
436 assert!((stats.reduction_ratio() - 0.2).abs() < 1e-6);
437 }
438
439 #[test]
440 fn test_simplify_double_negation_sexp() {
441 let simplifier = ProofSimplifier::new();
442 let conclusion = "(not (not p))";
443 let result = simplifier.simplify_conclusion_double_neg(conclusion);
444 assert_eq!(result, Some("p".to_string()));
445 }
446
447 #[test]
448 fn test_simplify_double_negation_unicode() {
449 let simplifier = ProofSimplifier::new();
450 let conclusion = "¬¬p";
451 let result = simplifier.simplify_conclusion_double_neg(conclusion);
452 assert_eq!(result, Some("p".to_string()));
453 }
454
455 #[test]
456 fn test_apply_demorgan_not_and() {
457 let simplifier = ProofSimplifier::new();
458 let conclusion = "(not (and p q))";
459 let result = simplifier.apply_demorgan_to_conclusion(conclusion);
460 assert_eq!(result, Some("(or (not p) (not q))".to_string()));
461 }
462
463 #[test]
464 fn test_apply_demorgan_not_or() {
465 let simplifier = ProofSimplifier::new();
466 let conclusion = "(not (or p q))";
467 let result = simplifier.apply_demorgan_to_conclusion(conclusion);
468 assert_eq!(result, Some("(and (not p) (not q))".to_string()));
469 }
470
471 #[test]
472 fn test_remove_identity_and_true() {
473 let simplifier = ProofSimplifier::new();
474 let conclusion = "(and p true)";
475 let result = simplifier.remove_identities_from_conclusion(conclusion);
476 assert_eq!(result, Some("p".to_string()));
477 }
478
479 #[test]
480 fn test_remove_identity_or_false() {
481 let simplifier = ProofSimplifier::new();
482 let conclusion = "(or p false)";
483 let result = simplifier.remove_identities_from_conclusion(conclusion);
484 assert_eq!(result, Some("p".to_string()));
485 }
486
487 #[test]
488 fn test_is_tautology_or_not() {
489 let simplifier = ProofSimplifier::new();
490 assert!(simplifier.is_tautology("(or p (not p))"));
491 assert!(simplifier.is_tautology("(or (not p) p)"));
492 assert!(!simplifier.is_tautology("(or p q)"));
493 }
494
495 #[test]
496 fn test_extract_binary_args_simple() {
497 let simplifier = ProofSimplifier::new();
498 let result = simplifier.extract_binary_args("p q)");
499 assert_eq!(result, Some(("p".to_string(), "q".to_string())));
500 }
501
502 #[test]
503 fn test_extract_binary_args_nested() {
504 let simplifier = ProofSimplifier::new();
505 let result = simplifier.extract_binary_args("(and a b) q)");
506 assert_eq!(result, Some(("(and a b)".to_string(), "q".to_string())));
507 }
508
509 #[test]
510 fn test_simplify_proof_empty() {
511 let mut proof = Proof::new();
512 let stats = simplify_proof(&mut proof);
513 assert_eq!(stats.total_simplifications(), 0);
514 assert_eq!(stats.nodes_before, 0);
515 assert_eq!(stats.nodes_after, 0);
516 }
517
518 #[test]
519 fn test_simplify_proof_double_negation() {
520 let mut proof = Proof::new();
521 proof.add_axiom("(not (not p))".to_string());
522
523 let stats = simplify_proof(&mut proof);
524 assert!(stats.double_negations > 0);
525
526 let node = proof
528 .get_node(ProofNodeId(0))
529 .expect("test operation should succeed");
530 assert_eq!(node.conclusion(), "p");
531 }
532
533 #[test]
534 fn test_simplify_proof_identity() {
535 let mut proof = Proof::new();
536 proof.add_axiom("(and p true)".to_string());
537
538 let stats = simplify_proof(&mut proof);
539 assert!(stats.identities_removed > 0);
540
541 let node = proof
542 .get_node(ProofNodeId(0))
543 .expect("test operation should succeed");
544 assert_eq!(node.conclusion(), "p");
545 }
546
547 #[test]
548 fn test_simplify_proof_tautology() {
549 let mut proof = Proof::new();
550 proof.add_axiom("(or p (not p))".to_string());
551
552 let stats = simplify_proof(&mut proof);
553 assert!(stats.tautologies_simplified > 0);
554
555 let node = proof
556 .get_node(ProofNodeId(0))
557 .expect("test operation should succeed");
558 assert_eq!(node.conclusion(), "true");
559 }
560
561 #[test]
562 fn test_simplify_proof_multiple_passes() {
563 let mut proof = Proof::new();
564 proof.add_axiom("(not (not (and p true)))".to_string());
567
568 let config = SimplificationConfig::new().with_max_passes(3);
569 let simplifier = ProofSimplifier::with_config(config);
570 let stats = simplifier.simplify(&mut proof);
571
572 assert!(stats.passes >= 2);
573 }
576
577 #[test]
578 fn test_simplifier_with_custom_config() {
579 let config = SimplificationConfig::new().without_demorgan();
580 let simplifier = ProofSimplifier::with_config(config);
581
582 let mut proof = Proof::new();
583 proof.add_axiom("(not (and p q))".to_string());
584
585 let stats = simplifier.simplify(&mut proof);
586 assert_eq!(stats.demorgan_applications, 0);
588 }
589}