Skip to main content

spin_sim/
config.rs

1use validator::{Validate, ValidationError};
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum SweepMode {
5    Metropolis,
6    Gibbs,
7}
8
9impl TryFrom<&str> for SweepMode {
10    type Error = String;
11    fn try_from(s: &str) -> Result<Self, Self::Error> {
12        match s {
13            "metropolis" => Ok(Self::Metropolis),
14            "gibbs" => Ok(Self::Gibbs),
15            _ => Err(format!(
16                "unknown sweep_mode '{s}', expected 'metropolis' or 'gibbs'"
17            )),
18        }
19    }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum ClusterMode {
24    Wolff,
25    Sw,
26}
27
28impl TryFrom<&str> for ClusterMode {
29    type Error = String;
30    fn try_from(s: &str) -> Result<Self, Self::Error> {
31        match s {
32            "wolff" => Ok(Self::Wolff),
33            "sw" => Ok(Self::Sw),
34            _ => Err(format!(
35                "unknown cluster_mode '{s}', expected 'wolff' or 'sw'"
36            )),
37        }
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq)]
42pub enum OverlapClusterBuildMode {
43    Houdayer(usize),
44    Jorg,
45    Cmr,
46}
47
48impl OverlapClusterBuildMode {
49    pub fn group_size(&self) -> usize {
50        match self {
51            Self::Houdayer(n) => *n,
52            _ => 2,
53        }
54    }
55}
56
57impl TryFrom<&str> for OverlapClusterBuildMode {
58    type Error = String;
59    fn try_from(s: &str) -> Result<Self, Self::Error> {
60        match s {
61            "houdayer" | "houd2" => Ok(Self::Houdayer(2)),
62            "jorg" => Ok(Self::Jorg),
63            "cmr" | "cmr2" => Ok(Self::Cmr),
64            _ if s.starts_with("houd") => {
65                let n: usize = s[4..].parse().map_err(|_| {
66                    format!(
67                        "invalid Houdayer group size in '{s}', expected 'houdN' with even integer N >= 2"
68                    )
69                })?;
70                if n < 2 || !n.is_multiple_of(2) {
71                    return Err(format!(
72                        "Houdayer group size must be even and >= 2, got {n}"
73                    ));
74                }
75                if n > 2 {
76                    eprintln!(
77                        "WARNING: houd{n} (group_size > 2) is experimental and very likely \
78                         does not satisfy detailed balance"
79                    );
80                }
81                Ok(Self::Houdayer(n))
82            }
83            _ => Err(format!(
84                "unknown overlap_cluster_build_mode '{s}', expected 'houdayer', 'houdN', 'jorg', or 'cmr'"
85            )),
86        }
87    }
88}
89
90#[derive(Debug)]
91pub struct ClusterConfig {
92    pub interval: usize,
93    pub mode: ClusterMode,
94    pub collect_stats: bool,
95}
96
97#[derive(Debug)]
98pub struct OverlapClusterConfig {
99    pub interval: usize,
100    pub modes: Vec<OverlapClusterBuildMode>,
101    pub cluster_mode: ClusterMode,
102    pub collect_stats: bool,
103    pub snapshot_interval: Option<usize>,
104}
105
106impl OverlapClusterConfig {
107    pub fn max_group_size(&self) -> usize {
108        self.modes.iter().map(|m| m.group_size()).max().unwrap_or(2)
109    }
110}
111
112pub fn parse_overlap_modes(s: &str) -> Result<Vec<OverlapClusterBuildMode>, String> {
113    s.split('+')
114        .map(|part| OverlapClusterBuildMode::try_from(part.trim()))
115        .collect()
116}
117
118fn validate_sim_config(cfg: &SimConfig) -> Result<(), ValidationError> {
119    if cfg.n_sweeps < 1 {
120        return Err(ValidationError::new("n_sweeps must be >= 1"));
121    }
122    if cfg.warmup_sweeps > cfg.n_sweeps {
123        return Err(ValidationError::new("warmup_sweeps must be <= n_sweeps"));
124    }
125    if let Some(ref c) = cfg.cluster_update {
126        if c.interval < 1 {
127            return Err(ValidationError::new("cluster_update interval must be >= 1"));
128        }
129    }
130    if let Some(ref h) = cfg.overlap_cluster {
131        if h.interval < 1 {
132            return Err(ValidationError::new(
133                "overlap_cluster interval must be >= 1",
134            ));
135        }
136        if let Some(si) = h.snapshot_interval {
137            if si < 1 || si % h.interval != 0 {
138                return Err(ValidationError::new(
139                    "snapshot_interval must be a positive multiple of overlap_cluster interval",
140                ));
141            }
142        }
143    }
144    Ok(())
145}
146
147#[derive(Debug, Validate)]
148#[validate(schema(function = "validate_sim_config"))]
149pub struct SimConfig {
150    pub n_sweeps: usize,
151    pub warmup_sweeps: usize,
152    pub sweep_mode: SweepMode,
153    pub cluster_update: Option<ClusterConfig>,
154    pub pt_interval: Option<usize>,
155    pub overlap_cluster: Option<OverlapClusterConfig>,
156    pub autocorrelation_max_lag: Option<usize>,
157    pub sequential: bool,
158    pub equilibration_diagnostic: bool,
159}