1pub(crate) mod driver;
2pub mod realization;
3pub mod xy;
4
5pub use realization::Realization;
6
7use std::sync::atomic::AtomicBool;
8
9use crate::config::{ClusterAction, OverlapClusterBuildMode, SimConfig, SweepMode};
10use crate::geometry::Lattice;
11use crate::statistics::graph::{fold_histograms, histograms, ClusterObservationAccums};
12use crate::statistics::physics::{CachedTotals, PhysicsCollector, PhysicsOptions};
13use crate::statistics::results::{
14 add_move_counts, add_rmc_counts, fold_counts, OverlapMoveCounts, RmcEdgeCounts,
15};
16use crate::statistics::{
17 AutocorrAccum, ClusterSnapshot, ClusterStats, Diagnostics, DisorderSample,
18 EquilDiagnosticAccum, OverlapAccum, Statistics, SweepResult,
19};
20use crate::{clusters, mcmc, spins};
21
22struct ObservableCache<'a> {
27 lattice: &'a Lattice,
28 lookup: Option<&'a mcmc::sweep::UnitCouplingLookup>,
29 sequential: bool,
30 unit_totals: Vec<i64>,
32 deltas: Vec<mcmc::sweep::UnitSweepDelta>,
33 magnetization_sums: Vec<i64>,
34 valid: bool,
35}
36
37impl<'a> ObservableCache<'a> {
38 fn new(
39 lattice: &'a Lattice,
40 lookup: Option<&'a mcmc::sweep::UnitCouplingLookup>,
41 n_systems: usize,
42 config: &SimConfig,
43 ) -> Self {
44 let n_tracked = if lookup.is_some() { n_systems } else { 0 };
45 Self {
46 lattice,
47 lookup,
48 sequential: config.sequential,
49 unit_totals: vec![0; n_tracked],
50 deltas: vec![mcmc::sweep::UnitSweepDelta::default(); n_tracked],
51 magnetization_sums: vec![0; n_systems],
52 valid: false,
53 }
54 }
55
56 fn begin_sweep(&mut self, consumed: bool) -> Option<&mut [mcmc::sweep::UnitSweepDelta]> {
59 self.valid &= self.lookup.is_some() && consumed;
60 self.valid.then_some(self.deltas.as_mut_slice())
61 }
62
63 fn end_sweep(&mut self, energies: &mut [f32]) {
65 if !self.valid {
66 return;
67 }
68 for (system, delta) in self.deltas.iter().enumerate() {
69 self.unit_totals[system] += delta.interaction;
70 self.magnetization_sums[system] += delta.magnetization;
71 energies[system] =
72 spins::energy::unit_energy(self.unit_totals[system], self.lattice.n_spins);
73 }
74 }
75
76 fn invalidate(&mut self) {
77 self.valid = false;
78 }
79
80 fn ensure(&mut self, real: &mut Realization) {
82 if self.valid {
83 return;
84 }
85 self.valid = true;
86 let Some(lookup) = self.lookup else {
87 spins::energy::refresh_energies_and_magnetizations(
88 self.lattice,
89 &real.spins,
90 &real.couplings,
91 &mut real.energies,
92 &mut self.magnetization_sums,
93 self.sequential,
94 );
95 return;
96 };
97 spins::energy::refresh_unit_totals(
98 self.lattice,
99 &real.spins,
100 lookup.couplings(),
101 &mut self.unit_totals,
102 &mut self.magnetization_sums,
103 self.sequential,
104 );
105 for (energy, &total) in real.energies.iter_mut().zip(&self.unit_totals) {
106 *energy = spins::energy::unit_energy(total, self.lattice.n_spins);
107 }
108 }
109}
110
111#[allow(clippy::too_many_arguments)]
122pub fn run_sweep_loop(
123 lattice: &Lattice,
124 real: &mut Realization,
125 n_replicas: usize,
126 n_temps: usize,
127 config: &SimConfig,
128 interrupted: &AtomicBool,
129 on_sweep: &(dyn Fn() + Sync),
130 realization_idx: usize,
131) -> Result<SweepResult, String> {
132 driver::validate_layout(config, n_replicas, n_temps)?;
133 real.validate(lattice, n_replicas, n_temps)?;
134 run_sweep_loop_impl(
135 lattice,
136 real,
137 n_replicas,
138 n_temps,
139 config,
140 interrupted,
141 on_sweep,
142 realization_idx,
143 true,
144 None,
145 )
146}
147
148#[allow(clippy::too_many_arguments)]
149fn run_sweep_loop_impl(
150 lattice: &Lattice,
151 real: &mut Realization,
152 n_replicas: usize,
153 n_temps: usize,
154 config: &SimConfig,
155 interrupted: &AtomicBool,
156 on_sweep: &(dyn Fn() + Sync),
157 realization_idx: usize,
158 materialize_disabled_stats: bool,
159 physics_options: Option<&PhysicsOptions>,
160) -> Result<SweepResult, String> {
161 let acceptance = match config.sweep_mode {
162 SweepMode::Metropolis => Some(mcmc::sweep::Acceptance::Metropolis),
163 SweepMode::Gibbs => Some(mcmc::sweep::Acceptance::Gibbs),
164 SweepMode::None => None,
165 };
166 let unit_lookup = acceptance.and_then(|acceptance| {
167 mcmc::sweep::UnitCouplingLookup::new(
168 &real.couplings,
169 &real.temperatures,
170 lattice.n_neighbors,
171 acceptance,
172 )
173 });
174
175 let n_spins = lattice.n_spins;
176 let n_systems = n_replicas * n_temps;
177 let n_sweeps = config.n_sweeps;
178
179 let n_modes = config.overlap_cluster.as_ref().map_or(0, |h| h.modes.len());
180
181 let n_pairs = n_replicas / 2;
182 let overlap_active: Vec<bool> = real.temperatures[..n_temps]
183 .iter()
184 .map(|&t| {
185 config
186 .overlap_cluster
187 .as_ref()
188 .and_then(|oc| oc.max_temperature)
189 .is_none_or(|max| t <= max)
190 })
191 .collect();
192 let observe_fk = config
193 .cluster_update
194 .as_ref()
195 .is_some_and(|cluster| cluster.action == ClusterAction::Observe);
196 let observe_overlap = config
197 .overlap_cluster
198 .as_ref()
199 .is_some_and(|cluster| cluster.action == ClusterAction::Observe);
200 let collect_fk = config
201 .cluster_update
202 .as_ref()
203 .is_some_and(|c| c.collect_stats || c.action == ClusterAction::Observe);
204 let collect_overlap = config
205 .overlap_cluster
206 .as_ref()
207 .is_some_and(|h| h.collect_stats || h.action == ClusterAction::Observe)
208 && n_pairs > 0;
209
210 let mut observation_accums = ClusterObservationAccums::default();
211 if observe_fk {
212 observation_accums.enable_fk(n_temps);
213 }
214 if let Some(overlap) = config.overlap_cluster.as_ref().filter(|_| observe_overlap) {
215 for mode in &overlap.modes {
216 observation_accums.enable_overlap(mode, n_temps);
217 }
218 }
219
220 let mut fk_csd_slots = histograms(if collect_fk { n_systems } else { 0 }, n_spins);
223 let fk_updates_spins = config
225 .cluster_update
226 .as_ref()
227 .is_some_and(|cluster| cluster.action == ClusterAction::Update);
228 let mut fk_visits = vec![0u64; if fk_updates_spins { n_systems } else { 0 }];
229 let mut fk_recorded_updates = 0u64;
230 let mut fk_observation_buf = if observe_fk {
231 vec![clusters::GraphObservationSlot::default(); n_systems]
232 } else {
233 vec![]
234 };
235
236 let mut overlap_csd_slots: Vec<Vec<Vec<u64>>> = (0..n_modes)
237 .map(|_| {
238 histograms(
239 if collect_overlap {
240 n_temps * n_pairs
241 } else {
242 0
243 },
244 n_spins,
245 )
246 })
247 .collect();
248
249 let collect_top = collect_overlap;
250 let collect_moves = config
252 .overlap_cluster
253 .as_ref()
254 .is_some_and(|overlap| overlap.collect_stats && overlap.action == ClusterAction::Update);
255 let mut move_slots: Vec<Vec<OverlapMoveCounts>> = (0..n_modes)
256 .map(|_| {
257 vec![OverlapMoveCounts::default(); if collect_moves { n_temps * n_pairs } else { 0 }]
258 })
259 .collect();
260 let mut rmc_slots = vec![RmcEdgeCounts::default(); if collect_moves { n_systems } else { 0 }];
261
262 let mut top4_accum: Vec<Vec<[f64; 4]>> = if collect_top {
263 (0..n_modes).map(|_| vec![[0.0; 4]; n_temps]).collect()
264 } else {
265 vec![]
266 };
267 let mut top4_n: Vec<usize> = if collect_top {
268 vec![0; n_modes]
269 } else {
270 vec![]
271 };
272 let mut top4_buf: Vec<[u32; 4]> = if collect_top {
273 vec![[0u32; 4]; n_temps * n_pairs]
274 } else {
275 vec![]
276 };
277 let mut overlap_observation_buf = if observe_overlap {
278 vec![clusters::GraphObservationSlot::default(); n_temps * n_pairs]
279 } else {
280 vec![]
281 };
282
283 let mut snapshot_due = false;
285
286 let snapshot_interval = if realization_idx == 0 {
287 config
288 .overlap_cluster
289 .as_ref()
290 .and_then(|oc| oc.snapshot_interval)
291 } else {
292 None
293 };
294 let n_pair_slots = n_temps * n_pairs;
295 let mut snap_buf: Vec<Vec<u32>> = if snapshot_interval.is_some() {
296 (0..n_pair_slots)
297 .map(|_| Vec::with_capacity(n_spins))
298 .collect()
299 } else {
300 vec![]
301 };
302 let mut blue_snap_buf: Vec<Vec<u32>> = if snapshot_interval.is_some() {
303 (0..n_pair_slots)
304 .map(|_| Vec::with_capacity(n_spins))
305 .collect()
306 } else {
307 vec![]
308 };
309 let mut spin_snap_buf: Vec<Vec<[Vec<i8>; 2]>> = if snapshot_interval.is_some() {
310 (0..n_pair_slots).map(|_| Vec::new()).collect()
311 } else {
312 vec![]
313 };
314 let mut sid_snap_buf: Vec<Vec<[usize; 2]>> = if snapshot_interval.is_some() {
315 (0..n_pair_slots).map(|_| Vec::new()).collect()
316 } else {
317 vec![]
318 };
319 let mut cluster_snapshots: Vec<ClusterSnapshot> = Vec::new();
320
321 let mut mags_stat = Statistics::new(n_temps, 1);
322 let mut mags2_stat = Statistics::new(n_temps, 1);
323 let mut mags4_stat = Statistics::new(n_temps, 1);
324 let mut energies_stat = Statistics::new(n_temps, 1);
325 let mut energies2_stat = Statistics::new(n_temps, 2);
326 let n_measurement_sweeps = config.n_measurement_sweeps();
327 let ac_max_lag = config.autocorrelation_lag();
328 let autocorrelation = || {
329 ac_max_lag.map(|k| {
330 AutocorrAccum::with_backend(
331 k,
332 n_temps,
333 config.autocorrelation_backend,
334 n_measurement_sweeps,
335 )
336 })
337 };
338 let mut m2_accum = autocorrelation();
339 let mut energy_accum = autocorrelation();
340 let mut q2_accum = autocorrelation().filter(|_| n_pairs > 0);
341 let collect_ac = ac_max_lag.is_some();
342 let collect_q2_ac = q2_accum.is_some();
343 let mut m2_ac_buf = vec![0.0f64; if collect_ac { n_temps } else { 0 }];
345 let mut energy_ac_buf = m2_ac_buf.clone();
346
347 let equil_diag = config.equilibration_diagnostic;
348 let mut equil_accum = if equil_diag {
349 Some(EquilDiagnosticAccum::new(n_temps, n_sweeps))
350 } else {
351 None
352 };
353 let mut diag_e_buf = if equil_diag {
354 vec![0.0f32; n_temps]
355 } else {
356 vec![]
357 };
358
359 let mut ov_accum = OverlapAccum::new(
360 n_temps,
361 n_spins,
362 n_pairs,
363 lattice.n_neighbors,
364 equil_diag,
365 collect_q2_ac,
366 )
367 .with_replicas(n_replicas);
368 if !config.conditional_link_overlap {
369 ov_accum = ov_accum.without_conditional_link_overlap();
370 }
371
372 let mut mags_buf = vec![0.0f32; n_temps];
373 let mut mags2_buf = vec![0.0f32; n_temps];
374 let mut mags4_buf = vec![0.0f32; n_temps];
375 let mut energies_buf = vec![0.0f32; n_temps];
376 let mut cache = ObservableCache::new(lattice, unit_lookup.as_ref(), n_systems, config);
377
378 let mut physics = physics_options
379 .map(|options| PhysicsCollector::new(lattice, n_temps, options, 1))
380 .transpose()?
381 .map(|collector| {
382 if n_pairs > 0 {
383 collector.with_overlap(lattice)
384 } else {
385 collector
386 }
387 });
388 let occupied = if physics.is_some() {
389 vec![true; n_spins]
390 } else {
391 vec![]
392 };
393
394 let first_sweep = real.schedule.sweeps;
395 driver::run_sweeps(config, interrupted, on_sweep, first_sweep, |step| {
396 let sweep_id = step.index;
397 let record = step.record;
398 snapshot_due |= record && snapshot_interval.is_some_and(|si| step.clock.is_multiple_of(si));
399 if let Some(acceptance) = acceptance {
400 let cluster_mutates = step.cluster
403 && config
404 .cluster_update
405 .as_ref()
406 .is_some_and(|cluster| cluster.action == ClusterAction::Update);
407 let overlap_mutates_first = !(record || equil_diag)
408 && config
409 .overlap_cluster
410 .as_ref()
411 .is_some_and(|overlap| overlap.action == ClusterAction::Update && step.overlap);
412 let deltas = cache.begin_sweep(!cluster_mutates && !overlap_mutates_first);
413 mcmc::sweep::single_spin_sweep(
414 lattice,
415 &mut real.spins,
416 &real.couplings,
417 &real.temperatures,
418 &real.system_ids,
419 &mut real.rngs,
420 config.sequential,
421 acceptance,
422 unit_lookup.as_ref(),
423 deltas,
424 );
425 cache.end_sweep(&mut real.energies);
426 }
427
428 let cluster_cfg = config
430 .cluster_update
431 .as_ref()
432 .filter(|cluster| step.cluster && (cluster.action == ClusterAction::Update || record));
433 if let Some(cluster_cfg) = cluster_cfg {
434 let wolff = cluster_cfg.mode == crate::config::ClusterMode::Wolff;
435 let csd_out = (collect_fk && record).then_some(fk_csd_slots.as_mut_slice());
436 let observation_out = if cluster_cfg.action == ClusterAction::Observe && record {
437 fk_observation_buf.fill(clusters::GraphObservationSlot::default());
438 Some(fk_observation_buf.as_mut_slice())
439 } else {
440 None
441 };
442
443 clusters::fk_update(
444 lattice,
445 &mut real.spins,
446 &real.couplings,
447 &real.temperatures,
448 &real.system_ids,
449 &mut real.rngs,
450 wolff,
451 cluster_cfg.action,
452 csd_out,
453 observation_out,
454 config.sequential,
455 (fk_updates_spins && record).then_some(fk_visits.as_mut_slice()),
456 );
457 if cluster_cfg.action == ClusterAction::Update {
458 cache.invalidate();
459 fk_recorded_updates += u64::from(record);
460 }
461
462 if let Some(accum) = observation_accums.fk.as_mut().filter(|_| record) {
463 for (slot, observation) in fk_observation_buf.iter().copied().enumerate() {
464 accum.push(slot % n_temps, observation);
465 }
466 }
467 }
468
469 let pt_this_sweep = step.temper && n_temps > 1;
471
472 if record || equil_diag {
474 cache.ensure(real);
475 }
476
477 if equil_diag {
478 diag_e_buf.fill(0.0);
479 #[allow(clippy::needless_range_loop)]
480 for r in 0..n_replicas {
481 let offset = r * n_temps;
482 for t in 0..n_temps {
483 let system_id = real.system_ids[offset + t];
484 diag_e_buf[t] += real.energies[system_id];
485 }
486 }
487 let inv = 1.0 / n_replicas as f32;
488 for v in diag_e_buf.iter_mut() {
489 *v *= inv;
490 }
491 }
492
493 if (equil_diag || record) && n_pairs > 0 {
494 ov_accum.collect(lattice, &real.spins, &real.system_ids, record);
495 }
496
497 if equil_diag {
498 equil_accum.as_mut().unwrap().push(
499 &diag_e_buf,
500 &ov_accum.diag_ql_buf,
501 &ov_accum.diag_q2_buf,
502 );
503 }
504
505 if record {
506 for t in 0..n_temps {
507 mags_buf[t] = 0.0;
508 mags2_buf[t] = 0.0;
509 mags4_buf[t] = 0.0;
510 energies_buf[t] = 0.0;
511 }
512
513 if collect_ac {
514 m2_ac_buf.fill(0.0);
515 energy_ac_buf.fill(0.0);
516 }
517
518 for r in 0..n_replicas {
519 let offset = r * n_temps;
520 for t in 0..n_temps {
521 let system_id = real.system_ids[offset + t];
522 let mag = cache.magnetization_sums[system_id] as f32 / n_spins as f32;
523 let m2 = mag * mag;
524 mags_buf[t] = mag;
525 mags2_buf[t] = m2;
526 mags4_buf[t] = m2 * m2;
527 energies_buf[t] = real.energies[system_id];
528 }
529
530 if collect_ac {
531 for t in 0..n_temps {
532 m2_ac_buf[t] += mags2_buf[t] as f64;
533 energy_ac_buf[t] += energies_buf[t] as f64;
534 }
535 }
536
537 mags_stat.update(&mags_buf);
538 mags2_stat.update(&mags2_buf);
539 mags4_stat.update(&mags4_buf);
540 energies_stat.update(&energies_buf);
541 energies2_stat.update(&energies_buf);
542 }
543
544 if let (Some(m2), Some(energy)) = (m2_accum.as_mut(), energy_accum.as_mut()) {
545 let inv = 1.0 / n_replicas as f64;
546 for v in m2_ac_buf.iter_mut().chain(energy_ac_buf.iter_mut()) {
547 *v *= inv;
548 }
549 m2.push(&m2_ac_buf);
550 energy.push(&energy_ac_buf);
551 }
552
553 if let Some(ref mut acc) = q2_accum {
554 acc.push(&ov_accum.q2_ac_buf);
555 }
556 }
557
558 if let Some(collector) = physics.as_mut().filter(|_| record) {
559 for (slot, &system) in real.system_ids.iter().enumerate() {
561 collector.measure_cached(
562 lattice,
563 &real.spins[system * n_spins..(system + 1) * n_spins],
564 &real.couplings,
565 &occupied,
566 slot % n_temps,
567 CachedTotals {
568 energy: -(real.energies[system] as f64),
569 magnetization: [cache.magnetization_sums[system] as f64, 0.0],
570 },
571 );
572 }
573 let weight = n_replicas as f64 / n_pairs.max(1) as f64;
575 for pair in 0..n_pairs {
576 for t in 0..n_temps {
577 let a = real.system_ids[2 * pair * n_temps + t];
578 let b = real.system_ids[(2 * pair + 1) * n_temps + t];
579 collector.measure_overlap(
580 lattice,
581 &real.spins[a * n_spins..(a + 1) * n_spins],
582 &real.spins[b * n_spins..(b + 1) * n_spins],
583 &occupied,
584 t,
585 weight,
586 );
587 }
588 }
589 if n_pairs > 0 {
590 for t in 0..n_temps {
591 let replicas: Vec<&[i8]> = (0..n_replicas)
592 .map(|r| {
593 let system = real.system_ids[r * n_temps + t];
594 &real.spins[system * n_spins..(system + 1) * n_spins]
595 })
596 .collect();
597 collector.measure_overlap_all_pairs(&replicas, t, n_replicas as f64);
598 }
599 }
600 collector.end_sweep(n_replicas);
601 }
602
603 if let Some(oc_cfg) = config.overlap_cluster.as_ref().filter(|_| step.overlap) {
604 let mode_idx = real.schedule.overlap_calls % n_modes;
605 let mode = &oc_cfg.modes[mode_idx];
606 real.schedule.overlap_calls += 1;
608 if oc_cfg.action == ClusterAction::Update || record {
609 if oc_cfg.action == ClusterAction::Update {
610 cache.invalidate();
611 }
612
613 let ov_csd_out = (collect_overlap && record)
614 .then_some(overlap_csd_slots[mode_idx].as_mut_slice());
615 let observation_out = if oc_cfg.action == ClusterAction::Observe && record {
616 overlap_observation_buf.fill(clusters::GraphObservationSlot::default());
617 Some(overlap_observation_buf.as_mut_slice())
618 } else {
619 None
620 };
621
622 let top4_out = if collect_top && record {
623 for slot in top4_buf.iter_mut() {
624 *slot = [0u32; 4];
625 }
626 Some(top4_buf.as_mut_slice())
627 } else {
628 None
629 };
630
631 let is_rmc = *mode == OverlapClusterBuildMode::Rmc;
632 let take_snapshot = snapshot_due && !is_rmc;
634 snapshot_due &= !take_snapshot;
635
636 let is_cmr = matches!(mode, crate::config::OverlapClusterBuildMode::Cmr);
637
638 let snap = if take_snapshot {
639 for buf in spin_snap_buf.iter_mut() {
640 buf.clear();
641 }
642 for buf in sid_snap_buf.iter_mut() {
643 buf.clear();
644 }
645 Some(snap_buf.as_mut_slice())
646 } else {
647 None
648 };
649 let blue_snap = if take_snapshot && is_cmr {
650 Some(blue_snap_buf.as_mut_slice())
651 } else {
652 None
653 };
654 let spin_snap = if take_snapshot {
655 Some(spin_snap_buf.as_mut_slice())
656 } else {
657 None
658 };
659 let sid_snap = if take_snapshot {
660 Some(sid_snap_buf.as_mut_slice())
661 } else {
662 None
663 };
664
665 if is_rmc {
666 clusters::rmc_update(
667 lattice,
668 &mut real.spins,
669 &real.couplings,
670 &real.temperatures,
671 &real.system_ids,
672 n_replicas,
673 n_temps,
674 &overlap_active,
675 &mut real.rngs,
676 oc_cfg.cluster_mode,
677 config.sequential,
678 (collect_moves && record).then_some(rmc_slots.as_mut_slice()),
679 );
680 } else {
681 clusters::overlap_update(
682 lattice,
683 &mut real.spins,
684 &real.couplings,
685 &real.temperatures,
686 &real.system_ids,
687 n_replicas,
688 n_temps,
689 &overlap_active,
690 &mut real.pair_rngs,
691 mode,
692 oc_cfg.cluster_mode,
693 oc_cfg.action,
694 ov_csd_out,
695 top4_out,
696 observation_out,
697 config.sequential,
698 snap,
699 blue_snap,
700 spin_snap,
701 sid_snap,
702 (collect_moves && record && n_pairs > 0)
703 .then_some(move_slots[mode_idx].as_mut_slice()),
704 );
705 }
706
707 if take_snapshot {
708 let ids: Vec<Vec<u32>> = (0..n_temps)
709 .map(|t| snap_buf[t * n_pairs].clone())
710 .collect();
711 let blue = if is_cmr {
712 Some(
713 (0..n_temps)
714 .map(|t| blue_snap_buf[t * n_pairs].clone())
715 .collect(),
716 )
717 } else {
718 None
719 };
720 let spins: Vec<[Vec<i8>; 2]> = (0..n_temps)
721 .map(|t| {
722 spin_snap_buf[t * n_pairs]
723 .first()
724 .cloned()
725 .unwrap_or_else(|| [vec![], vec![]])
726 })
727 .collect();
728 let sids: Vec<[usize; 2]> = (0..n_temps)
729 .map(|t| sid_snap_buf[t * n_pairs].first().copied().unwrap_or([0, 0]))
730 .collect();
731 cluster_snapshots.push(ClusterSnapshot {
732 sweep_id,
733 mode_idx,
734 cluster_ids: ids,
735 blue_ids: blue,
736 spins,
737 system_ids: sids,
738 });
739 }
740
741 if collect_top && record {
742 for t in 0..n_temps {
743 for p in 0..n_pairs {
744 let raw = top4_buf[t * n_pairs + p];
745 for (k, &v) in raw.iter().enumerate() {
746 top4_accum[mode_idx][t][k] += v as f64 / n_spins as f64;
747 }
748 }
749 }
750 top4_n[mode_idx] += 1;
751 }
752
753 if record && oc_cfg.action == ClusterAction::Observe {
754 let accum = observation_accums.overlap_mut(mode);
755 for (slot, observation) in overlap_observation_buf.iter().copied().enumerate() {
756 accum.push(slot / n_pairs, observation);
757 }
758 }
759 }
760 }
761
762 if pt_this_sweep {
763 cache.ensure(real);
764 real.temper(n_spins, n_replicas, n_temps, config.pt_schedule);
765 }
766 real.schedule.sweeps += 1;
767 })?;
768
769 let top_cluster_sizes: Vec<Vec<[f64; 4]>> = if collect_top {
770 let modes = &config.overlap_cluster.as_ref().unwrap().modes;
771 top4_accum
772 .iter()
773 .zip(top4_n.iter())
774 .zip(modes)
775 .map(|((mode_accum, &count), mode)| {
776 let n_groups = match mode {
778 OverlapClusterBuildMode::Rmc => 0,
779 _ => n_replicas / mode.group_size(),
780 };
781 if count == 0 || n_groups == 0 {
782 return vec![[0.0; 4]; n_temps];
783 }
784 let denom = (count * n_groups) as f64;
785 mode_accum
786 .iter()
787 .map(|arr| {
788 [
789 arr[0] / denom,
790 arr[1] / denom,
791 arr[2] / denom,
792 arr[3] / denom,
793 ]
794 })
795 .collect()
796 })
797 .collect()
798 } else {
799 vec![]
800 };
801
802 let (mags2_tau, mags2_tau_converged) = driver::tau_estimates(m2_accum.as_ref());
803 let (overlap2_tau, overlap2_tau_converged) = driver::tau_estimates(q2_accum.as_ref());
804 let (energy_tau, energy_tau_converged) = driver::tau_estimates(energy_accum.as_ref());
805
806 let equil_checkpoints = equil_accum.map(|acc| acc.finish()).unwrap_or_default();
807 let winding_dims = if lattice.square_shape().is_some() {
809 2
810 } else if lattice.cubic_shape().is_some() {
811 3
812 } else {
813 0
814 };
815
816 let keep_shape = |collected: bool| collected || materialize_disabled_stats;
818 let fk_csd = if keep_shape(collect_fk) {
819 fold_histograms(&fk_csd_slots, n_temps, n_spins, |slot| slot % n_temps)
820 } else {
821 vec![]
822 };
823 let overlap_csd: Vec<Vec<Vec<u64>>> = if keep_shape(collect_overlap) {
824 overlap_csd_slots
825 .iter()
826 .map(|slots| fold_histograms(slots, n_temps, n_spins, |slot| slot / n_pairs))
827 .collect()
828 } else {
829 vec![]
830 };
831 let cluster_observations = observation_accums.finish(
832 n_spins,
833 lattice.n_neighbors,
834 winding_dims,
835 &fk_csd,
836 config.overlap_cluster.as_ref().map_or(&[], |o| &o.modes),
837 &overlap_csd,
838 );
839
840 let overlap_stats = ov_accum.finish();
841 let mags2 = mags2_stat.average();
842 let mags4 = mags4_stat.average();
843 let energies = energies_stat.average();
844 let energy_variance = Statistics::variance(&energies_stat, &energies2_stat);
845 let sample = DisorderSample {
846 mags2: mags2.clone(),
847 mags4: mags4.clone(),
848 energies: energies.clone(),
849 energy_variance: energy_variance.clone(),
850 overlap2: overlap_stats.overlap2.clone(),
851 overlap4: overlap_stats.overlap4.clone(),
852 link_overlap: overlap_stats.link_overlap.clone(),
853 link_overlap2: overlap_stats.link_overlap2.clone(),
854 link_overlap4: overlap_stats.link_overlap4.clone(),
855 mags2_tau: mags2_tau.clone(),
856 overlap2_tau: overlap2_tau.clone(),
857 energy_tau: energy_tau.clone(),
858 mags2_tau_converged: mags2_tau_converged.clone(),
859 overlap2_tau_converged: overlap2_tau_converged.clone(),
860 energy_tau_converged: energy_tau_converged.clone(),
861 };
862
863 Ok(SweepResult {
864 mags: mags_stat.average(),
865 mags2,
866 mags4,
867 energies,
868 energies2: energies2_stat.average(),
869 energy_variance,
870 overlap_stats,
871 cluster_stats: ClusterStats {
872 fk_csd,
873 overlap_csd,
874 top_cluster_sizes,
875 fk_updates: if fk_updates_spins {
876 vec![fk_recorded_updates * n_replicas as u64; n_temps]
877 } else {
878 vec![]
879 },
880 overlap_moves: if collect_moves {
881 config
882 .overlap_cluster
883 .as_ref()
884 .map_or(&[][..], |overlap| &overlap.modes)
885 .iter()
886 .zip(&move_slots)
887 .map(|(mode, slots)| match mode {
888 OverlapClusterBuildMode::Rmc => vec![],
889 _ => fold_counts(
890 slots,
891 n_temps,
892 |slot| slot / n_pairs.max(1),
893 add_move_counts,
894 ),
895 })
896 .collect()
897 } else {
898 vec![]
899 },
900 rmc_edges: if collect_moves
901 && config
902 .overlap_cluster
903 .as_ref()
904 .is_some_and(|overlap| overlap.modes.contains(&OverlapClusterBuildMode::Rmc))
905 {
906 let mut edges =
907 fold_counts(&rmc_slots, n_temps, |slot| slot % n_temps, add_rmc_counts);
908 edges.pop();
909 edges
910 } else {
911 vec![]
912 },
913 fk_visited_spins: if fk_updates_spins {
914 (0..n_temps)
915 .map(|t| fk_visits.iter().skip(t).step_by(n_temps).sum())
916 .collect()
917 } else {
918 vec![]
919 },
920 },
921 per_disorder_cluster_observations: vec![cluster_observations],
922 per_disorder_physics: physics.map(|p| vec![p.finish()]).unwrap_or_default(),
923 per_disorder: vec![sample],
924 diagnostics: Diagnostics {
925 mags2_tau,
926 overlap2_tau,
927 energy_tau,
928 mags2_tau_converged,
929 overlap2_tau_converged,
930 energy_tau_converged,
931 autocorrelation_lag: ac_max_lag,
932 equil_checkpoints,
933 },
934 cluster_snapshots,
935 })
936}
937
938pub fn run_sweep_parallel(
946 lattice: &Lattice,
947 realizations: &mut [Realization],
948 n_replicas: usize,
949 n_temps: usize,
950 config: &SimConfig,
951 interrupted: &AtomicBool,
952 on_sweep: &(dyn Fn() + Sync),
953) -> Result<SweepResult, String> {
954 run_sweep_parallel_with_physics(
955 lattice,
956 realizations,
957 n_replicas,
958 n_temps,
959 config,
960 interrupted,
961 on_sweep,
962 None,
963 )
964}
965
966#[allow(clippy::too_many_arguments)]
968pub fn run_sweep_parallel_with_physics(
969 lattice: &Lattice,
970 realizations: &mut [Realization],
971 n_replicas: usize,
972 n_temps: usize,
973 config: &SimConfig,
974 interrupted: &AtomicBool,
975 on_sweep: &(dyn Fn() + Sync),
976 physics: Option<&PhysicsOptions>,
977) -> Result<SweepResult, String> {
978 driver::validate_batch(lattice, realizations, n_replicas, n_temps, config)?;
979 if let Some(options) = physics {
980 options.validate(lattice, 1)?;
981 }
982 let single = realizations.len() == 1;
983 let mut effective = config.clone();
984 effective.sequential = driver::effective_sequential(config, realizations.len());
985 let config = &effective;
986 let mut results = driver::map_realizations(realizations, |idx, real| {
987 run_sweep_loop_impl(
988 lattice,
989 real,
990 n_replicas,
991 n_temps,
992 config,
993 interrupted,
994 on_sweep,
995 idx,
996 single,
997 physics,
998 )
999 })?;
1000 if single {
1001 return Ok(results.remove(0));
1002 }
1003 let snapshots = std::mem::take(&mut results[0].cluster_snapshots);
1004 let physics = results
1005 .iter_mut()
1006 .flat_map(|r| std::mem::take(&mut r.per_disorder_physics))
1007 .collect();
1008 let mut agg = SweepResult::aggregate_without_overlap_samples(&results);
1009 agg.per_disorder_physics = physics;
1010 if !agg.overlap_stats.overlap.is_empty() {
1011 agg.overlap_stats.per_sample_histogram = results
1012 .iter_mut()
1013 .map(|r| std::mem::take(&mut r.overlap_stats.histogram))
1014 .collect();
1015 }
1016 if !agg.overlap_stats.ql_at_q_sum.is_empty() {
1017 agg.overlap_stats.per_sample_ql_at_q_sum = results
1018 .iter_mut()
1019 .map(|r| std::mem::take(&mut r.overlap_stats.ql_at_q_sum))
1020 .collect();
1021 agg.overlap_stats.per_sample_ql2_at_q_sum = results
1022 .iter_mut()
1023 .map(|r| std::mem::take(&mut r.overlap_stats.ql2_at_q_sum))
1024 .collect();
1025 }
1026 if agg.cluster_stats.fk_csd.is_empty() {
1027 agg.cluster_stats.fk_csd = (0..n_temps)
1028 .map(|_| vec![0u64; lattice.n_spins + 1])
1029 .collect();
1030 }
1031 let n_modes = config.overlap_cluster.as_ref().map_or(0, |h| h.modes.len());
1032 if n_modes > 0 && agg.cluster_stats.overlap_csd.is_empty() {
1033 agg.cluster_stats.overlap_csd = (0..n_modes)
1034 .map(|_| {
1035 (0..n_temps)
1036 .map(|_| vec![0u64; lattice.n_spins + 1])
1037 .collect()
1038 })
1039 .collect();
1040 }
1041 agg.cluster_snapshots = snapshots;
1042 Ok(agg)
1043}
1044
1045#[cfg(test)]
1046mod tests {
1047 use super::*;
1048 use crate::config::{
1049 AutocorrelationBackend, ClusterConfig, ClusterMode, OverlapClusterBuildMode,
1050 OverlapClusterConfig, PtSchedule,
1051 };
1052
1053 fn run_with_cluster_stats(collect_stats: bool) -> SweepResult {
1054 let lattice = Lattice::new(vec![4, 4]);
1055 let couplings = vec![1.0; lattice.n_spins * lattice.n_neighbors];
1056 let mut realization = Realization::new(&lattice, couplings, &[2.0], 1, 42);
1057 let config = SimConfig {
1058 n_sweeps: 1,
1059 warmup_sweeps: 0,
1060 sweep_mode: SweepMode::Metropolis,
1061 cluster_update: Some(ClusterConfig {
1062 interval: 1,
1063 mode: ClusterMode::Sw,
1064 action: ClusterAction::Update,
1065 collect_stats,
1066 }),
1067 pt_interval: None,
1068 pt_schedule: PtSchedule::SingleRandomEdge,
1069 overlap_cluster: None,
1070 autocorrelation_max_lag: None,
1071 autocorrelation_backend: AutocorrelationBackend::Ring,
1072 sequential: true,
1073 equilibration_diagnostic: false,
1074 conditional_link_overlap: true,
1075 };
1076
1077 run_sweep_loop(
1078 &lattice,
1079 &mut realization,
1080 1,
1081 1,
1082 &config,
1083 &AtomicBool::new(false),
1084 &|| {},
1085 0,
1086 )
1087 .unwrap()
1088 }
1089
1090 fn plain_config(n_sweeps: usize) -> SimConfig {
1091 SimConfig {
1092 n_sweeps,
1093 warmup_sweeps: 0,
1094 sweep_mode: SweepMode::Metropolis,
1095 cluster_update: None,
1096 pt_interval: None,
1097 pt_schedule: PtSchedule::SingleRandomEdge,
1098 overlap_cluster: None,
1099 autocorrelation_max_lag: None,
1100 autocorrelation_backend: AutocorrelationBackend::Ring,
1101 sequential: true,
1102 equilibration_diagnostic: false,
1103 conditional_link_overlap: true,
1104 }
1105 }
1106
1107 fn run(
1108 lattice: &Lattice,
1109 real: &mut Realization,
1110 n_replicas: usize,
1111 config: &SimConfig,
1112 ) -> SweepResult {
1113 let n_temps = real.temperatures.len() / n_replicas;
1114 run_sweep_loop(
1115 lattice,
1116 real,
1117 n_replicas,
1118 n_temps,
1119 config,
1120 &AtomicBool::new(false),
1121 &|| {},
1122 0,
1123 )
1124 .unwrap()
1125 }
1126
1127 #[test]
1130 fn interval_phase_spans_calls() {
1131 let lattice = Lattice::new(vec![4, 4]);
1132 let couplings = vec![1.0; lattice.n_spins * lattice.n_neighbors];
1133 let mut real = Realization::new(&lattice, couplings, &[1.5, 2.5], 1, 9);
1134 let mut config = plain_config(1);
1135 config.pt_interval = Some(2);
1136 for _ in 0..6 {
1137 run(&lattice, &mut real, 1, &config);
1138 }
1139 assert_eq!(real.pt_edge_attempts(), &[3]);
1140
1141 real.reset(&lattice, 1, 2, 9);
1142 run(&lattice, &mut real, 1, &config);
1143 assert_eq!(real.pt_edge_attempts(), &[1], "reset restarts the phase");
1144 }
1145
1146 #[test]
1147 fn conditional_link_overlap_can_be_turned_off() {
1148 let lattice = Lattice::new(vec![4, 4]);
1149 let couplings = vec![1.0; lattice.n_spins * lattice.n_neighbors];
1150 let mut config = plain_config(4);
1151 for enabled in [true, false] {
1152 config.conditional_link_overlap = enabled;
1153 let mut real = Realization::new(&lattice, couplings.clone(), &[2.0], 2, 3);
1154 let stats = run(&lattice, &mut real, 2, &config).overlap_stats;
1155 assert_eq!(stats.histogram[0].iter().sum::<u64>(), 4);
1156 assert_eq!(stats.ql_at_q_sum.is_empty(), !enabled);
1157 assert_eq!(stats.ql2_at_q_sum.is_empty(), !enabled);
1158 }
1159 }
1160
1161 #[test]
1164 fn overlap_move_counters_are_consistent() {
1165 let lattice = Lattice::new(vec![6, 6]);
1166 let couplings: Vec<f32> = (0..lattice.n_spins * lattice.n_neighbors)
1167 .map(|i| if i % 3 == 0 { -1.0 } else { 1.0 })
1168 .collect();
1169 let temps = [0.8, 1.2, 2.0];
1170 let mut config = plain_config(60);
1171 config.warmup_sweeps = 10;
1172 config.pt_interval = Some(1);
1173 config.overlap_cluster = Some(OverlapClusterConfig {
1174 interval: 1,
1175 modes: vec![
1176 OverlapClusterBuildMode::Houdayer(2),
1177 OverlapClusterBuildMode::Cmr,
1178 OverlapClusterBuildMode::Rmc,
1179 ],
1180 cluster_mode: ClusterMode::Sw,
1181 action: ClusterAction::Update,
1182 collect_stats: true,
1183 snapshot_interval: None,
1184 max_temperature: None,
1185 });
1186 let mut real = Realization::new(&lattice, couplings, &temps, 2, 11);
1187 let stats = run(&lattice, &mut real, 2, &config).cluster_stats;
1188 assert_eq!(stats.overlap_moves.len(), 3);
1189 assert!(
1190 stats.overlap_moves[2].is_empty(),
1191 "rmc publishes no group moves"
1192 );
1193 for (counts, calls) in stats.overlap_moves[..2].iter().zip([16, 17]) {
1196 for counts in counts {
1197 assert_eq!((counts.groups, counts.counted_groups), (calls, calls));
1198 assert!(counts.moved_sites <= counts.active_sites);
1199 }
1200 }
1201 let n = lattice.n_spins as u64;
1202 assert!(stats.overlap_moves[1]
1203 .iter()
1204 .all(|c| c.active_sites == 17 * n));
1205 assert_eq!(stats.rmc_edges.len(), temps.len() - 1);
1206 for edge in &stats.rmc_edges {
1207 assert!(edge.proposed > 0 && edge.accepted <= edge.proposed);
1208 assert!(edge.flipped_sites <= 2 * 16 * n);
1209 }
1210 }
1211
1212 #[test]
1214 fn warmup_only_calls_report_nan_moments() {
1215 let lattice = Lattice::new(vec![4, 4]);
1216 let couplings = vec![1.0; lattice.n_spins * lattice.n_neighbors];
1217 let mut real = Realization::new(&lattice, couplings, &[2.0], 2, 3);
1218 let mut config = plain_config(5);
1219 config.warmup_sweeps = 5;
1220 config.autocorrelation_max_lag = Some(2);
1221 let result = run(&lattice, &mut real, 2, &config);
1222 assert!(result.mags2[0].is_nan() && result.energies[0].is_nan());
1223 assert!(result.overlap_stats.overlap2[0].is_nan());
1224 assert!(result.diagnostics.mags2_tau[0].is_nan());
1225 assert_eq!(result.diagnostics.mags2_tau_converged, vec![false]);
1226 assert_eq!(result.per_disorder.len(), 1);
1227 }
1228
1229 #[test]
1232 fn tracked_energies_match_recomputation() {
1233 let lattice = Lattice::new(vec![6, 5]);
1234 let couplings: Vec<f32> = (0..lattice.n_spins * lattice.n_neighbors)
1235 .map(|i| [1.0, -1.0, 1.0, 0.0][i % 4])
1236 .collect();
1237 let temps = [0.6, 1.1, 1.9, 3.0];
1238 for (sweep_mode, with_clusters) in [
1239 (SweepMode::Metropolis, false),
1240 (SweepMode::Gibbs, false),
1241 (SweepMode::Metropolis, true),
1242 ] {
1243 let mut realization = Realization::new(&lattice, couplings.clone(), &temps, 2, 5);
1244 let config = SimConfig {
1245 n_sweeps: 300,
1246 warmup_sweeps: 100,
1247 sweep_mode,
1248 cluster_update: with_clusters.then_some(ClusterConfig {
1249 interval: 7,
1250 mode: ClusterMode::Sw,
1251 action: ClusterAction::Update,
1252 collect_stats: false,
1253 }),
1254 pt_interval: Some(1),
1255 pt_schedule: PtSchedule::FullLadder,
1256 overlap_cluster: with_clusters.then_some(OverlapClusterConfig {
1257 interval: 5,
1258 modes: vec![OverlapClusterBuildMode::Cmr, OverlapClusterBuildMode::Rmc],
1259 cluster_mode: ClusterMode::Sw,
1260 action: ClusterAction::Update,
1261 collect_stats: false,
1262 snapshot_interval: None,
1263 max_temperature: None,
1264 }),
1265 autocorrelation_max_lag: None,
1266 autocorrelation_backend: AutocorrelationBackend::Ring,
1267 sequential: false,
1268 equilibration_diagnostic: false,
1269 conditional_link_overlap: true,
1270 };
1271 run_sweep_loop(
1272 &lattice,
1273 &mut realization,
1274 2,
1275 temps.len(),
1276 &config,
1277 &AtomicBool::new(false),
1278 &|| {},
1279 0,
1280 )
1281 .unwrap();
1282 let mut expected = vec![0.0; realization.energies.len()];
1283 spins::energy::compute_energies_into(
1284 &lattice,
1285 &realization.spins,
1286 &realization.couplings,
1287 &mut expected,
1288 );
1289 assert_eq!(
1292 realization.energies, expected,
1293 "{sweep_mode:?} clusters={with_clusters}"
1294 );
1295 }
1296 }
1297
1298 #[test]
1301 fn overlap_moves_respect_max_temperature() {
1302 let lattice = Lattice::new(vec![6, 6]);
1303 let couplings: Vec<f32> = (0..lattice.n_spins * lattice.n_neighbors)
1304 .map(|i| if i % 3 == 0 { -1.0 } else { 1.0 })
1305 .collect();
1306 let temps = [0.5, 1.0, 3.0];
1307 for mode in [
1308 OverlapClusterBuildMode::Houdayer(2),
1309 OverlapClusterBuildMode::Rmc,
1310 ] {
1311 let mut realization = Realization::new(&lattice, couplings.clone(), &temps, 2, 31);
1312 let initial = realization.spins.clone();
1313 let config = SimConfig {
1314 n_sweeps: 20,
1315 warmup_sweeps: 0,
1316 sweep_mode: SweepMode::None,
1317 cluster_update: None,
1318 pt_interval: None,
1319 pt_schedule: PtSchedule::SingleRandomEdge,
1320 overlap_cluster: Some(OverlapClusterConfig {
1321 interval: 1,
1322 modes: vec![mode],
1323 cluster_mode: ClusterMode::Sw,
1324 action: ClusterAction::Update,
1325 collect_stats: false,
1326 snapshot_interval: None,
1327 max_temperature: Some(1.0),
1328 }),
1329 autocorrelation_max_lag: None,
1330 autocorrelation_backend: AutocorrelationBackend::Ring,
1331 sequential: true,
1332 equilibration_diagnostic: false,
1333 conditional_link_overlap: true,
1334 };
1335 run_sweep_loop(
1336 &lattice,
1337 &mut realization,
1338 2,
1339 temps.len(),
1340 &config,
1341 &AtomicBool::new(false),
1342 &|| {},
1343 0,
1344 )
1345 .unwrap();
1346 let n = lattice.n_spins;
1347 let changed = |t: usize| {
1348 (0..2).any(|r| {
1349 let system = realization.system_ids[r * temps.len() + t];
1350 realization.spins[system * n..(system + 1) * n]
1351 != initial[system * n..(system + 1) * n]
1352 })
1353 };
1354 assert!(!changed(2), "{mode:?} moved the T = 3.0 systems");
1355 assert!(changed(0), "{mode:?} never moved the T = 0.5 systems");
1356 }
1357 }
1358
1359 #[test]
1360 fn disabled_cluster_histograms_preserve_public_shape() {
1361 let without_stats = run_with_cluster_stats(false);
1362 assert_eq!(without_stats.cluster_stats.fk_csd.len(), 1);
1363 assert_eq!(without_stats.cluster_stats.fk_csd[0].iter().sum::<u64>(), 0);
1364
1365 let with_stats = run_with_cluster_stats(true);
1366 assert_eq!(with_stats.cluster_stats.fk_csd.len(), 1);
1367 assert!(with_stats.cluster_stats.fk_csd[0].iter().sum::<u64>() > 0);
1368 }
1369
1370 #[test]
1371 fn disabled_overlap_histograms_preserve_public_shape() {
1372 let lattice = Lattice::new(vec![4, 4]);
1373 let couplings = vec![1.0; lattice.n_spins * lattice.n_neighbors];
1374
1375 for collect_stats in [false, true] {
1376 let mut realization = Realization::new(&lattice, couplings.clone(), &[2.0], 2, 42);
1377 let config = SimConfig {
1378 n_sweeps: 1,
1379 warmup_sweeps: 0,
1380 sweep_mode: SweepMode::Metropolis,
1381 cluster_update: None,
1382 pt_interval: None,
1383 pt_schedule: PtSchedule::SingleRandomEdge,
1384 overlap_cluster: Some(OverlapClusterConfig {
1385 interval: 1,
1386 modes: vec![OverlapClusterBuildMode::Houdayer(2)],
1387 cluster_mode: ClusterMode::Sw,
1388 action: ClusterAction::Update,
1389 collect_stats,
1390 snapshot_interval: None,
1391 max_temperature: None,
1392 }),
1393 autocorrelation_max_lag: None,
1394 autocorrelation_backend: AutocorrelationBackend::Ring,
1395 sequential: true,
1396 equilibration_diagnostic: false,
1397 conditional_link_overlap: true,
1398 };
1399
1400 let result = run_sweep_loop(
1401 &lattice,
1402 &mut realization,
1403 2,
1404 1,
1405 &config,
1406 &AtomicBool::new(false),
1407 &|| {},
1408 0,
1409 )
1410 .unwrap();
1411
1412 if collect_stats {
1413 assert_eq!(result.cluster_stats.overlap_csd.len(), 1);
1414 assert!(result.cluster_stats.overlap_csd[0][0].iter().sum::<u64>() > 0);
1415 } else {
1416 assert_eq!(result.cluster_stats.overlap_csd.len(), 1);
1417 assert_eq!(
1418 result.cluster_stats.overlap_csd[0][0].iter().sum::<u64>(),
1419 0
1420 );
1421 }
1422 }
1423 }
1424
1425 #[test]
1428 fn top_cluster_sizes_average_over_replica_groups() {
1429 let lattice = Lattice::new(vec![6, 6]);
1430 let n = lattice.n_spins;
1431 let couplings = vec![1.0; n * lattice.n_neighbors];
1432 let mut realization = Realization::new(&lattice, couplings, &[2.0], 4, 3);
1433 let balanced: Vec<bool> = (0..n)
1435 .map(|i| {
1436 (0..4)
1437 .map(|r| realization.spins[r * n + i] as i32)
1438 .sum::<i32>()
1439 == 0
1440 })
1441 .collect();
1442 let mut component = vec![usize::MAX; n];
1443 let mut sizes = Vec::new();
1444 for start in 0..n {
1445 if component[start] != usize::MAX {
1446 continue;
1447 }
1448 component[start] = sizes.len();
1449 let (mut stack, mut size) = (vec![start], 0);
1450 while let Some(i) = stack.pop() {
1451 size += 1;
1452 for d in 0..lattice.n_neighbors {
1453 for j in [lattice.neighbor_fwd(i, d), lattice.neighbor_bwd(i, d)] {
1454 if balanced[i] && balanced[j] && component[j] == usize::MAX {
1455 component[j] = sizes.len();
1456 stack.push(j);
1457 }
1458 }
1459 }
1460 }
1461 sizes.push(size);
1462 }
1463 sizes.sort_unstable_by(|a, b| b.cmp(a));
1464 let expected: Vec<f64> = sizes[..4].iter().map(|&s| s as f64 / n as f64).collect();
1465
1466 let config = SimConfig {
1467 n_sweeps: 1,
1468 warmup_sweeps: 0,
1469 sweep_mode: SweepMode::None,
1470 cluster_update: None,
1471 pt_interval: None,
1472 pt_schedule: PtSchedule::SingleRandomEdge,
1473 overlap_cluster: Some(OverlapClusterConfig {
1474 interval: 1,
1475 modes: vec![OverlapClusterBuildMode::Houdayer(4)],
1476 cluster_mode: ClusterMode::Sw,
1477 action: ClusterAction::Update,
1478 collect_stats: true,
1479 snapshot_interval: None,
1480 max_temperature: None,
1481 }),
1482 autocorrelation_max_lag: None,
1483 autocorrelation_backend: AutocorrelationBackend::Ring,
1484 sequential: true,
1485 equilibration_diagnostic: false,
1486 conditional_link_overlap: true,
1487 };
1488 let result = run_sweep_loop(
1489 &lattice,
1490 &mut realization,
1491 4,
1492 1,
1493 &config,
1494 &AtomicBool::new(false),
1495 &|| {},
1496 0,
1497 )
1498 .unwrap();
1499 assert_eq!(
1500 result.cluster_stats.top_cluster_sizes[0][0].to_vec(),
1501 expected
1502 );
1503 }
1504
1505 #[test]
1506 fn fk_observe_records_without_changing_spins() {
1507 let lattice = Lattice::new(vec![4, 4]);
1508 let couplings = vec![1.0; lattice.n_spins * lattice.n_neighbors];
1509 let mut observed = Realization::new(&lattice, couplings.clone(), &[2.0], 1, 19);
1510 let mut baseline = Realization::new(&lattice, couplings, &[2.0], 1, 19);
1511 let mut config = SimConfig {
1512 n_sweeps: 1,
1513 warmup_sweeps: 0,
1514 sweep_mode: SweepMode::Metropolis,
1515 cluster_update: None,
1516 pt_interval: None,
1517 pt_schedule: PtSchedule::SingleRandomEdge,
1518 overlap_cluster: None,
1519 autocorrelation_max_lag: None,
1520 autocorrelation_backend: AutocorrelationBackend::Ring,
1521 sequential: true,
1522 equilibration_diagnostic: false,
1523 conditional_link_overlap: true,
1524 };
1525 run_sweep_loop(
1526 &lattice,
1527 &mut baseline,
1528 1,
1529 1,
1530 &config,
1531 &AtomicBool::new(false),
1532 &|| {},
1533 0,
1534 )
1535 .unwrap();
1536
1537 config.cluster_update = Some(ClusterConfig {
1538 interval: 1,
1539 mode: ClusterMode::Sw,
1540 action: ClusterAction::Observe,
1541 collect_stats: true,
1542 });
1543 let result = run_sweep_loop(
1544 &lattice,
1545 &mut observed,
1546 1,
1547 1,
1548 &config,
1549 &AtomicBool::new(false),
1550 &|| {},
1551 0,
1552 )
1553 .unwrap();
1554
1555 assert_eq!(observed.spins, baseline.spins);
1556 assert_eq!(observed.system_ids, baseline.system_ids);
1557 let summary = result.per_disorder_cluster_observations[0]
1558 .fk
1559 .as_ref()
1560 .unwrap();
1561 assert_eq!(summary.observation_count, vec![1]);
1562 assert_eq!(
1563 summary.cluster_size_counts[0]
1564 .iter()
1565 .enumerate()
1566 .map(|(size, &count)| size as u64 * count)
1567 .sum::<u64>(),
1568 lattice.n_spins as u64
1569 );
1570 }
1571
1572 #[test]
1573 fn cmr_observe_stops_before_cluster_actions() {
1574 let lattice = Lattice::new(vec![4, 4]);
1575 let couplings = vec![1.0; lattice.n_spins * lattice.n_neighbors];
1576 let mut observed = Realization::new(&lattice, couplings.clone(), &[1.5], 2, 23);
1577 let mut baseline = Realization::new(&lattice, couplings, &[1.5], 2, 23);
1578 let mut config = SimConfig {
1579 n_sweeps: 1,
1580 warmup_sweeps: 0,
1581 sweep_mode: SweepMode::Metropolis,
1582 cluster_update: None,
1583 pt_interval: None,
1584 pt_schedule: PtSchedule::SingleRandomEdge,
1585 overlap_cluster: None,
1586 autocorrelation_max_lag: None,
1587 autocorrelation_backend: AutocorrelationBackend::Ring,
1588 sequential: true,
1589 equilibration_diagnostic: false,
1590 conditional_link_overlap: true,
1591 };
1592 run_sweep_loop(
1593 &lattice,
1594 &mut baseline,
1595 2,
1596 1,
1597 &config,
1598 &AtomicBool::new(false),
1599 &|| {},
1600 0,
1601 )
1602 .unwrap();
1603
1604 config.overlap_cluster = Some(OverlapClusterConfig {
1605 interval: 1,
1606 modes: vec![OverlapClusterBuildMode::Cmr],
1607 cluster_mode: ClusterMode::Sw,
1608 action: ClusterAction::Observe,
1609 collect_stats: true,
1610 snapshot_interval: None,
1611 max_temperature: None,
1612 });
1613 let result = run_sweep_loop(
1614 &lattice,
1615 &mut observed,
1616 2,
1617 1,
1618 &config,
1619 &AtomicBool::new(false),
1620 &|| {},
1621 0,
1622 )
1623 .unwrap();
1624
1625 assert_eq!(observed.spins, baseline.spins);
1626 assert_eq!(observed.system_ids, baseline.system_ids);
1627 assert_eq!(
1628 result.per_disorder_cluster_observations[0]
1629 .cmr_blue
1630 .as_ref()
1631 .unwrap()
1632 .observation_count,
1633 vec![1]
1634 );
1635 }
1636}