1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Agent {
12 pub id: usize,
14 pub position: Vec<f64>,
16 pub belief: f64,
18 pub constraint: f64,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Block {
25 pub id: usize,
27 pub agents: Vec<usize>,
29 pub center: Vec<f64>,
31 pub mean_belief: f64,
33 pub aggregate_constraint: f64,
35 pub size: usize,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct BlockTransform {
42 pub scale: usize,
44 pub block_size: usize,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum BlockingStrategy {
51 Sequential,
53 SpatialNearest,
55 MajorityRule,
57}
58
59impl BlockTransform {
60 pub fn new(scale: usize, block_size: usize) -> Self {
62 Self { scale, block_size }
63 }
64
65 pub fn transform(&self, agents: &[Agent], strategy: BlockingStrategy) -> Vec<Block> {
68 match strategy {
69 BlockingStrategy::Sequential => self.sequential_blocking(agents),
70 BlockingStrategy::SpatialNearest => self.spatial_blocking(agents),
71 BlockingStrategy::MajorityRule => self.majority_rule_blocking(agents),
72 }
73 }
74
75 fn sequential_blocking(&self, agents: &[Agent]) -> Vec<Block> {
76 let b = self.block_size.max(1);
77 let n = agents.len();
78 let mut blocks = Vec::new();
79 let mut block_id = 0;
80
81 let mut i = 0;
82 while i < n {
83 let end = (i + b).min(n);
84 let block_agents: Vec<usize> = (i..end).map(|j| agents[j].id).collect();
85 let count = end - i;
86
87 let mean_belief: f64 = (i..end).map(|j| agents[j].belief).sum::<f64>() / count as f64;
88 let agg_constraint: f64 = (i..end).map(|j| agents[j].constraint).sum();
89
90 let ndim = agents[i].position.len();
91 let center: Vec<f64> = (0..ndim)
92 .map(|d| (i..end).map(|j| agents[j].position[d]).sum::<f64>() / count as f64)
93 .collect();
94
95 blocks.push(Block {
96 id: block_id,
97 agents: block_agents,
98 center,
99 mean_belief,
100 aggregate_constraint: agg_constraint,
101 size: count,
102 });
103 block_id += 1;
104 i += b;
105 }
106 blocks
107 }
108
109 fn spatial_blocking(&self, agents: &[Agent]) -> Vec<Block> {
110 let mut indexed: Vec<usize> = (0..agents.len()).collect();
112 indexed.sort_by(|&a, &b| {
113 let pa = &agents[a].position;
114 let pb = &agents[b].position;
115 for i in 0..pa.len().min(pb.len()) {
117 let cmp = pa[i].partial_cmp(&pb[i]).unwrap_or(std::cmp::Ordering::Equal);
118 if cmp != std::cmp::Ordering::Equal {
119 return cmp;
120 }
121 }
122 std::cmp::Ordering::Equal
123 });
124
125 let sorted_agents: Vec<Agent> = indexed.iter().map(|&i| agents[i].clone()).collect();
126 let b = self.block_size.max(1);
127 let n = sorted_agents.len();
128 let mut blocks = Vec::new();
129 let mut block_id = 0;
130
131 let mut i = 0;
132 while i < n {
133 let end = (i + b).min(n);
134 let count = end - i;
135 let block_agents: Vec<usize> = (i..end).map(|j| sorted_agents[j].id).collect();
136
137 let mean_belief: f64 = (i..end).map(|j| sorted_agents[j].belief).sum::<f64>() / count as f64;
138 let agg_constraint: f64 = (i..end).map(|j| sorted_agents[j].constraint).sum();
139
140 let ndim = sorted_agents[i].position.len();
141 let center: Vec<f64> = (0..ndim)
142 .map(|d| (i..end).map(|j| sorted_agents[j].position[d]).sum::<f64>() / count as f64)
143 .collect();
144
145 blocks.push(Block {
146 id: block_id,
147 agents: block_agents,
148 center,
149 mean_belief,
150 aggregate_constraint: agg_constraint,
151 size: count,
152 });
153 block_id += 1;
154 i += b;
155 }
156 blocks
157 }
158
159 fn majority_rule_blocking(&self, agents: &[Agent]) -> Vec<Block> {
160 let b = self.block_size.max(1);
161 let n = agents.len();
162 let mut blocks = Vec::new();
163 let mut block_id = 0;
164
165 let mut i = 0;
166 while i < n {
167 let end = (i + b).min(n);
168 let count = end - i;
169 let block_agents: Vec<usize> = (i..end).map(|j| agents[j].id).collect();
170
171 let mean_belief: f64 = (i..end).map(|j| agents[j].belief).sum::<f64>() / count as f64;
172 let block_spin = if mean_belief >= 0.0 { 1.0 } else { -1.0 };
174 let agg_constraint: f64 = (i..end).map(|j| agents[j].constraint).sum();
175
176 let ndim = agents[i].position.len();
177 let center: Vec<f64> = (0..ndim)
178 .map(|d| (i..end).map(|j| agents[j].position[d]).sum::<f64>() / count as f64)
179 .collect();
180
181 blocks.push(Block {
182 id: block_id,
183 agents: block_agents,
184 center,
185 mean_belief: block_spin,
186 aggregate_constraint: agg_constraint,
187 size: count,
188 });
189 block_id += 1;
190 i += b;
191 }
192 blocks
193 }
194}
195
196pub fn blocks_to_agents(blocks: &[Block]) -> Vec<Agent> {
199 blocks
200 .iter()
201 .map(|b| Agent {
202 id: b.id,
203 position: b.center.clone(),
204 belief: b.mean_belief,
205 constraint: b.aggregate_constraint,
206 })
207 .collect()
208}
209
210pub fn magnetization(agents: &[Agent]) -> f64 {
212 if agents.is_empty() {
213 return 0.0;
214 }
215 agents.iter().map(|a| a.belief).sum::<f64>() / agents.len() as f64
216}
217
218pub fn total_constraint(agents: &[Agent]) -> f64 {
220 agents.iter().map(|a| a.constraint).sum()
221}
222
223pub fn ising_energy(agents: &[Agent], j_coupling: f64, h_field: f64) -> f64 {
226 let n = agents.len();
227 if n == 0 {
228 return 0.0;
229 }
230 let mut energy = 0.0;
231 for i in 0..n {
232 energy -= h_field * agents[i].belief;
233 if i + 1 < n {
234 energy -= j_coupling * agents[i].belief * agents[i + 1].belief;
235 }
236 }
237 energy
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 fn make_uniform_agents(n: usize, belief: f64, constraint: f64) -> Vec<Agent> {
245 (0..n)
246 .map(|i| Agent {
247 id: i,
248 position: vec![i as f64],
249 belief,
250 constraint,
251 })
252 .collect()
253 }
254
255 fn make_alternating_agents(n: usize) -> Vec<Agent> {
256 (0..n)
257 .map(|i| Agent {
258 id: i,
259 position: vec![i as f64],
260 belief: if i % 2 == 0 { 1.0 } else { -1.0 },
261 constraint: 1.0,
262 })
263 .collect()
264 }
265
266 #[test]
267 fn test_block_transform_reduces_count() {
268 let agents = make_uniform_agents(16, 1.0, 1.0);
269 let bt = BlockTransform::new(1, 4);
270 let blocks = bt.transform(&agents, BlockingStrategy::Sequential);
271 assert_eq!(blocks.len(), 4); }
273
274 #[test]
275 fn test_block_transform_non_divisible() {
276 let agents = make_uniform_agents(10, 1.0, 1.0);
277 let bt = BlockTransform::new(1, 3);
278 let blocks = bt.transform(&agents, BlockingStrategy::Sequential);
279 assert_eq!(blocks.len(), 4); }
281
282 #[test]
283 fn test_block_mean_belief_uniform() {
284 let agents = make_uniform_agents(8, 0.7, 1.0);
285 let bt = BlockTransform::new(1, 4);
286 let blocks = bt.transform(&agents, BlockingStrategy::Sequential);
287 for b in &blocks {
288 assert!((b.mean_belief - 0.7).abs() < 1e-10);
289 }
290 }
291
292 #[test]
293 fn test_block_aggregate_constraint() {
294 let agents = make_uniform_agents(8, 1.0, 2.0);
295 let bt = BlockTransform::new(1, 4);
296 let blocks = bt.transform(&agents, BlockingStrategy::Sequential);
297 for b in &blocks {
298 assert!((b.aggregate_constraint - 8.0).abs() < 1e-10); }
300 }
301
302 #[test]
303 fn test_block_center_position() {
304 let agents = make_uniform_agents(4, 1.0, 1.0);
305 let bt = BlockTransform::new(1, 2);
306 let blocks = bt.transform(&agents, BlockingStrategy::Sequential);
307 assert!((blocks[0].center[0] - 0.5).abs() < 1e-10); assert!((blocks[1].center[0] - 2.5).abs() < 1e-10); }
310
311 #[test]
312 fn test_sequential_vs_spatial_sorted() {
313 let agents = make_uniform_agents(8, 1.0, 1.0);
314 let bt = BlockTransform::new(1, 2);
315 let seq = bt.transform(&agents, BlockingStrategy::Sequential);
316 let spat = bt.transform(&agents, BlockingStrategy::SpatialNearest);
317 assert_eq!(seq.len(), spat.len());
319 for (s, sp) in seq.iter().zip(spat.iter()) {
320 assert!((s.mean_belief - sp.mean_belief).abs() < 1e-10);
321 }
322 }
323
324 #[test]
325 fn test_majority_rule_positive() {
326 let agents = make_uniform_agents(6, 0.8, 1.0);
327 let bt = BlockTransform::new(1, 3);
328 let blocks = bt.transform(&agents, BlockingStrategy::MajorityRule);
329 for b in &blocks {
330 assert!((b.mean_belief - 1.0).abs() < 1e-10);
331 }
332 }
333
334 #[test]
335 fn test_majority_rule_negative() {
336 let agents = make_uniform_agents(6, -0.8, 1.0);
337 let bt = BlockTransform::new(1, 3);
338 let blocks = bt.transform(&agents, BlockingStrategy::MajorityRule);
339 for b in &blocks {
340 assert!((b.mean_belief - (-1.0)).abs() < 1e-10);
341 }
342 }
343
344 #[test]
345 fn test_magnetization_uniform() {
346 let agents = make_uniform_agents(10, 1.0, 1.0);
347 assert!((magnetization(&agents) - 1.0).abs() < 1e-10);
348 }
349
350 #[test]
351 fn test_magnetization_alternating() {
352 let agents = make_alternating_agents(10);
353 assert!(magnetization(&agents).abs() < 1e-10);
354 }
355
356 #[test]
357 fn test_total_constraint_conservation() {
358 let agents = make_uniform_agents(12, 1.0, 3.0);
359 let total_before = total_constraint(&agents);
360
361 let bt = BlockTransform::new(1, 4);
362 let blocks = bt.transform(&agents, BlockingStrategy::Sequential);
363 let total_after: f64 = blocks.iter().map(|b| b.aggregate_constraint).sum();
364 assert!((total_before - total_after).abs() < 1e-10);
365 }
366
367 #[test]
368 fn test_blocks_to_agents_roundtrip() {
369 let agents = make_uniform_agents(8, 1.0, 1.0);
370 let bt = BlockTransform::new(1, 4);
371 let blocks = bt.transform(&agents, BlockingStrategy::Sequential);
372 let new_agents = blocks_to_agents(&blocks);
373 assert_eq!(new_agents.len(), 2); assert_eq!(new_agents[0].id, 0);
375 assert_eq!(new_agents[1].id, 1);
376 }
377
378 #[test]
379 fn test_iterative_coarse_graining() {
380 let agents = make_uniform_agents(64, 1.0, 1.0);
381 let bt1 = BlockTransform::new(1, 4);
383 let blocks1 = bt1.transform(&agents, BlockingStrategy::Sequential);
384 assert_eq!(blocks1.len(), 16);
385
386 let agents2 = blocks_to_agents(&blocks1);
387 let bt2 = BlockTransform::new(2, 4);
388 let blocks2 = bt2.transform(&agents2, BlockingStrategy::Sequential);
389 assert_eq!(blocks2.len(), 4);
390
391 let agents3 = blocks_to_agents(&blocks2);
392 let bt3 = BlockTransform::new(3, 4);
393 let blocks3 = bt3.transform(&agents3, BlockingStrategy::Sequential);
394 assert_eq!(blocks3.len(), 1);
395 }
396}