use serde::{Deserialize, Serialize};
use crate::CalcError;
const MU_0: f64 = 4.0 * std::f64::consts::PI * 1e-7;
const MILS_TO_METERS: f64 = 25.4e-6;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SpiralShape {
Square,
Hexagonal,
Octagonal,
Circle,
}
impl SpiralShape {
fn coefficients(self) -> (f64, f64) {
match self {
Self::Square => (2.34, 2.75),
Self::Hexagonal => (2.33, 3.82),
Self::Octagonal => (2.25, 3.55),
Self::Circle => (2.23, 3.45),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InductorResult {
pub din_mils: f64,
pub rho: f64,
pub d_avg_mils: f64,
pub inductance_nh: f64,
}
pub fn planar_spiral(
n_turns: u32,
width_mils: f64,
spacing_mils: f64,
dout_mils: f64,
shape: SpiralShape,
) -> Result<InductorResult, CalcError> {
if n_turns == 0 {
return Err(CalcError::OutOfRange {
name: "n_turns",
value: n_turns as f64,
expected: ">= 1",
});
}
if width_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "width_mils",
value: width_mils,
});
}
if spacing_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "spacing_mils",
value: spacing_mils,
});
}
if dout_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "dout_mils",
value: dout_mils,
});
}
let n = n_turns as f64;
let din_mils = dout_mils - 2.0 * n * (width_mils + spacing_mils) + 2.0 * spacing_mils;
if din_mils <= 0.0 {
return Err(CalcError::OutOfRange {
name: "din_mils (derived)",
value: din_mils,
expected: "> 0 — reduce n_turns, width, or spacing, or increase dout",
});
}
let rho = (dout_mils - din_mils) / (dout_mils + din_mils);
let d_avg_mils = (dout_mils + din_mils) / 2.0;
let d_avg_m = d_avg_mils * MILS_TO_METERS;
let (k1, k2) = shape.coefficients();
let inductance_h = k1 * MU_0 * n * n * d_avg_m / (1.0 + k2 * rho);
let inductance_nh = inductance_h * 1e9;
Ok(InductorResult {
din_mils,
rho,
d_avg_mils,
inductance_nh,
})
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn saturn_page30_square() {
let result = planar_spiral(5, 10.0, 10.0, 350.0, SpiralShape::Square).unwrap();
assert_relative_eq!(result.din_mils, 170.0, epsilon = 1e-10);
assert_relative_eq!(result.rho, 0.34615, epsilon = 1e-4);
assert_relative_eq!(result.inductance_nh, 248.59, epsilon = 0.2);
}
#[test]
fn derived_din_matches_spec() {
let result = planar_spiral(5, 10.0, 10.0, 350.0, SpiralShape::Square).unwrap();
assert_relative_eq!(result.din_mils, 170.0, epsilon = 1e-10);
}
#[test]
fn error_on_zero_turns() {
assert!(planar_spiral(0, 10.0, 10.0, 350.0, SpiralShape::Square).is_err());
}
#[test]
fn error_on_din_negative() {
assert!(planar_spiral(50, 10.0, 10.0, 350.0, SpiralShape::Square).is_err());
}
#[test]
fn hexagonal_shape_accepted() {
assert!(planar_spiral(3, 10.0, 10.0, 200.0, SpiralShape::Hexagonal).is_ok());
}
}