KiCad PCB EDA Suite
eserie.cpp
Go to the documentation of this file.
1/*
2 * This program source code file
3 * is part of KiCad, a free EDA CAD application.
4 *
5 * Copyright (C) 2020 <[email protected]>
6 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include <array>
23#include <algorithm>
24
26#include <wx/msgdlg.h>
27
28/* If BENCHMARK is defined, any 4R E12 calculations will print its execution time to console
29 * My Hasswell Enthusiast reports 225 mSec what are reproducible within plusminus 2 percent
30 */
31//#define BENCHMARK
32
33#ifdef BENCHMARK
34#include <profile.h>
35#endif
36
37#include "eserie.h"
38
39extern double DoubleFromString( const wxString& TextValue );
40
42
43// Return a string from aValue (aValue is expected in ohms)
44// If aValue < 1000 the returned string is aValue with unit = R
45// If aValue >= 1000 the returned string is aValue/1000 with unit = K
46// with notation similar to 2K2
47// If aValue >= 1e6 the returned string is aValue/1e6 with unit = M
48// with notation = 1M
49static std::string strValue( double aValue )
50{
51 std::string result;
52
53 if( aValue < 1000.0 )
54 {
55 result = std::to_string( static_cast<int>( aValue ) );
56 result += 'R';
57 }
58 else
59 {
60 double div = 1e3;
61 int unit = 'K';
62
63 if( aValue >= 1e6 )
64 {
65 div = 1e6;
66 unit = 'M';
67 }
68
69 aValue /= div;
70
71 int integer = static_cast<int>( aValue );
72 result = std::to_string(integer);
73 result += unit;
74
75 // Add mantissa: 1 digit, suitable for series up to E24
76 double mantissa = aValue - integer;
77
78 if( mantissa > 0 )
79 result += std::to_string( static_cast<int>( (mantissa*10)+0.5 ) );
80 }
81
82 return result;
83}
84
85
87{
88 // Build the list of available resistor values in each En serie
89 double listValuesE1[] = { E1_VALUES };
90 double listValuesE3[] = { E3_VALUES };
91 double listValuesE6[] = { E6_VALUES };
92 double listValuesE12[] = { E12_VALUES };
93 double listValuesE24[] = { E24_VALUES };
94 // buildSerieData must be called in the order of En series, because
95 // the list of series is expected indexed by En for the serie En
96 buildSerieData( E1, listValuesE1 );
97 buildSerieData( E3, listValuesE3 );
98 buildSerieData( E6, listValuesE6 );
99 buildSerieData( E12, listValuesE12 );
100 int count = buildSerieData( E24, listValuesE24 );
101
102 // Reserve a buffer for intermediate calculations:
103 // the buffer size is 2*count*count to store all combinaisons of 2 values
104 // there are 2*count*count = 29282 combinations for E24
105 int bufsize = 2*count*count;
106 m_cmb_lut.reserve( bufsize );
107
108 // Store predefined R_DATA items.
109 for( int ii = 0; ii < bufsize; ii++ )
110 m_cmb_lut.emplace_back( "", 0.0 );
111}
112
113
114int E_SERIE::buildSerieData( int aEserie, double aList[] )
115{
116 double curr_coeff = FIRST_VALUE;
117 int count = 0;
118
119 std::vector<R_DATA> curr_list;
120
121 for( ; ; )
122 {
123 double curr_r = curr_coeff;
124
125 for( int ii = 0; ; ii++ )
126 {
127 if( aList[ii] == 0.0 ) // End of list
128 break;
129
130 double curr_r = curr_coeff * aList[ii];
131 curr_list.emplace_back( strValue( curr_r ), curr_r );
132 count++;
133
134 if( curr_r >= LAST_VALUE )
135 break;
136 }
137
138 if( curr_r >= LAST_VALUE )
139 break;
140
141 curr_coeff *= 10;
142 }
143
144 m_luts.push_back( std::move( curr_list ) );
145
146 return count;
147}
148
149
150void E_SERIE::Exclude( double aValue )
151{
152 if( aValue ) // if there is a value to exclude other than a wire jumper
153 {
154 for( R_DATA& i : m_luts[m_series] ) // then search it in the selected E-Serie lookup table
155 {
156 if( i.e_value == aValue ) // if the value to exclude is found
157 i.e_use = false; // disable its use
158 }
159 }
160}
161
162
163void E_SERIE::simple_solution( uint32_t aSize )
164{
165 uint32_t i;
166
167 m_results.at( S2R ).e_value = std::numeric_limits<double>::max(); // assume no 2R solution or max deviation
168
169 for( i = 0; i < aSize; i++ )
170 {
171 if( abs( m_cmb_lut.at( i ).e_value - m_required_value ) < abs( m_results.at( S2R ).e_value ) )
172 {
173 m_results.at( S2R ).e_value = m_cmb_lut.at( i ).e_value - m_required_value; // save signed deviation in Ohms
174 m_results.at( S2R ).e_name = m_cmb_lut.at( i ).e_name; // save combination text
175 m_results.at( S2R ).e_use = true; // this is a possible solution
176 }
177 }
178}
179
180
181void E_SERIE::combine4( uint32_t aSize )
182{
183 uint32_t i,j;
184 double tmp;
185
186 m_results.at( S4R ).e_use = false; // disable 4R solution, until
187 m_results.at( S4R ).e_value = m_results.at( S3R ).e_value; // 4R becomes better than 3R solution
188
189 #ifdef BENCHMARK
190 PROF_TIMER timer; // start timer to count execution time
191 #endif
192
193 for( i = 0; i < aSize; i++ ) // 4R search outer loop
194 { // scan valid intermediate 2R solutions
195 for( j = 0; j < aSize; j++ ) // inner loop combines all with itself
196 {
197 tmp = m_cmb_lut.at( i ).e_value + m_cmb_lut.at( j ).e_value; // calculate 2R+2R serial
198 tmp -= m_required_value; // calculate 4R deviation
199
200 if( abs( tmp ) < abs( m_results.at(S4R).e_value ) ) // if new 4R is better
201 {
202 m_results.at( S4R ).e_value = tmp; // save amount of benefit
203 std::string s = "( ";
204 s.append( m_cmb_lut.at( i ).e_name ); // mention 1st 2 component
205 s.append( " ) + ( " ); // in series
206 s.append( m_cmb_lut.at( j ).e_name ); // with 2nd 2 components
207 s.append( " )" );
208 m_results.at( S4R ).e_name = s; // save the result and
209 m_results.at( S4R ).e_use = true; // enable for later use
210 }
211
212 tmp = ( m_cmb_lut[i].e_value * m_cmb_lut.at( j ).e_value ) /
213 ( m_cmb_lut[i].e_value + m_cmb_lut.at( j ).e_value ); // calculate 2R|2R parallel
214 tmp -= m_required_value; // calculate 4R deviation
215
216 if( abs( tmp ) < abs( m_results.at( S4R ).e_value ) ) // if new 4R is better
217 {
218 m_results.at( S4R ).e_value = tmp; // save amount of benefit
219 std::string s = "( ";
220 s.append( m_cmb_lut.at( i ).e_name ); // mention 1st 2 component
221 s.append( " ) | ( " ); // in parallel
222 s.append( m_cmb_lut.at( j ).e_name ); // with 2nd 2 components
223 s.append( " )" );
224 m_results.at( S4R ).e_name = s; // save the result
225 m_results.at( S4R ).e_use = true; // enable later use
226 }
227 }
228 }
229
230 #ifdef BENCHMARK
231 printf( "Calculation time = %d mS", timer.msecs() );
232 fflush( 0 );
233 #endif
234}
235
236
238{
239 for( R_DATA& i : m_cmb_lut )
240 i.e_use = false; // before any calculation is done, assume that
241
242 for( R_DATA& i : m_results )
243 i.e_use = false; // no combinations and no results are available
244
245 for( R_DATA& i : m_luts[m_series])
246 i.e_use = true; // all selected E-values available
247}
248
249
251{
252 uint32_t combi2R = 0; // target index counts calculated 2R combinations
253 std::string s;
254
255 for( const R_DATA& i : m_luts[m_series] ) // outer loop to sweep selected source lookup table
256 {
257 if( i.e_use )
258 {
259 for( const R_DATA& j : m_luts[m_series] ) // inner loop to combine values with itself
260 {
261 if( j.e_use )
262 {
263 m_cmb_lut.at( combi2R ).e_use = true;
264 m_cmb_lut.at( combi2R ).e_value = i.e_value + j.e_value; // calculate 2R serial
265 s = i.e_name;
266 s.append( " + " );
267 m_cmb_lut.at( combi2R ).e_name = s.append( j.e_name);
268 combi2R++; // next destination
269 m_cmb_lut.at( combi2R ).e_use = true; // calculate 2R parallel
270 m_cmb_lut.at( combi2R ).e_value = i.e_value * j.e_value / ( i.e_value + j.e_value );
271 s = i.e_name;
272 s.append( " | " );
273 m_cmb_lut.at( combi2R ).e_name = s.append( j.e_name );
274 combi2R++; // next destination
275 }
276 }
277 }
278 }
279 return combi2R;
280}
281
282
283void E_SERIE::combine3( uint32_t aSize )
284{
285 uint32_t j = 0;
286 double tmp = 0; // avoid warning for being uninitialized
287 std::string s;
288
289 m_results.at( S3R ).e_use = false; // disable 3R solution, until
290 m_results.at( S3R ).e_value = m_results.at( S2R ).e_value; // 3R becomes better than 2R solution
291
292 for( const R_DATA& i : m_luts[m_series] ) // 3R Outer loop to selected primary E serie LUT
293 {
294 if( i.e_use ) // skip all excluded values
295 {
296 for( j = 0; j < aSize; j++ ) // inner loop combines with all 2R intermediate results
297 { // R+2R serial combi
298 tmp = m_cmb_lut.at( j ).e_value + i.e_value;
299 tmp -= m_required_value; // calculate deviation
300
301 if( abs( tmp ) < abs( m_results.at( S3R ).e_value ) ) // compare if better
302 { // then take it
303 s = i.e_name; // mention 3rd component
304 s.append( " + ( " ); // in series
305 s.append( m_cmb_lut.at( j ).e_name ); // with 2R combination
306 s.append( " )" );
307 m_results.at( S3R ).e_name = s; // save S3R result
308 m_results.at( S3R ).e_value = tmp; // save amount of benefit
309 m_results.at( S3R ).e_use = true; // enable later use
310 }
311
312 tmp = i.e_value * m_cmb_lut.at( j ).e_value /
313 ( i.e_value + m_cmb_lut.at( j ).e_value ); // calculate R + 2R parallel
314 tmp -= m_required_value; // calculate deviation
315
316 if( abs( tmp ) < abs( m_results.at( S3R ).e_value ) ) // compare if better
317 { // then take it
318 s = i.e_name; // mention 3rd component
319 s.append( " | ( " ); // in parallel
320 s.append( m_cmb_lut.at( j ).e_name ); // with 2R combination
321 s.append( " )" );
322 m_results.at( S3R ).e_name = s;
323 m_results.at( S3R ).e_value = tmp; // save amount of benefit
324 m_results.at( S3R ).e_use = true; // enable later use
325 }
326 }
327 }
328 }
329
330 // If there is a 3R result with remaining deviation consider to search a possibly better 4R solution
331 // calculate 4R for small series always
332 if(( m_results.at( S3R ).e_use == true ) && tmp )
333 combine4( aSize );
334}
335
336
338{
339 uint32_t no_of_2Rcombi = 0;
340
341 no_of_2Rcombi = combine2(); // combine all 2R combinations for selected E serie
342 simple_solution( no_of_2Rcombi ); // search for simple 2 component solution
343
344 if( m_results.at( S2R ).e_value ) // if simple 2R result is not exact
345 combine3( no_of_2Rcombi ); // continiue searching for a possibly better solution
346
347 strip3();
348 strip4();
349}
350
351
353{
354 std::string s;
355
356 if( m_results.at( S3R ).e_use ) // if there is a 3 term result available
357 { // what is connected either by two "|" or by 3 plus
358 s = m_results.at( S3R ).e_name;
359
360 if( ( std::count( s.begin(), s.end(), '+' ) == 2 )
361 || ( std::count( s.begin(), s.end(), '|' ) == 2 ) )
362 { // then strip one pair of braces
363 s.erase( s.find( "(" ), 1 ); // it is known sure, this is available
364 s.erase( s.find( ")" ), 1 ); // in any unstripped 3R result term
365 m_results.at( S3R ).e_name = s; // use stripped result
366 }
367 }
368}
369
370
372{
373 std::string s;
374
375 if( m_results.at( S4R ).e_use ) // if there is a 4 term result available
376 { // what are connected either by 3 "+" or by 3 "|"
377 s = m_results.at( S4R ).e_name;
378
379 if( ( std::count( s.begin(), s.end(), '+' ) == 3 )
380 || ( std::count( s.begin(), s.end(), '|' ) == 3 ) )
381 { // then strip two pair of braces
382 s.erase( s.find( "(" ), 1 ); // it is known sure, they are available
383 s.erase( s.find( ")" ), 1 ); // in any unstripped 4R result term
384 s.erase( s.find( "(" ), 1 );
385 s.erase( s.find( ")" ), 1 );
386 m_results.at( S4R ).e_name = s; // use stripped result
387 }
388 }
389}
390
391
392void PANEL_E_SERIE::OnCalculateESeries( wxCommandEvent& event )
393{
394 double reqr; // required resistor stored in local copy
395 double error, err3 = 0;
396 wxString es, fs; // error and formula strings
397
398 wxBusyCursor dummy;
399
400 reqr = ( 1000 * DoubleFromString( m_ResRequired->GetValue() ) );
401 r.SetRequiredValue( reqr ); // keep a local copy of required resistor value
402 r.NewCalc(); // assume all values available
403 /*
404 * Exclude itself. For the case, a value from the available series is found as required value,
405 * the calculator assumes this value needs a replacement for the reason of being not available.
406 * Two further exclude values can be entered to exclude and are skipped as not being available.
407 * All values entered in KiloOhms are converted to Ohm for internal calculation
408 */
409 r.Exclude( 1000 * DoubleFromString( m_ResRequired->GetValue()));
410 r.Exclude( 1000 * DoubleFromString( m_ResExclude1->GetValue()));
411 r.Exclude( 1000 * DoubleFromString( m_ResExclude2->GetValue()));
412
413 try
414 {
415 r.Calculate();
416 }
417 catch (std::out_of_range const& exc)
418 {
419 wxString msg;
420 msg << "Internal error: " << exc.what();
421
422 wxMessageBox( msg );
423 return;
424 }
425
426 fs = r.GetResults()[S2R].e_name; // show 2R solution formula string
427 m_ESeries_Sol2R->SetValue( fs );
428 error = reqr + r.GetResults()[S2R].e_value; // absolute value of solution
429 error = ( reqr / error - 1 ) * 100; // error in percent
430
431 if( error )
432 {
433 if( std::abs( error ) < 0.01 )
434 es.Printf( "<%.2f", 0.01 );
435 else
436 es.Printf( "%+.2f",error);
437 }
438 else
439 {
440 es = _( "Exact" );
441 }
442
443 m_ESeriesError2R->SetValue( es ); // anyway show 2R error string
444
445 if( r.GetResults()[S3R].e_use ) // if 3R solution available
446 {
447 err3 = reqr + r.GetResults()[S3R].e_value; // calculate the 3R
448 err3 = ( reqr / err3 - 1 ) * 100; // error in percent
449
450 if( err3 )
451 {
452 if( std::abs( err3 ) < 0.01 )
453 es.Printf( "<%.2f", 0.01 );
454 else
455 es.Printf( "%+.2f",err3);
456 }
457 else
458 {
459 es = _( "Exact" );
460 }
461
462 m_ESeriesError3R->SetValue( es ); // show 3R error string
463 fs = r.GetResults()[S3R].e_name;
464 m_ESeries_Sol3R->SetValue( fs ); // show 3R formula string
465 }
466 else // nothing better than 2R found
467 {
468 fs = _( "Not worth using" );
469 m_ESeries_Sol3R->SetValue( fs );
470 m_ESeriesError3R->SetValue( wxEmptyString );
471 }
472
473 fs = wxEmptyString;
474
475 if( r.GetResults()[S4R].e_use ) // show 4R solution if available
476 {
477 fs = r.GetResults()[S4R].e_name;
478
479 error = reqr + r.GetResults()[S4R].e_value; // absolute value of solution
480 error = ( reqr / error - 1 ) * 100; // error in percent
481
482 if( error )
483 es.Printf( "%+.2f",error );
484 else
485 es = _( "Exact" );
486
487 m_ESeriesError4R->SetValue( es );
488 }
489 else // no 4R solution
490 {
491 fs = _( "Not worth using" );
492 es = wxEmptyString;
493 m_ESeriesError4R->SetValue( es );
494 }
495
496 m_ESeries_Sol4R->SetValue( fs );
497}
498
499
500void PANEL_E_SERIE::OnESeriesSelection( wxCommandEvent& event )
501{
502 if( event.GetEventObject() == m_e1 )
503 r.SetSeries( E1 );
504 else if( event.GetEventObject() == m_e3 )
505 r.SetSeries( E3 );
506 else if( event.GetEventObject() == m_e12 )
507 r.SetSeries( E12 );
508 else if( event.GetEventObject() == m_e24 )
509 r.SetSeries( E24 );
510 else
511 r.SetSeries( E6 );
512}
Definition: eserie.h:89
std::vector< std::vector< R_DATA > > m_luts
Definition: eserie.h:183
double m_required_value
Definition: eserie.h:200
int buildSerieData(int aEserie, double aList[])
Build the list of R_DATA existing for a given serie Series are E1, E6 .
Definition: eserie.cpp:114
const std::array< R_DATA, S4R+1 > & GetResults()
Definition: eserie.h:118
void NewCalc()
initialize next calculation and erase results from previous calculation
Definition: eserie.cpp:237
void simple_solution(uint32_t aSize)
Search for closest two component solution.
Definition: eserie.cpp:163
void combine4(uint32_t aSize)
Check if there is a better four component solution.
Definition: eserie.cpp:181
std::vector< R_DATA > m_cmb_lut
Definition: eserie.h:195
void Calculate()
called on calculate button to execute all the 2R, 3R and 4R calculations
Definition: eserie.cpp:337
void SetSeries(uint32_t aSeries)
Interface for CheckBox, RadioButton, RequriedResistor and calculated Results.
Definition: eserie.h:114
std::array< R_DATA, S4R+1 > m_results
Definition: eserie.h:197
void strip3()
Definition: eserie.cpp:352
void strip4()
Definition: eserie.cpp:371
uint32_t m_series
Definition: eserie.h:198
void Exclude(double aValue)
If any value of the selected E-serie not available, it can be entered as an exclude value.
Definition: eserie.cpp:150
void SetRequiredValue(double aValue)
Definition: eserie.h:115
uint32_t combine2()
Build all 2R combinations from the selected E-serie values.
Definition: eserie.cpp:250
E_SERIE()
Definition: eserie.cpp:86
void combine3(uint32_t aSize)
Check if there is a better 3 R solution than previous one using only two components.
Definition: eserie.cpp:283
wxRadioButton * m_e3
wxRadioButton * m_e24
wxTextCtrl * m_ResExclude2
wxTextCtrl * m_ResRequired
wxTextCtrl * m_ESeries_Sol3R
wxTextCtrl * m_ESeriesError4R
wxRadioButton * m_e12
wxTextCtrl * m_ResExclude1
wxTextCtrl * m_ESeriesError2R
wxTextCtrl * m_ESeries_Sol2R
wxRadioButton * m_e1
wxTextCtrl * m_ESeries_Sol4R
wxTextCtrl * m_ESeriesError3R
void OnCalculateESeries(wxCommandEvent &event) override
Called on calculate button and executes all E-series calculations.
Definition: eserie.cpp:392
void OnESeriesSelection(wxCommandEvent &event) override
Radio Buttons to select the E-serie for the resistor calculator.
Definition: eserie.cpp:500
A small class to help profiling.
Definition: profile.h:47
double msecs(bool aSinceLast=false)
Definition: profile.h:147
#define _(s)
static std::string strValue(double aValue)
Definition: eserie.cpp:49
double DoubleFromString(const wxString &TextValue)
E_SERIE r
Definition: eserie.cpp:41
@ E6
Definition: eserie.h:61
@ E3
Definition: eserie.h:61
@ E24
Definition: eserie.h:61
@ E1
Definition: eserie.h:61
@ E12
Definition: eserie.h:61
#define E12_VALUES
Definition: eserie.h:40
#define LAST_VALUE
Definition: eserie.h:53
@ S3R
Definition: eserie.h:66
@ S2R
Definition: eserie.h:66
@ S4R
Definition: eserie.h:66
#define E1_VALUES
Definition: eserie.h:46
#define E3_VALUES
Definition: eserie.h:44
#define E24_VALUES
E-Values derived from a geometric sequence formula by Charles Renard were already accepted and widely...
Definition: eserie.h:37
#define E6_VALUES
Definition: eserie.h:42
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:401
#define FIRST_VALUE
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:74
Definition: eserie.h:70