Skip to main content

spin_sim/statistics/
results.rs

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