KiCad PCB EDA Suite
Loading...
Searching...
No Matches
transline_calculation_base.cpp
Go to the documentation of this file.
1/*
2 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this package. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include <algorithm>
19#include <stdexcept>
20#include <utility>
21
24
25
27namespace TC = TRANSLINE_CALCULATIONS;
28
29
30void TRANSLINE_CALCULATION_BASE::InitProperties( const std::initializer_list<TRANSLINE_PARAMETERS>& aParams )
31{
32 for( const TRANSLINE_PARAMETERS& param : aParams )
33 m_parameters[param] = 0.0;
34}
35
36
37std::unordered_map<TRANSLINE_PARAMETERS, std::pair<double, TRANSLINE_STATUS>>&
43
44
45std::unordered_map<TRANSLINE_PARAMETERS, std::pair<double, TRANSLINE_STATUS>>&
51
52
54 const TRANSLINE_STATUS aStatus )
55{
56 m_analysisStatus[aParam] = { aValue, aStatus };
57}
58
59
61 const TRANSLINE_STATUS aStatus )
62{
63 m_synthesisStatus[aParam] = { aValue, aStatus };
64}
65
66
68 const TRANSLINE_PARAMETERS aMeasure, bool aRecalculateLength )
69{
70 double& var = GetParameterRef( aOptimise );
71 double& Z0_param = GetParameterRef( aMeasure );
72 double& ANG_L_param = GetParameterRef( TCP::ANG_L );
73
74 if( !std::isfinite( Z0_param ) )
75 {
76 var = NAN;
77 return false;
78 }
79
80 if( ( !std::isfinite( var ) ) || ( var == 0 ) )
81 var = 0.001;
82
83 /* required value of Z0 */
84 double Z0_dest = Z0_param;
85
86 /* required value of angl_l */
87 double angl_l_dest = ANG_L_param;
88
89 /* Newton's method */
90 int iteration = 0;
91
92 /* compute parameters */
93 Analyse();
94 double Z0_current = Z0_param;
95
96 double error = fabs( Z0_dest - Z0_current );
97
98 while( error > m_maxError )
99 {
100 iteration++;
101 double increment = var / 100.0;
102 var += increment;
103
104 /* compute parameters */
105 Analyse();
106 double Z0_result = Z0_param;
107
108 // f(w(n)) = Z0 - Z0(w(n))
109 // f'(w(n)) = -f'(Z0(w(n)))
110 // f'(Z0(w(n))) = (Z0(w(n)) - Z0(w(n+delw))/delw
111 // w(n+1) = w(n) - f(w(n))/f'(w(n))
112 double slope = ( Z0_result - Z0_current ) / increment;
113 slope = ( Z0_dest - Z0_current ) / slope - increment;
114 var += slope;
115
116 if( var <= 0.0 )
117 var = increment;
118
119 /* find new error */
120 /* compute parameters */
121 Analyse();
122 Z0_current = Z0_param;
123 error = fabs( Z0_dest - Z0_current );
124
125 if( iteration > 250 )
126 break;
127 }
128
129 /* Compute one last time, but with correct length */
130 if( aRecalculateLength )
131 {
132 Z0_param = Z0_dest;
133 ANG_L_param = angl_l_dest;
135 * ANG_L_param / 2.0 / M_PI ); /* in m */
136 Analyse();
137
138 /* Restore parameters */
139 Z0_param = Z0_dest;
140 ANG_L_param = angl_l_dest;
142 * ANG_L_param / 2.0 / M_PI ); /* in m */
143 }
144
145 return error <= m_maxError;
146}
147
148
150{
151 double depth = 1.0
154 return depth;
155}
156
157
158double TRANSLINE_CALCULATION_BASE::UnitPropagationDelay( const double aEpsilonEff )
159{
160 return std::sqrt( aEpsilonEff ) * ( 1.0e10 / 2.99e8 );
161}
162
163
165{
166 const auto selected = static_cast<int>( GetParameter( TCP::DIELECTRIC_MODEL_SEL ) );
167
168 if( selected != static_cast<int>( DIELECTRIC_MODEL::DJORDJEVIC_SARKAR ) )
169 {
170 m_dsModel.reset();
171 return;
172 }
173
174 const double epsRSpec = GetParameter( TCP::EPSILONR );
175 const double tanDSpec = GetParameter( TCP::TAND );
176 const double fSpec = GetParameter( TCP::EPSILONR_SPEC_FREQ );
177
178 // User-supplied spec frequency must be positive, otherwise fall back to CONSTANT.
179 if( !std::isfinite( fSpec ) || fSpec <= 0.0 )
180 {
181 m_dsModel.reset();
182 return;
183 }
184
185 try
186 {
188 ds.Fit( epsRSpec, tanDSpec, fSpec );
189 m_dsModel.emplace( std::move( ds ) );
190 }
191 catch( const std::invalid_argument& )
192 {
193 m_dsModel.reset();
194 }
195}
196
197
199{
200 if( m_dsModel )
201 return m_dsModel->EpsilonRealAt( aF );
202
203 return GetParameter( TCP::EPSILONR );
204}
205
206
208{
209 if( m_dsModel )
210 return m_dsModel->TanDeltaAt( aF );
211
212 return GetParameter( TCP::TAND );
213}
214
215
216double TRANSLINE_CALCULATION_BASE::WanHoorfarQ2( double aU, double aHBarTop )
217{
218 // Wan-Hoorfar 2000 eq. (4): Hammerstad-style effective strip width for wide strips.
219 const double wBarEff = aU + ( 2.0 / M_PI ) * std::log( 17.08 * ( 0.5 * aU + 0.92 ) );
220
221 // Wan-Hoorfar eq. (5): v-bar parametrising the field contraction above the strip as
222 // the boundary h_2 moves up. Clamps to 1 as h_2 -> infinity, 0 as h_2 -> h.
223 const double denom = wBarEff * M_PI - 4.0;
224
225 if( denom <= 0.0 || !std::isfinite( denom ) )
226 return 0.0;
227
228 const double vBar = ( 2.0 / M_PI ) * std::atan( ( 2.0 * M_PI / denom ) * ( aHBarTop - 1.0 ) );
229 const double halfPi = 0.5 * M_PI * vBar;
230
231 if( aU >= 1.0 )
232 {
233 // Wide strip. q_1 from eq (2) and q_2 from the improved eq (12). With n = 3 this
234 // reduces to q_2 = 1 - q_1 - correction, i.e. the fraction of the total field that
235 // lies between h and h_2. The full sum-form collapses here because we only need q_2.
236 const double q1 = 1.0 - std::log( wBarEff * M_PI - 1.0 ) / ( 2.0 * wBarEff );
237 const double inner = 2.0 * wBarEff * std::cos( halfPi ) / ( 2.0 * aHBarTop - 1.0 + vBar )
238 + std::sin( halfPi );
239
240 if( inner <= 0.0 || !std::isfinite( inner ) )
241 return 0.0;
242
243 const double correction = ( 1.0 - vBar ) / ( 2.0 * wBarEff ) * std::log( inner );
244 return std::max( 0.0, 1.0 - q1 - correction );
245 }
246
247 // Narrow strip. Wan-Hoorfar eq (6), (7), (8), (13). b_j is the strip-geometry
248 // constant; the arccos term encodes the same field-capture geometry as the wide-strip
249 // branch but fit against the narrow-strip conformal mapping.
250 const double logEighth = std::log( 0.125 * aU );
251
252 if( !std::isfinite( logEighth ) || logEighth == 0.0 )
253 return 0.0;
254
255 const double q1 = 0.5 + 0.9 / ( M_PI * logEighth );
256 const double bj = ( aHBarTop + 1.0 ) / ( aHBarTop + 0.25 * aU - 1.0 );
257
258 if( bj <= 0.0 || !std::isfinite( bj ) )
259 return 0.0;
260
261 const double acosArg = std::sqrt( bj / aHBarTop ) * ( aHBarTop - 1.0 + 0.125 * aU );
262
263 if( acosArg < -1.0 || acosArg > 1.0 )
264 return 0.0;
265
266 const double correction = ( std::log( bj ) * std::acos( acosArg ) ) / ( 4.0 * logEighth );
267 return std::max( 0.0, 1.0 - q1 - correction );
268}
269
270
271std::pair<double, double>
272TRANSLINE_CALCULATION_BASE::ApplySoldermaskCorrection( double aEpsEffUncoated, double aTanDeltaSubstrate,
273 double aEpsRSubstrate, double aWOverH,
274 double /*aF*/ ) const
275{
276 // Bit-identical no-op paths. Any single missing ingredient disables the correction.
278 return { aEpsEffUncoated, aTanDeltaSubstrate };
279
280 const double C = GetParameter( TCP::SOLDERMASK_THICKNESS );
281 const double h = GetParameter( TCP::H );
282
283 if( C <= 0.0 || h <= 0.0 || !std::isfinite( C ) || !std::isfinite( h ) )
284 return { aEpsEffUncoated, aTanDeltaSubstrate };
285
286 const double epsMask = GetParameter( TCP::SOLDERMASK_EPSILONR );
287 const double tanDMask = GetParameter( TCP::SOLDERMASK_TAND );
288
289 // Reject non-physical mask material parameters. eps_r < 1 violates causality, NaN/inf
290 // would propagate into Z0 / loss outputs, and a negative tan delta would drive
291 // ATTEN_DILECTRIC negative (a "dielectric gain" energy-conservation violation).
292 if( !std::isfinite( epsMask ) || epsMask <= 1.0 )
293 return { aEpsEffUncoated, aTanDeltaSubstrate };
294
295 if( !std::isfinite( tanDMask ) || tanDMask < 0.0 )
296 return { aEpsEffUncoated, aTanDeltaSubstrate };
297
298 if( aEpsRSubstrate <= 1.0 || !std::isfinite( aEpsRSubstrate ) )
299 return { aEpsEffUncoated, aTanDeltaSubstrate };
300
301 // Delta q_mask: fraction of the un-coated air region above the trace that the mask
302 // displaces. Subtracting the h_2 = h baseline cancels a non-zero offset in Svacina's
303 // formula so the correction vanishes smoothly at C = 0.
304 const double deltaQ = GetSoldermaskDeltaQ( aWOverH, C / h );
305
306 if( deltaQ <= 0.0 )
307 return { aEpsEffUncoated, aTanDeltaSubstrate };
308
309 // Air-replacement decomposition (Bahl and Stuchly 1980, "Analysis of a Microstrip
310 // Covered with a Lossy Dielectric", IEEE MTT-28 (2), eq. 22 in the d -> infinity
311 // limit). The mask displaces AIR, not substrate, so
312 // eps_eff_coated = eps_eff_uncoated + Delta q_mask * (eps_mask - 1).
313 const double epsEffCoated = aEpsEffUncoated + deltaQ * ( epsMask - 1.0 );
314
315 // q_sub from the standard filling-factor identity for a two-layer microstrip; held
316 // fixed when the mask is added on top because the substrate field distribution does
317 // not change at leading order.
318 const double qSub = std::clamp( ( aEpsEffUncoated - 1.0 ) / ( aEpsRSubstrate - 1.0 ), 0.0, 1.0 );
319
320 // Cap Delta q_mask so the residual air fraction stays non-negative. Saturates when
321 // the subclass factor overshoots (e.g. an empirical CPW model driven by a very thick
322 // mask) and prevents dielectric loss from being synthesised out of nowhere.
323 const double deltaQCapped = std::min( deltaQ, std::max( 0.0, 1.0 - qSub ) );
324
325 double tanDCoated = aTanDeltaSubstrate;
326
327 if( epsEffCoated > 0.0 )
328 {
329 tanDCoated = ( qSub * aEpsRSubstrate * aTanDeltaSubstrate
330 + deltaQCapped * epsMask * tanDMask )
331 / epsEffCoated;
332 }
333
334 return { epsEffCoated, tanDCoated };
335}
336
337
338std::pair<double, double> TRANSLINE_CALCULATION_BASE::EllipticIntegral( const double arg )
339{
340 static constexpr double NR_EPSI = 2.2204460492503131e-16;
341 int iMax = 16;
342
343 double k = 0.0, e = 0.0;
344
345 if( arg == 1.0 )
346 {
347 k = INFINITY; // infinite
348 e = 0;
349 }
350 else if( std::isinf( arg ) && arg < 0 )
351 {
352 k = 0;
353 e = INFINITY; // infinite
354 }
355 else
356 {
357 double a, b, c, fr, s, fk = 1, fe = 1, t, da = arg;
358 int i;
359
360 if( arg < 0 )
361 {
362 fk = 1 / sqrt( 1 - arg );
363 fe = sqrt( 1 - arg );
364 da = -arg / ( 1 - arg );
365 }
366
367 a = 1;
368 b = sqrt( 1 - da );
369 c = sqrt( da );
370 fr = 0.5;
371 s = fr * c * c;
372
373 for( i = 0; i < iMax; i++ )
374 {
375 t = ( a + b ) / 2;
376 c = ( a - b ) / 2;
377 b = sqrt( a * b );
378 a = t;
379 fr *= 2;
380 s += fr * c * c;
381
382 if( c / a < NR_EPSI )
383 break;
384 }
385
386 if( i >= iMax )
387 {
388 k = 0;
389 e = 0;
390 }
391 else
392 {
393 k = M_PI_2 / a;
394 e = M_PI_2 * ( 1 - s ) / a;
395 if( arg < 0 )
396 {
397 k *= fk;
398 e *= fe;
399 }
400 }
401 }
402
403 return { k, e };
404}
Kramers-Kronig-consistent wideband dielectric model after Djordjevic et al.
void Fit(double aEpsRSpec, double aTanDSpec, double aFSpec, double aF1=1.0e3, double aF2=1.0e12)
Fit the model from a single (epsR, tan delta) datapoint at f_spec.
double GetDispersedEpsilonR(double aF) const
Dispersed permittivity at aF. Returns raw EPSILONR when the model is inactive.
double GetDispersedTanDelta(double aF) const
Dispersed loss tangent at aF. Returns raw TAND when the model is inactive.
std::unordered_map< TRANSLINE_PARAMETERS, std::pair< double, TRANSLINE_STATUS > > & GetSynthesisResults()
Gets the output parameters following synthesis.
virtual void SetAnalysisResults()=0
Sets values in the output analysis results structure.
virtual void SetSynthesisResults()=0
Sets values in the output synthesis results structure.
double GetParameter(const TRANSLINE_PARAMETERS aParam) const
Gets the given calculation property.
void InitProperties(const std::initializer_list< TRANSLINE_PARAMETERS > &aParams)
Initialises the properties used (as inputs or outputs) by the calculation.
std::optional< DIELECTRIC_DJORDJEVIC_SARKAR > m_dsModel
Fitted Djordjevic-Sarkar model. Empty unless DIELECTRIC_MODEL_SEL selects it.
static std::pair< double, double > EllipticIntegral(double arg)
Computes the complete elliptic integral of first kind K() and the second kind E() using the arithmeti...
void SetParameter(const TRANSLINE_PARAMETERS aParam, const double aValue)
Sets the given calculation property.
void SetSynthesisResult(TRANSLINE_PARAMETERS aParam, const double aValue, const TRANSLINE_STATUS aStatus=TRANSLINE_STATUS::OK)
Sets a synthesis result.
void UpdateDielectricModel()
Refit the Djordjevic-Sarkar model from the current parameter map.
static constexpr double m_maxError
The maximum error for Z0 optimisations.
std::pair< double, double > ApplySoldermaskCorrection(double aEpsEffUncoated, double aTanDeltaSubstrate, double aEpsRSubstrate, double aWOverH, double aF) const
Apply a three-layer (substrate / soldermask / air) correction to an un-coated (eps_eff,...
double SkinDepth() const
Calculate skin depth.
static double WanHoorfarQ2(double aU, double aHBarTop)
Wan-Hoorfar 2000 eq.
virtual void Analyse()=0
Analyses the transmission line using the current parameter set.
virtual double GetSoldermaskDeltaQ(double, double) const
Incremental filling factor Delta q_mask representing the fraction of the un-coated above-trace air re...
double & GetParameterRef(const TRANSLINE_PARAMETERS aParam)
Adds a constant to the given parameter.
std::unordered_map< TRANSLINE_PARAMETERS, double > m_parameters
All input and output properties used by the calculation.
bool MinimiseZ0Error1D(TRANSLINE_PARAMETERS aOptimise, TRANSLINE_PARAMETERS aMeasure, bool aRecalculateLength=false)
minimizeZ0Error1D
void SetAnalysisResult(TRANSLINE_PARAMETERS aParam, const double aValue, const TRANSLINE_STATUS aStatus=TRANSLINE_STATUS::OK)
Sets an analysis result.
std::unordered_map< TRANSLINE_PARAMETERS, std::pair< double, TRANSLINE_STATUS > > m_synthesisStatus
Synthesis results.
static double UnitPropagationDelay(double aEpsilonEff)
Calculates the unit propagation delay (ps/cm) for the given effective permittivity.
std::unordered_map< TRANSLINE_PARAMETERS, std::pair< double, TRANSLINE_STATUS > > & GetAnalysisResults()
Gets the output parameters following analysis.
std::unordered_map< TRANSLINE_PARAMETERS, std::pair< double, TRANSLINE_STATUS > > m_analysisStatus
Analysis results.
TRANSLINE_PARAMETERS TCP
constexpr double correction
#define M_PI
TRANSLINE_STATUS
Parameter status values.
TRANSLINE_PARAMETERS
All possible parameters used (as inputs or outputs) by the transmission line calculations.