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; see the file COPYING. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include <algorithm>
21#include <stdexcept>
22#include <utility>
23
26
27
29namespace TC = TRANSLINE_CALCULATIONS;
30
31
32void TRANSLINE_CALCULATION_BASE::InitProperties( const std::initializer_list<TRANSLINE_PARAMETERS>& aParams )
33{
34 for( const TRANSLINE_PARAMETERS& param : aParams )
35 m_parameters[param] = 0.0;
36}
37
38
39std::unordered_map<TRANSLINE_PARAMETERS, std::pair<double, TRANSLINE_STATUS>>&
45
46
47std::unordered_map<TRANSLINE_PARAMETERS, std::pair<double, TRANSLINE_STATUS>>&
53
54
56 const TRANSLINE_STATUS aStatus )
57{
58 m_analysisStatus[aParam] = { aValue, aStatus };
59}
60
61
63 const TRANSLINE_STATUS aStatus )
64{
65 m_synthesisStatus[aParam] = { aValue, aStatus };
66}
67
68
70 const TRANSLINE_PARAMETERS aMeasure, bool aRecalculateLength )
71{
72 double& var = GetParameterRef( aOptimise );
73 double& Z0_param = GetParameterRef( aMeasure );
74 double& ANG_L_param = GetParameterRef( TCP::ANG_L );
75
76 if( !std::isfinite( Z0_param ) )
77 {
78 var = NAN;
79 return false;
80 }
81
82 if( ( !std::isfinite( var ) ) || ( var == 0 ) )
83 var = 0.001;
84
85 /* required value of Z0 */
86 double Z0_dest = Z0_param;
87
88 /* required value of angl_l */
89 double angl_l_dest = ANG_L_param;
90
91 /* Newton's method */
92 int iteration = 0;
93
94 /* compute parameters */
95 Analyse();
96 double Z0_current = Z0_param;
97
98 double error = fabs( Z0_dest - Z0_current );
99
100 while( error > m_maxError )
101 {
102 iteration++;
103 double increment = var / 100.0;
104 var += increment;
105
106 /* compute parameters */
107 Analyse();
108 double Z0_result = Z0_param;
109
110 // f(w(n)) = Z0 - Z0(w(n))
111 // f'(w(n)) = -f'(Z0(w(n)))
112 // f'(Z0(w(n))) = (Z0(w(n)) - Z0(w(n+delw))/delw
113 // w(n+1) = w(n) - f(w(n))/f'(w(n))
114 double slope = ( Z0_result - Z0_current ) / increment;
115 slope = ( Z0_dest - Z0_current ) / slope - increment;
116 var += slope;
117
118 if( var <= 0.0 )
119 var = increment;
120
121 /* find new error */
122 /* compute parameters */
123 Analyse();
124 Z0_current = Z0_param;
125 error = fabs( Z0_dest - Z0_current );
126
127 if( iteration > 250 )
128 break;
129 }
130
131 /* Compute one last time, but with correct length */
132 if( aRecalculateLength )
133 {
134 Z0_param = Z0_dest;
135 ANG_L_param = angl_l_dest;
137 * ANG_L_param / 2.0 / M_PI ); /* in m */
138 Analyse();
139
140 /* Restore parameters */
141 Z0_param = Z0_dest;
142 ANG_L_param = angl_l_dest;
144 * ANG_L_param / 2.0 / M_PI ); /* in m */
145 }
146
147 return error <= m_maxError;
148}
149
150
152{
153 double depth = 1.0
156 return depth;
157}
158
159
160double TRANSLINE_CALCULATION_BASE::UnitPropagationDelay( const double aEpsilonEff )
161{
162 return std::sqrt( aEpsilonEff ) * ( 1.0e10 / 2.99e8 );
163}
164
165
167{
168 const auto selected = static_cast<int>( GetParameter( TCP::DIELECTRIC_MODEL_SEL ) );
169
170 if( selected != static_cast<int>( DIELECTRIC_MODEL::DJORDJEVIC_SARKAR ) )
171 {
172 m_dsModel.reset();
173 return;
174 }
175
176 const double epsRSpec = GetParameter( TCP::EPSILONR );
177 const double tanDSpec = GetParameter( TCP::TAND );
178 const double fSpec = GetParameter( TCP::EPSILONR_SPEC_FREQ );
179
180 // User-supplied spec frequency must be positive, otherwise fall back to CONSTANT.
181 if( !std::isfinite( fSpec ) || fSpec <= 0.0 )
182 {
183 m_dsModel.reset();
184 return;
185 }
186
187 try
188 {
190 ds.Fit( epsRSpec, tanDSpec, fSpec );
191 m_dsModel.emplace( std::move( ds ) );
192 }
193 catch( const std::invalid_argument& )
194 {
195 m_dsModel.reset();
196 }
197}
198
199
201{
202 if( m_dsModel )
203 return m_dsModel->EpsilonRealAt( aF );
204
205 return GetParameter( TCP::EPSILONR );
206}
207
208
210{
211 if( m_dsModel )
212 return m_dsModel->TanDeltaAt( aF );
213
214 return GetParameter( TCP::TAND );
215}
216
217
218double TRANSLINE_CALCULATION_BASE::WanHoorfarQ2( double aU, double aHBarTop )
219{
220 // Wan-Hoorfar 2000 eq. (4): Hammerstad-style effective strip width for wide strips.
221 const double wBarEff = aU + ( 2.0 / M_PI ) * std::log( 17.08 * ( 0.5 * aU + 0.92 ) );
222
223 // Wan-Hoorfar eq. (5): v-bar parametrising the field contraction above the strip as
224 // the boundary h_2 moves up. Clamps to 1 as h_2 -> infinity, 0 as h_2 -> h.
225 const double denom = wBarEff * M_PI - 4.0;
226
227 if( denom <= 0.0 || !std::isfinite( denom ) )
228 return 0.0;
229
230 const double vBar = ( 2.0 / M_PI ) * std::atan( ( 2.0 * M_PI / denom ) * ( aHBarTop - 1.0 ) );
231 const double halfPi = 0.5 * M_PI * vBar;
232
233 if( aU >= 1.0 )
234 {
235 // Wide strip. q_1 from eq (2) and q_2 from the improved eq (12). With n = 3 this
236 // reduces to q_2 = 1 - q_1 - correction, i.e. the fraction of the total field that
237 // lies between h and h_2. The full sum-form collapses here because we only need q_2.
238 const double q1 = 1.0 - std::log( wBarEff * M_PI - 1.0 ) / ( 2.0 * wBarEff );
239 const double inner = 2.0 * wBarEff * std::cos( halfPi ) / ( 2.0 * aHBarTop - 1.0 + vBar )
240 + std::sin( halfPi );
241
242 if( inner <= 0.0 || !std::isfinite( inner ) )
243 return 0.0;
244
245 const double correction = ( 1.0 - vBar ) / ( 2.0 * wBarEff ) * std::log( inner );
246 return std::max( 0.0, 1.0 - q1 - correction );
247 }
248
249 // Narrow strip. Wan-Hoorfar eq (6), (7), (8), (13). b_j is the strip-geometry
250 // constant; the arccos term encodes the same field-capture geometry as the wide-strip
251 // branch but fit against the narrow-strip conformal mapping.
252 const double logEighth = std::log( 0.125 * aU );
253
254 if( !std::isfinite( logEighth ) || logEighth == 0.0 )
255 return 0.0;
256
257 const double q1 = 0.5 + 0.9 / ( M_PI * logEighth );
258 const double bj = ( aHBarTop + 1.0 ) / ( aHBarTop + 0.25 * aU - 1.0 );
259
260 if( bj <= 0.0 || !std::isfinite( bj ) )
261 return 0.0;
262
263 const double acosArg = std::sqrt( bj / aHBarTop ) * ( aHBarTop - 1.0 + 0.125 * aU );
264
265 if( acosArg < -1.0 || acosArg > 1.0 )
266 return 0.0;
267
268 const double correction = ( std::log( bj ) * std::acos( acosArg ) ) / ( 4.0 * logEighth );
269 return std::max( 0.0, 1.0 - q1 - correction );
270}
271
272
273std::pair<double, double>
274TRANSLINE_CALCULATION_BASE::ApplySoldermaskCorrection( double aEpsEffUncoated, double aTanDeltaSubstrate,
275 double aEpsRSubstrate, double aWOverH,
276 double /*aF*/ ) const
277{
278 // Bit-identical no-op paths. Any single missing ingredient disables the correction.
280 return { aEpsEffUncoated, aTanDeltaSubstrate };
281
282 const double C = GetParameter( TCP::SOLDERMASK_THICKNESS );
283 const double h = GetParameter( TCP::H );
284
285 if( C <= 0.0 || h <= 0.0 || !std::isfinite( C ) || !std::isfinite( h ) )
286 return { aEpsEffUncoated, aTanDeltaSubstrate };
287
288 const double epsMask = GetParameter( TCP::SOLDERMASK_EPSILONR );
289 const double tanDMask = GetParameter( TCP::SOLDERMASK_TAND );
290
291 // Reject non-physical mask material parameters. eps_r < 1 violates causality, NaN/inf
292 // would propagate into Z0 / loss outputs, and a negative tan delta would drive
293 // ATTEN_DILECTRIC negative (a "dielectric gain" energy-conservation violation).
294 if( !std::isfinite( epsMask ) || epsMask <= 1.0 )
295 return { aEpsEffUncoated, aTanDeltaSubstrate };
296
297 if( !std::isfinite( tanDMask ) || tanDMask < 0.0 )
298 return { aEpsEffUncoated, aTanDeltaSubstrate };
299
300 if( aEpsRSubstrate <= 1.0 || !std::isfinite( aEpsRSubstrate ) )
301 return { aEpsEffUncoated, aTanDeltaSubstrate };
302
303 // Delta q_mask: fraction of the un-coated air region above the trace that the mask
304 // displaces. Subtracting the h_2 = h baseline cancels a non-zero offset in Svacina's
305 // formula so the correction vanishes smoothly at C = 0.
306 const double deltaQ = GetSoldermaskDeltaQ( aWOverH, C / h );
307
308 if( deltaQ <= 0.0 )
309 return { aEpsEffUncoated, aTanDeltaSubstrate };
310
311 // Air-replacement decomposition (Bahl and Stuchly 1980, "Analysis of a Microstrip
312 // Covered with a Lossy Dielectric", IEEE MTT-28 (2), eq. 22 in the d -> infinity
313 // limit). The mask displaces AIR, not substrate, so
314 // eps_eff_coated = eps_eff_uncoated + Delta q_mask * (eps_mask - 1).
315 const double epsEffCoated = aEpsEffUncoated + deltaQ * ( epsMask - 1.0 );
316
317 // q_sub from the standard filling-factor identity for a two-layer microstrip; held
318 // fixed when the mask is added on top because the substrate field distribution does
319 // not change at leading order.
320 const double qSub = std::clamp( ( aEpsEffUncoated - 1.0 ) / ( aEpsRSubstrate - 1.0 ), 0.0, 1.0 );
321
322 // Cap Delta q_mask so the residual air fraction stays non-negative. Saturates when
323 // the subclass factor overshoots (e.g. an empirical CPW model driven by a very thick
324 // mask) and prevents dielectric loss from being synthesised out of nowhere.
325 const double deltaQCapped = std::min( deltaQ, std::max( 0.0, 1.0 - qSub ) );
326
327 double tanDCoated = aTanDeltaSubstrate;
328
329 if( epsEffCoated > 0.0 )
330 {
331 tanDCoated = ( qSub * aEpsRSubstrate * aTanDeltaSubstrate
332 + deltaQCapped * epsMask * tanDMask )
333 / epsEffCoated;
334 }
335
336 return { epsEffCoated, tanDCoated };
337}
338
339
340std::pair<double, double> TRANSLINE_CALCULATION_BASE::EllipticIntegral( const double arg )
341{
342 static constexpr double NR_EPSI = 2.2204460492503131e-16;
343 int iMax = 16;
344
345 double k = 0.0, e = 0.0;
346
347 if( arg == 1.0 )
348 {
349 k = INFINITY; // infinite
350 e = 0;
351 }
352 else if( std::isinf( arg ) && arg < 0 )
353 {
354 k = 0;
355 e = INFINITY; // infinite
356 }
357 else
358 {
359 double a, b, c, fr, s, fk = 1, fe = 1, t, da = arg;
360 int i;
361
362 if( arg < 0 )
363 {
364 fk = 1 / sqrt( 1 - arg );
365 fe = sqrt( 1 - arg );
366 da = -arg / ( 1 - arg );
367 }
368
369 a = 1;
370 b = sqrt( 1 - da );
371 c = sqrt( da );
372 fr = 0.5;
373 s = fr * c * c;
374
375 for( i = 0; i < iMax; i++ )
376 {
377 t = ( a + b ) / 2;
378 c = ( a - b ) / 2;
379 b = sqrt( a * b );
380 a = t;
381 fr *= 2;
382 s += fr * c * c;
383
384 if( c / a < NR_EPSI )
385 break;
386 }
387
388 if( i >= iMax )
389 {
390 k = 0;
391 e = 0;
392 }
393 else
394 {
395 k = M_PI_2 / a;
396 e = M_PI_2 * ( 1 - s ) / a;
397 if( arg < 0 )
398 {
399 k *= fk;
400 e *= fe;
401 }
402 }
403 }
404
405 return { k, e };
406}
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.