You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

513 lines
18 KiB

  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. #include <array>
  22. #include <algorithm>
  23. #include <calculator_panels/panel_eserie.h>
  24. #include <wx/msgdlg.h>
  25. /* If BENCHMARK is defined, any 4R E12 calculations will print its execution time to console
  26. * My Hasswell Enthusiast reports 225 mSec what are reproducible within plusminus 2 percent
  27. */
  28. //#define BENCHMARK
  29. #ifdef BENCHMARK
  30. #include <profile.h>
  31. #endif
  32. #include "eserie.h"
  33. extern double DoubleFromString( const wxString& TextValue );
  34. E_SERIE r;
  35. // Return a string from aValue (aValue is expected in ohms)
  36. // If aValue < 1000 the returned string is aValue with unit = R
  37. // If aValue >= 1000 the returned string is aValue/1000 with unit = K
  38. // with notation similar to 2K2
  39. // If aValue >= 1e6 the returned string is aValue/1e6 with unit = M
  40. // with notation = 1M
  41. static std::string strValue( double aValue )
  42. {
  43. std::string result;
  44. if( aValue < 1000.0 )
  45. {
  46. result = std::to_string( static_cast<int>( aValue ) );
  47. result += 'R';
  48. }
  49. else
  50. {
  51. double div = 1e3;
  52. int unit = 'K';
  53. if( aValue >= 1e6 )
  54. {
  55. div = 1e6;
  56. unit = 'M';
  57. }
  58. aValue /= div;
  59. int integer = static_cast<int>( aValue );
  60. result = std::to_string(integer);
  61. result += unit;
  62. // Add mantissa: 1 digit, suitable for series up to E24
  63. double mantissa = aValue - integer;
  64. if( mantissa > 0 )
  65. result += std::to_string( static_cast<int>( (mantissa*10)+0.5 ) );
  66. }
  67. return result;
  68. }
  69. E_SERIE::E_SERIE()
  70. {
  71. // Build the list of available resistor values in each En serie
  72. double listValuesE1[] = { E1_VALUES };
  73. double listValuesE3[] = { E3_VALUES };
  74. double listValuesE6[] = { E6_VALUES };
  75. double listValuesE12[] = { E12_VALUES };
  76. double listValuesE24[] = { E24_VALUES };
  77. // buildSerieData must be called in the order of En series, because
  78. // the list of series is expected indexed by En for the serie En
  79. buildSerieData( E1, listValuesE1 );
  80. buildSerieData( E3, listValuesE3 );
  81. buildSerieData( E6, listValuesE6 );
  82. buildSerieData( E12, listValuesE12 );
  83. int count = buildSerieData( E24, listValuesE24 );
  84. // Reserve a buffer for intermediate calculations:
  85. // the buffer size is 2*count*count to store all combinaisons of 2 values
  86. // there are 2*count*count = 29282 combinations for E24
  87. int bufsize = 2*count*count;
  88. m_cmb_lut.reserve( bufsize );
  89. // Store predefined R_DATA items.
  90. for( int ii = 0; ii < bufsize; ii++ )
  91. m_cmb_lut.emplace_back( "", 0.0 );
  92. }
  93. int E_SERIE::buildSerieData( int aEserie, double aList[] )
  94. {
  95. double curr_coeff = FIRST_VALUE;
  96. int count = 0;
  97. std::vector<R_DATA> curr_list;
  98. for( ; ; )
  99. {
  100. double curr_r = curr_coeff;
  101. for( int ii = 0; ; ii++ )
  102. {
  103. if( aList[ii] == 0.0 ) // End of list
  104. break;
  105. double curr_r = curr_coeff * aList[ii];
  106. curr_list.emplace_back( strValue( curr_r ), curr_r );
  107. count++;
  108. if( curr_r >= LAST_VALUE )
  109. break;
  110. }
  111. if( curr_r >= LAST_VALUE )
  112. break;
  113. curr_coeff *= 10;
  114. }
  115. m_luts.push_back( std::move( curr_list ) );
  116. return count;
  117. }
  118. void E_SERIE::Exclude( double aValue )
  119. {
  120. if( aValue ) // if there is a value to exclude other than a wire jumper
  121. {
  122. for( R_DATA& i : m_luts[m_series] ) // then search it in the selected E-Serie lookup table
  123. {
  124. if( i.e_value == aValue ) // if the value to exclude is found
  125. i.e_use = false; // disable its use
  126. }
  127. }
  128. }
  129. void E_SERIE::simple_solution( uint32_t aSize )
  130. {
  131. uint32_t i;
  132. m_results.at( S2R ).e_value = std::numeric_limits<double>::max(); // assume no 2R solution or max deviation
  133. for( i = 0; i < aSize; i++ )
  134. {
  135. if( abs( m_cmb_lut.at( i ).e_value - m_required_value ) < abs( m_results.at( S2R ).e_value ) )
  136. {
  137. m_results.at( S2R ).e_value = m_cmb_lut.at( i ).e_value - m_required_value; // save signed deviation in Ohms
  138. m_results.at( S2R ).e_name = m_cmb_lut.at( i ).e_name; // save combination text
  139. m_results.at( S2R ).e_use = true; // this is a possible solution
  140. }
  141. }
  142. }
  143. void E_SERIE::combine4( uint32_t aSize )
  144. {
  145. uint32_t i,j;
  146. double tmp;
  147. std::string s;
  148. m_results.at( S4R ).e_use = false; // disable 4R solution, until
  149. m_results.at( S4R ).e_value = m_results.at( S3R ).e_value; // 4R becomes better than 3R solution
  150. #ifdef BENCHMARK
  151. PROF_COUNTER timer; // start timer to count execution time
  152. #endif
  153. for( i = 0; i < aSize; i++ ) // 4R search outer loop
  154. { // scan valid intermediate 2R solutions
  155. for( j = 0; j < aSize; j++ ) // inner loop combines all with itself
  156. {
  157. tmp = m_cmb_lut.at( i ).e_value + m_cmb_lut.at( j ).e_value; // calculate 2R+2R serial
  158. tmp -= m_required_value; // calculate 4R deviation
  159. if( abs( tmp ) < abs( m_results.at(S4R).e_value ) ) // if new 4R is better
  160. {
  161. m_results.at( S4R ).e_value = tmp; // save amount of benefit
  162. std::string s = "( ";
  163. s.append( m_cmb_lut.at( i ).e_name ); // mention 1st 2 component
  164. s.append( " ) + ( " ); // in series
  165. s.append( m_cmb_lut.at( j ).e_name ); // with 2nd 2 components
  166. s.append( " )" );
  167. m_results.at( S4R ).e_name = s; // save the result and
  168. m_results.at( S4R ).e_use = true; // enable for later use
  169. }
  170. tmp = ( m_cmb_lut[i].e_value * m_cmb_lut.at( j ).e_value ) /
  171. ( m_cmb_lut[i].e_value + m_cmb_lut.at( j ).e_value ); // calculate 2R|2R parallel
  172. tmp -= m_required_value; // calculate 4R deviation
  173. if( abs( tmp ) < abs( m_results.at( S4R ).e_value ) ) // if new 4R is better
  174. {
  175. m_results.at( S4R ).e_value = tmp; // save amount of benefit
  176. std::string s = "( ";
  177. s.append( m_cmb_lut.at( i ).e_name ); // mention 1st 2 component
  178. s.append( " ) | ( " ); // in parallel
  179. s.append( m_cmb_lut.at( j ).e_name ); // with 2nd 2 components
  180. s.append( " )" );
  181. m_results.at( S4R ).e_name = s; // save the result
  182. m_results.at( S4R ).e_use = true; // enable later use
  183. }
  184. }
  185. }
  186. #ifdef BENCHMARK
  187. printf( "Calculation time = %d mS", timer.msecs() );
  188. fflush( 0 );
  189. #endif
  190. }
  191. void E_SERIE::NewCalc()
  192. {
  193. for( R_DATA& i : m_cmb_lut )
  194. i.e_use = false; // before any calculation is done, assume that
  195. for( R_DATA& i : m_results )
  196. i.e_use = false; // no combinations and no results are available
  197. for( R_DATA& i : m_luts[m_series])
  198. i.e_use = true; // all selected E-values available
  199. }
  200. uint32_t E_SERIE::combine2()
  201. {
  202. uint32_t combi2R = 0; // target index counts calculated 2R combinations
  203. std::string s;
  204. for( const R_DATA& i : m_luts[m_series] ) // outer loop to sweep selected source lookup table
  205. {
  206. if( i.e_use )
  207. {
  208. for( const R_DATA& j : m_luts[m_series] ) // inner loop to combine values with itself
  209. {
  210. if( j.e_use )
  211. {
  212. m_cmb_lut.at( combi2R ).e_use = true;
  213. m_cmb_lut.at( combi2R ).e_value = i.e_value + j.e_value; // calculate 2R serial
  214. s = i.e_name;
  215. s.append( " + " );
  216. m_cmb_lut.at( combi2R ).e_name = s.append( j.e_name);
  217. combi2R++; // next destination
  218. m_cmb_lut.at( combi2R ).e_use = true; // calculate 2R parallel
  219. m_cmb_lut.at( combi2R ).e_value = i.e_value * j.e_value / ( i.e_value + j.e_value );
  220. s = i.e_name;
  221. s.append( " | " );
  222. m_cmb_lut.at( combi2R ).e_name = s.append( j.e_name );
  223. combi2R++; // next destination
  224. }
  225. }
  226. }
  227. }
  228. return combi2R;
  229. }
  230. void E_SERIE::combine3( uint32_t aSize )
  231. {
  232. uint32_t j = 0;
  233. double tmp = 0; // avoid warning for being uninitialized
  234. std::string s;
  235. m_results.at( S3R ).e_use = false; // disable 3R solution, until
  236. m_results.at( S3R ).e_value = m_results.at( S2R ).e_value; // 3R becomes better than 2R solution
  237. for( const R_DATA& i : m_luts[m_series] ) // 3R Outer loop to selected primary E serie LUT
  238. {
  239. if( i.e_use ) // skip all excluded values
  240. {
  241. for( j = 0; j < aSize; j++ ) // inner loop combines with all 2R intermediate results
  242. { // R+2R serial combi
  243. tmp = m_cmb_lut.at( j ).e_value + i.e_value;
  244. tmp -= m_required_value; // calculate deviation
  245. if( abs( tmp ) < abs( m_results.at( S3R ).e_value ) ) // compare if better
  246. { // then take it
  247. s = i.e_name; // mention 3rd component
  248. s.append( " + ( " ); // in series
  249. s.append( m_cmb_lut.at( j ).e_name ); // with 2R combination
  250. s.append( " )" );
  251. m_results.at( S3R ).e_name = s; // save S3R result
  252. m_results.at( S3R ).e_value = tmp; // save amount of benefit
  253. m_results.at( S3R ).e_use = true; // enable later use
  254. }
  255. tmp = i.e_value * m_cmb_lut.at( j ).e_value /
  256. ( i.e_value + m_cmb_lut.at( j ).e_value ); // calculate R + 2R parallel
  257. tmp -= m_required_value; // calculate deviation
  258. if( abs( tmp ) < abs( m_results.at( S3R ).e_value ) ) // compare if better
  259. { // then take it
  260. s = i.e_name; // mention 3rd component
  261. s.append( " | ( " ); // in parallel
  262. s.append( m_cmb_lut.at( j ).e_name ); // with 2R combination
  263. s.append( " )" );
  264. m_results.at( S3R ).e_name = s;
  265. m_results.at( S3R ).e_value = tmp; // save amount of benefit
  266. m_results.at( S3R ).e_use = true; // enable later use
  267. }
  268. }
  269. }
  270. }
  271. // If there is a 3R result with remaining deviation consider to search a possibly better 4R solution
  272. // calculate 4R for small series always
  273. if(( m_results.at( S3R ).e_use == true ) && tmp )
  274. combine4( aSize );
  275. }
  276. void E_SERIE::Calculate()
  277. {
  278. uint32_t no_of_2Rcombi = 0;
  279. no_of_2Rcombi = combine2(); // combine all 2R combinations for selected E serie
  280. simple_solution( no_of_2Rcombi ); // search for simple 2 component solution
  281. if( m_results.at( S2R ).e_value ) // if simple 2R result is not exact
  282. combine3( no_of_2Rcombi ); // continiue searching for a possibly better solution
  283. strip3();
  284. strip4();
  285. }
  286. void E_SERIE::strip3()
  287. {
  288. std::string s;
  289. if( m_results.at( S3R ).e_use ) // if there is a 3 term result available
  290. { // what is connected either by two "|" or by 3 plus
  291. s = m_results.at( S3R ).e_name;
  292. if( ( std::count( s.begin(), s.end(), '+' ) == 2 )
  293. || ( std::count( s.begin(), s.end(), '|' ) == 2 ) )
  294. { // then strip one pair of braces
  295. s.erase( s.find( "(" ), 1 ); // it is known sure, this is available
  296. s.erase( s.find( ")" ), 1 ); // in any unstripped 3R result term
  297. m_results.at( S3R ).e_name = s; // use stripped result
  298. }
  299. }
  300. }
  301. void E_SERIE::strip4()
  302. {
  303. std::string s;
  304. if( m_results.at( S4R ).e_use ) // if there is a 4 term result available
  305. { // what are connected either by 3 "+" or by 3 "|"
  306. s = m_results.at( S4R ).e_name;
  307. if( ( std::count( s.begin(), s.end(), '+' ) == 3 )
  308. || ( std::count( s.begin(), s.end(), '|' ) == 3 ) )
  309. { // then strip two pair of braces
  310. s.erase( s.find( "(" ), 1 ); // it is known sure, they are available
  311. s.erase( s.find( ")" ), 1 ); // in any unstripped 4R result term
  312. s.erase( s.find( "(" ), 1 );
  313. s.erase( s.find( ")" ), 1 );
  314. m_results.at( S4R ).e_name = s; // use stripped result
  315. }
  316. }
  317. }
  318. void PANEL_E_SERIE::OnCalculateESeries( wxCommandEvent& event )
  319. {
  320. double reqr; // required resistor stored in local copy
  321. double error, err3 = 0;
  322. wxString es, fs; // error and formula strings
  323. wxBusyCursor dummy;
  324. reqr = ( 1000 * DoubleFromString( m_ResRequired->GetValue() ) );
  325. r.SetRequiredValue( reqr ); // keep a local copy of required resistor value
  326. r.NewCalc(); // assume all values available
  327. /*
  328. * Exclude itself. For the case, a value from the available series is found as required value,
  329. * the calculator assumes this value needs a replacement for the reason of being not available.
  330. * Two further exclude values can be entered to exclude and are skipped as not being available.
  331. * All values entered in KiloOhms are converted to Ohm for internal calculation
  332. */
  333. r.Exclude( 1000 * DoubleFromString( m_ResRequired->GetValue()));
  334. r.Exclude( 1000 * DoubleFromString( m_ResExclude1->GetValue()));
  335. r.Exclude( 1000 * DoubleFromString( m_ResExclude2->GetValue()));
  336. try
  337. {
  338. r.Calculate();
  339. }
  340. catch (std::out_of_range const& exc)
  341. {
  342. wxString msg;
  343. msg << "Internal error: " << exc.what();
  344. wxMessageBox( msg );
  345. return;
  346. }
  347. fs = r.GetResults()[S2R].e_name; // show 2R solution formula string
  348. m_ESeries_Sol2R->SetValue( fs );
  349. error = reqr + r.GetResults()[S2R].e_value; // absolute value of solution
  350. error = ( reqr / error - 1 ) * 100; // error in percent
  351. if( error )
  352. {
  353. if( std::abs( error ) < 0.01 )
  354. es.Printf( "<%.2f", 0.01 );
  355. else
  356. es.Printf( "%+.2f",error);
  357. }
  358. else
  359. {
  360. es = _( "Exact" );
  361. }
  362. m_ESeriesError2R->SetValue( es ); // anyway show 2R error string
  363. if( r.GetResults()[S3R].e_use ) // if 3R solution available
  364. {
  365. err3 = reqr + r.GetResults()[S3R].e_value; // calculate the 3R
  366. err3 = ( reqr / err3 - 1 ) * 100; // error in percent
  367. if( err3 )
  368. {
  369. if( std::abs( err3 ) < 0.01 )
  370. es.Printf( "<%.2f", 0.01 );
  371. else
  372. es.Printf( "%+.2f",err3);
  373. }
  374. else
  375. {
  376. es = _( "Exact" );
  377. }
  378. m_ESeriesError3R->SetValue( es ); // show 3R error string
  379. fs = r.GetResults()[S3R].e_name;
  380. m_ESeries_Sol3R->SetValue( fs ); // show 3R formula string
  381. }
  382. else // nothing better than 2R found
  383. {
  384. fs = _( "Not worth using" );
  385. m_ESeries_Sol3R->SetValue( fs );
  386. m_ESeriesError3R->SetValue( wxEmptyString );
  387. }
  388. fs = wxEmptyString;
  389. if( r.GetResults()[S4R].e_use ) // show 4R solution if available
  390. {
  391. fs = r.GetResults()[S4R].e_name;
  392. error = reqr + r.GetResults()[S4R].e_value; // absolute value of solution
  393. error = ( reqr / error - 1 ) * 100; // error in percent
  394. if( error )
  395. es.Printf( "%+.2f",error );
  396. else
  397. es = _( "Exact" );
  398. m_ESeriesError4R->SetValue( es );
  399. }
  400. else // no 4R solution
  401. {
  402. fs = _( "Not worth using" );
  403. es = wxEmptyString;
  404. m_ESeriesError4R->SetValue( es );
  405. }
  406. m_ESeries_Sol4R->SetValue( fs );
  407. }
  408. void PANEL_E_SERIE::OnESeriesSelection( wxCommandEvent& event )
  409. {
  410. if( event.GetEventObject() == m_e1 )
  411. r.SetSeries( E1 );
  412. else if( event.GetEventObject() == m_e3 )
  413. r.SetSeries( E3 );
  414. else if( event.GetEventObject() == m_e12 )
  415. r.SetSeries( E12 );
  416. else if( event.GetEventObject() == m_e24 )
  417. r.SetSeries( E24 );
  418. else
  419. r.SetSeries( E6 );
  420. }