use crate::CalcError;
use super::types::{DifferentialResult, kb_terminated};
pub struct BroadsideCoupledInput {
pub width: f64,
pub separation: f64,
pub height_total: f64,
pub thickness: f64,
pub er: f64,
pub shielded: bool,
}
pub fn calculate(input: &BroadsideCoupledInput) -> Result<DifferentialResult, CalcError> {
let BroadsideCoupledInput { width, separation, height_total, thickness, er, shielded } = *input;
if width <= 0.0 {
return Err(CalcError::NegativeDimension { name: "width", value: width });
}
if separation <= 0.0 {
return Err(CalcError::NegativeDimension { name: "separation", value: separation });
}
if height_total <= 0.0 {
return Err(CalcError::NegativeDimension { name: "height_total", value: height_total });
}
if thickness <= 0.0 {
return Err(CalcError::NegativeDimension { name: "thickness", value: thickness });
}
if er < 1.0 {
return Err(CalcError::OutOfRange {
name: "er",
value: er,
expected: ">= 1.0",
});
}
let z0 = if shielded {
(60.0 / er.sqrt())
* (1.9 * height_total / (0.8 * width + thickness)).ln()
} else {
(87.0 / (er + 1.41_f64).sqrt())
* (5.98 * (separation + height_total) / (0.8 * width + thickness)).ln()
};
let cf = 1.0 - 1.0 / (std::f64::consts::PI * width / (2.0 * separation)).cosh();
let zodd = z0 * (1.0 - cf);
let zeven = z0 * (1.0 + cf);
let zdiff = 2.0 * zodd;
let kb = (zeven - zodd) / (zeven + zodd);
let kb_db = 20.0 * kb.log10();
let kb_term = kb_terminated(kb);
let kb_term_db = 20.0 * kb_term.log10();
Ok(DifferentialResult {
zdiff,
zo: z0,
zodd,
zeven,
kb,
kb_db,
kb_term,
kb_term_db,
})
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn shielded(width: f64, separation: f64, height_total: f64, thickness: f64, er: f64) -> BroadsideCoupledInput {
BroadsideCoupledInput { width, separation, height_total, thickness, er, shielded: true }
}
fn unshielded(width: f64, separation: f64, height_total: f64, thickness: f64, er: f64) -> BroadsideCoupledInput {
BroadsideCoupledInput { width, separation, height_total, thickness, er, shielded: false }
}
#[test]
fn shielded_wider_strip_lowers_z0() {
let narrow = calculate(&shielded(5.0, 5.0, 30.0, 2.0, 4.5)).unwrap();
let wide = calculate(&shielded(20.0, 5.0, 30.0, 2.0, 4.5)).unwrap();
assert!(
narrow.zo > wide.zo,
"narrow Z0 {:.3} should exceed wide Z0 {:.3}",
narrow.zo,
wide.zo
);
}
#[test]
fn shielded_larger_separation_reduces_coupling() {
let close = calculate(&shielded(10.0, 5.0, 30.0, 2.0, 4.5)).unwrap();
let far = calculate(&shielded(10.0, 20.0, 30.0, 2.0, 4.5)).unwrap();
assert!(
far.kb < close.kb,
"wider separation Kb {:.4} should be less than {:.4}",
far.kb,
close.kb
);
}
#[test]
fn shielded_zdiff_equals_two_zodd() {
let r = calculate(&shielded(10.0, 5.0, 30.0, 2.0, 4.5)).unwrap();
assert_relative_eq!(r.zdiff, 2.0 * r.zodd, max_relative = 1e-12);
}
#[test]
fn unshielded_reasonable_impedance() {
let r = calculate(&unshielded(10.0, 5.0, 15.0, 2.0, 4.5)).unwrap();
assert!(
r.zo >= 20.0 && r.zo <= 200.0,
"Z0 {:.3} should be in [20, 200] Ohm range",
r.zo
);
}
#[test]
fn rejects_negative_width() {
let result = calculate(&shielded(-1.0, 5.0, 30.0, 2.0, 4.5));
assert!(result.is_err());
}
#[test]
fn rejects_er_below_one() {
let result = calculate(&shielded(10.0, 5.0, 30.0, 2.0, 0.5));
assert!(result.is_err());
}
}