pcb_toolkit/differential/
edge_coupled_internal_sym.rs1use crate::CalcError;
7use super::types::{DifferentialResult, kb_terminated};
8
9pub struct EdgeCoupledInternalSymInput {
11 pub width: f64,
13 pub spacing: f64,
15 pub height: f64,
18 pub thickness: f64,
20 pub er: f64,
22}
23
24pub fn calculate(input: &EdgeCoupledInternalSymInput) -> Result<DifferentialResult, CalcError> {
26 let EdgeCoupledInternalSymInput { width, spacing, height, thickness, er } = *input;
27
28 if width <= 0.0 {
29 return Err(CalcError::NegativeDimension { name: "width", value: width });
30 }
31 if spacing <= 0.0 {
32 return Err(CalcError::NegativeDimension { name: "spacing", value: spacing });
33 }
34 if height <= 0.0 {
35 return Err(CalcError::NegativeDimension { name: "height", value: height });
36 }
37 if thickness <= 0.0 {
38 return Err(CalcError::NegativeDimension { name: "thickness", value: thickness });
39 }
40 if er < 1.0 {
41 return Err(CalcError::OutOfRange {
42 name: "er",
43 value: er,
44 expected: ">= 1.0",
45 });
46 }
47
48 let z0 = (60.0 / er.sqrt()) * (1.9 * (2.0 * height + thickness) / (0.8 * width + thickness)).ln();
49
50 let zodd = z0 * (1.0 - 0.48 * (-0.96 * spacing / height).exp());
51 let zeven = z0 * z0 / zodd;
52 let zdiff = 2.0 * zodd;
53 let kb = (zeven - zodd) / (zeven + zodd);
54 let kb_db = 20.0 * kb.log10();
55 let kb_term = kb_terminated(kb);
56 let kb_term_db = 20.0 * kb_term.log10();
57
58 Ok(DifferentialResult {
59 zdiff,
60 zo: z0,
61 zodd,
62 zeven,
63 kb,
64 kb_db,
65 kb_term,
66 kb_term_db,
67 })
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use approx::assert_relative_eq;
74
75 fn input(width: f64, spacing: f64, height: f64, thickness: f64, er: f64) -> EdgeCoupledInternalSymInput {
76 EdgeCoupledInternalSymInput { width, spacing, height, thickness, er }
77 }
78
79 #[test]
82 fn web_reference_weak_coupling() {
83 let result = calculate(&input(10.0, 63.0, 63.0, 1.2, 4.0)).unwrap();
84
85 assert!(
86 result.zdiff >= 140.0 && result.zdiff <= 170.0,
87 "Zdiff {:.3} should be in range 140–170 Ω",
88 result.zdiff
89 );
90 }
91
92 #[test]
94 fn wider_spacing_reduces_coupling() {
95 let close = calculate(&input(10.0, 5.0, 63.0, 1.2, 4.0)).unwrap();
96 let far = calculate(&input(10.0, 20.0, 63.0, 1.2, 4.0)).unwrap();
97
98 assert!(
99 far.kb.abs() < close.kb.abs(),
100 "wider spacing Kb {:.4} should be smaller than {:.4}",
101 far.kb,
102 close.kb
103 );
104 }
105
106 #[test]
108 fn higher_er_gives_lower_z0() {
109 let low_er = calculate(&input(10.0, 10.0, 63.0, 1.2, 2.2)).unwrap();
110 let high_er = calculate(&input(10.0, 10.0, 63.0, 1.2, 4.6)).unwrap();
111
112 assert!(
113 high_er.zo < low_er.zo,
114 "higher Er Z0 {:.3} should be less than {:.3}",
115 high_er.zo,
116 low_er.zo
117 );
118 }
119
120 #[test]
122 fn er_eff_equals_er() {
123 let er = 4.6_f64;
124 let result = calculate(&input(10.0, 10.0, 63.0, 1.2, er)).unwrap();
125
126 let ln_term = (1.9_f64 * (2.0 * 63.0 + 1.2) / (0.8 * 10.0 + 1.2)).ln();
128 let er_back = (60.0 * ln_term / result.zo).powi(2);
129 assert_relative_eq!(er_back, er, max_relative = 1e-10);
130 }
131
132 #[test]
133 fn rejects_negative_width() {
134 let result = calculate(&input(-1.0, 10.0, 63.0, 1.2, 4.0));
135 assert!(result.is_err());
136 }
137
138 #[test]
139 fn rejects_er_below_one() {
140 let result = calculate(&input(10.0, 10.0, 63.0, 1.2, 0.5));
141 assert!(result.is_err());
142 }
143}