Skip to main content

forge_reasoning/belief/
mod.rs

1//! Belief dependency graph with cycle detection
2//!
3//! This module provides a directed graph model for dependencies between beliefs (hypotheses).
4//! It enables automatic cycle detection using Tarjan's SCC algorithm and provides query APIs
5//! for dependency chains.
6
7mod graph;
8
9pub use graph::BeliefGraph;
10
11use crate::hypothesis::HypothesisBoard;
12use crate::hypothesis::types::HypothesisId;
13use crate::errors::Result;
14
15/// Combined reasoning system with hypotheses and belief dependencies
16pub struct ReasoningSystem {
17    pub board: HypothesisBoard,
18    pub graph: BeliefGraph,
19}
20
21impl ReasoningSystem {
22    pub fn new(board: HypothesisBoard) -> Self {
23        Self {
24            board,
25            graph: BeliefGraph::new(),
26        }
27    }
28
29    pub fn in_memory() -> Self {
30        Self::new(HypothesisBoard::in_memory())
31    }
32
33    /// Create a dependency edge between hypotheses
34    pub async fn add_dependency(
35        &mut self,
36        hypothesis_id: HypothesisId,
37        depends_on: HypothesisId,
38    ) -> Result<()> {
39        // Verify both hypotheses exist
40        if self.board.get(hypothesis_id).await?.is_none() {
41            return Err(crate::errors::ReasoningError::NotFound(
42                format!("Hypothesis {} not found", hypothesis_id)
43            ));
44        }
45        if self.board.get(depends_on).await?.is_none() {
46            return Err(crate::errors::ReasoningError::NotFound(
47                format!("Hypothesis {} not found", depends_on)
48            ));
49        }
50
51        self.graph.add_dependency(hypothesis_id, depends_on)
52    }
53
54    /// Remove a dependency edge
55    pub fn remove_dependency(
56        &mut self,
57        hypothesis_id: HypothesisId,
58        depends_on: HypothesisId,
59    ) -> Result<bool> {
60        self.graph.remove_dependency(hypothesis_id, depends_on)
61    }
62
63    /// Get dependents (what depends on this hypothesis)
64    pub fn dependents(&self, hypothesis_id: HypothesisId) -> Result<indexmap::IndexSet<HypothesisId>> {
65        self.graph.dependents(hypothesis_id)
66    }
67
68    /// Get dependees (what this hypothesis depends on)
69    pub fn dependees(&self, hypothesis_id: HypothesisId) -> Result<indexmap::IndexSet<HypothesisId>> {
70        self.graph.dependees(hypothesis_id)
71    }
72
73    /// Get full dependency chain
74    pub fn dependency_chain(&self, hypothesis_id: HypothesisId) -> Result<indexmap::IndexSet<HypothesisId>> {
75        self.graph.dependency_chain(hypothesis_id)
76    }
77
78    /// Detect cycles in the belief graph
79    pub fn detect_cycles(&self) -> Vec<Vec<HypothesisId>> {
80        self.graph.detect_cycles()
81    }
82
83    /// Remove a hypothesis and its graph node
84    pub async fn remove_hypothesis(&mut self, id: HypothesisId) -> Result<bool> {
85        let removed_from_board = self.board.delete(id).await?;
86        let removed_from_graph = self.graph.remove_hypothesis(id)?;
87        Ok(removed_from_board || removed_from_graph)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use crate::hypothesis::confidence::Confidence;
95
96    #[tokio::test]
97    async fn test_reasoning_system_integration() {
98        let mut system = ReasoningSystem::in_memory();
99
100        // Create hypotheses
101        let h1 = system.board.propose("H1", Confidence::new(0.5).unwrap()).await.unwrap();
102        let h2 = system.board.propose("H2", Confidence::new(0.5).unwrap()).await.unwrap();
103        let h3 = system.board.propose("H3", Confidence::new(0.5).unwrap()).await.unwrap();
104
105        // Create dependencies: H1 depends on H2, H2 depends on H3
106        system.add_dependency(h1, h2).await.unwrap();
107        system.add_dependency(h2, h3).await.unwrap();
108
109        // Query dependency chain
110        let chain = system.dependency_chain(h1).unwrap();
111        assert_eq!(chain.len(), 2);
112        assert!(chain.contains(&h2));
113        assert!(chain.contains(&h3));
114
115        // Detect cycles (should be none)
116        assert_eq!(system.detect_cycles().len(), 0);
117    }
118
119    #[tokio::test]
120    async fn test_cycle_prevention() {
121        let mut system = ReasoningSystem::in_memory();
122
123        let h1 = system.board.propose("H1", Confidence::new(0.5).unwrap()).await.unwrap();
124        let h2 = system.board.propose("H2", Confidence::new(0.5).unwrap()).await.unwrap();
125
126        system.add_dependency(h1, h2).await.unwrap();
127
128        // Try to create a cycle
129        let result = system.add_dependency(h2, h1).await;
130        assert!(result.is_err());
131    }
132}