Skip to main content

oxiz_proof/
traversal.rs

1//! Proof traversal and transformation utilities.
2//!
3//! This module provides various ways to traverse and transform proof trees,
4//! including visitors, iterators, and transformation passes.
5
6use crate::proof::{Proof, ProofNode, ProofNodeId, ProofStep};
7use std::collections::{HashSet, VecDeque};
8
9/// Visitor trait for proof tree traversal.
10pub trait ProofVisitor {
11    /// Visit a proof node.
12    fn visit_node(&mut self, proof: &Proof, node: &ProofNode);
13
14    /// Visit an axiom node.
15    fn visit_axiom(&mut self, _proof: &Proof, _id: ProofNodeId, _conclusion: &str) {}
16
17    /// Visit an inference node.
18    fn visit_inference(
19        &mut self,
20        _proof: &Proof,
21        _id: ProofNodeId,
22        _rule: &str,
23        _premises: &[ProofNodeId],
24        _conclusion: &str,
25    ) {
26    }
27}
28
29/// Proof tree traversal order.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum TraversalOrder {
32    /// Pre-order: visit node before its children.
33    PreOrder,
34    /// Post-order: visit node after its children.
35    PostOrder,
36    /// Breadth-first: visit nodes level by level.
37    BreadthFirst,
38}
39
40/// Traverse a proof tree with a visitor.
41pub fn traverse<V: ProofVisitor>(proof: &Proof, visitor: &mut V, order: TraversalOrder) {
42    if let Some(root) = proof.root() {
43        let mut visited = HashSet::new();
44        match order {
45            TraversalOrder::PreOrder => traverse_pre_order(proof, root, visitor, &mut visited),
46            TraversalOrder::PostOrder => traverse_post_order(proof, root, visitor, &mut visited),
47            TraversalOrder::BreadthFirst => traverse_breadth_first(proof, visitor),
48        }
49    }
50}
51
52/// Pre-order traversal (root, then children).
53fn traverse_pre_order<V: ProofVisitor>(
54    proof: &Proof,
55    node_id: ProofNodeId,
56    visitor: &mut V,
57    visited: &mut HashSet<ProofNodeId>,
58) {
59    if visited.contains(&node_id) {
60        return;
61    }
62    visited.insert(node_id);
63
64    if let Some(node) = proof.get_node(node_id) {
65        // Visit this node first
66        visitor.visit_node(proof, node);
67
68        match &node.step {
69            ProofStep::Axiom { conclusion } => {
70                visitor.visit_axiom(proof, node_id, conclusion);
71            }
72            ProofStep::Inference {
73                rule,
74                premises,
75                conclusion,
76                ..
77            } => {
78                visitor.visit_inference(proof, node_id, rule, premises, conclusion);
79
80                // Then visit children
81                for &premise in premises {
82                    traverse_pre_order(proof, premise, visitor, visited);
83                }
84            }
85        }
86    }
87}
88
89/// Post-order traversal (children first, then root).
90fn traverse_post_order<V: ProofVisitor>(
91    proof: &Proof,
92    node_id: ProofNodeId,
93    visitor: &mut V,
94    visited: &mut HashSet<ProofNodeId>,
95) {
96    if visited.contains(&node_id) {
97        return;
98    }
99    visited.insert(node_id);
100
101    if let Some(node) = proof.get_node(node_id) {
102        // Visit children first
103        if let ProofStep::Inference { premises, .. } = &node.step {
104            for &premise in premises {
105                traverse_post_order(proof, premise, visitor, visited);
106            }
107        }
108
109        // Then visit this node
110        visitor.visit_node(proof, node);
111
112        match &node.step {
113            ProofStep::Axiom { conclusion } => {
114                visitor.visit_axiom(proof, node_id, conclusion);
115            }
116            ProofStep::Inference {
117                rule,
118                premises,
119                conclusion,
120                ..
121            } => {
122                visitor.visit_inference(proof, node_id, rule, premises, conclusion);
123            }
124        }
125    }
126}
127
128/// Breadth-first traversal (level by level).
129fn traverse_breadth_first<V: ProofVisitor>(proof: &Proof, visitor: &mut V) {
130    if let Some(root) = proof.root() {
131        let mut queue = VecDeque::new();
132        let mut visited = HashSet::new();
133
134        queue.push_back(root);
135        visited.insert(root);
136
137        while let Some(node_id) = queue.pop_front() {
138            if let Some(node) = proof.get_node(node_id) {
139                visitor.visit_node(proof, node);
140
141                match &node.step {
142                    ProofStep::Axiom { conclusion } => {
143                        visitor.visit_axiom(proof, node_id, conclusion);
144                    }
145                    ProofStep::Inference {
146                        rule,
147                        premises,
148                        conclusion,
149                        ..
150                    } => {
151                        visitor.visit_inference(proof, node_id, rule, premises, conclusion);
152
153                        for &premise in premises {
154                            if !visited.contains(&premise) {
155                                visited.insert(premise);
156                                queue.push_back(premise);
157                            }
158                        }
159                    }
160                }
161            }
162        }
163    }
164}
165
166/// Collect all nodes in topological order (leaves first, root last).
167#[must_use]
168pub fn topological_order(proof: &Proof) -> Vec<ProofNodeId> {
169    let mut order = Vec::new();
170    if let Some(root) = proof.root() {
171        let mut visited = HashSet::new();
172        collect_topological(proof, root, &mut order, &mut visited);
173    }
174    order
175}
176
177fn collect_topological(
178    proof: &Proof,
179    node_id: ProofNodeId,
180    order: &mut Vec<ProofNodeId>,
181    visited: &mut HashSet<ProofNodeId>,
182) {
183    if visited.contains(&node_id) {
184        return;
185    }
186    visited.insert(node_id);
187
188    if let Some(node) = proof.get_node(node_id)
189        && let ProofStep::Inference { premises, .. } = &node.step
190    {
191        for &premise in premises {
192            collect_topological(proof, premise, order, visited);
193        }
194    }
195
196    order.push(node_id);
197}
198
199/// Find all paths from leaves to root.
200#[must_use]
201pub fn find_all_paths(proof: &Proof) -> Vec<Vec<ProofNodeId>> {
202    let mut paths = Vec::new();
203    if let Some(root) = proof.root() {
204        let mut current_path = Vec::new();
205        collect_paths(proof, root, &mut current_path, &mut paths);
206    }
207    paths
208}
209
210fn collect_paths(
211    proof: &Proof,
212    node_id: ProofNodeId,
213    current_path: &mut Vec<ProofNodeId>,
214    all_paths: &mut Vec<Vec<ProofNodeId>>,
215) {
216    current_path.push(node_id);
217
218    if let Some(node) = proof.get_node(node_id) {
219        match &node.step {
220            ProofStep::Axiom { .. } => {
221                // Reached a leaf - save the path
222                all_paths.push(current_path.clone());
223            }
224            ProofStep::Inference { premises, .. } => {
225                // Continue down each premise
226                for &premise in premises {
227                    collect_paths(proof, premise, current_path, all_paths);
228                }
229            }
230        }
231    }
232
233    current_path.pop();
234}
235
236/// A visitor that counts nodes by type.
237
238#[derive(Debug, Default)]
239pub struct NodeCounter {
240    /// Number of axiom nodes.
241    pub axioms: usize,
242    /// Number of inference nodes.
243    pub inferences: usize,
244}
245
246impl ProofVisitor for NodeCounter {
247    fn visit_node(&mut self, _proof: &Proof, node: &ProofNode) {
248        match node.step {
249            ProofStep::Axiom { .. } => self.axioms += 1,
250            ProofStep::Inference { .. } => self.inferences += 1,
251        }
252    }
253}
254
255/// A visitor that collects all conclusions.
256
257#[derive(Debug, Default)]
258pub struct ConclusionCollector {
259    /// All conclusions in the proof.
260    pub conclusions: Vec<String>,
261}
262
263impl ProofVisitor for ConclusionCollector {
264    fn visit_node(&mut self, _proof: &Proof, node: &ProofNode) {
265        let conclusion = match &node.step {
266            ProofStep::Axiom { conclusion } => conclusion.clone(),
267            ProofStep::Inference { conclusion, .. } => conclusion.clone(),
268        };
269        self.conclusions.push(conclusion);
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_node_counter() {
279        let mut proof = Proof::new();
280        let p1 = proof.add_axiom("p");
281        let p2 = proof.add_axiom("q");
282        let _p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
283
284        let mut counter = NodeCounter::default();
285        traverse(&proof, &mut counter, TraversalOrder::PreOrder);
286
287        assert_eq!(counter.axioms, 2);
288        assert_eq!(counter.inferences, 1);
289    }
290
291    #[test]
292    fn test_conclusion_collector() {
293        let mut proof = Proof::new();
294        let p1 = proof.add_axiom("p");
295        let p2 = proof.add_axiom("q");
296        let _p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
297
298        let mut collector = ConclusionCollector::default();
299        traverse(&proof, &mut collector, TraversalOrder::PreOrder);
300
301        assert_eq!(collector.conclusions.len(), 3);
302        assert!(collector.conclusions.contains(&"p".to_string()));
303        assert!(collector.conclusions.contains(&"q".to_string()));
304        assert!(collector.conclusions.contains(&"(and p q)".to_string()));
305    }
306
307    #[test]
308    fn test_topological_order() {
309        let mut proof = Proof::new();
310        let p1 = proof.add_axiom("p");
311        let p2 = proof.add_axiom("q");
312        let p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
313
314        let order = topological_order(&proof);
315        assert_eq!(order.len(), 3);
316
317        // The root (p3) should come last in topological order
318        assert_eq!(order[order.len() - 1], p3);
319    }
320
321    #[test]
322    fn test_find_all_paths() {
323        let mut proof = Proof::new();
324        let p1 = proof.add_axiom("p");
325        let p2 = proof.add_axiom("q");
326        let _p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
327
328        let paths = find_all_paths(&proof);
329        assert_eq!(paths.len(), 2); // Two paths: one through p, one through q
330    }
331
332    #[test]
333    fn test_traversal_orders() {
334        let mut proof = Proof::new();
335        let p1 = proof.add_axiom("p");
336        let p2 = proof.add_axiom("q");
337        let _p3 = proof.add_inference("and", vec![p1, p2], "(and p q)");
338
339        // Test all traversal orders don't crash
340        let mut counter = NodeCounter::default();
341        traverse(&proof, &mut counter, TraversalOrder::PreOrder);
342        assert_eq!(counter.axioms, 2);
343
344        let mut counter = NodeCounter::default();
345        traverse(&proof, &mut counter, TraversalOrder::PostOrder);
346        assert_eq!(counter.axioms, 2);
347
348        let mut counter = NodeCounter::default();
349        traverse(&proof, &mut counter, TraversalOrder::BreadthFirst);
350        assert_eq!(counter.axioms, 2);
351    }
352}