use crate::error::{LmmError, Result};
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq)]
pub struct SymbolicAxiom {
pub name: String,
pub antecedents: Vec<String>,
pub consequent: String,
pub weight: f64,
}
impl SymbolicAxiom {
pub fn new(
name: impl Into<String>,
antecedents: Vec<String>,
consequent: impl Into<String>,
) -> Self {
Self {
name: name.into(),
antecedents,
consequent: consequent.into(),
weight: 1.0,
}
}
pub fn with_weight(mut self, weight: f64) -> Self {
self.weight = weight.clamp(f64::MIN_POSITIVE, 1.0);
self
}
pub fn is_applicable(&self, facts: &HashSet<String>) -> bool {
self.antecedents.iter().all(|a| facts.contains(a))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeductionStep {
pub axiom_name: String,
pub from: Vec<String>,
pub derived: String,
pub confidence: f64,
}
#[derive(Debug, Clone)]
pub struct CompositeProof {
pub succeeded: bool,
pub target: String,
pub steps: Vec<DeductionStep>,
pub final_facts: HashSet<String>,
}
impl CompositeProof {
pub fn proof_confidence(&self) -> f64 {
self.steps
.iter()
.map(|s| s.confidence)
.fold(1.0_f64, f64::min)
}
}
#[derive(Debug, Clone, Default)]
pub struct DeductionEngine {
axioms: Vec<SymbolicAxiom>,
max_depth: usize,
}
impl DeductionEngine {
pub fn new(max_depth: usize) -> Self {
Self {
axioms: Vec::new(),
max_depth: max_depth.max(1),
}
}
pub fn register(&mut self, axiom: SymbolicAxiom) {
self.axioms.push(axiom);
}
pub fn axiom_count(&self) -> usize {
self.axioms.len()
}
pub fn prove(&self, initial_facts: &[String], target: &str) -> Result<CompositeProof> {
let mut facts: HashSet<String> = initial_facts.iter().cloned().collect();
let mut steps: Vec<DeductionStep> = Vec::new();
if facts.contains(target) {
return Ok(CompositeProof {
succeeded: true,
target: target.to_string(),
steps,
final_facts: facts,
});
}
for depth in 0..self.max_depth {
let mut new_derivations: Vec<(String, &SymbolicAxiom)> = self
.axioms
.iter()
.filter(|ax| ax.is_applicable(&facts) && !facts.contains(&ax.consequent))
.map(|ax| (ax.consequent.clone(), ax))
.collect();
new_derivations.sort_by(|a, b| {
b.1.weight
.partial_cmp(&a.1.weight)
.unwrap_or(std::cmp::Ordering::Equal)
});
if new_derivations.is_empty() {
return Ok(CompositeProof {
succeeded: false,
target: target.to_string(),
steps,
final_facts: facts,
});
}
for (derived, axiom) in &new_derivations {
steps.push(DeductionStep {
axiom_name: axiom.name.clone(),
from: axiom.antecedents.clone(),
derived: derived.clone(),
confidence: axiom.weight,
});
facts.insert(derived.clone());
}
if facts.contains(target) {
return Ok(CompositeProof {
succeeded: true,
target: target.to_string(),
steps,
final_facts: facts,
});
}
if depth + 1 >= self.max_depth {
return Err(LmmError::Timeout);
}
}
Ok(CompositeProof {
succeeded: false,
target: target.to_string(),
steps,
final_facts: facts,
})
}
pub fn is_known(&self, facts: &[String], target: &str) -> bool {
facts.iter().any(|f| f == target)
}
}