1use serde::{Deserialize, Serialize};
9
10use crate::CalcError;
11
12const MU_0: f64 = 4.0 * std::f64::consts::PI * 1e-7;
14
15const MILS_TO_METERS: f64 = 25.4e-6;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum SpiralShape {
21 Square,
22 Hexagonal,
23 Octagonal,
24 Circle,
25}
26
27impl SpiralShape {
28 fn coefficients(self) -> (f64, f64) {
30 match self {
31 Self::Square => (2.34, 2.75),
32 Self::Hexagonal => (2.33, 3.82),
33 Self::Octagonal => (2.25, 3.55),
34 Self::Circle => (2.23, 3.45),
35 }
36 }
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub struct InductorResult {
42 pub din_mils: f64,
44 pub rho: f64,
46 pub d_avg_mils: f64,
48 pub inductance_nh: f64,
50}
51
52pub fn planar_spiral(
67 n_turns: u32,
68 width_mils: f64,
69 spacing_mils: f64,
70 dout_mils: f64,
71 shape: SpiralShape,
72) -> Result<InductorResult, CalcError> {
73 if n_turns == 0 {
74 return Err(CalcError::OutOfRange {
75 name: "n_turns",
76 value: n_turns as f64,
77 expected: ">= 1",
78 });
79 }
80 if width_mils <= 0.0 {
81 return Err(CalcError::NegativeDimension {
82 name: "width_mils",
83 value: width_mils,
84 });
85 }
86 if spacing_mils <= 0.0 {
87 return Err(CalcError::NegativeDimension {
88 name: "spacing_mils",
89 value: spacing_mils,
90 });
91 }
92 if dout_mils <= 0.0 {
93 return Err(CalcError::NegativeDimension {
94 name: "dout_mils",
95 value: dout_mils,
96 });
97 }
98
99 let n = n_turns as f64;
100 let din_mils = dout_mils - 2.0 * n * (width_mils + spacing_mils) + 2.0 * spacing_mils;
101
102 if din_mils <= 0.0 {
103 return Err(CalcError::OutOfRange {
104 name: "din_mils (derived)",
105 value: din_mils,
106 expected: "> 0 — reduce n_turns, width, or spacing, or increase dout",
107 });
108 }
109
110 let rho = (dout_mils - din_mils) / (dout_mils + din_mils);
111 let d_avg_mils = (dout_mils + din_mils) / 2.0;
112 let d_avg_m = d_avg_mils * MILS_TO_METERS;
113
114 let (k1, k2) = shape.coefficients();
115 let inductance_h = k1 * MU_0 * n * n * d_avg_m / (1.0 + k2 * rho);
116 let inductance_nh = inductance_h * 1e9;
117
118 Ok(InductorResult {
119 din_mils,
120 rho,
121 d_avg_mils,
122 inductance_nh,
123 })
124}
125
126#[cfg(test)]
127mod tests {
128 use approx::assert_relative_eq;
129
130 use super::*;
131
132 #[test]
135 fn saturn_page30_square() {
136 let result = planar_spiral(5, 10.0, 10.0, 350.0, SpiralShape::Square).unwrap();
137 assert_relative_eq!(result.din_mils, 170.0, epsilon = 1e-10);
138 assert_relative_eq!(result.rho, 0.34615, epsilon = 1e-4);
139 assert_relative_eq!(result.inductance_nh, 248.59, epsilon = 0.2);
140 }
141
142 #[test]
143 fn derived_din_matches_spec() {
144 let result = planar_spiral(5, 10.0, 10.0, 350.0, SpiralShape::Square).unwrap();
146 assert_relative_eq!(result.din_mils, 170.0, epsilon = 1e-10);
147 }
148
149 #[test]
150 fn error_on_zero_turns() {
151 assert!(planar_spiral(0, 10.0, 10.0, 350.0, SpiralShape::Square).is_err());
152 }
153
154 #[test]
155 fn error_on_din_negative() {
156 assert!(planar_spiral(50, 10.0, 10.0, 350.0, SpiralShape::Square).is_err());
158 }
159
160 #[test]
161 fn hexagonal_shape_accepted() {
162 assert!(planar_spiral(3, 10.0, 10.0, 200.0, SpiralShape::Hexagonal).is_ok());
163 }
164}