use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, PartialEq)]
pub enum Justification {
Axiom,
Derived {
from: Vec<usize>,
},
}
#[derive(Debug, Clone)]
pub struct TmsNode {
pub id: usize,
pub statement: String,
pub supported: bool,
pub justification: Justification,
}
#[derive(Debug, Default)]
pub struct TruthMaintenanceSystem {
nodes: Vec<TmsNode>,
assumptions: HashSet<usize>,
}
impl TruthMaintenanceSystem {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
assumptions: HashSet::new(),
}
}
pub fn add_axiom(&mut self, statement: impl Into<String>) -> usize {
let id = self.nodes.len();
self.nodes.push(TmsNode {
id,
statement: statement.into(),
supported: true,
justification: Justification::Axiom,
});
id
}
pub fn add_derived(&mut self, statement: impl Into<String>, from: Vec<usize>) -> usize {
let id = self.nodes.len();
let supported = from
.iter()
.all(|&dep| self.nodes.get(dep).map(|n| n.supported).unwrap_or(false));
self.nodes.push(TmsNode {
id,
statement: statement.into(),
supported,
justification: Justification::Derived { from },
});
id
}
pub fn retract(&mut self, node_id: usize) {
if node_id >= self.nodes.len() {
return;
}
self.assumptions.insert(node_id);
self.nodes[node_id].supported = false;
let affected = self.reachable(node_id);
for dep_id in affected {
self.nodes[dep_id].supported = false;
}
}
pub fn restore(&mut self, node_id: usize) {
if node_id >= self.nodes.len() {
return;
}
self.assumptions.remove(&node_id);
let supported = self.compute_supported(node_id);
self.nodes[node_id].supported = supported;
let downstream = self.reachable(node_id);
for dep_id in downstream {
let s = self.compute_supported(dep_id);
self.nodes[dep_id].supported = s;
}
}
pub fn is_supported(&self, node_id: usize) -> bool {
self.nodes
.get(node_id)
.map(|n| n.supported)
.unwrap_or(false)
}
pub fn supported_count(&self) -> usize {
self.nodes.iter().filter(|n| n.supported).count()
}
pub fn unsupported_count(&self) -> usize {
self.nodes.iter().filter(|n| !n.supported).count()
}
pub fn dependents(&self, node_id: usize) -> Vec<usize> {
self.nodes
.iter()
.filter(|n| match &n.justification {
Justification::Derived { from } => from.contains(&node_id),
_ => false,
})
.map(|n| n.id)
.collect()
}
pub fn all_nodes(&self) -> &[TmsNode] {
&self.nodes
}
pub fn reachable(&self, root_id: usize) -> Vec<usize> {
let mut visited = HashSet::new();
let mut stack = vec![root_id];
while let Some(current) = stack.pop() {
for dep in self.dependents(current) {
if visited.insert(dep) {
stack.push(dep);
}
}
}
let mut result: Vec<usize> = visited.into_iter().collect();
result.sort_unstable();
result
}
fn compute_supported(&self, id: usize) -> bool {
if self.assumptions.contains(&id) {
return false;
}
match self.nodes.get(id) {
None => false,
Some(node) => match &node.justification {
Justification::Axiom => true,
Justification::Derived { from } => from.iter().all(|&dep| self.is_supported(dep)),
},
}
}
#[allow(dead_code)]
fn build_dependent_index(&self) -> HashMap<usize, Vec<usize>> {
let mut index: HashMap<usize, Vec<usize>> = HashMap::new();
for node in &self.nodes {
if let Justification::Derived { from } = &node.justification {
for &dep in from {
index.entry(dep).or_default().push(node.id);
}
}
}
index
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_tms_is_empty() {
let tms = TruthMaintenanceSystem::new();
assert_eq!(tms.all_nodes().len(), 0);
assert_eq!(tms.supported_count(), 0);
assert_eq!(tms.unsupported_count(), 0);
}
#[test]
fn test_add_axiom_returns_id() {
let mut tms = TruthMaintenanceSystem::new();
let id = tms.add_axiom("A");
assert_eq!(id, 0);
}
#[test]
fn test_axiom_is_supported() {
let mut tms = TruthMaintenanceSystem::new();
let id = tms.add_axiom("A");
assert!(tms.is_supported(id));
}
#[test]
fn test_multiple_axioms_ids_sequential() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_axiom("B");
assert_eq!(a, 0);
assert_eq!(b, 1);
}
#[test]
fn test_derived_node_supported_when_deps_supported() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_axiom("B");
let c = tms.add_derived("C", vec![a, b]);
assert!(tms.is_supported(c));
}
#[test]
fn test_derived_node_unsupported_when_dep_unsupported() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_derived("B", vec![a]);
tms.retract(a);
assert!(!tms.is_supported(b));
}
#[test]
fn test_derived_node_with_no_deps_is_supported() {
let mut tms = TruthMaintenanceSystem::new();
let c = tms.add_derived("C", vec![]);
assert!(tms.is_supported(c));
}
#[test]
fn test_retract_axiom() {
let mut tms = TruthMaintenanceSystem::new();
let id = tms.add_axiom("A");
tms.retract(id);
assert!(!tms.is_supported(id));
}
#[test]
fn test_retract_propagates_one_level() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_derived("B <- A", vec![a]);
tms.retract(a);
assert!(!tms.is_supported(a));
assert!(!tms.is_supported(b));
}
#[test]
fn test_retract_propagates_two_levels() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_derived("B <- A", vec![a]);
let c = tms.add_derived("C <- B", vec![b]);
tms.retract(a);
assert!(!tms.is_supported(b));
assert!(!tms.is_supported(c));
}
#[test]
fn test_retract_does_not_affect_unrelated() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_axiom("B");
tms.retract(a);
assert!(!tms.is_supported(a));
assert!(tms.is_supported(b));
}
#[test]
fn test_retract_invalid_id_is_noop() {
let mut tms = TruthMaintenanceSystem::new();
tms.retract(999); }
#[test]
fn test_restore_axiom() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
tms.retract(a);
tms.restore(a);
assert!(tms.is_supported(a));
}
#[test]
fn test_restore_propagates_to_derived() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_derived("B <- A", vec![a]);
tms.retract(a);
assert!(!tms.is_supported(b));
tms.restore(a);
assert!(tms.is_supported(a));
assert!(tms.is_supported(b));
}
#[test]
fn test_restore_does_not_restore_if_other_dep_still_retracted() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_axiom("B");
let c = tms.add_derived("C <- A, B", vec![a, b]);
tms.retract(a);
tms.retract(b);
tms.restore(a);
assert!(!tms.is_supported(c));
}
#[test]
fn test_counts_after_retract() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let _b = tms.add_axiom("B");
let _c = tms.add_derived("C <- A", vec![a]);
tms.retract(a);
assert_eq!(tms.supported_count(), 1); assert_eq!(tms.unsupported_count(), 2); }
#[test]
fn test_counts_all_supported() {
let mut tms = TruthMaintenanceSystem::new();
let _a = tms.add_axiom("A");
let _b = tms.add_axiom("B");
assert_eq!(tms.supported_count(), 2);
assert_eq!(tms.unsupported_count(), 0);
}
#[test]
fn test_dependents_empty_when_none() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
assert!(tms.dependents(a).is_empty());
}
#[test]
fn test_dependents_returns_derived_nodes() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_derived("B <- A", vec![a]);
let c = tms.add_derived("C <- A", vec![a]);
let mut deps = tms.dependents(a);
deps.sort_unstable();
assert_eq!(deps, vec![b, c]);
}
#[test]
fn test_reachable_empty() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
assert!(tms.reachable(a).is_empty());
}
#[test]
fn test_reachable_chain() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_derived("B <- A", vec![a]);
let c = tms.add_derived("C <- B", vec![b]);
let reachable = tms.reachable(a);
assert!(reachable.contains(&b));
assert!(reachable.contains(&c));
}
#[test]
fn test_reachable_diamond() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_derived("B <- A", vec![a]);
let c = tms.add_derived("C <- A", vec![a]);
let d = tms.add_derived("D <- B, C", vec![b, c]);
let reachable = tms.reachable(a);
assert!(reachable.contains(&b));
assert!(reachable.contains(&c));
assert!(reachable.contains(&d));
assert_eq!(reachable.len(), 3);
}
#[test]
fn test_all_nodes_returns_correct_slice() {
let mut tms = TruthMaintenanceSystem::new();
tms.add_axiom("A");
tms.add_axiom("B");
let nodes = tms.all_nodes();
assert_eq!(nodes.len(), 2);
assert_eq!(nodes[0].statement, "A");
assert_eq!(nodes[1].statement, "B");
}
#[test]
fn test_axiom_justification() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
assert_eq!(tms.all_nodes()[a].justification, Justification::Axiom);
}
#[test]
fn test_derived_justification_from_ids() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
let b = tms.add_derived("B", vec![a]);
assert_eq!(
tms.all_nodes()[b].justification,
Justification::Derived { from: vec![a] }
);
}
#[test]
fn test_is_supported_out_of_range() {
let tms = TruthMaintenanceSystem::new();
assert!(!tms.is_supported(999));
}
#[test]
fn test_double_retract_is_idempotent() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
tms.retract(a);
tms.retract(a);
assert!(!tms.is_supported(a));
}
#[test]
fn test_restore_then_retract() {
let mut tms = TruthMaintenanceSystem::new();
let a = tms.add_axiom("A");
tms.retract(a);
tms.restore(a);
assert!(tms.is_supported(a));
tms.retract(a);
assert!(!tms.is_supported(a));
}
}