Skip to main content

pcb_toolkit/
units.rs

1//! Unit conversion at the API boundary.
2//!
3//! All internal computation uses canonical units (mils for length, Hz for frequency, etc.).
4//! These types and functions convert user-facing values to/from internal representation.
5
6use serde::{Deserialize, Serialize};
7
8/// Length units accepted at the API boundary.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum LengthUnit {
11    Mils,
12    Mm,
13    Inches,
14    #[serde(rename = "um")]
15    Um,
16}
17
18/// Frequency units accepted at the API boundary.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum FreqUnit {
21    Hz,
22    #[serde(rename = "kHz")]
23    KHz,
24    MHz,
25    GHz,
26}
27
28/// Capacitance units for display.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum CapUnit {
31    F,
32    #[serde(rename = "uF")]
33    UF,
34    #[serde(rename = "nF")]
35    NF,
36    #[serde(rename = "pF")]
37    PF,
38}
39
40/// Inductance units for display.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum IndUnit {
43    H,
44    #[serde(rename = "mH")]
45    MH,
46    #[serde(rename = "uH")]
47    UH,
48    #[serde(rename = "nH")]
49    NH,
50}
51
52/// Resistance units for display.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
54pub enum ResUnit {
55    #[serde(rename = "mOhm")]
56    MOhm,
57    Ohm,
58    #[serde(rename = "kOhm")]
59    KOhm,
60    #[serde(rename = "MOhm")]
61    MOhmMega,
62}
63
64/// Temperature units.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66pub enum TempUnit {
67    Celsius,
68    Fahrenheit,
69}
70
71// ── Length conversions ───────────────────────────────────────────────
72
73/// Convert from user units to mils (internal canonical unit).
74pub fn to_mils(value: f64, unit: LengthUnit) -> f64 {
75    match unit {
76        LengthUnit::Mils => value,
77        LengthUnit::Mm => value / 0.0254,
78        LengthUnit::Inches => value * 1000.0,
79        LengthUnit::Um => value / 25.4,
80    }
81}
82
83/// Convert from mils to user units.
84pub fn from_mils(value: f64, unit: LengthUnit) -> f64 {
85    match unit {
86        LengthUnit::Mils => value,
87        LengthUnit::Mm => value * 0.0254,
88        LengthUnit::Inches => value / 1000.0,
89        LengthUnit::Um => value * 25.4,
90    }
91}
92
93// ── Frequency conversions ───────────────────────────────────────────
94
95/// Convert from user units to Hz (internal canonical unit).
96pub fn to_hz(value: f64, unit: FreqUnit) -> f64 {
97    match unit {
98        FreqUnit::Hz => value,
99        FreqUnit::KHz => value * 1e3,
100        FreqUnit::MHz => value * 1e6,
101        FreqUnit::GHz => value * 1e9,
102    }
103}
104
105/// Convert from Hz to user units.
106pub fn from_hz(value: f64, unit: FreqUnit) -> f64 {
107    match unit {
108        FreqUnit::Hz => value,
109        FreqUnit::KHz => value / 1e3,
110        FreqUnit::MHz => value / 1e6,
111        FreqUnit::GHz => value / 1e9,
112    }
113}
114
115// ── Capacitance conversions ─────────────────────────────────────────
116
117/// Convert from user units to Farads (internal canonical unit).
118pub fn to_farads(value: f64, unit: CapUnit) -> f64 {
119    match unit {
120        CapUnit::F => value,
121        CapUnit::UF => value * 1e-6,
122        CapUnit::NF => value * 1e-9,
123        CapUnit::PF => value * 1e-12,
124    }
125}
126
127/// Convert from Farads to user units.
128pub fn from_farads(value: f64, unit: CapUnit) -> f64 {
129    match unit {
130        CapUnit::F => value,
131        CapUnit::UF => value / 1e-6,
132        CapUnit::NF => value / 1e-9,
133        CapUnit::PF => value / 1e-12,
134    }
135}
136
137// ── Inductance conversions ──────────────────────────────────────────
138
139/// Convert from user units to Henries (internal canonical unit).
140pub fn to_henries(value: f64, unit: IndUnit) -> f64 {
141    match unit {
142        IndUnit::H => value,
143        IndUnit::MH => value * 1e-3,
144        IndUnit::UH => value * 1e-6,
145        IndUnit::NH => value * 1e-9,
146    }
147}
148
149/// Convert from Henries to user units.
150pub fn from_henries(value: f64, unit: IndUnit) -> f64 {
151    match unit {
152        IndUnit::H => value,
153        IndUnit::MH => value / 1e-3,
154        IndUnit::UH => value / 1e-6,
155        IndUnit::NH => value / 1e-9,
156    }
157}
158
159// ── Temperature conversions ─────────────────────────────────────────
160
161/// Convert to Celsius (internal canonical unit).
162pub fn to_celsius(value: f64, unit: TempUnit) -> f64 {
163    match unit {
164        TempUnit::Celsius => value,
165        TempUnit::Fahrenheit => (value - 32.0) * 5.0 / 9.0,
166    }
167}
168
169/// Convert from Celsius to user units.
170pub fn from_celsius(value: f64, unit: TempUnit) -> f64 {
171    match unit {
172        TempUnit::Celsius => value,
173        TempUnit::Fahrenheit => value * 9.0 / 5.0 + 32.0,
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn length_roundtrip() {
183        let mils = 100.0;
184        for unit in [LengthUnit::Mils, LengthUnit::Mm, LengthUnit::Inches, LengthUnit::Um] {
185            let converted = from_mils(mils, unit);
186            let back = to_mils(converted, unit);
187            assert!((back - mils).abs() < 1e-10, "roundtrip failed for {unit:?}");
188        }
189    }
190
191    #[test]
192    fn known_conversions() {
193        // 1 mil = 0.0254 mm
194        assert!((to_mils(0.0254, LengthUnit::Mm) - 1.0).abs() < 1e-10);
195        // 1 inch = 1000 mils
196        assert!((to_mils(1.0, LengthUnit::Inches) - 1000.0).abs() < 1e-10);
197        // 25.4 µm = 1 mil
198        assert!((to_mils(25.4, LengthUnit::Um) - 1.0).abs() < 1e-10);
199        // 1 GHz = 1e9 Hz
200        assert!((to_hz(1.0, FreqUnit::GHz) - 1e9).abs() < 1.0);
201        // 32°F = 0°C
202        assert!((to_celsius(32.0, TempUnit::Fahrenheit)).abs() < 1e-10);
203    }
204}