Skip to main content

spin_sim/statistics/
results.rs

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