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}