1pub mod agent;
38pub mod coherence;
39pub mod error;
40pub mod laplacian;
41pub mod section;
42pub mod sheaf;
43
44pub use agent::{AgentBelief, AgentSheaf};
45pub use coherence::CoherenceMeasure;
46pub use error::SheafError;
47pub use laplacian::SheafLaplacian;
48pub use section::GlobalSection;
49pub use sheaf::{CellularSheaf, SheafBuilder};
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
58 fn test_constant_sheaf() {
59 let s = CellularSheaf::constant(4, 3).unwrap();
60 assert_eq!(s.node_count(), 4);
61 assert_eq!(s.total_dim(), 12);
62 assert!(s.restriction_maps.is_empty());
63 }
64
65 #[test]
66 fn test_path_sheaf() {
67 let s = CellularSheaf::path(3, 2).unwrap();
68 assert_eq!(s.node_count(), 3);
69 assert_eq!(s.restriction_maps.len(), 2); }
71
72 #[test]
73 fn test_cycle_sheaf() {
74 let s = CellularSheaf::cycle(4, 2).unwrap();
75 assert_eq!(s.restriction_maps.len(), 4); }
77
78 #[test]
79 fn test_complete_sheaf() {
80 let s = CellularSheaf::complete(4, 2).unwrap();
81 assert_eq!(s.restriction_maps.len(), 6);
83 }
84
85 #[test]
86 fn test_empty_sheaf_rejected() {
87 assert!(CellularSheaf::constant(0, 2).is_err());
88 }
89
90 #[test]
91 fn test_cycle_too_small() {
92 assert!(CellularSheaf::cycle(2, 1).is_err());
93 }
94
95 #[test]
96 fn test_builder_custom_sheaf() {
97 let s = CellularSheaf::builder()
98 .add_node(2)
99 .add_node(2)
100 .add_node(3)
101 .add_edge(0, 1, vec![vec![1.0, 0.0], vec![0.0, 1.0]])
102 .build()
103 .unwrap();
104 assert_eq!(s.node_count(), 3);
105 assert_eq!(s.total_dim(), 7);
106 assert_eq!(s.restriction_maps.len(), 1);
107 }
108
109 #[test]
110 fn test_dimension_mismatch_detected() {
111 let res = CellularSheaf::builder()
113 .add_node(2)
114 .add_node(3)
115 .add_edge(0, 1, vec![vec![1.0, 0.0], vec![0.0, 1.0]])
116 .build();
117 assert!(matches!(res, Err(SheafError::DimensionMismatch { .. })));
118 }
119
120 #[test]
121 fn test_get_restriction_map() {
122 let s = CellularSheaf::path(3, 2).unwrap();
123 let m = s.get_restriction_map(0, 1);
124 assert!(m.is_some());
125 let m = s.get_restriction_map(1, 0);
126 assert!(m.is_some()); let m = s.get_restriction_map(0, 2);
128 assert!(m.is_none());
129 }
130
131 #[test]
134 fn test_laplacian_single_node() {
135 let s = CellularSheaf::constant(1, 2).unwrap();
136 let lap = SheafLaplacian::from_sheaf(&s).unwrap();
137 assert_eq!(lap.n, 2);
138 assert_eq!(lap.matrix, vec![vec![0.0; 2]; 2]);
140 }
141
142 #[test]
143 fn test_laplacian_path_2_nodes() {
144 let s = CellularSheaf::path(2, 1).unwrap();
145 let lap = SheafLaplacian::from_sheaf(&s).unwrap();
146 assert_eq!(lap.matrix, vec![vec![1.0, -1.0], vec![-1.0, 1.0]]);
149 }
150
151 #[test]
152 fn test_laplacian_complete_3_nodes_1d() {
153 let s = CellularSheaf::complete(3, 1).unwrap();
154 let lap = SheafLaplacian::from_sheaf(&s).unwrap();
155 assert!((lap.matrix[0][0] - 2.0).abs() < 1e-10);
158 assert!((lap.matrix[0][1] - (-1.0)).abs() < 1e-10);
159 }
160
161 #[test]
162 fn test_laplacian_quadratic_form_constant_section() {
163 let s = CellularSheaf::path(3, 2).unwrap();
165 let lap = SheafLaplacian::from_sheaf(&s).unwrap();
166 let x = vec![1.0, 0.0, 1.0, 0.0, 1.0, 0.0]; let q = lap.quadratic_form(&x);
168 assert!(q.abs() < 1e-10, "constant section should have zero energy, got {q}");
169 }
170
171 #[test]
172 fn test_laplacian_apply_zero_for_global_section() {
173 let s = CellularSheaf::complete(3, 2).unwrap();
174 let lap = SheafLaplacian::from_sheaf(&s).unwrap();
175 let x = vec![2.0, 3.0, 2.0, 3.0, 2.0, 3.0];
177 let lx = lap.apply(&x);
178 for v in &lx {
179 assert!(v.abs() < 1e-10, "L_F x should be zero for global section");
180 }
181 }
182
183 #[test]
184 fn test_laplacian_eigenvalues() {
185 let s = CellularSheaf::path(2, 1).unwrap();
186 let lap = SheafLaplacian::from_sheaf(&s).unwrap();
187 let (largest, _) = lap.power_iteration(500, 1e-10);
189 assert!((largest - 2.0).abs() < 0.05, "largest eigenvalue should be ~2, got {largest}");
190 let (smallest, _) = lap.smallest_eigenvalue(500, 1e-10);
192 assert!(smallest.abs() < 0.05, "smallest eigenvalue should be ~0, got {smallest}");
193 }
194
195 #[test]
198 fn test_global_section_exact() {
199 let s = CellularSheaf::complete(3, 1).unwrap();
200 let values = vec![vec![1.0], vec![1.0], vec![1.0]];
201 let gs = GlobalSection::new(&s, values, 1e-8).unwrap();
202 assert!(gs.is_exact);
203 assert!(gs.residual < 1e-8);
204 }
205
206 #[test]
207 fn test_global_section_not_exact() {
208 let s = CellularSheaf::complete(3, 1).unwrap();
209 let values = vec![vec![1.0], vec![0.0], vec![0.0]];
210 let gs = GlobalSection::new(&s, values, 1e-8).unwrap();
211 assert!(!gs.is_exact);
212 assert!(gs.residual > 0.1);
213 }
214
215 #[test]
216 fn test_find_global_section() {
217 let s = CellularSheaf::complete(3, 1).unwrap();
218 let gs = GlobalSection::find(&s, 200, 1e-10).unwrap();
219 assert!(gs.is_exact); }
221
222 #[test]
223 fn test_section_flatten() {
224 let s = CellularSheaf::path(2, 2).unwrap();
225 let values = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
226 let gs = GlobalSection::new(&s, values, 1e-8).unwrap();
227 assert_eq!(gs.flatten(), vec![1.0, 2.0, 3.0, 4.0]);
228 }
229
230 #[test]
233 fn test_perfect_coherence() {
234 let s = CellularSheaf::complete(3, 2).unwrap();
235 let x = vec![1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
236 let c = CoherenceMeasure::from_flat(&s, &x, 100, 1e-10).unwrap();
237 assert!(c.alignment > 0.99, "alignment = {}", c.alignment);
238 for d in &c.disagreement {
239 assert!(d < &0.01, "per-edge disagreement should be ~0");
240 }
241 }
242
243 #[test]
244 fn test_zero_coherence() {
245 let s = CellularSheaf::path(2, 1).unwrap();
246 let x = vec![1.0, -1.0]; let c = CoherenceMeasure::from_flat(&s, &x, 100, 1e-10).unwrap();
248 assert!(c.alignment < 0.01, "alignment should be near 0, got {}", c.alignment);
249 }
250
251 #[test]
252 fn test_partial_coherence() {
253 let s = CellularSheaf::complete(3, 2).unwrap();
254 let x = vec![1.0, 0.0, 1.0, 0.0, 0.8, 0.6];
256 let c = CoherenceMeasure::from_flat(&s, &x, 100, 1e-10).unwrap();
257 assert!(c.alignment > 0.0 && c.alignment < 1.0, "alignment = {}", c.alignment);
259 }
260
261 #[test]
262 fn test_disagreement_per_edge() {
263 let s = CellularSheaf::path(3, 1).unwrap();
264 let x = vec![0.0, 1.0, 2.0];
265 let c = CoherenceMeasure::from_flat(&s, &x, 100, 1e-10).unwrap();
266 assert_eq!(c.disagreement.len(), 2); }
268
269 #[test]
270 fn test_avg_and_max_disagreement() {
271 let s = CellularSheaf::path(3, 1).unwrap();
272 let x = vec![0.0, 1.0, 2.0];
273 let c = CoherenceMeasure::from_flat(&s, &x, 100, 1e-10).unwrap();
274 assert!(c.max_disagreement() >= c.avg_disagreement());
275 }
276
277 #[test]
278 fn test_is_aligned() {
279 let s = CellularSheaf::complete(3, 1).unwrap();
280 let x = vec![1.0, 1.0, 1.0];
281 let c = CoherenceMeasure::from_flat(&s, &x, 100, 1e-10).unwrap();
282 assert!(c.is_aligned(0.95));
283 }
284
285 #[test]
286 fn test_dominant_mode_nonempty() {
287 let s = CellularSheaf::complete(3, 2).unwrap();
288 let x = vec![1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
289 let c = CoherenceMeasure::from_flat(&s, &x, 100, 1e-10).unwrap();
290 assert_eq!(c.dominant_mode.len(), 6);
291 }
292
293 #[test]
296 fn test_agent_sheaf_complete() {
297 let agents = vec![
298 AgentBelief::new("a", vec![1.0, 0.0], 0.9),
299 AgentBelief::new("b", vec![1.0, 0.0], 0.8),
300 ];
301 let asheaf = AgentSheaf::complete(agents).unwrap();
302 assert_eq!(asheaf.len(), 2);
303 assert_eq!(asheaf.sheaf.restriction_maps.len(), 1);
304 }
305
306 #[test]
307 fn test_agent_sheaf_path() {
308 let agents = vec![
309 AgentBelief::new("a", vec![1.0], 1.0),
310 AgentBelief::new("b", vec![1.0], 1.0),
311 AgentBelief::new("c", vec![1.0], 1.0),
312 ];
313 let asheaf = AgentSheaf::path(agents).unwrap();
314 assert_eq!(asheaf.sheaf.restriction_maps.len(), 2);
315 }
316
317 #[test]
318 fn test_agent_sheaf_custom_edges() {
319 let agents = vec![
320 AgentBelief::new("a", vec![1.0], 1.0),
321 AgentBelief::new("b", vec![1.0], 1.0),
322 AgentBelief::new("c", vec![1.0], 1.0),
323 ];
324 let asheaf = AgentSheaf::with_edges(agents, &[(0, 1), (1, 2)]).unwrap();
325 assert_eq!(asheaf.sheaf.restriction_maps.len(), 2);
326 }
327
328 #[test]
329 fn test_agent_coherence_perfect() {
330 let agents = vec![
331 AgentBelief::new("a", vec![5.0, 3.0], 1.0),
332 AgentBelief::new("b", vec![5.0, 3.0], 1.0),
333 AgentBelief::new("c", vec![5.0, 3.0], 1.0),
334 ];
335 let asheaf = AgentSheaf::complete(agents).unwrap();
336 let coh = asheaf.coherence(200, 1e-10).unwrap();
337 assert!(coh.alignment > 0.99);
338 }
339
340 #[test]
341 fn test_agent_global_section() {
342 let agents = vec![
343 AgentBelief::new("a", vec![1.0, 0.0], 1.0),
344 AgentBelief::new("b", vec![1.0, 0.0], 1.0),
345 ];
346 let asheaf = AgentSheaf::complete(agents).unwrap();
347 let gs = asheaf.global_section(1e-8).unwrap();
348 assert!(gs.is_exact);
349 }
350
351 #[test]
352 fn test_agent_dimension_mismatch() {
353 let agents = vec![
354 AgentBelief::new("a", vec![1.0, 0.0], 1.0),
355 AgentBelief::new("b", vec![1.0], 1.0), ];
357 assert!(AgentSheaf::complete(agents).is_err());
358 }
359
360 #[test]
361 fn test_agent_flat_beliefs() {
362 let agents = vec![
363 AgentBelief::new("a", vec![1.0, 2.0], 1.0),
364 AgentBelief::new("b", vec![3.0, 4.0], 1.0),
365 ];
366 let asheaf = AgentSheaf::complete(agents).unwrap();
367 assert_eq!(asheaf.flat_beliefs(), vec![1.0, 2.0, 3.0, 4.0]);
368 }
369
370 #[test]
371 fn test_agent_confidence_clamped() {
372 let a = AgentBelief::new("x", vec![1.0], 1.5);
373 assert!((a.confidence - 1.0).abs() < 1e-10);
374 let a = AgentBelief::new("x", vec![1.0], -0.5);
375 assert!(a.confidence.abs() < 1e-10);
376 }
377
378 #[test]
379 fn test_agent_empty_rejected() {
380 let agents: Vec<AgentBelief> = vec![];
381 assert!(AgentSheaf::complete(agents).is_err());
382 }
383
384 #[test]
387 fn test_serde_roundtrip() {
388 let s = CellularSheaf::complete(3, 2).unwrap();
389 let json = serde_json::to_string(&s).unwrap();
390 let s2: CellularSheaf = serde_json::from_str(&json).unwrap();
391 assert_eq!(s.node_count(), s2.node_count());
392 assert_eq!(s.restriction_maps.len(), s2.restriction_maps.len());
393 }
394}