use super::working_memory::FactHandle;
use std::collections::{HashMap, HashSet};
use std::time::Instant;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JustificationType {
Explicit,
Logical,
}
#[derive(Debug, Clone)]
pub struct Justification {
pub fact_handle: FactHandle,
pub justification_type: JustificationType,
pub source_rule: Option<String>,
pub premise_facts: Vec<FactHandle>,
pub created_at: Instant,
pub id: u64,
}
impl Justification {
pub fn explicit(fact_handle: FactHandle, id: u64) -> Self {
Self {
fact_handle,
justification_type: JustificationType::Explicit,
source_rule: None,
premise_facts: Vec::new(),
created_at: Instant::now(),
id,
}
}
pub fn logical(
fact_handle: FactHandle,
source_rule: String,
premise_facts: Vec<FactHandle>,
id: u64,
) -> Self {
Self {
fact_handle,
justification_type: JustificationType::Logical,
source_rule: Some(source_rule),
premise_facts,
created_at: Instant::now(),
id,
}
}
pub fn is_valid(&self, retracted_facts: &HashSet<FactHandle>) -> bool {
if self.justification_type == JustificationType::Explicit {
return true;
}
!self
.premise_facts
.iter()
.any(|h| retracted_facts.contains(h))
}
}
pub struct TruthMaintenanceSystem {
justifications: HashMap<u64, Justification>,
fact_justifications: HashMap<FactHandle, Vec<u64>>,
fact_dependents: HashMap<FactHandle, Vec<u64>>,
logical_facts: HashSet<FactHandle>,
explicit_facts: HashSet<FactHandle>,
retracted_facts: HashSet<FactHandle>,
next_justification_id: u64,
}
impl TruthMaintenanceSystem {
pub fn new() -> Self {
Self {
justifications: HashMap::new(),
fact_justifications: HashMap::new(),
fact_dependents: HashMap::new(),
logical_facts: HashSet::new(),
explicit_facts: HashSet::new(),
retracted_facts: HashSet::new(),
next_justification_id: 1,
}
}
pub fn add_explicit_justification(&mut self, fact_handle: FactHandle) {
let id = self.next_justification_id;
self.next_justification_id += 1;
let justification = Justification::explicit(fact_handle, id);
self.justifications.insert(id, justification);
self.fact_justifications
.entry(fact_handle)
.or_default()
.push(id);
self.explicit_facts.insert(fact_handle);
}
pub fn add_logical_justification(
&mut self,
fact_handle: FactHandle,
source_rule: String,
premise_facts: Vec<FactHandle>,
) {
let id = self.next_justification_id;
self.next_justification_id += 1;
let justification =
Justification::logical(fact_handle, source_rule, premise_facts.clone(), id);
self.justifications.insert(id, justification);
self.fact_justifications
.entry(fact_handle)
.or_default()
.push(id);
for premise in premise_facts {
self.fact_dependents.entry(premise).or_default().push(id);
}
self.logical_facts.insert(fact_handle);
}
pub fn is_logical(&self, fact_handle: FactHandle) -> bool {
self.logical_facts.contains(&fact_handle)
}
pub fn is_explicit(&self, fact_handle: FactHandle) -> bool {
self.explicit_facts.contains(&fact_handle)
}
pub fn get_justifications(&self, fact_handle: FactHandle) -> Vec<&Justification> {
if let Some(just_ids) = self.fact_justifications.get(&fact_handle) {
just_ids
.iter()
.filter_map(|id| self.justifications.get(id))
.collect()
} else {
Vec::new()
}
}
pub fn has_valid_justification(&self, fact_handle: FactHandle) -> bool {
if let Some(just_ids) = self.fact_justifications.get(&fact_handle) {
just_ids
.iter()
.filter_map(|id| self.justifications.get(id))
.any(|j| j.is_valid(&self.retracted_facts))
} else {
false
}
}
pub fn retract_with_cascade(&mut self, fact_handle: FactHandle) -> Vec<FactHandle> {
let mut to_retract = Vec::new();
self.retracted_facts.insert(fact_handle);
self.logical_facts.remove(&fact_handle);
self.explicit_facts.remove(&fact_handle);
if let Some(dependent_just_ids) = self.fact_dependents.get(&fact_handle) {
for just_id in dependent_just_ids.clone() {
if let Some(justification) = self.justifications.get(&just_id) {
let dependent_fact = justification.fact_handle;
if !self.has_valid_justification(dependent_fact) {
if !self.retracted_facts.contains(&dependent_fact) {
to_retract.push(dependent_fact);
let cascaded = self.retract_with_cascade(dependent_fact);
to_retract.extend(cascaded);
}
}
}
}
}
to_retract
}
pub fn remove_justifications(&mut self, fact_handle: FactHandle) {
if let Some(just_ids) = self.fact_justifications.remove(&fact_handle) {
for id in just_ids {
self.justifications.remove(&id);
}
}
}
pub fn stats(&self) -> TmsStats {
TmsStats {
total_justifications: self.justifications.len(),
logical_facts: self.logical_facts.len(),
explicit_facts: self.explicit_facts.len(),
retracted_facts: self.retracted_facts.len(),
}
}
pub fn clear(&mut self) {
self.justifications.clear();
self.fact_justifications.clear();
self.fact_dependents.clear();
self.logical_facts.clear();
self.explicit_facts.clear();
self.retracted_facts.clear();
self.next_justification_id = 1;
}
pub fn get_logical_facts(&self) -> &HashSet<FactHandle> {
&self.logical_facts
}
pub fn get_explicit_facts(&self) -> &HashSet<FactHandle> {
&self.explicit_facts
}
}
impl Default for TruthMaintenanceSystem {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TmsStats {
pub total_justifications: usize,
pub logical_facts: usize,
pub explicit_facts: usize,
pub retracted_facts: usize,
}
impl std::fmt::Display for TmsStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"TMS Stats: {} justifications, {} logical facts, {} explicit facts, {} retracted",
self.total_justifications,
self.logical_facts,
self.explicit_facts,
self.retracted_facts
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_explicit_justification() {
let mut tms = TruthMaintenanceSystem::new();
let fact = FactHandle::new(1);
tms.add_explicit_justification(fact);
assert!(tms.is_explicit(fact));
assert!(!tms.is_logical(fact));
assert!(tms.has_valid_justification(fact));
}
#[test]
fn test_logical_justification() {
let mut tms = TruthMaintenanceSystem::new();
let premise = FactHandle::new(1);
let derived = FactHandle::new(2);
tms.add_explicit_justification(premise);
tms.add_logical_justification(derived, "TestRule".to_string(), vec![premise]);
assert!(tms.is_logical(derived));
assert!(!tms.is_explicit(derived));
assert!(tms.has_valid_justification(derived));
}
#[test]
fn test_cascade_retraction() {
let mut tms = TruthMaintenanceSystem::new();
let fact_a = FactHandle::new(1);
let fact_b = FactHandle::new(2);
let fact_c = FactHandle::new(3);
tms.add_explicit_justification(fact_a);
tms.add_logical_justification(fact_b, "Rule1".to_string(), vec![fact_a]);
tms.add_logical_justification(fact_c, "Rule2".to_string(), vec![fact_b]);
let cascaded = tms.retract_with_cascade(fact_a);
assert!(cascaded.contains(&fact_b));
assert!(cascaded.contains(&fact_c));
assert!(!tms.has_valid_justification(fact_b));
assert!(!tms.has_valid_justification(fact_c));
}
#[test]
fn test_multiple_justifications() {
let mut tms = TruthMaintenanceSystem::new();
let premise1 = FactHandle::new(1);
let premise2 = FactHandle::new(2);
let derived = FactHandle::new(3);
tms.add_explicit_justification(premise1);
tms.add_explicit_justification(premise2);
tms.add_logical_justification(derived, "Rule1".to_string(), vec![premise1]);
tms.add_logical_justification(derived, "Rule2".to_string(), vec![premise2]);
let cascaded = tms.retract_with_cascade(premise1);
assert!(tms.has_valid_justification(derived));
assert!(cascaded.is_empty());
let cascaded = tms.retract_with_cascade(premise2);
assert!(!tms.has_valid_justification(derived));
assert!(cascaded.contains(&derived));
}
}