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.

479 lines
15 KiB

  1. /*
  2. * This program source code file
  3. * is part of KiCad, a free EDA CAD application.
  4. *
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include "resistor_substitution_utils.h"
  21. #include <algorithm>
  22. #include <cmath>
  23. #include <functional>
  24. #include <stdexcept>
  25. // If BENCHMARK is defined, calculations will print their execution time to the STDERR
  26. // #define BENCHMARK
  27. #ifdef BENCHMARK
  28. #include <core/profile.h>
  29. #endif
  30. // Comparison operators used by std::sort and std::lower_bound
  31. bool operator<( const RESISTANCE& aLhs, double aRhs )
  32. {
  33. return aLhs.value < aRhs;
  34. }
  35. bool operator<( const RESISTANCE& aLhs, const RESISTANCE& aRhs )
  36. {
  37. return aLhs.value < aRhs.value;
  38. }
  39. class SolutionCollector
  40. /**
  41. * Helper class that collects solutions and keeps one with the best deviation.
  42. * In order to avoid performing costly string operations too frequently,
  43. * they are postponed until the very end, when we know the best combination.
  44. */
  45. {
  46. public:
  47. SolutionCollector( double aTarget ) : m_target( aTarget ) {}
  48. /**
  49. * Add two solutions, based on single 2R buffer lookup, to the collector.
  50. *
  51. * @param aResults are the resistances found in 2R buffer
  52. * @param aValueFunc transforms value from aResults into final value of the combination
  53. * @param aResultFunc transforms RESISTANCE instance from aResults into final instance
  54. */
  55. void Add2RLookupResults( std::pair<RESISTANCE&, RESISTANCE&> aResults,
  56. std::function<double( double )> aValueFunc,
  57. std::function<RESISTANCE( RESISTANCE& )> aResultFunc )
  58. {
  59. addSolution( aValueFunc( aResults.first.value ), &aResults.first, aResultFunc );
  60. addSolution( aValueFunc( aResults.second.value ), &aResults.second, aResultFunc );
  61. }
  62. /**
  63. * Return the best collected combination, running the corresponding result_func.
  64. */
  65. RESISTANCE GetBest()
  66. {
  67. if( !m_best_found_resistance )
  68. throw std::logic_error( "Empty solution collector" );
  69. return m_best_result_func( *m_best_found_resistance );
  70. }
  71. private:
  72. /**
  73. * Add single solution to the collector.
  74. *
  75. * @param aValue is a value of the combination in Ohms
  76. * @param aFound is the corresponding RESISTANCE found in 2R buffer
  77. * @param aResultFunc is a function calculating final result (RESISTANCE instance)
  78. * for this combination
  79. */
  80. void addSolution( double aValue, RESISTANCE* aFound,
  81. std::function<RESISTANCE( RESISTANCE& )>& aResultFunc )
  82. {
  83. double deviation = std::abs( aValue - m_target );
  84. if( deviation < m_best_deviation )
  85. {
  86. m_best_deviation = deviation;
  87. m_best_found_resistance = aFound;
  88. m_best_result_func = aResultFunc;
  89. }
  90. }
  91. double m_target;
  92. double m_best_deviation = INFINITY;
  93. RESISTANCE* m_best_found_resistance = nullptr;
  94. std::function<RESISTANCE( RESISTANCE& )> m_best_result_func;
  95. };
  96. /**
  97. * If aText contains aRequiredSymbol as top-level (i.e. not in parentheses) operator,
  98. * return aText enclosed in parentheses.
  99. * Otherwise, return aText unmodified.
  100. */
  101. static std::string maybeEmbrace( const std::string& aText, char aRequiredSymbol )
  102. {
  103. bool shouldEmbrace = false;
  104. // scan for required top-level symbol
  105. int parenLevel = 0;
  106. for( char c : aText )
  107. {
  108. if( c == '(' )
  109. parenLevel++;
  110. else if( c == ')' )
  111. parenLevel--;
  112. else if( c == aRequiredSymbol && parenLevel == 0 )
  113. shouldEmbrace = true;
  114. }
  115. // embrace or not
  116. if( shouldEmbrace )
  117. return '(' + aText + ')';
  118. else
  119. return aText;
  120. }
  121. /**
  122. * Functions calculating values and text representations of serial and parallel combinations.
  123. * Functions marked as 'Simple' do not care about parentheses, which makes them faster.
  124. */
  125. static inline double serialValue( double aR1, double aR2 )
  126. {
  127. return aR1 + aR2;
  128. }
  129. static inline double parallelValue( double aR1, double aR2 )
  130. {
  131. return aR1 * aR2 / ( aR1 + aR2 );
  132. }
  133. static inline RESISTANCE serialResistance( const RESISTANCE& aR1, const RESISTANCE& aR2 )
  134. {
  135. std::string name = maybeEmbrace( aR1.name, '|' ) + " + " + maybeEmbrace( aR2.name, '|' );
  136. return RESISTANCE( serialValue( aR1.value, aR2.value ), name );
  137. }
  138. static inline RESISTANCE parallelResistance( const RESISTANCE& aR1, const RESISTANCE& aR2 )
  139. {
  140. std::string name = maybeEmbrace( aR1.name, '+' ) + " | " + maybeEmbrace( aR2.name, '+' );
  141. return RESISTANCE( parallelValue( aR1.value, aR2.value ), name );
  142. }
  143. static inline RESISTANCE serialResistanceSimple( const RESISTANCE& aR1, const RESISTANCE& aR2 )
  144. {
  145. std::string name = aR1.name + " + " + aR2.name;
  146. return RESISTANCE( serialValue( aR1.value, aR2.value ), name );
  147. }
  148. static inline RESISTANCE parallelResistanceSimple( const RESISTANCE& aR1, const RESISTANCE& aR2 )
  149. {
  150. std::string name = aR1.name + " | " + aR2.name;
  151. return RESISTANCE( parallelValue( aR1.value, aR2.value ), name );
  152. }
  153. // Return a string from aValue (aValue is expected in ohms).
  154. // If aValue < 1000 the returned string is aValue with unit = R.
  155. // If aValue >= 1000 the returned string is aValue/1000 with unit = K
  156. // with notation similar to 2K2.
  157. // If aValue >= 1e6 the returned string is aValue/1e6 with unit = M
  158. // with notation = 1M.
  159. static std::string strValue( double aValue )
  160. {
  161. std::string result;
  162. if( aValue < 1000.0 )
  163. {
  164. result = std::to_string( static_cast<int>( aValue ) );
  165. result += 'R';
  166. }
  167. else
  168. {
  169. double div = 1e3;
  170. char unit = 'K';
  171. if( aValue >= 1e6 )
  172. {
  173. div = 1e6;
  174. unit = 'M';
  175. }
  176. aValue /= div;
  177. int valueAsInt = static_cast<int>( aValue );
  178. result = std::to_string( valueAsInt );
  179. result += unit;
  180. // Add mantissa: 1 digit, suitable for series up to E24
  181. double mantissa = aValue - valueAsInt;
  182. if( mantissa > 0 )
  183. result += std::to_string( lround( mantissa * 10 ) );
  184. }
  185. return result;
  186. }
  187. RES_EQUIV_CALC::RES_EQUIV_CALC()
  188. {
  189. // series must be added to vector in correct order
  190. m_e_series.push_back( buildSeriesData( ESERIES::E1_VALUES() ) );
  191. m_e_series.push_back( buildSeriesData( ESERIES::E3_VALUES() ) );
  192. m_e_series.push_back( buildSeriesData( ESERIES::E6_VALUES() ) );
  193. m_e_series.push_back( buildSeriesData( ESERIES::E12_VALUES() ) );
  194. m_e_series.push_back( buildSeriesData( ESERIES::E24_VALUES() ) );
  195. }
  196. void RES_EQUIV_CALC::SetSeries( uint32_t aSeries )
  197. {
  198. m_series = aSeries;
  199. }
  200. void RES_EQUIV_CALC::NewCalc( double aTargetValue )
  201. {
  202. m_target = aTargetValue;
  203. m_exclude_mask.resize( m_e_series[m_series].size() );
  204. std::fill( m_exclude_mask.begin(), m_exclude_mask.end(), false );
  205. std::fill( m_results.begin(), m_results.end(), std::nullopt );
  206. }
  207. void RES_EQUIV_CALC::Exclude( double aValue )
  208. {
  209. if( std::isnan( aValue ) )
  210. return;
  211. std::vector<RESISTANCE>& series = m_e_series[m_series];
  212. auto it = std::lower_bound( series.begin(), series.end(), aValue - epsilon );
  213. if( it != series.end() && std::abs( it->value - aValue ) < epsilon )
  214. m_exclude_mask[it - series.begin()] = true;
  215. }
  216. void RES_EQUIV_CALC::Calculate()
  217. {
  218. #ifdef BENCHMARK
  219. PROF_TIMER timer( "Resistor calculation" );
  220. #endif
  221. prepare1RBuffer();
  222. prepare2RBuffer();
  223. RESISTANCE solution_2r = calculate2RSolution();
  224. m_results[S2R] = solution_2r;
  225. if( std::abs( solution_2r.value - m_target ) > epsilon )
  226. {
  227. RESISTANCE solution_3r = calculate3RSolution();
  228. m_results[S3R] = solution_3r;
  229. if( std::abs( solution_3r.value - m_target ) > epsilon )
  230. m_results[S4R] = calculate4RSolution();
  231. }
  232. #ifdef BENCHMARK
  233. timer.Show();
  234. #endif
  235. }
  236. std::vector<RESISTANCE> RES_EQUIV_CALC::buildSeriesData( const ESERIES::ESERIES_VALUES& aList )
  237. {
  238. std::vector<RESISTANCE> result_list;
  239. for( double curr_decade = RES_EQUIV_CALC_FIRST_VALUE;; curr_decade *= 10.0 ) // iterate over decades
  240. {
  241. double multiplier = curr_decade / aList[0];
  242. for( const uint16_t listvalue : aList ) // iterate over values in decade
  243. {
  244. double value = multiplier * listvalue;
  245. result_list.emplace_back( value, strValue( value ) );
  246. if( value >= RES_EQUIV_CALC_LAST_VALUE )
  247. return result_list;
  248. }
  249. }
  250. }
  251. void RES_EQUIV_CALC::prepare1RBuffer()
  252. {
  253. std::vector<RESISTANCE>& series = m_e_series[m_series];
  254. m_buffer_1R.clear();
  255. for( size_t i = 0; i < series.size(); i++ )
  256. {
  257. if( !m_exclude_mask[i] )
  258. m_buffer_1R.push_back( series[i] );
  259. }
  260. }
  261. void RES_EQUIV_CALC::prepare2RBuffer()
  262. {
  263. m_buffer_2R.clear();
  264. for( size_t i1 = 0; i1 < m_buffer_1R.size(); i1++ )
  265. {
  266. for( size_t i2 = i1; i2 < m_buffer_1R.size(); i2++ )
  267. {
  268. m_buffer_2R.push_back( serialResistanceSimple( m_buffer_1R[i1], m_buffer_1R[i2] ) );
  269. m_buffer_2R.push_back( parallelResistanceSimple( m_buffer_1R[i1], m_buffer_1R[i2] ) );
  270. }
  271. }
  272. std::sort( m_buffer_2R.begin(), m_buffer_2R.end() );
  273. }
  274. std::pair<RESISTANCE&, RESISTANCE&> RES_EQUIV_CALC::findIn2RBuffer( double aTarget )
  275. {
  276. // in case of NaN, return anything valid
  277. if( std::isnan( aTarget ) )
  278. return { m_buffer_2R[0], m_buffer_2R[0] };
  279. // target value is often too small or too big, so check that manually
  280. if( aTarget <= m_buffer_2R.front().value || aTarget >= m_buffer_2R.back().value )
  281. return { m_buffer_2R.front(), m_buffer_2R.back() };
  282. auto it = std::lower_bound( m_buffer_2R.begin(), m_buffer_2R.end(), aTarget )
  283. - m_buffer_2R.begin();
  284. if( it == 0 )
  285. return { m_buffer_2R[0], m_buffer_2R[0] };
  286. else if( it == m_buffer_2R.size() )
  287. return { m_buffer_2R[it - 1], m_buffer_2R[it - 1] };
  288. else
  289. return { m_buffer_2R[it - 1], m_buffer_2R[it] };
  290. }
  291. RESISTANCE RES_EQUIV_CALC::calculate2RSolution()
  292. {
  293. SolutionCollector solution( m_target );
  294. auto valueFunc = []( double aFoundValue )
  295. {
  296. return aFoundValue;
  297. };
  298. auto resultFunc = []( RESISTANCE& aFoundRes )
  299. {
  300. return aFoundRes;
  301. };
  302. solution.Add2RLookupResults( findIn2RBuffer( m_target ), valueFunc, resultFunc );
  303. return solution.GetBest();
  304. }
  305. RESISTANCE RES_EQUIV_CALC::calculate3RSolution()
  306. {
  307. SolutionCollector solution( m_target );
  308. for( RESISTANCE& r : m_buffer_1R )
  309. {
  310. // try r + 2R combination
  311. {
  312. auto valueFunc = [&]( double aFoundValue )
  313. {
  314. return serialValue( aFoundValue, r.value );
  315. };
  316. auto resultFunc = [&]( RESISTANCE& aFoundRes )
  317. {
  318. return serialResistance( aFoundRes, r );
  319. };
  320. solution.Add2RLookupResults( findIn2RBuffer( m_target - r.value ), valueFunc,
  321. resultFunc );
  322. }
  323. // try r | 2R combination
  324. {
  325. auto valueFunc = [&]( double aFoundValue )
  326. {
  327. return parallelValue( aFoundValue, r.value );
  328. };
  329. auto resultFunc = [&]( RESISTANCE& aFoundRes )
  330. {
  331. return parallelResistance( aFoundRes, r );
  332. };
  333. solution.Add2RLookupResults(
  334. findIn2RBuffer( m_target * r.value / ( r.value - m_target ) ), valueFunc,
  335. resultFunc );
  336. }
  337. }
  338. return solution.GetBest();
  339. }
  340. RESISTANCE RES_EQUIV_CALC::calculate4RSolution()
  341. {
  342. SolutionCollector solution( m_target );
  343. for( RESISTANCE& rr : m_buffer_2R )
  344. {
  345. // try 2R + 2R combination
  346. {
  347. auto valueFunc = [&]( double aFoundValue )
  348. {
  349. return serialValue( aFoundValue, rr.value );
  350. };
  351. auto resultFunc = [&]( RESISTANCE& aFoundRes )
  352. {
  353. return serialResistance( aFoundRes, rr );
  354. };
  355. solution.Add2RLookupResults( findIn2RBuffer( m_target - rr.value ), valueFunc,
  356. resultFunc );
  357. }
  358. // try 2R | 2R combination
  359. {
  360. auto valueFunc = [&]( double aFoundValue )
  361. {
  362. return parallelValue( aFoundValue, rr.value );
  363. };
  364. auto resultFunc = [&]( RESISTANCE& aFoundRes )
  365. {
  366. return parallelResistance( aFoundRes, rr );
  367. };
  368. solution.Add2RLookupResults(
  369. findIn2RBuffer( m_target * rr.value / ( rr.value - m_target ) ), valueFunc,
  370. resultFunc );
  371. }
  372. }
  373. for( RESISTANCE& r1 : m_buffer_1R )
  374. {
  375. for( RESISTANCE& r2 : m_buffer_1R )
  376. {
  377. // try r1 + (r2 | 2R)
  378. {
  379. auto valueFunc = [&]( double aFoundValue )
  380. {
  381. return serialValue( r1.value, parallelValue( r2.value, aFoundValue ) );
  382. };
  383. auto resultFunc = [&]( RESISTANCE& aFoundRes )
  384. {
  385. return serialResistance( r1, parallelResistance( r2, aFoundRes ) );
  386. };
  387. solution.Add2RLookupResults( findIn2RBuffer( ( m_target - r1.value ) * r2.value
  388. / ( r1.value + r2.value - m_target ) ),
  389. valueFunc, resultFunc );
  390. }
  391. // try r1 | (r2 + 2R)
  392. {
  393. auto valueFunc = [&]( double aFoundValue )
  394. {
  395. return parallelValue( r1.value, serialValue( r2.value, aFoundValue ) );
  396. };
  397. auto resultFunc = [&]( RESISTANCE& aFoundRes )
  398. {
  399. return parallelResistance( r1, serialResistance( r2, aFoundRes ) );
  400. };
  401. solution.Add2RLookupResults(
  402. findIn2RBuffer( m_target * r1.value / ( r1.value - m_target ) - r2.value ),
  403. valueFunc, resultFunc );
  404. }
  405. }
  406. }
  407. return solution.GetBest();
  408. }