use crate::proof::{Proof, ProofNodeId, ProofStep};
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
pub fn merge_proofs(proofs: &[Proof]) -> Proof {
let mut merged = Proof::new();
for proof in proofs {
let mut id_map: FxHashMap<ProofNodeId, ProofNodeId> = FxHashMap::default();
for node in proof.nodes() {
let new_id = match &node.step {
ProofStep::Axiom { conclusion } => merged.add_axiom(conclusion.clone()),
ProofStep::Inference {
rule,
premises,
conclusion,
args,
} => {
let new_premises: Vec<ProofNodeId> = premises
.iter()
.filter_map(|&old_id| id_map.get(&old_id).copied())
.collect();
if args.is_empty() {
merged.add_inference(rule, new_premises, conclusion.clone())
} else {
merged.add_inference_with_args(
rule,
new_premises,
args.to_vec(),
conclusion.clone(),
)
}
}
};
id_map.insert(node.id, new_id);
}
}
merged
}
pub fn slice_proof(proof: &Proof, target: ProofNodeId) -> Proof {
let mut sliced = Proof::new();
let mut id_map: FxHashMap<ProofNodeId, ProofNodeId> = FxHashMap::default();
let mut visited = std::collections::HashSet::new();
let mut dependencies = Vec::new();
let mut queue = VecDeque::new();
queue.push_back(target);
visited.insert(target);
while let Some(node_id) = queue.pop_front() {
dependencies.push(node_id);
if let Some(node) = proof.get_node(node_id)
&& let ProofStep::Inference { premises, .. } = &node.step
{
for &premise in premises.iter() {
if visited.insert(premise) {
queue.push_back(premise);
}
}
}
}
dependencies.reverse();
for node_id in dependencies {
if let Some(node) = proof.get_node(node_id) {
let new_id = match &node.step {
ProofStep::Axiom { conclusion } => sliced.add_axiom(conclusion.clone()),
ProofStep::Inference {
rule,
premises,
conclusion,
args,
} => {
let new_premises: Vec<ProofNodeId> = premises
.iter()
.filter_map(|&old_id| id_map.get(&old_id).copied())
.collect();
if args.is_empty() {
sliced.add_inference(rule, new_premises, conclusion.clone())
} else {
sliced.add_inference_with_args(
rule,
new_premises,
args.to_vec(),
conclusion.clone(),
)
}
}
};
id_map.insert(node.id, new_id);
}
}
sliced
}
pub fn slice_proof_multi(proof: &Proof, targets: &[ProofNodeId]) -> Vec<Proof> {
targets
.iter()
.map(|&target| slice_proof(proof, target))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_proofs_empty() {
let merged = merge_proofs(&[]);
assert!(merged.is_empty());
}
#[test]
fn test_merge_proofs_single() {
let mut proof = Proof::new();
proof.add_axiom("p");
proof.add_axiom("q");
let merged = merge_proofs(&[proof.clone()]);
assert_eq!(merged.len(), proof.len());
}
#[test]
fn test_merge_proofs_multiple() {
let mut proof1 = Proof::new();
proof1.add_axiom("p");
proof1.add_axiom("q");
let mut proof2 = Proof::new();
proof2.add_axiom("r");
proof2.add_axiom("s");
let merged = merge_proofs(&[proof1, proof2]);
assert_eq!(merged.len(), 4);
}
#[test]
fn test_merge_with_inferences() {
let mut proof1 = Proof::new();
let p = proof1.add_axiom("p");
let q = proof1.add_axiom("q");
proof1.add_inference("and", vec![p, q], "p ∧ q");
let mut proof2 = Proof::new();
let r = proof2.add_axiom("r");
proof2.add_inference("not", vec![r], "¬r");
let merged = merge_proofs(&[proof1, proof2]);
assert_eq!(merged.len(), 5); }
#[test]
fn test_slice_proof_axiom() {
let mut proof = Proof::new();
let p = proof.add_axiom("p");
let _q = proof.add_axiom("q");
let sliced = slice_proof(&proof, p);
assert_eq!(sliced.len(), 1);
}
#[test]
fn test_slice_proof_inference() {
let mut proof = Proof::new();
let p = proof.add_axiom("p");
let q = proof.add_axiom("q");
let pq = proof.add_inference("and", vec![p, q], "p ∧ q");
let _r = proof.add_axiom("r");
let sliced = slice_proof(&proof, pq);
assert_eq!(sliced.len(), 3); }
#[test]
fn test_slice_proof_deep() {
let mut proof = Proof::new();
let p = proof.add_axiom("p");
let q = proof.add_axiom("q");
let pq = proof.add_inference("and", vec![p, q], "p ∧ q");
let r = proof.add_axiom("r");
let pqr = proof.add_inference("and", vec![pq, r], "(p ∧ q) ∧ r");
let sliced = slice_proof(&proof, pqr);
assert_eq!(sliced.len(), 5); }
#[test]
fn test_slice_multi() {
let mut proof = Proof::new();
let p = proof.add_axiom("p");
let q = proof.add_axiom("q");
let pq = proof.add_inference("and", vec![p, q], "p ∧ q");
let slices = slice_proof_multi(&proof, &[p, pq]);
assert_eq!(slices.len(), 2);
assert_eq!(slices[0].len(), 1);
assert_eq!(slices[1].len(), 3);
}
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_merge_preserves_nodes(num_proofs in 1usize..5, nodes_per_proof in 1usize..10) {
let proofs: Vec<Proof> = (0..num_proofs)
.map(|i| {
let mut proof = Proof::new();
for j in 0..nodes_per_proof {
proof.add_axiom(format!("p{}_{}", i, j));
}
proof
})
.collect();
let total_expected = num_proofs * nodes_per_proof;
let merged = merge_proofs(&proofs);
prop_assert!(merged.len() <= total_expected);
}
#[test]
fn prop_merge_empty_proofs(n in 0usize..10) {
let proofs: Vec<Proof> = (0..n).map(|_| Proof::new()).collect();
let merged = merge_proofs(&proofs);
prop_assert!(merged.is_empty());
}
#[test]
fn prop_slice_smaller_or_equal(axiom_count in 1usize..20) {
let mut proof = Proof::new();
let mut ids = Vec::new();
for i in 0..axiom_count {
ids.push(proof.add_axiom(format!("p{}", i)));
}
if let Some(&target) = ids.first() {
let sliced = slice_proof(&proof, target);
prop_assert!(sliced.len() <= proof.len());
prop_assert!(!sliced.is_empty());
}
}
#[test]
fn prop_slice_contains_target(depth in 1usize..10) {
let mut proof = Proof::new();
let mut prev = proof.add_axiom("base");
for i in 0..depth {
prev = proof.add_inference("step", vec![prev], format!("level{}", i));
}
let sliced = slice_proof(&proof, prev);
prop_assert_eq!(sliced.len(), depth + 1); }
#[test]
fn prop_merge_single_is_copy(node_count in 1usize..20) {
let mut proof = Proof::new();
for i in 0..node_count {
proof.add_axiom(format!("p{}", i));
}
let merged = merge_proofs(&[proof.clone()]);
prop_assert_eq!(merged.len(), proof.len());
}
#[test]
fn prop_slice_axiom_is_single(axiom_count in 2usize..20, target_idx in 0usize..20) {
let target_idx = target_idx % axiom_count;
let mut proof = Proof::new();
let mut ids = Vec::new();
for i in 0..axiom_count {
ids.push(proof.add_axiom(format!("p{}", i)));
}
let sliced = slice_proof(&proof, ids[target_idx]);
prop_assert_eq!(sliced.len(), 1);
}
}
}
}