1#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum ToleranceGrade {
18 Precision,
20 Standard,
22 Loose,
24 Wide,
26 Ideal,
28}
29
30impl ToleranceGrade {
31 pub fn fraction(self) -> f64 {
33 match self {
34 ToleranceGrade::Precision => 0.01,
35 ToleranceGrade::Standard => 0.05,
36 ToleranceGrade::Loose => 0.10,
37 ToleranceGrade::Wide => 0.20,
38 ToleranceGrade::Ideal => 0.0,
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
56pub struct ToleranceEngine {
57 seed: u64,
59 resistor_grade: ToleranceGrade,
61 capacitor_grade: ToleranceGrade,
63}
64
65impl ToleranceEngine {
66 pub fn new(seed: u64, grade: ToleranceGrade) -> Self {
68 Self {
69 seed,
70 resistor_grade: grade,
71 capacitor_grade: grade,
72 }
73 }
74
75 pub fn with_grades(
80 seed: u64,
81 resistor_grade: ToleranceGrade,
82 capacitor_grade: ToleranceGrade,
83 ) -> Self {
84 Self {
85 seed,
86 resistor_grade,
87 capacitor_grade,
88 }
89 }
90
91 pub fn ideal() -> Self {
93 Self::new(0, ToleranceGrade::Ideal)
94 }
95
96 pub fn apply_resistor(&self, nominal: f64, component_index: usize) -> f64 {
101 self.apply_internal(nominal, component_index, self.resistor_grade)
102 }
103
104 pub fn apply_capacitor(&self, nominal: f64, component_index: usize) -> f64 {
106 self.apply_internal(nominal, component_index, self.capacitor_grade)
107 }
108
109 fn apply_internal(&self, nominal: f64, component_index: usize, grade: ToleranceGrade) -> f64 {
111 let fraction = grade.fraction();
112 if fraction == 0.0 {
113 return nominal;
114 }
115
116 let random = self.deterministic_random(component_index);
119
120 nominal * (1.0 + fraction * random)
123 }
124
125 fn deterministic_random(&self, component_index: usize) -> f64 {
130 let mut z = self
132 .seed
133 .wrapping_add(component_index as u64)
134 .wrapping_mul(0x9E3779B97F4A7C15);
135 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
136 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
137 z ^= z >> 31;
138
139 (z as f64 / u64::MAX as f64) * 2.0 - 1.0
141 }
142
143 pub fn seed(&self) -> u64 {
145 self.seed
146 }
147}
148
149#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn ideal_tolerance_unchanged() {
159 let tol = ToleranceEngine::ideal();
160 let nominal = 4700.0;
161 let actual = tol.apply_resistor(nominal, 0);
162 assert!(
163 (actual - nominal).abs() < 1e-10,
164 "Ideal should not change value: {actual}"
165 );
166 }
167
168 #[test]
169 fn tolerance_deterministic() {
170 let tol1 = ToleranceEngine::new(42, ToleranceGrade::Loose);
171 let tol2 = ToleranceEngine::new(42, ToleranceGrade::Loose);
172
173 for i in 0..20 {
174 let v1 = tol1.apply_resistor(1000.0, i);
175 let v2 = tol2.apply_resistor(1000.0, i);
176 assert!(
177 (v1 - v2).abs() < 1e-10,
178 "Same seed should give same result: i={i}, v1={v1}, v2={v2}"
179 );
180 }
181 }
182
183 #[test]
184 fn tolerance_varies_with_seed() {
185 let tol1 = ToleranceEngine::new(1, ToleranceGrade::Loose);
186 let tol2 = ToleranceEngine::new(2, ToleranceGrade::Loose);
187
188 let v1 = tol1.apply_resistor(1000.0, 0);
189 let v2 = tol2.apply_resistor(1000.0, 0);
190
191 assert!(
192 (v1 - v2).abs() > 0.01,
193 "Different seeds should give different values: v1={v1}, v2={v2}"
194 );
195 }
196
197 #[test]
198 fn tolerance_within_bounds() {
199 let tol = ToleranceEngine::new(123, ToleranceGrade::Loose);
200 let nominal = 10_000.0;
201 let fraction = ToleranceGrade::Loose.fraction(); for i in 0..1000 {
204 let actual = tol.apply_resistor(nominal, i);
205 let lo = nominal * (1.0 - fraction);
206 let hi = nominal * (1.0 + fraction);
207 assert!(
208 actual >= lo && actual <= hi,
209 "Component {i} out of bounds: {actual}, expected [{lo}, {hi}]"
210 );
211 }
212 }
213
214 #[test]
215 fn tolerance_varies_across_components() {
216 let tol = ToleranceEngine::new(42, ToleranceGrade::Standard);
217 let nominal = 1000.0;
218
219 let values: Vec<f64> = (0..100).map(|i| tol.apply_resistor(nominal, i)).collect();
220
221 let unique: std::collections::HashSet<u64> = values.iter().map(|&v| v.to_bits()).collect();
223 assert!(
224 unique.len() > 50,
225 "Should have good variety across components: {} unique values",
226 unique.len()
227 );
228 }
229
230 #[test]
231 fn tolerance_different_grades() {
232 let precision = ToleranceEngine::new(42, ToleranceGrade::Precision);
233 let wide = ToleranceEngine::new(42, ToleranceGrade::Wide);
234 let nominal = 1000.0;
235
236 let precision_dev: f64 = (0..100)
238 .map(|i| (precision.apply_resistor(nominal, i) - nominal).abs())
239 .sum::<f64>()
240 / 100.0;
241 let wide_dev: f64 = (0..100)
242 .map(|i| (wide.apply_resistor(nominal, i) - nominal).abs())
243 .sum::<f64>()
244 / 100.0;
245
246 assert!(
247 wide_dev > precision_dev * 3.0,
248 "Wide should deviate much more than precision: wide={wide_dev:.2}, precision={precision_dev:.2}"
249 );
250 }
251
252 #[test]
253 fn separate_resistor_capacitor_grades() {
254 let tol = ToleranceEngine::with_grades(
255 42,
256 ToleranceGrade::Standard, ToleranceGrade::Wide, );
259 let nominal = 1000.0;
260
261 let r_dev: f64 = (0..100)
263 .map(|i| (tol.apply_resistor(nominal, i) - nominal).abs())
264 .sum::<f64>()
265 / 100.0;
266 let c_dev: f64 = (0..100)
267 .map(|i| (tol.apply_capacitor(nominal, i) - nominal).abs())
268 .sum::<f64>()
269 / 100.0;
270
271 assert!(
272 c_dev > r_dev * 2.0,
273 "Capacitor deviation should be larger: c={c_dev:.2}, r={r_dev:.2}"
274 );
275 }
276
277 #[test]
278 fn tolerance_distribution_centered() {
279 let tol = ToleranceEngine::new(777, ToleranceGrade::Loose);
281 let nominal = 1000.0;
282
283 let mean: f64 = (0..10000)
284 .map(|i| tol.apply_resistor(nominal, i))
285 .sum::<f64>()
286 / 10000.0;
287
288 assert!(
290 (mean - nominal).abs() < nominal * 0.02,
291 "Mean should be near nominal: mean={mean:.2}, nominal={nominal}"
292 );
293 }
294}