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 <janvi@veith.net>
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 
39 extern 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
49 static 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 
114 int 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 
150 void 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 
163 void 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 
181 void E_SERIE::combine4( uint32_t aSize )
182 {
183  uint32_t i,j;
184  double tmp;
185  std::string s;
186 
187  m_results.at( S4R ).e_use = false; // disable 4R solution, until
188  m_results.at( S4R ).e_value = m_results.at( S3R ).e_value; // 4R becomes better than 3R solution
189 
190  #ifdef BENCHMARK
191  PROF_COUNTER timer; // start timer to count execution time
192  #endif
193 
194  for( i = 0; i < aSize; i++ ) // 4R search outer loop
195  { // scan valid intermediate 2R solutions
196  for( j = 0; j < aSize; j++ ) // inner loop combines all with itself
197  {
198  tmp = m_cmb_lut.at( i ).e_value + m_cmb_lut.at( j ).e_value; // calculate 2R+2R serial
199  tmp -= m_required_value; // calculate 4R deviation
200 
201  if( abs( tmp ) < abs( m_results.at(S4R).e_value ) ) // if new 4R is better
202  {
203  m_results.at( S4R ).e_value = tmp; // save amount of benefit
204  std::string s = "( ";
205  s.append( m_cmb_lut.at( i ).e_name ); // mention 1st 2 component
206  s.append( " ) + ( " ); // in series
207  s.append( m_cmb_lut.at( j ).e_name ); // with 2nd 2 components
208  s.append( " )" );
209  m_results.at( S4R ).e_name = s; // save the result and
210  m_results.at( S4R ).e_use = true; // enable for later use
211  }
212 
213  tmp = ( m_cmb_lut[i].e_value * m_cmb_lut.at( j ).e_value ) /
214  ( m_cmb_lut[i].e_value + m_cmb_lut.at( j ).e_value ); // calculate 2R|2R parallel
215  tmp -= m_required_value; // calculate 4R deviation
216 
217  if( abs( tmp ) < abs( m_results.at( S4R ).e_value ) ) // if new 4R is better
218  {
219  m_results.at( S4R ).e_value = tmp; // save amount of benefit
220  std::string s = "( ";
221  s.append( m_cmb_lut.at( i ).e_name ); // mention 1st 2 component
222  s.append( " ) | ( " ); // in parallel
223  s.append( m_cmb_lut.at( j ).e_name ); // with 2nd 2 components
224  s.append( " )" );
225  m_results.at( S4R ).e_name = s; // save the result
226  m_results.at( S4R ).e_use = true; // enable later use
227  }
228  }
229  }
230 
231  #ifdef BENCHMARK
232  printf( "Calculation time = %d mS", timer.msecs() );
233  fflush( 0 );
234  #endif
235 }
236 
237 
239 {
240  for( R_DATA& i : m_cmb_lut )
241  i.e_use = false; // before any calculation is done, assume that
242 
243  for( R_DATA& i : m_results )
244  i.e_use = false; // no combinations and no results are available
245 
246  for( R_DATA& i : m_luts[m_series])
247  i.e_use = true; // all selected E-values available
248 }
249 
250 
252 {
253  uint32_t combi2R = 0; // target index counts calculated 2R combinations
254  std::string s;
255 
256  for( const R_DATA& i : m_luts[m_series] ) // outer loop to sweep selected source lookup table
257  {
258  if( i.e_use )
259  {
260  for( const R_DATA& j : m_luts[m_series] ) // inner loop to combine values with itself
261  {
262  if( j.e_use )
263  {
264  m_cmb_lut.at( combi2R ).e_use = true;
265  m_cmb_lut.at( combi2R ).e_value = i.e_value + j.e_value; // calculate 2R serial
266  s = i.e_name;
267  s.append( " + " );
268  m_cmb_lut.at( combi2R ).e_name = s.append( j.e_name);
269  combi2R++; // next destination
270  m_cmb_lut.at( combi2R ).e_use = true; // calculate 2R parallel
271  m_cmb_lut.at( combi2R ).e_value = i.e_value * j.e_value / ( i.e_value + j.e_value );
272  s = i.e_name;
273  s.append( " | " );
274  m_cmb_lut.at( combi2R ).e_name = s.append( j.e_name );
275  combi2R++; // next destination
276  }
277  }
278  }
279  }
280  return combi2R;
281 }
282 
283 
284 void E_SERIE::combine3( uint32_t aSize )
285 {
286  uint32_t j = 0;
287  double tmp = 0; // avoid warning for being uninitialized
288  std::string s;
289 
290  m_results.at( S3R ).e_use = false; // disable 3R solution, until
291  m_results.at( S3R ).e_value = m_results.at( S2R ).e_value; // 3R becomes better than 2R solution
292 
293  for( const R_DATA& i : m_luts[m_series] ) // 3R Outer loop to selected primary E serie LUT
294  {
295  if( i.e_use ) // skip all excluded values
296  {
297  for( j = 0; j < aSize; j++ ) // inner loop combines with all 2R intermediate results
298  { // R+2R serial combi
299  tmp = m_cmb_lut.at( j ).e_value + i.e_value;
300  tmp -= m_required_value; // calculate deviation
301 
302  if( abs( tmp ) < abs( m_results.at( S3R ).e_value ) ) // compare if better
303  { // then take it
304  s = i.e_name; // mention 3rd component
305  s.append( " + ( " ); // in series
306  s.append( m_cmb_lut.at( j ).e_name ); // with 2R combination
307  s.append( " )" );
308  m_results.at( S3R ).e_name = s; // save S3R result
309  m_results.at( S3R ).e_value = tmp; // save amount of benefit
310  m_results.at( S3R ).e_use = true; // enable later use
311  }
312 
313  tmp = i.e_value * m_cmb_lut.at( j ).e_value /
314  ( i.e_value + m_cmb_lut.at( j ).e_value ); // calculate R + 2R parallel
315  tmp -= m_required_value; // calculate deviation
316 
317  if( abs( tmp ) < abs( m_results.at( S3R ).e_value ) ) // compare if better
318  { // then take it
319  s = i.e_name; // mention 3rd component
320  s.append( " | ( " ); // in parallel
321  s.append( m_cmb_lut.at( j ).e_name ); // with 2R combination
322  s.append( " )" );
323  m_results.at( S3R ).e_name = s;
324  m_results.at( S3R ).e_value = tmp; // save amount of benefit
325  m_results.at( S3R ).e_use = true; // enable later use
326  }
327  }
328  }
329  }
330 
331  // If there is a 3R result with remaining deviation consider to search a possibly better 4R solution
332  // calculate 4R for small series always
333  if(( m_results.at( S3R ).e_use == true ) && tmp )
334  combine4( aSize );
335 }
336 
337 
339 {
340  uint32_t no_of_2Rcombi = 0;
341 
342  no_of_2Rcombi = combine2(); // combine all 2R combinations for selected E serie
343  simple_solution( no_of_2Rcombi ); // search for simple 2 component solution
344 
345  if( m_results.at( S2R ).e_value ) // if simple 2R result is not exact
346  combine3( no_of_2Rcombi ); // continiue searching for a possibly better solution
347 
348  strip3();
349  strip4();
350 }
351 
352 
354 {
355  std::string s;
356 
357  if( m_results.at( S3R ).e_use ) // if there is a 3 term result available
358  { // what is connected either by two "|" or by 3 plus
359  s = m_results.at( S3R ).e_name;
360 
361  if( ( std::count( s.begin(), s.end(), '+' ) == 2 )
362  || ( std::count( s.begin(), s.end(), '|' ) == 2 ) )
363  { // then strip one pair of braces
364  s.erase( s.find( "(" ), 1 ); // it is known sure, this is available
365  s.erase( s.find( ")" ), 1 ); // in any unstripped 3R result term
366  m_results.at( S3R ).e_name = s; // use stripped result
367  }
368  }
369 }
370 
371 
373 {
374  std::string s;
375 
376  if( m_results.at( S4R ).e_use ) // if there is a 4 term result available
377  { // what are connected either by 3 "+" or by 3 "|"
378  s = m_results.at( S4R ).e_name;
379 
380  if( ( std::count( s.begin(), s.end(), '+' ) == 3 )
381  || ( std::count( s.begin(), s.end(), '|' ) == 3 ) )
382  { // then strip two pair of braces
383  s.erase( s.find( "(" ), 1 ); // it is known sure, they are available
384  s.erase( s.find( ")" ), 1 ); // in any unstripped 4R result term
385  s.erase( s.find( "(" ), 1 );
386  s.erase( s.find( ")" ), 1 );
387  m_results.at( S4R ).e_name = s; // use stripped result
388  }
389  }
390 }
391 
392 
393 void PANEL_E_SERIE::OnCalculateESeries( wxCommandEvent& event )
394 {
395  double reqr; // required resistor stored in local copy
396  double error, err3 = 0;
397  wxString es, fs; // error and formula strings
398 
399  wxBusyCursor dummy;
400 
401  reqr = ( 1000 * DoubleFromString( m_ResRequired->GetValue() ) );
402  r.SetRequiredValue( reqr ); // keep a local copy of required resistor value
403  r.NewCalc(); // assume all values available
404  /*
405  * Exclude itself. For the case, a value from the available series is found as required value,
406  * the calculator assumes this value needs a replacement for the reason of being not available.
407  * Two further exclude values can be entered to exclude and are skipped as not being available.
408  * All values entered in KiloOhms are converted to Ohm for internal calculation
409  */
410  r.Exclude( 1000 * DoubleFromString( m_ResRequired->GetValue()));
411  r.Exclude( 1000 * DoubleFromString( m_ResExclude1->GetValue()));
412  r.Exclude( 1000 * DoubleFromString( m_ResExclude2->GetValue()));
413 
414  try
415  {
416  r.Calculate();
417  }
418  catch (std::out_of_range const& exc)
419  {
420  wxString msg;
421  msg << "Internal error: " << exc.what();
422 
423  wxMessageBox( msg );
424  return;
425  }
426 
427  fs = r.GetResults()[S2R].e_name; // show 2R solution formula string
428  m_ESeries_Sol2R->SetValue( fs );
429  error = reqr + r.GetResults()[S2R].e_value; // absolute value of solution
430  error = ( reqr / error - 1 ) * 100; // error in percent
431 
432  if( error )
433  {
434  if( std::abs( error ) < 0.01 )
435  es.Printf( "<%.2f", 0.01 );
436  else
437  es.Printf( "%+.2f",error);
438  }
439  else
440  {
441  es = _( "Exact" );
442  }
443 
444  m_ESeriesError2R->SetValue( es ); // anyway show 2R error string
445 
446  if( r.GetResults()[S3R].e_use ) // if 3R solution available
447  {
448  err3 = reqr + r.GetResults()[S3R].e_value; // calculate the 3R
449  err3 = ( reqr / err3 - 1 ) * 100; // error in percent
450 
451  if( err3 )
452  {
453  if( std::abs( err3 ) < 0.01 )
454  es.Printf( "<%.2f", 0.01 );
455  else
456  es.Printf( "%+.2f",err3);
457  }
458  else
459  {
460  es = _( "Exact" );
461  }
462 
463  m_ESeriesError3R->SetValue( es ); // show 3R error string
464  fs = r.GetResults()[S3R].e_name;
465  m_ESeries_Sol3R->SetValue( fs ); // show 3R formula string
466  }
467  else // nothing better than 2R found
468  {
469  fs = _( "Not worth using" );
470  m_ESeries_Sol3R->SetValue( fs );
471  m_ESeriesError3R->SetValue( wxEmptyString );
472  }
473 
474  fs = wxEmptyString;
475 
476  if( r.GetResults()[S4R].e_use ) // show 4R solution if available
477  {
478  fs = r.GetResults()[S4R].e_name;
479 
480  error = reqr + r.GetResults()[S4R].e_value; // absolute value of solution
481  error = ( reqr / error - 1 ) * 100; // error in percent
482 
483  if( error )
484  es.Printf( "%+.2f",error );
485  else
486  es = _( "Exact" );
487 
488  m_ESeriesError4R->SetValue( es );
489  }
490  else // no 4R solution
491  {
492  fs = _( "Not worth using" );
493  es = wxEmptyString;
494  m_ESeriesError4R->SetValue( es );
495  }
496 
497  m_ESeries_Sol4R->SetValue( fs );
498 }
499 
500 
501 void PANEL_E_SERIE::OnESeriesSelection( wxCommandEvent& event )
502 {
503  if( event.GetEventObject() == m_e1 )
504  r.SetSeries( E1 );
505  else if( event.GetEventObject() == m_e3 )
506  r.SetSeries( E3 );
507  else if( event.GetEventObject() == m_e12 )
508  r.SetSeries( E12 );
509  else if( event.GetEventObject() == m_e24 )
510  r.SetSeries( E24 );
511  else
512  r.SetSeries( E6 );
513 }
std::vector< R_DATA > m_cmb_lut
Definition: eserie.h:195
void NewCalc()
initialize next calculation and erase results from previous calculation
Definition: eserie.cpp:238
void SetRequiredValue(double aValue)
Definition: eserie.h:115
void combine3(uint32_t aSize)
Check if there is a better 3 R solution than previous one using only two components.
Definition: eserie.cpp:284
Definition: eserie.h:66
static std::string strValue(double aValue)
Definition: eserie.cpp:49
#define E6_VALUES
Definition: eserie.h:42
double msecs(bool aSinceLast=false)
Definition: profile.h:146
#define E24_VALUES
E-Values derived from a geometric sequence formula by Charles Renard were already accepted and widely...
Definition: eserie.h:37
void OnESeriesSelection(wxCommandEvent &event) override
Radio Buttons to select the E-serie for the resistor calculator.
Definition: eserie.cpp:501
Definition: eserie.h:69
Definition: eserie.h:61
std::vector< std::vector< R_DATA > > m_luts
Definition: eserie.h:183
#define LAST_VALUE
Definition: eserie.h:53
wxRadioButton * m_e12
double m_required_value
Definition: eserie.h:200
A small class to help profiling.
Definition: profile.h:45
void OnCalculateESeries(wxCommandEvent &event) override
Called on calculate button and executes all E-series calculations.
Definition: eserie.cpp:393
wxTextCtrl * m_ESeries_Sol2R
double DoubleFromString(const wxString &TextValue)
void SetSeries(uint32_t aSeries)
Interface for CheckBox, RadioButton, RequriedResistor and calculated Results.
Definition: eserie.h:114
Definition: eserie.h:61
uint32_t combine2()
Build all 2R combinations from the selected E-serie values.
Definition: eserie.cpp:251
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:72
wxRadioButton * m_e3
void Calculate()
called on calculate button to execute all the 2R, 3R and 4R calculations
Definition: eserie.cpp:338
Definition: eserie.h:61
#define FIRST_VALUE
const std::array< R_DATA, S4R+1 > & GetResults()
Definition: eserie.h:118
uint32_t m_series
Definition: eserie.h:198
wxTextCtrl * m_ESeries_Sol4R
wxTextCtrl * m_ResExclude2
Definition: eserie.h:66
E_SERIE()
Definition: eserie.cpp:86
#define _(s)
wxRadioButton * m_e1
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
E_SERIE r
Definition: eserie.cpp:41
wxTextCtrl * m_ESeriesError2R
wxTextCtrl * m_ESeriesError3R
Definition: eserie.h:66
Definition: eserie.h:88
void combine4(uint32_t aSize)
Check if there is a better four component solution.
Definition: eserie.cpp:181
#define E3_VALUES
Definition: eserie.h:44
Definition: eserie.h:61
#define E1_VALUES
Definition: eserie.h:46
wxTextCtrl * m_ESeries_Sol3R
Definition: eserie.h:61
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
wxTextCtrl * m_ResExclude1
void strip4()
Definition: eserie.cpp:372
std::array< R_DATA, S4R+1 > m_results
Definition: eserie.h:197
wxTextCtrl * m_ESeriesError4R
wxRadioButton * m_e24
void simple_solution(uint32_t aSize)
Search for closest two component solution.
Definition: eserie.cpp:163
#define E12_VALUES
Definition: eserie.h:40
wxTextCtrl * m_ResRequired
void strip3()
Definition: eserie.cpp:353