Skip to content

Commit cbbd54f

Browse files
committed
extract τ per realization in Rust, average τ across disorder (literature-standard)
Signed-off-by: PeaBrane <yanrpei@gmail.com>
1 parent 0a13066 commit cbbd54f

File tree

5 files changed

+52
-114
lines changed

5 files changed

+52
-114
lines changed

python/peapods/spin_models.py

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -226,58 +226,13 @@ def sample(
226226
if "top_cluster_sizes" in result:
227227
self.top_cluster_sizes = result["top_cluster_sizes"]
228228

229-
if "mags2_autocorr" in result:
230-
self.mags2_autocorr = result["mags2_autocorr"]
231-
if "overlap2_autocorr" in result:
232-
self.overlap2_autocorr = result["overlap2_autocorr"]
229+
if "mags2_tau" in result:
230+
self.mags2_tau = result["mags2_tau"]
231+
if "overlap2_tau" in result:
232+
self.overlap2_tau = result["overlap2_tau"]
233233

234234
return result
235235

236-
@staticmethod
237-
def _sokal_tau(gamma):
238-
"""Integrated autocorrelation time via Sokal's self-consistent windowing.
239-
240-
Args:
241-
gamma: 1D array, normalized autocorrelation Γ(δ) with Γ[0]=1.
242-
243-
Returns:
244-
τ_int estimate (float).
245-
"""
246-
tau = 0.5
247-
for w in range(1, len(gamma)):
248-
tau += gamma[w]
249-
if w >= 5 * tau:
250-
return tau
251-
return tau
252-
253-
def mags2_autocorrelation_time(self):
254-
"""Integrated autocorrelation time of m² per temperature.
255-
256-
Returns:
257-
Array of shape ``(n_temps,)``.
258-
"""
259-
if not hasattr(self, "mags2_autocorr"):
260-
raise RuntimeError(
261-
"No autocorrelation data; call sample() with autocorrelation_max_lag first"
262-
)
263-
return np.array(
264-
[self._sokal_tau(self.mags2_autocorr[t]) for t in range(self.n_temps)]
265-
)
266-
267-
def overlap2_autocorrelation_time(self):
268-
"""Integrated autocorrelation time of q² per temperature. Requires n_replicas >= 2.
269-
270-
Returns:
271-
Array of shape ``(n_temps,)``.
272-
"""
273-
if not hasattr(self, "overlap2_autocorr"):
274-
raise RuntimeError(
275-
"No overlap autocorrelation data; call sample() with autocorrelation_max_lag and n_replicas >= 2"
276-
)
277-
return np.array(
278-
[self._sokal_tau(self.overlap2_autocorr[t]) for t in range(self.n_temps)]
279-
)
280-
281236
def get_energies(self):
282237
"""Return the mean energies per temperature from the last sample run."""
283238
return self.energies_avg

python/peapods/sweep.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ def _save_data(models, config_label, temperatures, output_dir):
5050
save_dict[f"{prefix}_mean_cluster_size"] = model.mean_cluster_size
5151
if hasattr(model, "top_cluster_sizes"):
5252
save_dict[f"{prefix}_top_cluster_sizes"] = model.top_cluster_sizes
53-
if hasattr(model, "mags2_autocorr"):
54-
save_dict[f"{prefix}_mags2_tau"] = model.mags2_autocorrelation_time()
55-
if hasattr(model, "overlap2_autocorr"):
56-
save_dict[f"{prefix}_overlap2_tau"] = model.overlap2_autocorrelation_time()
53+
if hasattr(model, "mags2_tau"):
54+
save_dict[f"{prefix}_mags2_tau"] = model.mags2_tau
55+
if hasattr(model, "overlap2_tau"):
56+
save_dict[f"{prefix}_overlap2_tau"] = model.overlap2_tau
5757

5858
path = Path(output_dir) / f"sweep_{config_label}.npz"
5959
np.savez(path, **save_dict)
@@ -139,9 +139,9 @@ def _plot_autocorrelation_time(all_results, temperatures, plot_temp, output_dir)
139139
else:
140140
t_idx = None
141141

142-
for obs_name, attr, tau_method in [
143-
("m2", "mags2_autocorr", "mags2_autocorrelation_time"),
144-
("q2", "overlap2_autocorr", "overlap2_autocorrelation_time"),
142+
for obs_name, attr in [
143+
("m2", "mags2_tau"),
144+
("q2", "overlap2_tau"),
145145
]:
146146
has_any = any(
147147
hasattr(m, attr) for models in all_results.values() for m in models.values()
@@ -156,7 +156,7 @@ def _plot_autocorrelation_time(all_results, temperatures, plot_temp, output_dir)
156156
for size_label, model in models.items():
157157
if not hasattr(model, attr):
158158
continue
159-
tau_arr = getattr(model, tau_method)()
159+
tau_arr = getattr(model, attr)
160160
L = max(model.lattice_shape)
161161
sizes_L.append(L)
162162
if t_idx is not None:

spin-sim/src/simulation.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@ impl AutocorrAccum {
8888
}
8989
}
9090

91+
fn sokal_tau(gamma: &[f64]) -> f64 {
92+
let mut tau = 0.5;
93+
for (w, &g) in gamma.iter().enumerate().skip(1) {
94+
tau += g;
95+
if w as f64 >= 5.0 * tau {
96+
return tau;
97+
}
98+
}
99+
tau
100+
}
101+
91102
/// Mutable state for one disorder realization.
92103
///
93104
/// Holds the coupling array (fixed after construction), spin configurations for
@@ -603,13 +614,13 @@ pub fn run_sweep_loop(
603614
vec![]
604615
};
605616

606-
let mags2_autocorrelation = m2_accum
617+
let mags2_tau = m2_accum
607618
.as_ref()
608-
.map(|acc| acc.finish())
619+
.map(|acc| acc.finish().iter().map(|g| sokal_tau(g)).collect())
609620
.unwrap_or_default();
610-
let overlap2_autocorrelation = q2_accum
621+
let overlap2_tau = q2_accum
611622
.as_ref()
612-
.map(|acc| acc.finish())
623+
.map(|acc| acc.finish().iter().map(|g| sokal_tau(g)).collect())
613624
.unwrap_or_default();
614625

615626
Ok(SweepResult {
@@ -636,8 +647,8 @@ pub fn run_sweep_loop(
636647
fk_csd: fk_csd_accum,
637648
overlap_csd: overlap_csd_accum,
638649
top_cluster_sizes,
639-
mags2_autocorrelation,
640-
overlap2_autocorrelation,
650+
mags2_tau,
651+
overlap2_tau,
641652
})
642653
}
643654

spin-sim/src/statistics/results.rs

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ pub struct SweepResult {
2626
/// Average relative size of k-th largest blue cluster per temperature.
2727
/// Shape: [n_temps][4]. Empty if collect_top_clusters=false.
2828
pub top_cluster_sizes: Vec<[f64; 4]>,
29-
/// Normalized autocorrelation Γ(Δt) of m², shape [n_temps][max_lag+1].
29+
/// Integrated autocorrelation time τ_int(m²) per temperature.
3030
/// Empty if autocorrelation_max_lag is None.
31-
pub mags2_autocorrelation: Vec<Vec<f64>>,
32-
/// Normalized autocorrelation Γ(Δt) of q², shape [n_temps][max_lag+1].
31+
pub mags2_tau: Vec<f64>,
32+
/// Integrated autocorrelation time τ_int(q²) per temperature.
3333
/// Empty if autocorrelation_max_lag is None or n_replicas < 2.
34-
pub overlap2_autocorrelation: Vec<Vec<f64>>,
34+
pub overlap2_tau: Vec<f64>,
3535
}
3636

3737
impl SweepResult {
@@ -48,16 +48,8 @@ impl SweepResult {
4848

4949
let n_top = results[0].top_cluster_sizes.len();
5050

51-
let m2_ac_ntemps = results[0].mags2_autocorrelation.len();
52-
let m2_ac_len = results[0]
53-
.mags2_autocorrelation
54-
.first()
55-
.map_or(0, |v| v.len());
56-
let q2_ac_ntemps = results[0].overlap2_autocorrelation.len();
57-
let q2_ac_len = results[0]
58-
.overlap2_autocorrelation
59-
.first()
60-
.map_or(0, |v| v.len());
51+
let m2_tau_len = results[0].mags2_tau.len();
52+
let q2_tau_len = results[0].overlap2_tau.len();
6153

6254
let mut agg = SweepResult {
6355
mags: vec![0.0; n_temps],
@@ -71,8 +63,8 @@ impl SweepResult {
7163
fk_csd: (0..n_fk_csd).map(|_| vec![0u64; fk_len]).collect(),
7264
overlap_csd: (0..n_ov_csd).map(|_| vec![0u64; ov_len]).collect(),
7365
top_cluster_sizes: vec![[0.0; 4]; n_top],
74-
mags2_autocorrelation: (0..m2_ac_ntemps).map(|_| vec![0.0; m2_ac_len]).collect(),
75-
overlap2_autocorrelation: (0..q2_ac_ntemps).map(|_| vec![0.0; q2_ac_len]).collect(),
66+
mags2_tau: vec![0.0; m2_tau_len],
67+
overlap2_tau: vec![0.0; q2_tau_len],
7668
};
7769

7870
for r in results {
@@ -119,23 +111,11 @@ impl SweepResult {
119111
a[k] += s[k];
120112
}
121113
}
122-
for (a_row, r_row) in agg
123-
.mags2_autocorrelation
124-
.iter_mut()
125-
.zip(r.mags2_autocorrelation.iter())
126-
{
127-
for (a, &v) in a_row.iter_mut().zip(r_row.iter()) {
128-
*a += v;
129-
}
114+
for (a, &v) in agg.mags2_tau.iter_mut().zip(r.mags2_tau.iter()) {
115+
*a += v;
130116
}
131-
for (a_row, r_row) in agg
132-
.overlap2_autocorrelation
133-
.iter_mut()
134-
.zip(r.overlap2_autocorrelation.iter())
135-
{
136-
for (a, &v) in a_row.iter_mut().zip(r_row.iter()) {
137-
*a += v;
138-
}
117+
for (a, &v) in agg.overlap2_tau.iter_mut().zip(r.overlap2_tau.iter()) {
118+
*a += v;
139119
}
140120
}
141121

@@ -159,15 +139,11 @@ impl SweepResult {
159139
}
160140
}
161141

162-
for row in agg.mags2_autocorrelation.iter_mut() {
163-
for v in row.iter_mut() {
164-
*v /= n;
165-
}
142+
for v in agg.mags2_tau.iter_mut() {
143+
*v /= n;
166144
}
167-
for row in agg.overlap2_autocorrelation.iter_mut() {
168-
for v in row.iter_mut() {
169-
*v /= n;
170-
}
145+
for v in agg.overlap2_tau.iter_mut() {
146+
*v /= n;
171147
}
172148

173149
agg

src/lib.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -270,19 +270,15 @@ impl IsingSimulation {
270270
dict.set_item("top_cluster_sizes", arr)?;
271271
}
272272

273-
if !agg.mags2_autocorrelation.is_empty() {
274-
let k = agg.mags2_autocorrelation[0].len();
275-
let arr = Array2::from_shape_fn((n_temps, k), |(t, d)| agg.mags2_autocorrelation[t][d])
276-
.into_pyarray(py);
277-
dict.set_item("mags2_autocorr", arr)?;
273+
if !agg.mags2_tau.is_empty() {
274+
dict.set_item("mags2_tau", Array1::from(agg.mags2_tau).into_pyarray(py))?;
278275
}
279276

280-
if !agg.overlap2_autocorrelation.is_empty() {
281-
let k = agg.overlap2_autocorrelation[0].len();
282-
let arr =
283-
Array2::from_shape_fn((n_temps, k), |(t, d)| agg.overlap2_autocorrelation[t][d])
284-
.into_pyarray(py);
285-
dict.set_item("overlap2_autocorr", arr)?;
277+
if !agg.overlap2_tau.is_empty() {
278+
dict.set_item(
279+
"overlap2_tau",
280+
Array1::from(agg.overlap2_tau).into_pyarray(py),
281+
)?;
286282
}
287283

288284
Ok(dict)

0 commit comments

Comments
 (0)