converge_optimization/gate/
determinism.rs1use serde::{Deserialize, Serialize};
4use std::hash::{Hash, Hasher};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct DeterminismSpec {
9 pub seed: u64,
11 pub tie_break: TieBreakStrategy,
13 pub stable_sort: bool,
15 pub rules_version: String,
17}
18
19impl Default for DeterminismSpec {
20 fn default() -> Self {
21 Self {
22 seed: 0,
23 tie_break: TieBreakStrategy::LexicographicFirst,
24 stable_sort: true,
25 rules_version: "v1".to_string(),
26 }
27 }
28}
29
30impl DeterminismSpec {
31 pub fn with_seed(seed: u64) -> Self {
33 Self {
34 seed,
35 ..Default::default()
36 }
37 }
38
39 pub fn new(seed: u64, tie_break: TieBreakStrategy) -> Self {
41 Self {
42 seed,
43 tie_break,
44 stable_sort: true,
45 rules_version: "v1".to_string(),
46 }
47 }
48
49 pub fn sub_seed(&self, phase: &str) -> u64 {
54 let mut hasher = std::collections::hash_map::DefaultHasher::new();
55 self.seed.hash(&mut hasher);
56 phase.hash(&mut hasher);
57 hasher.finish()
58 }
59
60 pub fn iter_seed(&self, iteration: usize) -> u64 {
62 let mut hasher = std::collections::hash_map::DefaultHasher::new();
63 self.seed.hash(&mut hasher);
64 iteration.hash(&mut hasher);
65 hasher.finish()
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "snake_case")]
72pub enum TieBreakStrategy {
73 LexicographicFirst,
75 LexicographicLast,
77 SmallestIndex,
79 LargestIndex,
81 SeededRandom,
83}
84
85impl TieBreakStrategy {
86 pub fn select<T: Ord + Clone>(&self, candidates: &[T], seed: u64) -> Option<T> {
88 if candidates.is_empty() {
89 return None;
90 }
91 match self {
92 Self::LexicographicFirst | Self::SmallestIndex => candidates.iter().min().cloned(),
93 Self::LexicographicLast | Self::LargestIndex => candidates.iter().max().cloned(),
94 Self::SeededRandom => {
95 let idx = (seed as usize) % candidates.len();
97 Some(candidates[idx].clone())
98 }
99 }
100 }
101
102 pub fn select_by<'a, T, F>(&self, candidates: &'a [T], seed: u64, compare: F) -> Option<&'a T>
104 where
105 F: Fn(&T, &T) -> std::cmp::Ordering,
106 {
107 if candidates.is_empty() {
108 return None;
109 }
110 match self {
111 Self::LexicographicFirst | Self::SmallestIndex => {
112 candidates.iter().min_by(|a, b| compare(a, b))
113 }
114 Self::LexicographicLast | Self::LargestIndex => {
115 candidates.iter().max_by(|a, b| compare(a, b))
116 }
117 Self::SeededRandom => {
118 let idx = (seed as usize) % candidates.len();
119 Some(&candidates[idx])
120 }
121 }
122 }
123
124 pub fn sort<T: Ord>(&self, candidates: &mut [T]) {
126 match self {
127 Self::LexicographicFirst | Self::SmallestIndex => candidates.sort(),
128 Self::LexicographicLast | Self::LargestIndex => {
129 candidates.sort();
130 candidates.reverse();
131 }
132 Self::SeededRandom => {
133 }
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_default_determinism() {
145 let spec = DeterminismSpec::default();
146 assert_eq!(spec.seed, 0);
147 assert_eq!(spec.tie_break, TieBreakStrategy::LexicographicFirst);
148 assert!(spec.stable_sort);
149 }
150
151 #[test]
152 fn test_sub_seed_consistency() {
153 let spec = DeterminismSpec::with_seed(12345);
154
155 let seed1 = spec.sub_seed("phase1");
157 let seed2 = spec.sub_seed("phase1");
158 assert_eq!(seed1, seed2);
159
160 let seed3 = spec.sub_seed("phase2");
162 assert_ne!(seed1, seed3);
163 }
164
165 #[test]
166 fn test_tie_break_lexicographic() {
167 let candidates = vec!["charlie", "alice", "bob"];
168
169 let first = TieBreakStrategy::LexicographicFirst.select(&candidates, 0);
170 assert_eq!(first, Some("alice"));
171
172 let last = TieBreakStrategy::LexicographicLast.select(&candidates, 0);
173 assert_eq!(last, Some("charlie"));
174 }
175
176 #[test]
177 fn test_tie_break_seeded() {
178 let candidates = vec![1, 2, 3, 4, 5];
179
180 let sel1 = TieBreakStrategy::SeededRandom.select(&candidates, 42);
182 let sel2 = TieBreakStrategy::SeededRandom.select(&candidates, 42);
183 assert_eq!(sel1, sel2);
184
185 let sel3 = TieBreakStrategy::SeededRandom.select(&candidates, 43);
187 let _ = sel3; }
190
191 #[test]
192 fn test_tie_break_empty() {
193 let empty: Vec<i32> = vec![];
194 assert_eq!(TieBreakStrategy::LexicographicFirst.select(&empty, 0), None);
195 }
196}