%FILTER_DESIGN_QS_UNC Design a 2-D filter with quadrantal symmetry by using
%   the minimax error between the ideal frequency response and the
%   approximation given by the window method as the design criteria. UNC
%   stands for unconstrained optimization.
%
%   h = filter_design_qs_unc(Hd,Nfilt_row,Nfilt_col)
%   Hd: the ideal frequency response
%   Nfilt_row: vertical order of the output filter
%   Nfilt_col: horizontal order of the output filter
%   h: output filter
%
%   This function will also print out the minimax error between the output
%   filter and the ideal filter.
%
%   Example
%   --------
%   Design a 15x15 lowpass filter based on the ideal frequency response Hd
%
%   h=filter_design_qs_unc(Hd,15,15);
%
%   NOTE: Hd can be designed using the function ideal_response_qs. Please
%   refer to the help documents of ideal_response_qs for more information.

function h = filter_design_unc(Hd,Nfilt_row,Nfilt_col)

Npt=size(Hd,1);
%Extract one quadrant of ideal filter response
Hd_qua = Hd((size(Hd,1)*0.5+1):size(Hd,1),(size(Hd,2)*0.5 + 1):size(Hd,2));
%Generate desired window
win=ones(Nfilt_row,Nfilt_col);
%Generate actual filter response using window method
ha = fwind2(Hd,win);
ha_qua0 = ha(((size(ha,1)-1)*0.5+1):size(ha,1) , ((size(ha,2)-1)*0.5+1):size(ha,2));
ha_vec0 = reshape(ha_qua0,((Nfilt_row-1)*.5+1)*((Nfilt_col-1)*.5+1),1);
%Set optimizing options
options = optimset('MaxFunEvals',100000,'TolFun',1e-15,'TolCon',1e-15,'TolX',1e-15);

%Optimize solution with constraints
%Find minimum-error solution
p=2;
exitcond=0;
sol = fminunc(@(ha_vec) p_error(ha_vec,Hd_qua,Npt,Nfilt_row,Nfilt_col,2),ha_vec0);
error_minimax=minmax_error(sol,Nfilt_row,Nfilt_col,Hd_qua,Npt);
while not(exitcond)
    p=p+2;
    sol_tem = fminunc(@(ha_vec) p_error(ha_vec,Hd_qua   ,Npt,Nfilt_row,Nfilt_col,p),ha_vec0);
    err_tem=minmax_error(sol_tem,Nfilt_row,Nfilt_col,Hd_qua,Npt);
    if (err_tem>=error_minimax)
        exitcond=1;
        p=p-2
    else
        sol=sol_tem;
        error_minimax=err_tem;
    end
end

%Display minimax error
error_minimax
%Convert solution vector to a quadrant
solqua=reshape(sol,(Nfilt_row-1)*0.5+1,(Nfilt_col-1)*0.5+1);
%Convert quadrant to full response
bottom = [fliplr(solqua(:,2:size(solqua,2))) solqua];
h = [flipud(bottom(2:size(bottom,1),:)) ; bottom];


%%%%%%%%%%%%%SUBFUNCTIONS to be used in this design%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%Subfunction1: minmax_error
function err = minmax_error(vec,Nfilt_row,Nfilt_col,Hd_qua,Npt)
%Generate full impulse response and frequency response from one quadrant.
ha_qua=reshape(vec,(Nfilt_row-1)*0.5+1,(Nfilt_col-1)*0.5+1);
r=size(ha_qua,1);
c=size(ha_qua,2);
upright = flipud(ha_qua(2:r,:));
upleft = fliplr(upright(:,2:c));
dleft = fliplr(ha_qua(:,2:c));
ha_pad = [ha_qua zeros(r,Npt-2*c+1) dleft;zeros(Npt-2*r+1,Npt);
    upright zeros(r-1,Npt-2*c+1) upleft];
tem = fft2(ha_pad);
Ha_qua = real(tem(1:Npt/2,1:Npt/2));
%Calculate the minimax error
err = max(max(abs(Ha_qua - Hd_qua)));

%Subfunction2: p_error
%This function computes the p-error difference between the desired filter
%FREQUENCY response and the actual filter FREQUENCY response for one
%quadrant. 

%Input: ha_qua is the coefficient matrix for one quadrant of the actual 
%impulse response; Hd_qua is one quadrant of the ideal frequency response;
%Npt is the number of points on each dimension of Hd; Nfilt is the size of
%the actual impulse response; p is the power of the difference.
%Output:a scalar quantity indicating the pth power difference between 
%the actual frequency response and the ideal frequency response for one
%quadrant.
function err = p_error(ha_vec,Hd_qua,Npt,Nfilt_row,Nfilt_col,p)

%Generate full impulse response and frequency response from one quadrant.
ha_qua = reshape(ha_vec,(Nfilt_row-1)*0.5+1,(Nfilt_col-1)*0.5+1);
r=size(ha_qua,1);
c=size(ha_qua,2);
upright = flipud(ha_qua(2:r,:));
upleft = fliplr(upright(:,2:c));
dleft = fliplr(ha_qua(:,2:c));
ha_pad = [ha_qua zeros(r,Npt-2*c+1) dleft;zeros(Npt-2*r+1,Npt);
    upright zeros(r-1,Npt-2*c+1) upleft];
tem = fft2(ha_pad);
Ha_qua = real(tem(1:Npt/2,1:Npt/2));

%Calculate the p-error
err = sum(sum((Ha_qua - Hd_qua).^p));
