Skip to main content

sheaf_coherence/
agent.rs

1use crate::coherence::CoherenceMeasure;
2use crate::error::SheafError;
3use crate::laplacian::SheafLaplacian;
4use crate::section::GlobalSection;
5use crate::sheaf::{CellularSheaf, SheafBuilder};
6
7/// An agent's belief represented as a vector in a stalk.
8#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
9pub struct AgentBelief {
10    /// Agent identifier.
11    pub id: String,
12    /// Belief vector (lives in the stalk space).
13    pub belief_vector: Vec<f64>,
14    /// Confidence in this belief (0..1).
15    pub confidence: f64,
16}
17
18impl AgentBelief {
19    pub fn new(id: impl Into<String>, belief_vector: Vec<f64>, confidence: f64) -> Self {
20        Self {
21            id: id.into(),
22            belief_vector,
23            confidence: confidence.clamp(0.0, 1.0),
24        }
25    }
26}
27
28/// Wraps a group of agents as a cellular sheaf.
29///
30/// Each agent becomes a node, edges are built from a connectivity graph,
31/// and restriction maps default to identity (can be customized).
32#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
33pub struct AgentSheaf {
34    /// The agents and their beliefs.
35    pub agents: Vec<AgentBelief>,
36    /// The underlying cellular sheaf.
37    pub sheaf: CellularSheaf,
38}
39
40impl AgentSheaf {
41    /// Build an agent sheaf where all agents share the same stalk dimension,
42    /// connected as a complete graph with identity restriction maps.
43    pub fn complete(agents: Vec<AgentBelief>) -> Result<Self, SheafError> {
44        if agents.is_empty() {
45            return Err(SheafError::EmptySheaf);
46        }
47        let stalk_dim = agents[0].belief_vector.len();
48        for a in &agents {
49            if a.belief_vector.len() != stalk_dim {
50                return Err(SheafError::BeliefDimensionMismatch {
51                    agent: a.id.clone(),
52                    expected: stalk_dim,
53                    got: a.belief_vector.len(),
54                });
55            }
56        }
57        let n = agents.len();
58        let sheaf = CellularSheaf::complete(n, stalk_dim)?;
59        Ok(Self { agents, sheaf })
60    }
61
62    /// Build an agent sheaf with a path connectivity.
63    pub fn path(agents: Vec<AgentBelief>) -> Result<Self, SheafError> {
64        if agents.is_empty() {
65            return Err(SheafError::EmptySheaf);
66        }
67        let stalk_dim = agents[0].belief_vector.len();
68        for a in &agents {
69            if a.belief_vector.len() != stalk_dim {
70                return Err(SheafError::BeliefDimensionMismatch {
71                    agent: a.id.clone(),
72                    expected: stalk_dim,
73                    got: a.belief_vector.len(),
74                });
75            }
76        }
77        let n = agents.len();
78        let sheaf = if n == 1 {
79            CellularSheaf::constant(1, stalk_dim)?
80        } else {
81            CellularSheaf::path(n, stalk_dim)?
82        };
83        Ok(Self { agents, sheaf })
84    }
85
86    /// Build an agent sheaf with custom edges and identity restriction maps.
87    pub fn with_edges(agents: Vec<AgentBelief>, edges: &[(usize, usize)]) -> Result<Self, SheafError> {
88        if agents.is_empty() {
89            return Err(SheafError::EmptySheaf);
90        }
91        let stalk_dim = agents[0].belief_vector.len();
92        for a in &agents {
93            if a.belief_vector.len() != stalk_dim {
94                return Err(SheafError::BeliefDimensionMismatch {
95                    agent: a.id.clone(),
96                    expected: stalk_dim,
97                    got: a.belief_vector.len(),
98                });
99            }
100        }
101
102        let identity = {
103            let mut m = vec![vec![0.0; stalk_dim]; stalk_dim];
104            for (i, row) in m.iter_mut().enumerate() {
105                row[i] = 1.0;
106            }
107            m
108        };
109
110        let mut builder = SheafBuilder::default();
111        for _ in &agents {
112            builder = builder.add_node(stalk_dim);
113        }
114        for &(i, j) in edges {
115            builder = builder.add_edge(i, j, identity.clone());
116        }
117        let sheaf = builder.build()?;
118
119        Ok(Self { agents, sheaf })
120    }
121
122    /// Flatten all agent beliefs into a single vector.
123    pub fn flat_beliefs(&self) -> Vec<f64> {
124        self.agents.iter().flat_map(|a| a.belief_vector.iter().copied()).collect()
125    }
126
127    /// Compute the sheaf Laplacian.
128    pub fn laplacian(&self) -> Result<SheafLaplacian, SheafError> {
129        SheafLaplacian::from_sheaf(&self.sheaf)
130    }
131
132    /// Measure coherence of current beliefs.
133    pub fn coherence(&self, max_iter: usize, tol: f64) -> Result<CoherenceMeasure, SheafError> {
134        let flat = self.flat_beliefs();
135        CoherenceMeasure::from_flat(&self.sheaf, &flat, max_iter, tol)
136    }
137
138    /// Check if current beliefs form a global section.
139    pub fn global_section(&self, tol: f64) -> Result<GlobalSection, SheafError> {
140        let values: Vec<Vec<f64>> = self.agents.iter().map(|a| a.belief_vector.clone()).collect();
141        GlobalSection::new(&self.sheaf, values, tol)
142    }
143
144    /// Number of agents.
145    pub fn len(&self) -> usize {
146        self.agents.len()
147    }
148
149    /// True if there are no agents.
150    pub fn is_empty(&self) -> bool {
151        self.agents.is_empty()
152    }
153}