use crate::chc::{PredId, RuleId};
use crate::frames::LemmaId;
use oxiz_core::TermId;
use smallvec::SmallVec;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum ProofStep {
InitialLemma {
pred: PredId,
lemma: TermId,
justification: Justification,
},
InductiveLemma {
pred: PredId,
lemma: TermId,
level: u32,
dependencies: SmallVec<[LemmaId; 4]>,
},
Propagation {
lemma_id: LemmaId,
from_level: u32,
to_level: u32,
},
Fixpoint {
level: u32,
lemmas: SmallVec<[LemmaId; 8]>,
},
}
#[derive(Debug, Clone)]
pub enum Justification {
Initial { rule: RuleId },
Inductive { rules: SmallVec<[RuleId; 2]> },
Generalization { original: TermId },
Subsumed { by: LemmaId },
}
#[derive(Debug, Clone)]
pub struct SafetyProof {
steps: Vec<ProofStep>,
invariants: HashMap<PredId, SmallVec<[TermId; 8]>>,
}
impl SafetyProof {
pub fn new() -> Self {
Self {
steps: Vec::new(),
invariants: HashMap::new(),
}
}
pub fn add_step(&mut self, step: ProofStep) {
self.steps.push(step);
}
pub fn set_invariants(&mut self, invariants: HashMap<PredId, SmallVec<[TermId; 8]>>) {
self.invariants = invariants;
}
pub fn steps(&self) -> &[ProofStep] {
&self.steps
}
pub fn invariants(&self) -> &HashMap<PredId, SmallVec<[TermId; 8]>> {
&self.invariants
}
pub fn validate(&self) -> Result<(), ProofError> {
if self.steps.is_empty() {
return Err(ProofError::EmptyProof);
}
let has_fixpoint = self
.steps
.iter()
.any(|step| matches!(step, ProofStep::Fixpoint { .. }));
if !has_fixpoint {
return Err(ProofError::MissingFixpoint);
}
if self.invariants.is_empty() {
return Err(ProofError::NoInvariants);
}
Ok(())
}
pub fn stats(&self) -> ProofStats {
let mut stats = ProofStats::default();
for step in &self.steps {
match step {
ProofStep::InitialLemma { .. } => stats.num_initial += 1,
ProofStep::InductiveLemma { .. } => stats.num_inductive += 1,
ProofStep::Propagation { .. } => stats.num_propagations += 1,
ProofStep::Fixpoint { lemmas, .. } => {
stats.num_fixpoints += 1;
stats.fixpoint_size = lemmas.len();
}
}
}
stats.num_predicates = self.invariants.len();
stats.total_lemmas = self.invariants.values().map(|v| v.len()).sum();
stats
}
}
impl Default for SafetyProof {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct ProofStats {
pub num_initial: usize,
pub num_inductive: usize,
pub num_propagations: usize,
pub num_fixpoints: usize,
pub fixpoint_size: usize,
pub num_predicates: usize,
pub total_lemmas: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProofError {
EmptyProof,
MissingFixpoint,
NoInvariants,
InvalidDependency(LemmaId),
CircularDependency,
}
#[derive(Debug)]
pub struct ProofBuilder {
steps: Vec<ProofStep>,
enabled: bool,
}
impl ProofBuilder {
pub fn new(enabled: bool) -> Self {
Self {
steps: Vec::new(),
enabled,
}
}
pub fn record_initial(&mut self, pred: PredId, lemma: TermId, rule: RuleId) {
if self.enabled {
self.steps.push(ProofStep::InitialLemma {
pred,
lemma,
justification: Justification::Initial { rule },
});
}
}
pub fn record_inductive(
&mut self,
pred: PredId,
lemma: TermId,
level: u32,
dependencies: SmallVec<[LemmaId; 4]>,
) {
if self.enabled {
self.steps.push(ProofStep::InductiveLemma {
pred,
lemma,
level,
dependencies,
});
}
}
pub fn record_propagation(&mut self, lemma_id: LemmaId, from_level: u32, to_level: u32) {
if self.enabled {
self.steps.push(ProofStep::Propagation {
lemma_id,
from_level,
to_level,
});
}
}
pub fn record_fixpoint(&mut self, level: u32, lemmas: SmallVec<[LemmaId; 8]>) {
if self.enabled {
self.steps.push(ProofStep::Fixpoint { level, lemmas });
}
}
pub fn build(self, invariants: HashMap<PredId, SmallVec<[TermId; 8]>>) -> SafetyProof {
let mut proof = SafetyProof::new();
proof.steps = self.steps;
proof.set_invariants(invariants);
proof
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn num_steps(&self) -> usize {
self.steps.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_proof_creation() {
let proof = SafetyProof::new();
assert_eq!(proof.steps().len(), 0);
assert_eq!(proof.invariants().len(), 0);
}
#[test]
fn test_proof_validation_empty() {
let proof = SafetyProof::new();
assert_eq!(proof.validate(), Err(ProofError::EmptyProof));
}
#[test]
fn test_proof_with_fixpoint() {
let mut proof = SafetyProof::new();
proof.add_step(ProofStep::Fixpoint {
level: 5,
lemmas: SmallVec::from_vec(vec![LemmaId::new(0), LemmaId::new(1)]),
});
let mut invariants = HashMap::new();
invariants.insert(PredId::new(0), SmallVec::from_vec(vec![TermId::new(1)]));
proof.set_invariants(invariants);
assert!(proof.validate().is_ok());
}
#[test]
fn test_proof_stats() {
let mut proof = SafetyProof::new();
proof.add_step(ProofStep::InitialLemma {
pred: PredId::new(0),
lemma: TermId::new(1),
justification: Justification::Initial {
rule: RuleId::new(0),
},
});
proof.add_step(ProofStep::InductiveLemma {
pred: PredId::new(0),
lemma: TermId::new(2),
level: 1,
dependencies: SmallVec::new(),
});
proof.add_step(ProofStep::Fixpoint {
level: 2,
lemmas: SmallVec::from_vec(vec![LemmaId::new(0)]),
});
let stats = proof.stats();
assert_eq!(stats.num_initial, 1);
assert_eq!(stats.num_inductive, 1);
assert_eq!(stats.num_fixpoints, 1);
}
#[test]
fn test_proof_builder() {
let mut builder = ProofBuilder::new(true);
assert!(builder.is_enabled());
assert_eq!(builder.num_steps(), 0);
builder.record_initial(PredId::new(0), TermId::new(1), RuleId::new(0));
assert_eq!(builder.num_steps(), 1);
let invariants = HashMap::new();
let proof = builder.build(invariants);
assert_eq!(proof.steps().len(), 1);
}
#[test]
fn test_proof_builder_disabled() {
let mut builder = ProofBuilder::new(false);
builder.record_initial(PredId::new(0), TermId::new(1), RuleId::new(0));
assert_eq!(builder.num_steps(), 0); }
}