Implement new resistor substitution algorithm
Significantly improves performance of the tool. Also corrects an mistake in the formula for computing relative errors
This commit is contained in:
parent
463e7c3b30
commit
ac9a863496
|
@ -35,6 +35,13 @@
|
||||||
wxString r_calculator_help =
|
wxString r_calculator_help =
|
||||||
#include "r_calculator_help.h"
|
#include "r_calculator_help.h"
|
||||||
|
|
||||||
|
/* RES_EQUIV_CALC class considers resistor values from a limited range
|
||||||
|
* and only combinations of up to 4 resistors, so target values less than
|
||||||
|
* parallel combination of minimum resistors or greater than serial combination
|
||||||
|
* of maximum resistors cannot be reasonably looked for
|
||||||
|
*/
|
||||||
|
static const double min_target_value = static_cast<double>(RES_EQUIV_CALC_FIRST_VALUE) / 4;
|
||||||
|
static const double max_target_value = static_cast<double>(RES_EQUIV_CALC_LAST_VALUE) * 4;
|
||||||
|
|
||||||
extern double DoubleFromString( const wxString& TextValue );
|
extern double DoubleFromString( const wxString& TextValue );
|
||||||
|
|
||||||
|
@ -94,22 +101,25 @@ void PANEL_R_CALCULATOR::LoadSettings( PCB_CALCULATOR_SETTINGS* aCfg )
|
||||||
|
|
||||||
void PANEL_R_CALCULATOR::OnCalculateESeries( wxCommandEvent& event )
|
void PANEL_R_CALCULATOR::OnCalculateESeries( wxCommandEvent& event )
|
||||||
{
|
{
|
||||||
double reqr; // required resistor stored in local copy
|
double reqr = 1000 * DoubleFromString( m_ResRequired->GetValue() );
|
||||||
double error, err3 = 0;
|
|
||||||
wxString es, fs; // error and formula strings
|
|
||||||
|
|
||||||
wxBusyCursor dummy;
|
if( std::isnan( reqr ) || reqr < min_target_value || reqr > max_target_value )
|
||||||
|
{
|
||||||
|
wxMessageBox( wxString::Format( _( "Incorrect required resistance value: %s" ),
|
||||||
|
m_ResRequired->GetValue() ) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxBusyCursor busyCursor; // As long as this variable exists, the cursor will be 'busy'
|
||||||
|
|
||||||
reqr = ( 1000 * DoubleFromString( m_ResRequired->GetValue() ) );
|
m_eSeries.NewCalc( reqr ); // assume all values available
|
||||||
m_eSeries.SetRequiredValue( reqr ); // keep a local copy of required resistor value
|
|
||||||
m_eSeries.NewCalc(); // assume all values available
|
|
||||||
/*
|
/*
|
||||||
* Exclude itself. For the case, a value from the available series is found as required value,
|
* Exclude itself. For the case, a value from the available series is found as required value,
|
||||||
* the calculator assumes this value needs a replacement for the reason of being not available.
|
* the calculator assumes this value needs a replacement for the reason of being not available.
|
||||||
* Two further exclude values can be entered to exclude and are skipped as not being available.
|
* Two further exclude values can be entered to exclude and are skipped as not being available.
|
||||||
* All values entered in KiloOhms are converted to Ohm for internal calculation
|
* All values entered in KiloOhms are converted to Ohm for internal calculation
|
||||||
*/
|
*/
|
||||||
m_eSeries.Exclude( 1000 * DoubleFromString( m_ResRequired->GetValue() ) );
|
m_eSeries.Exclude( reqr );
|
||||||
m_eSeries.Exclude( 1000 * DoubleFromString( m_ResExclude1->GetValue() ) );
|
m_eSeries.Exclude( 1000 * DoubleFromString( m_ResExclude1->GetValue() ) );
|
||||||
m_eSeries.Exclude( 1000 * DoubleFromString( m_ResExclude2->GetValue() ) );
|
m_eSeries.Exclude( 1000 * DoubleFromString( m_ResExclude2->GetValue() ) );
|
||||||
|
|
||||||
|
@ -117,88 +127,43 @@ void PANEL_R_CALCULATOR::OnCalculateESeries( wxCommandEvent& event )
|
||||||
{
|
{
|
||||||
m_eSeries.Calculate();
|
m_eSeries.Calculate();
|
||||||
}
|
}
|
||||||
catch( std::out_of_range const& exc )
|
catch( const std::exception& exc )
|
||||||
{
|
{
|
||||||
wxString msg;
|
wxMessageBox( wxString::Format( "Internal error: %s", exc.what() ) );
|
||||||
msg << "Internal error: " << exc.what();
|
|
||||||
|
|
||||||
wxMessageBox( msg );
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs = m_eSeries.GetResults()[RES_EQUIV_CALC::S2R].e_name; // show 2R solution formula string
|
auto showResult = [reqr]( const std::optional<RESISTANCE>& aResult, wxTextCtrl* aFormulaField,
|
||||||
m_ESeries_Sol2R->SetValue( fs );
|
wxTextCtrl* aErrorField )
|
||||||
error = reqr
|
|
||||||
+ m_eSeries.GetResults()[RES_EQUIV_CALC::S2R].e_value; // absolute value of solution
|
|
||||||
error = ( reqr / error - 1 ) * 100; // error in percent
|
|
||||||
|
|
||||||
if( error )
|
|
||||||
{
|
{
|
||||||
if( std::abs( error ) < 0.01 )
|
wxString fs, es; // formula and error string
|
||||||
es.Printf( "<%.2f", 0.01 );
|
|
||||||
else
|
|
||||||
es.Printf( "%+.2f", error );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
es = _( "Exact" );
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ESeriesError2R->SetValue( es ); // anyway show 2R error string
|
if( aResult ) // if value is present
|
||||||
|
|
||||||
if( m_eSeries.GetResults()[RES_EQUIV_CALC::S3R].e_use ) // if 3R solution available
|
|
||||||
{
|
|
||||||
err3 = reqr + m_eSeries.GetResults()[RES_EQUIV_CALC::S3R].e_value; // calculate the 3R
|
|
||||||
err3 = ( reqr / err3 - 1 ) * 100; // error in percent
|
|
||||||
|
|
||||||
if( err3 )
|
|
||||||
{
|
{
|
||||||
if( std::abs( err3 ) < 0.01 )
|
fs = aResult->name;
|
||||||
|
double sol = aResult->value;
|
||||||
|
double error = ( sol / reqr - 1 ) * 100; // relative error in percent
|
||||||
|
|
||||||
|
if( std::abs( error ) < epsilon )
|
||||||
|
es = _( "Exact" );
|
||||||
|
else if( std::abs( error ) < 0.01 )
|
||||||
es.Printf( "<%.2f", 0.01 );
|
es.Printf( "<%.2f", 0.01 );
|
||||||
else
|
else
|
||||||
es.Printf( "%+.2f", err3 );
|
es.Printf( "%+.2f", error );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
es = _( "Exact" );
|
fs = _( "Not worth using" );
|
||||||
|
es = wxEmptyString;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ESeriesError3R->SetValue( es ); // show 3R error string
|
aFormulaField->SetValue( fs );
|
||||||
fs = m_eSeries.GetResults()[RES_EQUIV_CALC::S3R].e_name;
|
aErrorField->SetValue( es );
|
||||||
m_ESeries_Sol3R->SetValue( fs ); // show 3R formula string
|
};
|
||||||
}
|
|
||||||
else // nothing better than 2R found
|
|
||||||
{
|
|
||||||
fs = _( "Not worth using" );
|
|
||||||
m_ESeries_Sol3R->SetValue( fs );
|
|
||||||
m_ESeriesError3R->SetValue( wxEmptyString );
|
|
||||||
}
|
|
||||||
|
|
||||||
fs = wxEmptyString;
|
showResult( m_eSeries.GetResults()[RES_EQUIV_CALC::S2R], m_ESeries_Sol2R, m_ESeriesError2R );
|
||||||
|
showResult( m_eSeries.GetResults()[RES_EQUIV_CALC::S3R], m_ESeries_Sol3R, m_ESeriesError3R );
|
||||||
if( m_eSeries.GetResults()[RES_EQUIV_CALC::S4R].e_use ) // show 4R solution if available
|
showResult( m_eSeries.GetResults()[RES_EQUIV_CALC::S4R], m_ESeries_Sol4R, m_ESeriesError4R );
|
||||||
{
|
|
||||||
fs = m_eSeries.GetResults()[RES_EQUIV_CALC::S4R].e_name;
|
|
||||||
|
|
||||||
error = reqr
|
|
||||||
+ m_eSeries.GetResults()[RES_EQUIV_CALC::S4R].e_value; // absolute value of solution
|
|
||||||
error = ( reqr / error - 1 ) * 100; // error in percent
|
|
||||||
|
|
||||||
if( error )
|
|
||||||
es.Printf( "%+.2f", error );
|
|
||||||
else
|
|
||||||
es = _( "Exact" );
|
|
||||||
|
|
||||||
m_ESeriesError4R->SetValue( es );
|
|
||||||
}
|
|
||||||
else // no 4R solution
|
|
||||||
{
|
|
||||||
fs = _( "Not worth using" );
|
|
||||||
es = wxEmptyString;
|
|
||||||
m_ESeriesError4R->SetValue( es );
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ESeries_Sol4R->SetValue( fs );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
* This program source code file
|
* This program source code file
|
||||||
* is part of KiCad, a free EDA CAD application.
|
* is part of KiCad, a free EDA CAD application.
|
||||||
*
|
*
|
||||||
* Copyright (C) 2020 <janvi@veith.net>
|
|
||||||
* Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
* Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
@ -19,30 +18,167 @@
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <limits>
|
|
||||||
#include "resistor_substitution_utils.h"
|
#include "resistor_substitution_utils.h"
|
||||||
#include "eseries.h"
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <functional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
/*
|
// If BENCHMARK is defined, calculations will print their execution time to the STDERR
|
||||||
* If BENCHMARK is defined, any 4R E12 calculations will print its execution time to console
|
// #define BENCHMARK
|
||||||
* My Hasswell Enthusiast reports 225 mSec what are reproducible within plusminus 2 percent
|
|
||||||
*/
|
|
||||||
//#define BENCHMARK
|
|
||||||
|
|
||||||
#ifdef BENCHMARK
|
#ifdef BENCHMARK
|
||||||
#include <core/profile.h>
|
#include <core/profile.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Comparison operators used by std::sort and std::lower_bound
|
||||||
|
bool operator<( const RESISTANCE& aLhs, double aRhs )
|
||||||
|
{
|
||||||
|
return aLhs.value < aRhs;
|
||||||
|
}
|
||||||
|
|
||||||
// Return a string from aValue (aValue is expected in ohms)
|
bool operator<( const RESISTANCE& aLhs, const RESISTANCE& aRhs )
|
||||||
// If aValue < 1000 the returned string is aValue with unit = R
|
{
|
||||||
|
return aLhs.value < aRhs.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SolutionCollector
|
||||||
|
/**
|
||||||
|
* Helper class that collects solutions and keeps one with the best deviation.
|
||||||
|
* In order to avoid performing costly string operations too frequently,
|
||||||
|
* they are postponed until the very end, when we know the best combination.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SolutionCollector( double aTarget ) : m_target( aTarget ) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add two solutions, based on single 2R buffer lookup, to the collector.
|
||||||
|
*
|
||||||
|
* @param aResults are the resistances found in 2R buffer
|
||||||
|
* @param aValueFunc transforms value from aResults into final value of the combination
|
||||||
|
* @param aResultFunc transforms RESISTANCE instance from aResults into final instance
|
||||||
|
*/
|
||||||
|
void Add2RLookupResults( std::pair<RESISTANCE&, RESISTANCE&> aResults,
|
||||||
|
std::function<double( double )> aValueFunc,
|
||||||
|
std::function<RESISTANCE( RESISTANCE& )> aResultFunc )
|
||||||
|
{
|
||||||
|
addSolution( aValueFunc( aResults.first.value ), &aResults.first, aResultFunc );
|
||||||
|
addSolution( aValueFunc( aResults.second.value ), &aResults.second, aResultFunc );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the best collected combination, running the corresponding result_func.
|
||||||
|
*/
|
||||||
|
RESISTANCE GetBest()
|
||||||
|
{
|
||||||
|
if( !m_best_found_resistance )
|
||||||
|
throw std::logic_error( "Empty solution collector" );
|
||||||
|
|
||||||
|
return m_best_result_func( *m_best_found_resistance );
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Add single solution to the collector.
|
||||||
|
*
|
||||||
|
* @param aValue is a value of the combination in Ohms
|
||||||
|
* @param aFound is the corresponding RESISTANCE found in 2R buffer
|
||||||
|
* @param aResultFunc is a function calculating final result (RESISTANCE instance)
|
||||||
|
* for this combination
|
||||||
|
*/
|
||||||
|
void addSolution( double aValue, RESISTANCE *aFound,
|
||||||
|
std::function<RESISTANCE( RESISTANCE& )> aResultFunc )
|
||||||
|
{
|
||||||
|
double deviation = std::abs( aValue - m_target );
|
||||||
|
if( deviation < m_best_deviation )
|
||||||
|
{
|
||||||
|
m_best_deviation = deviation;
|
||||||
|
m_best_found_resistance = aFound;
|
||||||
|
m_best_result_func = aResultFunc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double m_target;
|
||||||
|
double m_best_deviation = INFINITY;
|
||||||
|
RESISTANCE* m_best_found_resistance = nullptr;
|
||||||
|
std::function<RESISTANCE( RESISTANCE& )> m_best_result_func;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If aText contains aRequiredSymbol as top-level (i.e. not in parentheses) operator,
|
||||||
|
* return aText enclosed in parentheses.
|
||||||
|
* Otherwise, return aText unmodified.
|
||||||
|
*/
|
||||||
|
static std::string maybeEmbrace( const std::string& aText, char aRequiredSymbol )
|
||||||
|
{
|
||||||
|
bool shouldEmbrace = false;
|
||||||
|
|
||||||
|
// scan for required top-level symbol
|
||||||
|
int parenLevel = 0;
|
||||||
|
|
||||||
|
for( char c : aText )
|
||||||
|
{
|
||||||
|
if( c == '(' )
|
||||||
|
parenLevel++;
|
||||||
|
else if( c == ')' )
|
||||||
|
parenLevel--;
|
||||||
|
else if( c == aRequiredSymbol && parenLevel == 0 )
|
||||||
|
shouldEmbrace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// embrace or not
|
||||||
|
if( shouldEmbrace )
|
||||||
|
return '(' + aText + ')';
|
||||||
|
else
|
||||||
|
return aText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions calculating values and text representations of serial and parallel combinations.
|
||||||
|
* Functions marked as 'Simple' do not care about parentheses, which makes them faster.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline double serialValue( double aR1, double aR2 )
|
||||||
|
{
|
||||||
|
return aR1 + aR2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline double parallelValue( double aR1, double aR2 )
|
||||||
|
{
|
||||||
|
return aR1 * aR2 / ( aR1 + aR2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline RESISTANCE serialResistance( const RESISTANCE& aR1, const RESISTANCE& aR2 )
|
||||||
|
{
|
||||||
|
std::string name = maybeEmbrace( aR1.name, '|' ) + " + " + maybeEmbrace( aR2.name, '|' );
|
||||||
|
return RESISTANCE( serialValue( aR1.value, aR2.value ), name );
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline RESISTANCE parallelResistance( const RESISTANCE& aR1, const RESISTANCE& aR2 )
|
||||||
|
{
|
||||||
|
std::string name = maybeEmbrace( aR1.name, '+' ) + " | " + maybeEmbrace( aR2.name, '+' );
|
||||||
|
return RESISTANCE( parallelValue( aR1.value, aR2.value ), name );
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline RESISTANCE serialResistanceSimple( const RESISTANCE& aR1, const RESISTANCE& aR2 )
|
||||||
|
{
|
||||||
|
std::string name = aR1.name + " + " + aR2.name;
|
||||||
|
return RESISTANCE( serialValue( aR1.value, aR2.value ), name );
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline RESISTANCE parallelResistanceSimple( const RESISTANCE& aR1, const RESISTANCE& aR2 )
|
||||||
|
{
|
||||||
|
std::string name = aR1.name + " | " + aR2.name;
|
||||||
|
return RESISTANCE( parallelValue( aR1.value, aR2.value ), name );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a string from aValue (aValue is expected in ohms).
|
||||||
|
// If aValue < 1000 the returned string is aValue with unit = R.
|
||||||
// If aValue >= 1000 the returned string is aValue/1000 with unit = K
|
// If aValue >= 1000 the returned string is aValue/1000 with unit = K
|
||||||
// with notation similar to 2K2
|
// with notation similar to 2K2.
|
||||||
// If aValue >= 1e6 the returned string is aValue/1e6 with unit = M
|
// If aValue >= 1e6 the returned string is aValue/1e6 with unit = M
|
||||||
// with notation = 1M
|
// with notation = 1M.
|
||||||
static std::string strValue( double aValue )
|
static std::string strValue( double aValue )
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
|
@ -73,7 +209,7 @@ static std::string strValue( double aValue )
|
||||||
double mantissa = aValue - valueAsInt;
|
double mantissa = aValue - valueAsInt;
|
||||||
|
|
||||||
if( mantissa > 0 )
|
if( mantissa > 0 )
|
||||||
result += std::to_string( static_cast<int>( ( mantissa * 10 ) + 0.5 ) );
|
result += std::to_string( lround( mantissa * 10 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -82,312 +218,262 @@ static std::string strValue( double aValue )
|
||||||
|
|
||||||
RES_EQUIV_CALC::RES_EQUIV_CALC()
|
RES_EQUIV_CALC::RES_EQUIV_CALC()
|
||||||
{
|
{
|
||||||
// Build the list of available resistor values in each En serie
|
// series must be added to vector in correct order
|
||||||
const ESERIES::ESERIES_VALUES listValuesE1 = ESERIES::E1_VALUES();
|
m_e_series.push_back( buildSeriesData( ESERIES::E1_VALUES() ) );
|
||||||
const ESERIES::ESERIES_VALUES listValuesE3 = ESERIES::E3_VALUES();
|
m_e_series.push_back( buildSeriesData( ESERIES::E3_VALUES() ) );
|
||||||
const ESERIES::ESERIES_VALUES listValuesE6 = ESERIES::E6_VALUES();
|
m_e_series.push_back( buildSeriesData( ESERIES::E6_VALUES() ) );
|
||||||
const ESERIES::ESERIES_VALUES listValuesE12 = ESERIES::E12_VALUES();
|
m_e_series.push_back( buildSeriesData( ESERIES::E12_VALUES() ) );
|
||||||
const ESERIES::ESERIES_VALUES listValuesE24 = ESERIES::E24_VALUES();
|
m_e_series.push_back( buildSeriesData( ESERIES::E24_VALUES() ) );
|
||||||
// buildSeriesData must be called in the order of En series, because
|
|
||||||
// the list of series is expected indexed by En for the serie En
|
|
||||||
buildSeriesData( listValuesE1 );
|
|
||||||
buildSeriesData( listValuesE3 );
|
|
||||||
buildSeriesData( listValuesE6 );
|
|
||||||
buildSeriesData( listValuesE12 );
|
|
||||||
int count = buildSeriesData( listValuesE24 );
|
|
||||||
|
|
||||||
// Reserve a buffer for intermediate calculations:
|
|
||||||
// the buffer size is 2*count*count to store all combinations of 2 values
|
|
||||||
// there are 2*count*count = 29282 combinations for E24
|
|
||||||
int bufsize = 2 * count * count;
|
|
||||||
m_combined_table.reserve( bufsize );
|
|
||||||
|
|
||||||
// Store predefined R_DATA items.
|
|
||||||
for( int ii = 0; ii < bufsize; ii++ )
|
|
||||||
m_combined_table.emplace_back( "", 0.0 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RES_EQUIV_CALC::SetSeries( uint32_t aSeries )
|
||||||
int RES_EQUIV_CALC::buildSeriesData( const ESERIES::ESERIES_VALUES aList )
|
|
||||||
{
|
{
|
||||||
uint_fast32_t curr_decade = FIRST_VALUE;
|
m_series = aSeries;
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
std::vector<R_DATA> curr_list;
|
|
||||||
|
|
||||||
uint_fast16_t first_value_in_decade = aList[0];
|
|
||||||
|
|
||||||
for( ;; )
|
|
||||||
{
|
|
||||||
double curr_r = LAST_VALUE;
|
|
||||||
|
|
||||||
for( const uint16_t listvalue : aList )
|
|
||||||
{
|
|
||||||
curr_r = 1.0 * curr_decade * listvalue / first_value_in_decade;
|
|
||||||
curr_list.emplace_back( strValue( curr_r ), curr_r );
|
|
||||||
count++;
|
|
||||||
|
|
||||||
if( curr_r >= LAST_VALUE )
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( curr_r >= LAST_VALUE )
|
|
||||||
break;
|
|
||||||
|
|
||||||
curr_decade *= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_tables.push_back( std::move( curr_list ) );
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RES_EQUIV_CALC::NewCalc( double aTargetValue )
|
||||||
|
{
|
||||||
|
m_target = aTargetValue;
|
||||||
|
|
||||||
|
m_exclude_mask.resize( m_e_series[m_series].size() );
|
||||||
|
std::fill( m_exclude_mask.begin(), m_exclude_mask.end(), false );
|
||||||
|
|
||||||
|
std::fill( m_results.begin(), m_results.end(), std::nullopt );
|
||||||
|
}
|
||||||
|
|
||||||
void RES_EQUIV_CALC::Exclude( double aValue )
|
void RES_EQUIV_CALC::Exclude( double aValue )
|
||||||
{
|
{
|
||||||
if( aValue != 0.0 ) // if there is a value to exclude other than a wire jumper
|
if( std::isnan( aValue ) )
|
||||||
{
|
return;
|
||||||
for( R_DATA& i : m_tables[m_series] ) // then search it in the selected E-Series table
|
|
||||||
{
|
std::vector<RESISTANCE>& series = m_e_series[m_series];
|
||||||
if( i.e_value == aValue ) // if the value to exclude is found
|
auto it = std::lower_bound( series.begin(), series.end(), aValue - epsilon );
|
||||||
i.e_use = false; // disable its use
|
|
||||||
}
|
if( it != series.end() && std::abs( it->value - aValue ) < epsilon )
|
||||||
}
|
m_exclude_mask[it - series.begin()] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void RES_EQUIV_CALC::simple_solution( uint32_t aSize )
|
|
||||||
{
|
|
||||||
uint32_t i;
|
|
||||||
|
|
||||||
m_results.at( S2R ).e_value =
|
|
||||||
std::numeric_limits<double>::max(); // assume no 2R solution or max deviation
|
|
||||||
|
|
||||||
for( i = 0; i < aSize; i++ )
|
|
||||||
{
|
|
||||||
if( std::abs( m_combined_table.at( i ).e_value - m_required_value )
|
|
||||||
< std::abs( m_results.at( S2R ).e_value ) )
|
|
||||||
{
|
|
||||||
m_results[S2R].e_value =
|
|
||||||
m_combined_table[i].e_value - m_required_value; // save signed deviation in Ohms
|
|
||||||
m_results[S2R].e_name = m_combined_table[i].e_name; // save combination text
|
|
||||||
m_results[S2R].e_use = true; // this is a possible solution
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void RES_EQUIV_CALC::combine4( uint32_t aSize )
|
|
||||||
{
|
|
||||||
uint32_t i, j;
|
|
||||||
double tmp;
|
|
||||||
|
|
||||||
m_results[S4R].e_use = false; // disable 4R solution, until
|
|
||||||
m_results[S4R].e_value = m_results[S3R].e_value; // 4R becomes better than 3R solution
|
|
||||||
|
|
||||||
#ifdef BENCHMARK
|
|
||||||
PROF_TIMER timer; // start timer to count execution time
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for( i = 0; i < aSize; i++ ) // 4R search outer loop
|
|
||||||
{ // scan valid intermediate 2R solutions
|
|
||||||
for( j = 0; j < aSize; j++ ) // inner loop combines all with itself
|
|
||||||
{
|
|
||||||
tmp = m_combined_table[i].e_value
|
|
||||||
+ m_combined_table[j].e_value; // calculate 2R+2R serial
|
|
||||||
tmp -= m_required_value; // calculate 4R deviation
|
|
||||||
|
|
||||||
if( std::abs( tmp ) < std::abs( m_results.at( S4R ).e_value ) ) // if new 4R is better
|
|
||||||
{
|
|
||||||
m_results[S4R].e_value = tmp; // save amount of benefit
|
|
||||||
std::string s = "( ";
|
|
||||||
s.append( m_combined_table[i].e_name ); // mention 1st 2 component
|
|
||||||
s.append( " ) + ( " ); // in series
|
|
||||||
s.append( m_combined_table[j].e_name ); // with 2nd 2 components
|
|
||||||
s.append( " )" );
|
|
||||||
m_results[S4R].e_name = s; // save the result and
|
|
||||||
m_results[S4R].e_use = true; // enable for later use
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = ( m_combined_table[i].e_value * m_combined_table[j].e_value )
|
|
||||||
/ ( m_combined_table[i].e_value
|
|
||||||
+ m_combined_table[j].e_value ); // calculate 2R|2R parallel
|
|
||||||
tmp -= m_required_value; // calculate 4R deviation
|
|
||||||
|
|
||||||
if( std::abs( tmp ) < std::abs( m_results[S4R].e_value ) ) // if new 4R is better
|
|
||||||
{
|
|
||||||
m_results[S4R].e_value = tmp; // save amount of benefit
|
|
||||||
std::string s = "( ";
|
|
||||||
s.append( m_combined_table[i].e_name ); // mention 1st 2 component
|
|
||||||
s.append( " ) | ( " ); // in parallel
|
|
||||||
s.append( m_combined_table[j].e_name ); // with 2nd 2 components
|
|
||||||
s.append( " )" );
|
|
||||||
m_results[S4R].e_name = s; // save the result
|
|
||||||
m_results[S4R].e_use = true; // enable later use
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef BENCHMARK
|
|
||||||
printf( "Calculation time = %d mS", timer.msecs() );
|
|
||||||
fflush( 0 );
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void RES_EQUIV_CALC::NewCalc()
|
|
||||||
{
|
|
||||||
for( R_DATA& i : m_combined_table )
|
|
||||||
i.e_use = false; // before any calculation is done, assume that
|
|
||||||
|
|
||||||
for( R_DATA& i : m_results )
|
|
||||||
i.e_use = false; // no combinations and no results are available
|
|
||||||
|
|
||||||
for( R_DATA& i : m_tables[m_series] )
|
|
||||||
i.e_use = true; // all selected E-values available
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint32_t RES_EQUIV_CALC::combine2()
|
|
||||||
{
|
|
||||||
uint32_t combi2R = 0; // target index counts calculated 2R combinations
|
|
||||||
std::string s;
|
|
||||||
|
|
||||||
for( const R_DATA& i : m_tables[m_series] ) // outer loop to sweep selected source lookup table
|
|
||||||
{
|
|
||||||
if( i.e_use )
|
|
||||||
{
|
|
||||||
for( const R_DATA& j : m_tables[m_series] ) // inner loop to combine values with itself
|
|
||||||
{
|
|
||||||
if( j.e_use )
|
|
||||||
{
|
|
||||||
m_combined_table[combi2R].e_use = true;
|
|
||||||
m_combined_table[combi2R].e_value =
|
|
||||||
i.e_value + j.e_value; // calculate 2R serial
|
|
||||||
s = i.e_name;
|
|
||||||
s.append( " + " );
|
|
||||||
m_combined_table[combi2R].e_name = s.append( j.e_name );
|
|
||||||
combi2R++; // next destination
|
|
||||||
m_combined_table[combi2R].e_use = true; // calculate 2R parallel
|
|
||||||
m_combined_table[combi2R].e_value =
|
|
||||||
i.e_value * j.e_value / ( i.e_value + j.e_value );
|
|
||||||
s = i.e_name;
|
|
||||||
s.append( " | " );
|
|
||||||
m_combined_table[combi2R].e_name = s.append( j.e_name );
|
|
||||||
combi2R++; // next destination
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return combi2R;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void RES_EQUIV_CALC::combine3( uint32_t aSize )
|
|
||||||
{
|
|
||||||
uint32_t j = 0;
|
|
||||||
double tmp = 0; // avoid warning for being uninitialized
|
|
||||||
std::string s;
|
|
||||||
|
|
||||||
m_results[S3R].e_use = false; // disable 3R solution, until 3R
|
|
||||||
m_results[S3R].e_value = m_results[S2R].e_value; // becomes better than 2R solution
|
|
||||||
|
|
||||||
for( const R_DATA& i : m_tables[m_series] ) // 3R Outer loop to selected primary E series table
|
|
||||||
{
|
|
||||||
if( i.e_use ) // skip all excluded values
|
|
||||||
{
|
|
||||||
for( j = 0; j < aSize; j++ ) // inner loop combines with all 2R intermediate
|
|
||||||
{ // results R+2R serial combi
|
|
||||||
tmp = m_combined_table[j].e_value + i.e_value;
|
|
||||||
tmp -= m_required_value; // calculate deviation
|
|
||||||
|
|
||||||
if( std::abs( tmp ) < std::abs( m_results[S3R].e_value ) ) // compare if better
|
|
||||||
{ // then take it
|
|
||||||
s = i.e_name; // mention 3rd component
|
|
||||||
s.append( " + ( " ); // in series
|
|
||||||
s.append( m_combined_table[j].e_name ); // with 2R combination
|
|
||||||
s.append( " )" );
|
|
||||||
m_results[S3R].e_name = s; // save S3R result
|
|
||||||
m_results[S3R].e_value = tmp; // save amount of benefit
|
|
||||||
m_results[S3R].e_use = true; // enable later use
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = i.e_value * m_combined_table[j].e_value
|
|
||||||
/ ( i.e_value + m_combined_table[j].e_value ); // calculate R + 2R parallel
|
|
||||||
tmp -= m_required_value; // calculate deviation
|
|
||||||
|
|
||||||
if( std::abs( tmp ) < std::abs( m_results[S3R].e_value ) ) // compare if better
|
|
||||||
{ // then take it
|
|
||||||
s = i.e_name; // mention 3rd component
|
|
||||||
s.append( " | ( " ); // in parallel
|
|
||||||
s.append( m_combined_table[j].e_name ); // with 2R combination
|
|
||||||
s.append( " )" );
|
|
||||||
m_results[S3R].e_name = s;
|
|
||||||
m_results[S3R].e_value = tmp; // save amount of benefit
|
|
||||||
m_results[S3R].e_use = true; // enable later use
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a 3R result with remaining deviation consider to search a possibly better
|
|
||||||
// 4R solution
|
|
||||||
// calculate 4R for small series always
|
|
||||||
if( m_results[S3R].e_use && tmp )
|
|
||||||
combine4( aSize );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void RES_EQUIV_CALC::Calculate()
|
void RES_EQUIV_CALC::Calculate()
|
||||||
{
|
{
|
||||||
uint32_t no_of_2Rcombi = 0;
|
#ifdef BENCHMARK
|
||||||
|
PROF_TIMER timer( "Resistor calculation" );
|
||||||
|
#endif
|
||||||
|
|
||||||
no_of_2Rcombi = combine2(); // combine all 2R combinations for selected E serie
|
prepare1RBuffer();
|
||||||
simple_solution( no_of_2Rcombi ); // search for simple 2 component solution
|
prepare2RBuffer();
|
||||||
|
|
||||||
if( m_results[S2R].e_value ) // if simple 2R result is not exact
|
RESISTANCE solution_2r = calculate2RSolution();
|
||||||
combine3( no_of_2Rcombi ); // continiue searching for a possibly better solution
|
m_results[S2R] = solution_2r;
|
||||||
|
|
||||||
strip3();
|
if( std::abs( solution_2r.value - m_target ) > epsilon )
|
||||||
strip4();
|
{
|
||||||
|
RESISTANCE solution_3r = calculate3RSolution();
|
||||||
|
m_results[S3R] = solution_3r;
|
||||||
|
|
||||||
|
if( std::abs( solution_3r.value - m_target ) > epsilon )
|
||||||
|
m_results[S4R] = calculate4RSolution();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef BENCHMARK
|
||||||
|
timer.Show();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<RESISTANCE> RES_EQUIV_CALC::buildSeriesData( const ESERIES::ESERIES_VALUES& aList )
|
||||||
void RES_EQUIV_CALC::strip3()
|
|
||||||
{
|
{
|
||||||
std::string s;
|
std::vector<RESISTANCE> result_list;
|
||||||
|
|
||||||
if( m_results[S3R].e_use ) // if there is a 3 term result available
|
for( double curr_decade = RES_EQUIV_CALC_FIRST_VALUE;; curr_decade *= 10.0 ) // iterate over decades
|
||||||
{ // what is connected either by two "|" or by 3 plus
|
{
|
||||||
s = m_results[S3R].e_name;
|
double multiplier = curr_decade / aList[0];
|
||||||
|
|
||||||
if( ( std::count( s.begin(), s.end(), '+' ) == 2 )
|
for( const uint16_t listvalue : aList ) // iterate over values in decade
|
||||||
|| ( std::count( s.begin(), s.end(), '|' ) == 2 ) )
|
{
|
||||||
{ // then strip one pair of braces
|
double value = multiplier * listvalue;
|
||||||
s.erase( s.find( "( " ), 2 ); // it is known sure, this is available
|
result_list.emplace_back( value, strValue( value ) );
|
||||||
s.erase( s.find( " )" ), 2 ); // in any unstripped 3R result term
|
|
||||||
m_results[S3R].e_name = s; // use stripped result
|
if( value >= RES_EQUIV_CALC_LAST_VALUE )
|
||||||
|
return result_list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RES_EQUIV_CALC::prepare1RBuffer()
|
||||||
void RES_EQUIV_CALC::strip4()
|
|
||||||
{
|
{
|
||||||
std::string s;
|
std::vector<RESISTANCE>& series = m_e_series[m_series];
|
||||||
|
m_buffer_1R.clear();
|
||||||
|
|
||||||
if( m_results[S4R].e_use ) // if there is a 4 term result available
|
for( size_t i = 0; i < series.size(); i++ )
|
||||||
{ // what are connected either by 3 "+" or by 3 "|"
|
{
|
||||||
s = m_results[S4R].e_name;
|
if( !m_exclude_mask[i] )
|
||||||
|
m_buffer_1R.push_back( series[i] );
|
||||||
if( ( std::count( s.begin(), s.end(), '+' ) == 3 )
|
|
||||||
|| ( std::count( s.begin(), s.end(), '|' ) == 3 ) )
|
|
||||||
{ // then strip two pair of braces
|
|
||||||
s.erase( s.find( "( " ), 2 ); // it is known sure, they are available
|
|
||||||
s.erase( s.find( " )" ), 2 ); // in any unstripped 4R result term
|
|
||||||
s.erase( s.find( "( " ), 2 );
|
|
||||||
s.erase( s.find( " )" ), 2 );
|
|
||||||
m_results[S4R].e_name = s; // use stripped result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RES_EQUIV_CALC::prepare2RBuffer()
|
||||||
|
{
|
||||||
|
m_buffer_2R.clear();
|
||||||
|
|
||||||
|
for( size_t i1 = 0; i1 < m_buffer_1R.size(); i1++ )
|
||||||
|
{
|
||||||
|
for( size_t i2 = i1; i2 < m_buffer_1R.size(); i2++ )
|
||||||
|
{
|
||||||
|
m_buffer_2R.push_back( serialResistanceSimple( m_buffer_1R[i1], m_buffer_1R[i2] ) );
|
||||||
|
m_buffer_2R.push_back( parallelResistanceSimple( m_buffer_1R[i1], m_buffer_1R[i2] ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort( m_buffer_2R.begin(), m_buffer_2R.end() );
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<RESISTANCE&, RESISTANCE&> RES_EQUIV_CALC::findIn2RBuffer( double aTarget )
|
||||||
|
{
|
||||||
|
// in case of NaN, return anything valid
|
||||||
|
if( std::isnan( aTarget ) )
|
||||||
|
return { m_buffer_2R[0], m_buffer_2R[0] };
|
||||||
|
|
||||||
|
// target value is often too small or too big, so check that manually
|
||||||
|
if( aTarget <= m_buffer_2R.front().value || aTarget >= m_buffer_2R.back().value )
|
||||||
|
return { m_buffer_2R.front(), m_buffer_2R.back() };
|
||||||
|
|
||||||
|
auto it = std::lower_bound( m_buffer_2R.begin(), m_buffer_2R.end(), aTarget )
|
||||||
|
- m_buffer_2R.begin();
|
||||||
|
|
||||||
|
if( it == 0 )
|
||||||
|
return { m_buffer_2R[0], m_buffer_2R[0] };
|
||||||
|
else if( it == m_buffer_2R.size() )
|
||||||
|
return { m_buffer_2R[it - 1], m_buffer_2R[it - 1] };
|
||||||
|
else
|
||||||
|
return { m_buffer_2R[it - 1], m_buffer_2R[it] };
|
||||||
|
}
|
||||||
|
|
||||||
|
RESISTANCE RES_EQUIV_CALC::calculate2RSolution()
|
||||||
|
{
|
||||||
|
SolutionCollector solution( m_target );
|
||||||
|
|
||||||
|
auto valueFunc = []( double aFoundValue )
|
||||||
|
{
|
||||||
|
return aFoundValue;
|
||||||
|
};
|
||||||
|
auto resultFunc = []( RESISTANCE& aFoundRes )
|
||||||
|
{
|
||||||
|
return aFoundRes;
|
||||||
|
};
|
||||||
|
solution.Add2RLookupResults( findIn2RBuffer( m_target ), valueFunc, resultFunc );
|
||||||
|
|
||||||
|
return solution.GetBest();
|
||||||
|
}
|
||||||
|
|
||||||
|
RESISTANCE RES_EQUIV_CALC::calculate3RSolution()
|
||||||
|
{
|
||||||
|
SolutionCollector solution( m_target );
|
||||||
|
|
||||||
|
for( RESISTANCE& r : m_buffer_1R )
|
||||||
|
{
|
||||||
|
// try r + 2R combination
|
||||||
|
{
|
||||||
|
auto valueFunc = [&]( double aFoundValue )
|
||||||
|
{
|
||||||
|
return serialValue( aFoundValue, r.value );
|
||||||
|
};
|
||||||
|
auto resultFunc = [&]( RESISTANCE& aFoundRes )
|
||||||
|
{
|
||||||
|
return serialResistance( aFoundRes, r );
|
||||||
|
};
|
||||||
|
solution.Add2RLookupResults( findIn2RBuffer( m_target - r.value ), valueFunc,
|
||||||
|
resultFunc );
|
||||||
|
}
|
||||||
|
|
||||||
|
// try r | 2R combination
|
||||||
|
{
|
||||||
|
auto valueFunc = [&]( double aFoundValue )
|
||||||
|
{
|
||||||
|
return parallelValue( aFoundValue, r.value );
|
||||||
|
};
|
||||||
|
auto resultFunc = [&]( RESISTANCE& aFoundRes )
|
||||||
|
{
|
||||||
|
return parallelResistance( aFoundRes, r );
|
||||||
|
};
|
||||||
|
solution.Add2RLookupResults(
|
||||||
|
findIn2RBuffer( m_target * r.value / ( r.value - m_target ) ), valueFunc,
|
||||||
|
resultFunc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return solution.GetBest();
|
||||||
|
}
|
||||||
|
|
||||||
|
RESISTANCE RES_EQUIV_CALC::calculate4RSolution()
|
||||||
|
{
|
||||||
|
SolutionCollector solution( m_target );
|
||||||
|
|
||||||
|
for( RESISTANCE& rr : m_buffer_2R )
|
||||||
|
{
|
||||||
|
// try 2R + 2R combination
|
||||||
|
{
|
||||||
|
auto valueFunc = [&]( double aFoundValue )
|
||||||
|
{
|
||||||
|
return serialValue( aFoundValue, rr.value );
|
||||||
|
};
|
||||||
|
auto resultFunc = [&]( RESISTANCE& aFoundRes )
|
||||||
|
{
|
||||||
|
return serialResistance( aFoundRes, rr );
|
||||||
|
};
|
||||||
|
solution.Add2RLookupResults( findIn2RBuffer( m_target - rr.value ), valueFunc,
|
||||||
|
resultFunc );
|
||||||
|
}
|
||||||
|
|
||||||
|
// try 2R | 2R combination
|
||||||
|
{
|
||||||
|
auto valueFunc = [&]( double aFoundValue )
|
||||||
|
{
|
||||||
|
return parallelValue( aFoundValue, rr.value );
|
||||||
|
};
|
||||||
|
auto resultFunc = [&]( RESISTANCE& aFoundRes )
|
||||||
|
{
|
||||||
|
return parallelResistance( aFoundRes, rr );
|
||||||
|
};
|
||||||
|
solution.Add2RLookupResults(
|
||||||
|
findIn2RBuffer( m_target * rr.value / ( rr.value - m_target ) ), valueFunc,
|
||||||
|
resultFunc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for( RESISTANCE& r1 : m_buffer_1R )
|
||||||
|
{
|
||||||
|
for( RESISTANCE& r2 : m_buffer_1R )
|
||||||
|
{
|
||||||
|
// try r1 + (r2 | 2R)
|
||||||
|
{
|
||||||
|
auto valueFunc = [&]( double aFoundValue )
|
||||||
|
{
|
||||||
|
return serialValue( r1.value, parallelValue( r2.value, aFoundValue ) );
|
||||||
|
};
|
||||||
|
auto resultFunc = [&]( RESISTANCE& aFoundRes )
|
||||||
|
{
|
||||||
|
return serialResistance( r1, parallelResistance( r2, aFoundRes ) );
|
||||||
|
};
|
||||||
|
solution.Add2RLookupResults( findIn2RBuffer( ( m_target - r1.value ) * r2.value
|
||||||
|
/ ( r1.value + r2.value - m_target ) ),
|
||||||
|
valueFunc, resultFunc );
|
||||||
|
}
|
||||||
|
|
||||||
|
// try r1 | (r2 + 2R)
|
||||||
|
{
|
||||||
|
auto valueFunc = [&]( double aFoundValue )
|
||||||
|
{
|
||||||
|
return parallelValue( r1.value, serialValue( r2.value, aFoundValue ) );
|
||||||
|
};
|
||||||
|
auto resultFunc = [&]( RESISTANCE& aFoundRes )
|
||||||
|
{
|
||||||
|
return parallelResistance( r1, serialResistance( r2, aFoundRes ) );
|
||||||
|
};
|
||||||
|
solution.Add2RLookupResults(
|
||||||
|
findIn2RBuffer( m_target * r1.value / ( r1.value - m_target ) - r2.value ),
|
||||||
|
valueFunc, resultFunc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return solution.GetBest();
|
||||||
|
}
|
||||||
|
|
|
@ -20,168 +20,142 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
#include <array>
|
|
||||||
#include "eseries.h"
|
#include "eseries.h"
|
||||||
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// First value of resistor in ohm
|
const double epsilon = 1e-12; // machine epsilon for floating-point equality testing
|
||||||
|
|
||||||
|
// First value of resistor in Ohm
|
||||||
|
// It should be first value of the decade, i.e. power of 10
|
||||||
// This value is only pertinent to the resistor calculator.
|
// This value is only pertinent to the resistor calculator.
|
||||||
// It is used to reduce the computational complexity of its calculations.
|
// It is used to reduce the computational complexity of its calculations.
|
||||||
// There are valid resistor values using E-series numbers below this
|
// There are valid resistor values using E-series numbers below this
|
||||||
// value and above the below LAST_VALUE.
|
// value and above the below LAST_VALUE.
|
||||||
#define FIRST_VALUE 10
|
#define RES_EQUIV_CALC_FIRST_VALUE 10
|
||||||
|
|
||||||
// last value of resistor in ohm
|
// Last value of resistor in Ohm
|
||||||
// This value is only pertinent to the resistor calculator. See above.
|
// This value is only pertinent to the resistor calculator. See above.
|
||||||
#define LAST_VALUE 1e6
|
#define RES_EQUIV_CALC_LAST_VALUE 1e6
|
||||||
|
|
||||||
// R_DATA handles a resistor: string value, value and allowed to use
|
// Struct representing resistance value together with its composition, e.g. {20.0, "10R + 10R"}
|
||||||
struct R_DATA
|
struct RESISTANCE
|
||||||
{
|
{
|
||||||
R_DATA() : e_use( true ), e_value( 0.0 ) {}
|
double value;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
R_DATA( const std::string& aName, double aValue )
|
RESISTANCE( double aValue = 0.0, std::string aName = "" ) :
|
||||||
|
value( aValue ), name( std::move( aName ) )
|
||||||
{
|
{
|
||||||
e_use = true;
|
|
||||||
e_name = aName;
|
|
||||||
e_value = aValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool e_use;
|
|
||||||
std::string e_name;
|
|
||||||
double e_value;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class RES_EQUIV_CALC
|
class RES_EQUIV_CALC
|
||||||
/*! \brief Performs calculations on E-series values primarily to find target values.
|
/*! \brief Performs calculations on E-series values primarily to find target values
|
||||||
|
* as combinations (serial, parallel) of them.
|
||||||
*
|
*
|
||||||
* E_SERIES class stores and performs calcuations on E-series values. It currently
|
* RES_EQUIV_CALC class stores and performs calcuations on E-series values. It currently
|
||||||
* is targeted toward the resistor calculator and hard codes some limitations
|
* is targeted toward the resistor calculator and hard codes some limitations
|
||||||
* to optimize its use in the resistor calculator.
|
* to optimize its use in the resistor calculator.
|
||||||
*
|
*
|
||||||
* At this time these limitations are that this class ignores all E-series larger
|
* At this time these limitations are that this class handles only E-series up to
|
||||||
* than E24 and it does not consider resistor values below 10 Ohm or above 1M Ohm.
|
* E24 and it does not consider resistor values below 10 Ohm or above 1M Ohm.
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RES_EQUIV_CALC();
|
RES_EQUIV_CALC();
|
||||||
|
|
||||||
/**
|
|
||||||
* This calculator suggests solutions for 2R, 3R and 4R replacement combinations
|
|
||||||
*/
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
S2R,
|
S2R,
|
||||||
S3R,
|
S3R,
|
||||||
S4R
|
S4R,
|
||||||
|
NUMBER_OF_LEVELS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set E-series to be used in calculations.
|
||||||
|
* Correct values are from 0 to 4 inclusive,
|
||||||
|
* representing series (consecutively) E1, E3, E6, E12, E24.
|
||||||
|
* After changing the series, NewCalc must be called before running calculations.
|
||||||
|
*/
|
||||||
|
void SetSeries( uint32_t aSeries );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize next calculation, clear exclusion mask
|
||||||
|
* and erase results from previous calculation.
|
||||||
|
*
|
||||||
|
* @param aTargetValue is the value (in Ohms) to be looked for
|
||||||
|
*/
|
||||||
|
void NewCalc( double aTargetValue );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If any value of the selected E-series not available, it can be entered as an exclude value.
|
* If any value of the selected E-series not available, it can be entered as an exclude value.
|
||||||
*
|
*
|
||||||
* @param aValue is the value to exclude from calculation
|
* @param aValue is the value (in Ohms) to exclude from calculation
|
||||||
* Values to exclude are set to false in the selected E-series source lookup table
|
* Values to exclude are set to true in the current exclusion mask and will not be
|
||||||
|
* considered during calculations.
|
||||||
*/
|
*/
|
||||||
void Exclude( double aValue );
|
void Exclude( double aValue );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initialize next calculation and erase results from previous calculation
|
* Executes all the calculations.
|
||||||
*/
|
* Results are to be retrieved using GetResults (below).
|
||||||
void NewCalc();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* called on calculate button to execute all the 2R, 3R and 4R calculations
|
|
||||||
*/
|
*/
|
||||||
void Calculate();
|
void Calculate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for CheckBox, RadioButton, RequriedResistor and calculated Results
|
* Accessor to calculation results.
|
||||||
|
* Empty std::optional means that the exact value can be achieved using fewer resistors.
|
||||||
*/
|
*/
|
||||||
void SetSeries( uint32_t aSeries ) { m_series = aSeries; }
|
const std::array<std::optional<RESISTANCE>, NUMBER_OF_LEVELS>& GetResults()
|
||||||
void SetRequiredValue( double aValue ) { m_required_value = aValue; }
|
{
|
||||||
|
return m_results;
|
||||||
// Accessor:
|
}
|
||||||
const std::array<R_DATA, S4R + 1>& GetResults() { return m_results; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Add values from aList to m_tables. Covers all decades between FIRST_VALUE and LAST_VALUE.
|
* Add values from aList to m_e_series tables.
|
||||||
* @return the count of items added to m_tables.
|
* Covers all decades between FIRST_VALUE and LAST_VALUE.
|
||||||
*/
|
*/
|
||||||
int buildSeriesData( const ESERIES::ESERIES_VALUES );
|
std::vector<RESISTANCE> buildSeriesData( const ESERIES::ESERIES_VALUES& aList );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build all 2R combinations from the selected E-series values
|
* Build 1R buffer, which is selected E-series table with excluded values removed.
|
||||||
*
|
*/
|
||||||
* Pre-calculated value combinations are saved in intermediate look up table m_combined_table
|
void prepare1RBuffer();
|
||||||
* @return is the number of found combinations what also depends from exclude values
|
|
||||||
*/
|
|
||||||
uint32_t combine2();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for closest two component solution
|
* Build 2R buffer, which consists of all possible combinations of two resistors
|
||||||
*
|
* from 1R buffer (serial and parallel), sorted by value.
|
||||||
* @param aSize is the number of valid 2R combinations in m_combined_table on where to search
|
*/
|
||||||
* The 2R result with smallest deviation will be saved in results
|
void prepare2RBuffer();
|
||||||
*/
|
|
||||||
void simple_solution( uint32_t aSize );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if there is a better 3 R solution than previous one using only two components.
|
* Find in 2R buffer two values nearest to the given value (one smaller and one larger).
|
||||||
*
|
* It always returns two valid values, even for input out of range or Nan.
|
||||||
* @param aSize gives the number of available combinations to be checked inside
|
|
||||||
* m_combined_table. Therefore m_combined_table is combined with the primary
|
|
||||||
* E-series look up table. The 3R result with smallest deviation will be saved
|
|
||||||
* in results if better than 2R
|
|
||||||
*/
|
*/
|
||||||
void combine3( uint32_t aSize );
|
std::pair<RESISTANCE&, RESISTANCE&> findIn2RBuffer( double aTargetValue );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if there is a better four component solution.
|
* Calculate the best combination consisting of exactly 2, 3 or 4 resistors.
|
||||||
*
|
|
||||||
* @param aSsize gives the number of 2R combinations to be checked inside m_combined_table
|
|
||||||
* Occupied calculation time depends from number of available E-series values with the power
|
|
||||||
* of 4 why execution for E12 is conditional with 4R check box for the case the previously
|
|
||||||
* found 3R solution is already exact
|
|
||||||
*/
|
*/
|
||||||
void combine4( uint32_t aSize );
|
RESISTANCE calculate2RSolution();
|
||||||
|
RESISTANCE calculate3RSolution();
|
||||||
/*
|
RESISTANCE calculate4RSolution();
|
||||||
* Strip redundant braces from three component result
|
|
||||||
*
|
|
||||||
* Example: R1+(R2+R3) become R1+R2+R3
|
|
||||||
* and R1|(R2|R3) become R1|R2|R3
|
|
||||||
* while R1+(R2|R3) or (R1+R2)|R3) remains untouched
|
|
||||||
*/
|
|
||||||
void strip3();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Strip redundant braces from four component result
|
|
||||||
*
|
|
||||||
* Example: (R1+R2)+(R3+R4) become R1+R2+R3+R4
|
|
||||||
* and (R1|R2)|(R2|R3) become R1|R2|R3|R4
|
|
||||||
* while (R1+R2)|(R3+R4) remains untouched
|
|
||||||
*/
|
|
||||||
void strip4();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::vector<R_DATA>> m_tables;
|
std::vector<std::vector<RESISTANCE>> m_e_series;
|
||||||
|
std::vector<bool> m_exclude_mask;
|
||||||
|
std::vector<RESISTANCE> m_buffer_1R;
|
||||||
|
std::vector<RESISTANCE> m_buffer_2R;
|
||||||
|
|
||||||
/* Note: intermediate calculations use m_combined_table
|
uint32_t m_series = ESERIES::E6;
|
||||||
* if the biggest list is En, reserved array size should be 2*En*En of std::vector primary list.
|
double m_target = 0;
|
||||||
* 2 component combinations including redundant swappable terms are for the moment
|
|
||||||
* ( using values between 10 ohms and 1Mohm )
|
|
||||||
* 72 combinations for E1
|
|
||||||
* 512 combinations for E3
|
|
||||||
* 1922 combinations for E6
|
|
||||||
* 7442 combinations for E12
|
|
||||||
* 29282 combinations for E24
|
|
||||||
*/
|
|
||||||
std::vector<R_DATA> m_combined_table; // intermediate 2R combinations
|
|
||||||
|
|
||||||
std::array<R_DATA, S4R + 1> m_results; // 2R, 3R and 4R results
|
std::array<std::optional<RESISTANCE>, NUMBER_OF_LEVELS> m_results;
|
||||||
uint32_t m_series = ESERIES::E6; // Radio Button State
|
};
|
||||||
double m_required_value = 0.0; // required Resistor
|
|
||||||
};
|
|
Loading…
Reference in New Issue