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 Ok(Self::Houdayer(n))
76 }
77 _ => Err(format!(
78 "unknown overlap_cluster_build_mode '{s}', expected 'houdayer', 'houdN', 'jorg', or 'cmr'"
79 )),
80 }
81 }
82}
83
84#[derive(Debug)]
85pub struct ClusterConfig {
86 pub interval: usize,
87 pub mode: ClusterMode,
88 pub collect_stats: bool,
89}
90
91#[derive(Debug)]
92pub struct OverlapClusterConfig {
93 pub interval: usize,
94 pub modes: Vec<OverlapClusterBuildMode>,
95 pub cluster_mode: ClusterMode,
96 pub collect_stats: bool,
97}
98
99impl OverlapClusterConfig {
100 pub fn max_group_size(&self) -> usize {
101 self.modes.iter().map(|m| m.group_size()).max().unwrap_or(2)
102 }
103}
104
105pub fn parse_overlap_modes(s: &str) -> Result<Vec<OverlapClusterBuildMode>, String> {
106 s.split('+')
107 .map(|part| OverlapClusterBuildMode::try_from(part.trim()))
108 .collect()
109}
110
111fn validate_sim_config(cfg: &SimConfig) -> Result<(), ValidationError> {
112 if cfg.n_sweeps < 1 {
113 return Err(ValidationError::new("n_sweeps must be >= 1"));
114 }
115 if cfg.warmup_sweeps > cfg.n_sweeps {
116 return Err(ValidationError::new("warmup_sweeps must be <= n_sweeps"));
117 }
118 if let Some(ref c) = cfg.cluster_update {
119 if c.interval < 1 {
120 return Err(ValidationError::new("cluster_update interval must be >= 1"));
121 }
122 }
123 if let Some(ref h) = cfg.overlap_cluster {
124 if h.interval < 1 {
125 return Err(ValidationError::new(
126 "overlap_cluster interval must be >= 1",
127 ));
128 }
129 }
130 Ok(())
131}
132
133#[derive(Debug, Validate)]
134#[validate(schema(function = "validate_sim_config"))]
135pub struct SimConfig {
136 pub n_sweeps: usize,
137 pub warmup_sweeps: usize,
138 pub sweep_mode: SweepMode,
139 pub cluster_update: Option<ClusterConfig>,
140 pub pt_interval: Option<usize>,
141 pub overlap_cluster: Option<OverlapClusterConfig>,
142 pub autocorrelation_max_lag: Option<usize>,
143 pub sequential: bool,
144 pub equilibration_diagnostic: bool,
145}