KiCad PCB EDA Suite
Loading...
Searching...
No Matches
resistor_substitution_utils.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 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
22#include <algorithm>
23#include <cmath>
24#include <functional>
25#include <stdexcept>
26#include <optional>
27
28// If BENCHMARK is defined, calculations will print their execution time to the STDERR
29// #define BENCHMARK
30
31#ifdef BENCHMARK
32#include <core/profile.h>
33#endif
34
35// Comparison operators used by std::sort and std::lower_bound
36bool operator<( const RESISTANCE& aLhs, double aRhs )
37{
38 return aLhs.value < aRhs;
39}
40
41bool operator<( const RESISTANCE& aLhs, const RESISTANCE& aRhs )
42{
43 return aLhs.value < aRhs.value;
44}
45
52{
53public:
54 SolutionCollector( double aTarget ) :
55 m_target( aTarget )
56 {
57 }
58
66 void Add2RLookupResults( std::pair<RESISTANCE&, RESISTANCE&> aResults, std::function<double( double )> aValueFunc,
67 std::function<RESISTANCE( RESISTANCE& )> aResultFunc )
68 {
69 considerSolution( aValueFunc( aResults.first.value ), aResults.first, aResultFunc );
70 considerSolution( aValueFunc( aResults.second.value ), aResults.second, aResultFunc );
71 }
72
77 {
78 if( !m_best_solution )
79 throw std::logic_error( "Empty solution collector" );
80
81 return *m_best_solution;
82 }
83
84private:
85 void considerSolution( double aValue, RESISTANCE& aFound, std::function<RESISTANCE( RESISTANCE& )>& aResultFunc )
86 {
87 double deviation = std::abs( aValue - m_target );
88
89 if( deviation + epsilon < m_best_deviation )
90 {
91 m_best_deviation = deviation;
92 m_best_solution = aResultFunc( aFound );
93 }
94 else if( std::abs( deviation - m_best_deviation ) < epsilon )
95 {
96 RESISTANCE candidate = aResultFunc( aFound );
97
98 if( !m_best_solution || betterCandidate( candidate, *m_best_solution ) )
99 m_best_solution = std::move( candidate );
100 }
101 }
102
103 static int uniqueCount( const RESISTANCE& aRes )
104 {
105 std::vector<double> parts = aRes.parts;
106 std::sort( parts.begin(), parts.end() );
107
108 int count = 0;
109 double last = 0.0;
110 bool first = true;
111
112 for( double v : parts )
113 {
114 if( first || std::abs( v - last ) > epsilon )
115 {
116 count++;
117 last = v;
118 first = false;
119 }
120 }
121
122 return count;
123 }
124
125 static bool betterCandidate( const RESISTANCE& aCand, const RESISTANCE& aBest )
126 {
127 int candUnique = uniqueCount( aCand );
128 int bestUnique = uniqueCount( aBest );
129
130 if( candUnique != bestUnique )
131 return candUnique < bestUnique;
132
133 if( aCand.parts.size() != aBest.parts.size() )
134 return aCand.parts.size() < aBest.parts.size();
135
136 return aCand.name < aBest.name;
137 }
138
139 double m_target;
141 std::optional<RESISTANCE> m_best_solution;
142};
143
149static std::string maybeEmbrace( const std::string& aText, char aRequiredSymbol )
150{
151 bool shouldEmbrace = false;
152
153 // scan for required top-level symbol
154 int parenLevel = 0;
155
156 for( char c : aText )
157 {
158 if( c == '(' )
159 parenLevel++;
160 else if( c == ')' )
161 parenLevel--;
162 else if( c == aRequiredSymbol && parenLevel == 0 )
163 shouldEmbrace = true;
164 }
165
166 // embrace or not
167 if( shouldEmbrace )
168 return '(' + aText + ')';
169 else
170 return aText;
171}
172
178static inline double serialValue( double aR1, double aR2 )
179{
180 return aR1 + aR2;
181}
182
183static inline double parallelValue( double aR1, double aR2 )
184{
185 return aR1 * aR2 / ( aR1 + aR2 );
186}
187
188static inline RESISTANCE serialResistance( const RESISTANCE& aR1, const RESISTANCE& aR2 )
189{
190 std::string name = maybeEmbrace( aR1.name, '|' ) + " + " + maybeEmbrace( aR2.name, '|' );
191 std::vector<double> parts = aR1.parts;
192 parts.insert( parts.end(), aR2.parts.begin(), aR2.parts.end() );
193 return RESISTANCE( serialValue( aR1.value, aR2.value ), name, parts );
194}
195
196static inline RESISTANCE parallelResistance( const RESISTANCE& aR1, const RESISTANCE& aR2 )
197{
198 std::string name = maybeEmbrace( aR1.name, '+' ) + " | " + maybeEmbrace( aR2.name, '+' );
199 std::vector<double> parts = aR1.parts;
200 parts.insert( parts.end(), aR2.parts.begin(), aR2.parts.end() );
201 return RESISTANCE( parallelValue( aR1.value, aR2.value ), name, parts );
202}
203
204static inline RESISTANCE serialResistanceSimple( const RESISTANCE& aR1, const RESISTANCE& aR2 )
205{
206 std::string name = aR1.name + " + " + aR2.name;
207 std::vector<double> parts = aR1.parts;
208 parts.insert( parts.end(), aR2.parts.begin(), aR2.parts.end() );
209 return RESISTANCE( serialValue( aR1.value, aR2.value ), name, parts );
210}
211
212static inline RESISTANCE parallelResistanceSimple( const RESISTANCE& aR1, const RESISTANCE& aR2 )
213{
214 std::string name = aR1.name + " | " + aR2.name;
215 std::vector<double> parts = aR1.parts;
216 parts.insert( parts.end(), aR2.parts.begin(), aR2.parts.end() );
217 return RESISTANCE( parallelValue( aR1.value, aR2.value ), name, parts );
218}
219
220// Return a string from aValue (aValue is expected in ohms).
221// If aValue < 1000 the returned string is aValue with unit = R.
222// If aValue >= 1000 the returned string is aValue/1000 with unit = K
223// with notation similar to 2K2.
224// If aValue >= 1e6 the returned string is aValue/1e6 with unit = M
225// with notation = 1M.
226static std::string strValue( double aValue )
227{
228 std::string result;
229
230 if( aValue < 1000.0 )
231 {
232 result = std::to_string( static_cast<int>( aValue ) );
233 result += 'R';
234 }
235 else
236 {
237 double div = 1e3;
238 char unit = 'K';
239
240 if( aValue >= 1e6 )
241 {
242 div = 1e6;
243 unit = 'M';
244 }
245
246 aValue /= div;
247
248 int valueAsInt = static_cast<int>( aValue );
249 result = std::to_string( valueAsInt );
250 result += unit;
251
252 // Add mantissa: 1 digit, suitable for series up to E24
253 double mantissa = aValue - valueAsInt;
254
255 if( mantissa > 0 )
256 result += std::to_string( lround( mantissa * 10 ) );
257 }
258
259 return result;
260}
261
262
264{
265 // series must be added to vector in correct order
271}
272
273void RES_EQUIV_CALC::SetSeries( uint32_t aSeries )
274{
275 m_series = aSeries;
276}
277
278void RES_EQUIV_CALC::NewCalc( double aTargetValue )
279{
280 m_target = aTargetValue;
281
282 m_exclude_mask.resize( m_e_series[m_series].size() );
283 std::fill( m_exclude_mask.begin(), m_exclude_mask.end(), false );
284
285 std::fill( m_results.begin(), m_results.end(), std::nullopt );
286}
287
288void RES_EQUIV_CALC::Exclude( double aValue )
289{
290 if( std::isnan( aValue ) )
291 return;
292
293 std::vector<RESISTANCE>& series = m_e_series[m_series];
294 auto it = std::lower_bound( series.begin(), series.end(), aValue - epsilon );
295
296 if( it != series.end() && std::abs( it->value - aValue ) < epsilon )
297 m_exclude_mask[it - series.begin()] = true;
298}
299
301{
302#ifdef BENCHMARK
303 PROF_TIMER timer( "Resistor calculation" );
304#endif
305
308
309 RESISTANCE solution_2r = calculate2RSolution();
310 m_results[S2R] = solution_2r;
311
312 if( std::abs( solution_2r.value - m_target ) > epsilon )
313 {
314 RESISTANCE solution_3r = calculate3RSolution();
315 m_results[S3R] = solution_3r;
316
317 if( std::abs( solution_3r.value - m_target ) > epsilon )
319 }
320
321#ifdef BENCHMARK
322 timer.Show();
323#endif
324}
325
326std::vector<RESISTANCE> RES_EQUIV_CALC::buildSeriesData( const ESERIES::ESERIES_VALUES& aList )
327{
328 std::vector<RESISTANCE> result_list;
329
330 for( double curr_decade = RES_EQUIV_CALC_FIRST_VALUE;; curr_decade *= 10.0 ) // iterate over decades
331 {
332 double multiplier = curr_decade / aList[0];
333
334 for( const uint16_t listvalue : aList ) // iterate over values in decade
335 {
336 double value = multiplier * listvalue;
337 result_list.emplace_back( value, strValue( value ) );
338
339 if( value >= RES_EQUIV_CALC_LAST_VALUE )
340 return result_list;
341 }
342 }
343}
344
346{
347 std::vector<RESISTANCE>& series = m_e_series[m_series];
348 m_buffer_1R.clear();
349
350 for( size_t i = 0; i < series.size(); i++ )
351 {
352 if( !m_exclude_mask[i] )
353 m_buffer_1R.push_back( series[i] );
354 }
355}
356
358{
359 m_buffer_2R.clear();
360
361 for( size_t i1 = 0; i1 < m_buffer_1R.size(); i1++ )
362 {
363 for( size_t i2 = i1; i2 < m_buffer_1R.size(); i2++ )
364 {
367 }
368 }
369
370 std::sort( m_buffer_2R.begin(), m_buffer_2R.end() );
371}
372
373std::pair<RESISTANCE&, RESISTANCE&> RES_EQUIV_CALC::findIn2RBuffer( double aTarget )
374{
375 // in case of NaN, return anything valid
376 if( std::isnan( aTarget ) )
377 return { m_buffer_2R[0], m_buffer_2R[0] };
378
379 // target value is often too small or too big, so check that manually
380 if( aTarget <= m_buffer_2R.front().value || aTarget >= m_buffer_2R.back().value )
381 return { m_buffer_2R.front(), m_buffer_2R.back() };
382
383 auto it = std::lower_bound( m_buffer_2R.begin(), m_buffer_2R.end(), aTarget ) - m_buffer_2R.begin();
384
385 if( it == 0 )
386 return { m_buffer_2R[0], m_buffer_2R[0] };
387 else if( it == m_buffer_2R.size() )
388 return { m_buffer_2R[it - 1], m_buffer_2R[it - 1] };
389 else
390 return { m_buffer_2R[it - 1], m_buffer_2R[it] };
391}
392
394{
395 SolutionCollector solution( m_target );
396
397 auto valueFunc = []( double aFoundValue )
398 {
399 return aFoundValue;
400 };
401 auto resultFunc = []( RESISTANCE& aFoundRes )
402 {
403 return aFoundRes;
404 };
405 solution.Add2RLookupResults( findIn2RBuffer( m_target ), valueFunc, resultFunc );
406
407 return solution.GetBest();
408}
409
411{
412 SolutionCollector solution( m_target );
413
414 for( RESISTANCE& r : m_buffer_1R )
415 {
416 // try r + 2R combination
417 {
418 auto valueFunc = [&]( double aFoundValue )
419 {
420 return serialValue( aFoundValue, r.value );
421 };
422 auto resultFunc = [&]( RESISTANCE& aFoundRes )
423 {
424 return serialResistance( aFoundRes, r );
425 };
426 solution.Add2RLookupResults( findIn2RBuffer( m_target - r.value ), valueFunc, resultFunc );
427 }
428
429 // try r | 2R combination
430 {
431 auto valueFunc = [&]( double aFoundValue )
432 {
433 return parallelValue( aFoundValue, r.value );
434 };
435 auto resultFunc = [&]( RESISTANCE& aFoundRes )
436 {
437 return parallelResistance( aFoundRes, r );
438 };
439 solution.Add2RLookupResults( findIn2RBuffer( m_target * r.value / ( r.value - m_target ) ), valueFunc,
440 resultFunc );
441 }
442 }
443
444 return solution.GetBest();
445}
446
448{
449 SolutionCollector solution( m_target );
450
451 for( RESISTANCE& rr : m_buffer_2R )
452 {
453 // try 2R + 2R combination
454 {
455 auto valueFunc = [&]( double aFoundValue )
456 {
457 return serialValue( aFoundValue, rr.value );
458 };
459 auto resultFunc = [&]( RESISTANCE& aFoundRes )
460 {
461 return serialResistance( aFoundRes, rr );
462 };
463 solution.Add2RLookupResults( findIn2RBuffer( m_target - rr.value ), valueFunc, resultFunc );
464 }
465
466 // try 2R | 2R combination
467 {
468 auto valueFunc = [&]( double aFoundValue )
469 {
470 return parallelValue( aFoundValue, rr.value );
471 };
472 auto resultFunc = [&]( RESISTANCE& aFoundRes )
473 {
474 return parallelResistance( aFoundRes, rr );
475 };
476 solution.Add2RLookupResults( findIn2RBuffer( m_target * rr.value / ( rr.value - m_target ) ), valueFunc,
477 resultFunc );
478 }
479 }
480
481 for( RESISTANCE& r1 : m_buffer_1R )
482 {
483 for( RESISTANCE& r2 : m_buffer_1R )
484 {
485 // try r1 + (r2 | 2R)
486 {
487 auto valueFunc = [&]( double aFoundValue )
488 {
489 return serialValue( r1.value, parallelValue( r2.value, aFoundValue ) );
490 };
491 auto resultFunc = [&]( RESISTANCE& aFoundRes )
492 {
493 return serialResistance( r1, parallelResistance( r2, aFoundRes ) );
494 };
495 solution.Add2RLookupResults(
496 findIn2RBuffer( ( m_target - r1.value ) * r2.value / ( r1.value + r2.value - m_target ) ),
497 valueFunc, resultFunc );
498 }
499
500 // try r1 | (r2 + 2R)
501 {
502 auto valueFunc = [&]( double aFoundValue )
503 {
504 return parallelValue( r1.value, serialValue( r2.value, aFoundValue ) );
505 };
506 auto resultFunc = [&]( RESISTANCE& aFoundRes )
507 {
508 return parallelResistance( r1, serialResistance( r2, aFoundRes ) );
509 };
510 solution.Add2RLookupResults( findIn2RBuffer( m_target * r1.value / ( r1.value - m_target ) - r2.value ),
511 valueFunc, resultFunc );
512 }
513 }
514 }
515
516 return solution.GetBest();
517}
const char * name
Definition: DXF_plotter.cpp:62
Creates a vector of integers of the E12 series values.
Definition: eseries.h:124
Creates a vector of integers of the E1 series values.
Definition: eseries.h:97
Creates a vector of integers of the E24 series values.
Definition: eseries.h:133
Creates a vector of integers of the E3 series values.
Definition: eseries.h:106
Creates a vector of integers of the E6 series values.
Definition: eseries.h:115
A small class to help profiling.
Definition: profile.h:49
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition: profile.h:105
std::array< std::optional< RESISTANCE >, NUMBER_OF_LEVELS > m_results
RESISTANCE calculate2RSolution()
Calculate the best combination consisting of exactly 2, 3 or 4 resistors.
std::vector< RESISTANCE > m_buffer_2R
std::pair< RESISTANCE &, RESISTANCE & > findIn2RBuffer(double aTargetValue)
Find in 2R buffer two values nearest to the given value (one smaller and one larger).
std::vector< RESISTANCE > m_buffer_1R
std::vector< RESISTANCE > buildSeriesData(const ESERIES::ESERIES_VALUES &aList)
Add values from aList to m_e_series tables.
void Exclude(double aValue)
If any value of the selected E-series not available, it can be entered as an exclude value.
void SetSeries(uint32_t aSeries)
Set E-series to be used in calculations.
std::vector< std::vector< RESISTANCE > > m_e_series
void prepare1RBuffer()
Build 1R buffer, which is selected E-series table with excluded values removed.
void Calculate()
Executes all the calculations.
void NewCalc(double aTargetValue)
Initialize next calculation, clear exclusion mask and erase results from previous calculation.
void prepare2RBuffer()
Build 2R buffer, which consists of all possible combinations of two resistors from 1R buffer (serial ...
std::vector< bool > m_exclude_mask
Helper class that collects solutions and keeps one with the best deviation.
RESISTANCE GetBest()
Return the best collected combination.
void Add2RLookupResults(std::pair< RESISTANCE &, RESISTANCE & > aResults, std::function< double(double)> aValueFunc, std::function< RESISTANCE(RESISTANCE &)> aResultFunc)
Add two solutions, based on single 2R buffer lookup, to the collector.
void considerSolution(double aValue, RESISTANCE &aFound, std::function< RESISTANCE(RESISTANCE &)> &aResultFunc)
std::optional< RESISTANCE > m_best_solution
static bool betterCandidate(const RESISTANCE &aCand, const RESISTANCE &aBest)
static int uniqueCount(const RESISTANCE &aRes)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:400
static std::string maybeEmbrace(const std::string &aText, char aRequiredSymbol)
If aText contains aRequiredSymbol as top-level (i.e.
static RESISTANCE parallelResistanceSimple(const RESISTANCE &aR1, const RESISTANCE &aR2)
static std::string strValue(double aValue)
static double parallelValue(double aR1, double aR2)
static double serialValue(double aR1, double aR2)
Functions calculating values and text representations of serial and parallel combinations.
static RESISTANCE serialResistanceSimple(const RESISTANCE &aR1, const RESISTANCE &aR2)
static RESISTANCE serialResistance(const RESISTANCE &aR1, const RESISTANCE &aR2)
bool operator<(const RESISTANCE &aLhs, double aRhs)
static RESISTANCE parallelResistance(const RESISTANCE &aR1, const RESISTANCE &aR2)
#define RES_EQUIV_CALC_FIRST_VALUE
#define RES_EQUIV_CALC_LAST_VALUE
const double epsilon
std::vector< double > parts
#define INFINITY
Definition: transline.cpp:35