Skip to main content

spin_sim/statistics/
results.rs

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
13pub struct ClusterStats {
14    /// FK cluster size histogram per temperature: `hist[s]` = count of size-`s` clusters.
15    pub fk_csd: Vec<Vec<u64>>,
16    /// Per-mode overlap cluster size histogram: `[n_modes][n_temps][n_spins+1]`.
17    pub overlap_csd: Vec<Vec<Vec<u64>>>,
18    /// Per-mode average relative size of k-th largest overlap cluster: `[n_modes][n_temps][4]`.
19    pub top_cluster_sizes: Vec<Vec<[f64; 4]>>,
20}
21
22pub struct Diagnostics {
23    /// Integrated autocorrelation time τ_int(m²) per temperature.
24    /// Empty if autocorrelation_max_lag is None.
25    pub mags2_tau: Vec<f64>,
26    /// Integrated autocorrelation time τ_int(q²) per temperature.
27    /// Empty if autocorrelation_max_lag is None or n_replicas < 2.
28    pub overlap2_tau: Vec<f64>,
29    /// Equilibration diagnostic checkpoints (energy + link overlap running averages).
30    /// Empty if equilibration_diagnostic is false.
31    pub equil_checkpoints: Vec<EquilCheckpoint>,
32}
33
34/// Per-temperature observables averaged over measurement sweeps and replicas.
35///
36/// All vectors are indexed by temperature index and have length `n_temps`.
37/// Overlap vectors are empty when `n_replicas < 2`.
38pub struct SweepResult {
39    /// ⟨m⟩ — mean magnetization per spin.
40    pub mags: Vec<f64>,
41    /// ⟨m²⟩.
42    pub mags2: Vec<f64>,
43    /// ⟨m⁴⟩.
44    pub mags4: Vec<f64>,
45    /// ⟨E⟩ — mean energy per spin.
46    pub energies: Vec<f64>,
47    /// ⟨E²⟩.
48    pub energies2: Vec<f64>,
49    pub overlap_stats: OverlapStats,
50    pub cluster_stats: ClusterStats,
51    pub diagnostics: Diagnostics,
52    pub cluster_snapshots: Vec<ClusterSnapshot>,
53}
54
55impl SweepResult {
56    /// Average [`SweepResult`]s across disorder realizations.
57    pub fn aggregate(results: &[Self]) -> Self {
58        let n = results.len() as f64;
59        let n_temps = results[0].mags.len();
60        let n_fk_csd = results[0].cluster_stats.fk_csd.len();
61
62        let fk_len = results[0]
63            .cluster_stats
64            .fk_csd
65            .first()
66            .map_or(0, |v| v.len());
67
68        let n_modes = results[0].cluster_stats.overlap_csd.len();
69        let ov_inner_len = results[0]
70            .cluster_stats
71            .overlap_csd
72            .first()
73            .and_then(|v| v.first())
74            .map_or(0, |v| v.len());
75        let ov_inner_temps = results[0]
76            .cluster_stats
77            .overlap_csd
78            .first()
79            .map_or(0, |v| v.len());
80
81        let n_top_modes = results[0].cluster_stats.top_cluster_sizes.len();
82        let n_top_temps = results[0]
83            .cluster_stats
84            .top_cluster_sizes
85            .first()
86            .map_or(0, |v| v.len());
87
88        let m2_tau_len = results[0].diagnostics.mags2_tau.len();
89        let q2_tau_len = results[0].diagnostics.overlap2_tau.len();
90        let n_ckpts = results[0].diagnostics.equil_checkpoints.len();
91
92        let overlap_stats =
93            OverlapStats::aggregate(&results.iter().map(|r| &r.overlap_stats).collect::<Vec<_>>());
94
95        let mut agg = SweepResult {
96            mags: vec![0.0; n_temps],
97            mags2: vec![0.0; n_temps],
98            mags4: vec![0.0; n_temps],
99            energies: vec![0.0; n_temps],
100            energies2: vec![0.0; n_temps],
101            overlap_stats,
102            cluster_stats: ClusterStats {
103                fk_csd: (0..n_fk_csd).map(|_| vec![0u64; fk_len]).collect(),
104                overlap_csd: (0..n_modes)
105                    .map(|_| {
106                        (0..ov_inner_temps)
107                            .map(|_| vec![0u64; ov_inner_len])
108                            .collect()
109                    })
110                    .collect(),
111                top_cluster_sizes: (0..n_top_modes)
112                    .map(|_| vec![[0.0; 4]; n_top_temps])
113                    .collect(),
114            },
115            diagnostics: Diagnostics {
116                mags2_tau: vec![0.0; m2_tau_len],
117                overlap2_tau: vec![0.0; q2_tau_len],
118                equil_checkpoints: (0..n_ckpts)
119                    .map(|i| EquilCheckpoint {
120                        sweep: results[0].diagnostics.equil_checkpoints[i].sweep,
121                        energy_avg: vec![0.0; n_temps],
122                        link_overlap_avg: vec![0.0; n_temps],
123                    })
124                    .collect(),
125            },
126            cluster_snapshots: Vec::new(),
127        };
128
129        for r in results {
130            for (a, &v) in agg.mags.iter_mut().zip(r.mags.iter()) {
131                *a += v;
132            }
133            for (a, &v) in agg.mags2.iter_mut().zip(r.mags2.iter()) {
134                *a += v;
135            }
136            for (a, &v) in agg.mags4.iter_mut().zip(r.mags4.iter()) {
137                *a += v;
138            }
139            for (a, &v) in agg.energies.iter_mut().zip(r.energies.iter()) {
140                *a += v;
141            }
142            for (a, &v) in agg.energies2.iter_mut().zip(r.energies2.iter()) {
143                *a += v;
144            }
145            for (a, s) in agg
146                .cluster_stats
147                .fk_csd
148                .iter_mut()
149                .zip(r.cluster_stats.fk_csd.iter())
150            {
151                for (ah, &sh) in a.iter_mut().zip(s.iter()) {
152                    *ah += sh;
153                }
154            }
155            for (am, sm) in agg
156                .cluster_stats
157                .overlap_csd
158                .iter_mut()
159                .zip(r.cluster_stats.overlap_csd.iter())
160            {
161                for (a, s) in am.iter_mut().zip(sm.iter()) {
162                    for (ah, &sh) in a.iter_mut().zip(s.iter()) {
163                        *ah += sh;
164                    }
165                }
166            }
167            for (am, sm) in agg
168                .cluster_stats
169                .top_cluster_sizes
170                .iter_mut()
171                .zip(r.cluster_stats.top_cluster_sizes.iter())
172            {
173                for (a, &s) in am.iter_mut().zip(sm.iter()) {
174                    for k in 0..4 {
175                        a[k] += s[k];
176                    }
177                }
178            }
179            for (a, &v) in agg
180                .diagnostics
181                .mags2_tau
182                .iter_mut()
183                .zip(r.diagnostics.mags2_tau.iter())
184            {
185                *a += v;
186            }
187            for (a, &v) in agg
188                .diagnostics
189                .overlap2_tau
190                .iter_mut()
191                .zip(r.diagnostics.overlap2_tau.iter())
192            {
193                *a += v;
194            }
195            for (ac, rc) in agg
196                .diagnostics
197                .equil_checkpoints
198                .iter_mut()
199                .zip(r.diagnostics.equil_checkpoints.iter())
200            {
201                for (a, &v) in ac.energy_avg.iter_mut().zip(rc.energy_avg.iter()) {
202                    *a += v;
203                }
204                for (a, &v) in ac
205                    .link_overlap_avg
206                    .iter_mut()
207                    .zip(rc.link_overlap_avg.iter())
208                {
209                    *a += v;
210                }
211            }
212        }
213
214        for v in agg
215            .mags
216            .iter_mut()
217            .chain(agg.mags2.iter_mut())
218            .chain(agg.mags4.iter_mut())
219            .chain(agg.energies.iter_mut())
220            .chain(agg.energies2.iter_mut())
221        {
222            *v /= n;
223        }
224
225        for mode_tops in agg.cluster_stats.top_cluster_sizes.iter_mut() {
226            for arr in mode_tops.iter_mut() {
227                for v in arr.iter_mut() {
228                    *v /= n;
229                }
230            }
231        }
232
233        for v in agg.diagnostics.mags2_tau.iter_mut() {
234            *v /= n;
235        }
236        for v in agg.diagnostics.overlap2_tau.iter_mut() {
237            *v /= n;
238        }
239        for ckpt in agg.diagnostics.equil_checkpoints.iter_mut() {
240            for v in ckpt.energy_avg.iter_mut() {
241                *v /= n;
242            }
243            for v in ckpt.link_overlap_avg.iter_mut() {
244                *v /= n;
245            }
246        }
247
248        agg
249    }
250}