1use std::collections::{HashMap, HashSet, VecDeque};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
4pub struct EventId(pub u64);
5
6#[derive(Debug, Clone)]
7pub struct Event {
8 pub id: EventId,
9 pub timestamp: Option<f64>,
10 pub structural_tag: Option<f64>,
11}
12
13#[derive(Debug, Clone)]
14pub struct DscdEdge {
15 pub edge_id: u64,
16 pub from: EventId,
17 pub to: EventId,
18 pub observer_id: u32,
19 pub trust_value: f64,
20 pub trust_at_creation: f64,
21 pub rewrite_rule_at_source: u32,
22}
23
24#[derive(Debug, Default, Clone)]
25pub struct DscdGraph {
26 pub events: Vec<Event>,
27 pub edges: Vec<DscdEdge>,
28}
29
30impl DscdGraph {
31 pub fn add_event(&mut self, event: Event) {
32 if let Some(existing) = self
33 .events
34 .iter_mut()
35 .find(|candidate| candidate.id == event.id)
36 {
37 *existing = event;
38 return;
39 }
40
41 self.events.push(event);
42 self.events.sort_by_key(|candidate| candidate.id);
43 }
44
45 pub fn has_event(&self, event_id: EventId) -> bool {
46 self.events.iter().any(|event| event.id == event_id)
47 }
48}
49
50pub fn add_trust_gated_edge(
51 graph: &mut DscdGraph,
52 from: EventId,
53 to: EventId,
54 observer_id: u32,
55 trust_value: f64,
56 trust_threshold: f64,
57) {
58 add_trust_gated_edge_with_provenance(
59 graph,
60 from,
61 to,
62 observer_id,
63 trust_value,
64 trust_threshold,
65 0,
66 );
67}
68
69pub fn add_trust_gated_edge_with_provenance(
70 graph: &mut DscdGraph,
71 from: EventId,
72 to: EventId,
73 observer_id: u32,
74 trust_value: f64,
75 trust_threshold: f64,
76 rewrite_rule_at_source: u32,
77) {
78 if from >= to || trust_value < trust_threshold {
79 return;
80 }
81
82 if !(graph.has_event(from) && graph.has_event(to)) {
83 return;
84 }
85
86 if graph
87 .edges
88 .iter()
89 .any(|edge| edge.from == from && edge.to == to && edge.observer_id == observer_id)
90 {
91 return;
92 }
93
94 if reachable_from(graph, to, None).contains(&from) {
95 return;
96 }
97
98 let edge_id = graph.edges.len() as u64;
99 graph.edges.push(DscdEdge {
100 edge_id,
101 from,
102 to,
103 observer_id,
104 trust_value,
105 trust_at_creation: trust_value,
106 rewrite_rule_at_source,
107 });
108}
109
110pub fn reachable_from(graph: &DscdGraph, start: EventId, max_depth: Option<usize>) -> Vec<EventId> {
111 if !graph.has_event(start) {
112 return Vec::new();
113 }
114
115 let mut adjacency: HashMap<EventId, Vec<EventId>> = HashMap::new();
116 for edge in &graph.edges {
117 adjacency.entry(edge.from).or_default().push(edge.to);
118 }
119 for neighbors in adjacency.values_mut() {
120 neighbors.sort_unstable();
121 }
122
123 let mut visited = HashSet::new();
124 let mut queue = VecDeque::from([(start, 0_usize)]);
125 visited.insert(start);
126
127 while let Some((node, depth)) = queue.pop_front() {
128 if max_depth.is_some_and(|limit| depth >= limit) {
129 continue;
130 }
131
132 if let Some(neighbors) = adjacency.get(&node) {
133 for &next in neighbors {
134 if visited.insert(next) {
135 queue.push_back((next, depth + 1));
136 }
137 }
138 }
139 }
140
141 let mut out: Vec<_> = visited.into_iter().collect();
142 out.sort_unstable();
143 out
144}
145
146pub fn expansion_ratio(graph: &DscdGraph, start: EventId) -> f64 {
147 if graph.events.is_empty() {
148 return 0.0;
149 }
150
151 reachable_from(graph, start, None).len() as f64 / graph.events.len() as f64
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 fn toy_graph() -> DscdGraph {
159 let mut graph = DscdGraph::default();
160 for raw_id in 0..4_u64 {
161 graph.add_event(Event {
162 id: EventId(raw_id),
163 timestamp: Some(raw_id as f64),
164 structural_tag: None,
165 });
166 }
167 graph
168 }
169
170 #[test]
171 fn trust_gated_edges_remain_acyclic() {
172 let mut graph = toy_graph();
173 add_trust_gated_edge(&mut graph, EventId(0), EventId(1), 0, 0.9, 0.5);
174 add_trust_gated_edge(&mut graph, EventId(1), EventId(2), 0, 0.9, 0.5);
175 add_trust_gated_edge(&mut graph, EventId(2), EventId(0), 0, 0.9, 0.5);
176 add_trust_gated_edge(&mut graph, EventId(2), EventId(2), 0, 0.9, 0.5);
177
178 assert_eq!(graph.edges.len(), 2);
179 assert!(graph.edges.iter().all(|edge| edge.from < edge.to));
180 }
181
182 #[test]
183 fn reachable_nodes_are_sorted_and_include_start() {
184 let mut graph = toy_graph();
185 add_trust_gated_edge(&mut graph, EventId(0), EventId(1), 0, 0.9, 0.5);
186 add_trust_gated_edge(&mut graph, EventId(1), EventId(3), 0, 0.9, 0.5);
187
188 assert_eq!(
189 reachable_from(&graph, EventId(0), None),
190 vec![EventId(0), EventId(1), EventId(3)]
191 );
192 }
193}