ミニマックス法(Parks-McCllelanのアルゴリズム)によるFIRフィルタの設計



clear; close all;

global delta_p delta_s;
delta_p = 1;
delta_s = 1;
global wc;
wc = 0.5*pi;
wd = 0.6*pi;

N = 31;
M = (N-1)/2;

%STEP1: ωiを初期化
%ωiを初期化: 当分割
epsilon = 0.001;
if mod(M,2)==1
    %M=奇数
    wi_left = linspace(0+epsilon,wc,(M+1)/2+1);
    wi_right = linspace(wd,pi-epsilon, (M+1)/2);
else
    %M=偶数
    wi_left = linspace(0,wc,(M+2)/2);
    wi_right = linspace(wd,pi, (M+2)/2);
end
wi = [wi_left, wi_right];

%STEP2: Qを計算
num = 0;
den = 0;

for i=0:M+1
    alpha_i = 1;
    for k=0:M+1
        if k==i
            continue
        else
            alpha_i = alpha_i * 1/(cos(wi(k+1))-cos(wi(i+1)));
        end
    end
    num = num + alpha_i * D(wi(i+1));
    den = den + ((-1)^i * alpha_i) / W(wi(i+1));
end
Q = num / den;

%STEP3: H(ωi)を計算し、さらにH(ω)を計算する。
%H(ωi)の計算
Hwi = zeros(size(wi));
for i=0:M+1
    Hwi(i+1) = D(wi(i+1)) - (-1)^(i)*Q/W(wi(i+1));
end
figure();
plot(wi, Hwi,'o-');
title('H(wi)');

%H(ω)の計算
syms w;
H_num = 0;
for i=0:M+1
    gamma_i = 1;
    for k=0:M
        if k==i
            continue
        else
            gamma_i = gamma_i * 1/(cos(wi(k+1)-cos(wi(i+1))));
        end
    end
    H_num = H_num + (gamma_i / (cos(w)-cos(wi(i+1)))) * Hwi(i+1);
end
H_den = 0;
for i=0:M+1
    gamma_i = 1;
    for k=0:M
        if k==i
            continue
        else
            gamma_i = gamma_i * 1/(cos(wi(k+1)-cos(wi(i+1))));
        end
    end
    H_den = H_den + gamma_i / (cos(w)-cos(wi(i+1)));
end
H = H_num / H_den;

%STEP4: E=W•(H-D)の計算
omega = linspace(0,pi,500);
E = zeros(size(omega));
for i=1:length(omega)
    val = W(omega(i)) * (subs(H,w,omega(i))-D(omega(i)));
    E(i) = vpa(val);
end

%STEP5: |E|と|Q|の比較
if abs(E)<= Q
    return
else
    disp('Continue Parks-McCllalen Algorithm')
end

figure();
plot(omega, E);
[E_pks,wi_locs] = findpeaks(E);
title('E');

%STEP6: E(w)の極値を与えるwiとその個数を見出す
wi2 = omega(wi_locs);
L = length(wi2);

%STEP7: 極値の個数Lによって場合分けを行う
if L==(M+2)
    disp('L=M+2. Proceed to Step8');
elseif L==(M+3)
    disp('L=M+3. Proceed to Step8');
else
    lab = sprintf('L=%d. M=%d. M+2=%d. M+3=%d. Computational error',L,M,M+2,M+3);
    disp(lab);
    return;
end

%STEP8: Qの再計算をする
num = 0;
den = 0;

for i=0:M+1
    alpha_i = 1;
    for k=0:M+1
        if k==i
            continue
        else
            alpha_i = alpha_i * 1/(cos(wi2(k+1))-cos(wi2(i+1)));
        end
    end
    num = num + alpha_i * D(wi2(i+1));
    den = den + ((-1)^i * alpha_i) / W(wi2(i+1));
end
Q2 = num / den;

%STEP9: Qに変化があったかを判定する
if abs(Q-Q2)>1e-5
    disp('Q changed.');
else
    disp('NO change in Q.');
    return
end

%STEP10: fiの計算
%fiを求める式 Σ_k fk cos(k•wi) = G(wi), i=0
% i=0: f0•cos(0•w0) + f1•cos(1•w0) + ... + fM•cos(M•w0) = G(w0)
% i=1: f0•cos(0•w1) + f1•cos(1•w1) + ... + fM•cos(M•w1) = G(w1)
% i=M: f0•cos(0•wM) + f1*cos(1•wM) + ... + fM•cos(M•wM) = G(wM)
% i=M+1: f0•cos(0•w_M+1) + f1*cos(1•w_M+1) + ... + fM•cos(M•w_M+1) = G(wM+1)
% これらは以下の連立方程式に変形できる
%(M+2)x(M+1) 行列                        (M+1)x1 ベクトル (M+2)x1 ベクトル
% [cos(0•w0) cos(1•w0)     ...   cos(M•w0)] [ f0  ]    [ G(w0) ]
% [cos(0•w1) cos(1•w1)     ...   cos(M•w1)] [ f1  ]    [ G(w1) ]
% [                                       ] [     ]  = [       ]
% [cos(0•wM) cos(1•wM)     ...   cos(M•wM)] [ fM  ]    [ G(wM) ]
% [cos(0•wM+1) cos(1•wM+1) ... cos(M•wM+1)]            [ G(wM+1) ]
% C • f = Gとすると、
% f = inv(C)•Gで代数的に求まる。

C = zeros(M+2, M+1);
for k=0:M+1
    for i=0:M
        C(k+1,i+1) = cos(k*wi2(i+1));
    end
end

G = zeros(M+2, 1);
for i=0:M+1
    G(i+1) = D(wi2(i+1)) - (-1)^i*Q2/W(wi2(i+1));
end

f = C\G;

h_head = flip(f(2:end) / 2); 
h_tail = f(1);
h = [h_head; h_tail];%縦ベクトル

c = w*zeros(M,1);
for k=1:M
    c(k) = 2*cos(k*w);
end
c = flip(c);
c(end+1) = 1;

H_final = transpose(c) * h;
H_vals = vpa(subs(H_final, w, omega));
plot(omega,H_vals);

big;

function res = D(w)
global wc;
if w<wc
    res=1;
else
    res=0;
end
end


function res = W(w)
global delta_p delta_s;
global wc;
if w<wc
    res=1;
else
    res=delta_p/delta_s;
end
end

計算は最後まで到達したが、ローパスフィルタは得られていない。
Matlabにはfirpm()関数が実装されているので、これを使うのがよいのか。