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 (C) 2021-2023 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
27// If BENCHMARK is defined, calculations will print their execution time to the STDERR
28// #define BENCHMARK
29
30#ifdef BENCHMARK
31#include <core/profile.h>
32#endif
33
34// Comparison operators used by std::sort and std::lower_bound
35bool operator<( const RESISTANCE& aLhs, double aRhs )
36{
37 return aLhs.value < aRhs;
38}
39
40bool operator<( const RESISTANCE& aLhs, const RESISTANCE& aRhs )
41{
42 return aLhs.value < aRhs.value;
43}
44
51{
52public:
53 SolutionCollector( double aTarget ) : m_target( aTarget ) {}
54
62 void Add2RLookupResults( std::pair<RESISTANCE&, RESISTANCE&> aResults,
63 std::function<double( double )> aValueFunc,
64 std::function<RESISTANCE( RESISTANCE& )> aResultFunc )
65 {
66 addSolution( aValueFunc( aResults.first.value ), &aResults.first, aResultFunc );
67 addSolution( aValueFunc( aResults.second.value ), &aResults.second, aResultFunc );
68 }
69
74 {
76 throw std::logic_error( "Empty solution collector" );
77
79 }
80
81private:
90 void addSolution( double aValue, RESISTANCE *aFound,
91 std::function<RESISTANCE( RESISTANCE& )> aResultFunc )
92 {
93 double deviation = std::abs( aValue - m_target );
94 if( deviation < m_best_deviation )
95 {
96 m_best_deviation = deviation;
98 m_best_result_func = aResultFunc;
99 }
100 }
101
102 double m_target;
106};
107
113static std::string maybeEmbrace( const std::string& aText, char aRequiredSymbol )
114{
115 bool shouldEmbrace = false;
116
117 // scan for required top-level symbol
118 int parenLevel = 0;
119
120 for( char c : aText )
121 {
122 if( c == '(' )
123 parenLevel++;
124 else if( c == ')' )
125 parenLevel--;
126 else if( c == aRequiredSymbol && parenLevel == 0 )
127 shouldEmbrace = true;
128 }
129
130 // embrace or not
131 if( shouldEmbrace )
132 return '(' + aText + ')';
133 else
134 return aText;
135}
136
142static inline double serialValue( double aR1, double aR2 )
143{
144 return aR1 + aR2;
145}
146
147static inline double parallelValue( double aR1, double aR2 )
148{
149 return aR1 * aR2 / ( aR1 + aR2 );
150}
151
152static inline RESISTANCE serialResistance( const RESISTANCE& aR1, const RESISTANCE& aR2 )
153{
154 std::string name = maybeEmbrace( aR1.name, '|' ) + " + " + maybeEmbrace( aR2.name, '|' );
155 return RESISTANCE( serialValue( aR1.value, aR2.value ), name );
156}
157
158static inline RESISTANCE parallelResistance( const RESISTANCE& aR1, const RESISTANCE& aR2 )
159{
160 std::string name = maybeEmbrace( aR1.name, '+' ) + " | " + maybeEmbrace( aR2.name, '+' );
161 return RESISTANCE( parallelValue( aR1.value, aR2.value ), name );
162}
163
164static inline RESISTANCE serialResistanceSimple( const RESISTANCE& aR1, const RESISTANCE& aR2 )
165{
166 std::string name = aR1.name + " + " + aR2.name;
167 return RESISTANCE( serialValue( aR1.value, aR2.value ), name );
168}
169
170static inline RESISTANCE parallelResistanceSimple( const RESISTANCE& aR1, const RESISTANCE& aR2 )
171{
172 std::string name = aR1.name + " | " + aR2.name;
173 return RESISTANCE( parallelValue( aR1.value, aR2.value ), name );
174}
175
176// Return a string from aValue (aValue is expected in ohms).
177// If aValue < 1000 the returned string is aValue with unit = R.
178// If aValue >= 1000 the returned string is aValue/1000 with unit = K
179// with notation similar to 2K2.
180// If aValue >= 1e6 the returned string is aValue/1e6 with unit = M
181// with notation = 1M.
182static std::string strValue( double aValue )
183{
184 std::string result;
185
186 if( aValue < 1000.0 )
187 {
188 result = std::to_string( static_cast<int>( aValue ) );
189 result += 'R';
190 }
191 else
192 {
193 double div = 1e3;
194 char unit = 'K';
195
196 if( aValue >= 1e6 )
197 {
198 div = 1e6;
199 unit = 'M';
200 }
201
202 aValue /= div;
203
204 int valueAsInt = static_cast<int>( aValue );
205 result = std::to_string( valueAsInt );
206 result += unit;
207
208 // Add mantissa: 1 digit, suitable for series up to E24
209 double mantissa = aValue - valueAsInt;
210
211 if( mantissa > 0 )
212 result += std::to_string( lround( mantissa * 10 ) );
213 }
214
215 return result;
216}
217
218
220{
221 // series must be added to vector in correct order
227}
228
229void RES_EQUIV_CALC::SetSeries( uint32_t aSeries )
230{
231 m_series = aSeries;
232}
233
234void RES_EQUIV_CALC::NewCalc( double aTargetValue )
235{
236 m_target = aTargetValue;
237
238 m_exclude_mask.resize( m_e_series[m_series].size() );
239 std::fill( m_exclude_mask.begin(), m_exclude_mask.end(), false );
240
241 std::fill( m_results.begin(), m_results.end(), std::nullopt );
242}
243
244void RES_EQUIV_CALC::Exclude( double aValue )
245{
246 if( std::isnan( aValue ) )
247 return;
248
249 std::vector<RESISTANCE>& series = m_e_series[m_series];
250 auto it = std::lower_bound( series.begin(), series.end(), aValue - epsilon );
251
252 if( it != series.end() && std::abs( it->value - aValue ) < epsilon )
253 m_exclude_mask[it - series.begin()] = true;
254}
255
257{
258#ifdef BENCHMARK
259 PROF_TIMER timer( "Resistor calculation" );
260#endif
261
264
265 RESISTANCE solution_2r = calculate2RSolution();
266 m_results[S2R] = solution_2r;
267
268 if( std::abs( solution_2r.value - m_target ) > epsilon )
269 {
270 RESISTANCE solution_3r = calculate3RSolution();
271 m_results[S3R] = solution_3r;
272
273 if( std::abs( solution_3r.value - m_target ) > epsilon )
275 }
276
277#ifdef BENCHMARK
278 timer.Show();
279#endif
280}
281
282std::vector<RESISTANCE> RES_EQUIV_CALC::buildSeriesData( const ESERIES::ESERIES_VALUES& aList )
283{
284 std::vector<RESISTANCE> result_list;
285
286 for( double curr_decade = RES_EQUIV_CALC_FIRST_VALUE;; curr_decade *= 10.0 ) // iterate over decades
287 {
288 double multiplier = curr_decade / aList[0];
289
290 for( const uint16_t listvalue : aList ) // iterate over values in decade
291 {
292 double value = multiplier * listvalue;
293 result_list.emplace_back( value, strValue( value ) );
294
295 if( value >= RES_EQUIV_CALC_LAST_VALUE )
296 return result_list;
297 }
298 }
299}
300
302{
303 std::vector<RESISTANCE>& series = m_e_series[m_series];
304 m_buffer_1R.clear();
305
306 for( size_t i = 0; i < series.size(); i++ )
307 {
308 if( !m_exclude_mask[i] )
309 m_buffer_1R.push_back( series[i] );
310 }
311}
312
314{
315 m_buffer_2R.clear();
316
317 for( size_t i1 = 0; i1 < m_buffer_1R.size(); i1++ )
318 {
319 for( size_t i2 = i1; i2 < m_buffer_1R.size(); i2++ )
320 {
323 }
324 }
325
326 std::sort( m_buffer_2R.begin(), m_buffer_2R.end() );
327}
328
329std::pair<RESISTANCE&, RESISTANCE&> RES_EQUIV_CALC::findIn2RBuffer( double aTarget )
330{
331 // in case of NaN, return anything valid
332 if( std::isnan( aTarget ) )
333 return { m_buffer_2R[0], m_buffer_2R[0] };
334
335 // target value is often too small or too big, so check that manually
336 if( aTarget <= m_buffer_2R.front().value || aTarget >= m_buffer_2R.back().value )
337 return { m_buffer_2R.front(), m_buffer_2R.back() };
338
339 auto it = std::lower_bound( m_buffer_2R.begin(), m_buffer_2R.end(), aTarget )
340 - m_buffer_2R.begin();
341
342 if( it == 0 )
343 return { m_buffer_2R[0], m_buffer_2R[0] };
344 else if( it == m_buffer_2R.size() )
345 return { m_buffer_2R[it - 1], m_buffer_2R[it - 1] };
346 else
347 return { m_buffer_2R[it - 1], m_buffer_2R[it] };
348}
349
351{
352 SolutionCollector solution( m_target );
353
354 auto valueFunc = []( double aFoundValue )
355 {
356 return aFoundValue;
357 };
358 auto resultFunc = []( RESISTANCE& aFoundRes )
359 {
360 return aFoundRes;
361 };
362 solution.Add2RLookupResults( findIn2RBuffer( m_target ), valueFunc, resultFunc );
363
364 return solution.GetBest();
365}
366
368{
369 SolutionCollector solution( m_target );
370
371 for( RESISTANCE& r : m_buffer_1R )
372 {
373 // try r + 2R combination
374 {
375 auto valueFunc = [&]( double aFoundValue )
376 {
377 return serialValue( aFoundValue, r.value );
378 };
379 auto resultFunc = [&]( RESISTANCE& aFoundRes )
380 {
381 return serialResistance( aFoundRes, r );
382 };
383 solution.Add2RLookupResults( findIn2RBuffer( m_target - r.value ), valueFunc,
384 resultFunc );
385 }
386
387 // try r | 2R combination
388 {
389 auto valueFunc = [&]( double aFoundValue )
390 {
391 return parallelValue( aFoundValue, r.value );
392 };
393 auto resultFunc = [&]( RESISTANCE& aFoundRes )
394 {
395 return parallelResistance( aFoundRes, r );
396 };
397 solution.Add2RLookupResults(
398 findIn2RBuffer( m_target * r.value / ( r.value - m_target ) ), valueFunc,
399 resultFunc );
400 }
401 }
402
403 return solution.GetBest();
404}
405
407{
408 SolutionCollector solution( m_target );
409
410 for( RESISTANCE& rr : m_buffer_2R )
411 {
412 // try 2R + 2R combination
413 {
414 auto valueFunc = [&]( double aFoundValue )
415 {
416 return serialValue( aFoundValue, rr.value );
417 };
418 auto resultFunc = [&]( RESISTANCE& aFoundRes )
419 {
420 return serialResistance( aFoundRes, rr );
421 };
422 solution.Add2RLookupResults( findIn2RBuffer( m_target - rr.value ), valueFunc,
423 resultFunc );
424 }
425
426 // try 2R | 2R combination
427 {
428 auto valueFunc = [&]( double aFoundValue )
429 {
430 return parallelValue( aFoundValue, rr.value );
431 };
432 auto resultFunc = [&]( RESISTANCE& aFoundRes )
433 {
434 return parallelResistance( aFoundRes, rr );
435 };
436 solution.Add2RLookupResults(
437 findIn2RBuffer( m_target * rr.value / ( rr.value - m_target ) ), valueFunc,
438 resultFunc );
439 }
440 }
441
442 for( RESISTANCE& r1 : m_buffer_1R )
443 {
444 for( RESISTANCE& r2 : m_buffer_1R )
445 {
446 // try r1 + (r2 | 2R)
447 {
448 auto valueFunc = [&]( double aFoundValue )
449 {
450 return serialValue( r1.value, parallelValue( r2.value, aFoundValue ) );
451 };
452 auto resultFunc = [&]( RESISTANCE& aFoundRes )
453 {
454 return serialResistance( r1, parallelResistance( r2, aFoundRes ) );
455 };
456 solution.Add2RLookupResults( findIn2RBuffer( ( m_target - r1.value ) * r2.value
457 / ( r1.value + r2.value - m_target ) ),
458 valueFunc, resultFunc );
459 }
460
461 // try r1 | (r2 + 2R)
462 {
463 auto valueFunc = [&]( double aFoundValue )
464 {
465 return parallelValue( r1.value, serialValue( r2.value, aFoundValue ) );
466 };
467 auto resultFunc = [&]( RESISTANCE& aFoundRes )
468 {
469 return parallelResistance( r1, serialResistance( r2, aFoundRes ) );
470 };
471 solution.Add2RLookupResults(
472 findIn2RBuffer( m_target * r1.value / ( r1.value - m_target ) - r2.value ),
473 valueFunc, resultFunc );
474 }
475 }
476 }
477
478 return solution.GetBest();
479}
const char * name
Definition: DXF_plotter.cpp:57
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, running the corresponding result_func.
std::function< RESISTANCE(RESISTANCE &)> m_best_result_func
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 addSolution(double aValue, RESISTANCE *aFound, std::function< RESISTANCE(RESISTANCE &)> aResultFunc)
Add single solution to the collector.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:424
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
#define INFINITY
Definition: transline.cpp:35