pcb_toolkit/differential/
edge_coupled_internal_asym.rs1use crate::CalcError;
7use super::types::{DifferentialResult, kb_terminated};
8
9pub struct EdgeCoupledInternalAsymInput {
11 pub width: f64,
13 pub spacing: f64,
15 pub height1: f64,
17 pub height2: f64,
19 pub thickness: f64,
21 pub er: f64,
23}
24
25pub fn calculate(input: &EdgeCoupledInternalAsymInput) -> Result<DifferentialResult, CalcError> {
27 let EdgeCoupledInternalAsymInput { width, spacing, height1, height2, thickness, er } = *input;
28
29 if width <= 0.0 {
30 return Err(CalcError::NegativeDimension { name: "width", value: width });
31 }
32 if spacing <= 0.0 {
33 return Err(CalcError::NegativeDimension { name: "spacing", value: spacing });
34 }
35 if height1 <= 0.0 {
36 return Err(CalcError::NegativeDimension { name: "height1", value: height1 });
37 }
38 if height2 <= 0.0 {
39 return Err(CalcError::NegativeDimension { name: "height2", value: height2 });
40 }
41 if thickness <= 0.0 {
42 return Err(CalcError::NegativeDimension { name: "thickness", value: thickness });
43 }
44 if er < 1.0 {
45 return Err(CalcError::OutOfRange {
46 name: "er",
47 value: er,
48 expected: ">= 1.0",
49 });
50 }
51
52 let z0 = (60.0 / er.sqrt())
53 * (1.9 * (height1 + height2 + thickness) / (0.8 * width + thickness)).ln();
54
55 let h_ref = (height1 + height2) / 2.0;
56 let zodd = z0 * (1.0 - 0.48 * (-0.96 * spacing / h_ref).exp());
57 let zeven = z0 * z0 / zodd;
58 let zdiff = 2.0 * zodd;
59 let kb = (zeven - zodd) / (zeven + zodd);
60 let kb_db = 20.0 * kb.log10();
61 let kb_term = kb_terminated(kb);
62 let kb_term_db = 20.0 * kb_term.log10();
63
64 Ok(DifferentialResult {
65 zdiff,
66 zo: z0,
67 zodd,
68 zeven,
69 kb,
70 kb_db,
71 kb_term,
72 kb_term_db,
73 })
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use approx::assert_relative_eq;
80
81 fn input(
82 width: f64,
83 spacing: f64,
84 height1: f64,
85 height2: f64,
86 thickness: f64,
87 er: f64,
88 ) -> EdgeCoupledInternalAsymInput {
89 EdgeCoupledInternalAsymInput { width, spacing, height1, height2, thickness, er }
90 }
91
92 #[test]
95 fn symmetric_case_matches_formula() {
96 let w = 10.0;
97 let s = 5.0;
98 let h = 10.0;
99 let t = 1.4;
100 let er = 4.6_f64;
101
102 let result = calculate(&input(w, s, h, h, t, er)).unwrap();
103
104 let z0_expected = (60.0 / er.sqrt()) * (1.9 * (2.0 * h + t) / (0.8 * w + t)).ln();
105 assert_relative_eq!(result.zo, z0_expected, max_relative = 1e-10);
106
107 let h_ref = h;
108 let zodd_expected = z0_expected * (1.0 - 0.48 * (-0.96 * s / h_ref).exp());
109 assert_relative_eq!(result.zodd, zodd_expected, max_relative = 1e-10);
110 assert_relative_eq!(result.zdiff, 2.0 * zodd_expected, max_relative = 1e-10);
111 }
112
113 #[test]
115 fn asymmetric_heights_change_z0() {
116 let baseline = calculate(&input(10.0, 5.0, 10.0, 10.0, 1.4, 4.6)).unwrap();
117 let taller = calculate(&input(10.0, 5.0, 15.0, 10.0, 1.4, 4.6)).unwrap();
118
119 assert!(
120 taller.zo > baseline.zo,
121 "taller dielectric span Z0 {:.4} should exceed baseline Z0 {:.4}",
122 taller.zo,
123 baseline.zo
124 );
125 }
126
127 #[test]
129 fn wider_spacing_reduces_coupling() {
130 let close = calculate(&input(10.0, 5.0, 10.0, 10.0, 1.4, 4.6)).unwrap();
131 let far = calculate(&input(10.0, 20.0, 10.0, 10.0, 1.4, 4.6)).unwrap();
132
133 assert!(
134 far.kb.abs() < close.kb.abs(),
135 "wider spacing Kb {:.4} should be smaller than {:.4}",
136 far.kb,
137 close.kb
138 );
139 }
140
141 #[test]
143 fn higher_er_gives_lower_z0() {
144 let low_er = calculate(&input(10.0, 5.0, 10.0, 10.0, 1.4, 2.2)).unwrap();
145 let high_er = calculate(&input(10.0, 5.0, 10.0, 10.0, 1.4, 4.6)).unwrap();
146
147 assert!(
148 high_er.zo < low_er.zo,
149 "higher Er Z0 {:.3} should be less than lower Er Z0 {:.3}",
150 high_er.zo,
151 low_er.zo
152 );
153 }
154
155 #[test]
156 fn rejects_negative_height1() {
157 let result = calculate(&input(10.0, 5.0, -1.0, 10.0, 1.4, 4.6));
158 assert!(result.is_err());
159 }
160
161 #[test]
162 fn rejects_er_below_one() {
163 let result = calculate(&input(10.0, 5.0, 10.0, 10.0, 1.4, 0.5));
164 assert!(result.is_err());
165 }
166}