function Line=findlyap(MainHandles)
%FINDLYAP  determines the Lyapunov exponents and dimension
%
%    The alogrithm employed in this toolbox for determining Lyapunov
%    exponents is according to the algorithms proposed in
%
%    [1] A. Wolf, J. B. Swift, H. L. Swinney, and J. A. Vastano,
%        "Determining Lyapunov Exponents from a Time Series," Physica D,
%        Vol. 16, pp. 285-317, 1985.
%
%    [2] J. P. Eckmann and D. Ruelle, "Ergodic Theory of Chaos and Strange
%        Attractors," Rev. Mod. Phys., Vol. 57, pp. 617-656, 1985.
%
%    The algorithm given in [1] is used for first-order systems while
%    the QR-based algorithm proposed in [2] is applied for higher order
%    systems.

%   by Steve W. K. SIU, July 5, 1998.

%Print the results to the file every iteration
PrintStep=10;
%Display the results on the screen every iteration
DisplayStep=1;

%Clear the current axis objects
cla;
v=axis;
xc=(v(2)+v(1))/2;	%Center of the axis box [xc,y]
y=(v(4)+v(3))/2;
aW=v(2)-v(1);		%Axis width
x=xc-1/4*aW;

%Display the text "Loading... Please wait!"
th=text(x,y,'Loading... Please wait!','Color','r','FontSize',15,...
   	'FontAngle','italic');
drawnow;
%Clear the bottom text if any
set(MainHandles(1),'String','');

%Get the data stored in 'UserData' of the setting button
DATA=get(MainHandles(4),'UserData');
%Restore the data input by the user
output=DATA(1);		%Output checkbox: "on"=1; "off"=0
LEout=DATA(2);			%Output Lyapunov exponents to file: "yes"=1; "no"=0
LEprec=DATA(3);      %Precision of the output Lyapunov exponents
LDout=DATA(4);			%Output Lyapunov dimension to file: "yes"=1; "no"=0
LDprec=DATA(5);      %Precision of the Lyapunov dimension
IntMethod=DATA(6);	%Integration method: 1=Discrete map, 2=ODE45, 3=ODE23
							% 4=ODE113, 5=ODE23S, 6=ODE15S
InitialTime=DATA(7);	%Initial time
FinalTime=DATA(8);	%Final time
TimeStep=DATA(9);		%Time step
RelTol=DATA(10);		%Relative tolerance
AbsTol=DATA(11);		%Absolute tolerance
plot1=DATA(12);		%Plot imediately: 1="checked", 0="unchecked"
plot2=DATA(13);		%Plot according to specified iterations
ItrNum=DATA(14);		%No. of iteration for updating the plot
Color=DATA(15);		%Line Color option
							%Line color: 1="blue", 2="balck", 3="green", 4="red",
                     %5="yellow", 6="magenta", 7="cyan"
DiscardItr=DATA(16);	%Iterations to be discarded
UpdateStepNum=DATA(17);	%Lyapunov exponents updating steps
linODEnum=DATA(18);	%No. of linearized ODEs
ic=DATA(19:length(DATA));	%Initial conditions

%Get the output file and ODE function names stored in
%'UserData' of the "Start" button.
%Handles(2) is the handle of "Start" button
NAMES=get(MainHandles(2),'UserData');
OutputFile=rmspace(NAMES(1,:));
odefun=rmspace(NAMES(2,:));

%Construct a look-up table for the line colors
COLORS='bkgrymc';
%Map the "Line color" pop-up menu position to the look-up table
LineColor=COLORS(Color);

%Construct a look-up table for integration methods
Methods=char('Discrete map', 'ode45','ode23','ode113','ode23s','ode15s');
ODEsolver=strcat(Methods(IntMethod,:));

%Dimension of the linearized system (total: d x d ODEs)
d=sqrt(linODEnum);
%Initial conditions for the linearized ODEs
Q0=eye(d);
IC=[ic(:);Q0(:)];
ICnum=length(IC);		%Total no. of initial coniditions
%One iteration: Duration for updating the LEs
Iteration=UpdateStepNum*TimeStep;
DiscardTime=DiscardItr*Iteration+InitialTime;

%MATLAB's ODE functions will give the intermediate solutions if
%the duration between the initial time and the final time is only
%one time step, this will slow down the whole iteration process.
%To avoid this, reduce the time step by half.
if (UpdateStepNum==1 & IntMethod~=3)
   TimeStep=TimeStep/2;
end

T1=InitialTime;
T2=T1+Iteration;
TSpan=[T1:TimeStep:T2];
%Absolute tolerance of each components is set to the same value
options=odeset('RelTol',RelTol,'AbsTol',ones(1,ICnum)*AbsTol);

%Initialize variables
n=0;			%Iteration counter
k=0;			%Effective iteration counter
				% (discarded iterations are not counted)
h=1;			%No. of line handles sets (1 set = d line handles)
delLine=0;  %Indicator for deleting the drawn lines
Sum=zeros(1,d);
xData=[];
yData=[];
Line=[];
bufferSize=10000; %Max. no. of data can be stored in the buffer before creating a new line.
if ( output & (LEout | LDout) )
   %If the output file cannot be opened, warn the user
   msg=['Unable to open "' sprintf(OutputFile) '".'];
   Warn='errordlg(msg,''ERROR'',''replace''); problem=1;';
   eval('fid=fopen(OutputFile,''wt''); problem=0;',Warn)
   %Construct a look-up table for precision format
   Prec=char('%.4f','%.6f','%.8f','%.10f','%.12f');
   %Map the pop-up menu position to its corresponding format
   LEprecision=strcat(Prec(LEprec,:));
   LDprecision=strcat(Prec(LDprec,:));
   if ~problem
      fprintf(fid,'Time');
      if LEout
         for i=1:d
            Str1=['\tLE%d'];
            fprintf(fid,Str1,i);
         end
      end
      if LDout
         Str2=['\tLD'];
         fprintf(fid,Str2);
      end
      fprintf(fid,'\n');
   end
else
   problem=0;
end

%Start the stop watch
tic;
%Get the state of the "Stop" button
stop=get(MainHandles(3),'UserData');
if DiscardTime>0
   %Display the text "Discarding transient steps..."
   set(MainHandles(1),'String','Discarding transient steps...');
end

A=[];

%String that contains the integration command
IntegrationStr=['[t,X]=',ODEsolver,'(odefun,TSpan,IC,options);'];
%Main loop
while (~stop & ~problem)
   n=n+1;
   %Integration
   if IntMethod>1
      eval(IntegrationStr);
   else	%If it is a discrete map
      for i=1:UpdateStepNum
         X(i,:)=(feval(odefun,IC))';
      end
   end
   [rX,cX]=size(X);
   L=cX-linODEnum;      %No. of initial conditions for
                        %the original system
   for i=1:d
      m1=L+1+(i-1)*d;
      m2=m1+d-1;
      A(:,i)=(X(rX,m1:m2))';
   end
   %QR decomposition
   if d>1
      %The algorithm for 1st-order system doesn't require
      %QR decomposition
      [Q,R]=qr(A);
      if T2>DiscardTime
         Q0=Q;
      else
         Q0=eye(d);
      end
   else
      R=A;
   end

   %Delete the text "Loading...Please wait!"
   %before the first iteration
   if n==1
      delete(th);
      drawnow;
      %Display the final time
      set(MainHandles(11),'String',FinalTime);
   end

  %Any zero diagonal element will cause overflow
  %in the following calculation, so discard this step.
   permission=1;
   for i=1:d
      if R(i,i)==0
         permission=0;
         break;
      end
   end
  %To determine the Lyapunov exponents
   if (T2>DiscardTime & permission)
      k=k+1;
      T=k*Iteration;
      TT=n*Iteration+InitialTime;
      %There are d Lyapunov exponents
      Sum=Sum+log(abs(diag(R))');
      lambda=Sum/T;

      %Sort the Lyapunov exponents in descenting order
      Lambda=fliplr(sort(lambda));
      %To calculate the Lyapunov dimension (or Kaplan-Yorke dimension)
      LESum=Lambda(1);
      LD=0;
      if (d>1 & Lambda(1)>0)
         for N=1:d-1
            if Lambda(N+1)~=0
               LD=N+LESum/abs(Lambda(N+1));
               LESum=LESum+Lambda(N+1);
               if LESum<0
                  break;
               end
            end
         end
      end
      %Store the [x,y] data for plotting
      [rxD,cxD]=size(xData);
      [ryD,cyD]=size(yData);
      if rxD<=bufferSize
         xData=[xData;TT];
         yData=[yData;lambda];
      else
         %When the buffers are full, refresh them
         %Max. size of buffers = 10000 data
         xData=[xData(rxD);TT];
         yData=[yData(ryD,:);lambda];
         h=h+1;		%add one set of line handles
         delLine=0;	%After refreshing the buffers, the
                     % previous drawn lines must not be deleted
      end

      if ( output & ~problem & (LEout | LDout) & rem(k,PrintStep)==0)
         fprintf(fid,'%.2f',TT);
         if LEout
            for i=1:d
               Str1=['\t',LEprecision];
               fprintf(fid,Str1,Lambda(i));
            end
         end
         if LDout
            Str2=['\t',LDprecision];
            fprintf(fid,Str2,LD);
         end
         fprintf(fid,'\n');
      end

      %Draw lines immediately if "update the plot immediately" was chosen.
      if (plot1==1 | plot2==1 & rem(k,ItrNum)==0)
         if delLine
            %Clear the previous drawn line if any
            delete(Line(h,:));
         end
      	%Draw d lines
         for i=1:d
            %Set "Erase Mode" to "none" for increasing speed (less refresh)
            Line(h,i)=line('EraseMode','none','Color',LineColor,...
                      'xData',xData,'yData',yData(:,i));
            %Force MATLAB to draw immediately
            drawnow;
         end
         delLine=1;	%Set a flag to indicate that the lines now can be deleted
      end
      %Display the calculated Lyapunov exponents
      if rem(k,DisplayStep)==0
         set(MainHandles(1),'String',[num2str(Lambda),blanks(3),'( ',num2str(LD),' )']);
      end
   end

   %To see whether "Stop" is pressed
   stop=get(MainHandles(3),'UserData');

   %Display current time, and time used
   set(MainHandles(10),'String',num2str(round(T2)));		%Current time
   set(MainHandles(12),'String',num2str(round(toc)));		%Used time
   drawnow;

   %If calculation is finished or "stop" button is pressed, exit the loop.
   if (stop | T2>=FinalTime)
      %Reset the "Erase mode" to normal
      set(Line,'EraseMode','normal');
      %Show the final results (for making sure the final results being shown if DisplayStep>1)
      if (T2>DiscardTime & permission)
         set(MainHandles(1),'String',[num2str(Lambda),blanks(3),'( ',num2str(LD),' )']);
      end
      break;
   end
   %Update the initial conditions and time span for the next iteration
   if IntMethod>1
      ic=X(rX,1:L);
      T1=T1+Iteration;
      T2=T2+Iteration;
      TSpan=[T1:TimeStep:T2];
   else %For discrete map
      ic=X(UpdateStepNum,1:L);
      T2=T2+Iteration;
   end
   IC=[ic(:);Q0(:)];
end		%End of main loop

if T2>=FinalTime
   set(MainHandles([2,4,13,15]),'Enable','On');
   set(MainHandles(3),'Enable','Off');
end
if (output & ~problem)
   fclose(fid);
end

%----------------Subroutine-----------------------------------
function outStr=rmspace(inStr)
%RMSPACE		Function for removing the beginning and ending
%				spaces of a string

%Remove spaces at the end of the string
outStr=strcat(inStr);
%Delete spaces at the beginning of the string
if ~isempty(outStr)
   while isspace(outStr(1))
   	outStr=outStr(2:length(outStr));
   end
end


function [ n_data, x, cbi ] = airy_cbi_values ( n_data )

%*****************************************************************************80
%
%% AIRY_CBI_VALUES returns some values of the Airy Bi(x) with complex argument.
%
%  Discussion:
%
%    The Airy functions Ai(X) and Bi(X) are a pair of linearly independent
%    solutions of the differential equation:
%
%      W'' - X * W = 0
%
%    In Mathematica, the function can be evaluated by:
%
%      AiryBi[x]
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    13 April 2007
%
%  Author:
%
%    John Burkardt
%
%  Reference:
%
%    Milton Abramowitz, Irene Stegun,
%    Handbook of Mathematical Functions,
%    National Bureau of Standards, 1964,
%    ISBN: 0-486-61272-4,
%    LC: QA47.A34.
%
%    Stephen Wolfram,
%    The Mathematica Book,
%    Fourth Edition,
%    Cambridge University Press, 1999,
%    ISBN: 0-521-64314-7,
%    LC: QA76.95.W65.
%
%  Parameters:
%
%    Input/output, integer N_DATA.  The user sets N_DATA to 0 before the
%    first call.  On each call, the routine increments N_DATA by 1, and
%    returns the corresponding data; when there is no more data, the
%    output value of N_DATA will be 0 again.
%
%    Output, complex X, the argument of the function.
%
%    Output, complex CBI, the value of the Airy BI function.
%
  n_max = 10;

  cbi_vec = [ ...
    1.207423594952871  + 0.0000000000000000 * i, ...
    0.9127160108293936 + 0.3800456133135556 * i, ...
    0.6824453575635721 + 0.3343047153635002 * i, ...
    0.5726265660086474 + 0.3988641086982559 * i, ...
    0.2511841251049547 + 0.3401447690712719 * i, ...
    0.1039973894969446 + 0.0000000000000000 * i, ...
    0.2511841251049547 - 0.3401447690712719 * i, ...
    0.5726265660086474 - 0.3988641086982559 * i, ...
    0.6824453575635721 - 0.3343047153635002 * i, ...
    0.9127160108293936 - 0.3800456133135556 * i ];

  x_vec = [ ...
     1.0000000000000000 + 0.0000000000000000 * i, ...
     0.8090169943749474 + 0.5877852522924731 * i, ...
     0.3090169943749474 + 0.9510565162951536 * i, ...
    -0.3090169943749474 + 0.9510565162951536 * i, ...
    -0.8090169943749474 + 0.5877852522924731 * i, ...
    -1.0000000000000000 + 0.0000000000000000 * i, ...
    -0.8090169943749474 - 0.5877852522924731 * i, ...
    -0.3090169943749474 - 0.9510565162951536 * i, ...
     0.3090169943749474 - 0.9510565162951536 * i, ...
     0.8090169943749474 - 0.5877852522924731 * i ];

  if ( n_data < 0 )
    n_data = 0;
  end

  n_data = n_data + 1;

  if ( n_max < n_data )
    n_data = 0;
    x = 0.0;
    cbi = 0.0;
  else
    x = x_vec(n_data);
    cbi = cbi_vec(n_data);
  end

  return
end


function u = acsc(a)
%ACSC         Slope inverse cosecant acsc(a)
%

% written  12/06/98     S.M. Rump
% modified 04/04/04     S.M. Rump  set round to nearest for safety
% modified 04/06/05     S.M. Rump  rounding unchanged
% modified 09/28/08     S.M. Rump  check for rounding to nearest improved
% modified 08/26/12     S.M. Rump  global variables removed
%

  e = 1e-30;
  if 1+e==1-e                           % fast check for rounding to nearest
    rndold = 0;
  else
    rndold = getround;
    setround(0)
  end

  INTLAB_SLOPE = getappdata(0,'INTLAB_SLOPE');

  u = a;

  u.r = acsc(a.r);
  indexc = 1:INTLAB_SLOPE.NUMVAR;
  indexr = 2:INTLAB_SLOPE.NUMVAR+1;
  Xxs = hull(a.r(:,indexc),a.r(:,indexr));
  Index = 1:size(a.r.inf,1);

  index = all( a.r.sup<=0 , 2);
  if any(index)
    aindex.r = a.r(index,:);
    aindex.s = a.s(index,:);
    u.s(index,:) = ...
      slopeconvexconcave('acsc','-1./(abs(%).*sqrt(sqr(%)-1))',aindex,0);
    Index(index) = 0;
  end

  index = all( a.r.inf>=0 , 2);
  if any(index)
    aindex.r = a.r(index,:);
    aindex.s = a.s(index,:);
    u.s(index,:) = ...
      slopeconvexconcave('acsc','-1./(abs(%).*sqrt(sqr(%)-1))',aindex,1);
    Index(index) = 0;
  end

  if any(Index)
    Index( Index==0 ) = [];
    Xxs = Xxs(Index);
    u.s(Index,:) = - a.s(Index,:) ./ ( abs(Xxs) .* sqrt( sqr(Xxs)-1 ) );
  end

  if rndold
    setround(rndold)
  end


function val = m00Q0011(F00, F11)
%------------------------------------------------------------------------------
%
% Integrates interpolated gridfunction F on quincunx grid (F00 united with F11)
% where interpolation is assumed piecewise constant.
%
%
% Design and implementation by:
% Dr. Paul M. de Zeeuw <Paul.de.Zeeuw@cwi.nl>  http://homepages.cwi.nl/~pauldz/
% Last Revision: February 1, 2001.
%  2001 Stichting CWI, Amsterdam
%------------------------------------------------------------------------------
[hx, hy] = Q0011gridfdims(F00, F11);
Qhxy = 2 * hx * hy;
val = ( sum(sum(F00)) + sum(sum(F11)) ) * Qhxy;
%------------------------------------------------------------------------------


function nfmi = fmi(ima, imb, imf, feature, w)

% FMI calculates the Feature Mutual Information (FMI), the non-reference
% performance metric for fusion algorithms, proposed in:
%
% M.B.A. Haghighat, A. Aghagolzadeh, H. Seyedarabi, "A Non-Reference Image
% Fusion Metric Based on Mutual Information of Image Features," Computers
% and Electrical Engineering, vol. 37, no. 5, pp. 744-756, Sept. 2011.
% http://dx.doi.org/10.1016/j.compeleceng.2011.07.012
%
%
% This code is the implementation of FAST-FMI, presented in:
%
% M. Haghighat, M.A. Razian, "Fast-FMI: non-reference image fusion metric,"
% 8th International Conference on Application of Information and
% Communication Technologies (AICT), pp. 1-3, 2014.
%
%
% Inputs:
% 		ima 	: 	First source image
% 		imb 	: 	Second source image
% 		imf 	: 	Fused image
% 		feature : 	Feature extraction method: gradient, edge, dct, wavelet, none (raw pixels) (default: image with no feature extraction)
% 		w       : 	Sliding window size w by w (default: 3)
%
% Output:
% 		nfmi 	: 	Normalized Feature Mutual Information
%
%
% Sample use:
% nfmi = fmi(ima,imb,imf,'none',3);
% % Or simply:
% nfmi = fmi(ima,imb,imf);
%
%
% (C)	Mohammad Haghighat, University of Miami
%       haghighat@ieee.org
%       IF YOU USE THIS CODE IN YOUR WORK, PLEASE CITE THE ABOVE PAPERS.

if nargin < 3               % Check correct number of arguments
    error('There should be three input images (2 source images and 1 fused image)!');
end

if size(ima) ~= size(imb)	% Check if the source images are of the same size
    error('Size of the source images must be the same!');
end

if size(ima) ~= size(imf)	% Check if the source images are of the same size
    error('Size of the source and fused images must be the same!');
end

if ~exist('feature', 'var')
    feature = 'edge';       % Default feature extraction
end

if ~exist('w', 'var')
    w = 3;                  % Default window size
end

ima = double(ima);
imb = double(imb);
imf = double(imf);

% Feature Extraction

switch feature

	case 'none'         % Raw pixels (no feature extraction)
        aFeature = ima;
        bFeature = imb;
        fFeature = imf;

    case 'gradient'     % Gradient
        aFeature = gradient(ima);
        bFeature = gradient(imb);
        fFeature = gradient(imf);

    case 'edge'         % Edge
        aFeature = edge(ima);
        bFeature = edge(imb);
        fFeature = edge(imf);

    case 'dct'          % DCT
        aFeature = dct2(ima);
        bFeature = dct2(imb);
        fFeature = dct2(imf);

    case 'wavelet'      % Discrete Meyer wavelet
        [cA,cH,cV,cD] = dwt2(ima,'dmey');
        aFeature = rerange([cA,cH;cV,cD]);
        [cA,cH,cV,cD] = dwt2(imb,'dmey');
        bFeature = rerange([cA,cH;cV,cD]);
        [cA,cH,cV,cD] = dwt2(imf,'dmey');
        fFeature = rerange([cA,cH;cV,cD]);

    otherwise
        error('Please specify a feature extraction method among ''gradient'', ''edge'', ''dct'', ''wavelet'', or ''none'' (raw pixels)!');

end

% Sliding window

[m,n] = size(aFeature);
w = floor(w/2);
fmi_map = ones(m-2*w, n-2*w);

for p = w+1:m-w
    for q = w+1:n-w

        aSub = aFeature(p-w:p+w, q-w:q+w);
        bSub = bFeature(p-w:p+w, q-w:q+w);
        fSub = fFeature(p-w:p+w, q-w:q+w);

        l = round((2*w+1).^2);

        if aSub == fSub
            fmi_af = 1;
        else
            aMax = max(aSub(:));
            aMin = min(aSub(:));
            if aMax == aMin
                aSub = ones(2*w+1);
            else
                aSub = (aSub - aMin)/(aMax - aMin);
            end

            fMax = max(fSub(:));
            fMin = min(fSub(:));
            if fMax == fMin
                fSub = ones(2*w+1);
            else
                fSub = (fSub - fMin)/(fMax - fMin);
            end

            % Normalize the feature images to get the marginal PDFs
            aPdf = aSub(:)./(sum(sum(aSub)));
            fPdf = fSub(:)./(sum(sum(fSub)));

            % PDF to CDF tranformation
            aCdf = zeros(size(aPdf));
            fCdf = zeros(size(fPdf));
            aCdf(1) = aPdf(1);
            fCdf(1) = fPdf(1);
            for i = 2:l
                aCdf(i) = aPdf(i)+aCdf(i-1);
                fCdf(i) = fPdf(i)+fCdf(i-1);
            end

            % Pearson correlation between marginal PDFs
            aTemp = aPdf - mean(aPdf);
            fTemp = fPdf - mean(fPdf);
            if sum(aTemp.*fTemp) == 0
                c = 0;
            else
                c = sum(aTemp.*fTemp)/sqrt(sum(aTemp.*aTemp)*sum(fTemp.*fTemp));
            end

            % Population standard deviations
            e_aPdf = 0; e2_aPdf = 0; e_fPdf = 0; e2_fPdf = 0;
            for i = 1:l
                e_aPdf  = e_aPdf  + i.*aPdf(i);         % Expected value of aPdf
                e2_aPdf = e2_aPdf + (i.^2).*aPdf(i);    % 2nd-order moment of aPdf
                e_fPdf  = e_fPdf  + i.*fPdf(i);         % Expected value of fPdf
                e2_fPdf = e2_fPdf + (i.^2).*fPdf(i);	% 2nd-order moment of fPdf
            end
            aSd = sqrt(e2_aPdf - e_aPdf.^2);	% Population standard deviation of aPdf
            fSd = sqrt(e2_fPdf - e_fPdf.^2);	% Population standard deviation of fPdf

            jointEntropy = 0;
            % Joint PDF calculation using simplified Nelsen method with joint entropy calculation
            if c >= 0

                if c == 0 || aSd == 0 || fSd == 0
                    phi = 0;
                else
                    covUp = 0;
                    for i = 1:l
                        for j = 1:l
                            % Frechet's upper bound bivariate distributions between f & a
                            covUp = covUp + 0.5*(fCdf(i)+aCdf(j)-abs(fCdf(i)-aCdf(j))) - fCdf(i).*aCdf(j);
                        end
                    end
                    corrUp = covUp/(fSd*aSd); % Upper correlation between f & a
                    phi = c/corrUp;
                end

                jpdfUp = 0.5*(fCdf(1)+aCdf(1)-abs(fCdf(1)-aCdf(1)));
                jpdf = phi.*jpdfUp + (1-phi).*fPdf(1).*aPdf(1);
                if jpdf~=0
                    jointEntropy = real(-jpdf.*log2(jpdf)); % Joint entropy
                end

                % 1-D boundaries
                for i=2:l
                    jpdfUp = 0.5*(fCdf(i)+aCdf(1)-abs(fCdf(i)-aCdf(1))) - ...
                        0.5*(fCdf(i-1)+aCdf(1)-abs(fCdf(i-1)-aCdf(1)));
                    jpdf = phi.*jpdfUp + (1-phi).*fPdf(i).*aPdf(1);
                    if jpdf~=0
                        jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                    end
                end
                for j=2:l
                    jpdfUp = 0.5*(fCdf(1)+aCdf(j)-abs(fCdf(1)-aCdf(j))) - ...
                        0.5*(fCdf(1)+aCdf(j-1)-abs(fCdf(1)-aCdf(j-1)));
                    jpdf = phi.*jpdfUp + (1-phi).*fPdf(1).*aPdf(j);
                    if jpdf~=0
                        jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                    end
                end
                % 2-D walls
                for i=2:l
                    for j=2:l
                        jpdfUp = 0.5*(fCdf(i)+aCdf(j)-abs(fCdf(i)-aCdf(j))) - ...
                            0.5*(fCdf(i-1)+aCdf(j)-abs(fCdf(i-1)-aCdf(j))) - ...
                            0.5*(fCdf(i)+aCdf(j-1)-abs(fCdf(i)-aCdf(j-1))) + ...
                            0.5*(fCdf(i-1)+aCdf(j-1)-abs(fCdf(i-1)-aCdf(j-1)));
                        jpdf = phi.*jpdfUp + (1-phi).*fPdf(i).*aPdf(j);
                        if jpdf~=0
                            jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                        end
                    end
                end

            end

            if c < 0

                if aSd == 0 || fSd == 0
                    theta = 0;
                else
                    covLo = 0;
                    for i = 1:l
                        for j = 1:l
                            % Frechet's lower bound bivariate distributions between f & a
                            covLo = covLo + 0.5*(fCdf(i)+aCdf(j)-1+abs(fCdf(i)+aCdf(j)-1)) - fCdf(i).*aCdf(j);
                        end
                    end
                    corrLo = covLo/(fSd*aSd); % Lower correlation between f & a
                    theta = c/corrLo;
                end

                jpdfLo = 0.5*(fCdf(1)+aCdf(1)-1+abs(fCdf(1)+aCdf(1)-1));
                jpdf = theta.*jpdfLo + (1-theta).*fPdf(1).*aPdf(1);
                if jpdf~=0
                    jointEntropy = real(-jpdf.*log2(jpdf)); % Joint entropy
                end

                % 1-D boundaries
                for i=2:l
                    jpdfLo = 0.5*(fCdf(i)+aCdf(1)-1+abs(fCdf(i)+aCdf(1)-1)) - ...
                        0.5*(fCdf(i-1)+aCdf(1)-1+abs(fCdf(i-1)+aCdf(1)-1));
                    jpdf = theta.*jpdfLo + (1-theta).*fPdf(i).*aPdf(1);
                    if jpdf~=0
                        jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                    end
                end
                for j=2:l
                    jpdfLo = 0.5*(fCdf(1)+aCdf(j)-1+abs(fCdf(1)+aCdf(j)-1)) - ...
                        0.5*(fCdf(1)+aCdf(j-1)-1+abs(fCdf(1)+aCdf(j-1)-1));
                    jpdf = theta.*jpdfLo + (1-theta).*fPdf(1).*aPdf(j);
                    if jpdf~=0
                        jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                    end
                end
                % 2-D walls
                for i=2:l
                    for j=2:l
                        jpdfLo = 0.5*(fCdf(i)+aCdf(j)-1+abs(fCdf(i)+aCdf(j)-1)) - ...
                            0.5*(fCdf(i-1)+aCdf(j)-1+abs(fCdf(i-1)+aCdf(j)-1)) - ...
                            0.5*(fCdf(i)+aCdf(j-1)-1+abs(fCdf(i)+aCdf(j-1)-1)) + ...
                            0.5*(fCdf(i-1)+aCdf(j-1)-1+abs(fCdf(i-1)+aCdf(j-1)-1));
                        jpdf = theta.*jpdfLo + (1-theta).*fPdf(i).*aPdf(j);
                        if jpdf~=0
                            jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                        end
                    end
                end

            end

            % Marginal entropies
            index = find(aPdf~=0);
            aEntropy = sum(-aPdf(index).*log2(aPdf(index)));
            index = find(fPdf~=0);
            fEntropy = sum(-fPdf(index).*log2(fPdf(index)));

            % Mutual information between a & f
            mi = aEntropy + fEntropy - jointEntropy;

            % Overall normalized mutual information
            if mi == 0
                fmi_af = 0;
            else
                fmi_af = 2.*mi./(aEntropy+fEntropy);
            end

        end

        if bSub == fSub
            fmi_bf = 1;
        else
            bMax = max(bSub(:));
            bMin = min(bSub(:));
            if bMax == bMin
                bSub = ones(2*w+1);
            else
                bSub = (bSub - bMin)/(bMax - bMin);
            end

            fMax = max(fSub(:));
            fMin = min(fSub(:));
            if fMax == fMin
                fSub = ones(2*w+1);
            else
                fSub = (fSub - fMin)/(fMax - fMin);
            end

            % PDF of the gradients of the images
            bPdf = bSub(:)./(sum(sum(bSub)));
            fPdf = fSub(:)./(sum(sum(fSub)));

            l = length(bPdf);

            % PDF to CDF tranformation
            bCdf = zeros(size(bPdf));
            fCdf = zeros(size(fPdf));
            bCdf(1) = bPdf(1);
            fCdf(1) = fPdf(1);
            for i=2:l
                bCdf(i) = bPdf(i)+bCdf(i-1);
                fCdf(i) = fPdf(i)+fCdf(i-1);
            end

            % Pearson correlation between marginal PDFs
            bTemp = bPdf - mean(bPdf);
            fTemp = fPdf - mean(fPdf);
            if sum(bTemp.*fTemp) == 0
                c = 0;
            else
                c = sum(bTemp.*fTemp)/sqrt(sum(bTemp.*bTemp)*sum(fTemp.*fTemp));
            end

            % Population standard deviations
            e_bPdf = 0; e2_bPdf = 0; e_fPdf = 0; e2_fPdf = 0;
            for i = 1:l
                e_bPdf  = e_bPdf  + i.*bPdf(i);         % Expected value of the aPdf
                e2_bPdf = e2_bPdf + (i.^2).*bPdf(i);	% 2nd-order moment of the aPdf
                e_fPdf  = e_fPdf  + i.*fPdf(i);         % Expected value of the bPdf
                e2_fPdf = e2_fPdf + (i.^2).*fPdf(i);	% 2nd-order moment of the bPdf
            end
            bSd = sqrt(e2_bPdf - e_bPdf.^2);	% Population standard deviation of the intensity
            fSd = sqrt(e2_fPdf - e_fPdf.^2);	% Population standard deviation of the intensity

            jointEntropy = 0;
            % JPDF calculation using simplified Nelsen method with joint entropy calculation
            if c >= 0

                if c == 0 || bSd == 0 || fSd == 0
                    phi = 0;
                else
                    covUp = 0;
                    for i = 1:l
                        for j = 1:l
                            % Frechet's upper bound bivariate distributions between f & b
                            covUp = covUp + 0.5*(fCdf(i)+bCdf(j)-abs(fCdf(i)-bCdf(j))) - fCdf(i).*bCdf(j);
                        end
                    end
                    corrUp = covUp/(fSd*bSd); % Upper correlation between f & b
                    phi = c/corrUp;
                end

                jpdfUp = 0.5*(fCdf(1)+bCdf(1)-abs(fCdf(1)-bCdf(1)));
                jpdf = phi.*jpdfUp + (1-phi).*fPdf(1).*bPdf(1);
                if jpdf~=0
                    jointEntropy = real(-jpdf.*log2(jpdf)); % Joint entropy
                end

                % 1-D boundaries
                for i=2:l
                    jpdfUp = 0.5*(fCdf(i)+bCdf(1)-abs(fCdf(i)-bCdf(1))) - ...
                        0.5*(fCdf(i-1)+bCdf(1)-abs(fCdf(i-1)-bCdf(1)));
                    jpdf = phi.*jpdfUp + (1-phi).*fPdf(i).*bPdf(1);
                    if jpdf~=0
                        jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                    end
                end
                for j=2:l
                    jpdfUp = 0.5*(fCdf(1)+bCdf(j)-abs(fCdf(1)-bCdf(j))) - ...
                        0.5*(fCdf(1)+bCdf(j-1)-abs(fCdf(1)-bCdf(j-1)));
                    jpdf = phi.*jpdfUp + (1-phi).*fPdf(1).*bPdf(j);
                    if jpdf~=0
                        jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                    end
                end
                % 2-D walls
                for i=2:l
                    for j=2:l
                        jpdfUp = 0.5*(fCdf(i)+bCdf(j)-abs(fCdf(i)-bCdf(j))) - ...
                            0.5*(fCdf(i-1)+bCdf(j)-abs(fCdf(i-1)-bCdf(j))) - ...
                            0.5*(fCdf(i)+bCdf(j-1)-abs(fCdf(i)-bCdf(j-1))) + ...
                            0.5*(fCdf(i-1)+bCdf(j-1)-abs(fCdf(i-1)-bCdf(j-1)));
                        jpdf = phi.*jpdfUp + (1-phi).*fPdf(i).*bPdf(j);
                        if jpdf~=0
                            jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                        end
                    end
                end

            end

            if c < 0

                if bSd == 0 || fSd == 0
                    theta = 0;
                else
                    covLo = 0;
                    for i = 1:l
                        for j = 1:l
                            % Frechet's lower bound bivariate distributions between f & b
                            covLo = covLo + 0.5*(fCdf(i)+bCdf(j)-1+abs(fCdf(i)+bCdf(j)-1)) - fCdf(i).*bCdf(j);
                        end
                    end
                    corrLo = covLo/(fSd*bSd); % Lower correlation between f & b
                    theta = c/corrLo;
                end

                jpdfLo = 0.5*(fCdf(1)+bCdf(1)-1+abs(fCdf(1)+bCdf(1)-1));
                jpdf = theta.*jpdfLo + (1-theta).*fPdf(1).*bPdf(1);
                if jpdf~=0
                    jointEntropy = real(-jpdf.*log2(jpdf)); % Joint entropy
                end

                % 1-D boundaries
                for i=2:l
                    jpdfLo = 0.5*(fCdf(i)+bCdf(1)-1+abs(fCdf(i)+bCdf(1)-1)) - ...
                        0.5*(fCdf(i-1)+bCdf(1)-1+abs(fCdf(i-1)+bCdf(1)-1));
                    jpdf = theta.*jpdfLo + (1-theta).*fPdf(i).*bPdf(1);
                    if jpdf~=0
                        jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                    end
                end
                for j=2:l
                    jpdfLo = 0.5*(fCdf(1)+bCdf(j)-1+abs(fCdf(1)+bCdf(j)-1)) - ...
                        0.5*(fCdf(1)+bCdf(j-1)-1+abs(fCdf(1)+bCdf(j-1)-1));
                    jpdf = theta.*jpdfLo + (1-theta).*fPdf(1).*bPdf(j);
                    if jpdf~=0
                        jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                    end
                end
                % 2-D walls
                for i=2:l
                    for j=2:l
                        jpdfLo = 0.5*(fCdf(i)+bCdf(j)-1+abs(fCdf(i)+bCdf(j)-1)) - ...
                            0.5*(fCdf(i-1)+bCdf(j)-1+abs(fCdf(i-1)+bCdf(j)-1)) - ...
                            0.5*(fCdf(i)+bCdf(j-1)-1+abs(fCdf(i)+bCdf(j-1)-1)) + ...
                            0.5*(fCdf(i-1)+bCdf(j-1)-1+abs(fCdf(i-1)+bCdf(j-1)-1));
                        jpdf = theta.*jpdfLo + (1-theta).*fPdf(i).*bPdf(j);
                        if jpdf~=0
                            jointEntropy = jointEntropy + real(-jpdf.*log2(jpdf)); % Joint entropy
                        end
                    end
                end

            end

            % Marginal entropies
            index = find(bPdf~=0);
            bEntropy = sum(-bPdf(index).*log2(bPdf(index)));
            index = find(fPdf~=0);
            fEntropy = sum(-fPdf(index).*log2(fPdf(index)));

            % Mutual information between b & f
            mi = bEntropy + fEntropy - jointEntropy;

            % Overall normalized mutual information
            if mi == 0
                fmi_bf = 0;
            else
                fmi_bf = 2.*mi./(bEntropy+fEntropy);
            end

        end

        fmi_map(p-w,q-w) = (fmi_af + fmi_bf)./2;

    end
end

nfmi = mean2(fmi_map);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function imNorm = rerange(im)

% RERANGE changes the range of images into interval [0,1]

im = double(im);

[m,n] = size(im);

imMax = max(im(:));
imMin = min(im(:));

if imMax == imMin
    imNorm = ones(m,n);
else
    imNorm = (im - imMin)/(imMax - imMin);
end


function y = tapas_condhalluc_obs_sim(r, infStates, p)
% Simulates responses according to the condhalluc_obs model
%
% --------------------------------------------------------------------------------------------------
% Copyright (C) 2016 Christoph Mathys, TNU, UZH & ETHZ
%
% This file is part of the HGF toolbox, which is released under the terms of the GNU General Public
% Licence (GPL), version 3. You can redistribute it and/or modify it under the terms of the GPL
% (either version 3 or, at your option, any later version). For further details, see the file
% COPYING or <http://www.gnu.org/licenses/>.

% Inverse decision temparature beta is the only parameter
be = p;

% Prediction trajectory
mu1hat = infStates(:,1,1);

% Get true-positive rate corresponding to stimuli
tp = r.u(:,2);

% Calculate belief x using Bayes' theorem
x = tp.*mu1hat./(tp.*mu1hat + (1-mu1hat).^2);

% Belief is mu1hat in trials where there is no tone
x(find(tp==0)) = mu1hat(find(tp==0));

% Apply the logistic sigmoid to the inferred beliefs
prob = tapas_sgm(be.*(2.*x-1),1);

% Initialize random number generator
if isnan(r.c_sim.seed)
    rng('shuffle');
else
    rng(r.c_sim.seed);
end

% Simulate
y = binornd(1, prob);

return;


function [ c_vec1, c_vec2 ] = real_cross( p_vec1, p_vec2, pcross_real,...
                                            eta_c, min_realvar, max_realvar)
%   Applies SBX over two vectors of double.

nreal = length(p_vec1);

epsilon = 1.0e-14 ;
% ncross = 0 ;

c_vec1 = zeros(1,nreal);
c_vec2 = zeros(1,nreal);

if(rand(1) <= pcross_real)
% if(randomperc() <= pcross_real) % SLOW !!!
    % ncross = ncross + 1 ;
    if(nreal < 10)
        [c_vec1, c_vec2] = sbx_looped(p_vec1, p_vec2, ...
                                    c_vec1, c_vec2, ...
                                    epsilon, eta_c, ...
                                    min_realvar, max_realvar);
    else
        [c_vec1, c_vec2] = sbx_vectorized(p_vec1, p_vec2, ...
                                    c_vec1, c_vec2, ...
                                    epsilon, eta_c, ...
                                    min_realvar, max_realvar);
    end
else
    c_vec1 = p_vec1 ;
    c_vec2 = p_vec2 ;
end
end

function [c_vec1, c_vec2] = sbx_looped(p_vec1, p_vec2, ...
                                            c_vec1, c_vec2, ...
                                            epsilon, eta_c, ...
                                            min_realvar, max_realvar)
% This is the original implementation
nreal = length(p_vec1);
for i = 1:nreal
   if (rand(1) <= 0.5)
   % if (randomperc() <= 0.5) % SLOW !!!
       if (abs(p_vec1(i) - p_vec2(i)) > epsilon)
            if (p_vec1(i) < p_vec2(i))
                y1 = p_vec1(i) ;
                y2 = p_vec2(i) ;
            else
                y1 = p_vec2(i) ;
                y2 = p_vec1(i) ;
            end
            yl = min_realvar(i);
            yu = max_realvar(i);
            r = rand(1) ;
            % r = randomperc() ; % SLOW !!!
            beta_ = 1.0 + (2.0 * (y1 - yl) / (y2 - y1));
            alpha_ = 2.0 - (beta_ ^ (-1.0 * (eta_c + 1.0)));
            if (r <= (1.0 / alpha_))
                    betaq = (r * alpha_) ^ (1.0/(eta_c + 1.0));
            else
                    betaq = (1.0 / (2.0 - r * alpha_)) ^ (1.0 / (eta_c + 1.0));
            end
            c1 = 0.5 * ((y1 + y2) - (betaq * (y2 - y1)));
            beta_ = 1.0 + (2.0 * (yu - y2)/(y2 - y1));
            alpha_ = 2.0 - (beta_ ^ (-1.0 * (eta_c + 1.0)));
            if (r <= (1.0 / alpha_))
                    betaq = (r * alpha_) ^ (1.0 / (eta_c + 1.0));
            else
                    betaq = (1.0 / (2.0 - r * alpha_)) ^ (1.0 / (eta_c + 1.0));
            end
            c2 = 0.5 * ((y1 + y2) + betaq * (y2 - y1));
            if (c1 < yl)
                c1 = yl;
            end
            if (c2 < yl)
                c2 = yl;
            end
            if (c1 > yu)
                c1 = yu;
            end
            if (c2 > yu)
                c2 = yu;
            end
            if (rand(1) <= 0.5)
            % if (randomperc() <= 0.5) % SLOW !!!
                c_vec1(i) = c2;
                c_vec2(i) = c1;
            else
                c_vec1(i) = c1;
                c_vec2(i) = c2;
            end
        else
            c_vec1(i) = p_vec1(i) ;
            c_vec2(i) = p_vec2(i) ;
        end
    else
        c_vec1(i) = p_vec1(i) ;
        c_vec2(i) = p_vec2(i) ;
    end
end
%
end

function [c_vec1, c_vec2] = sbx_vectorized(p_vec1, p_vec2, ...
                                            c_vec1, c_vec2, ...
                                            epsilon, eta_c, ...
                                            min_realvar, max_realvar)
% This is the vectorized version of the above code, this will generally
% give you 2-times speed up than the above code, especially you will
% observe even more speed up when the length of the variable gets larger.
% However, short variable length may make it slower (like in Osyczka's
% problems)
nreal = length(p_vec1);
randv1lthalf = rand(1,nreal) <  0.5 ;
% randv1lthalf = randompercv(1,nreal) <  0.5 ; % SLOW !!!
absdiffgteps = abs(p_vec1 - p_vec2) > (zeros(1,nreal) * epsilon) ;
xover_index = randv1lthalf & absdiffgteps ;
abs_xover_index = (1:length(xover_index));
abs_xover_index = abs_xover_index(xover_index);
p1 = p_vec1(xover_index);
p2 = p_vec2(xover_index);
len = length(p1);
if(len > 0)
    eta_cv = ones(1, len) * eta_c ;
    rv = rand(1,len);
    % rv = randompercv(1,len); % SLOW !!!
    randv2lthalf = rand(1,len) < 0.5 ;
    % randv2lthalf = randompercv(1,len) < 0.5 ; % SLOW !!!
    %
    pveclt = p1 < p2 ;
    y1v = p1 .* pveclt + p2 .* (~pveclt);
    y2v = p1 .* (~pveclt) + p2 .* pveclt;
    ylv = min_realvar(xover_index).' ;
    yuv = max_realvar(xover_index).' ;
    %
    beta_v = 1.0 + (2.0 .* (y1v - ylv) ./ (y2v - y1v));
    alpha_v = 2.0 - (beta_v .^ (-1.0 .* (eta_cv + 1.0)));
    rltalpha = rv <= (1.0 ./ alpha_v);
    % bsxfun here ?
    betaqv = ((rv .* alpha_v) .^ (1.0 ./ (eta_cv + 1.0))) .* ...
                rltalpha + ((1.0 ./ (2.0 - rv .* alpha_v)) .^ ...
                    (1.0 ./ (eta_cv + 1.0))) .* (~rltalpha);
    c1v = 0.5 .* ((y1v + y2v) - (betaqv .* (y2v - y1v)));
    %
    beta_v = 1.0 + (2.0 .* (yuv - y2v) ./ (y2v - y1v));
    alpha_v = 2.0 - (beta_v .^ (-1.0 .* (eta_cv + 1.0)));
    rltalpha = rv <= (1.0 ./ alpha_v);
    % bsxfun here ?
    betaqv = ((rv .* alpha_v) .^ (1.0 ./ (eta_cv + 1.0))) .* ...
                rltalpha + ((1.0 ./ (2.0 - rv .* alpha_v)) .^ ...
                    (1.0 ./ (eta_cv + 1.0))) .* (~rltalpha);
    c2v = 0.5 .* ((y1v + y2v) + betaqv .* (y2v - y1v));
    %
    c1ltyl = c1v < ylv ;
    c1gtyu = c1v > yuv ;
    c1v = (c1ltyl .* ylv) + (c1v .* (~c1ltyl));
    c1v = (c1gtyu .* yuv) + (c1v .* (~c1gtyu));
    c2ltyl = c2v < ylv ;
    c2gtyu = c2v > yuv ;
    c2v = (c2ltyl .* ylv) + (c2v .* (~c2ltyl));
    c2v = (c2gtyu .* yuv) + (c2v .* (~c2gtyu));
    %
    p1 = c2v .* randv2lthalf + c1v .* (~randv2lthalf);
    p2 = c1v .* randv2lthalf + c2v .* (~randv2lthalf);
end
%
c_vec1 = c_vec1' ;
c_vec1(abs_xover_index.',1) = p1' ;
c_vec1((~xover_index).',1) = p_vec1(~xover_index).' ;
c_vec1 = c_vec1';
%
c_vec2 = c_vec2' ;
c_vec2(abs_xover_index.',1) = p2' ;
c_vec2((~xover_index).',1) = p_vec2(~xover_index).' ;
c_vec2 = c_vec2' ;
%
end


% calculate non-negative leaset squares
function H = calc_nls_nmf(X, W, lambda)
    R = size(W,2);
    %H = inv(W'*W + lambda * eye(R)) * W'* X;
    H = (W'*W + lambda * eye(R)) \ W'* X;
    H = max(H, 1e-16);
end


% hierarchical bayesian mixture model
function [MCMC_State] = grp_hbmm_mcmc(varargin)

% inputs:
%   B: {subj}(M_i x M_i x Q) matrix of (possibly time-varying)  (obtain from EEG(i).CAT.Conn)
%      connectivities or basis coefficients
%   S: {subj}(M_i x 3) matrix of dipole locations (obtain from EEG(i).dipfit)
%   M_i: [N x 1] vector of number of sources for each subject
%   M:  number of clusters required
% automatically determined:
%   N:  number of subjects (length(B))
%   Q:  number of basis coefficients (size(B{1},3))
%   k:  clustering mode (1 = kmeans, 2=gmm)

arg_define([0 3], varargin, ...
    arg_norep({'B','Connectivity'},mandatory,[],'Connectivity matrices. B{i} is an (M_i x M_i x Q) matrix of (possibly time-varying) connectivity values or basis coefficients for the ith subject. M_i is the number of components for the ith subject.'), ...
    arg_norep({'S','DipoleLocations'},mandatory,[],'Dipole locations. S{i} is an (M_i x 3) matrix of [X Y Z] dipole locations for the ith subject. M_i is the number of components for the ith subject.'), ...
    arg_norep({'MCMC_InitState'},struct([]),[],sprintf('Initial state of MCMC sampler. This can be computed from grp_hbmm_initMCMC or can be the output of a previous call to grp_hmbb_mcmc (i.e. the last state of the MCMC iterator). This structure must contain the following fields:\nZ\t: Mi x M matrices of group indicators\nS_BAR\t: cluster centroid locations\nSIGMA_S\t: cluster centroid variances\nB_BAR\t: group level connectivities\nSIGMA_B\t: variances of connectivities\nN_k\t: number of components that belong to each cluster\nN_k1k2\t: pairwise counts of group level clusters'),'type','struct'), ...
    arg_sub({'hyperparams','Hyperparameters'},[],...
    {...
        arg({'c','ConnMeanPriorVar'},10000,[eps Inf],'Between-cluster mean connectivity prior variance. Variance of gaussian prior for between-cluster mean connectivity. Larger --> more uncertainty'), ...
        arg({'eta','DipoleLocVarPriorShape'},[],[],'Within-cluster dipole location prior variance D.O.F. Degrees of freedom for inverse-wishart prior distribution for within-cluster dipole location covariance matrices. Leave empty to compute from initial clustering.'), ...
        arg({'SS','DipoleLocVarPriorScale'},[],[],'Within-cluster dipole location prior variance scale. Scale matrix of inverse-wishart prior distribution for within-cluster dipole location covariance matrices. Leave empty to compute from initial clustering.'), ...
        arg({'p1','ConnVarPriorShape','a'},1,[],'Between-cluster mean connectivity variance shape.'), ...
        arg({'p2','ConnVarPriorScale','b'},1,[],'Between-cluster mean connectivity variance scale.'), ...
    },'MCMC hyperparameters'), ...
    arg({'nMCMCiters','NumMCMCIters','niter','niters'},1000, [1 Inf], 'Number of MCMC iterations for spline fitting'), ...
    arg({'burnInFraction','BurnInFractionForMCMC'},0.5,[0 0.99],'Fraction of initial MCMC samples to discard (burn in period). The number of MCMC samples is taken to be the smaller of MCMCitersOffDiag and MCMCitersDiag.'), ...
    arg({'thinFactor','ThinningFactor'},1,[1 Inf],'Thinning factor for MCMC. We keep every kth MCMC sample, where k=ThinningFactor. This is useful when we have limited available memory to store MCMC results since successive MCMC estimates are more likely to be correlated.'), ...
    arg({'normlog','NormalizeAndLogTransform'},true,[],'Transform data before smoothinFactorg. Normalize across last dim (time), add 1 and take logarithm. Inverse transform is applied after smoothinFactorg'), ...
    arg({'verb','VerbosityLevel'},2,{int32(0) int32(1) int32(2)},'Verbosity level. 0 = no output, 1 = text, 2 = graphical'), ...
    arg({'appendLastState','AppendLastState'},false,[],'Append new state to initial MCMC state') ...
    );

%     arg({'basisCoeffVarPrior'},1000,[eps Inf],'Variance of basis coefficient gaussian prior. Larger --> more wiggling allowed'), ...
%     arg({'noiseVarPriorShape'},0.01,[eps Inf],'Shape (D.O.F) of noise variance prior. This is the "alpha" parameter of the inverse gamma prior distribution. Increasing noiseVarPriorShape --> decreased variance of noise variance distribution.'), ...
%     arg({'noiseVarPriorScale'},0.01,[eps Inf],'Scale parameter of noise variance prior. This is the "theta" (1/beta) parameter of inverse gamma prior distribution. Increasing noiseVarPriorScale --> right-shift of distribution --> (increase in expected noise variance). In general MEAN(noiseVariance) = noiseVarPriorScale/noiseVarPriorShape and MODE(noiseVariance) = noiseVarPriorScale/(noiseVarPriorShape-1) for noiseVarPriorShape>=1.'), ...
%     arg({'initNoiseVariance'},0.1,[eps Inf],'Initial noise variance'), ...

% set up initial state of MCMC
if isempty(MCMC_InitState)
    error('You must provide an initial state for the MCMC iterator (MCMC_InitState)');
end

% initialize vars based on last state of MCMC_InitState
% this will initialize the following variables:
% 'Z','S_BAR','SIGMA_S','B_BAR','SIGMA_B','N_k','N_k1k2'
varnames = setdiff_bc(fieldnames(MCMC_InitState),'initstate');
if MCMC_InitState.initstate
    % initialize to initial state of MCMC iterator
    for i = 1:length(varnames)
        eval(sprintf('%s=MCMC_InitState.(''%s'');',varnames{i},varnames{i}));
    end
else
    % initialize to last state of MCMC iterator
    for i = 1:length(varnames)
        eval(sprintf('%s=MCMC_InitState.(''%s''){end};',varnames{i},varnames{i}));
    end
end
if ~appendLastState
    clear MCMC_InitState;
end

% define some vars
M   = length(N_k);                      % number of clusters
N   = length(B);                        % number of subjects
M_i = cellfun(@(B_i) size(B_i,1),B);    % number of sources for each subject
Q   = size(B{1},3);                     % connectivity time-series dimension

% initialize prior probabilities of cluster membership
if ~exist('MU','var')
    MU  = ones(M,1)/M; end
% initialize hyperparams
if isempty(hyperparams.eta)
    hyperparams.eta=median(N_k); end
if isempty(hyperparams.SS)
    hyperparams.SS = hyperparams.eta*mean(SIGMA_S,3); end

% compute number of burn-in samples
numBurnInSamples = floor(burnInFraction*nMCMCiters);
niterToKeep      = round((nMCMCiters-numBurnInSamples)/thinFactor);
if verb,
    fprintf(['I will discard %d burn-in samples.\n' ...
             'I will thin the distribution by a factor of %d samples\n', ...
             'The distribution of the estimator will have %d samples\n'], ...
            numBurnInSamples,thinFactor,niterToKeep);
end

%% initialize arrays of posterior draws
Z_array         = cell(niterToKeep,1);
S_BAR_array     = cell(niterToKeep,1);
SIGMA_S_array   = cell(niterToKeep,1);
B_BAR_array     = cell(niterToKeep,1);
SIGMA_B_array   = cell(niterToKeep,1);
MU_array        = cell(niterToKeep,1);
N_k_array       = cell(niterToKeep,1);
N_k1k2_array    = cell(niterToKeep,1);
iter_array      = 0;

%% run MCMC
for iter=1:nMCMCiters

    % Draw S_BAR (group centroid locations)
    SSinv = double(inverse(hyperparams.SS));
    for k=1:M
        mu_s_bar_k=zeros(3,1);
        Sigma_s_bar_k=double(inverse(N_k(k)*double(inverse(SIGMA_S(:,:,k)))+SSinv));
        for i=1:N
            if max(Z{i}(:,k))==1
                j_k=find(Z{i}(:,k)==1);
                for j=1:length(j_k)
                    mu_s_bar_k=mu_s_bar_k+S{i}(j_k(j),:)';
                end
            end
        end
        mu_s_bar_k=Sigma_s_bar_k/SIGMA_S(:,:,k)*mu_s_bar_k;
        S_BAR(k,:)=mvnrnd(mu_s_bar_k,Sigma_s_bar_k);
    end

    % Draw SIGMA_S (group centroid covariance matrices)
    for k=1:M
        eta_k=hyperparams.eta+.5*N_k(k);
        SS_k=hyperparams.SS;
        for i=1:N
            if max(Z{i}(:,k))==1
                j_k=find(Z{i}(:,k)==1);
                for j=1:length(j_k)
                    SS_k=SS_k+.5*(S{i}(j_k(j),:)-S_BAR(k,:))'*(S{i}(j_k(j),:)-S_BAR(k,:));
                end
            end
        end
        SS_k=.5*(SS_k+SS_k');
        inv_SS_k=double(inverse(SS_k));
        inv_SS_k=.5*(inv_SS_k+inv_SS_k');
        SIGMA_S(:,:,k)=double(inverse(wishrnd(inv_SS_k,eta_k)));
    end

    % Draw B_BAR (group mean connectivites)
    for k1=1:M
        for k2=1:M
            mu_b_bar_k1k2=zeros(Q,1);
            Sigma_sq_b_k1k2=1/(1/hyperparams.c+N_k1k2(k1,k2)/SIGMA_B(k1,k2))*eye(Q);
            for i=1:N
                n_k1=sum(Z{i}(:,k1));
                n_k2=sum(Z{i}(:,k2));
                if(and(n_k1>=1,n_k2>=1))
                    j_k1=find(Z{i}(:,k1)==1);
                    j_k2=find(Z{i}(:,k2)==1);
                    for j1=1:n_k1
                        for j2=1:n_k2
                            mu_b_bar_k1k2=mu_b_bar_k1k2+squish(B{i}(j_k1(j1),j_k2(j2),:));
                        end
                    end
                end
            end
            mu_b_bar_k1k2=Sigma_sq_b_k1k2*mu_b_bar_k1k2/SIGMA_B(k1,k2);
            B_BAR(k1,k2,:)=mvnrnd(mu_b_bar_k1k2,Sigma_sq_b_k1k2);
        end
    end

    % Draw SIGMA_B (group connectivity variances)
    for k1=1:M
        for k2=1:M
            p1_k1k2=hyperparams.p1+.5*Q*N_k1k2(k1,k2);
            p2_k1k2=hyperparams.p2;
            for i=1:N
                n_k1=sum(Z{i}(:,k1));
                n_k2=sum(Z{i}(:,k2));
                if(and(n_k1>=1,n_k2>=1))
                    j_k1=find(Z{i}(:,k1)==1);
                    j_k2=find(Z{i}(:,k2)==1);
                    for j1=1:n_k1
                        for j2=1:n_k2
                            p2_k1k2=p2_k1k2+.5*sum((B{i}(j_k1(j1),j_k2(j2),:)-B_BAR(k1,k2,:)).^2);
                        end
                    end
                end
            end
            SIGMA_B(k1,k2)=1/gamrnd(p1_k1k2,1/p2_k1k2);
        end
    end

    % Draw Z (indicators of group membership)
    ind=randperm(N);
    for i=ind
        ind_i=randperm(M_i(i));
        for j=ind_i
            Log_p_ij=zeros(M,1);
            for k=1:M
                Sigma_s_ijk=SIGMA_S(:,:,k);
                s_ijk=S{i}(j,:)-S_BAR(k,:);
                log_p_s_ijk=-.5*log(det(Sigma_s_ijk))-.5*s_ijk/Sigma_s_ijk*s_ijk';
                log_p_b_ijk=0;
                for j1=1:M_i(i)
                    if j==j1
                        log_p_b_ijk=log_p_b_ijk-.5*Q*log(det(SIGMA_B(k,k)))...
                        -.5*sum((B{i}(j,j,:)-B_BAR(k,k,:)).^2)/SIGMA_B(k,k);
                    end
                    if and(not(j==j1),find(Z{i}(j1,:)==1)==k)
                        log_p_b_ijk=log_p_b_ijk-.5*Q*log(det(SIGMA_B(k,k)))...
                        -.5*sum((B{i}(j1,j1,:)-B_BAR(Z{i}(j1,:)==1,Z{i}(j1,:)==1,:)).^2)/...
                            SIGMA_B(Z{i}(j,:)==1,Z{i}(j,:)==1);
                    end
                    if not(find(Z{i}(j,:)==1)==find(Z{i}(j1,:)==1))
                        log_p_b_ijk=log_p_b_ijk-.5*Q*log(det(SIGMA_B(k,Z{i}(j1,:)==1)))...
                        -.5*sum((B{i}(j,j1,:)-B_BAR(k,Z{i}(j1,:)==1,:)).^2)/...
                              SIGMA_B(k,Z{i}(j1,:)==1);
                    end
                end
                for j2=1:M_i(i)
                    if not(find(Z{i}(j2,:)==1)==find(Z{i}(j,:)==1))
                        log_p_b_ijk=log_p_b_ijk-.5*Q*log(det(SIGMA_B(Z{i}(j2,:)==1,k)))...
                        -.5*sum((B{i}(j2,j,:)-B_BAR(Z{i}(j2,:)==1,k,:)).^2)/...
                                SIGMA_B(Z{i}(j2,:)==1,k);
                    end
                end
                Log_p_ij(k)=MU(k)+log_p_s_ijk+log_p_b_ijk;
            end
            Log_p_ij=Log_p_ij-max(Log_p_ij);
            P_ij=exp(Log_p_ij)/sum(exp(Log_p_ij));
            v=rand;
            Z{i}(j,:)=0*Z{i}(j,:);
            if v<=P_ij(1)
                   Z{i}(j,1)=1;
                   log_p_ij=Log_p_ij(1);
            end
            for k=1:(M-1)
               if(and(v>sum(P_ij(1:k)),v<=sum(P_ij(1:(k+1)))))
                   Z{i}(j,k+1)=1;
                   log_p_ij=Log_p_ij(k);
               end
            end
        end
    end

    % Draw MU (probabilities of clusters)
    N_k1k2=zeros(M);
    for k1=1:M
        for k2=1:M
            for i=1:N
                n_k1=sum(Z{i}(:,k1));
                n_k2=sum(Z{i}(:,k2));
                if(and(n_k1>=1,n_k2>=1))
                    if not(k1==k2)
                        N_k1k2(k1,k2)=N_k1k2(k1,k2)+n_k1*n_k2;
                    end
                    if k1==k2
                        N_k1k2(k1,k2)=N_k1k2(k1,k2)+n_k1;
                    end
                end
            end
        end
    end
    N_k=diag(N_k1k2);
    MU=drchrnd(1/M*ones(1,M)+N_k',1);

    % Posterior Draw arrays
    if and(iter>=numBurnInSamples,thinFactor*round(iter/thinFactor)==iter)
        iter_array=iter_array+1;
        Z_array{iter_array}=Z;
        S_BAR_array{iter_array}=S_BAR;
        SIGMA_S_array{iter_array}=SIGMA_S;
        B_BAR_array{iter_array}=B_BAR;
        SIGMA_B_array{iter_array}=SIGMA_B;
        N_k_array{iter_array}=N_k;
        N_k1k2_array{iter_array}= N_k1k2;
        MU_array{iter_array}=MU;

        if verb
            disp(['iter: ' num2str(iter)]);
%             % compute and print out intermediate estimates of source locations
%             S_BAR_mean=S_BAR_array{1};
%             for j=2:iter_array
%                 S_BAR_mean=S_BAR_mean+S_BAR_array{j};
%             end
%             S_BAR_mean=S_BAR_mean/iter_array;
%             disp([S_BAR zeros(M,1) S_BAR_mean])
        end

        %[sqrt(sum(min(pdist2(S_BAR_true,S_BAR_st)').^2))/M sqrt(sum(min(pdist2(S_BAR_true,S_BAR_mean)').^2))/M]

        if verb
%             %compute and print intermediate indicator probs for random subject
%             i=discretesample(ones(N,1)/N,1);
%             Z_i_mean=Z_array{1}{i};
%             for j=2:iter_array
%                Z_i_mean=Z_i_mean+Z_array{j}{i};
%             end
%             Z_i_mean=Z_i_mean/iter_array;
% %         Z_st{i}
%            disp(Z_i_mean);
        end
    end
end

% return MCMC state
varnames = {'Z_array','S_BAR_array','SIGMA_S_array','B_BAR_array','SIGMA_B_array','MU_array','N_k1k2_array','N_k_array'};
for i = 1:length(varnames)
    % state fieldnames have trailing '_array' stripped
    fname = strrep(varnames{i},'_array','');
    vname = varnames{i};
    if appendLastState
        % append current state estimates to last state estimate
        MCMC_State.(fname) = [MCMC_State.(fname); eval(vname)];
    else
        % replace last state estimate with current state estimates
        MCMC_State.(fname) = eval(vname);
    end
end
MCMC_State.initstate = false;

function r = drchrnd(a,n)
    p = length(a);
    r = gamrnd(repmat(a,n,1),1,n,p);
    r = r ./ repmat(sum(r,2),1,p);


function rrd = jpleph_mice (et, ntarg, ncent)

% reads the jpl planetary ephemeris and gives the position and velocity
% of the point 'ntarg' with respect to point 'ncent' using MICE routines

% input

%   et    = TDB julian date at which interpolation is wanted

%   ntarg = integer number of 'target' point

%   ncent = integer number of center point

%   the numbering convention for 'ntarg' and 'ncent' is:

%        1 = mercury           8 = neptune
%        2 = venus             9 = pluto
%        3 = earth            10 = moon
%        4 = mars             11 = sun
%        5 = jupiter
%        6 = saturn
%        7 = uranus

% output

%   rrd = output 6-word array containing position and velocity
%         of point 'ntarg' relative to 'ncent'. the units are
%         determined by the value of km passed via global.

% global

%   iephem  = initialization flag (1 = initialize)
%   ephname = name of ephemeris binary data file (de436.bsp, etc.)
%   km      = state vector units flag (1 = km & km/sec, 0 = au & au/day)
%   au      = numerical value of astronomical unit (kilometers)

% Orbital Mechanics with Matlab

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

global iephem ephname km au

if (iephem == 1)

    % load binary ephemeris data file

    go_dir = Core.getGlobalConfig().getLocalStorageDir();
    cspice_furnsh(fullfile(go_dir, ephname));

    % reset initialization flag

    iephem = 0;

end

% set name of target body

switch ntarg

    case (1)

        targ = 'mercury';

    case (2)

        targ = 'venus';

    case (3)

        targ = 'earth';

    case (4)

        targ = 'mars';

    case(5)

        targ = 'jupiter';

    case (6)

        targ = 'saturn';

    case (7)

        targ = 'neptune';

    case (8)

        targ = 'uranus';

    case (9)

        targ = 'pluto';

    case (10)

        targ = 'moon';

    case (11)

        targ = 'sun';

end

% set name of central body

switch ncent

    case (1)

        obs = 'mercury';

    case (2)

        obs = 'venus';

    case (3)

        obs = 'earth';

    case (4)

        obs = 'mars';

    case (5)

        obs = 'jupiter';

    case (6)

        obs = 'saturn';

    case (7)

        obs = 'neptune';

    case (8)

        obs = 'uranus';

    case (9)

        obs = 'pluto';

    case (10)

        obs = 'moon';

    case (11)

        obs = 'sun';

end

% compute time, expressed as TDB seconds past J2000 TDB (2451545.0)

etime = 86400.0d0 * (et - 2451545.0d0);

% compute position and velocity vectors in eme2000 system (no corrections)

starg = mice_spkezr(targ, etime, 'J2000', 'NONE', obs);

% provide output in user-requested units

if (km == 1)

    % state is kilometers and kilometers/second

    rrd = starg.state;

else

    % state is au's and au's/day

    rrd(1:3) = starg.state(1:3) / au;

    rrd(4:6) = 86400.0 * starg.state(4:6) / au;

    rrd = rrd';

end


%
% BEZIER
%
% ger en bezierkurza mellan p1 och p2, med styrpunkter b och c. n stycken
% punkter, inkluderar p1, men inte p2.

function r=bezier(p1,b,c,p2,n)

t=(0:1/n:1-(1/n)/2)';
r=(1-t).^3*p1+3*t.*(1-t).^2*b+3*t.^2.*(1-t)*c+t.^3*p2;


function [x, infos] = deep_bidirectional_nmf(X, rank_layers, in_options)
% Deep Bidir-Semi-NMF.
%
% The problem of interest is defined as
%
%           min || X - Z_1 * Z_2 * ... * Z_n * H_n ||_F^2,
%           where
%           {H_n} >= 0.
%
% Given a matrix X, factor matrices {Z_1, Z_2, ..., Z_n, H_n} are calculated.
%
%
% Inputs:
%       X           : (m x n) matrix to factorize
%       rank_layers : ranks in each layer
%       in_options
%
%
% Output:
%       x           : non-negative matrix solution, i.e., x.Z (cell), x.H (cell)
%       infos       : log information
%           epoch   : iteration nuber
%           cost    : objective function value
%           optgap  : optimality gap
%           time    : elapsed time
%           grad_calc_count : number of sampled data elements (gradient calculations)
%
% References
%           G. Trigeorgis, K. Bousmalis, S. Zafeiriou and B. Schuller,
%           "A deep semi-NMF model for learning hidden representations",
%           ICML2014, 2014.
%
%           G. Trigeorgis, K. Bousmalis, S. Zafeiriou and B. Schuller
%           "A deep matrix factorization method for learning attribute representations,"
%           IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI), vol.39, no.3, pp.417-429, 2017
%
%
% This file is part of NMFLibrary
%
% Originally created by G.Trigeorgis.
%
% Change log:
%
%       Jul. 26, 2018 (Hiroyuki Kasai): Modified code structures.
%
%       Jul. 12, 2022 (Hiroyuki Kasai): Modified code structures.
%

    % set dimensions and samples
    [m, n] = size(X);

    % set the number of rank_layers
    num_of_layers = numel(rank_layers);

    % set local options
    local_options.bUpdateZ  = true;
    local_options.bUpdateH  = true;
    local_options.bUpdateLastH = true;
    local_options.deepZ     = true;
    local_options.deepH     = true;
    local_options.updateH_alg = 'mu';  % 'mu' or 'apg'
    local_options.eval_clustering_acc = 0;
    local_options.classnum      = 0;

    % check input options
    if ~exist('in_options', 'var') || isempty(in_options)
        in_options = struct();
    end
    % merge options
    options = mergeOptions(get_nmf_default_options(), local_options);
    options = mergeOptions(options, in_options);

    % initialize
    method_name = 'Deep-Bidir-SemiNMF';
    epoch = 0;
    grad_calc_count = 0;

    if options.verbose > 0
        fprintf('# %s: started ...\n', method_name);
    end

    if ~isempty(options.gnd) && options.classnum > 1
        options.eval_clustering_acc = 1;
    end

    % initialize Z and H
    Z = cell(1, num_of_layers);
    H = cell(1, num_of_layers);
    P = cell(1, num_of_layers);
    Q = cell(1, num_of_layers);
    %A = cell(1, num_of_layers+1);
    %B = cell(1, num_of_layers);
    if ~isfield(options, 'x_init')

        if options.deepH
            for i_layer = 1:num_of_layers

                if options.verbose > 1
                    fprintf('### Initializing by %s for layer %d ... ', method_name, i_layer);
                end

                if i_layer == 1
                    % For the first layer we go linear from X to Z*H, so we use id
                    V = X;
                else
                    V = H{i_layer-1};
                end

                % For the later rank_layers we use nonlinearities as we go from
                % g(H_{k-1}) to Z*H_k
                semi_nmf_options.max_iter  = options.max_epoch;
                semi_nmf_options.bUpdateH  = options.bUpdateH;
                semi_nmf_options.bUpdateZ  = options.bUpdateZ;
                semi_nmf_options.verbose   = 0;

                [semi_nmf_x, ~] = semi_mu_nmf(V, rank_layers(i_layer), semi_nmf_options);
                Z{i_layer} = semi_nmf_x.W;
                H{i_layer} = semi_nmf_x.H;

                %fprintf('V: %5.5f, Zi: %5.5f, Hi: %5.5f\n', norm(V), norm(Z{i_layer}), norm(H{i_layer}));
                if options.verbose > 1
                    fprintf('done\n');
                end
            end
        end

        if options.deepZ
            XT = X';
            for i_layer = 1:num_of_layers

                if options.verbose > 1
                    fprintf('### Initializing by %s for layer %d ... ', method_name, i_layer);
                end

                if i_layer == 1
                    % For the first layer we go linear from X to Z*H, so we use id
                    V = XT;
                else
                    V = Q{i_layer-1};
                end

                % For the later rank_layers we use nonlinearities as we go from
                % g(H_{k-1}) to Z*H_k
                semi_nmf_options.max_iter  = options.max_epoch;
                semi_nmf_options.bUpdateH  = options.bUpdateH;
                semi_nmf_options.bUpdateZ  = options.bUpdateZ;
                semi_nmf_options.verbose   = 0;

                [semi_nmf_x, ~] = semi_mu_nmf(V, rank_layers(i_layer), semi_nmf_options);
                P{i_layer} = semi_nmf_x.W;
                Q{i_layer} = semi_nmf_x.H;

                %fprintf('V: %5.5f, Zi: %5.5f, Hi: %5.5f\n', norm(V), norm(Z{i_layer}), norm(H{i_layer}));
                if options.verbose > 1
                    fprintf('done\n');
                end
            end
        end

    else
        Z = options.Z;
        H = options.H;
    end

    Hm = H{num_of_layers};
    Qm = Q{num_of_layers};

    % select disp_freq
    disp_freq = set_disp_frequency(options);

    % store initial info
    clear infos;
    Z_rec = reconst_Z(Z, num_of_layers);

   % Concatinated_H = B{1};
    %Concatinated_Q = Z{1}';

    if options.deepH
        B{num_of_layers} = Hm;
        for i_layer = num_of_layers-1:-1:1
            B{i_layer} = Z{i_layer+1} * B{i_layer+1};
        end
        H_rec = B{1};
        Z_rec = Z{1};
        Concatinated_Q = Z{1}';
    elseif options.deepZ
        B{num_of_layers} = Qm;
        for i_layer = num_of_layers-1:-1:1
            B{i_layer} = P{i_layer+1} * B{i_layer+1};
        end
        H_rec = P{1}';
        Z_rec = B{1}';
    elseif options.deepH && options.deepZ

    end

    %[infos, f_val, optgap] = store_nmf_info(X, Z_rec, Hm, [], options, [], epoch, grad_calc_count, 0);
    [infos, f_val, optgap] = store_nmf_info(X, Z_rec, H_rec, [], options, [], epoch, grad_calc_count, 0);

    % evaluate clustering accuracy
    if ~isempty(options.gnd) && options.classnum > 1
        [infos] = store_clustering_accuracy(Hm, options.gnd, options.classnum, infos, options.eval_clustering_num, 0);
    end

    if options.verbose > 1
        fprintf('%s: Epoch = 0000, cost = %.16e, optgap = %.4e\n', method_name, f_val, optgap);
    end

    % set start time
    start_time = tic();

    % main loop
    while true

        % check stop condition
        [stop_flag, reason, max_reached_flag] = check_stop_condition(epoch, infos, options);
        if stop_flag
            display_stop_reason(epoch, infos, options, method_name, reason, max_reached_flag);
            break;
        end

        % update Z and deep H
        if options.deepH
            Z{1} = Concatinated_Q';
            [Z, Hm, Concatinated_H] = calc_deep_matrices(X, Z, Hm, num_of_layers, options, 1);
        else
            Concatinated_H = P{1}';
        end

%         [infos, f_val, optgap] = store_nmf_info(X, Z{1}, Concatinated_H, [], options, infos, epoch, grad_calc_count, 0);
%         % display infos
%         if options.verbose > 1
%             if ~mod(epoch, disp_freq)
%                 fprintf('Deep-SemiNMF (B): Epoch = %04d, cost = %.16e, optgap = %.4e\n', epoch, f_val, optgap);
%             end
%         end
%
        if options.deepZ
            % update H and deep Z
            P{1} = Concatinated_H';
            [P, Qm, Concatinated_Q] = calc_deep_matrices(XT, P, Qm, num_of_layers, options, 0);
        else
            Concatinated_Q = Z{1}';
        end

        %fprintf('%e\n', norm(XT-P{1}*Concatinated_Q, 'fro')^2 / 2 );

        % measure elapsed time
        elapsed_time = toc(start_time);

        % measure gradient calc count
        grad_calc_count = grad_calc_count + m*n;

        % update epoch
        epoch = epoch + 1;

        % store info
        %Z_rec = reconst_Z(Z, num_of_layers);
        %[infos, f_val, optgap] = store_nmf_info(X, Z_rec, Hm, [], options, infos, epoch, grad_calc_count, elapsed_time);
        [infos, f_val, optgap] = store_nmf_info(X, Concatinated_Q', P{1}', [], options, infos, epoch, grad_calc_count, elapsed_time);

%         % display infos
%         if options.verbose > 1
%             if ~mod(epoch, disp_freq)
%                 fprintf('Deep-SemiNMF: Epoch = %04d, cost = %.16e, optgap = %.4e\n', epoch, f_val, optgap);
%             end
%         end

        % evaluate clustering accuracy
        if options.eval_clustering_acc
            [infos] = store_clustering_accuracy(Hm, options.gnd, options.classnum, infos, options.eval_clustering_num, epoch);
        end

        % display infos
        if options.verbose > 1
            if ~mod(epoch, disp_freq)

                if ~options.eval_clustering_acc
                    fprintf('%s: Epoch = %04d, cost = %.16e, optgap = %.4e\n', method_name, epoch, f_val, optgap);
                else
                    fprintf('%s: Epoch = %04d, cost = %.16e, optgap = %.4e, acc = %.4f, nmi = %.4f, purity = %.4f, f = %.4f\n', ...
                        method_name, epoch, f_val, optgap, infos.clustering_acc(end).acc, infos.clustering_acc(end).nmi, infos.clustering_acc(end).purity, infos.clustering_acc(end).f_val);
                end

            end

        elseif options.verbose == 1
            textwaitbar(epoch, options.max_epoch, '  progress');
        end

    end

    H{num_of_layers} = Hm;
    x.Z = Z;
    x.H = H;

end

function [Z, Hm, Concatinated_H] = calc_deep_matrices(X, Z, Hm, num_of_layers, options, Hm_nonnegative)

    A = cell(1, num_of_layers+1);
    B = cell(1, num_of_layers);

    m = size(Z, 1);

    % B{1} = Z{2} * Z{3} * ... * Z{num_of_layers} * Hm
    % B{2} = Z{3} * ... * Z{num_of_layers} * Hm
    % B{3} = ....
    % ....
    % B{num_of_layers} = Hm
    B{num_of_layers} = Hm;
    for i_layer = num_of_layers-1:-1:1
        B{i_layer} = Z{i_layer+1} * B{i_layer+1};
    end

    %% update Z
    % where Z = Z{1} = X * (B{1})^{-1} due to Z{1} B{1} = X.
    Z{1} = X  * pinv(B{1});

    A{1} = eye(m);
    % update Z{2}, ... , Z{num_of_layers}
    for i = 2 : num_of_layers

        % A{1} =
        % A{2} = Z{1}
        % A{3} = Z{1} * Z{2}
        % A{4} = Z{1} * Z{2} * Z{3}
        % ....
        % A{num_of_layers} = Z{1} * Z{2} * ... * Z{num_of_layers-1}
        if i == 2
            A{i} = Z{i-1};
        else
            A{i} = A{i-1} * Z{i-1};
        end

        Z{i} = pinv(A{i}) * X * pinv(B{i});

    end

    % update Hm
    A{num_of_layers+1} = A{num_of_layers} * Z{num_of_layers};
    if Hm_nonnegative
        if strcmp(options.updateH_alg, 'mu')
            P = A{num_of_layers+1}' * X;
            Pp = (abs(P)+P)./2;
            Pn = (abs(P)-P)./2;

            Q = A{num_of_layers+1}' * A{num_of_layers+1};

            Qp = (abs(Q)+Q)./2;
            Qn = (abs(Q)-Q)./2;

            Hm = Hm .* sqrt((Pp + Qn * Hm) ./ max(Pn + Qp * Hm, 1e-16));
        else
            % min_H 1/2 | X - A{num_of_layers+1}*H |^2_F
            % --> min_H 1/2 | X' - H' * A{num_of_layers+1}' |^2_F
            % ----> min_A 1/2 | X - A * B' |^2_F in "nesterov_mnls(X, B, A,..)"
            [tmpH, ~, ~] = nesterov_mnls(X', A{num_of_layers+1}, Hm', 1, options.apg_maxiter, 'basic');
            Hm = tmpH';
        end
    else
        Hm = pinv(A{num_of_layers+1}) * X;
    end

    %
    B{num_of_layers} = Hm;
    for i_layer = num_of_layers-1:-1:1
        B{i_layer} = Z{i_layer+1} * B{i_layer+1};
    end
    Concatinated_H = B{1};

%     % B{1} = Z{2} * Z{3} * ... * Z{num_of_layers} * H{num_of_layers}
%     % B{2} = Z{3} * ... * Z{num_of_layers} * H{num_of_layers}
%     % B{3} = ....
%     % ....
%     % B{num_of_layers} = H{num_of_layers}
%     B{num_of_layers} = H{num_of_layers};
%     for i_layer = num_of_layers-1:-1:1
%         B{i_layer} = Z{i_layer+1} * B{i_layer+1};
%     end
%
%     %% update Z
%     % where Z = Z{1} = X * (B{1})^{-1} due to Z{1} B{1} = X.
%     Z{1} = X  * pinv(B{1});
%
%     A{1} = eye(m);
%     % update Z{2}, ... , Z{num_of_layers}
%     for i = 2 : num_of_layers
%
%         % A{1} =
%         % A{2} = Z{1}
%         % A{3} = Z{1} * Z{2}
%         % A{4} = Z{1} * Z{2} * Z{3}
%         % ....
%         % A{num_of_layers} = Z{1} * Z{2} * ... * Z{num_of_layers-1}
%         if i == 2
%             A{i} = Z{i-1};
%         else
%             A{i} = A{i-1} * Z{i-1};
%         end
%
%         Z{i} = pinv(A{i}) * X * pinv(B{i});
%
%     end
%
%     % update H
%     A{num_of_layers+1} = A{num_of_layers} * Z{num_of_layers};
%     if strcmp(options.updateH_alg, 'mu')
%         P = A{num_of_layers+1}' * X;
%         Pp = (abs(P)+P)./2;
%         Pn = (abs(P)-P)./2;
%
%         Q = A{num_of_layers+1}' * A{num_of_layers+1};
%
%         Qp = (abs(Q)+Q)./2;
%         Qn = (abs(Q)-Q)./2;
%
%
%         H{num_of_layers} = H{num_of_layers} .* sqrt((Pp + Qn * H{num_of_layers}) ./ max(Pn + Qp * H{num_of_layers}, 1e-10));
%     else
%         % min_H 1/2 | X - A{num_of_layers+1}*H |^2_F
%         % --> min_H 1/2 | X' - H' * A{num_of_layers+1}' |^2_F
%         % ----> min_A 1/2 | X - A * B' |^2_F in "nesterov_mnls(X, B, A,..)"
%         [tmpH, ~, ~] = nesterov_mnls(X', A{num_of_layers+1}, H{num_of_layers}', 1, 100, 'basic');
%         H{num_of_layers} = tmpH';
%     end

end

% calculate Z = Z_1 * Z_2 * ... * Z_n
function Z_rec = reconst_Z(Z, num_of_layers)

    Z_rec = Z{num_of_layers};

    for k = num_of_layers-1 : -1 : 1
        Z_rec =  Z{k} * Z_rec;
    end

end


function Kss=cp_SSgain(A,Q,H,R,P, mtd)
num_st=size(A,1);

if (mtd==0)     %method to use when dare fails to reach a solution
    % [Beq, yy]=chol(Q,'lower');
    Beq=Q^0.5;

    %basis for observable space
    Ob=obsv(A,H);
    [u,s,v]=svd(Ob'*Ob);
    num_null_obs=length(find(diag(s)<1e-6));
    num_range_obs=num_st-num_null_obs;
    base_null_obs=v(:,(num_range_obs+1):num_st);
    base_range_obs=u(:,1:num_range_obs);
    Wo=[base_null_obs base_range_obs];   %x=Wz

    %basis for controllable and uncontrollable spaces
    Co=ctrb(A,Beq);
    [u,s,v]=svd(Co*Co');
    num_null_ctr=length(find(diag(s)<1e-12));
    num_range_ctr=size(A,1)-num_null_ctr;
    base_null_ctr=v(:,(num_range_ctr+1):size(A,1));
    base_range_ctr=u(:,1:num_range_ctr);
    Wc=[base_null_ctr base_range_ctr];   %x=Wz

    %%%compute the intersection of observable space with cont/uncont spaces
    %obs+ctr space (min real)
    if (num_null_ctr>0)
        mx_a=[base_range_obs base_range_ctr];
        [mx_b,pvt] = rref(mx_a);
        ind=setdiff(1:(num_range_obs+num_range_ctr),pvt);
        if (~isempty(ind))
            Woc=base_range_obs*mx_b(1:num_range_obs,ind);
            Woc*diag(diag(Woc'*Woc).^-0.5); %normalize
            num_base_oc=size(Woc,2);
        else
            Woc=[];
            num_base_oc=0;
        end
        %obs+unctr space
        mx_a=[base_range_obs base_null_ctr];
        [mx_b,pvt] = rref(mx_a);
        ind=setdiff(1:(num_range_obs+num_null_ctr),pvt);
        if (~isempty(ind))
            Wou=base_range_obs*mx_b(1:num_range_obs,ind);
            Wou=diag(diag(Wou'*Wou).^-0.5); %normalize
            num_base_ou=size(Wou,2);
        else
            Wou=[];
            num_base_ou=0;
        end

        %%%overall transformation matrix
        W=[base_null_obs Wou Woc];
    else    %no uncontrollable subspace
        W=[base_null_obs base_range_obs];
        num_base_ou=0;
        num_base_oc=base_range_obs;
    end

    %transform the system
    W_inv=inv(W);
    A_t=W_inv*A*W;
    Beq_t=W_inv*Beq;
    Qeq_t=Beq_t*Beq_t';
    H_t=H*W;

    %partitions
    in1=num_null_obs;
    in2=num_null_obs+num_base_ou;
    in3=num_null_obs+num_base_ou+num_base_oc;

    Au=A_t(1:in1,1:in1);   %unobservable part
    Au_cross=A_t(1:in1,in1+1:end);   %cross effects of observable part on unobservable part
    Aou=A_t(in1+1:in2,in1+1:in2);   %observable but unctrollable part
    Aoc=A_t(in2+1:end,in2+1:end);   %observable and controllable
    Aoc_cross=A_t(in2+1:end,in1+1:in2); %cross effects of ou part on oc part (not used)
    Ao=A_t(in1+1:end,in1+1:end);   %observable part

    Qu=Qeq_t(1:in1,1:in1);
    Qu_cross=Qeq_t(1:in1,in1+1:end);    %cross disturbance between o and uno parts
    Qoc=Qeq_t(in2+1:end,in2+1:end);
    Qo=Qeq_t(in1+1:end,in1+1:end);

    Hou=H_t(:,in1+1:in2);
    Hoc=H_t(:,in2+1:end);
    Ho=H_t(:,in1+1:end);    %observable

    %steady state solution for ubsorvable and controllable parts (including
    %uncont. parts except the unit circle)
    [Poc,clp,Koc]=dare(Aoc',Hoc', Qoc, R);
    Koc=Poc*Hoc'*inv(Hoc*Poc*Hoc'+R);

    %Add SS gain values for the unit circle uncontrollable parts but observable
    %parts
    Ko=[zeros(num_base_ou,size(Koc,2));Koc];
    Po=diag_mat(Poc,1,zeros(num_base_ou));

    % %%Steady state solution for observable part
    % [Po,clp,Ko]=dare(A3',H2', Q3, R);
    % Ko=Po*H2'*inv(H2*Po*H2'+R);

    %%%%Compute the cross gain
    mx_a=Au;
    mx_b=(eye(num_range_obs)-Ho'*inv(Ho*Po*Ho'+R)*Ho*Po)*Ao';
    mx_c=Au_cross*(Po-Ko*Ho*Po)'*Ao'+Qu_cross;
    %%check the multiplication of eigenvalues
    if (isempty(mx_a)) %no unobservable space
        eig_mul=0;
    else
        lef=eig(mx_a);
        rig=eig(mx_b);
        eig_mul=lef*rig';
    end

    if (isempty(find(eig_mul==1,1,'first')))
        Pu_cross = dlyap(mx_a,mx_b,mx_c);
        Ku_cross=Pu_cross*Ho'*inv(Ho*Po*Ho'+R);
        %%overall gain
        Kss=W*[Ku_cross;Ko];
    else %in this case stein equation has no unique solution (solution depends initial P value)
        %Perform standard KF iteration to find a K
        disp('comp_ss_gain: NO SS!!');
        K_pre=zeros(num_st,size(H,1));
        K=P*H'*inv(H*P*H'+R);
        while (max(abs(K(:)-K_pre(:))>1e-7))
            K_pre=K;
            P=(eye(size(A))-K*H)*P;
            P=A*P*A'+Beq*Beq';
            K=P*H'*inv(H*P*H'+R);
        end
        Kss=K;
    end
elseif (mtd==1) %iterations
    K_pre=zeros(num_st,size(H,1));
    K=P*H'*inv(H*P*H'+R);
    in=0;
    while (max(abs(K(:)-K_pre(:))>1e-7))
        K_pre=K;
        P=(eye(size(A))-K*H)*P;
        P=A*P*A'+Q;
        K=P*H'*inv(H*P*H'+R);
        in=in+1;
    end
    disp(['cpSSgain 145: SS iteration =' num2str(in)]);
    Kss=K;
end


function gdout=filterbankdual(g,a,varargin)
%FILTERBANKDUAL  Dual filters
%   Usage:  gd=filterbankdual(g,a,L);
%           gd=filterbankdual(g,a);
%
%
%   `filterbankdual(g,a,L)` computes the canonical dual filters of *g* for a
%   channel subsampling rate of *a* (hop-size) and system length *L*.
%   *L* must be compatible with subsampling rate *a* as
%   `L==filterbanklength(L,a)`. This will create a dual frame valid for
%   signals of length *L*.
%
%   `filterabankrealdual(g,a)` does the same, but the filters must be FIR
%   filters, as the transform length is unspecified. *L* will be set to
%   next suitable length equal or bigger than the longest impulse response
%   such that `L=filterbanklength(gl_longest,a)`.
%
%   The input and output format of the filters *g* are described in the
%   help of |filterbank|.
%
%   In addition, the funtion recognizes a 'forcepainless' flag which
%   forces treating the filterbank *g* and *a* as a painless case
%   filterbank.
%
%   To actually invert the output of a filterbank, use the dual filters
%   together with the |ifilterbank| function.
%
%   REMARK: In general, perfect reconstruction can be obtained for signals
%   of length *L*. In some cases, using dual system calculated for shorter
%   *L* might work but check the reconstruction error.
%
%   See also: filterbank, ufilterbank, ifilterbank

complainif_notenoughargs(nargin,2,'FILTERBANKDUAL');

definput.import={'filterbankdual'};
definput.flags.outformat = {'fir','full','econ','asfreqfilter'};
definput.keyvals.efsuppthr = 10^(-5);

[flags,kv,L]=ltfatarghelper({'L'},definput,varargin);

[g,asan,info]=filterbankwin(g,a,L,'normal');
if isempty(L)
    if info.isfir
        % Pick shortest possible length for FIR filterbank
        L = filterbanklength(info.longestfilter,asan);
    else
        % Just thow an error, nothing reasonable can be done without L
        error(['%s: L must be specified when not working with FIR ',...'
               'filterbanks.'], upper(mfilename));
    end
end
M=info.M;

% Force usage of the painless algorithm
if flags.do_forcepainless
    info.ispainless = 1;
end

% Check user defined L
if L~=filterbanklength(L,a)
     error(['%s: Specified length L is incompatible with the length of ' ...
            'the time shifts.'],upper(mfilename));
end;

% Prioritize painless over uniform algorithm if both are suitable
if info.isuniform && info.ispainless
    info.isuniform = 0;
end

% Factorization of frame operator to block-diagonal matrix
if info.isuniform
  % Uniform filterbank, use polyphase representation
  a=a(1);

  % Transfer functions of individual filters as cols
  G = filterbankfreqz(g,a,L);

  N=L/a;

  gd=zeros(M,N,class(G));

  for w=0:N-1
    idx = mod(w-(0:a-1)*N,L)+1;
    H = G(idx,:);

    H=pinv(H)';

    gd(:,idx)=H.';
  end;
  % gd was created transposed because the indexing gd(:,idx_a)
  % is much faster than gd(idx_a,:)
  gd =  gd.';
  thisclass = class(G);

switch flags.outformat
    case 'fir'
        gd=ifft(gd)*a;
        % Matrix cols to cell elements + cast
        gdout = cellfun(@(gdEl) cast(gdEl,thisclass), num2cell(gd,1),...
                  'UniformOutput',0);

  %      All filters in gdout will be treated as FIR of length L. Convert them
  %      to a struct with .h and .offset format.
        gdout = filterbankwin(gdout,a);
    case 'full'

        gdout = gd*a;
    case 'econ'
        Shorten filters to essential support
        gd = gd*a;
        gdout=economize_filters(gd,'efsuppthr',kv.efsuppthr);

    case 'asfreqfilter'
        gd = gd*a;
%        All filters in gdout will be treated as (numeric) freqfilter format.
%        Manually convert them to a struct with .H and .foff.
        template = struct('H',[],'foff',0,'realonly',0,'delay',0,'L',L);
        gdout = cell(1,M);
        gdout(:) = {template};

        [H,foff,~]=economize_filters(gd,'efsuppthr',kv.efsuppthr);
        for kk = 1:M
            gdout{kk} = setfield(gdout{kk},'H',H{kk});
            gdout{kk} = setfield(gdout{kk},'foff',foff(kk));
        end
    otherwise
        error('%s: Unknown filter format.', upper(mfilename));
end

elseif info.ispainless
   % Factorized frame operator is diagonal.
   gdout = comp_painlessfilterbank(g,asan,L,'dual',0);
else
        error(['%s: The canonical dual frame of this system is not a ' ...
               'filterbank. You must either call an iterative ' ...
               'method to perform the desired inverstion or transform ',...
               'or transform the filterbank to uniform one. Please see ' ...
               'FRANAITER or FRSYNITER for the former and ',...
               'NONU2UFILTERBANK for the latter case.'],upper(mfilename));

end;


function [ y ] = ill_si( t, x, lambda)
%ILL_SIR Summary of this function goes here
%   输入参数 x 包含一个分量，为：Infective 的比例
% lambda   日接触率

y = [lambda * x(1) * (1 - x(1))]';

end


function h = PlotSpectrogram (x, varargin)
% Plot a gray-level spectrogram. The spectrogram is plotted as an image
% with the intensities encoding the levels. The spectrogram has time on the
% abscissa and frequency on the ordinate axis. The spectrogram consists of
% vertical slices displaying the spectral response as intensities in dB.
% The slices are calculated as the DFT of windowed samples. The time window
% for each slice is centred at the corresponding abscissa point. The length
% of the time window determines the time-frequency resolution trade-off.
% The default window length (190 samples) gives relatively good frequency
% resolution.
%
% The intensity values in the plot is calibrated in terms of the decibel
% value relative to a full scale (dBov, see ITU-T G.100.1). A full scale
% sine wave with frequency at one of the DFT values appears  at -6 dBov.
% The default mapping makes the maximum value appear as the darkest value
% and -80 dB below that correspond to the lightest value.
%
%   h = PlotSpectrogram (x, [ts, tf], Fs, Options)
% Simplified forms
%   h = PlotSpectrogram (x, Fs)
%   h = PlotSPectrogram (x)
% Options can appear as the last arguments in any of these forms. Options
% take the form of pairs of arguments, the first being a keyword, the
% second being a value.
%
% h    - handle to the image
% x    - data vector. The data is considered to be extended with zeros at
%        each end in case the windows extend into those regions.
% [ts, tf] - Specifies the start and stop times. The first spectrogram
%        slice is centred at ts and the last spectrogram slice is centred
%        at tf. The default is to create slices spanning the entire data
%        vector, i.e., ts=0, tf=(N-1)/Fs, where N is the number of samples
%        in the data vector.
% Fs   - sampling frequency, default 1.
%
% Options
%  Data window
%    The data window used for each spectrum slice can be specified with
%    the keywords 'Win', 'Lwin' or 'BW'.
%  'Win': the data window vector is explicitly specified.
%  'LWin': the length of the data window (in samples) is specified.
%    A Hamming window of that length is used.
%  'BW': the 3 dB bandwidth (in Hz) of the Hamming window to be used
%    is specified. The length of the Hamming window is chosen using the
%    formula, LWin = 1.18523 * Fs / BW. The default bandwidth is Fs/160.
%    The bandwidth can also be specified with the character strings
%    'NB' (narrowband spectrogram - good frequency resolution, but poor
%    time resolution, same as Fs/160), or 'WB' (wideband spectrogram - poor
%    frequency resolution, but good time resolution, same as 6/160 Fs).
% 'NSlice'
%   The number of spectrogram slices, default 500.
% 'Nfft'
%   The number of rows in the spectrogram is determined by the DFT size,
%   Nfft. The Nfft/2+1 rows correspond to frequencies from 0 to Fs/2. The
%   frequency resolution of the DFT is Fs/Nfft. If not specified, Nfft is
%   chosen to be 1024 or 2048, with the larger value chosen if the window
%   length is greater than 0.9*1024 (0.9 is the Kell factor).
% 'PLimdB'
%   Minimum and maximum levels for the spectrogram on dB. The spectrogram
%   levels are clipped outside of these values. The value for 'PLimdB' can
%   be a two element vector giving the minimum and maximum values. The
%   'PLimdB' value can also be a single value giving the dynamic range in
%   dB. In that case, the maximum is determined automatically and the
%   minimum is set according to the specified dynamic range. The default is
%   to determine the maximum automatically and apply a dynamic range of 80
%   dB.
% 'Amax'
%   The data is expected to take on values between -Amax to +Amax. The
%   default is 1 is suitable for data acquired from sound files. This value
%   affects the scaling of the intensities.
% 'FLim'
%   This parameter is of the form [Fs, Ff] specifying the frequency range
%   of the spectrogram to be plotted. The spectrogram is always calculated
%   over the whole frequency range. This parameter can be used to select
%   a subrange of frequencies for plotting. The default is [0, Fs/2].
% 'preF'
%   The frequency response values can be pre-emphasized to better show
%   lower amplitude high-frequency components. The pre-emphasis response is
%   that of a first order difference filter with parameter preF. The
%   default value of preF is 0. A suitable value for pre-emphasis might be
%   0.97.
%
% Notes:
% - To get a colour spectrogram with a colorbar legend:
%       PlotSpectrogram(...);
%       colormap(SpecColorMap);
%       colorbar;
% - To get an intensity scale in dB SPL (sound pressure level):
%   - Assume that a full scale sine results in PmaxdBSPL (often chosen to
%     be 92 dB SPL).
%   - Then to convert dBov to dB SPL,
%       PmaxdBSPL = 92;  % Max level in dB SPL
%       Amax = 1;        % Assume normalized scaling from a sound file
%       PoffsdB = PmaxdBSPL + 20*log10(2);
%       g = 10^(PoffsdB/20);
%       AmaxN = Amax/g;
%       PlotSpectrogram(..., 'Amax', AmaxN);
%   - This conversion brings the peak sine wave response in the display
%     to be PmaxdBSPL.

% $Id: PlotSpectrogram.m,v 1.10 2009/06/01 18:38:22 pkabal Exp $

% Process arguments
[TLim, Fs, Options] = DecodeArgs(varargin{:});

% Process options
[NSlice, Win, Nfft, FLim, PLimdB, Amax, preF] = ...
                                            PSDecodeOptions(Options, Fs);
% Fill in the default time limits
% Set the times of the slices
if (isempty(TLim))
  TLim = [0 (length(x)-1)/Fs];
end
t = linspace(TLim(1), TLim(2), NSlice);

% Get spectrum in dB
[PdB, f] = SpecSlices(t, x, Fs, Win, Nfft);

% Select a subset of frequencies
I = find(f >= FLim(1) & f <= FLim(2));
f = f(I);
PdB = PdB(I,:);

% Convert to dBov
PdBov = SpecCalib(PdB, Amax, Win);

% Pre-emphasis
pre = (1 + preF^2) - 2 * preF * cos(2 * pi * f / Fs);
predB = 10 * log10(pre');
for (i = 1:NSlice)
  PdBov(:,i) = PdBov(:,i) + predB;
end

% Automatic scaling
if (length(PLimdB) == 1)
  PmaxdB = max(max(PdBov));
  PLimdB = [PmaxdB-PLimdB PmaxdB];
end

% Plot the image
h = imagesc(t, f, PdBov, PLimdB);
axis('xy');

% Grayscale map (dark is more intense)
colormap(flipud(gray));

xlabel('Time (s)');
ylabel('Frequency (Hz)');

return

% ===============================
function PdBov = SpecCalib (PdB, Amax, Win)
% Calibration of a spectrum, The input spectrum in dB is gain modified so
% that a sine wave of maximum amplitude Amax gives a total energy level of
% -3 dBov. This mean that each of the two peaks (one at -fc and the other
% at +fc) will have a level of -6 dBov. A dc level of Amax will give a
% 0 dBov level at zero frequency.
% - Amax, maximum level of the sinusoid (typically 1 for scaled data from
%   sound files). With the true maximum level, the scaling gives an
%   output in dBov. However, Amax can be artificially changed to affect a
%   change in scaling. For instance halving Amax, increases the levels by
%   6 dB.

% Calibration
% - Input:
%   - dc peak amplitude Amax gives 0 dBov
%      - DFT gives sum(Win) at dc
%   - sine peak amplitude Amax give -6dBov at fc and -fc
%     - Assumes sine frequency is coincident with a DFT bin, i.e. fc is of
%       the form m * Fs/Nfft
%     - The peak amplitude of the DFT of the windowed response (assuming no
%       overlap of the components due to the sine at -fc and +fc) is
%       Apeak = abs (Amax / 2 * Wpeak)
Apeak = abs (Amax * sum(Win));
GLdB = -20 * log10(Apeak);

PdBov = PdB + GLdB;

return

% ===============================
function [TLim, Fs, Options] = DecodeArgs(varargin)
% Decode [ts, tf], Fs, Options
% These are distinguished by being a 2 element vector, a scalar, and
% a character string (marking the start of the options)

Options = [];
Fs = 1;
TLim = [];

% Find the first argument with a character string
% Store it and the following arguments in Options
Narg = length(varargin);
for (i = 1:Narg)
  if (ischar(varargin{i}))
    Options = varargin(i:end);
    if (mod(length(Options), 2) ~= 0)
      error('PlotSpectrogram: Invalid Options format');
    end
    varargin(i:end) = [];
    break;
  end
end

% Check for a TLim argument
if (length(varargin) > 1 && length(varargin{1}) == 2)
  TLim = varargin{1};
  varargin(1) = [];
end

% Pick off Fs
if (~ isempty(varargin) && length(varargin{1}) == 1)
  Fs = varargin{1};
  varargin(1) = [];
end

if (~ isempty(varargin))
  error('PlotSpectrogram: Too many input arguments');
end

return


function [X,Zf] = moving_average(N,X,Zi,dim)
% Like filter() for the special case of moving-average kernels.
% [X,Zf] = moving_average(N,X,Zi,Dim)
%
% This is an overall very fast implementation whose running time does now grow with N (beyond
% N=100). The algorithm does not run into numerical problems for large data sizes unlike the usual
% cumsum-based implementations.
%
% In:
%   N : filter length in samples
%
%   X : data matrix
%
%   Zi : initial filter conditions (default: [])
%
%   Dim : dimension along which to filter (default: first non-singleton dimension)
%
% Out:
%   X : the filtered data
%
%   Zf : final filter conditions
%
% See also:
%   filter
%
%                           Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
%                           2012-01-10

% Copyright (C) Christian Kothe, SCCN, 2012, christian@sccn.ucsd.edu
%
% This program is free software; you can redistribute it and/or modify it under the terms of the GNU
% General Public License as published by the Free Software Foundation; either version 2 of the
% License, or (at your option) any later version.
%
% This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
% even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
% General Public License for more details.
%
% You should have received a copy of the GNU General Public License along with this program; if not,
% write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
% USA

% determine the dimension along which to filter
if nargin <= 3
    if isscalar(X)
        dim = 1;
    else
        dim = find(size(X)~=1,1);
    end
end

% empty initial state
if nargin <= 2
    Zi = []; end

lenx = size(X,dim);
if lenx == 0
    % empty X
    Zf = Zi;
else
    if N < 100
        % small N: use filter
        [X,Zf] = filter(ones(N,1)/N,1,X,Zi,dim);
    else
        % we try to avoid permuting dimensions below as this would increase the running time by ~3x
        if ndims(X) == 2
            if dim == 1
                % --- process along 1st dimension ---
                if isempty(Zi)
                    % zero initial state
                    Zi = zeros(N,size(X,2));
                elseif size(Zi,1) == N-1
                    % reverse engineer filter's initial state (assuming a moving average)
                    tmp = diff(Zi(end:-1:1,:),1,1);
                    Zi = [tmp(end:-1:1,:); Zi(end,:)]*N;
                    Zi = [-sum(Zi,1); Zi];
                elseif ~isequal(size(Zi),[N,size(X,2)])
                    error('These initial conditions do not have the correct format.');
                end

                % pre-pend initial state & get dimensions
                Y = [Zi; X]; M = size(Y,1);
                % get alternating index vector (for additions & subtractions)
                I = [1:M-N; 1+N:M];
                % get sign vector (also alternating, and includes the scaling)
                S = [-ones(1,M-N); ones(1,M-N)]/N;
                % run moving average
                X = cumsum(bsxfun(@times,Y(I(:),:),S(:)),1);
                % read out result
                X = X(2:2:end,:);

                % construct final state
                if nargout > 1
                    Zf = [-(X(end,:)*N-Y(end-N+1,:)); Y(end-N+2:end,:)]; end
            else
                % --- process along 2nd dimension ---
                if isempty(Zi)
                    % zero initial state
                    Zi = zeros(N,size(X,1));
                elseif size(Zi,1) == N-1
                    % reverse engineer filter's initial state (assuming a moving average)
                    tmp = diff(Zi(end:-1:1,:),1,1);
                    Zi = [tmp(end:-1:1,:); Zi(end,:)]*N;
                    Zi = [-sum(Zi,1); Zi];
                elseif ~isequal(size(Zi),[N,size(X,1)])
                    error('These initial conditions do not have the correct format.');
                end

                % pre-pend initial state & get dimensions
                Y = [Zi' X]; M = size(Y,2);
                % get alternating index vector (for additions & subtractions)
                I = [1:M-N; 1+N:M];
                % get sign vector (also alternating, and includes the scaling)
                S = [-ones(1,M-N); ones(1,M-N)]/N;
                % run moving average
                X = cumsum(bsxfun(@times,Y(:,I(:)),S(:)'),2);
                % read out result
                X = X(:,2:2:end);

                % construct final state
                if nargout > 1
                    Zf = [-(X(:,end)*N-Y(:,end-N+1)) Y(:,end-N+2:end)]'; end
            end
        else
            % --- ND array ---
            [X,nshifts] = shiftdim(X,dim-1);
            shape = size(X); X = reshape(X,size(X,1),[]);

            if isempty(Zi)
                % zero initial state
                Zi = zeros(N,size(X,2));
            elseif size(Zi,1) == N-1
                % reverse engineer filter's initial state (assuming a moving average)
                tmp = diff(Zi(end:-1:1,:),1,1);
                Zi = [tmp(end:-1:1,:); Zi(end,:)]*N;
                Zi = [-sum(Zi,1); Zi];
            elseif ~isequal(size(Zi),[N,size(X,2)])
                error('These initial conditions do not have the correct format.');
            end

            % pre-pend initial state & get dimensions
            Y = [Zi; X]; M = size(Y,1);
            % get alternating index vector (for additions & subtractions)
            I = [1:M-N; 1+N:M];
            % get sign vector (also alternating, and includes the scaling)
            S = [-ones(1,M-N); ones(1,M-N)]/N;
            % run moving average
            X = cumsum(bsxfun(@times,Y(I(:),:),S(:)),1);
            % read out result
            X = X(2:2:end,:);

            % construct final state
            if nargout > 1
                Zf = [-(X(end,:)*N-Y(end-N+1,:)); Y(end-N+2:end,:)]; end

            X = reshape(X,shape);
            X = shiftdim(X,ndims(X)-nshifts);
        end
    end
end


function C = inv(S)
% compliance to stiffness tensor
%
% Input
%  S - @complianceTensor
%
% Output
%  C - @stiffnessTensor
%

C = stiffnessTensor(inv@tensor(S));

end

% this can be done more explicitely by
function test

M = matrix(S,'voigt');

D = M(1,1,:) .* M(2,2,:) .* M(3,3,:) ...
  - M(1,1,:) .* M(2,3,:).^2 ...
  - M(2,2,:) .* M(1,3,:) .* M(1,3,:) ...
  - M(3,3,:) .* M(1,2,:).^2 ...
  + 2 * M(1,2,:) .* M(2,3,:) .* M(1,3,:);

C11 = (M(2,2,:) .* M(3,3,:)-M(2,3,:) .* M(2,3,:))/D;
C12 = (M(1,3,:) .* M(2,3,:)-M(1,2,:) .* M(3,3,:))/D;
C13 = (M(1,2,:) .* M(2,3,:)-M(1,3,:) .* M(2,2,:))/D;
C22 = (M(1,1,:) .* M(3,3,:)-M(1,3,:) .* M(1,3,:))/D;
C23 = (M(1,2,:) .* M(1,3,:)-M(2,3,:) .* M(1,1,:))/D;
C33 = (M(1,1,:) .* M(2,2,:)-M(1,2,:) .* M(1,2,:))/D;

% Enter tensor as 6 by 6 matrix,M line by line.
M = [[  C11   C12   C13    0     0     0];...
    [   C12   C22   C23    0     0     0];...
    [   C13   C23   C33    0     0     0];...
    [   0      0      0   1./M(4,4,:)    0     0];...
    [   0      0      0    0    1./M(5,5,:)    0];...
    [   0      0      0    0     0   1./M(6,6,:)]];
%
C = reshape(stiffnessTensor(M,cs_tensor,'density',rho),size(S));

end


function KLdiv = KL_divergence2(bnetP, bnetQ)
% KL_DIVERGENCE2 computes the Kullback-Leibler divergence between two BNET distributions
% KLdiv = KL_divergence2(bnetP, bnetQ)
%
% Output :
%   div = sum_x  P(x).log(P(x)/Q(x))
%
% Rem :
%   This version is optimized for memory use, but quite slow !!!
%     ==> if you have no memory problem, use kl_divergence instead
%
%   ONLY FOR TABULAR NODES
%   Make sure that you have done the params learning.
%
%   V1.1 : 8 oct 2004 (Ph. Leray - philippe.leray@univ-nantes.fr)

N = size(bnetP.dag,1);
N2 = size(bnetQ.dag,1);
ns= bnetP.node_sizes;
ns2= bnetQ.node_sizes;
if N~=N2, error('size of dags must be the same'), end
if ns~=ns2, error('node sizes of dags must be the same'), end
tiny = exp(-700);
KLdiv=0;

for i=1:prod(ns),
  inst = ind2subv(ns, i); % i'th instantiation
  Px=1; Qx=1;
  for i=1:N,
    ps = parents(bnetP.dag, i);
    e = bnetP.equiv_class(i);
    [tmp Pxi] = prob_node(bnetP.CPD{e}, inst(i), inst(ps)');
    Px=Px*Pxi;
    ps = parents(bnetQ.dag, i);
    e = bnetQ.equiv_class(i);
    [tmp Qxi] = prob_node(bnetQ.CPD{e}, inst(i), inst(ps)');
    Qx=Qx*Qxi;
  end
    Px = Px + (Px==0)*tiny; % replace 0s by tiny
    Qx = Qx + (Qx==0)*tiny; % replace 0s by tiny
  KLdiv = KLdiv + Px*log(Px/Qx);
end