1use super::equilibration::EquilCheckpoint;
2use super::overlap::OverlapStats;
3
4pub struct ClusterSnapshot {
5 pub sweep_id: usize,
6 pub mode_idx: usize,
7 pub cluster_ids: Vec<Vec<u32>>,
8 pub blue_ids: Option<Vec<Vec<u32>>>,
9 pub spins: Vec<[Vec<i8>; 2]>,
10 pub system_ids: Vec<[usize; 2]>,
11}
12
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15pub struct OverlapMoveCounts {
16 pub groups: u64,
18 pub moved_sites: u64,
21 pub counted_groups: u64,
24 pub active_sites: u64,
27}
28
29impl OverlapMoveCounts {
30 fn add(&mut self, other: &Self) {
31 self.groups += other.groups;
32 self.moved_sites += other.moved_sites;
33 self.counted_groups += other.counted_groups;
34 self.active_sites += other.active_sites;
35 }
36}
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
40pub struct RmcEdgeCounts {
41 pub proposed: u64,
43 pub accepted: u64,
45 pub flipped_sites: u64,
47}
48
49impl RmcEdgeCounts {
50 fn add(&mut self, other: &Self) {
51 self.proposed += other.proposed;
52 self.accepted += other.accepted;
53 self.flipped_sites += other.flipped_sites;
54 }
55}
56
57pub(crate) fn fold_counts<T: Copy + Default>(
59 slots: &[T],
60 n_groups: usize,
61 group_of: impl Fn(usize) -> usize,
62 add: impl Fn(&mut T, &T),
63) -> Vec<T> {
64 let mut folded = vec![T::default(); n_groups];
65 for (slot, counts) in slots.iter().enumerate() {
66 add(&mut folded[group_of(slot)], counts);
67 }
68 folded
69}
70
71pub(crate) fn add_move_counts(total: &mut OverlapMoveCounts, part: &OverlapMoveCounts) {
72 total.add(part);
73}
74
75pub(crate) fn add_rmc_counts(total: &mut RmcEdgeCounts, part: &RmcEdgeCounts) {
76 total.add(part);
77}
78
79pub struct ClusterStats {
80 pub fk_csd: Vec<Vec<u64>>,
82 pub overlap_csd: Vec<Vec<Vec<u64>>>,
86 pub top_cluster_sizes: Vec<Vec<[f64; 4]>>,
90 pub fk_updates: Vec<u64>,
93 pub fk_visited_spins: Vec<u64>,
98 pub overlap_moves: Vec<Vec<OverlapMoveCounts>>,
101 pub rmc_edges: Vec<RmcEdgeCounts>,
104}
105
106#[derive(Clone, Debug)]
107pub struct GraphObservationSummary {
108 pub observation_count: Vec<u64>,
109 pub cluster_size_counts: Vec<Vec<u64>>,
110 pub top_four_component_fractions: Vec<[f64; 4]>,
111 pub active_bond_density: Vec<f64>,
112 pub large_component_count: Vec<f64>,
113 pub winding: Option<Vec<[f64; 4]>>,
117 pub wrapping_axes: Option<Vec<Vec<f64>>>,
121 pub wrapping_any: Option<Vec<f64>>,
123 pub wrapping_all: Option<Vec<f64>>,
125 pub wrapping_cluster_count: Option<Vec<f64>>,
127 pub wrapping_fraction: Option<Vec<f64>>,
129 pub cluster_s2: Vec<f64>,
133 pub cluster_s4: Vec<f64>,
134 pub cluster_s2_squared: Vec<f64>,
135}
136
137#[derive(Clone, Debug, Default)]
138pub struct ClusterObservations {
139 pub fk: Option<GraphObservationSummary>,
140 pub houdayer: Option<GraphObservationSummary>,
141 pub jorg: Option<GraphObservationSummary>,
142 pub cmr_blue: Option<GraphObservationSummary>,
143}
144
145pub struct Diagnostics {
146 pub mags2_tau: Vec<f64>,
151 pub overlap2_tau: Vec<f64>,
154 pub energy_tau: Vec<f64>,
157 pub mags2_tau_converged: Vec<bool>,
161 pub overlap2_tau_converged: Vec<bool>,
162 pub energy_tau_converged: Vec<bool>,
163 pub autocorrelation_lag: Option<usize>,
166 pub equil_checkpoints: Vec<EquilCheckpoint>,
170}
171
172impl Diagnostics {
173 fn empty() -> Self {
174 Self {
175 mags2_tau: vec![],
176 overlap2_tau: vec![],
177 energy_tau: vec![],
178 mags2_tau_converged: vec![],
179 overlap2_tau_converged: vec![],
180 energy_tau_converged: vec![],
181 autocorrelation_lag: None,
182 equil_checkpoints: vec![],
183 }
184 }
185}
186
187#[derive(Clone, Debug, Default)]
194pub struct DisorderSample {
195 pub mags2: Vec<f64>,
196 pub mags4: Vec<f64>,
197 pub energies: Vec<f64>,
198 pub energy_variance: Vec<f64>,
199 pub overlap2: Vec<f64>,
200 pub overlap4: Vec<f64>,
201 pub link_overlap: Vec<f64>,
202 pub link_overlap2: Vec<f64>,
203 pub link_overlap4: Vec<f64>,
204 pub mags2_tau: Vec<f64>,
205 pub overlap2_tau: Vec<f64>,
206 pub energy_tau: Vec<f64>,
207 pub mags2_tau_converged: Vec<bool>,
208 pub overlap2_tau_converged: Vec<bool>,
209 pub energy_tau_converged: Vec<bool>,
210}
211
212pub struct SweepResult {
218 pub mags: Vec<f64>,
220 pub mags2: Vec<f64>,
222 pub mags4: Vec<f64>,
224 pub energies: Vec<f64>,
228 pub energies2: Vec<f64>,
230 pub energy_variance: Vec<f64>,
235 pub overlap_stats: OverlapStats,
236 pub cluster_stats: ClusterStats,
237 pub per_disorder_physics: Vec<super::physics::PhysicsResult>,
238 pub per_disorder_cluster_observations: Vec<ClusterObservations>,
239 pub per_disorder: Vec<DisorderSample>,
241 pub diagnostics: Diagnostics,
242 pub cluster_snapshots: Vec<ClusterSnapshot>,
243}
244
245fn mean_over<'a, R>(results: &'a [R], field: impl Fn(&'a R) -> &'a [f64]) -> Vec<f64> {
248 let mut mean = vec![0.0; results.first().map_or(0, |first| field(first).len())];
249 for result in results {
250 for (total, &value) in mean.iter_mut().zip(field(result)) {
251 *total += value;
252 }
253 }
254 let n = results.len() as f64;
255 for value in &mut mean {
256 *value /= n;
257 }
258 mean
259}
260
261fn defined_mean_over<'a, R>(results: &'a [R], field: impl Fn(&'a R) -> &'a [f64]) -> Vec<f64> {
263 let len = results.first().map_or(0, |first| field(first).len());
264 (0..len)
265 .map(|index| {
266 let defined = results
267 .iter()
268 .filter_map(|result| field(result).get(index).copied())
269 .filter(|value| !value.is_nan());
270 let (sum, count) = defined.fold((0.0, 0usize), |(sum, count), value| {
271 (sum + value, count + 1)
272 });
273 sum / count as f64
274 })
275 .collect()
276}
277
278fn sum_over<'a, R>(results: &'a [R], field: impl Fn(&'a R) -> &'a [u64]) -> Vec<u64> {
280 let mut total = vec![0; results.first().map_or(0, |first| field(first).len())];
281 for result in results {
282 for (total, &count) in total.iter_mut().zip(field(result)) {
283 *total += count;
284 }
285 }
286 total
287}
288
289fn all_over<'a, R>(results: &'a [R], field: impl Fn(&'a R) -> &'a [bool]) -> Vec<bool> {
291 let len = results.first().map_or(0, |first| field(first).len());
292 (0..len)
293 .map(|index| results.iter().all(|result| field(result)[index]))
294 .collect()
295}
296
297impl SweepResult {
298 pub fn empty() -> Self {
300 Self {
301 mags: vec![],
302 mags2: vec![],
303 mags4: vec![],
304 energies: vec![],
305 energies2: vec![],
306 energy_variance: vec![],
307 overlap_stats: OverlapStats::empty(),
308 cluster_stats: ClusterStats {
309 fk_csd: vec![],
310 overlap_csd: vec![],
311 top_cluster_sizes: vec![],
312 fk_updates: vec![],
313 fk_visited_spins: vec![],
314 overlap_moves: vec![],
315 rmc_edges: vec![],
316 },
317 per_disorder_physics: vec![],
318 per_disorder_cluster_observations: vec![],
319 per_disorder: vec![],
320 diagnostics: Diagnostics::empty(),
321 cluster_snapshots: vec![],
322 }
323 }
324
325 pub fn aggregate(results: &[Self]) -> Self {
330 Self::aggregate_impl(results, true)
331 }
332
333 pub(crate) fn aggregate_without_overlap_samples(results: &[Self]) -> Self {
334 Self::aggregate_impl(results, false)
335 }
336
337 fn aggregate_impl(results: &[Self], retain_overlap_samples: bool) -> Self {
338 if results.is_empty() {
339 return Self::empty();
340 }
341 let n = results.len() as f64;
342 let n_temps = results[0].mags.len();
343 let first = &results[0].cluster_stats;
344
345 let mut fk_csd = first.fk_csd.clone();
346 for histogram in &mut fk_csd {
347 histogram.fill(0);
348 }
349 let mut overlap_csd = first.overlap_csd.clone();
350 for histogram in overlap_csd.iter_mut().flatten() {
351 histogram.fill(0);
352 }
353 let mut top_cluster_sizes: Vec<Vec<[f64; 4]>> = (0..first.top_cluster_sizes.len())
355 .map(|mode| {
356 let n_top_temps = results
357 .iter()
358 .filter_map(|r| r.cluster_stats.top_cluster_sizes.get(mode))
359 .map(Vec::len)
360 .max()
361 .unwrap_or(0);
362 vec![[0.0; 4]; n_top_temps]
363 })
364 .collect();
365 for stats in results.iter().map(|r| &r.cluster_stats) {
366 for (total, part) in fk_csd.iter_mut().zip(&stats.fk_csd) {
367 for (total, &count) in total.iter_mut().zip(part) {
368 *total += count;
369 }
370 }
371 for (total, part) in overlap_csd
372 .iter_mut()
373 .flatten()
374 .zip(stats.overlap_csd.iter().flatten())
375 {
376 for (total, &count) in total.iter_mut().zip(part) {
377 *total += count;
378 }
379 }
380 for (total, part) in top_cluster_sizes.iter_mut().zip(&stats.top_cluster_sizes) {
381 for (total, sizes) in total.iter_mut().zip(part) {
382 for k in 0..4 {
383 total[k] += sizes[k];
384 }
385 }
386 }
387 }
388 for size in top_cluster_sizes.iter_mut().flatten().flatten() {
389 *size /= n;
390 }
391
392 let overlap_results: Vec<_> = results.iter().map(|r| &r.overlap_stats).collect();
393 let overlap_stats = if retain_overlap_samples {
394 OverlapStats::aggregate(&overlap_results)
395 } else {
396 OverlapStats::aggregate_without_samples(&overlap_results)
397 };
398
399 let checkpoints = &results[0].diagnostics.equil_checkpoints;
400 let equil_checkpoints = (0..checkpoints.len())
401 .map(|i| EquilCheckpoint {
402 sweep: checkpoints[i].sweep,
403 energy_avg: mean_over(results, |r| &r.diagnostics.equil_checkpoints[i].energy_avg),
404 link_overlap_avg: mean_over(results, |r| {
405 &r.diagnostics.equil_checkpoints[i].link_overlap_avg
406 }),
407 overlap2_avg: mean_over(results, |r| {
408 &r.diagnostics.equil_checkpoints[i].overlap2_avg
409 }),
410 })
411 .collect();
412 debug_assert!(results.iter().all(|r| r.mags.len() == n_temps));
413
414 SweepResult {
415 mags: mean_over(results, |r| &r.mags),
416 mags2: mean_over(results, |r| &r.mags2),
417 mags4: mean_over(results, |r| &r.mags4),
418 energies: mean_over(results, |r| &r.energies),
419 energies2: mean_over(results, |r| &r.energies2),
420 energy_variance: mean_over(results, |r| &r.energy_variance),
421 overlap_stats,
422 cluster_stats: ClusterStats {
423 fk_csd,
424 overlap_csd,
425 top_cluster_sizes,
426 fk_updates: sum_over(results, |r| &r.cluster_stats.fk_updates),
427 fk_visited_spins: sum_over(results, |r| &r.cluster_stats.fk_visited_spins),
428 overlap_moves: (0..first.overlap_moves.len())
429 .map(|mode| {
430 let mut total = first.overlap_moves[mode].clone();
431 for stats in results[1..].iter().map(|r| &r.cluster_stats) {
432 for (total, part) in total.iter_mut().zip(&stats.overlap_moves[mode]) {
433 total.add(part);
434 }
435 }
436 total
437 })
438 .collect(),
439 rmc_edges: {
440 let mut total = first.rmc_edges.clone();
441 for stats in results[1..].iter().map(|r| &r.cluster_stats) {
442 for (total, part) in total.iter_mut().zip(&stats.rmc_edges) {
443 total.add(part);
444 }
445 }
446 total
447 },
448 },
449 per_disorder_cluster_observations: results
450 .iter()
451 .flat_map(|r| r.per_disorder_cluster_observations.iter().cloned())
452 .collect(),
453 per_disorder_physics: results
454 .iter()
455 .flat_map(|r| r.per_disorder_physics.iter().cloned())
456 .collect(),
457 per_disorder: results
458 .iter()
459 .flat_map(|r| r.per_disorder.iter().cloned())
460 .collect(),
461 diagnostics: Diagnostics {
462 mags2_tau: defined_mean_over(results, |r| &r.diagnostics.mags2_tau),
463 overlap2_tau: defined_mean_over(results, |r| &r.diagnostics.overlap2_tau),
464 energy_tau: defined_mean_over(results, |r| &r.diagnostics.energy_tau),
465 mags2_tau_converged: all_over(results, |r| &r.diagnostics.mags2_tau_converged),
466 overlap2_tau_converged: all_over(results, |r| {
467 &r.diagnostics.overlap2_tau_converged
468 }),
469 energy_tau_converged: all_over(results, |r| &r.diagnostics.energy_tau_converged),
470 autocorrelation_lag: results[0].diagnostics.autocorrelation_lag,
471 equil_checkpoints,
472 },
473 cluster_snapshots: Vec::new(),
474 }
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481
482 fn sample(mean: f64, variance: f64, top: Vec<Vec<[f64; 4]>>) -> SweepResult {
484 let mut result = SweepResult::empty();
485 result.mags = vec![0.0];
486 result.mags2 = vec![0.0];
487 result.mags4 = vec![0.0];
488 result.energies = vec![mean];
489 result.energies2 = vec![variance + mean * mean];
490 result.energy_variance = vec![variance];
491 result.cluster_stats.top_cluster_sizes = top;
492 result
493 }
494
495 #[test]
496 fn aggregate_averages_per_sample_thermal_variance() {
497 let agg = SweepResult::aggregate(&[sample(-1.0, 0.25, vec![]), sample(-2.0, 0.75, vec![])]);
498 assert_eq!(agg.energies, vec![-1.5]);
499 assert_eq!(agg.energy_variance, vec![0.5]);
500 assert_eq!(agg.energies2[0] - agg.energies[0].powi(2), 0.75);
502 }
503
504 #[test]
505 fn empty_aggregate_is_empty() {
506 let agg = SweepResult::aggregate(&[]);
507 assert!(agg.energies.is_empty() && agg.cluster_stats.top_cluster_sizes.is_empty());
508 }
509
510 #[test]
511 fn top_cluster_modes_keep_their_own_lengths() {
512 let tops = |first: Vec<[f64; 4]>| vec![first, vec![[0.5, 0.25, 0.0, 0.0]; 2]];
513 let agg = SweepResult::aggregate(&[
514 sample(0.0, 0.0, tops(vec![])),
515 sample(0.0, 0.0, tops(vec![[1.0; 4]; 2])),
516 ]);
517 let sizes = &agg.cluster_stats.top_cluster_sizes;
518 assert_eq!(sizes[0], vec![[0.5; 4]; 2]);
519 assert_eq!(sizes[1], vec![[0.5, 0.25, 0.0, 0.0]; 2]);
520 }
521}