1use serde::{Deserialize, Serialize};
13
14use crate::CalcError;
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct EirResult {
23 pub voltage_v: f64,
25 pub current_a: f64,
27 pub resistance_ohm: f64,
29 pub power_w: f64,
31}
32
33pub fn eir(
41 voltage_v: Option<f64>,
42 current_a: Option<f64>,
43 resistance_ohm: Option<f64>,
44) -> Result<EirResult, CalcError> {
45 let provided = [voltage_v, current_a, resistance_ohm]
46 .iter()
47 .filter(|v| v.is_some())
48 .count();
49 if provided != 2 {
50 return Err(CalcError::InsufficientInputs(
51 "exactly 2 of voltage_v, current_a, resistance_ohm must be provided",
52 ));
53 }
54
55 let (v, i, r) = match (voltage_v, current_a, resistance_ohm) {
56 (Some(v), Some(i), None) => {
57 if i == 0.0 {
58 return Err(CalcError::OutOfRange {
59 name: "current_a",
60 value: i,
61 expected: "!= 0 when computing resistance",
62 });
63 }
64 (v, i, v / i)
65 }
66 (Some(v), None, Some(r)) => {
67 if r == 0.0 {
68 return Err(CalcError::OutOfRange {
69 name: "resistance_ohm",
70 value: r,
71 expected: "!= 0 when computing current",
72 });
73 }
74 (v, v / r, r)
75 }
76 (None, Some(i), Some(r)) => (i * r, i, r),
77 _ => unreachable!(),
78 };
79
80 Ok(EirResult {
81 voltage_v: v,
82 current_a: i,
83 resistance_ohm: r,
84 power_w: v * i,
85 })
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct LedBiasResult {
95 pub resistance_ohm: f64,
97 pub power_w: f64,
99}
100
101pub fn led_bias(supply_v: f64, led_v: f64, led_current_a: f64) -> Result<LedBiasResult, CalcError> {
113 if led_v <= 0.0 {
114 return Err(CalcError::OutOfRange {
115 name: "led_v",
116 value: led_v,
117 expected: "> 0",
118 });
119 }
120 if supply_v <= led_v {
121 return Err(CalcError::OutOfRange {
122 name: "supply_v",
123 value: supply_v,
124 expected: "> led_v",
125 });
126 }
127 if led_current_a <= 0.0 {
128 return Err(CalcError::OutOfRange {
129 name: "led_current_a",
130 value: led_current_a,
131 expected: "> 0",
132 });
133 }
134
135 let v_drop = supply_v - led_v;
136 let resistance_ohm = v_drop / led_current_a;
137 let power_w = v_drop * led_current_a;
138
139 Ok(LedBiasResult {
140 resistance_ohm,
141 power_w,
142 })
143}
144
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151pub struct ResistorCombinationResult {
152 pub resistance_ohm: f64,
154}
155
156pub fn resistors_series(values: &[f64]) -> Result<ResistorCombinationResult, CalcError> {
160 if values.is_empty() {
161 return Err(CalcError::InsufficientInputs("at least one resistor required"));
162 }
163 Ok(ResistorCombinationResult {
164 resistance_ohm: values.iter().sum(),
165 })
166}
167
168pub fn resistors_parallel(values: &[f64]) -> Result<ResistorCombinationResult, CalcError> {
172 if values.is_empty() {
173 return Err(CalcError::InsufficientInputs("at least one resistor required"));
174 }
175 for (idx, &r) in values.iter().enumerate() {
176 if r == 0.0 {
177 return Err(CalcError::OutOfRange {
178 name: "resistor value",
179 value: r,
180 expected: "!= 0 for parallel combination",
181 });
182 }
183 let _ = idx;
184 }
185 let reciprocal_sum: f64 = values.iter().map(|r| 1.0 / r).sum();
186 Ok(ResistorCombinationResult {
187 resistance_ohm: 1.0 / reciprocal_sum,
188 })
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197pub struct CapacitorCombinationResult {
198 pub capacitance_f: f64,
200}
201
202pub fn capacitors_parallel(values: &[f64]) -> Result<CapacitorCombinationResult, CalcError> {
204 if values.is_empty() {
205 return Err(CalcError::InsufficientInputs("at least one capacitor required"));
206 }
207 Ok(CapacitorCombinationResult {
208 capacitance_f: values.iter().sum(),
209 })
210}
211
212pub fn capacitors_series(values: &[f64]) -> Result<CapacitorCombinationResult, CalcError> {
214 if values.is_empty() {
215 return Err(CalcError::InsufficientInputs("at least one capacitor required"));
216 }
217 for &c in values.iter() {
218 if c == 0.0 {
219 return Err(CalcError::OutOfRange {
220 name: "capacitor value",
221 value: c,
222 expected: "!= 0 for series combination",
223 });
224 }
225 }
226 let reciprocal_sum: f64 = values.iter().map(|c| 1.0 / c).sum();
227 Ok(CapacitorCombinationResult {
228 capacitance_f: 1.0 / reciprocal_sum,
229 })
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
238pub struct InductorCombinationResult {
239 pub inductance_h: f64,
241}
242
243pub fn inductors_series(values: &[f64]) -> Result<InductorCombinationResult, CalcError> {
245 if values.is_empty() {
246 return Err(CalcError::InsufficientInputs("at least one inductor required"));
247 }
248 Ok(InductorCombinationResult {
249 inductance_h: values.iter().sum(),
250 })
251}
252
253pub fn inductors_parallel(values: &[f64]) -> Result<InductorCombinationResult, CalcError> {
255 if values.is_empty() {
256 return Err(CalcError::InsufficientInputs("at least one inductor required"));
257 }
258 for &l in values.iter() {
259 if l == 0.0 {
260 return Err(CalcError::OutOfRange {
261 name: "inductor value",
262 value: l,
263 expected: "!= 0 for parallel combination",
264 });
265 }
266 }
267 let reciprocal_sum: f64 = values.iter().map(|l| 1.0 / l).sum();
268 Ok(InductorCombinationResult {
269 inductance_h: 1.0 / reciprocal_sum,
270 })
271}
272
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279pub struct AttenuatorResult {
280 pub attenuation_db: f64,
282 pub k: f64,
284 pub r_series_ohm: f64,
286 pub r_shunt_ohm: f64,
288}
289
290pub fn pi_pad(attenuation_db: f64, z_ohm: f64) -> Result<AttenuatorResult, CalcError> {
308 if attenuation_db <= 0.0 {
309 return Err(CalcError::OutOfRange {
310 name: "attenuation_db",
311 value: attenuation_db,
312 expected: "> 0",
313 });
314 }
315 if z_ohm <= 0.0 {
316 return Err(CalcError::OutOfRange {
317 name: "z_ohm",
318 value: z_ohm,
319 expected: "> 0",
320 });
321 }
322
323 let k = 10.0_f64.powf(attenuation_db / 20.0);
324 let r_shunt_ohm = z_ohm * (k + 1.0) / (k - 1.0);
325 let r_series_ohm = z_ohm * (k - 1.0) / (k + 1.0);
326
327 Ok(AttenuatorResult {
328 attenuation_db,
329 k,
330 r_series_ohm,
331 r_shunt_ohm,
332 })
333}
334
335pub fn t_pad(attenuation_db: f64, z_ohm: f64) -> Result<AttenuatorResult, CalcError> {
353 if attenuation_db <= 0.0 {
354 return Err(CalcError::OutOfRange {
355 name: "attenuation_db",
356 value: attenuation_db,
357 expected: "> 0",
358 });
359 }
360 if z_ohm <= 0.0 {
361 return Err(CalcError::OutOfRange {
362 name: "z_ohm",
363 value: z_ohm,
364 expected: "> 0",
365 });
366 }
367
368 let k = 10.0_f64.powf(attenuation_db / 20.0);
369 let r_series_ohm = z_ohm * (k - 1.0) / (k + 1.0);
370 let r_shunt_ohm = z_ohm * 2.0 * k / (k * k - 1.0);
371
372 Ok(AttenuatorResult {
373 attenuation_db,
374 k,
375 r_series_ohm,
376 r_shunt_ohm,
377 })
378}
379
380#[cfg(test)]
381mod tests {
382 use approx::assert_relative_eq;
383
384 use super::*;
385
386 #[test]
388 fn saturn_eir_vi() {
389 let result = eir(Some(12.0), Some(1.0), None).unwrap();
390 assert_relative_eq!(result.resistance_ohm, 12.0, epsilon = 1e-10);
391 assert_relative_eq!(result.power_w, 12.0, epsilon = 1e-10);
392 }
393
394 #[test]
395 fn eir_solve_voltage() {
396 let result = eir(None, Some(1.0), Some(12.0)).unwrap();
397 assert_relative_eq!(result.voltage_v, 12.0, epsilon = 1e-10);
398 assert_relative_eq!(result.power_w, 12.0, epsilon = 1e-10);
399 }
400
401 #[test]
403 fn saturn_led_bias() {
404 let result = led_bias(12.0, 2.0, 0.01).unwrap();
405 assert_relative_eq!(result.resistance_ohm, 1000.0, epsilon = 1e-6);
406 assert_relative_eq!(result.power_w, 0.1, epsilon = 1e-8);
407 }
408
409 #[test]
411 fn pi_pad_1db_50ohm() {
412 let result = pi_pad(1.0, 50.0).unwrap();
413 assert_relative_eq!(result.r_series_ohm, 2.88, epsilon = 0.01);
414 assert_relative_eq!(result.r_shunt_ohm, 869.5, epsilon = 0.5);
415 }
416
417 #[test]
419 fn pi_pad_10db_50ohm() {
420 let result = pi_pad(10.0, 50.0).unwrap();
421 assert_relative_eq!(result.r_series_ohm, 25.97, epsilon = 0.02);
422 assert_relative_eq!(result.r_shunt_ohm, 96.25, epsilon = 0.05);
423 }
424
425 #[test]
427 fn t_pad_10db_50ohm() {
428 let result = t_pad(10.0, 50.0).unwrap();
429 assert_relative_eq!(result.r_series_ohm, 25.97, epsilon = 0.02);
430 assert_relative_eq!(result.r_shunt_ohm, 35.14, epsilon = 0.02);
431 }
432
433 #[test]
434 fn resistors_series_two() {
435 let result = resistors_series(&[100.0, 200.0]).unwrap();
436 assert_relative_eq!(result.resistance_ohm, 300.0, epsilon = 1e-10);
437 }
438
439 #[test]
440 fn resistors_parallel_two_equal() {
441 let result = resistors_parallel(&[100.0, 100.0]).unwrap();
442 assert_relative_eq!(result.resistance_ohm, 50.0, epsilon = 1e-10);
443 }
444
445 #[test]
446 fn capacitors_parallel_two() {
447 let result = capacitors_parallel(&[10e-12, 10e-12]).unwrap();
448 assert_relative_eq!(result.capacitance_f, 20e-12, epsilon = 1e-22);
449 }
450
451 #[test]
452 fn capacitors_series_two_equal() {
453 let result = capacitors_series(&[10e-12, 10e-12]).unwrap();
454 assert_relative_eq!(result.capacitance_f, 5e-12, epsilon = 1e-22);
455 }
456
457 #[test]
458 fn inductors_series_two() {
459 let result = inductors_series(&[1e-6, 2e-6]).unwrap();
460 assert_relative_eq!(result.inductance_h, 3e-6, epsilon = 1e-16);
461 }
462
463 #[test]
464 fn inductors_parallel_two_equal() {
465 let result = inductors_parallel(&[10e-6, 10e-6]).unwrap();
466 assert_relative_eq!(result.inductance_h, 5e-6, epsilon = 1e-16);
467 }
468
469 #[test]
470 fn error_on_zero_attenuation() {
471 assert!(pi_pad(0.0, 50.0).is_err());
472 assert!(t_pad(0.0, 50.0).is_err());
473 }
474
475 #[test]
476 fn error_on_insufficient_eir_inputs() {
477 assert!(eir(Some(12.0), None, None).is_err());
478 assert!(eir(Some(12.0), Some(1.0), Some(12.0)).is_err());
479 }
480}