1use crate::metadata::ProofMetadata;
4use rustc_hash::FxHashMap;
5use smallvec::SmallVec;
6use std::fmt;
7use std::io::{self, Write};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct ProofNodeId(pub u32);
13
14impl fmt::Display for ProofNodeId {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 write!(f, "p{}", self.0)
17 }
18}
19
20#[derive(Debug, Clone)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct Proof {
27 nodes: Vec<ProofNode>,
29 roots: Vec<ProofNodeId>,
31 #[cfg_attr(feature = "serde", serde(skip))]
33 term_cache: FxHashMap<String, ProofNodeId>,
34 metadata: FxHashMap<ProofNodeId, ProofMetadata>,
36 next_id: u32,
38}
39
40#[derive(Debug, Clone)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43pub struct ProofNode {
44 pub id: ProofNodeId,
46 pub step: ProofStep,
48 pub dependents: SmallVec<[ProofNodeId; 2]>,
50 pub depth: u32,
52}
53
54#[derive(Debug, Clone)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub enum ProofStep {
58 Axiom {
60 conclusion: String,
62 },
63 Inference {
65 rule: String,
67 premises: SmallVec<[ProofNodeId; 4]>,
69 conclusion: String,
71 args: SmallVec<[String; 2]>,
73 },
74}
75
76impl ProofNode {
77 pub fn conclusion(&self) -> &str {
79 match &self.step {
80 ProofStep::Axiom { conclusion } => conclusion,
81 ProofStep::Inference { conclusion, .. } => conclusion,
82 }
83 }
84}
85
86impl Proof {
87 #[must_use]
89 pub fn new() -> Self {
90 Self {
91 nodes: Vec::new(),
92 roots: Vec::new(),
93 term_cache: FxHashMap::default(),
94 metadata: FxHashMap::default(),
95 next_id: 0,
96 }
97 }
98
99 fn alloc_id(&mut self) -> ProofNodeId {
101 let id = ProofNodeId(self.next_id);
102 self.next_id += 1;
103 id
104 }
105
106 pub fn add_axiom(&mut self, conclusion: impl Into<String>) -> ProofNodeId {
108 let conclusion = conclusion.into();
109
110 if let Some(&existing_id) = self.term_cache.get(&conclusion) {
112 return existing_id;
113 }
114
115 let id = self.alloc_id();
116 let node = ProofNode {
117 id,
118 step: ProofStep::Axiom {
119 conclusion: conclusion.clone(),
120 },
121 dependents: SmallVec::new(),
122 depth: 0,
123 };
124
125 self.nodes.push(node);
126 self.term_cache.insert(conclusion, id);
127 self.roots.push(id);
128 id
129 }
130
131 pub fn add_inference(
133 &mut self,
134 rule: impl Into<String>,
135 premises: Vec<ProofNodeId>,
136 conclusion: impl Into<String>,
137 ) -> ProofNodeId {
138 self.add_inference_with_args(rule, premises, Vec::new(), conclusion)
139 }
140
141 pub fn add_inference_with_args(
143 &mut self,
144 rule: impl Into<String>,
145 premises: Vec<ProofNodeId>,
146 args: Vec<String>,
147 conclusion: impl Into<String>,
148 ) -> ProofNodeId {
149 let conclusion = conclusion.into();
150 let rule = rule.into();
151
152 if let Some(&existing_id) = self.term_cache.get(&conclusion) {
154 return existing_id;
155 }
156
157 let id = self.alloc_id();
158
159 let depth = premises
161 .iter()
162 .filter_map(|p| self.get_node(*p))
163 .map(|n| n.depth)
164 .max()
165 .unwrap_or(0)
166 + 1;
167
168 let node = ProofNode {
169 id,
170 step: ProofStep::Inference {
171 rule,
172 premises: SmallVec::from_vec(premises.clone()),
173 conclusion: conclusion.clone(),
174 args: SmallVec::from_vec(args),
175 },
176 dependents: SmallVec::new(),
177 depth,
178 };
179
180 for premise_id in &premises {
182 if let Some(premise_node) = self.get_node_mut(*premise_id) {
183 premise_node.dependents.push(id);
184 }
185 }
186
187 self.nodes.push(node);
188 self.term_cache.insert(conclusion, id);
189 self.roots.push(id);
190 id
191 }
192
193 #[must_use]
195 pub fn get_node(&self, id: ProofNodeId) -> Option<&ProofNode> {
196 self.nodes.get(id.0 as usize)
197 }
198
199 fn get_node_mut(&mut self, id: ProofNodeId) -> Option<&mut ProofNode> {
201 self.nodes.get_mut(id.0 as usize)
202 }
203
204 pub fn update_conclusion(
209 &mut self,
210 id: ProofNodeId,
211 new_conclusion: impl Into<String>,
212 ) -> bool {
213 if let Some(node) = self.get_node_mut(id) {
214 let new_conclusion = new_conclusion.into();
215 match &mut node.step {
216 ProofStep::Axiom { conclusion } => *conclusion = new_conclusion,
217 ProofStep::Inference { conclusion, .. } => *conclusion = new_conclusion,
218 }
219 if let Some(old_term) = self
221 .term_cache
222 .iter()
223 .find_map(|(k, v)| if *v == id { Some(k.clone()) } else { None })
224 {
225 self.term_cache.remove(&old_term);
226 }
227 true
228 } else {
229 false
230 }
231 }
232
233 #[must_use]
235 pub fn nodes(&self) -> &[ProofNode] {
236 &self.nodes
237 }
238
239 #[must_use]
241 pub fn roots(&self) -> &[ProofNodeId] {
242 &self.roots
243 }
244
245 #[must_use]
247 pub fn root(&self) -> Option<ProofNodeId> {
248 self.roots.last().copied()
249 }
250
251 #[must_use]
253 pub fn root_node(&self) -> Option<&ProofNode> {
254 self.root().and_then(|id| self.get_node(id))
255 }
256
257 #[must_use]
259 pub fn len(&self) -> usize {
260 self.nodes.len()
261 }
262
263 #[must_use]
265 pub fn depth(&self) -> u32 {
266 self.stats().max_depth
267 }
268
269 #[must_use]
271 pub fn node_count(&self) -> usize {
272 self.len()
273 }
274
275 #[must_use]
277 pub fn leaf_nodes(&self) -> Vec<ProofNodeId> {
278 self.nodes
279 .iter()
280 .filter(|node| matches!(node.step, ProofStep::Axiom { .. }))
281 .map(|node| node.id)
282 .collect()
283 }
284
285 #[must_use]
287 pub fn premises(&self, node_id: ProofNodeId) -> Option<&[ProofNodeId]> {
288 self.get_node(node_id).and_then(|node| {
289 if let ProofStep::Inference { premises, .. } = &node.step {
290 Some(premises.as_slice())
291 } else {
292 None
293 }
294 })
295 }
296
297 #[must_use]
299 pub fn is_empty(&self) -> bool {
300 self.nodes.is_empty()
301 }
302
303 pub fn clear(&mut self) {
305 self.nodes.clear();
306 self.roots.clear();
307 self.term_cache.clear();
308 self.metadata.clear();
309 self.next_id = 0;
310 }
311
312 #[must_use]
314 pub fn stats(&self) -> ProofStats {
315 let mut stats = ProofStats {
316 total_nodes: self.nodes.len(),
317 axiom_count: 0,
318 inference_count: 0,
319 max_depth: 0,
320 avg_depth: 0.0,
321 root_count: self.roots.len(),
322 max_premises: 0,
323 avg_premises: 0.0,
324 leaf_count: 0,
325 };
326
327 let mut total_depth = 0u64;
328 let mut total_premises = 0usize;
329
330 for node in &self.nodes {
331 match &node.step {
332 ProofStep::Axiom { .. } => stats.axiom_count += 1,
333 ProofStep::Inference { premises, .. } => {
334 stats.inference_count += 1;
335 let premise_count = premises.len();
336 stats.max_premises = stats.max_premises.max(premise_count);
337 total_premises += premise_count;
338 }
339 }
340 stats.max_depth = stats.max_depth.max(node.depth);
341 total_depth += u64::from(node.depth);
342
343 if node.dependents.is_empty() {
345 stats.leaf_count += 1;
346 }
347 }
348
349 if !self.nodes.is_empty() {
350 stats.avg_depth = total_depth as f64 / self.nodes.len() as f64;
351 }
352
353 if stats.inference_count > 0 {
354 stats.avg_premises = total_premises as f64 / stats.inference_count as f64;
355 }
356
357 stats
358 }
359
360 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
362 writeln!(writer, "; Proof with {} nodes", self.len())?;
363 writeln!(writer)?;
364
365 for node in &self.nodes {
366 match &node.step {
367 ProofStep::Axiom { conclusion } => {
368 writeln!(writer, "{}: (axiom {})", node.id, conclusion)?;
369 }
370 ProofStep::Inference {
371 rule,
372 premises,
373 conclusion,
374 args,
375 } => {
376 write!(writer, "{}: ({} [", node.id, rule)?;
377 for (i, p) in premises.iter().enumerate() {
378 if i > 0 {
379 write!(writer, ", ")?;
380 }
381 write!(writer, "{}", p)?;
382 }
383 write!(writer, "]")?;
384 if !args.is_empty() {
385 write!(writer, " :args [")?;
386 for (i, arg) in args.iter().enumerate() {
387 if i > 0 {
388 write!(writer, ", ")?;
389 }
390 write!(writer, "{}", arg)?;
391 }
392 write!(writer, "]")?;
393 }
394 writeln!(writer, " => {})", conclusion)?;
395 }
396 }
397 }
398
399 Ok(())
400 }
401
402 #[must_use]
404 pub fn to_string_repr(&self) -> String {
405 let mut buf = Vec::new();
406 self.write(&mut buf)
407 .expect("writing to Vec should not fail");
408 String::from_utf8(buf).expect("proof output is UTF-8")
409 }
410
411 pub fn add_axioms(
413 &mut self,
414 conclusions: impl IntoIterator<Item = impl Into<String>>,
415 ) -> Vec<ProofNodeId> {
416 conclusions.into_iter().map(|c| self.add_axiom(c)).collect()
417 }
418
419 #[must_use]
421 pub fn find_nodes_by_rule(&self, rule: &str) -> Vec<ProofNodeId> {
422 self.nodes
423 .iter()
424 .filter(|node| matches!(&node.step, ProofStep::Inference { rule: r, .. } if r == rule))
425 .map(|node| node.id)
426 .collect()
427 }
428
429 #[must_use]
431 pub fn find_nodes_by_conclusion(&self, conclusion: &str) -> Vec<ProofNodeId> {
432 self.nodes
433 .iter()
434 .filter(|node| match &node.step {
435 ProofStep::Axiom { conclusion: c } => c == conclusion,
436 ProofStep::Inference { conclusion: c, .. } => c == conclusion,
437 })
438 .map(|node| node.id)
439 .collect()
440 }
441
442 #[must_use]
444 pub fn get_children(&self, node_id: ProofNodeId) -> Vec<ProofNodeId> {
445 self.premises(node_id)
446 .map(|p| p.to_vec())
447 .unwrap_or_default()
448 }
449
450 #[must_use]
452 pub fn get_parents(&self, node_id: ProofNodeId) -> Vec<ProofNodeId> {
453 self.get_node(node_id)
454 .map(|node| node.dependents.to_vec())
455 .unwrap_or_default()
456 }
457
458 #[must_use]
460 pub fn is_ancestor(&self, ancestor: ProofNodeId, descendant: ProofNodeId) -> bool {
461 if ancestor == descendant {
462 return false;
463 }
464
465 let mut visited = rustc_hash::FxHashSet::default();
466 let mut queue = std::collections::VecDeque::new();
467 queue.push_back(descendant);
468
469 while let Some(current) = queue.pop_front() {
470 if !visited.insert(current) {
471 continue;
472 }
473
474 if current == ancestor {
475 return true;
476 }
477
478 if let Some(premises) = self.premises(current) {
479 for &premise in premises {
480 queue.push_back(premise);
481 }
482 }
483 }
484
485 false
486 }
487
488 #[must_use]
490 pub fn get_all_ancestors(&self, node_id: ProofNodeId) -> Vec<ProofNodeId> {
491 let mut ancestors = Vec::new();
492 let mut visited = rustc_hash::FxHashSet::default();
493 let mut queue = std::collections::VecDeque::new();
494 queue.push_back(node_id);
495
496 while let Some(current) = queue.pop_front() {
497 if !visited.insert(current) {
498 continue;
499 }
500
501 if current != node_id {
502 ancestors.push(current);
503 }
504
505 if let Some(premises) = self.premises(current) {
506 for &premise in premises {
507 queue.push_back(premise);
508 }
509 }
510 }
511
512 ancestors
513 }
514
515 #[must_use]
517 pub fn count_rule(&self, rule: &str) -> usize {
518 self.nodes
519 .iter()
520 .filter(|node| matches!(&node.step, ProofStep::Inference { rule: r, .. } if r == rule))
521 .count()
522 }
523
524 #[cfg(feature = "serde")]
526 pub fn rebuild_cache(&mut self) {
527 self.term_cache.clear();
528 for node in &self.nodes {
529 let conclusion = match &node.step {
530 ProofStep::Axiom { conclusion } => conclusion,
531 ProofStep::Inference { conclusion, .. } => conclusion,
532 };
533 self.term_cache.insert(conclusion.clone(), node.id);
534 }
535 }
536
537 pub fn set_metadata(&mut self, node_id: ProofNodeId, metadata: ProofMetadata) {
541 self.metadata.insert(node_id, metadata);
542 }
543
544 #[must_use]
546 pub fn get_metadata(&self, node_id: ProofNodeId) -> Option<&ProofMetadata> {
547 self.metadata.get(&node_id)
548 }
549
550 pub fn get_metadata_mut(&mut self, node_id: ProofNodeId) -> Option<&mut ProofMetadata> {
552 self.metadata.get_mut(&node_id)
553 }
554
555 pub fn remove_metadata(&mut self, node_id: ProofNodeId) -> Option<ProofMetadata> {
557 self.metadata.remove(&node_id)
558 }
559
560 #[must_use]
562 pub fn has_metadata(&self, node_id: ProofNodeId) -> bool {
563 self.metadata.contains_key(&node_id)
564 }
565
566 #[must_use]
568 pub fn nodes_with_tag(&self, tag: &str) -> Vec<ProofNodeId> {
569 self.metadata
570 .iter()
571 .filter(|(_, meta)| meta.has_tag(tag))
572 .map(|(id, _)| *id)
573 .collect()
574 }
575
576 #[must_use]
578 pub fn nodes_with_priority(&self, priority: crate::metadata::Priority) -> Vec<ProofNodeId> {
579 self.metadata
580 .iter()
581 .filter(|(_, meta)| meta.priority() == priority)
582 .map(|(id, _)| *id)
583 .collect()
584 }
585
586 #[must_use]
588 pub fn nodes_with_difficulty(
589 &self,
590 difficulty: crate::metadata::Difficulty,
591 ) -> Vec<ProofNodeId> {
592 self.metadata
593 .iter()
594 .filter(|(_, meta)| meta.difficulty() == difficulty)
595 .map(|(id, _)| *id)
596 .collect()
597 }
598
599 #[must_use]
601 pub fn nodes_with_strategy(&self, strategy: crate::metadata::Strategy) -> Vec<ProofNodeId> {
602 self.metadata
603 .iter()
604 .filter(|(_, meta)| meta.has_strategy(strategy))
605 .map(|(id, _)| *id)
606 .collect()
607 }
608
609 pub fn get_or_create_metadata(&mut self, node_id: ProofNodeId) -> &mut ProofMetadata {
611 self.metadata.entry(node_id).or_default()
612 }
613}
614
615impl Default for Proof {
616 fn default() -> Self {
617 Self::new()
618 }
619}
620
621#[derive(Debug, Clone, Copy)]
623#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
624pub struct ProofStats {
625 pub total_nodes: usize,
627 pub axiom_count: usize,
629 pub inference_count: usize,
631 pub max_depth: u32,
633 pub avg_depth: f64,
635 pub root_count: usize,
637 pub max_premises: usize,
639 pub avg_premises: f64,
641 pub leaf_count: usize,
643}
644
645impl fmt::Display for ProofStats {
646 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
647 writeln!(f, "Proof Statistics:")?;
648 writeln!(f, " Total nodes: {}", self.total_nodes)?;
649 writeln!(f, " Axioms: {}", self.axiom_count)?;
650 writeln!(f, " Inferences: {}", self.inference_count)?;
651 writeln!(f, " Roots: {}", self.root_count)?;
652 writeln!(f, " Leaves: {}", self.leaf_count)?;
653 writeln!(f, " Max depth: {}", self.max_depth)?;
654 writeln!(f, " Avg depth: {:.2}", self.avg_depth)?;
655 writeln!(f, " Max premises: {}", self.max_premises)?;
656 writeln!(f, " Avg premises: {:.2}", self.avg_premises)
657 }
658}
659
660impl fmt::Display for ProofNode {
661 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
662 write!(f, "{}: {}", self.id, self.step)
663 }
664}
665
666impl fmt::Display for ProofStep {
667 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
668 match self {
669 ProofStep::Axiom { conclusion } => write!(f, "(axiom {})", conclusion),
670 ProofStep::Inference {
671 rule,
672 premises,
673 conclusion,
674 args,
675 } => {
676 write!(f, "({} [", rule)?;
677 for (i, p) in premises.iter().enumerate() {
678 if i > 0 {
679 write!(f, ", ")?;
680 }
681 write!(f, "{}", p)?;
682 }
683 write!(f, "]")?;
684 if !args.is_empty() {
685 write!(f, " :args {:?}", args)?;
686 }
687 write!(f, " => {})", conclusion)
688 }
689 }
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696
697 #[test]
698 fn test_proof_creation() {
699 let proof = Proof::new();
700 assert!(proof.is_empty());
701 assert_eq!(proof.len(), 0);
702 }
703
704 #[test]
705 fn test_add_axiom() {
706 let mut proof = Proof::new();
707 let id = proof.add_axiom("p");
708
709 assert_eq!(proof.len(), 1);
710 let node = proof.get_node(id).unwrap();
711 assert_eq!(node.id, ProofNodeId(0));
712 assert_eq!(node.depth, 0);
713 }
714
715 #[test]
716 fn test_add_inference() {
717 let mut proof = Proof::new();
718 let a1 = proof.add_axiom("p");
719 let a2 = proof.add_axiom("q");
720 let i1 = proof.add_inference("and", vec![a1, a2], "p /\\ q");
721
722 assert_eq!(proof.len(), 3);
723 let node = proof.get_node(i1).unwrap();
724 assert_eq!(node.depth, 1);
725 }
726
727 #[test]
728 fn test_proof_deduplication() {
729 let mut proof = Proof::new();
730 let id1 = proof.add_axiom("p");
731 let id2 = proof.add_axiom("p"); assert_eq!(id1, id2);
734 assert_eq!(proof.len(), 1); }
736
737 #[test]
738 fn test_proof_depth() {
739 let mut proof = Proof::new();
740 let a1 = proof.add_axiom("p");
741 let a2 = proof.add_axiom("q");
742 let i1 = proof.add_inference("and", vec![a1, a2], "p /\\ q");
743 let i2 = proof.add_inference("not", vec![i1], "~(p /\\ q)");
744
745 let node = proof.get_node(i2).unwrap();
746 assert_eq!(node.depth, 2);
747 }
748
749 #[test]
750 fn test_proof_stats() {
751 let mut proof = Proof::new();
752 proof.add_axiom("p");
753 proof.add_axiom("q");
754 let a1 = proof.add_axiom("r");
755 let a2 = proof.add_axiom("s");
756 proof.add_inference("and", vec![a1, a2], "r /\\ s");
757
758 let stats = proof.stats();
759 assert_eq!(stats.total_nodes, 5);
760 assert_eq!(stats.axiom_count, 4);
761 assert_eq!(stats.inference_count, 1);
762 assert_eq!(stats.max_depth, 1);
763 }
764
765 #[test]
766 fn test_proof_clear() {
767 let mut proof = Proof::new();
768 proof.add_axiom("p");
769 proof.add_axiom("q");
770
771 assert_eq!(proof.len(), 2);
772 proof.clear();
773 assert!(proof.is_empty());
774 }
775
776 #[test]
777 fn test_proof_display() {
778 let mut proof = Proof::new();
779 let a1 = proof.add_axiom("p");
780 proof.add_inference("not", vec![a1], "~p");
781
782 let output = proof.to_string_repr();
783 assert!(output.contains("axiom"));
784 assert!(output.contains("not"));
785 }
786
787 #[test]
788 fn test_dependent_tracking() {
789 let mut proof = Proof::new();
790 let a1 = proof.add_axiom("p");
791 let a2 = proof.add_axiom("q");
792 let i1 = proof.add_inference("and", vec![a1, a2], "p /\\ q");
793
794 let node_a1 = proof.get_node(a1).unwrap();
795 assert!(node_a1.dependents.contains(&i1));
796
797 let node_a2 = proof.get_node(a2).unwrap();
798 assert!(node_a2.dependents.contains(&i1));
799 }
800
801 #[test]
803 fn test_metadata_set_get() {
804 use crate::metadata::{Difficulty, Priority};
805
806 let mut proof = Proof::new();
807 let id = proof.add_axiom("p");
808
809 let meta = ProofMetadata::new()
810 .with_priority(Priority::High)
811 .with_difficulty(Difficulty::Easy);
812
813 proof.set_metadata(id, meta);
814
815 let retrieved = proof.get_metadata(id).unwrap();
816 assert_eq!(retrieved.priority(), Priority::High);
817 assert_eq!(retrieved.difficulty(), Difficulty::Easy);
818 }
819
820 #[test]
821 fn test_metadata_tags() {
822 let mut proof = Proof::new();
823 let id1 = proof.add_axiom("p");
824 let id2 = proof.add_axiom("q");
825
826 proof.set_metadata(id1, ProofMetadata::new().with_tag("important"));
827 proof.set_metadata(id2, ProofMetadata::new().with_tag("trivial"));
828
829 let important_nodes = proof.nodes_with_tag("important");
830 assert_eq!(important_nodes.len(), 1);
831 assert_eq!(important_nodes[0], id1);
832 }
833
834 #[test]
835 fn test_metadata_priority_filter() {
836 use crate::metadata::Priority;
837
838 let mut proof = Proof::new();
839 let id1 = proof.add_axiom("p");
840 let id2 = proof.add_axiom("q");
841 let id3 = proof.add_axiom("r");
842
843 proof.set_metadata(id1, ProofMetadata::new().with_priority(Priority::High));
844 proof.set_metadata(id2, ProofMetadata::new().with_priority(Priority::High));
845 proof.set_metadata(id3, ProofMetadata::new().with_priority(Priority::Low));
846
847 let high_priority = proof.nodes_with_priority(Priority::High);
848 assert_eq!(high_priority.len(), 2);
849 }
850
851 #[test]
852 fn test_metadata_strategy_filter() {
853 use crate::metadata::Strategy;
854
855 let mut proof = Proof::new();
856 let id1 = proof.add_axiom("p");
857 let id2 = proof.add_axiom("q");
858
859 proof.set_metadata(
860 id1,
861 ProofMetadata::new().with_strategy(Strategy::Resolution),
862 );
863 proof.set_metadata(id2, ProofMetadata::new().with_strategy(Strategy::Theory));
864
865 let resolution_nodes = proof.nodes_with_strategy(Strategy::Resolution);
866 assert_eq!(resolution_nodes.len(), 1);
867 assert_eq!(resolution_nodes[0], id1);
868 }
869
870 #[test]
871 fn test_metadata_remove() {
872 let mut proof = Proof::new();
873 let id = proof.add_axiom("p");
874
875 proof.set_metadata(id, ProofMetadata::new().with_tag("temp"));
876 assert!(proof.has_metadata(id));
877
878 proof.remove_metadata(id);
879 assert!(!proof.has_metadata(id));
880 }
881
882 #[test]
883 fn test_metadata_get_or_create() {
884 use crate::metadata::Priority;
885
886 let mut proof = Proof::new();
887 let id = proof.add_axiom("p");
888
889 let meta = proof.get_or_create_metadata(id);
890 meta.set_priority(Priority::VeryHigh);
891
892 let retrieved = proof.get_metadata(id).unwrap();
893 assert_eq!(retrieved.priority(), Priority::VeryHigh);
894 }
895
896 #[test]
897 fn test_metadata_clear() {
898 let mut proof = Proof::new();
899 let id = proof.add_axiom("p");
900 proof.set_metadata(id, ProofMetadata::new().with_tag("test"));
901
902 proof.clear();
903 assert!(!proof.has_metadata(id));
904 }
905
906 mod proptests {
908 use super::*;
909 use proptest::prelude::*;
910
911 fn var_name() -> impl Strategy<Value = String> {
913 "[a-z][0-9]*".prop_map(|s| s.to_string())
914 }
915
916 fn rule_name() -> impl Strategy<Value = String> {
918 prop_oneof![
919 Just("and".to_string()),
920 Just("or".to_string()),
921 Just("not".to_string()),
922 Just("imp".to_string()),
923 Just("resolution".to_string()),
924 ]
925 }
926
927 proptest! {
928 #[test]
930 fn prop_axiom_increases_size(conclusion in var_name()) {
931 let mut proof = Proof::new();
932 let initial_len = proof.len();
933 proof.add_axiom(&conclusion);
934 prop_assert!(proof.len() > initial_len || proof.len() == 1);
935 }
936
937 #[test]
939 fn prop_axiom_depth_zero(conclusion in var_name()) {
940 let mut proof = Proof::new();
941 let id = proof.add_axiom(&conclusion);
942 let node = proof.get_node(id).unwrap();
943 prop_assert_eq!(node.depth, 0);
944 }
945
946 #[test]
948 fn prop_axiom_deduplication(conclusion in var_name()) {
949 let mut proof = Proof::new();
950 let id1 = proof.add_axiom(&conclusion);
951 let id2 = proof.add_axiom(&conclusion);
952 prop_assert_eq!(id1, id2);
953 prop_assert_eq!(proof.len(), 1);
954 }
955
956 #[test]
958 fn prop_inference_depth(
959 rule in rule_name(),
960 conclusions in prop::collection::vec(var_name(), 1..5)
961 ) {
962 let mut proof = Proof::new();
963 let mut axiom_ids = Vec::new();
964
965 for conclusion in &conclusions {
966 let id = proof.add_axiom(conclusion);
967 axiom_ids.push(id);
968 }
969
970 if !axiom_ids.is_empty() {
971 let inf_id = proof.add_inference(
972 &rule,
973 axiom_ids.clone(),
974 format!("({} {})", rule, conclusions.join(" "))
975 );
976 let inf_node = proof.get_node(inf_id).unwrap();
977
978 for &premise_id in &axiom_ids {
979 let premise_node = proof.get_node(premise_id).unwrap();
980 prop_assert!(inf_node.depth > premise_node.depth);
981 }
982 }
983 }
984
985 #[test]
987 fn prop_stats_consistency(
988 axiom_count in 1..10_usize,
989 inference_count in 0..5_usize
990 ) {
991 let mut proof = Proof::new();
992 let mut axiom_ids = Vec::new();
993
994 for i in 0..axiom_count {
996 let id = proof.add_axiom(format!("p{}", i));
997 axiom_ids.push(id);
998 }
999
1000 for i in 0..inference_count {
1002 if axiom_ids.len() >= 2 {
1003 let premises = vec![axiom_ids[0], axiom_ids[1]];
1004 let id = proof.add_inference(
1005 "and",
1006 premises,
1007 format!("q{}", i)
1008 );
1009 axiom_ids.push(id);
1010 }
1011 }
1012
1013 let stats = proof.stats();
1014 prop_assert_eq!(stats.axiom_count, axiom_count);
1015 prop_assert!(stats.inference_count <= inference_count);
1016 prop_assert_eq!(stats.total_nodes, stats.axiom_count + stats.inference_count);
1017 }
1018
1019 #[test]
1021 fn prop_node_count_invariant(
1022 axioms in prop::collection::vec(var_name(), 0..10)
1023 ) {
1024 let mut proof = Proof::new();
1025 for axiom in &axioms {
1026 proof.add_axiom(axiom);
1027 }
1028
1029 let stats = proof.stats();
1030 prop_assert_eq!(proof.len(), stats.total_nodes);
1031 prop_assert_eq!(stats.total_nodes, stats.axiom_count + stats.inference_count);
1032 }
1033
1034 #[test]
1036 fn prop_all_nodes_reachable(
1037 conclusions in prop::collection::vec(var_name(), 1..8)
1038 ) {
1039 let mut proof = Proof::new();
1040 for conclusion in &conclusions {
1041 proof.add_axiom(conclusion);
1042 }
1043
1044 for i in 0..proof.len() {
1045 let id = ProofNodeId(i as u32);
1046 prop_assert!(proof.get_node(id).is_some());
1047 }
1048 }
1049
1050 #[test]
1052 fn prop_clear_resets(
1053 conclusions in prop::collection::vec(var_name(), 1..10)
1054 ) {
1055 let mut proof = Proof::new();
1056 for conclusion in &conclusions {
1057 proof.add_axiom(conclusion);
1058 }
1059
1060 proof.clear();
1061 prop_assert!(proof.is_empty());
1062 prop_assert_eq!(proof.len(), 0);
1063 prop_assert_eq!(proof.next_id, 0);
1064 }
1065
1066 #[test]
1068 fn prop_max_depth_bound(
1069 conclusions in prop::collection::vec(var_name(), 1..8)
1070 ) {
1071 let mut proof = Proof::new();
1072 let mut ids = Vec::new();
1073
1074 for conclusion in &conclusions {
1075 ids.push(proof.add_axiom(conclusion));
1076 }
1077
1078 if ids.len() >= 2 {
1080 let id = proof.add_inference("and", vec![ids[0], ids[1]], "result");
1081 ids.push(id);
1082 }
1083
1084 let stats = proof.stats();
1085 for id in &ids {
1086 if let Some(node) = proof.get_node(*id) {
1087 prop_assert!(stats.max_depth >= node.depth);
1088 }
1089 }
1090 }
1091
1092 #[test]
1094 fn prop_premise_validity(
1095 rule in rule_name(),
1096 conclusions in prop::collection::vec(var_name(), 2..6)
1097 ) {
1098 let mut proof = Proof::new();
1099 let mut premise_ids = Vec::new();
1100
1101 for conclusion in &conclusions {
1102 premise_ids.push(proof.add_axiom(conclusion));
1103 }
1104
1105 if premise_ids.len() >= 2 {
1106 let inf_id = proof.add_inference(
1107 &rule,
1108 premise_ids.clone(),
1109 "result"
1110 );
1111
1112 if let Some(premises) = proof.premises(inf_id) {
1113 for &premise_id in premises {
1114 prop_assert!(proof.get_node(premise_id).is_some());
1115 }
1116 }
1117 }
1118 }
1119 }
1120 }
1121}