energy note

色々と勉強中なので備忘録として。

(改)合成抵抗の組み合わせを求めるプログラム

以前このような記事を書きました。
energy-note.hatenablog.com
最近いろいろと工作を進める中で、オペアンプ回路とかで使う中途半端な抵抗値を求めるためにこのプログラムを使っていたところ、色々と問題点が見えてきました。
勉強&とりあえず急ぎで欲しかったからとはいえちょっと雑すぎたかぁと。
微妙にかゆいところに手が届かないし、構成が限られているし・・・ということでほぼ書き直しました。
今回も前回の記事同様の呼び名を使います。
f:id:energy_note:20200927232018p:plain
f:id:energy_note:20200927232042p:plain
f:id:energy_note:20200927232053p:plain

#Created 2021/6/13
import itertools
from tqdm import tqdm
import pandas as pd

#---------------------------------パラメータ
#持ち値
have_value = [22,100,200,'1k','2k']
#求めたい抵抗値Ω
set_value = 1005
#許容誤差±Ω
allowable_error = 1
#並列構成要素外直列数
max_para_cnt = 2
#最大直列数(並列構成要素)
st_seri_para_cnt = 2
#最大並列数
st_seri_cnt = 2
#---------------------------------

#接頭語を変換する
for i in range(len(have_value)):
    if type(have_value[i]) is str:
        if 'k' in have_value[i]:
            have_value[i] = round(float(have_value[i].replace('k','')) * 10**3)
            continue
        if 'M' in have_value[i]:
            have_value[i] = round(float(have_value[i].replace('M','')) * 10**6)
            continue
        if 'G' in have_value[i]:
            have_value[i] = round(float(have_value[i].replace('G','')) * 10**9)
            continue
        if 'm' in have_value[i]:
            have_value[i] = round(float(have_value[i].replace('m','')) * 10**-3,6)
            continue

#---------------------------------

#https://note.nkmk.me/python-list-flatten/ より引用
#リスト・タプルの構成要素数を調べる関数
def flatten_list(l):
    for el in l:
        if isinstance(el, list):
            yield from flatten_list(el)
        else:
            yield el

#並列合成抵抗を求める
def parallel(array):
    r = 0
    for i in array:
        r += 1 / i
    return(1 / r)

def main():
    print('スタート')
    df = pd.DataFrame(columns=['合成抵抗値', '組み合わせ','抵抗使用個数', '誤差', '誤差絶対値'])
    
    #最大直列数(並列構成要素)計算
    seri = [[]]*st_seri_para_cnt
    seri_calc = [[]]*st_seri_para_cnt
    for para_val in range(st_seri_para_cnt):
        seri[para_val] = []
        seri_calc[para_val] = []
        for i in itertools.combinations_with_replacement(have_value,para_val+1):
            seri[para_val].append(list(i))
            #計算処理用
            seri_calc[para_val].append(round(sum(i),6))
    print('直列数計算完了')

    #最大並列数計算
    st_para = []
    st_para_calc = []
    #組み合わせ結果を展開
    for i in seri:
        st_para+=i
    for i in seri_calc:
        st_para_calc+=i

    para_list = []
    para_list_calc = []
    for i in itertools.combinations_with_replacement(st_para,st_seri_cnt):
        para_list.append(list(i))
    
    #直列要素の中で設定値を超える値があったら削除する
    para_index = 0
    cut_cnt = 0
    for i in itertools.combinations_with_replacement(st_para_calc,st_seri_cnt):
        #並列合成抵抗計算
        paracal = round(parallel(list(i)),6)
        if paracal < set_value:
            #計算処理用
            #直列要素は設定値以下でないと拾わない
            para_list_calc.append(paracal)
        else:
            #構成保存用リストから削除する
            para_list.pop(para_index-cut_cnt)
            #削除した分リスト数が減る
            cut_cnt+=1
        para_index+=1
    
    print('並列要素計算完了')

    #最大直列数計算
    out_list = list(itertools.combinations_with_replacement(have_value+para_list,max_para_cnt))
    out_list_calc = list(itertools.combinations_with_replacement(have_value+para_list_calc,max_para_cnt))
    print('最終計算中')
    #すべての結果を合成する
    for i in tqdm(range(len(out_list_calc))):
        result = round(sum(out_list_calc[i]),6)
        err = abs(set_value-result)
        #設定した許容値に収まっていたらデータフレームに格納
        if err<=allowable_error:
            df = df.append({'合成抵抗値': result, '組み合わせ': out_list[i],'使用個数':len(list(flatten_list(out_list[i]))), '誤差': round((set_value-result) * 100 / set_value,6), '誤差絶対値': err}, ignore_index=True)

    #誤差でソート
    df.sort_values('誤差絶対値', inplace=True)
    #インデックスの振り直し
    df = df.reset_index(drop=True)
    #たぶん使っても上位10個くらいだろう
    if len(df['合成抵抗値'])<10:
        cnt = len(df['合成抵抗値'])
    else:
        cnt = 10
    print('計算結果数:',len(df['合成抵抗値']),'組み合わせ')
    #結果表示
    for i in range(cnt):
        combi = ''
        for ii in df['組み合わせ'][i]:
            #構成要素展開
            combi = combi + str(ii) +' + '
        print('合成抵抗値:'+str(df['合成抵抗値'][i])+' 組み合わせ:'+str(combi[:-3])+' 使用個数:'+str(df['使用個数'][i])+' 誤差:'+str(df['誤差'][i])+'%')

if __name__ == '__main__':
    main()

Python3.7.5+Jupyter Notebookで書いてます。
have_valueに持っている抵抗値をリスト形式の単位はΩで記入します。
求めたい抵抗値と許容誤差±を単位をΩで記入します。
最大並列数や最大直列数(並列構成要素)、並列構成要素外直列数とかは上記の画像の通りです。
あとは実行してもらえると誤差が低い順から10個表示されます。dfデータフレームにすべての計算結果が入っています。
前記事同様に出力される表記の構成はこのようになっています。
[[10, 10000], [5100, 750000]]+1
f:id:energy_note:20200929223608p:plain

今回、結構コードを変えました。
まず、1kや2.2Mなど接頭語を使った表現も使えるようにしました。

have_value = [1,2,5.1,10,20,'1k','2.2M']

のように文字列としてリストに入れてもらい、キロは小文字のk、メガは大文字のM、ギガは大文字のG、ミリは小文字のmを使えるようにしました。
遅い原因だった?であろうforのネストを取り払いました。一応コード内にはいますが、メインの計算部分ではないのでいいこととしますw
組み合わせの数が増えているかと思います。
後は抵抗の使用個数とかで制限もしようと考えましたが、結局は精度を求めて組み合わせるので、別にいいか、と機能はつけませんでした。
作り出すと止まらなくはなりますが、何せまだまだなので、効率的・効果的な書き方等ご指摘いただきましたら幸いです・・・
今回も一応結果のチェックはしてありますが、念のため検算はしてから接続しますようお願いします。
今回のコードは組み合わせの数が増えた分、特に直列数を増やすと組み合わせ数がすごい勢いで増えていくので、持っている抵抗数と回路数はある程度に抑えるのが実用的かと思います。



今回、組み合わせを色々とみててふと、誤差について気にしだしました。1000Ω±1%だった場合、1000±10Ωとなるわけですが、合成抵抗で1005Ωが欲しい!とかなった場合、「1000Ω±1% + 5Ω±1%」としても、元々の誤差に埋もれてしまうのでは・・・?
あるいは合成抵抗値で1005.001Ωとかいう精度で合成できたとしても、誤差を含む抵抗値をさらに合成しているわけだから・・・精度が悪いのか!?いいのか!?とせっかく作ったプログラムに対して疑問を持ち始めました。
cc.cqpub.co.jp
調べてみると、なるほど。組み合わせる数が多いほど誤差の分布が改善されると。
これを「誤差伝播」というそうで。なるほど。「四則演算 誤差伝播」などとぐぐってもらうといろいろなサイトが出てきます。
例えば100Ω±1%を2個つなげて200Ωを作った場合、100±1Ωと100±1Ωなので200Ω±2Ωではなく、

(100\pm1)+(100\pm1)=(100+100)\pm\sqrt{(1^2+1^2)}\\
=(200\pm1.414)
となって確率的に若干ばらつきが抑えられるわけですね。
200Ω±2Ωは最悪ケースの場合であって、2個とも誤差MAXである可能性はそもそも低く、数が増えるとその確率はもっと低くなると。・・・じゃぁ直列数を増やせば若干良くなる!?
実際に工作で使うときには量産とかでなければテスターで測ってください・・・今回はこの辺にしておきます。
(誤差と定格電力の計算もできるとさらによいのか・・・?)

※以前も書きましたが旧JIS派です。
こちら参考にさせていただきました。
直列抵抗の誤差leansixsigmastudy.wordpress.com
note.nkmk.me