KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_smith_chart_math.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21#include <cmath>
22#include <sim/sim_plot_tab.h>
23#include <sim/smith_math.h>
24
25BOOST_AUTO_TEST_SUITE( SmithChartMath )
26
27
28BOOST_AUTO_TEST_CASE( GammaScreenRoundTrip )
29{
30 SMITH_VIEW view;
31 view.center = wxPoint( 400, 300 );
32 view.radius = 250.0;
33 view.zoom = 1.0;
34 view.pan = wxRealPoint( 0.0, 0.0 );
35
36 // one pixel of rounding corresponds to 1/radius in gamma
37 double tol = 1.0 / view.radius;
38
39 for( double re : { -1.0, -0.5, 0.0, 0.3, 1.0 } )
40 {
41 for( double im : { -1.0, -0.25, 0.0, 0.6, 1.0 } )
42 {
43 wxRealPoint back = view.ToGamma( view.ToScreen( re, im ) );
44
45 BOOST_CHECK_SMALL( back.x - re, tol );
46 BOOST_CHECK_SMALL( back.y - im, tol );
47 }
48 }
49
50 // positive Im (inductive) plots above the center
51 BOOST_CHECK_LT( view.ToScreen( 0.0, 0.5 ).y, view.center.y );
52 BOOST_CHECK_GT( view.ToScreen( 0.0, -0.5 ).y, view.center.y );
53 BOOST_CHECK_GT( view.ToScreen( 0.5, 0.0 ).x, view.center.x );
54
55 view.zoom = 8.0;
56 view.radius = 250.0 * view.zoom;
57 view.pan = wxRealPoint( 0.4, -0.2 );
58
59 tol = 1.0 / view.radius;
60
61 wxRealPoint back = view.ToGamma( view.ToScreen( 0.45, -0.15 ) );
62
63 BOOST_CHECK_SMALL( back.x - 0.45, tol );
64 BOOST_CHECK_SMALL( back.y - -0.15, tol );
65
66 // the pan point lands on the window center
67 BOOST_CHECK_EQUAL( view.ToScreen( 0.4, -0.2 ).x, view.center.x );
68 BOOST_CHECK_EQUAL( view.ToScreen( 0.4, -0.2 ).y, view.center.y );
69}
70
71
72BOOST_AUTO_TEST_CASE( ZoomAboutPointKeepsGamma )
73{
74 // the gamma under the cursor must stay fixed while zooming
75 double baseRadius = 250.0;
76 double oldZoom = 2.0;
77 double newZoom = 5.0;
78 wxPoint center( 400, 300 );
79 wxPoint pos( 520, 180 );
80
81 SMITH_VIEW view;
82 view.center = center;
83 view.zoom = oldZoom;
84 view.radius = baseRadius * oldZoom;
85 view.pan = wxRealPoint( 0.1, 0.3 );
86
87 wxRealPoint gamma = view.ToGamma( pos );
88
89 SMITH_VIEW zoomed;
90 zoomed.center = center;
91 zoomed.zoom = newZoom;
92 zoomed.radius = baseRadius * newZoom;
93 zoomed.pan = SMITH_MATH::ZoomAboutPoint( view, pos, newZoom );
94
95 wxRealPoint after = zoomed.ToGamma( pos );
96
97 BOOST_CHECK_CLOSE( after.x, gamma.x, 1e-9 );
98 BOOST_CHECK_CLOSE( after.y, gamma.y, 1e-9 );
99
100 // zooming back out returns the original pan
101 SMITH_VIEW restored;
102 restored.center = center;
103 restored.zoom = oldZoom;
104 restored.radius = baseRadius * oldZoom;
105 restored.pan = SMITH_MATH::ZoomAboutPoint( zoomed, pos, oldZoom );
106
107 BOOST_CHECK_CLOSE( restored.pan.x, view.pan.x, 1e-9 );
108 BOOST_CHECK_CLOSE( restored.pan.y, view.pan.y, 1e-9 );
109}
110
111
112BOOST_AUTO_TEST_CASE( ToScreenOverflowSafety )
113{
114 SMITH_VIEW view;
115 view.center = wxPoint( 400, 300 );
116 view.radius = 25000.0; // 50x zoom on a large window
117 view.zoom = 50.0;
118 view.pan = wxRealPoint( 0.0, 0.0 );
119
120 // a huge gamma from an active circuit must clamp, not overflow the int math
121 wxPoint farPoint = view.ToScreen( 1e12, -1e12 );
122
123 BOOST_CHECK( farPoint.x > view.center.x );
124 BOOST_CHECK( farPoint.y > view.center.y );
125
126 // non-finite samples land on a defined point instead of feeding NaN to KiROUND
127 double nan = std::nan( "" );
128 double inf = std::numeric_limits<double>::infinity();
129
130 BOOST_CHECK_EQUAL( view.ToScreen( nan, 0.0 ).x, view.center.x );
131 BOOST_CHECK_EQUAL( view.ToScreen( 0.0, nan ).y, view.center.y );
132 BOOST_CHECK_EQUAL( view.ToScreen( inf, inf ).x, view.center.x );
133
134 // a NaN gamma cannot produce an impedance
135 double r, x;
136 BOOST_CHECK( !SMITH_MATH::GammaToImpedance( nan, 0.0, 50.0, r, x ) );
137 BOOST_CHECK( !SMITH_MATH::GammaToImpedance( 0.0, nan, 50.0, r, x ) );
138}
139
140
141BOOST_AUTO_TEST_CASE( ImpedanceFromGamma )
142{
143 double r, x;
144
145 // matched, gamma = 0 -> z = z0
146 BOOST_REQUIRE( SMITH_MATH::GammaToImpedance( 0.0, 0.0, 50.0, r, x ) );
147 BOOST_CHECK_CLOSE( r, 50.0, 1e-9 );
148 BOOST_CHECK_SMALL( x, 1e-9 );
149
150 // gamma = 1/3 on the real axis -> z = 2 z0
151 BOOST_REQUIRE( SMITH_MATH::GammaToImpedance( 1.0 / 3.0, 0.0, 50.0, r, x ) );
152 BOOST_CHECK_CLOSE( r, 100.0, 1e-9 );
153 BOOST_CHECK_SMALL( x, 1e-9 );
154
155 // gamma = -1/3 -> z = z0 / 2
156 BOOST_REQUIRE( SMITH_MATH::GammaToImpedance( -1.0 / 3.0, 0.0, 50.0, r, x ) );
157 BOOST_CHECK_CLOSE( r, 25.0, 1e-9 );
158
159 // gamma = j0.5 -> z = 0.6 + j0.8 normalized
160 BOOST_REQUIRE( SMITH_MATH::GammaToImpedance( 0.0, 0.5, 50.0, r, x ) );
161 BOOST_CHECK_CLOSE( r, 30.0, 1e-9 );
162 BOOST_CHECK_CLOSE( x, 40.0, 1e-9 );
163
164 BOOST_REQUIRE( SMITH_MATH::GammaToImpedance( 0.0, -0.5, 50.0, r, x ) );
165 BOOST_CHECK_CLOSE( x, -40.0, 1e-9 );
166
167 // 75 ohm reference scales linearly
168 BOOST_REQUIRE( SMITH_MATH::GammaToImpedance( 1.0 / 3.0, 0.0, 75.0, r, x ) );
169 BOOST_CHECK_CLOSE( r, 150.0, 1e-9 );
170
171 // open circuit singularity
172 BOOST_CHECK( !SMITH_MATH::GammaToImpedance( 1.0, 0.0, 50.0, r, x ) );
173}
174
175
176BOOST_AUTO_TEST_CASE( SeriesEquivalents )
177{
178 BOOST_CHECK_CLOSE( SMITH_MATH::SeriesInductance( 72.652, 13.56e6 ), 852.72e-9, 0.1 );
179 BOOST_CHECK_CLOSE( SMITH_MATH::SeriesInductance( 3259.6, 33.470438e6 ), 15.5e-6, 0.1 );
180
181 BOOST_CHECK_CLOSE( SMITH_MATH::SeriesCapacitance( -100.0, 1e6 ), 1.5915e-9, 0.1 );
182}
183
184
185BOOST_AUTO_TEST_CASE( MatchMetrics )
186{
187 BOOST_CHECK_CLOSE( SMITH_MATH::VSWR( 0.0 ), 1.0, 1e-9 );
188 BOOST_CHECK_CLOSE( SMITH_MATH::VSWR( 1.0 / 3.0 ), 2.0, 1e-9 );
189 BOOST_CHECK_CLOSE( SMITH_MATH::VSWR( 0.5 ), 3.0, 1e-9 );
190 BOOST_CHECK( std::isinf( SMITH_MATH::VSWR( 1.0 ) ) );
191
192 BOOST_CHECK_SMALL( SMITH_MATH::ReturnLoss( 1.0 ), 1e-9 );
193 BOOST_CHECK_CLOSE( SMITH_MATH::ReturnLoss( 0.1 ), 20.0, 1e-9 );
194 BOOST_CHECK( std::isinf( SMITH_MATH::ReturnLoss( 0.0 ) ) );
195
196 // the two dialects always agree
197 for( double gm : { 0.05, 0.2, 0.45, 0.7, 0.95 } )
198 {
199 double rl = SMITH_MATH::ReturnLoss( gm );
200 double lin = std::pow( 10.0, rl / 20.0 );
201
202 BOOST_CHECK_CLOSE( SMITH_MATH::VSWR( gm ), ( lin + 1.0 ) / ( lin - 1.0 ), 1e-6 );
203 }
204}
205
206
207BOOST_AUTO_TEST_CASE( SParamPortParsing )
208{
209 long response, drive;
210
211 BOOST_REQUIRE( SMITH_MATH::ParseSParamPorts( wxS( "S_1_1" ), &response, &drive ) );
212 BOOST_CHECK_EQUAL( response, 1 );
213 BOOST_CHECK_EQUAL( drive, 1 );
214
215 BOOST_REQUIRE( SMITH_MATH::ParseSParamPorts( wxS( "S_2_1" ), &response, &drive ) );
216 BOOST_CHECK_EQUAL( response, 2 );
217 BOOST_CHECK_EQUAL( drive, 1 );
218
219 BOOST_REQUIRE( SMITH_MATH::ParseSParamPorts( wxS( "S_12_3" ), &response, &drive ) );
220 BOOST_CHECK_EQUAL( response, 12 );
221 BOOST_CHECK_EQUAL( drive, 3 );
222
223 // user-defined or unrelated names are not S-parameter vectors
224 BOOST_CHECK( !SMITH_MATH::ParseSParamPorts( wxS( "S_out" ), &response, &drive ) );
225 BOOST_CHECK( !SMITH_MATH::ParseSParamPorts( wxS( "V(out)" ), &response, &drive ) );
226 BOOST_CHECK( !SMITH_MATH::ParseSParamPorts( wxS( "S_1_" ), &response, &drive ) );
227 BOOST_CHECK( !SMITH_MATH::ParseSParamPorts( wxS( "S__1" ), &response, &drive ) );
228 BOOST_CHECK( !SMITH_MATH::ParseSParamPorts( wxS( "user0" ), &response, &drive ) );
229}
230
231
232BOOST_AUTO_TEST_CASE( SmithCursorSurvivesPartialSweep )
233{
234 std::vector<double> freqs, re, im;
235
236 for( int ii = 0; ii <= 100; ii++ )
237 {
238 freqs.push_back( 1e9 + ii * 10e6 );
239 re.push_back( 0.5 * std::cos( ii * 0.05 ) );
240 im.push_back( 0.5 * std::sin( ii * 0.05 ) );
241 }
242
243 SMITH_TRACE trace( wxS( "S_1_1" ), (SIM_TRACE_TYPE) ( SPT_VOLTAGE | SPT_SP_SMITH ) );
244 SMITH_CURSOR cursor( &trace, nullptr );
245
246 trace.SetCursor( 1, &cursor );
247 trace.SetFrequencies( freqs );
248 trace.SetData( re, im );
249
250 cursor.SetCoordX( freqs[80] );
251 BOOST_REQUIRE_EQUAL( cursor.GetCoords().x, freqs[80] );
252
253 trace.SetFrequencies( std::vector<double>( freqs.begin(), freqs.begin() + 5 ) );
254 trace.SetData( std::vector<double>( re.begin(), re.begin() + 5 ),
255 std::vector<double>( im.begin(), im.begin() + 5 ) );
256 cursor.UpdateForNewData();
257
258 trace.SetFrequencies( freqs );
259 trace.SetData( re, im );
260 cursor.UpdateForNewData();
261
262 BOOST_CHECK_EQUAL( cursor.GetCoords().x, freqs[80] );
263 BOOST_CHECK_EQUAL( cursor.GetGamma().x, re[80] );
264 BOOST_CHECK_EQUAL( cursor.GetGamma().y, im[80] );
265}
266
267
268BOOST_AUTO_TEST_CASE( SmithCursorResolvesFrequencyOnceDataArrives )
269{
270 std::vector<double> freqs, re, im;
271
272 for( int ii = 0; ii <= 100; ii++ )
273 {
274 freqs.push_back( 1e9 + ii * 10e6 );
275 re.push_back( 0.5 * std::cos( ii * 0.05 ) );
276 im.push_back( 0.5 * std::sin( ii * 0.05 ) );
277 }
278
279 SMITH_TRACE trace( wxS( "S_1_1" ), (SIM_TRACE_TYPE) ( SPT_VOLTAGE | SPT_SP_SMITH ) );
280 SMITH_CURSOR cursor( &trace, nullptr );
281
282 trace.SetCursor( 1, &cursor );
283 cursor.SetCoordX( freqs[80] );
284
285 trace.SetFrequencies( freqs );
286 trace.SetData( re, im );
287 cursor.UpdateForNewData();
288
289 BOOST_CHECK_EQUAL( cursor.GetCoords().x, freqs[80] );
290 BOOST_CHECK_EQUAL( cursor.GetGamma().x, re[80] );
291}
292
293
Trace hidden while the tab is in Smith mode, kept so leaving the mode restores it.
Cursor that snaps along a Smith chart locus, keyed by frequency.
void SetFrequencies(const std::vector< double > &aFreqs)
void SetData(const std::vector< double > &aX, const std::vector< double > &aY) override
Assigns new data set for the trace.
void SetCursor(int aCursorId, CURSOR *aCursor)
double SeriesCapacitance(double aReactance, double aFreq)
Pan that keeps the gamma point under aPos fixed when the view zooms to aNewZoom.
Definition smith_math.h:101
double SeriesInductance(double aReactance, double aFreq)
Definition smith_math.h:96
double VSWR(double aGammaMag)
Definition smith_math.h:78
wxRealPoint ZoomAboutPoint(const SMITH_VIEW &aView, const wxPoint &aPos, double aNewZoom)
S-parameter vectors are named S_<responsePort>_<drivePort>.
Definition smith_math.h:107
bool ParseSParamPorts(const wxString &aVectorName, long *aResponsePort, long *aDrivePort)
Definition smith_math.h:117
bool GammaToImpedance(double aRe, double aIm, double aZ0, double &aResistance, double &aReactance)
< Impedance of a reflection coefficient, z = z0 ( 1 + gamma ) / ( 1 - gamma ), false at the gamma = 1...
Definition smith_math.h:65
double ReturnLoss(double aGammaMag)
Series equivalent element of a reactance at one frequency, henries for aReactance > 0,...
Definition smith_math.h:86
SIM_TRACE_TYPE
Definition sim_types.h:49
@ SPT_VOLTAGE
Definition sim_types.h:51
@ SPT_SP_SMITH
Definition sim_types.h:57
< Smith chart placement plus pan/zoom, maps a gamma point to a screen pixel and back.
Definition smith_math.h:32
wxRealPoint pan
Definition smith_math.h:36
double zoom
Definition smith_math.h:35
wxRealPoint ToGamma(const wxPoint &aPt) const
Definition smith_math.h:53
wxPoint center
Definition smith_math.h:33
wxPoint ToScreen(double aRe, double aIm) const
Definition smith_math.h:39
double radius
Definition smith_math.h:34
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR2I center
BOOST_AUTO_TEST_CASE(GammaScreenRoundTrip)
BOOST_CHECK_EQUAL(result, "25.4")