梯子型回路フィルタの周波数特性(csvファイルから読み込み)

モジュールのインポート

import numpy as np
import matplotlib.pyplot as plt
import csv
import scienceplots
#Warning : As of version 2.0.0, you need to add import scienceplots before setting the style (plt.style.use('science')).
plt.style.use(['science', 'notebook'])

ユーザ関数

複素電圧などから絶対値,デジベル(dB),偏角(deg)の計算は,
def dbdeg_unwrap(z): # 複素係数(複素電圧や複素電界等)から絶対値,デジベル(dB),偏角(deg)の計算
    q = 180.0/np.pi
    amp = np.abs(z)
    db = 20.0*np.log10(amp)
    rad = np.angle(z)
#    rad = np.unwrap(rad, discont=np.pi)
    rad = np.unwrap(rad)
    deg = rad*q
    return amp,db,deg
基本行列による直列素子,並列素子の縦続接続は,
def ladder_f(izy, zzyy): # izy = 0(sereis) or 1(shunt) first element
    n, n_s = zzyy.shape
    a = np.ones(n_s)
    d = np.ones(n_s)
    b = np.zeros(n_s)
    c = np.zeros(n_s)
    for i in range(n):
        zy = zzyy[i][:]
        if np.mod(i,2) == izy: # sereis element
            ai = 1.0*a
            bi = a*zy+b
            ci = 1.0*c
            di = c*zy+d
        else: #  shunt element
            ai = a+b*zy
            bi = 1.0*b
            ci = c+d*zy
            di = 1.0*d
        a = ai
        b = bi
        c = ci
        d = di
    return a, b, c, d
2端子対回路の基本行列要素から,動作伝送係数 $S_B$,反射係数 $\Gamma$,特性関数 $K$は,
# a, b, c, d: 2端子対回路の[F]行列の要素
# r1, r2: 入出力の終端抵抗
def f_to_sb(a, b, c, d, r1, r2):
    ab = a*r2 + b
    cd = c*r1*r2 + d*r1
#    sb = (ab+cd)/2.0/np.sqrt(r1*r2)
    sbi = 2.0*np.sqrt(r1*r2)/(ab+cd)
    sb = 1.0/sbi
#    gamma = (ab-cd)/(ab+cd)
    gamma = 1-2.0*cd/(ab+cd)
    k = sb*gamma
    return sb,gamma,k,sbi # 動作伝送係数S_B,反射係数Gamma,特性関数K
インダクタンス,キャパシタンスから直列素子のインピーダンス,並列素子のアドミタンスを求めるため,
def immittance_values(ip, omega, gk_f, gk_f2):
    n_omega = len(omega)
    s = 1.0j*omega
    zzyy = np.empty((0,n_omega))
    for i in range(1,n+1):
        if ip == 1:
            zy = s*gk_f[i]
        elif ip == 2:
            zy = 1.0/s/gk_f[i]
        elif ip == 3:
            zy = s*gk_f[i] + 1.0/s/gk_f2[i]
        elif ip == 4:
            zyi = s*gk_f[i] + 1.0/s/gk_f2[i]
            zyi = np.where(zyi==0.0, 1.0e-50, zyi)
            zy = 1.0/zyi
        zzyy = np.append(zzyy, [zy], axis=0)
    return zzyy
低域通過特性と有する原型の梯子型回路から,高域通過特性,帯域通過特性,帯域阻止特性に周波数変換するため,
def element_values(gk, ip):
    n2 = len(gk)
    n = n2-2
    gk_f = np.ones(n2)
    gk_f2 = np.ones(n2)
    gk_f[n+1] = 1.0*gk[n+1]
    gk_f2[n+1] = 1.0*gk[n+1]
    if ip == 1:
        gk_f[1:n+1] = 1.0*gk[1:n+1] # Normalized element values for lowpass response
    elif ip == 2:
        gk_f[1:n+1] = 1.0/gk[1:n+1] # Normalized element values for highpass response
    elif ip == 3:
        gk_f[1:n+1] = gk[1:n+1]/rbw # Normalized element values for bandpass response
        gk_f2[1:n+1] = 1.0/gk_f[1:n+1]
    elif ip == 4:
        gk_f[1:n+1] = 1.0/rbw/gk[1:n+1] # Normalized element values for bandstop response
        gk_f2[1:n+1] = 1.0/gk_f[1:n+1]
    print(f'{n:2d}',end=' ')
    [print(f"{gk_f[j]:6.4f}",end=' ') for j in range(n+2)]
    print('')
    if ip >= 3:
        print(f'{n:2d}',end=' ')
        [print(f"{gk_f2[j]:6.4f}",end=' ') for j in range(n+2)]
        print('')
    return gk_f, gk_f2
計算値のフォーマット付出力のために,
# 可変長引数*args(複数の引数をタプルとして受け取る) 
# 可変長引数**kwargs(複数の引数を辞書として受け取る)
def table_ss(x, *args, **kwargs): 
    nx = len(x)
    ny = len(args)
    for k in kwargs.values():
        print(f'{k:>14s}',end='')
    print('')
    for i in range(nx):
        print(f'{x[i]:14.3f}',end='')
        for j in range(ny):
            print(f'{args[j][i]:14.3f}',end='')
        print('')
    return

周波数特性の計算

特性関数として,0.5dBの等リプル特性を選択し,csvファイルから規格化素子値を読み込むと,
ic = 2 # 特性関数の選択
if ic == 1:
    htype = 'maximally_flat'
    hhtype = 'maximally flat'
    csv_filename = 'element_values_maximally_flat.csv'
elif ic == 2:
    htype = 'equal_ripple_0_5dB'
    hhtype = 'equal ripple (0.5dB)'
    csv_filename = 'element_values_equal_ripple_0_5dB.csv'
#    csv_filename = 'element_values_equal_ripple_20dB.csv'
with open(csv_filename) as file: # ファイルのオープン
    reader = csv.reader(file, quoting=csv.QUOTE_NONNUMERIC) # csvファイルからの読み込み
    sst = [row for row in reader]
梯子型回路の段数を設定して,
n_ = 7 # csvファイルのデータの中から段数の指定して配列に代入
for i in range(len(sst)):
    if len(sst[i])==n_+2:
        gk = np.array(sst[i])
n = n_
n, gk
(7,
 array([1.        , 1.73734225, 1.25821857, 2.63834489, 1.34431239,
        2.63834489, 1.25821857, 1.73734225, 1.        ]))
フィルタの周波数応答として,低域通過特性,高域通過特性,帯域通過特性,帯域阻止特性の中から一つ選択して,
# 周波数応答の選択
# ip = 1 # low pass
ip = 2 # high pass
# ip, rbw = 3, 1.0 # band pass
# ip, rbw = 4, 1.0 # band stop
if ip == 1:
    ftype = 'lowpass'
elif ip == 2:
    ftype = 'highpass'
elif ip == 3:
    ftype = 'bandpass'
elif ip == 4:
    ftype = 'bandstop'
gk_f, gk_f2 = element_values(gk, ip)
print(f'{"N":>2s}',end=' ')
[print(f"{'g':>2s}",f"{j:<3.0f}",end=' ') for j in range(n+2)]
7 1.0000 0.5756 0.7948 0.3790 0.7439 0.3790 0.7948 0.5756 1.0000 
N  g 0    g 1    g 2    g 3    g 4    g 5    g 6    g 7    g 8
双対的な二つの回路から,一つを選択して基本行列を求めると,
izy = 0 # dual(0 or 1): 双対的な二つの回路の選択
n_omega = 601
omega_log10 = np.linspace(-1.0, 1.0, n_omega) # logスケールで等間隔
omega = 10.0**omega_log10
omega = np.where(omega==0.0, 1.0e-100, omega)
zzyy = immittance_values(ip, omega, gk_f, gk_f2)
aa, bb, cc, dd = ladder_f(izy, zzyy) # izy = 0(sereis) or 1(shunt) first element
入出力の終端抵抗の値は,
r1 = gk_f[0]
r2 = gk_f[n+1]
r1,r2
(1.0, 1.0)
基本行列から動作伝送係数 $S_B$,反射係数 $\Gamma$ を計算して,
# 基本行列[F]から動作伝送係数S_B,反射係数の計算
sb,gamma,kk, sbi = f_to_sb(aa, bb, cc, dd, r1, r2)
# sb_amp,sb_db,sb_deg = dbdeg_unwrap(sb)
sbi_amp,sbi_db,sbi_deg = dbdeg_unwrap(sbi)
sb_db, sb_deg = -sbi_db, -sbi_deg
gamma_amp,gamma_db,gamma_deg = dbdeg_unwrap(gamma)
プロットするときのカラーを段数に応じて決めておくと,
mycolor = ['SteelBlue','SeaGreen','DarkOrange','Crimson', 'RebeccaPurple', \
           'SaddleBrown', 'DarkKhaki', 'MediumVioletRed', 'DarkSlateGray']
icr = n-2
計算した数値データから,確認のため,一部,出力すると,遮断角周波数 $\Omega=1$ において等リプル値の0.5dBになっていることがわかる.
ns = 40
table_ss(omega[::ns], gamma_db[::ns], gamma_deg[::ns], sb_db[::ns], sb_deg[::ns], \
         z0='Omega', z1='Gamma[dB]', z2='Arg Gamma[deg]', z3='-SB[dB]', z4='Arg 1/SB[deg]')
 Omega     Gamma[dB]Arg Gamma[deg]       -SB[dB] Arg 1/SB[deg]
 0.010         0.000        -0.660      -306.987       -90.660
 0.016        -0.000        -1.045      -278.985       -91.045
 0.025        -0.000        -1.657      -250.979       -91.657
 0.040         0.000        -2.627      -222.964       -92.627
 0.063         0.000        -4.167      -194.928       -94.167
 0.100        -0.000        -6.619      -166.836       -96.619
 0.158        -0.000       -10.546      -138.603      -100.546
 0.251        -0.000       -16.946      -110.006      -106.946
 0.398        -0.000       -27.866       -80.422      -117.866
 0.631        -0.000       -49.444       -47.760      -139.444
 1.000        -9.635      -188.822        -0.500      -278.822
 1.585        -9.653       -43.245        -0.498      -493.245
 2.512       -20.484        46.621        -0.039      -583.379
 3.981        -9.802        -5.104        -0.480      -635.104
 6.310       -10.482       -35.034        -0.407      -665.034
10.000       -13.158       -54.566        -0.215      -684.566
反射係数 $\Gamma$,透過係数 $1/S_B$ のデジベル値の周波数特性をプロットすると,右下の図より等リプル0.5dBを実現できていることがわかる.
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12, 4)) # nrows=縦に並べる数,ncols=横に並べる数
[ax[j].set_xlabel("Normalized angular frequency $\Omega$", fontsize=12) for j in range(2)] # x軸のラベル設定
[ax[j].set_ylim(0.01, 10.0) for j in range(2)] # x軸範囲の設定
[ax[j].set_ylabel("Relative power [dB]", fontsize=12) for j in range(2)] # y軸のラベル設定
#[ax[j].set_ylim(-40.0, 0.0) for j in range(2)] # y軸範囲の設定
ax[0].set_ylim(-40.0, 0.0) # y軸範囲の設定
ax[1].set_ylim(-2.0, 0.0) # y軸範囲の設定
[ax[j].set_xscale('log') for j in range(2)] # log スケール
[ax[j].grid(color = "gray", linestyle="--") for j in range(2)]
[ax[j].minorticks_on() for j in range(2)] # 補助目盛りをつける
ax[0].text( 0.015, -5, hhtype, fontsize=12)
ax[0].text( 0.25, -5, f"$N=${n:.0f}", fontsize=12)
ax[0].text( 2.5, -5, ftype, fontsize=12)
ax[0].plot(omega, gamma_db, label=r"$20 \log_{10}|\Gamma|$", color=mycolor[icr])
ax[1].plot(omega, -sb_db, label=r"$-20 \log_{10}|S_B|$", color=mycolor[icr])
[ax[j].legend(ncol=1, loc='lower right', fancybox=False, frameon = True, fontsize=12) for j in range(2)]
fig.tight_layout()
fig.savefig(htype+'_'+ftype+f'_N{n:.0f}_SB_dB.pdf')
plt.show()

また,反射・透過係数の位相[deg]の周波数特性をプロットすると,
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(12, 4)) # nrows=縦に並べる数,ncols=横に並べる数
[ax[j].set_xlabel("Normalized angular frequency $\Omega$", fontsize=12) for j in range(2)] # x軸のラベル設定
[ax[j].set_ylim(0.01, 10.0) for j in range(2)] # x軸範囲の設定
[ax[j].set_ylabel("Phase [deg]", fontsize=12) for j in range(2)] # y軸のラベル設定
[ax[j].set_ylim(-1440.0, 360.0) for j in range(2)] # y軸範囲の設定
[ax[j].set_xscale('log') for j in range(2)] # log スケール
[ax[j].grid(color = "gray", linestyle="--") for j in range(2)]
[ax[j].minorticks_on() for j in range(2)] # 補助目盛りをつける
[ax[j].set_yticks(np.arange(-1440.0, 540.0, step=360.0)) for j in range(2)]
[ax[j].text( 0.015, 180.0, hhtype, fontsize=12) for j in range(2)]
[ax[j].text( 0.25, 180.0, f"$N=${n:.0f}", fontsize=12) for j in range(2)]
[ax[j].text( 2.5, 180.0, ftype, fontsize=12) for j in range(2)]
ax[0].plot(omega, gamma_deg, label=r"Arg $\Gamma$", color=mycolor[icr], marker = '.', linewidth=0)
ax[1].plot(omega, -sb_deg, label=r"Arg $1/S_B$", color=mycolor[icr], marker = '.', linewidth=0)
[ax[j].legend(ncol=1, loc='lower left', fancybox=False, frameon = True, fontsize=12) for j in range(2)]
plt.tight_layout()
fig.savefig(htype+'_'+ftype+f'_N{n:.0f}_SB_deg.pdf')
plt.show()

段数を変えて計算すると,例えば,チェビシェフ特性を有する5段の高域通過特性では,

また,チェビシェフ特性を有する3段の高域通過特性では,

同様にして,ip=1に設定し,低域通過特性の周波数特性(デジベル値)を計算すると,

また,ip=3に設定し,比帯域$1.0$の帯域通過特性に周波数変換して周波数特性(デジベル値)を計算すると,

チェビシェフ特性を有する5段の帯域通過特性では,ic=2,ip=3,n=5として,

また,チェビシェフ特性を有する7段の帯域通過特性では,ic=2,ip=3,n=7として,

さらに,チェビシェフ特性を有する7段の帯域阻止特性では,ic=2,ip=4,n=7として,

最低平坦特性を有する7段の帯域阻止特性も計算でき,ic=1,ip=4,n=7として,

最低平坦特性を有する7段の帯域通過特性では,ic=1,ip=3,n=7として,

最低平坦特性を有する7段の高域通過特性では,ic=1,ip=2,n=7として,

最低平坦特性を有する7段の低域通過特性では,ic=1,ip=1,n=7として,