Skip to main content

spin_sim/statistics/
overlap.rs

1use crate::geometry::Lattice;
2
3use super::Statistics;
4
5/// Overlap statistics from replica-pair measurements.
6///
7/// All vectors are indexed by temperature. Empty when `n_replicas < 2`.
8pub struct OverlapStats {
9    /// ⟨q⟩ — mean replica overlap.
10    pub overlap: Vec<f64>,
11    /// ⟨q²⟩.
12    pub overlap2: Vec<f64>,
13    /// ⟨q⁴⟩.
14    pub overlap4: Vec<f64>,
15    /// ⟨q_l⟩ — mean link overlap.
16    pub link_overlap: Vec<f64>,
17    /// ⟨q_l²⟩.
18    pub link_overlap2: Vec<f64>,
19    /// ⟨q_l⁴⟩.
20    pub link_overlap4: Vec<f64>,
21    /// Overlap histogram P(q) per temperature: `[n_temps][n_spins + 1]`.
22    /// Bins correspond to dot-product values −N, −N+2, …, N where `idx = (dot + N) / 2`.
23    pub histogram: Vec<Vec<u64>>,
24    /// Conditional sum of q_l at each q bin: `[n_temps][n_spins + 1]`.
25    /// Divide by `histogram` counts to get ⟨q_l | q⟩.
26    pub ql_at_q_sum: Vec<Vec<f64>>,
27    /// Conditional sum of q_l² at each q bin: `[n_temps][n_spins + 1]`.
28    /// Use with `ql_at_q_sum` and `histogram` to compute A(q) = Var(q_l | q).
29    pub ql2_at_q_sum: Vec<Vec<f64>>,
30    /// Per-disorder-sample overlap histograms: `[n_disorder][n_temps][n_spins + 1]`.
31    /// Only populated by `aggregate()`; empty for single-realization results.
32    pub per_sample_histogram: Vec<Vec<Vec<u64>>>,
33    /// Per-disorder-sample conditional sums: `[n_disorder][n_temps][n_spins + 1]`.
34    /// Only populated by `aggregate()`; empty for single-realization results.
35    pub per_sample_ql_at_q_sum: Vec<Vec<Vec<f64>>>,
36    /// Per-disorder-sample conditional sums of squares: `[n_disorder][n_temps][n_spins + 1]`.
37    /// Only populated by `aggregate()`; empty for single-realization results.
38    pub per_sample_ql2_at_q_sum: Vec<Vec<Vec<f64>>>,
39}
40
41impl OverlapStats {
42    pub fn empty() -> Self {
43        Self {
44            overlap: vec![],
45            overlap2: vec![],
46            overlap4: vec![],
47            link_overlap: vec![],
48            link_overlap2: vec![],
49            link_overlap4: vec![],
50            histogram: vec![],
51            ql_at_q_sum: vec![],
52            ql2_at_q_sum: vec![],
53            per_sample_histogram: vec![],
54            per_sample_ql_at_q_sum: vec![],
55            per_sample_ql2_at_q_sum: vec![],
56        }
57    }
58
59    pub fn aggregate(results: &[&Self]) -> Self {
60        if results[0].overlap.is_empty() {
61            return Self::empty();
62        }
63
64        let n = results.len() as f64;
65        let n_temps = results[0].overlap.len();
66        let n_hist = results[0].histogram.len();
67        let hist_bins = results[0].histogram[0].len();
68        let n_ql = results[0].ql_at_q_sum.len();
69        let ql_bins = results[0].ql_at_q_sum.first().map_or(0, |v| v.len());
70
71        let mut agg = Self {
72            overlap: vec![0.0; n_temps],
73            overlap2: vec![0.0; n_temps],
74            overlap4: vec![0.0; n_temps],
75            link_overlap: vec![0.0; n_temps],
76            link_overlap2: vec![0.0; n_temps],
77            link_overlap4: vec![0.0; n_temps],
78            histogram: (0..n_hist).map(|_| vec![0u64; hist_bins]).collect(),
79            ql_at_q_sum: (0..n_ql).map(|_| vec![0.0; ql_bins]).collect(),
80            ql2_at_q_sum: (0..n_ql).map(|_| vec![0.0; ql_bins]).collect(),
81            per_sample_histogram: results.iter().map(|r| r.histogram.clone()).collect(),
82            per_sample_ql_at_q_sum: results.iter().map(|r| r.ql_at_q_sum.clone()).collect(),
83            per_sample_ql2_at_q_sum: results.iter().map(|r| r.ql2_at_q_sum.clone()).collect(),
84        };
85
86        for r in results {
87            for (a, &v) in agg.overlap.iter_mut().zip(r.overlap.iter()) {
88                *a += v;
89            }
90            for (a, &v) in agg.overlap2.iter_mut().zip(r.overlap2.iter()) {
91                *a += v;
92            }
93            for (a, &v) in agg.overlap4.iter_mut().zip(r.overlap4.iter()) {
94                *a += v;
95            }
96            for (a, &v) in agg.link_overlap.iter_mut().zip(r.link_overlap.iter()) {
97                *a += v;
98            }
99            for (a, &v) in agg.link_overlap2.iter_mut().zip(r.link_overlap2.iter()) {
100                *a += v;
101            }
102            for (a, &v) in agg.link_overlap4.iter_mut().zip(r.link_overlap4.iter()) {
103                *a += v;
104            }
105            for (a, s) in agg.histogram.iter_mut().zip(r.histogram.iter()) {
106                for (ah, &sh) in a.iter_mut().zip(s.iter()) {
107                    *ah += sh;
108                }
109            }
110            for (a, s) in agg.ql_at_q_sum.iter_mut().zip(r.ql_at_q_sum.iter()) {
111                for (ah, &sh) in a.iter_mut().zip(s.iter()) {
112                    *ah += sh;
113                }
114            }
115            for (a, s) in agg.ql2_at_q_sum.iter_mut().zip(r.ql2_at_q_sum.iter()) {
116                for (ah, &sh) in a.iter_mut().zip(s.iter()) {
117                    *ah += sh;
118                }
119            }
120        }
121
122        for v in agg
123            .overlap
124            .iter_mut()
125            .chain(agg.overlap2.iter_mut())
126            .chain(agg.overlap4.iter_mut())
127            .chain(agg.link_overlap.iter_mut())
128            .chain(agg.link_overlap2.iter_mut())
129            .chain(agg.link_overlap4.iter_mut())
130        {
131            *v /= n;
132        }
133
134        agg
135    }
136}
137
138/// Accumulator for overlap measurements during the sweep loop.
139pub struct OverlapAccum {
140    n_pairs: usize,
141    n_temps: usize,
142    n_spins: usize,
143    n_bonds: usize,
144    equil_diag: bool,
145    collect_q2_ac: bool,
146
147    overlap_stat: Statistics,
148    overlap2_stat: Statistics,
149    overlap4_stat: Statistics,
150    link_overlap_stat: Statistics,
151    link_overlap2_stat: Statistics,
152    link_overlap4_stat: Statistics,
153
154    histogram: Vec<Vec<u64>>,
155    ql_at_q_sum: Vec<Vec<f64>>,
156    ql2_at_q_sum: Vec<Vec<f64>>,
157
158    overlaps_buf: Vec<f32>,
159    overlaps2_buf: Vec<f32>,
160    overlaps4_buf: Vec<f32>,
161    link_overlaps_buf: Vec<f32>,
162    link_overlaps2_buf: Vec<f32>,
163    link_overlaps4_buf: Vec<f32>,
164
165    pub diag_ql_buf: Vec<f32>,
166    pub q2_ac_buf: Vec<f64>,
167}
168
169impl OverlapAccum {
170    pub fn new(
171        n_temps: usize,
172        n_spins: usize,
173        n_pairs: usize,
174        n_neighbors: usize,
175        equil_diag: bool,
176        collect_q2_ac: bool,
177    ) -> Self {
178        let has_pairs = n_pairs > 0;
179        Self {
180            n_pairs,
181            n_temps,
182            n_spins,
183            n_bonds: n_spins * n_neighbors,
184            equil_diag,
185            collect_q2_ac,
186
187            overlap_stat: Statistics::new(n_temps, 1),
188            overlap2_stat: Statistics::new(n_temps, 1),
189            overlap4_stat: Statistics::new(n_temps, 1),
190            link_overlap_stat: Statistics::new(n_temps, 1),
191            link_overlap2_stat: Statistics::new(n_temps, 1),
192            link_overlap4_stat: Statistics::new(n_temps, 1),
193
194            histogram: if has_pairs {
195                (0..n_temps).map(|_| vec![0u64; n_spins + 1]).collect()
196            } else {
197                vec![]
198            },
199            ql_at_q_sum: if has_pairs {
200                (0..n_temps).map(|_| vec![0.0f64; n_spins + 1]).collect()
201            } else {
202                vec![]
203            },
204            ql2_at_q_sum: if has_pairs {
205                (0..n_temps).map(|_| vec![0.0f64; n_spins + 1]).collect()
206            } else {
207                vec![]
208            },
209
210            overlaps_buf: vec![0.0f32; n_temps],
211            overlaps2_buf: vec![0.0f32; n_temps],
212            overlaps4_buf: vec![0.0f32; n_temps],
213            link_overlaps_buf: vec![0.0f32; n_temps],
214            link_overlaps2_buf: vec![0.0f32; n_temps],
215            link_overlaps4_buf: vec![0.0f32; n_temps],
216
217            diag_ql_buf: if equil_diag {
218                vec![0.0f32; n_temps]
219            } else {
220                vec![]
221            },
222            q2_ac_buf: if collect_q2_ac {
223                vec![0.0f64; n_temps]
224            } else {
225                vec![]
226            },
227        }
228    }
229
230    #[inline]
231    pub fn collect(&mut self, lattice: &Lattice, spins: &[i8], system_ids: &[usize], record: bool) {
232        if self.equil_diag {
233            self.diag_ql_buf.fill(0.0);
234        }
235        if self.collect_q2_ac && record {
236            self.q2_ac_buf.fill(0.0);
237        }
238
239        for pair_idx in 0..self.n_pairs {
240            let r_a = 2 * pair_idx;
241            let r_b = 2 * pair_idx + 1;
242
243            for t in 0..self.n_temps {
244                let sys_a = system_ids[r_a * self.n_temps + t];
245                let sys_b = system_ids[r_b * self.n_temps + t];
246                let base_a = sys_a * self.n_spins;
247                let base_b = sys_b * self.n_spins;
248
249                let mut dot_spin = 0i64;
250                let mut dot_link = 0i64;
251                for j in 0..self.n_spins {
252                    let sa = spins[base_a + j] as i64;
253                    let sb = spins[base_b + j] as i64;
254                    dot_spin += sa * sb;
255                    for d in 0..lattice.n_neighbors {
256                        let k = lattice.neighbor_fwd(j, d);
257                        dot_link +=
258                            (sa * spins[base_a + k] as i64) * (sb * spins[base_b + k] as i64);
259                    }
260                }
261
262                let ql = dot_link as f32 / self.n_bonds as f32;
263
264                if self.equil_diag {
265                    self.diag_ql_buf[t] += ql;
266                }
267                if !record {
268                    continue;
269                }
270
271                let q = dot_spin as f32 / self.n_spins as f32;
272                let q2 = q * q;
273                self.overlaps_buf[t] = q;
274                self.overlaps2_buf[t] = q2;
275                self.overlaps4_buf[t] = q2 * q2;
276
277                let ql2 = ql * ql;
278                self.link_overlaps_buf[t] = ql;
279                self.link_overlaps2_buf[t] = ql2;
280                self.link_overlaps4_buf[t] = ql2 * ql2;
281
282                let idx = ((dot_spin + self.n_spins as i64) / 2) as usize;
283                self.histogram[t][idx] += 1;
284                self.ql_at_q_sum[t][idx] += ql as f64;
285                self.ql2_at_q_sum[t][idx] += (ql * ql) as f64;
286            }
287
288            if !record {
289                continue;
290            }
291
292            if self.collect_q2_ac {
293                for t in 0..self.n_temps {
294                    self.q2_ac_buf[t] += self.overlaps2_buf[t] as f64;
295                }
296            }
297
298            self.overlap_stat.update(&self.overlaps_buf);
299            self.overlap2_stat.update(&self.overlaps2_buf);
300            self.overlap4_stat.update(&self.overlaps4_buf);
301            self.link_overlap_stat.update(&self.link_overlaps_buf);
302            self.link_overlap2_stat.update(&self.link_overlaps2_buf);
303            self.link_overlap4_stat.update(&self.link_overlaps4_buf);
304        }
305
306        if self.equil_diag {
307            let inv = 1.0 / self.n_pairs as f32;
308            for v in self.diag_ql_buf.iter_mut() {
309                *v *= inv;
310            }
311        }
312    }
313
314    pub fn finish(self) -> OverlapStats {
315        if self.n_pairs == 0 {
316            return OverlapStats::empty();
317        }
318        OverlapStats {
319            overlap: self.overlap_stat.average(),
320            overlap2: self.overlap2_stat.average(),
321            overlap4: self.overlap4_stat.average(),
322            link_overlap: self.link_overlap_stat.average(),
323            link_overlap2: self.link_overlap2_stat.average(),
324            link_overlap4: self.link_overlap4_stat.average(),
325            histogram: self.histogram,
326            ql_at_q_sum: self.ql_at_q_sum,
327            ql2_at_q_sum: self.ql2_at_q_sum,
328            per_sample_histogram: vec![],
329            per_sample_ql_at_q_sum: vec![],
330            per_sample_ql2_at_q_sum: vec![],
331        }
332    }
333}