From bc3d8d5070d8b16728a53d0604e6b2892e7915b5 Mon Sep 17 00:00:00 2001 From: Chad <167274875+UnsignedChad@users.noreply.github.com> Date: Sun, 24 May 2026 20:18:41 -0400 Subject: [PATCH] pdnkit: CavityModel uses HJ conductor roughness CavityConfig gains conductor_roughness_rq_m. When > 0, the cavity impedance routine augments the dielectric loss tangent at each frequency with K_HJ * (delta_s / d), the standard parallel-plate surface-loss term scaled by Hammerstad-Jensen roughness. Default 0 = smooth, preserves the bit-for-bit old behavior. Typical values: 0.4 um (smooth ED foil), 1 um (standard rolled), 2-5 um (black-oxide-treated). Effect is biggest at high frequency and near resonances where Q matters. 3 tests in [cavity-hj][validation]: * rq = 0 matches the old result bit-for-bit * rq = 5 um damps the cavity peak at the analytic TM10 freq * off-resonance change is small (< 5 percent) --- pdnkit/pi/CavityModel.cpp | 12 ++++++- pdnkit/pi/CavityModel.h | 9 +++++ pdnkit/tests/CMakeLists.txt | 1 + pdnkit/tests/cavity_hj_test.cpp | 64 +++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 pdnkit/tests/cavity_hj_test.cpp diff --git a/pdnkit/pi/CavityModel.cpp b/pdnkit/pi/CavityModel.cpp index 99d7475..d2f4505 100644 --- a/pdnkit/pi/CavityModel.cpp +++ b/pdnkit/pi/CavityModel.cpp @@ -1,4 +1,5 @@ #include "pi/CavityModel.h" +#include "pi/Roughness.h" #include @@ -21,7 +22,16 @@ std::complex cavity_impedance( const double mu = cfg.mu_r * kMu0; // Complex permittivity: eps = eps_r eps_0 (1 - j tan_delta). - const cd eps = cfg.eps_r * kEps0 * cd(1.0, -cfg.tan_delta); + cd eps = cfg.eps_r * kEps0 * cd(1.0, -cfg.tan_delta); + // Optional conductor surface loss (HJ roughness x delta_s / d). + if (cfg.conductor_roughness_rq_m > 0.0 && cfg.d > 0.0) { + const double f_hz = omega / (2.0 * std::numbers::pi); + const double delta_s = skin_depth_copper(omega); + const double k_hj = hj_roughness_multiplier( + cfg.conductor_roughness_rq_m, f_hz); + const double tan_delta_cond = k_hj * delta_s / cfg.d; + eps -= cd(0.0, 1.0) * cfg.eps_r * kEps0 * tan_delta_cond; + } const cd k_squared = omega * omega * mu * eps; cd sum(0.0, 0.0); diff --git a/pdnkit/pi/CavityModel.h b/pdnkit/pi/CavityModel.h index 6a160a0..9a0d585 100644 --- a/pdnkit/pi/CavityModel.h +++ b/pdnkit/pi/CavityModel.h @@ -34,6 +34,15 @@ struct CavityConfig { double mu_r = 1.0; // relative permeability (~1 for PCB dielectrics) double tan_delta = 0.020; // dielectric loss tangent (FR-4 default) int max_modes = 30; // m, n each summed 0..max_modes inclusive + + // Optional conductor surface roughness contribution. When > 0 the + // effective dielectric loss tangent is augmented per-frequency with + // K_HJ * (delta_s / d) -- the surface-loss term for a parallel-plate + // cavity, multiplied by the Hammerstad-Jensen roughness factor. + // Default 0 = smooth, preserves existing behavior. + // Typical values: 0.4e-6 (smooth ED foil), 1.0e-6 (standard rolled), + // 2.0-5.0e-6 (black-oxide treated). + double conductor_roughness_rq_m = 0.0; }; // Self/transfer impedance at angular frequency omega (rad/s) between ports diff --git a/pdnkit/tests/CMakeLists.txt b/pdnkit/tests/CMakeLists.txt index e91eaa1..754a020 100644 --- a/pdnkit/tests/CMakeLists.txt +++ b/pdnkit/tests/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable(pdnkit_tests ir_solver_test.cpp ir_result_mesh_test.cpp cavity_model_test.cpp + cavity_hj_test.cpp cavity_mount_l_test.cpp decap_optimizer_test.cpp transient_test.cpp diff --git a/pdnkit/tests/cavity_hj_test.cpp b/pdnkit/tests/cavity_hj_test.cpp new file mode 100644 index 0000000..7f293af --- /dev/null +++ b/pdnkit/tests/cavity_hj_test.cpp @@ -0,0 +1,64 @@ +#include +#include + +#include +#include +#include + +#include "pi/CavityModel.h" + +using namespace pdnkit::pi; +using Catch::Approx; + +namespace { +CavityConfig fr4() { + CavityConfig cfg; + cfg.a = 0.100; + cfg.b = 0.080; + cfg.d = 1.6e-3; + cfg.eps_r = 4.3; + cfg.tan_delta = 0.020; + cfg.max_modes = 15; + return cfg; +} +} // namespace + +TEST_CASE("cavity-hj: zero roughness is identical to default", + "[cavity-hj][validation]") { + CavityConfig a = fr4(); + a.conductor_roughness_rq_m = 0.0; + CavityConfig b = fr4(); + const double f = 1.0e8; + const double w = 2.0 * std::numbers::pi * f; + auto za = cavity_impedance(a, 0.020, 0.020, 0.080, 0.060, w); + auto zb = cavity_impedance(b, 0.020, 0.020, 0.080, 0.060, w); + REQUIRE(za.real() == Approx(zb.real())); + REQUIRE(za.imag() == Approx(zb.imag())); +} + +TEST_CASE("cavity-hj: roughness damps the resonance peak", + "[cavity-hj][validation]") { + const double f = 723.0e6; + const double w = 2.0 * std::numbers::pi * f; + CavityConfig smooth = fr4(); + CavityConfig rough = fr4(); + rough.conductor_roughness_rq_m = 5.0e-6; + auto z_smooth = cavity_impedance(smooth, 0.020, 0.020, 0.080, 0.060, w); + auto z_rough = cavity_impedance(rough, 0.020, 0.020, 0.080, 0.060, w); + INFO("|Z_smooth| = " << std::abs(z_smooth) + << " |Z_rough| = " << std::abs(z_rough)); + REQUIRE(std::abs(z_rough) < std::abs(z_smooth)); +} + +TEST_CASE("cavity-hj: off-resonance change is small", "[cavity-hj]") { + const double f = 50.0e6; + const double w = 2.0 * std::numbers::pi * f; + CavityConfig smooth = fr4(); + CavityConfig rough = fr4(); + rough.conductor_roughness_rq_m = 5.0e-6; + auto z_smooth = cavity_impedance(smooth, 0.020, 0.020, 0.080, 0.060, w); + auto z_rough = cavity_impedance(rough, 0.020, 0.020, 0.080, 0.060, w); + const double rel = std::abs(std::abs(z_rough) - std::abs(z_smooth)) + / std::abs(z_smooth); + REQUIRE(rel < 0.05); +}