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 <vector>
23 #include <array>
24 #include <iostream>
25 #include <string>
26 
27 #include <dialog_helpers.h>
28 #include "class_regulator_data.h"
29 #include "pcb_calculator_frame.h"
30 #include <wx/wx.h>
31 
32 #ifdef BENCHMARK
33 #include <sys/time.h>
34 #endif
35 
36 #include "eserie.h"
37 
38 
39 wxString eseries_help =
40 #include "eserie_help.h"
41 
42 eserie r;
43 
44 
45 void eserie::Exclude( double aValue )
46 {
47  if( aValue ) // if there is a value to exclude other than a wire jumper
48  {
49  for( r_data& i : luts[m_series] ) // then search it in the selected E-Serie lookup table
50  {
51  if( i.e_value == aValue ) // if value to exclude found
52  i.e_use = false; // disable its use
53  }
54  }
55 }
56 
57 
58 void eserie::simple_solution( uint32_t aSize )
59 {
60  uint32_t i;
61 
62  m_results[S2R].e_value = std::numeric_limits<double>::max(); // assume no 2R solution or max deviation
63 
64  for( i = 0; i < aSize; i++ )
65  {
66  if( abs( m_cmb_lut[i].e_value - m_required_value ) < abs( m_results[S2R].e_value ) )
67  {
68  m_results[S2R].e_value = m_cmb_lut[i].e_value - m_required_value; // save signed deviation in Ohms
69  m_results[S2R].e_name = m_cmb_lut[i].e_name; // save combination text
70  m_results[S2R].e_use = true; // this is a possible solution
71  }
72  }
73 }
74 
75 
76 void eserie::combine4( uint32_t aSize )
77 {
78  uint32_t i,j;
79  double tmp;
80  std::string s;
81 
82  m_results[S4R].e_use = false; // disable 4R solution, until
83  m_results[S4R].e_value = m_results[S3R].e_value; // 4R becomes better than 3R solution
84 
85  #ifdef BENCHMARK
86  PROF_COUNTER combine4_timer; // start timer to count execution time
87  #endif
88 
89  for( i = 0; i < aSize; i++ ) // 4R search outer loop
90  { // scan valid intermediate 2R solutions
91  for( j = 0; j < aSize; j++ ) // inner loop combines all with itself
92  {
93  tmp = m_cmb_lut[i].e_value + m_cmb_lut[j].e_value; // calculate 2R+2R serial
94  tmp -= m_required_value; // calculate 4R deviation
95 
96  if( abs( tmp ) < abs( m_results[S4R].e_value ) ) // if new 4R is better
97  {
98  m_results[S4R].e_value = tmp; // save amount of benefit
99  std::string s = "( ";
100  s.append( m_cmb_lut[i].e_name ); // mention 1st 2 component
101  s.append( " ) + ( " ); // in series
102  s.append( m_cmb_lut[j].e_name ); // with 2nd 2 components
103  s.append( " )" );
104  m_results[S4R].e_name = s; // save the result and
105  m_results[S4R].e_use = true; // enable for later use
106  }
107 
108  tmp = ( m_cmb_lut[i].e_value * m_cmb_lut[j].e_value ) /
109  ( m_cmb_lut[i].e_value + m_cmb_lut[j].e_value ); // calculate 2R|2R parallel
110  tmp -= m_required_value; // calculate 4R deviation
111 
112  if( abs( tmp ) < abs( m_results[S4R].e_value ) ) // if new 4R is better
113  {
114  m_results[S4R].e_value = tmp; // save amount of benefit
115  std::string s = "( ";
116  s.append( m_cmb_lut[i].e_name ); // mention 1st 2 component
117  s.append( " ) | ( " ); // in parallel
118  s.append( m_cmb_lut[j].e_name ); // with 2nd 2 components
119  s.append( " )" );
120  m_results[S4R].e_name = s; // save the result
121  m_results[S4R].e_use = true; // enable later use
122  }
123  }
124  }
125 
126  #ifdef BENCHMARK
127  if( m_series == E12 )
128  std::cout<<"4R Time = "<<combine4_timer.msecs()<<" mSec"<<std::endl;
129  #endif
130 }
131 
132 
133 void eserie::NewCalc( void )
134 {
135  for( r_data& i : m_cmb_lut )
136  i.e_use = false; // before any calculation is done, assume that
137 
138  for( r_data& i : m_results )
139  i.e_use = false; // no combinations and no results are available
140 
141  for( r_data& i : luts[m_series])
142  i.e_use = true; // all selecte E-values available
143 }
144 
145 
146 uint32_t eserie::combine2( void )
147 {
148  uint32_t combi2R = 0; // target index counts calculated 2R combinations
149  std::string s;
150 
151  for( const r_data& i : luts[m_series] ) // outer loop to sweep selected source lookup table
152  {
153  if( i.e_use )
154  {
155  for( const r_data& j : luts[m_series] ) // inner loop to combine values with itself
156  {
157  if( j.e_use )
158  {
159  m_cmb_lut[combi2R].e_use = true;
160  m_cmb_lut[combi2R].e_value = i.e_value + j.e_value; // calculate 2R serial
161  s = i.e_name;
162  s.append( " + " );
163  m_cmb_lut[combi2R].e_name = s.append( j.e_name);
164  combi2R++; // next destination
165  m_cmb_lut[combi2R].e_use = true; // calculate 2R parallel
166  m_cmb_lut[combi2R].e_value = i.e_value * j.e_value /
167  ( i.e_value + j.e_value );
168  s = i.e_name;
169  s.append( " | " );
170  m_cmb_lut[combi2R].e_name = s.append( j.e_name );
171  combi2R++; // next destination
172  }
173  }
174  }
175  }
176  return ( combi2R );
177 }
178 
179 
180 void eserie::combine3( uint32_t aSize )
181 {
182  uint32_t j = 0;
183  double tmp = 0; // avoid warning for being uninitialized
184  std::string s;
185 
186  m_results[S3R].e_use = false; // disable 3R solution, until
187  m_results[S3R].e_value = m_results[S2R].e_value; // 3R becomes better than 2R solution
188 
189  for( const r_data& i : luts[m_series] ) // 3R Outer loop to selected primary E serie LUT
190  {
191  if( i.e_use ) // skip all excluded values
192  {
193  for( j = 0; j < aSize; j++ ) // inner loop combines with all 2R intermediate results
194  { // R+2R serial combi
195  tmp = m_cmb_lut[j].e_value + i.e_value;
196  tmp -= m_required_value; // calculate deviation
197 
198  if( abs( tmp ) < abs( m_results[S3R].e_value ) ) // compare if better
199  { // then take it
200  s = i.e_name; // mention 3rd component
201  s.append( " + ( " ); // in series
202  s.append( m_cmb_lut[j].e_name ); // with 2R combination
203  s.append( " )" );
204  m_results[S3R].e_name = s; // save S3R result
205  m_results[S3R].e_value = tmp; // save amount of benefit
206  m_results[S3R].e_use = true; // enable later use
207  }
208 
209  tmp = i.e_value * m_cmb_lut[j].e_value /
210  ( i.e_value + m_cmb_lut[j].e_value ); // calculate R + 2R parallel
211  tmp -= m_required_value; // calculate deviation
212 
213  if( abs( tmp ) < abs( m_results[S3R].e_value ) ) // compare if better
214  { // then take it
215  s = i.e_name; // mention 3rd component
216  s.append( " | ( " ); // in parallel
217  s.append( m_cmb_lut[j].e_name ); // with 2R combination
218  s.append( " )" );
219  m_results[S3R].e_name = s;
220  m_results[S3R].e_value = tmp; // save amount of benefit
221  m_results[S3R].e_use = true; // enable later use
222  }
223  }
224  }
225  }
226  // if there is a 3R result with remaining deviation
227  if(( m_results[S3R].e_use == true ) && tmp )
228  { // consider to search a possibly better 4R solution
229  combine4( aSize ); // calculate 4R for small series always
230  }
231 }
232 
233 
234 void eserie::Calculate( void )
235 {
236  uint32_t no_of_2Rcombi = 0;
237 
238  no_of_2Rcombi = combine2(); // combine all 2R combinations for selected E serie
239  simple_solution( no_of_2Rcombi ); // search for simple 2 component solution
240 
241  if( m_results[S2R].e_value ) // if simple 2R result is not exact
242  combine3( no_of_2Rcombi ); // continiue searching for a possibly better solution
243 
244  strip3();
245  strip4();
246 }
247 
248 
249 void eserie::strip3( void )
250 {
251  std::string s;
252 
253  if( m_results[S3R].e_use ) // if there is a 3 term result available
254  { // what is connected either by two "|" or by 3 plus
255  s = m_results[S3R].e_name;
256 
257  if( ( std::count( s.begin(), s.end(), '+' ) == 2 )
258  || ( std::count( s.begin(), s.end(), '|' ) == 2 ) )
259  { // then strip one pair of braces
260  s.erase( s.find( "(" ), 1 ); // it is known sure, this is available
261  s.erase( s.find( ")" ), 1 ); // in any unstripped 3R result term
262  m_results[S3R].e_name = s; // use stripped result
263  }
264  }
265 }
266 
267 
268 void eserie::strip4( void )
269 {
270  std::string s;
271 
272  if( m_results[S4R].e_use ) // if there is a 4 term result available
273  { // what are connected either by 3 "+" or by 3 "|"
274  s = m_results[S4R].e_name;
275 
276  if( ( std::count( s.begin(), s.end(), '+' ) == 3 )
277  || ( std::count( s.begin(), s.end(), '|' ) == 3 ) )
278  { // then strip two pair of braces
279  s.erase( s.find( "(" ), 1 ); // it is known sure, they are available
280  s.erase( s.find( ")" ), 1 ); // in any unstripped 4R result term
281  s.erase( s.find( "(" ), 1 );
282  s.erase( s.find( ")" ), 1 );
283  m_results[S4R].e_name = s; // use stripped result
284  }
285  }
286 }
287 
288 
289 void PCB_CALCULATOR_FRAME::OnCalculateESeries( wxCommandEvent& event )
290 {
291  double reqr; // required resistor stored in local copy
292  double error, err3 = 0;
293  wxString es, fs; // error and formula strings
294 
295  reqr = ( 1000 * DoubleFromString( m_ResRequired->GetValue() ) );
296  r.SetRequiredValue( reqr ); // keep a local copy of requred resistor value
297  r.NewCalc(); // assume all values available
298  /*
299  * Exclude itself. For the case, a value from the available series is found as required value,
300  * the calculator assumes this value needs a replacement for the reason of being not available.
301  * Two further exclude values can be entered to exclude and are skipped as not being availabe.
302  * All values entered in KiloOhms are converted to Ohm for internal calculation
303  */
304  r.Exclude( 1000 * DoubleFromString( m_ResRequired->GetValue()));
305  r.Exclude( 1000 * DoubleFromString( m_ResExclude1->GetValue()));
306  r.Exclude( 1000 * DoubleFromString( m_ResExclude2->GetValue()));
307  r.Calculate();
308 
309  fs = r.get_rslt()[S2R].e_name; // show 2R solution formula string
310  m_ESeries_Sol2R->SetValue( fs );
311  error = reqr + r.get_rslt()[S2R].e_value; // absolute value of solution
312  error = ( reqr / error - 1 ) * 100; // error in percent
313 
314  if( error )
315  {
316  if( std::abs( error ) < 0.01 )
317  es.Printf( "<%.2f", 0.01 );
318  else
319  es.Printf( "%+.2f",error);
320  }
321  else
322  {
323  es = _( "Exact" );
324  }
325 
326  m_ESeriesError2R->SetValue( es ); // anyway show 2R error string
327 
328  if( r.get_rslt()[S3R].e_use ) // if 3R solution available
329  {
330  err3 = reqr + r.get_rslt()[S3R].e_value; // calculate the 3R
331  err3 = ( reqr / err3 - 1 ) * 100; // error in percent
332 
333  if( err3 )
334  {
335  if( std::abs( err3 ) < 0.01 )
336  es.Printf( "<%.2f", 0.01 );
337  else
338  es.Printf( "%+.2f",err3);
339  }
340  else
341  {
342  es = _( "Exact" );
343  }
344 
345  m_ESeriesError3R->SetValue( es ); // show 3R error string
346  fs = r.get_rslt()[S3R].e_name;
347  m_ESeries_Sol3R->SetValue( fs ); // show 3R formula string
348  }
349  else // nothing better than 2R found
350  {
351  fs = _( "Not worth using" );
352  m_ESeries_Sol3R->SetValue( fs );
353  m_ESeriesError3R->SetValue( wxEmptyString );
354  }
355 
356  fs = wxEmptyString;
357 
358  if( r.get_rslt()[S4R].e_use ) // show 4R solution if available
359  {
360  fs = r.get_rslt()[S4R].e_name;
361 
362  error = reqr + r.get_rslt()[S4R].e_value; // absolute value of solution
363  error = ( reqr / error - 1 ) * 100; // error in percent
364 
365  if( error )
366  es.Printf( "%+.2f",error );
367  else
368  es = _( "Exact" );
369 
370  m_ESeriesError4R->SetValue( es );
371  }
372  else // no 4R solution
373  {
374  fs = _( "Not worth using" );
375  es = wxEmptyString;
376  m_ESeriesError4R->SetValue( es );
377  }
378 
379  m_ESeries_Sol4R->SetValue( fs );
380 }
381 
382 void PCB_CALCULATOR_FRAME::OnESeriesSelection( wxCommandEvent& event )
383 {
384  if( event.GetEventObject() == m_e1 )
385  r.SetSeries( E1 );
386  else if( event.GetEventObject() == m_e3 )
387  r.SetSeries( E3 );
388  else if( event.GetEventObject() == m_e12 )
389  r.SetSeries( E12 );
390  else
391  r.SetSeries( E6 );
392 }
393 
394 void PCB_CALCULATOR_FRAME::initESeriesPanel() // initialize ESeries tab at each pcb-calculator start
395 {
396  wxString msg;
397 
398  // show markdown formula explanation in lower help panel
399  ConvertMarkdown2Html( wxGetTranslation( eseries_help ), msg );
400  m_panelESeriesHelp->SetPage( msg );
401 }
void OnESeriesSelection(wxCommandEvent &event) override
Radio Buttons to select the E-serie for the resistor calculator.
Definition: eserie.cpp:382
Definition: eserie.h:45
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
double msecs(bool aSinceLast=false)
Definition: profile.h:146
Definition: eserie.h:39
void Calculate(void)
called on calculate button to execute all the 2R, 3R and 4R calculations
Definition: eserie.cpp:234
Definition: eserie.h:123
void combine3(uint32_t aSize)
Check if there is a better 3 R solution than previous one using only two components.
Definition: eserie.cpp:180
Definition: eserie.h:39
void strip4(void)
Definition: eserie.cpp:268
Definition: eserie.h:39
Definition: eserie.h:45
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:45
void NewCalc(void)
initialize next calculation and erase results from previous calculation
Definition: eserie.cpp:133
A small class to help profiling.
Definition: profile.h:45
std::array< r_data, S4R+1 > m_results
Definition: eserie.h:231
double m_required_value
Definition: eserie.h:234
Definition: eserie.h:117
Definition: eserie.h:45
void initESeriesPanel()
Definition: eserie.cpp:394
Contains structures for storage of regulator data.
void SetSeries(uint32_t aSeries)
Interface for CheckBox, RadioButton, RequriedResistor and calculated Results.
Definition: eserie.h:147
Helper dialog and control classes.
void SetRequiredValue(double aValue)
Definition: eserie.h:148
std::array< r_data, MAX_CMB > m_cmb_lut
Definition: eserie.h:230
void OnCalculateESeries(wxCommandEvent &event) override
Called on calculate button and executes all E-series calculations.
Definition: eserie.cpp:289
double DoubleFromString(const wxString &TextValue)
void combine4(uint32_t aSize)
Check if there is a better four component solution.
Definition: eserie.cpp:76
#define _(s)
Definition: 3d_actions.cpp:33
std::array< r_data, S4R+1 > get_rslt(void)
Definition: eserie.h:150
void strip3(void)
Definition: eserie.cpp:249
uint32_t m_series
Definition: eserie.h:232
void simple_solution(uint32_t aSize)
Search for closest two component solution.
Definition: eserie.cpp:58
uint32_t combine2(void)
Build all 2R combinations from the selected E-serie values.
Definition: eserie.cpp:146
wxString eseries_help
Definition: eserie.cpp:39
Definition: eserie.h:39
std::vector< std::vector< r_data > > luts
Definition: eserie.h:207