1use crate::proof::{Proof, ProofNodeId, ProofStep};
4use rustc_hash::FxHashMap;
5
6pub fn normalize_proof(proof: &Proof) -> Proof {
34 let mut normalized = Proof::new();
35 let mut id_map: FxHashMap<ProofNodeId, ProofNodeId> = FxHashMap::default();
36
37 let mut axioms: Vec<(ProofNodeId, String)> = Vec::new();
39 let mut inferences: Vec<(ProofNodeId, &ProofStep)> = Vec::new();
40
41 for node in proof.nodes() {
42 match &node.step {
43 ProofStep::Axiom { conclusion } => {
44 axioms.push((node.id, conclusion.clone()));
45 }
46 ProofStep::Inference { .. } => {
47 inferences.push((node.id, &node.step));
48 }
49 }
50 }
51
52 axioms.sort_by(|a, b| a.1.cmp(&b.1));
54
55 for (old_id, conclusion) in axioms {
57 let new_id = normalized.add_axiom(conclusion);
58 id_map.insert(old_id, new_id);
59 }
60
61 let mut added = std::collections::HashSet::new();
63 let mut changed = true;
64
65 while changed && added.len() < inferences.len() {
66 changed = false;
67
68 for &(old_id, step) in &inferences {
69 if added.contains(&old_id) {
70 continue;
71 }
72
73 if let ProofStep::Inference {
74 rule,
75 premises,
76 conclusion,
77 args,
78 } = step
79 {
80 let all_premises_ready = premises.iter().all(|&p| id_map.contains_key(&p));
82
83 if all_premises_ready {
84 let new_premises: Vec<ProofNodeId> = premises
85 .iter()
86 .filter_map(|&old_p| id_map.get(&old_p).copied())
87 .collect();
88
89 let new_id = if args.is_empty() {
90 normalized.add_inference(rule, new_premises, conclusion.clone())
91 } else {
92 normalized.add_inference_with_args(
93 rule,
94 new_premises,
95 args.to_vec(),
96 conclusion.clone(),
97 )
98 };
99
100 id_map.insert(old_id, new_id);
101 added.insert(old_id);
102 changed = true;
103 }
104 }
105 }
106 }
107
108 normalized
109}
110
111pub fn canonicalize_conclusions(proof: &Proof) -> Proof {
123 let mut canonical = Proof::new();
124 let mut id_map: FxHashMap<ProofNodeId, ProofNodeId> = FxHashMap::default();
125
126 for node in proof.nodes() {
127 let new_id = match &node.step {
128 ProofStep::Axiom { conclusion } => {
129 let canonical_conclusion = canonicalize_string(conclusion);
130 canonical.add_axiom(canonical_conclusion)
131 }
132 ProofStep::Inference {
133 rule,
134 premises,
135 conclusion,
136 args,
137 } => {
138 let canonical_conclusion = canonicalize_string(conclusion);
139 let new_premises: Vec<ProofNodeId> = premises
140 .iter()
141 .filter_map(|&old_p| id_map.get(&old_p).copied())
142 .collect();
143
144 if args.is_empty() {
145 canonical.add_inference(rule, new_premises, canonical_conclusion)
146 } else {
147 canonical.add_inference_with_args(
148 rule,
149 new_premises,
150 args.to_vec(),
151 canonical_conclusion,
152 )
153 }
154 }
155 };
156
157 id_map.insert(node.id, new_id);
158 }
159
160 canonical
161}
162
163fn canonicalize_string(s: &str) -> String {
165 s.split_whitespace().collect::<Vec<_>>().join(" ")
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_normalize_proof_axioms() {
174 let mut proof = Proof::new();
175 proof.add_axiom("z");
176 proof.add_axiom("a");
177 proof.add_axiom("m");
178
179 let normalized = normalize_proof(&proof);
180
181 let nodes: Vec<_> = normalized.nodes().iter().collect();
183 assert_eq!(nodes.len(), 3);
184
185 if let ProofStep::Axiom { conclusion } = &nodes[0].step {
186 assert_eq!(conclusion, "a");
187 } else {
188 panic!("Expected axiom");
189 }
190
191 if let ProofStep::Axiom { conclusion } = &nodes[1].step {
192 assert_eq!(conclusion, "m");
193 } else {
194 panic!("Expected axiom");
195 }
196
197 if let ProofStep::Axiom { conclusion } = &nodes[2].step {
198 assert_eq!(conclusion, "z");
199 } else {
200 panic!("Expected axiom");
201 }
202 }
203
204 #[test]
205 fn test_normalize_proof_with_inferences() {
206 let mut proof = Proof::new();
207 let z = proof.add_axiom("z");
208 let a = proof.add_axiom("a");
209 proof.add_inference("and", vec![z, a], "z ∧ a");
210
211 let normalized = normalize_proof(&proof);
212
213 assert_eq!(normalized.len(), 3);
214 }
215
216 #[test]
217 fn test_canonicalize_conclusions() {
218 let mut proof = Proof::new();
219 proof.add_axiom("p q r"); proof.add_axiom(" x y "); let canonical = canonicalize_conclusions(&proof);
223
224 let nodes: Vec<_> = canonical.nodes().iter().collect();
225 if let ProofStep::Axiom { conclusion } = &nodes[0].step {
226 assert_eq!(conclusion, "p q r");
227 } else {
228 panic!("Expected axiom");
229 }
230
231 if let ProofStep::Axiom { conclusion } = &nodes[1].step {
232 assert_eq!(conclusion, "x y");
233 } else {
234 panic!("Expected axiom");
235 }
236 }
237
238 #[test]
239 fn test_canonicalize_string() {
240 assert_eq!(canonicalize_string(" a b c "), "a b c");
241 assert_eq!(canonicalize_string("x"), "x");
242 assert_eq!(canonicalize_string(" "), "");
243 }
244
245 #[test]
246 fn test_normalize_empty_proof() {
247 let proof = Proof::new();
248 let normalized = normalize_proof(&proof);
249 assert!(normalized.is_empty());
250 }
251
252 #[test]
253 fn test_canonicalize_empty_proof() {
254 let proof = Proof::new();
255 let canonical = canonicalize_conclusions(&proof);
256 assert!(canonical.is_empty());
257 }
258}