use serde::{Deserialize, Serialize};
use crate::CalcError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeviceType {
B1,
B2,
B3,
B4,
B5,
A6,
A7,
A8,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SpacingInput {
pub voltage: f64,
pub device_type: DeviceType,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SpacingResult {
pub spacing_mils: f64,
pub spacing_mm: f64,
}
#[rustfmt::skip]
const TABLE: [[f64; 9]; 8] = [
[ 1.97, 1.97, 3.94, 3.94, 7.87, 7.87, 7.87, 7.87, 9.84 ], [ 3.94, 3.94, 25.17, 25.17, 25.17, 49.21, 49.21, 49.21, 98.43 ], [ 3.94, 3.94, 25.17, 59.06, 125.98, 125.98, 251.97, 492.13, 492.13 ], [ 2.95, 2.95, 11.81, 11.81, 31.50, 31.50, 31.50, 31.50, 62.99 ], [ 2.95, 2.95, 5.12, 5.12, 15.75, 15.75, 15.75, 15.75, 31.50 ], [ 5.12, 5.12, 5.12, 5.12, 15.75, 15.75, 15.75, 15.75, 31.50 ], [ 5.12, 9.84, 15.75, 19.69, 31.50, 31.50, 31.50, 31.50, 59.06 ], [ 5.12, 9.84, 31.50, 39.37, 62.99, 62.99, 62.99, 62.99, 118.11 ], ];
#[rustfmt::skip]
const EXTRAP: [(f64, f64); 8] = [
(0.098425, 9.8425), (0.196850, 98.4252), (0.984252, 492.1260), (0.120070, 62.9900), (0.120070, 31.4961), (0.120070, 31.4961), (0.120070, 59.0551), (0.240157, 118.1100), ];
fn device_index(d: DeviceType) -> usize {
match d {
DeviceType::B1 => 0,
DeviceType::B2 => 1,
DeviceType::B3 => 2,
DeviceType::B4 => 3,
DeviceType::B5 => 4,
DeviceType::A6 => 5,
DeviceType::A7 => 6,
DeviceType::A8 => 7,
}
}
fn voltage_column(voltage: f64) -> usize {
if voltage <= 15.0 {
0
} else if voltage <= 30.0 {
1
} else if voltage <= 50.0 {
2
} else if voltage <= 100.0 {
3
} else if voltage <= 150.0 {
4
} else if voltage <= 170.0 {
5
} else if voltage <= 250.0 {
6
} else if voltage <= 300.0 {
7
} else {
8
}
}
pub fn spacing(input: &SpacingInput) -> Result<SpacingResult, CalcError> {
if input.voltage < 0.0 {
return Err(CalcError::OutOfRange {
name: "voltage",
value: input.voltage,
expected: ">= 0",
});
}
let row = device_index(input.device_type);
let spacing_mils = if input.voltage > 500.0 {
let (slope, intercept) = EXTRAP[row];
(input.voltage - 500.0) * slope + intercept
} else {
let col = voltage_column(input.voltage);
TABLE[row][col]
};
Ok(SpacingResult {
spacing_mils,
spacing_mm: spacing_mils * 0.0254,
})
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
fn lookup(voltage: f64, device_type: DeviceType) -> SpacingResult {
spacing(&SpacingInput { voltage, device_type }).unwrap()
}
#[test]
fn b1_10v() {
let r = lookup(10.0, DeviceType::B1);
assert_relative_eq!(r.spacing_mils, 1.97, epsilon = 1e-6);
}
#[test]
fn b3_40v() {
let r = lookup(40.0, DeviceType::B3);
assert_relative_eq!(r.spacing_mils, 25.17, epsilon = 1e-6);
}
#[test]
fn b1_600v_extrapolation() {
let r = lookup(600.0, DeviceType::B1);
let expected = (600.0 - 500.0) * 0.098425 + 9.8425;
assert_relative_eq!(r.spacing_mils, expected, epsilon = 1e-6);
assert_relative_eq!(r.spacing_mils, 19.685, epsilon = 1e-3);
}
#[test]
fn boundary_0v() {
let r = lookup(0.0, DeviceType::B1);
assert_relative_eq!(r.spacing_mils, TABLE[0][0], epsilon = 1e-9);
}
#[test]
fn boundary_15v() {
let r = lookup(15.0, DeviceType::B1);
assert_relative_eq!(r.spacing_mils, TABLE[0][0], epsilon = 1e-9);
}
#[test]
fn boundary_16v() {
let r = lookup(16.0, DeviceType::B1);
assert_relative_eq!(r.spacing_mils, TABLE[0][1], epsilon = 1e-9);
}
#[test]
fn boundary_30v() {
let r = lookup(30.0, DeviceType::B1);
assert_relative_eq!(r.spacing_mils, TABLE[0][1], epsilon = 1e-9);
}
#[test]
fn boundary_500v() {
let r = lookup(500.0, DeviceType::B1);
assert_relative_eq!(r.spacing_mils, TABLE[0][8], epsilon = 1e-9);
}
#[test]
fn boundary_501v_extrapolation() {
let r = lookup(501.0, DeviceType::B1);
let expected = 1.0 * 0.098425 + 9.8425;
assert_relative_eq!(r.spacing_mils, expected, epsilon = 1e-6);
}
#[test]
fn mm_conversion() {
let r = lookup(10.0, DeviceType::B2);
assert_relative_eq!(r.spacing_mm, r.spacing_mils * 0.0254, epsilon = 1e-9);
}
#[test]
fn rejects_negative_voltage() {
let result = spacing(&SpacingInput {
voltage: -1.0,
device_type: DeviceType::B1,
});
assert!(matches!(result, Err(CalcError::OutOfRange { name: "voltage", .. })));
}
}