function [mssim, ssim_map] = ssim_index(img1, img2, K, window, L)

%========================================================================
%SSIM Index, Version 1.0
%Copyright(c) 2003 Zhou Wang
%All Rights Reserved.
%
%The author was with Howard Hughes Medical Institute, and Laboratory
%for Computational Vision at Center for Neural Science and Courant
%Institute of Mathematical Sciences, New York University, USA. He is
%currently with Department of Electrical and Computer Engineering,
%University of Waterloo, Canada.
%
%----------------------------------------------------------------------
%Permission to use, copy, or modify this software and its documentation
%for educational and research purposes only and without fee is hereby
%granted, provided that this copyright notice and the original authors'
%names appear on all copies and supporting documentation. This program
%shall not be used, rewritten, or adapted as the basis of a commercial
%software or hardware product without first obtaining permission of the
%authors. The authors make no representations about the suitability of
%this software for any purpose. It is provided "as is" without express
%or implied warranty.
%----------------------------------------------------------------------
%
%This is an implementation of the algorithm for calculating the
%Structural SIMilarity (SSIM) index between two images. Please refer
%to the following paper:
%
%Z. Wang, A. C. Bovik, H. R. Sheikh, and E. P. Simoncelli, "Image
%quality assessment: From error measurement to structural similarity"
%IEEE Transactios on Image Processing, vol. 13, no. 4, Apr. 2004.
%
%Kindly report any suggestions or corrections to zhouwang@ieee.org
%
%----------------------------------------------------------------------
%
%Input : (1) img1: the first image being compared
%        (2) img2: the second image being compared
%        (3) K: constants in the SSIM index formula (see the above
%            reference). defualt value: K = [0.01 0.03]
%        (4) window: local window for statistics (see the above
%            reference). default widnow is Gaussian given by
%            window = fspecial('gaussian', 11, 1.5);
%        (5) L: dynamic range of the images. default: L = 255
%
%Output: (1) mssim: the mean SSIM index value between 2 images.
%            If one of the images being compared is regarded as
%            perfect quality, then mssim can be considered as the
%            quality measure of the other image.
%            If img1 = img2, then mssim = 1.
%        (2) ssim_map: the SSIM index map of the test image. The map
%            has a smaller size than the input images. The actual size:
%            size(img1) - size(window) + 1.
%
%Default Usage:
%   Given 2 test images img1 and img2, whose dynamic range is 0-255
%
%   [mssim ssim_map] = ssim_index(img1, img2);
%
%Advanced Usage:
%   User defined parameters. For example
%
%   K = [0.05 0.05];
%   window = ones(8);
%   L = 100;
%   [mssim ssim_map] = ssim_index(img1, img2, K, window, L);
%
%See the results:
%
%   mssim                        %Gives the mssim value
%   imshow(max(0, ssim_map).^4)  %Shows the SSIM index map
%
%========================================================================

if (nargin < 2 | nargin > 5)
   mssim = -Inf;
   ssim_map = -Inf;
   return;
end

if (size(img1) ~= size(img2))
   mssim = -Inf;
   ssim_map = -Inf;
   return;
end

[M N] = size(img1);

if (nargin == 2)
   if ((M < 11) | (N < 11))
	   mssim = -Inf;
	   ssim_map = -Inf;
      return
   end
   window = fspecial('gaussian', 11, 1.5);	%
   K(1) = 0.01;								      % default settings
   K(2) = 0.03;								      %
   L = 255;                                  %
end

if (nargin == 3)
   if ((M < 11) | (N < 11))
	   mssim = -Inf;
	   ssim_map = -Inf;
      return
   end
   window = fspecial('gaussian', 11, 1.5);
   L = 255;
   if (length(K) == 2)
      if (K(1) < 0 | K(2) < 0)
		   mssim = -Inf;
   		ssim_map = -Inf;
	   	return;
      end
   else
	   mssim = -Inf;
   	ssim_map = -Inf;
	   return;
   end
end

if (nargin == 4)
   [H W] = size(window);
   if ((H*W) < 4 | (H > M) | (W > N))
	   mssim = -Inf;
	   ssim_map = -Inf;
      return
   end
   L = 255;
   if (length(K) == 2)
      if (K(1) < 0 | K(2) < 0)
		   mssim = -Inf;
   		ssim_map = -Inf;
	   	return;
      end
   else
	   mssim = -Inf;
   	ssim_map = -Inf;
	   return;
   end
end

if (nargin == 5)
   [H W] = size(window);
   if ((H*W) < 4 | (H > M) | (W > N))
	   mssim = -Inf;
	   ssim_map = -Inf;
      return
   end
   if (length(K) == 2)
      if (K(1) < 0 | K(2) < 0)
		   mssim = -Inf;
   		ssim_map = -Inf;
	   	return;
      end
   else
	   mssim = -Inf;
   	ssim_map = -Inf;
	   return;
   end
end

C1 = (K(1)*L)^2;
C2 = (K(2)*L)^2;
window = window/sum(sum(window));
img1 = double(img1);
img2 = double(img2);

mu1   = filter2(window, img1, 'valid');
mu2   = filter2(window, img2, 'valid');
mu1_sq = mu1.*mu1;
mu2_sq = mu2.*mu2;
mu1_mu2 = mu1.*mu2;
sigma1_sq = filter2(window, img1.*img1, 'valid') - mu1_sq;
sigma2_sq = filter2(window, img2.*img2, 'valid') - mu2_sq;
sigma12 = filter2(window, img1.*img2, 'valid') - mu1_mu2;

if (C1 > 0 & C2 > 0)
   ssim_map = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))./((mu1_sq + mu2_sq + C1).*(sigma1_sq + sigma2_sq + C2));
else
   numerator1 = 2*mu1_mu2 + C1;
   numerator2 = 2*sigma12 + C2;
	denominator1 = mu1_sq + mu2_sq + C1;
   denominator2 = sigma1_sq + sigma2_sq + C2;
   ssim_map = ones(size(mu1));
   index = (denominator1.*denominator2 > 0);
   ssim_map(index) = (numerator1(index).*numerator2(index))./(denominator1(index).*denominator2(index));
   index = (denominator1 ~= 0) & (denominator2 == 0);
   ssim_map(index) = numerator1(index)./denominator1(index);
end

mssim = mean2(ssim_map);

return


function [bar] = kPa2bar(kPa)
% Convert pressure from kilopascals to bar.  It's interesting how we tend
% to say "bar" as the plural for "bar".  I've heard people say "millibars"
% but I've also heard "millibar" as its plural.  The same does not seem to
% be true for other units of pressure.  We'd say "5 atmospheres".  For some
% reason, I often hear people talk about singular form of animals as their
% plural, too.  Of course there's deer and fish, but I'm not talking about
% those.  I've actually heard people say "Look at those alpaca" or "we saw
% some bear" or "There are lots of squirrel out today".  Strange.
% Chad A. Greene 2012
bar = kPa/100;


function [oz] = yd32oz(yd3)
% Convert volume from cubic yards to US liquid ounces.
% Chad Greene 2012
oz = yd3*25852.675324;


function lambdas = hessianspectrum(problem, x, usepreconstr, storedb, key)
% Returns the eigenvalues of the (preconditioned) Hessian at x.
%
% function lambdas = hessianspectrum(problem, x)
% function lambdas = hessianspectrum(problem, x, useprecon)
% function lambdas = hessianspectrum(problem, x, useprecon, storedb)
% function lambdas = hessianspectrum(problem, x, useprecon, storedb, key)
%
% If useprecon is not set, or if it is set to 'noprecon' (default), this
% computes and returns the eigenvalues of the Hessian operator (which needs
% to be symmetric but not necessarily definite) on the tangent space at x.
% There are problem.M.dim() eigenvalues. Matlab's eigs is used internally.
%
% If useprecon is set to 'precon', the eigenvalues of the composition of
% the Hessian with the preconditioner at x are computed: Precon o Hessian.
% The preconditioner must have been defined in the problem structure and
% has to be symmetric, positive definite. It is supposed to approximate the
% inverse of the (Riemannian) Hessian. Ideally, the preconditioned Hessian
% is better conditioned (smaller ratio of largest to smallest eigenvalue in
% magnitude) than the non-preconditioned spectrum. The present tool can
% help assess that.
%
% The typical ways to define a preconditioner are via problem.precon or
% problem.sqrtprecon (see comment below). These should be function handles
% with the same input/output system as problem.hess for the Hessian.
%
% If the Hessian is not available from the problem structure, an
% approximate Hessian will be used. There are no guarantees of
% interpretability, but this may nevertheless be useful at times.
%
% Even though the Hessian and the preconditioner are both symmetric, their
% composition is not symmetric. This can slow down the call to 'eigs'
% substantially. If possible, you may specify the square root of the
% preconditioner in the problem structure, as sqrtprecon. This operator on
% the tangent space at x must also be symmetric, positive definite, and
% such that SqrtPrecon o SqrtPrecon = Precon. Then, the spectrum of the
% symmetric operator SqrtPrecon o Hessian o SqrtPrecon is computed: it is
% the same as the spectrum of Precon o Hessian, but is usually faster to
% compute. If both Precon and SqrtPrecon are provided, only SqrtPrecon will
% be used.
%
% The input and the output of the Hessian and of the preconditioner are
% projected on the tangent space to avoid undesired contributions of the
% ambient space.
%
% storedb is a StoreDB object, key is the StoreDB key to point x.
%
% Requires the manifold description in problem.M to have these functions:
%
%   u_vec = vec(x, u_mat) :
%       Returns a column vector representation of the normal (usually
%       matrix) representation of the tangent vector u_mat. vec must be an
%       isometry between the tangent space (with its Riemannian metric) and
%       a subspace of R^n where n = length(u_vec), with the 2-norm on R^n.
%       In other words: it is an orthogonal projector.
%
%   u_mat = mat(x, u_vec) :
%       The inverse of vec (its adjoint).
%
%   u_mat_clean = tangent(x, u_mat) :
%       Subtracts from the tangent vector u_mat any component that would
%       make it "not really tangent", by projection.
%
%   answer = vecmatareisometries() :
%       Returns true if the linear maps encoded by vec and mat are
%       isometries, false otherwise. It is better if the answer is yes.
%
% See also: hessianextreme canGetPrecon canGetSqrtPrecon

% This file is part of Manopt: www.manopt.org.
% Original author: Nicolas Boumal, July 3, 2013.
% Contributors:
% Change log:
%
%   Dec. 18, 2014 (NB):
%       The lambdas are now sorted when they are returned.
%
%   April 3, 2015 (NB):
%       Works with the new StoreDB class system.
%       Does no longer accept sqrtprecon as an input: the square root of
%       the preconditioner may now be specified directly in the problem
%       structure, following the same syntax as the preconditioner precon.
%
%   April 4, 2015 (NB):
%       By default, the spectrum is computed without the preconditioner's
%       effect, even if it is available. A new input option allows to
%       switch this behavior without the need to change the problem
%       structure.

    % Allow omission of the key, and even of storedb.
    if ~exist('key', 'var')
        if ~exist('storedb', 'var')
            storedb = StoreDB();
        end
        key = storedb.getNewKey();
    end

    % Manage the option to use or not use a preconditioner.
    % The input is a string. It is here transformed into a Boolean.
    if ~exist('usepreconstr', 'var') || isempty(usepreconstr)
        usepreconstr = 'noprecon';
    end
    switch lower(usepreconstr)
        case 'noprecon'
            useprecon = false;
        case 'precon'
            useprecon = true;
        otherwise
            % A bit of legacy code heads up.
            if isa(usepreconstr, 'function_handle')
                warning('manopt:hessianspectrum:oldsyntax', ...
                        ['This function no longer expects sqrtprecon ' ...
                         'as input. Place it in the problem structure.']);
            end
            error('Input useprecon must be either ''precon'' or ''noprecon''.');
    end

    % No warning if an approximate Hessian is available, as then the user
    % is presumably aware of what they are doing.
    if ~canGetHessian(problem) && ~canGetApproxHessian(problem)
        warning('manopt:hessianspectrum:nohessian', ...
                ['The Hessian appears to be unavailable.\n' ...
                 'Will try to use an approximate Hessian instead.\n'...
                 'Since this approximation may not be linear or '...
                 'symmetric,\nthe computation might fail and the '...
                 'results (if any)\nmight make no sense.']);
    end

    vec = @(u_mat) problem.M.vec(x, u_mat);
    mat = @(u_vec) problem.M.mat(x, u_vec);
    tgt = @(u_mat) problem.M.tangent(x, u_mat);

    % n: size of a vectorized tangent vector
    % dim: dimension of the tangent space
    % necessarily, n >= dim.
    % The vectorized operators we build below will have at least n - dim
    % zero eigenvalues.
    n = length(vec(problem.M.zerovec(x)));
    dim = problem.M.dim();

    % It is usually a good idea to force a gradient computation to make
    % sure precomputable things are precomputed.
    if canGetGradient(problem)
        [unused1, unused2] = getCostGrad(problem, x, storedb, key); %#ok
    end

    hess = @(u_mat) tgt(getHessian(problem, x, tgt(u_mat), storedb, key));
    hess_vec = @(u_vec) vec(hess(mat(u_vec)));

    % Regardless of preconditioning, we can only have a symmetric
    % eigenvalue problem if the vec/mat pair of the manifold is an
    % isometry:
    vec_mat_are_isometries = problem.M.vecmatareisometries();

    if ~useprecon

        % No preconditioner to use: simply use the Hessian as is.

        eigs_opts.issym = vec_mat_are_isometries;
        eigs_opts.isreal = true;
        lambdas = eigs(hess_vec, n, dim, 'LM', eigs_opts);

    elseif canGetSqrtPrecon(problem)

        % There is a preconditioner, and we have its square root: deal with
        % the symmetric composition SqrtPrecon o Hessian o SqrtPrecon.

        sqrtprec = @(u_mat) tgt(getSqrtPrecon(problem, x, tgt(u_mat), storedb, key));
        sqrtprec_vec = @(u_vec) vec(sqrtprec(mat(u_vec)));

        eigs_opts.issym = vec_mat_are_isometries;
        eigs_opts.isreal = true;
        lambdas = eigs(@(u_vec) ...
                      sqrtprec_vec(hess_vec(sqrtprec_vec(u_vec))), ...
                      n, dim, 'LM', eigs_opts);

    elseif canGetPrecon(problem)

        % There is a preconditioner, but we don't have its square root:
        % deal with the non-symmetric composition Precon o Hessian.

        prec = @(u_mat) tgt(getPrecon(problem, x, tgt(u_mat), storedb, key));
        prec_vec = @(u_vec) vec(prec(mat(u_vec)));
        % prec_inv_vec = @(u_vec) pcg(prec_vec, u_vec);

        eigs_opts.issym = false;
        eigs_opts.isreal = true;
        lambdas = eigs(@(u_vec) prec_vec(hess_vec(u_vec)), ...
                       n, dim, 'LM', eigs_opts);

    else

        error('No preconditioner is available in the problem structure.');

    end

    lambdas = sort(lambdas);

end


function Power = actuatorPower(States, Actuators, Phase)
% function Power = actuatorPower(States, Actuators, Phase)
%
% Computer Generated File -- DO NOT EDIT
%
% This function was created by the function Write_Power()
% 13-Dec-2013 11:41:00
%
%  ARGUMENTS:
%   States = [Nstate x Ntime] matrix of states
%   Actuator = [Nactuator x Ntime] matrix of actuator values
%   Phase = {'D','F','S1',S2'} or omit.
%       Since the ankle torques are acting against the ground, they should
%       be set to zero when a given leg is in flight. If Phase is included,
%       then this function will force these inputs to be zero. If it is
%       ommitted, then it is assumed that the user has done that error
%       checking.
%
%  RETURNS:
%   Power = a struct with fields for each actuator:
%       leg one (linear force actuator)
%       leg two (linear force actuator)
%       ankle one (torque actuator)
%       ankle two (torque actuator)
%       hip (torque from leg one on leg two
%
%
% Matthew Kelly
% Cornell University
%

%
% See also DERIVE_EOM
x0 = States(:,1); % (m) Hip horizontal position
y0 = States(:,2); % (m) Hip vertical position
x1 = States(:,3); % (m) Foot One horizontal position
y1 = States(:,4); % (m) Foot One vertical position
x2 = States(:,5); % (m) Foot Two horizontal position
y2 = States(:,6); % (m) Foot Two vertical position
dx0 = States(:,7); % (m/s) Hip horizontal velocity
dy0 = States(:,8); % (m/s) Hip vertical velocity
dx1 = States(:,9); % (m/s) Foot One horizontal velocity
dy1 = States(:,10); % (m/s) Foot One vertical velocity
dx2 = States(:,11); % (m/s) Foot Two horizontal velocity
dy2 = States(:,12); % (m/s) Foot Two vertical velocity

if nargin==2
    Phase = '';
end

F1 = Actuators(:,1); % (N) Compresive axial force in Leg One
F2 = Actuators(:,2); % (N) Compresive axial force in Leg Two
if strcmp(Phase,'F') || strcmp(Phase, 'S2')
    T1 = zeros(size(Actuators,1),1); %Foot One not in contact with ground!
else
    T1 = Actuators(:,3); % (Nm) External torque applied to Leg One
end
if strcmp(Phase,'F') || strcmp(Phase, 'S1')
    T2 = zeros(size(Actuators,1),1); %Foot Two not in contact with ground!
else
    T2 = Actuators(:,4); % (Nm) External torque applied to Leg Two
end
Thip = Actuators(:,5); % (Nm) Torque acting on Leg Two from Leg One

% Intermediate Calculations
L1 = ((x0 - x1).^2 + (y0 - y1).^2).^(1./2);
L2 = ((x0 - x2).^2 + (y0 - y2).^2).^(1./2);
dL1 = ((dx0 - dx1).*(x0 - x1) + (dy0 - dy1).*(y0 - y1))./L1;
dL2 = ((dx0 - dx2).*(x0 - x2) + (dy0 - dy2).*(y0 - y2))./L2;
% th1 = atan2(x1 - x0, y1 - y0);
% th2 = atan2(x2 - x0, y2 - y0);
dth1 = -((dy0 - dy1).*(x0 - x1) - (dx0 - dx1).*(y0 - y1))./L1.^2;
dth2 = -((dy0 - dy2).*(x0 - x2) - (dx0 - dx2).*(y0 - y2))./L2.^2;

Power.legOne = F1.*dL1;
Power.legTwo = F2.*dL2;
Power.ankleOne = T1.*dth1;
Power.ankleTwo = T2.*dth2;
Power.hip = -Thip.*(dth1 - dth2);

end


function calc_bootfitrms_a2(a,time,timef,bootloops,maepi)
    % function calc_bootfitrms_a2(a,time,timef,bootloops,maepi);
    % --------------------------------------------------
    % Plots Ncum observed vs. Ncum modeled for specified time windows
    %
    % Input variables:
    % a         : earthquake catalog
    % time      : learning period fo fit Omori parameters
    % timef     : forecast period
    % bootloops : Number of bootstraps
    % maepi     : mainshock

    % S.Neukomm/ S.Wiemer / J.Woessner
    % last update: 04.08.03

report_this_filefun(mfilename('fullpath'));
    % Surpress warnings from fmincon
    warning off;

    %[m_main, main] = max(a.Magnitude);
    date_matlab = datenum(a.Date.Year,a.Date.Month,a.Date.Day,a.Date.Hour,a.Date.Minute,zeros(size(a,1),1));
    date_main = datenum(floor(maepi(3)),maepi(4),maepi(5),maepi(8),maepi(9),0);
    time_aftershock = date_matlab-date_main;

    % Select biggest aftershock earliest in time, but more than 1 day after mainshock
    fDay = 1;
    ft_c=fDay/365; % Time not considered to find biggest aftershock
    vSel = (a.Date > maepi(:,3)+ft_c & a.Date<= maepi(:,3)+time/365);
    mCat = a.subset(vSel);
    vSel = mCat(:,6) == max(mCat(:,6));
    vBigAf = mCat(vSel,:)
    if length(mCat(:,1)) > 1
        vSel = vBigAf(:,3) == min(vBigAf(:,3));
        vBigAf = vBigAf(vSel,:);
    end

    date_biga = datenum(floor(vBigAf(3)),vBigAf(4),vBigAf(5),vBigAf(8),vBigAf(9),0);
    fT1 = date_biga - date_main; % Time of big aftershock

    % Aftershock times
    l = time_aftershock(:) > 0;
    tas = time_aftershock(l);
    eqcatalogue = a.subset(l);

    l = tas <= time;
    time_as=tas(l);

    % Calculate uncertainty and mean values of p,c,and k
    %[pval, pstd, cval, cstd, kval, kstd, loopout] = brutebootloglike_a2(tas(l),bootloops,fT1)
    % Calculate p,c,k for dataset
    nMod = 1;
    %nMod = 2
    if nMod == 1

        [pval1, pval2, cval1, cval2, kval1, kval2] = bruteforcerms_a2(time_as,fT1,nMod)

        pval1 = round(100*pval1)/100;
        cval1 = round(100*cval1)/100;
        kval1 = round(10*kval1)/10;
        kval2 = round(10*kval2)/10;

    else
        [pval1, pval2, cval1, cval2, kval1, kval2] = bruteforcerms_a2(time_as,fT1,nMod)
        pval1 = round(100*pval1)/100;
        pval2 = round(100*pval2)/100;
        cval1 = round(100*cval1)/100;
        cval2 = round(100*cval2)/100;
        kval1 = round(10*kval1)/10;
        kval2 = round(10*kval2)/10;
    end

    if (isnan(pval1) == 0 & isnan(pval2) == 0)

        figure_w_normalized_uicontrolunits('Numbertitle','off','Name','Forecast aftershock occurence')
        %     pval1 = 0.9;
        %     pval2 = 0.9;
        %     cval1 = 0.2;
        %     cval2 = 0.2;
        %     kval1 = 1000;
        %     kval2 = 2000;
        %loopout = [loopout , loopout(:,1)*0];

        %     % Times up to the forecast time
        %     lf = tas <= time+timef ;
        %     time_asf= [tas(lf) ];
        %     time_asf=sort(time_asf);
        %

        %
        %     % Compute the confidence limits
        %     for j = 1:length(loopout(:,1));
        %
        %         cumnr = (1:length(time_asf))'; cumnr_model = [];
        %         pvalb = loopout(j,1);
        %         cvalb = loopout(j,2);
        %         kvalb = loopout(j,3);
        %         for i=1:length(time_asf)
        %             if pval ~= 1
        %                 cm = kvalb/(pvalb-1)*(cvalb^(1-pvalb)-(time_asf(i)+cvalb)^(1-pvalb));
        %             else
        %                 cm = kvalb*log(time_asf(i)/cvalb+1);
        %             end
        %             cumnr_model = [cumnr_model; cm];
        %         end
        %         plot(time_asf,cumnr_model,'color',[0.8 0.8 0.8]);
        %         loopout(j,4) = max(cumnr_model);
        %         hold on
        %         %drawnow
        %     end
        %     % 2nd moment of bootstrap number of forecasted number of events
        %     fStdBst = calc_StdDev(loopout(:,4));
        %
        %     % now plot the forecast ...
        %     cumnrf = (1:length(time_asf))'; cumnr_modelf = [];
        %     for i=1:length(time_asf)
        %         if pval ~= 1
        %             cm = kval/(pval-1)*(cval^(1-pval)-(time_asf(i)+cval)^(1-pval));
        %         else
        %             cm = kval*log(time_asf(i)/cval+1);
        %         end
        %         cumnr_modelf = [cumnr_modelf; cm];
        %     end
        %     pf1 =  plot(time_asf,cumnr_modelf,'g-.','Linewidth',2);
        %     hold on
        %     pf2 =  plot(time_asf,cumnrf, 'b-','Linewidth',2);
        %
        % plot the best fit
        % Cumulative number of pbserved events
        cumnr = (1:length(tas))';
        cumnr_model = [];

        for i=1:length(tas)
            if tas(i) <= fT1
                if pval1 ~= 1
                    cm = kval1/(pval1-1)*(cval1^(1-pval1)-(tas(i)+cval1)^(1-pval1));
                else
                    cm = kval1*log(tas(i)/cval1+1);
                end
                cumnr_model = [cumnr_model; cm];
            else
                if (pval1 ~= 1 & pval2 ~= 1)
                    cm = kval1/(pval1-1)*(cval1^(1-pval1)-(tas(i)+cval1)^(1-pval1))+ kval2/(pval2-1)*(cval2^(1-pval2)-(tas(i)-fT1+cval2)^(1-pval2));
                else
                    cm = kval1*log(tas(i)/cval1+1) + kval2*log((tas(i)-fT1)/cval2+1);
                end
                cumnr_model = [cumnr_model; cm];
            end
        end
        tas=sort(tas);
        cumnr_model=sort(cumnr_model);
        p1 = plot(tas,cumnr_model,'r','Linewidth',2);
        hold on

        p2 = plot(tas,cumnr,'b','Linewidth',2,'Linestyle','--');
        %     string=['p = ' num2str(pval) '+-' num2str(pstd) '; c = ' num2str(cval) '+-' num2str(cstd) '; k = ' num2str(kval) '+-' num2str(kstd)];
        %     title(string)
        %
        %     % Plot observed events in forecast period from endpoint of modeled events in learning period
        %     vSel = time_asf >= max(time_as);
        %     vCumnr_forecast = cumnrf(vSel,:);
        %     vTime_forecast = time_asf(vSel,:);
        %     % Difference of modelled and observed number of events at time_as
        %     fDiff_timeas = cumnr_modelf(length(time_as))-cumnrf(length(time_as));
        %     vCumnr_forecast = vCumnr_forecast+fDiff_timeas;
        %     pf3 = plot(vTime_forecast, vCumnr_forecast,'m-.','Linewidth',2);
        %
        %
        % %     [Y, in] = sort(loopout(:,4));
        % %     %n5Conf = round(0.05*length(loopout))
        % %     %Y = find(in == n5Conf);
        % %     %Y = find(in == 5);
        % %     loops = loopout(in,:);
        % %     n5Conf = floor(min((find(round(loops(:,4))==round(prctile(loops(:,4),5))))));
        % %     %n5Conf = round(0.05*length(loops));
        % %     pvalb = loops(n5Conf,1);
        % %     cvalb = loops(n5Conf,2);
        % %     kvalb = loops(n5Conf,3);
        % %
        % %     cumnr = (1:length(time_as))'; cumnr_model = [];
        % %     for i=1:length(time_as)
        % %         if pval ~= 1
        % %             cm = kvalb/(pvalb-1)*(cvalb^(1-pvalb)-(time_as(i)+cvalb)^(1-pvalb));
        % %         else
        % %             cm = kvalb*log(time_as(i)/cvalb+1);
        % %         end
        % %         cumnr_model = [cumnr_model; cm];
        % %     end
        % %
        % %     %plot(time_as,cumnr_model,'k--','Linewidth',1);
        % %
        % %     %n95Conf = round(0.95*length(loopout));
        % %     %Y = find(in == n95Conf);
        % %     %loops = loopout(in,:);
        % %     n95Conf = ceil(max(find(round(loops(:,4))==round(prctile(loops(:,4),95)))));
        % %     %n95Conf = round(0.95*length(loops));
        % %     pvalb = loops(n95Conf,1);
        % %     cvalb = loops(n95Conf,2);
        % %     kvalb = loops(n95Conf,3);
        % %     cumnr = (1:length(time_as))'; cumnr_model = [];
        % %     for i=1:length(time_as)
        % %         if pval ~= 1
        % %             cm = kvalb/(pvalb-1)*(cvalb^(1-pvalb)-(time_as(i)+cvalb)^(1-pvalb));
        % %         else
        % %             cm = kvalb*log(time_as(i)/cvalb+1);
        % %         end
        % %         cumnr_model = [cumnr_model; cm];
        % %     end
        % %     %pc = plot(time_as,cumnr_model,'k--','Linewidth',1);
        %
        %     xlabel('Time [days]')
        %     ylabel('Cumulative number of aftershocks')
        %     xlim([0 max(time_asf)]);
        %
        %
        %     % calculate uncertainty sigma in forecasted number of aftershocks by
        %     % error propagation law
        %     time1 = time+timef;
        %     if pval == 1
        %         pv = 1-10^(-6);
        %     else
        %         pv = pval;
        %     end
        %     mpm1 = 1-pv;
        %     t1c = time1+cval;
        %     t0c = time+cval;
        %     sigma = (((-t1c^mpm1+t0c^mpm1)/(pv-1)*kstd)^2+...
        %         (kval/(pv-1)*(-t1c^mpm1*mpm1/t1c+t0c^mpm1*mpm1/t0c)*cstd)^2+...
        %         (kval/(pv-1)*(t1c^mpm1*log(t1c)+t1c^mpm1/(pv-1)-t0c^mpm1*log(t0c)-t0c^mpm1/(pv-1))*pstd)^2)^0.5;
        %     % Plot standard deviation error propagation law
        %     ps1=errorbar(max(time_asf),max(cumnr_modelf),sigma,sigma);
        %     set(ps1,'Linewidth',4,'Color',[0 1 0])
        %     % Plot standard deviation from bootstrap
        %     ps2=errorbar(max(time_asf),max(cumnr_modelf),fStdBst,fStdBst);
        %     set(ps2,'Linewidth',2,'Color',[1 0 0])
        %
        %     legend([p2 p1 pf1 pf3 min(ps1) min(ps2)],'data','model','forecast','observed','\sigma (Epl)','\sigma (Bst)',0)
        %
        %     % Title
        %     % Rate change from error propagation law
        %     % Find amount of events in forecast period for modeled data
        %     nummod = max(cumnr_modelf)-cumnr_modelf(length(time_as));
        %     % Find amount of  events in forecast period for observed data
        %     l = time_asf <=time+timef & time_asf > time;
        %     numreal = sum(l); % observed number of aftershocks
        %     fRc_Flaw = (numreal-nummod)/sigma;
        %     fRc_Bst = (numreal-nummod)/fStdBst;
        %     string=['p = ' num2str(pval) '+-' num2str(pstd) '; c = ' num2str(cval) '+-' num2str(cstd) '; k = ' num2str(kval) '+-' num2str(kstd)];
        %     title(string)
        %     string=['\sigma(Epl) = ' num2str(sigma) '; \sigma(Bst) = ' num2str(fStdBst)];
        %     text(0.25,100,string)
        %     % Set line for learning period
        %     yy = get(gca,'ylim');
        %     plot([max(time_as) max(time_as)],[0 yy(2)],'k-.')
    else
        disp('no result')
    end


function [y] = spm_gx_hdm_sck(x,u,P,M)
% Simulated BOLD response to input.
% FORMAT [y] = spm_gx_hdm_sck(x,u,P,M)
% y    - BOLD response (%)
% x    - state vector     (see spm_fx_fmri)
% P    - Parameter vector (see spm_fx_fmri)
%__________________________________________________________________________
%
% This function implements the BOLD signal model described in:
%
% Stephan KE, Weiskopf N, Drysdale PM, Robinson PA, Friston KJ (2007)
% Comparing hemodynamic models with DCM. NeuroImage 38: 387-401.
%__________________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

% Karl Friston & Klaas Enno Stephan
% $Id: spm_gx_hdm_sck.m 6263 2014-11-17 13:48:36Z karl $

% biophysical constants for 1.5 T:
%==========================================================================

% echo time (seconds)
%--------------------------------------------------------------------------
try
    TE = M(1).TE;
catch
    TE = 0.04;
end

% resting venous volume
%--------------------------------------------------------------------------
V0    = 100*0.08;

% slope r0 of intravascular relaxation rate R_iv as a function of oxygen
% saturation Y:  R_iv = r0*[(1-Y)-(1-Y0)]
%--------------------------------------------------------------------------
r0    = 25;

% frequency offset at the outer surface of magnetized vessels
%--------------------------------------------------------------------------
nu0   = 40.3;

% region-specific resting oxygen extraction fractions
%--------------------------------------------------------------------------
E0    = P(5,:);

% region-specific ratios of intra- to extravascular components of
% the gradient echo signal (prior mean = 1, log-normally distributed
% scaling factor)
%--------------------------------------------------------------------------
epsi  = exp(P(6,:));

% coefficients in BOLD signal model
%--------------------------------------------------------------------------
k1    = 4.3.*nu0.*E0.*TE;
k2    = epsi.*r0.*E0.*TE;
k3    = 1 - epsi;

% exponentiation of hemodynamic state variables
%--------------------------------------------------------------------------
x     = exp(x);

% BOLD signal
%--------------------------------------------------------------------------
v     = x(3,:);
q     = x(4,:);
y     = V0.*(k1.*(1 - q) + k2.*(1 - (q./v)) + k3.*(1 - v));


function [imgOut, L_wa] = YPWardGlobalTMO(img, Ld_max, maxLayers)
%
%       [imgOut, L_wa] = YPWardGlobalTMO(img, Ld_max, maxLayers)
%
%
%       Input:
%           -img: input HDR image
%           -Ld_max: maximum monitor LDR luminance in cd/m^2
%           -L_wa: world adpatation luminance in cd/m^2
%
%       Output
%           -imgOut: tone mapped image
%           -L_wa: luminance adaptation
%
%     Copyright (C) 2020 Francesco Banterle
%
%     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 3 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, see <http://www.gnu.org/licenses/>.
%
%     This is the TumblinTMO with Yee and Pattanaik Luminance Adaptation;
%     see the file tmo/util/YeePattanaikLuminanceAdaptation.m
%

if(~exist('Ld_max', 'var'))
    Ld_max = 100;
end

if(~exist('maxLayers', 'var'))
    maxLayers = 32;
end

if(Ld_max <= 0.0)
    Ld_max = 100;
end

L_wa = YeePattanaikLuminanceAdaptation(img, maxLayers);

imgOut = WardGlobalTMO(img, Ld_max, L_wa);

end


% WAVELET_LAYER_2D Compute the wavelet transform of a scattering layer
%
% Usage
%    [U_phi, U_psi] = WAVELET_LAYER_2D(U, filters, options)
%
% Input
%    U (struct): input scattering layer
%    filters (struct): filter bank
%    options (struct): same as wavelet_2d
%
% Output
%    A (struct): Averaged wavelet coefficients
%    V (struct): Wavelet coefficients of the next layer
%
% Description
%    This function has a pivotal role between WAVELET_2D (which computes a
%    single wavelet transform), and WAVELET_FACTORY_2D (which creates the
%    whole cascade). Given inputs modulus wavelet coefficients
%    corresponding to a layer, WAVELET_LAYER_2D computes the wavelet
%    transform coefficients of the next layer using WAVELET_2D.
%
% See also
%   WAVELET_2D, WAVELET_FACTORY_2D, WAVELET_LAYER_1D

function [U_phi, U_psi] = wavelet_layer_2d(U, filters, options)

    calculate_psi = (nargout>=2); % do not compute any convolution
    % with psi if the user does get U_psi

    if ~isfield(U.meta,'theta')
        U.meta.theta = zeros(0,size(U.meta.j,2));
    end

    if ~isfield(U.meta, 'resolution'),
        U.meta.resolution = 0;
    end

    p2 = 1;
    for p = 1:numel(U.signal)
        x = U.signal{p};
        if (numel(U.meta.j)>0)
            j = U.meta.j(end,p);
        else
            j = -1E20;
        end

        % compute mask for progressive paths
        options.psi_mask = calculate_psi & ...
            (filters.psi.meta.j >= j + filters.meta.Q);

        % set resolution of signal
        options.x_resolution = U.meta.resolution(p);

        % compute wavelet transform
        [x_phi, x_psi, meta_phi, meta_psi] = wavelet_2d(x, filters, options);

        % copy signal and meta for phi
        U_phi.signal{p} = x_phi;
        U_phi.meta.j(:,p) = [U.meta.j(:,p); filters.phi.meta.J];
        U_phi.meta.theta(:,p) = U.meta.theta(:,p);
        U_phi.meta.resolution(1,p) = meta_phi.resolution;

        % copy signal and meta for psi
        for p_psi = find(options.psi_mask)
            U_psi.signal{p2} = x_psi{p_psi};
            U_psi.meta.j(:,p2) = [U.meta.j(:,p);...
                filters.psi.meta.j(p_psi)];
            U_psi.meta.theta(:,p2) = [U.meta.theta(:,p);...
                filters.psi.meta.theta(p_psi)];
            U_psi.meta.resolution(1,p2) = meta_psi.resolution(p_psi);
            p2 = p2 +1;
        end

    end

end


function out=convert(in,varargin)
%OPTS Set gradient limits of the MR system.
%   out=convert(in,from,to) Convert the numerical data in, given in
%   specificed units 'from' to units specified in 'to'.
%
%   Valid unit strings are:
%    'Hz/m' 'mT/m' 'rad/ms/mm' 'Hz/m/s' 'mT/m/ms' 'T/m/s' 'rad/ms/mm/ms'

persistent parser
validGradUnits={'Hz/m','mT/m','rad/ms/mm'};
validSlewUnits={'Hz/m/s','mT/m/ms','T/m/s','rad/ms/mm/ms'};
validUnits=cat(2,validGradUnits,validSlewUnits);
if isempty(parser)
    parser = inputParser;
    parser.FunctionName = 'convert';
    parser.addRequired('in',@isnumeric);
    parser.addRequired('fromUnit',...
        @(x) any(validatestring(x,validUnits)));
    parser.addOptional('toUnit',[],...
        @(x) any(validatestring(x,validUnits)));
    parser.addParamValue('gamma',42.576e6,@isnumeric); % Hz/T
end
parse(parser,in,varargin{:});
opt = parser.Results;

% Set default output unit if not given
if isempty(opt.toUnit)
    if ismember(opt.fromUnit,validGradUnits)
        opt.toUnit = validGradUnits{1};
    elseif ismember(opt.fromUnit,validSlewUnits)
        opt.toUnit = validSlewUnits{1};
    end
end

% Convert to standard units
switch opt.fromUnit
    % Grad units
    case 'Hz/m'
        standard = in;
    case 'mT/m'
        standard = in*1e-3*opt.gamma;
    case 'rad/ms/mm'
        standard = in*1e6/(2*pi);
    % Slew units
    case 'Hz/m/s'
        standard = in;
    case {'mT/m/ms','T/m/s'}
        standard = in*opt.gamma;
    case 'rad/ms/mm/ms'
        standard = in*1e9/(2*pi);
end

% Convert from standard units
switch opt.toUnit
    % Grad units
    case 'Hz/m'
        out = standard;
    case 'mT/m'
        out = 1e3*standard/opt.gamma;
    case 'rad/ms/mm'
        out = standard*2*pi*1e-6;
    % Slew units
    case 'Hz/m/s'
        out = standard;
    case {'mT/m/ms','T/m/s'}
        out = standard/opt.gamma;
    case 'rad/ms/mm/ms'
        out = standard*2*pi*1e-9;
end


function [irf_estimates,D,gamma,D_estimates,gamma_estimates,strshocks_estimates,favar]...
    =olsirft5(betahat,IRFperiods,Y,X,n,m,p,k,endo,pref,IRFband,names,enddate,startdate,T,data_endo,data_exo,const,strctident,IRFt,IRF,favar)

% computes IRF values (point estimates and confidence bands) for the OLS VAR model
% inputs:  - vector 'betahat': OLS VAR coefficients in vectorised form (defined in 1.1.15)
%          - matrix 'sigmahat': OLS VAR variance-covariance matrix of residuals (defined in 1.1.10)
%          - integer 'IRFperiods': number of periods for IRFs
%          - integer 'IRFt': determines which type of structural decomposition to apply (none, Choleski, triangular factorization)
%          - matrix 'X': matrix of regressors for the VAR model (defined in 1.1.8)
%          - integer 'n': number of endogenous variables in the BVAR model (defined p 7 of technical guide)
%          - integer 'm': number of exogenous variables in the BVAR model (defined p 7 of technical guide)
%          - integer 'p': number of lags included in the model (defined p 7 of technical guide)
%          - integer 'k': number of coefficients to estimate for each equation in the BVAR model (defined p 7 of technical guide)
%          - integer 'q': total number of coefficients to estimate for the BVAR model (defined p 7 of technical guide)
%          - scalar 'IRFband': confidence level for IRFs
% outputs: - cell 'irf_estimates': lower bound, point estimates, and upper bound for the IRFs
%          - matrix 'D': structural matrix for the OLS model (defined in 2.3.3)
%          - matrix 'gamma': structural disturbance variance-covariance matrix (defined p 48 of technical guide)
%          - vector 'D_estimates': point estimate (median) of the structural matrix D, in vectorised form
%          - vector 'gamma_estimates': point estimate (median) of the structural disturbance variance-covariance matrix gamma, in vectorised form

It=1001;
Bu=0;
Acc=It-Bu;

%% IV routine
[~,~,~,~,~,irf_storage,storage2]=...
    bear.irfIVols(betahat,n,m,p,k,T,names,startdate,enddate,X,Y,endo,data_endo,data_exo,const,pref,strctident,IRFt,IRFperiods,It,Bu);

%% reorganise storage
% loop over iterations
for ii=1:Acc
    % loop over IRF periods
    for jj=1:IRFperiods
        % loop over variables
        for kk=1:n
            % loop over shocks
            for ll=1:n
                irf_record{kk,ll}(ii,jj)=irf_storage{ii,1}(kk,ll,jj);
            end
        end
    end
    D_record(:,ii)=storage2{ii,1}(:);
end

for ii=1:n^2
    D_estimates(ii,1)=quantile(D_record(ii,:),0.5);
end
D=reshape(D_estimates,n,n);
D_estimates=D(:);
gamma=eye(n); %equivalent to the gamma_record output of irfres
gamma_estimates=gamma(:);

% create then the cell storing the point estimates and confidence bands
irf_estimates=cell(n,n);

if IRF==1 | favar.IRFplot==1
    % rearrange
    for ii=1:n
        % loop over variables
        for jj=1:n
            % loop over IRF periods
            for kk=1:IRFperiods
                % median
                irf_estimates{jj,ii}(2,kk)=quantile(irf_record{jj,ii}(:,kk),0.5);
                % lower bound
                irf_estimates{jj,ii}(1,kk)=quantile(irf_record{jj,ii}(:,kk),(1-IRFband)/2);
                % upper bound
                irf_estimates{jj,ii}(3,kk)=quantile(irf_record{jj,ii}(:,kk),IRFband+(1-IRFband)/2);
            end
        end
    end
end

% check if the variables have been transformed
if favar.FAVAR==1
    if favar.transformation==1 || favar.plot_transform==1
        % re-transform irf_estimates
        favar.IRF.irf_estimates_nottransformed=irf_estimates; % before, save untransformed IRFs
        % re-transform
        [irf_estimates]=bear.favar_retransX_irf_estimates(irf_estimates,favar.transformationindex_endo,favar.levels);
    end
end

% finally, estimate structural shocks
% Compute first the model residuals
Bhat=reshape(betahat,k,n);
EPS=Y-X*Bhat;
% Then use (XXX) to recover the structural shocks
ETA=D\EPS';
% output
strshocks_estimates=ETA;


function [neg, zer, nd, pos] = convertGene2PathwayInteractions(E, epSys, uSys)
% This function was written to find out the type of epistatic interactions
% that either exist between two different or within same subsystem
%
% USAGE:
%
%     [neg,zer,nd,pos] = convertGene2PathwayInteractions(E,epSys,uSys)
%
% INPUT:
%    E:     A square epistatic interaction matrix (or genes or reactions)
%    epSys: Subsystems belonging to the gene at that index (a second
%           order cell array) Each cell array may contain one or more
%           subsystems.
%           e.g. for first gene or reaction in E:
%           epCmpt{1,1} = {'Glycolysis';'TCA cycle'}
%    uSys:  Unique Subsystems in the epCmpt (use this arguments if
%           interested in interactions between selected subsystems in the epSys)
%
% OUTPUTS:
%    neg:            matrix of number of aggravating (negative) pathway-pair interactions
%    zer:            matrix of number of no-epistatic interactions pathway-pair interactions
%    nd:             matrix of number of non-decisive interactions pathway-pair interactions
%    pos:            matrix of number of buffering (positive) interactions pathway-pair interactions
%
% NOTE:
%    See figures in following publication:
%    Joshi CJ and Prasad A, 2014, "Epistatic interactions among metabolic genes
%    depend upon environmental conditions", Mol. BioSyst., 10, 2578-2589.
%
% .. Authors:
%     - Chintan Joshi 10/26/2018

if (nargin < 3)
    uSys = unique(convertMyCell2List(epSys, 2));
end

negE = (E < -0.25);
zerE = (E >= -0.25) & (E < 0.25);
ndE = (E >= 0.25) & (E < 0.85);
posE = (E >= 0.85);
g1 = 0;
g2 = 0;
pos = zeros(length(uSys), length(uSys)); neg = pos; nd = pos; zer = pos;
for i = 1:length(uSys)
    index1 = cellfun(@strcmp, epSys, repmat(uSys(i), length(epSys), 1));
    for j = 1:length(uSys)
        index2 = cellfun(@strcmp, epSys, repmat(uSys(j), length(epSys), 1));
        pos(i, j) = sum(sum(posE(index1, index2)));
        neg(i, j) = sum(sum(negE(index1, index2)));
        nd(i, j) = sum(sum(ndE(index1, index2)));
        zer(i, j) = sum(sum(zerE(index1, index2)));
    end
end

function B = convertMyCell2List(A, dimSense)

% This function linearizes a cell array if some of the cells in the array are another embbedded cell arrays.
% (hence, two degrees of cell array)

% This will only work for atmost two degrees of cell array.

% A=cell array to be linearized.
% dimSense=determines if inner cells are row (dimSense=1) vectors or columns (dimSense=2, default)

cnt = 0;
if nargin < 2
    dimSense = 2;
end
for i = 1:length(A)
    for j = 1:length(A{i, 1})
        cnt = cnt + 1;
        if dimSense == 1
            B{cnt, 1} = A{i, 1}{1, j};
        elseif dimSense == 2
            B{cnt, 1} = A{i, 1}{j, 1};
        end
    end
end


function chronux
% This library performs time-frequency analysis (mostly using the
% multi-taper spectral estimation method) of univariate and multivariate
% data, both for continuous processes such as LFP/EEG and for point
% processes such as spike times. Point process can either be stored as
% times or as a binned process of counts. The routines in this library
% are named differently for the three cases. For calculations
% that can be performed for each of the three data types, we use suffixes
% c, pb, or pt to refer to continuous, point process binned counts, or
% point process times. For example, the spectrum calculation is performed
% mtspectrumc for continuous processes, mtspectrumpb for a binned point
% process, and mtspectrumpt for a point process consisting of times. There
% are also routines for calculating hybrid quantities involving one continuous
% and one point process. These are suffixed in a similar manner. For
% example, coherencycpb calculates the coherency between a binned point process
% and a continuous process.
%
% Certain variables are used repeatedly in this library.
%
% DATA
% data in most cases can be univariate or multivariate, and either point process,
% or continuous.
%
%      Continuous data: Continuous data is assumed to be a matrix with
%                       dimensions samples x channels/trials.
%
%      Point Process: A single time series of spike times can be in the form of
%                     a column vector.
%                     Multichannel/trial spike time data is not amenable to this
%                     storage format, since there are generally different
%                     number of spikes in each channel/trial. Instead,
%                     multichannel/trial spike data is stored in a structure
%                     array. A structure is a matlab data object with various
%                     fields. These fields contain the elements
%                       e.g. The command data=struct('times',[]); creates an empty
%                            structure with field 'times'. Similarly, the command
%                            data=struct('times',[1 2 3]); creates the structure with
%                            the field 'times' containing integers 1, 2, and 3.
%
%                     We can also have a structure array (or an array of structures)
%                     defined for example, by
%                     data(1)=struct('times',rand(1,100)); and
%                     data(2)=struct('times',rand(1,200));
%                     This is a 2 dimensional structure array where the
%                     first field is a 100 dimensional random vector, and
%                     the second field is a 200 dimensional random vector.
%                     This format allows storage of multichannel point
%                     process times in a single variable data.
%
%                     The above holds for point processes stored as times.
%                     If instead, the point processes are binned, then one
%                     can use a matrix to represent them
%
%
%      Summary: data - array of continuous data with dimensions time x channels
%                      structural array of spike times with dimensions
%                               equal to the number of channels
%                      1d array of spike times as a column vector
%                      array of binned spike counts with dimensions time x channels
%
% PARAMETERS:
% These are various parameters used in the spectral calculations. Since
% these parameters are used by most routines in Chronux, they are stored in
% a single structure params. The fields of params are
%
% tapers : precalculated tapers from dpss or in the one of the following
%          forms:
%          (1) A numeric vector [TW K] where TW is the
%              time-bandwidth product and K is the number of
%              tapers to be used (less than or equal to
%              2TW-1).
%          (2) A numeric vector [W T p] where W is the
%              bandwidth, T is the duration of the data and p
%              is an integer such that 2TW-p tapers are used. In
%              this form there is no default i.e. to specify
%              the bandwidth, you have to specify T and p as
%              well. Note that the units of W and T have to be
%              consistent: if W is in Hz, T must be in seconds
%              and vice versa. Note that these units must also
%              be consistent with the units of params.Fs: W can
%              be in Hz if and only if params.Fs is in Hz.
%              The default is to use form 1 with TW=3 and K=5
%
%
% pad:   (padding factor for the FFT) - optional (can take values -1,0,1,2...).
%         -1 corresponds to no padding, 0 corresponds to padding
%         to the next highest power of 2 etc.
%			  e.g. For N = 500, if PAD = -1, we do not pad; if PAD = 0, we pad the FFT
%			       to 512 points, if pad=1, we pad to 1024 points etc.
%			       Defaults to 0.
%
% Fs:sampling frequency.optional (default 1)
%
%
% fpass: frequencies in an fft calculation can range from 0 to Fs/2 where
%        Fs is the sampling frequency. Sometimes it may be useful to
%        compute fourier transforms (and resulting quantities like the
%        spectrum over a smaller range of frequencies). This is specified
%        by fpass, which can be in the form [fmin fmax] where fmin >=0 and
%        fmax<=Fs/2. optional (default [0 Fs/2])
%
% err=[errtype p] calculates theoretical error bars (confidence levels)
%                 when errtype=1 and jackknife error bars when errchk=2. In each case, the
%                 error is calculated at a p value specified by p. -
%                 optional (default 0)
%
% trialave: trialave controls whether or not to average over channels/trials for
%           multichannel/trial analyses. trialave=0 (default) implies no trial
%           averaging, trialave=1 implies that the quantity of interest is averaged
%           over channels/trials. optional (default 0)
%
% Other parameters are discussed in individual routines as and when they
% are used.


function OptimizationModel = buildMTAproblemFromModel(model,rxnFBS,Vref,varargin)
% Returns the COBRA Optimization model needed to perform the MTA
%
% USAGE:
%
%       OptimizationModel = buildMTAproblemFromModel(model,rxnFBS,Vref,alpha,epsilon)
%
% INPUT:
%    model:                 Metabolic model (COBRA format)
%    rxnFBS:                Forward, Backward and Unchanged (+1;0;-1) values
%                           corresponding to each reaction.
%    Vref:                  Reference flux of the source state.
%    alpha:                 parameter of the quadratic problem (default = 0.66)
%    epsilon                minimun disturbance for each reaction, (default = 0)
%
% OUTPUTS:
%    OptimizationModel:     COBRA model struct that includes the
%                           stoichiometric contrains, the thermodinamic
%                           constrains and the binary variables.
%
% .. Authors:
%       - Luis V. Valcarcel, 03/06/2015, University of Navarra, CIMA & TECNUN School of Engineering.
%       - Luis V. Valcarcel, 26/10/2018, University of Navarra, CIMA & TECNUN School of Engineering.

p = inputParser; % check the inputs
% check requiered arguments
addRequired(p, 'model');
addRequired(p, 'rxnFBS', @isnumeric);
addRequired(p, 'Vref', @isnumeric);
% Check optional arguments
addOptional(p, 'alpha', 0.66, @isnumeric);
addOptional(p, 'epsilon', zeros(size(model.rxns)), @isnumeric);
% extract variables from parser
parse(p, model, rxnFBS, Vref, varargin{:});
alpha = p.Results.alpha;
epsilon = p.Results.epsilon;

% sometimes epsilon can be given as a single value
if numel(epsilon)==1
    epsilon = epsilon * ones(size(model.rxns));
end

%% --- set the COBRA model ---

% variables
v = 1:length(model.rxns);
y_plus_F = (1:sum(rxnFBS==+1)) + v(end);             % 1 if change in rxnForward, 0 otherwise
y_minus_F = (1:sum(rxnFBS==+1)) + y_plus_F(end);     % 1 if no change in rxnForward, 0 otherwise
y_plus_B = (1:sum(rxnFBS==-1)) + y_minus_F(end);     % 1 if change in rxnBackward, 0 otherwise
y_minus_B = (1:sum(rxnFBS==-1)) + y_plus_B(end);     % 1 if no change in rxnBackward, 0 otherwise
n_var = y_minus_B(end);

% limits of the variables
lb = zeros(n_var,1);
ub = ones (n_var,1);
lb(v) = model.lb;
ub(v) = model.ub;

%type of variables
vartype(1:n_var) = 'B';
vartype(v) = 'C';

% constrains
Eq1 = 1:length(model.mets);                 % Stoichiometric matrix
Eq2 = (1:length(y_plus_F)) + Eq1(end);      % Changes in Forward
Eq3 = (1:length(y_plus_F)) + Eq2(end);      % Change or not change in Forward
Eq4 = (1:length(y_plus_B)) + Eq3(end);      % Changes in Backward
Eq5 = (1:length(y_plus_B)) + Eq4(end);      % Change or not change in Backward
nCon = Eq5(end);

% generate constrain matrix
A = spalloc(nCon, n_var, nnz(model.S) + 5*length(Eq2) + 5*length(Eq4));
b = zeros(nCon,1);
csense = char(zeros(nCon,1));

posF = find(rxnFBS == +1);
posB = find(rxnFBS == -1);
posS = find(rxnFBS == 0);

% First contraint, stoichiometric
A(Eq1,v) = model.S;
b(Eq1) = 0;
csense(Eq1) = 'E';

% Second contraint, Change or not change in Forward
A(Eq2,v(posF)) = eye(length(posF));
A(Eq2,y_plus_F) = - ( Vref(posF) + epsilon(posF) ) .* eye(length(posF));
A(Eq2,y_minus_F) = - model.lb(posF) .* eye(length(posF));
b(Eq2) = 0;
csense(Eq2) = 'G';

% Third contraint, Change or not change in Forward
A(Eq3,y_plus_F) = eye(length(Eq3));
A(Eq3,y_minus_F) = eye(length(Eq3));
b(Eq3) = 1;
csense(Eq3) = 'E';

% Fourth contraint, Backward changes
A(Eq4,posB) = eye(length(posB));
A(Eq4,y_plus_B) = - ( Vref(posB) - epsilon(posB) ) .* eye(length(posB));
A(Eq4,y_minus_B) = - model.ub(posB) .* eye(length(posB));
b(Eq4) = 0;
csense(Eq4) = 'L';

% Fiveth contraint, Change or not change in Backward
A(Eq5,y_plus_B) = eye(length(Eq5));
A(Eq5,y_minus_B) = eye(length(Eq5));
b(Eq5) = 1;
csense(Eq5) = 'E';

% Objective fuction
% linear part
c = zeros(n_var,1);
c(y_minus_F) = alpha/2;
c(y_minus_B) = alpha/2;
c(v(posS)) = -2 * Vref(posS) * (1-alpha);
% quadratic part
F = spalloc(n_var,n_var,length(posS));
F(v(posS),v(posS)) =  2 * (1-alpha) .* eye(length(posS));

% save the resultant model
OptimizationModel = struct();
[OptimizationModel.A, OptimizationModel.lb, OptimizationModel.ub] = deal(A, lb, ub);
[OptimizationModel.b, OptimizationModel.csense] = deal(b, csense);
[OptimizationModel.c, OptimizationModel.F] = deal(c, F);
[OptimizationModel.osense, OptimizationModel.vartype] = deal(+1, vartype); % +1 for minimization

%save the index of the variables
OptimizationModel.idx_variables.v = v;
OptimizationModel.idx_variables.y_plus_F = y_plus_F;
OptimizationModel.idx_variables.y_minus_F = y_minus_F;
OptimizationModel.idx_variables.y_plus_B = y_plus_B;
OptimizationModel.idx_variables.y_minus_B = y_minus_B;

end


function [p,t] = meshpoly(node,edge,qtree,p,options)

%*****************************************************************************80
%
% MESHPOLY: Core meshing routine called by mesh2d and meshfaces.
%
% Do not call this routine directly, use mesh2d or meshfaces instead!
%
% Inputs:
%
%  NODE     : Nx2 array of geometry XY co-ordinates
%  EDGE     : Mx2 array of connections between NODE, defining geometry
%             edges
%  QTREE    : Quadtree data structure, defining background mesh and element
%             size function
%  P        : Qx2 array of potential boundary nodes
%  OPTIONS  : Meshing options data structure
%  WBAR     : Handle to progress bar
%
% Outputs:
%
%  P        : Nx2 array of triangle nodes
%  T        : Mx3 array of triangles as indices into P
%
% Mesh2d is a delaunay based algorithm with a "Laplacian-like" smoothing
% operation built into the mesh generation process.
%
% An unbalanced quadtree decomposition is used to evaluate the element size
% distribution required to resolve the geometry. The quadtree is
% triangulated and used as a backgorund mesh to store the element size
% data.
%
% The main method attempts to optimise the node location and mesh topology
% through an iterative process. In each step a constrained delaunay
% triangulation is generated with a series of "Laplacian-like" smoothing
% operations used to improve triangle quality. Nodes are added or removed
% from the mesh to ensure the required element size distribution is
% approximated.
%
% The optimisation process generally returns well shaped meshes with no
% small angles and smooth element size variations. Mesh2d shares some
% similarities with the Distmesh code:
%
%   [1] P.-O. Persson, G. Strang, A Simple Mesh Generator in MATLAB.
%       SIAM Review, Volume 46 (2), pp. 329-345, June 2004
%
%  Author:
%
%    Darren Engwirda
%

shortedge   = 0.75;
longedge    = 1.5;
smalltri    = 0.25;
largetri    = 4.0;
qlimit      = 0.5;
dt          = 0.2;

stats = struct('t_init',0.0,'t_tri',0.0,'t_inpoly',0.0,'t_edge',0.0, ...
                  't_sparse',0.0,'t_search',0.0,'t_smooth',0.0,'t_density',0.0, ...
                     'n_tri',0);

% Initialise mesh
%  P     : Initial nodes
%  T     : Initial triangulation
%  TNDX  : Enclosing triangle for each node as indices into TH
%  FIX   : Indices of FIXED nodes in P
tic
if options.output
   fprintf('Initialising Mesh \n');
end
[p,fix,tndx] = initmesh(p,qtree.p,qtree.t,qtree.h,node,edge);
stats.t_init = toc;

% Main loop
if options.output
   fprintf('Iteration   Convergence (%%)\n');
end
for iter = 1:options.maxit

   [p,i,j] = unique(p,'rows');                                             % Ensure unique node list
   fix = j(fix);
   tndx = tndx(i);

   tic
   [p,t] = cdt(p,node,edge);                                               % Constrained Delaunay triangulation
   stats.n_tri = stats.n_tri+1;
   stats.t_tri = stats.t_tri+toc;

   tic
   e = getedges(t,size(p,1));                                    % Unique edges
   stats.t_edge = stats.t_edge+toc;

   % Sparse node-to-edge connectivity matrix
   tic
   nume = size(e,1);
   S = sparse(e(:),[1:nume,1:nume],[ones(nume,1); -ones(nume,1)],size(p,1),nume);
   stats.t_sparse = stats.t_sparse+toc;

   tic
%
% Find enclosing triangle in background mesh for nodes
%
  opt = 4;
  if ( opt == 0 )
    tndx = mytsearch ( qtree.p(:,1), qtree.p(:,2), qtree.t, p(:,1), p(:,2), tndx );
  elseif ( opt == 1 )
    tndx = tsearch ( qtree.p(:,1), qtree.p(:,2), qtree.t, p(:,1), p(:,2) );
  elseif ( opt == 2 )
    tndx = tsearch_mex ( qtree.p(:,1), qtree.p(:,2), qtree.t, p(:,1), p(:,2) );
  elseif ( opt == 3 )
    dtri = DelaunayTri ( qtree.p );
    tndx = pointLocation ( dtri, p );
  elseif ( opt == 4 )
    tndx = tsearchn ( qtree.p, qtree.t, p );
  end

   hn = tinterp(qtree.p,qtree.t,qtree.h,p,tndx);  % Size function at nodes via linear interpolation
   h = 0.5*(hn(e(:,1))+hn(e(:,2)));                                        % from the background mesh. Average to edge midpoints.
   stats.t_search = stats.t_search+toc;

   edgev = p(e(:,1),:)-p(e(:,2),:);
   L = max(sqrt(sum((edgev).^2,2)),eps);                                   % Edge lengths

   % Inner smoothing sub-iterations
   time = cputime;
   move = 1.0;
   done = false;
   for subiter = 1:(iter-1)

      moveold = move;

      % Spring based smoothing
      L0 = h*sqrt(sum(L.^2)/sum(h.^2));
      F = max(L0./L-1.0,-0.1);
      F = S*(edgev.*[F,F]);
      F(fix,:) = 0.0;
      p = p+dt*F;

      % Measure convergence
      edgev = p(e(:,1),:)-p(e(:,2),:);
      L0 = max(sqrt(sum((edgev).^2,2)),eps);                               % Edge lengths
      move = norm((L0-L)./L,'inf');                                        % Percentage change
      L = L0;

      if move<options.mlim                                                 % Test convergence
         done = true;
         break
      end

   end
   stats.t_smooth = stats.t_smooth+(cputime-time);

   if options.output
      fprintf('%2i           %2.1f\n',[iter, 100.0*min(1.0, options.mlim/max(move,eps))]);
   end

   tic
   [p,t] = cdt(p,node,edge);                                               % Constrained Delaunay triangulation
   stats.n_tri = stats.n_tri+1;
   stats.t_tri = stats.t_tri+toc;

   tic
   e = getedges(t,size(p,1));                                              % Unique edges
   stats.t_edge = stats.t_edge+toc;

   edgev = p(e(:,1),:)-p(e(:,2),:);
   L = max(sqrt(sum((edgev).^2,2)),eps);                                   % Edge lengths

   tic
%
% Find enclosing triangle in background mesh for nodes
%
  opt = 4;
  if ( opt == 0 )
    tndx = mytsearch ( qtree.p(:,1), qtree.p(:,2), qtree.t, p(:,1), p(:,2), tndx );
  elseif ( opt == 1 )
    tndx = tsearch ( qtree.p(:,1), qtree.p(:,2), qtree.t, p(:,1), p(:,2) );
  elseif ( opt == 2 )
    tndx = tsearch_mex ( qtree.p(:,1), qtree.p(:,2), qtree.t, p(:,1), p(:,2) );
  elseif ( opt == 3 )
    dtri = DelaunayTri ( qtree.p );
    tndx = pointLocation ( dtri, p );
  elseif ( opt == 4 )
    tndx = tsearchn ( qtree.p, qtree.t, p );
  end

   hn = tinterp(qtree.p,qtree.t,qtree.h,p,tndx);                           % Size function at nodes via linear interpolation
   h = 0.5*(hn(e(:,1))+hn(e(:,2)));                                        % from the background mesh. Average to edge midpoints.
   stats.t_search = stats.t_search+toc;

   r = L./h;
   if done && (max(r)<3.0)                                                 % Main loop convergence
      break
   else
      if (iter==options.maxit)
         fprintf('WARNING: Maximum number of iterations reached. Solution did not converge! \n');
         fprintf('Please email the geometry and settings to d_engwirda@hotmail.com \n');
      end
   end

   % Nodal density control
   tic
   if iter<options.maxit
      % Estimate required triangle area from size function
      Ah = 0.5*tricentre(t,hn).^2;
      % Remove nodes
      i = find(abs(triarea(p,t))<smalltri*Ah);                             % Remove all nodes in triangles with small area
      k = find(sum(abs(S),2)<2);                                           % Nodes with less than 2 edge connections
      j = find(r<shortedge);                                               % Remove both nodes for short edges
      if ~isempty(j) || ~isempty(k) || ~isempty(i)
         prob = false(size(p,1),1);                                        % True for nodes to be removed
         prob(e(j,:)) = true;                                              % Edges with insufficient length
         prob(t(i,:)) = true;                                              % Triangles with insufficient area
         prob(k) = true;                                                   % Remove nodes with less than 2 edge connections
         prob(fix) = false;                                                % Don't remove fixed nodes
         pnew = p(~prob,:);                                                % New node list
         tndx = tndx(~prob);
         j = zeros(size(p,1),1);                                           % Re-index FIX to keep consistent
         j(~prob) = 1;
         j = cumsum(j);
         fix = j(fix);
      else
         pnew = p;
      end
      % Add new nodes
      i = abs(triarea(p,t))>largetri*Ah;                                   % Large triangles
      r = longest(p,t)./tricentre(t,hn);
      k = (r>longedge) & (quality(p,t)<qlimit);                            % Low quality triangles
      if any(i|k)

         k = find(k & ~i);
         i = find(i);

         % Add new nodes at circumcentres
         cc = circumcircle(p,[t(i,:); t(k,:)]);

         % Don't add multiple points in one circumcircle
         ok = [true(size(i)); false(size(k))];
         for ii = (length(i)+1):size(cc,1)
            % Current point
            x = cc(ii,1);
            y = cc(ii,2);
            % Check if inside any accepted circumcircles
            in = false;
            j = find(ok);
            for jj = 1:length(j)
               kk = j(jj);
               dx = (x-cc(kk,1))^2;
               if dx<cc(kk,3) && (dx+(y-cc(kk,2))^2)<cc(kk,3)
                  in = true;
                  break;
               end
            end
            if ~in
               ok(ii) = true;
            end
         end
         cc = cc(ok,:);
         cc = cc(inpoly(cc(:,1:2),node,edge),:);                           % Only take internal points

         % New arrays
         pnew = [pnew; cc(:,1:2)];
         tndx = [tndx; zeros(size(cc,1),1)];
      end
      p = pnew;
   end
   stats.t_density = stats.t_density+toc;

end

if options.debug
   disp(stats);
end

end      % meshpoly()

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [p,t] = cdt(p,node,edge)

% Approximate geometry-constrained Delaunay triangulation.

t = mydelaunayn(p);                                                        % Delaunay triangulation via QHULL

% Impose geometry constraints
i = inpoly(tricentre(t,p),node,edge);                                      % Take triangles with internal centroids
t = t(i,:);

end      % [p,t] = cdt(p,node,edge)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [p,fix,tndx] = initmesh(p,ph,th,hh,node,edge)

% Initialise the mesh nodes

% Boundary nodes for all geometry edges have been passed in. Only take
% those in the current face
i = findedge(p,node,edge,1.0e-08);
p = p(i>0,:);
fix = (1:size(p,1))';

% Initial nodes taken as fixed boundary nodes + internal nodes from
% quadtree.
[i,j] = inpoly(ph,node,edge);
p = [p; ph(i&~j,:)];
tndx = zeros(size(p,1),1);

end      % initmesh()

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function e = getedges(t,n)

% Get the unique edges and boundary nodes in a triangulation.

e = sortrows( sort([t(:,[1,2]); t(:,[1,3]); t(:,[2,3])],2) );
idx = all(diff(e,1)==0,2);                                                 % Find shared edges
idx = [idx;false]|[false;idx];                                             % True for all shared edges
bnd = e(~idx,:);                                                           % Boundary edges
e = e(idx,:);                                                              % Internal edges
e = [bnd; e(1:2:end-1,:)];                                                 % Unique edges and bnd edges for tri's

end      % getedges()

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function fc = tricentre(t,f)

% Interpolate nodal F to the centroid of the triangles T.

fc = (f(t(:,1),:)+f(t(:,2),:)+f(t(:,3),:))/3.0;

end      % tricentre()

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function d = longest(p,t)

% Return the length of the longest edge in each triangle.

d1 = sum((p(t(:,2),:)-p(t(:,1),:)).^2,2);
d2 = sum((p(t(:,3),:)-p(t(:,2),:)).^2,2);
d3 = sum((p(t(:,1),:)-p(t(:,3),:)).^2,2);

d = sqrt(max([d1,d2,d3],[],2));

end      % longest()


function [F,P] = spm_MH_reml(YY,X,Q,N,hE);
% Estimation of covariance components from y*y' using sampling
% FORMAT [F,P] = spm_MH_reml(YY,X,Q,N,[hE]);
%
% YY  - (m x m) sample covariance matrix Y*Y'  {Y = (m x N) data matrix}
% X   - (m x p) design matrix
% Q   - {1 x q} covariance components
% N   - number of samples
%
% hE  - prior expectation: log-normal hyper-parameterisation (with hyperpriors)
%
% F   - [-ve] free energy F = log evidence = p(Y|X,Q)
% P   - smaple of hyperparameters from thier posterioir p(h|YY,X,Q)
%--------------------------------------------------------------------------
%
% This routiens using MCMC sampling (reverible Metropolis-Hastings)
%__________________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

% Karl Friston
% $Id: spm_MH_reml.m 5033 2012-11-02 20:59:54Z karl $

% assume a single sample if not specified
%--------------------------------------------------------------------------
try
    N;
catch
    N  = 1;
end

% assume OPT = 0
%--------------------------------------------------------------------------
try
    hE;
    OPT = hE;
catch
    OPT = 0;
end

% ortho-normalise X
%--------------------------------------------------------------------------
if isempty(X)
    X = sparse(length(Q{1}),1);
else
    X = orth(full(X));
end

% remove fixed effects
%--------------------------------------------------------------------------
n     = length(Q{1});
m     = length(Q);
h     = zeros(m,1);
R     = speye(n,n) - X*X';
YY    = R*YY*R;

M.OPT = OPT;
M.Q   = Q;
M.N   = N;

% initialise and specify hyperpriors
%--------------------------------------------------------------------------
[C,h,Ph,Fr] = spm_reml(YY,X,Q,N,0,4,OPT);
if M.OPT
    M.hE  = h - 16;
    M.hP  = eye(m,m)/32;
else
    M.hE  = zeros(m,1);
    M.hP  = speye(m,m)/exp(32);
end

% sample
%--------------------------------------------------------------------------
[P,F] = spm_MH('spm_MH_reml_likelihood',h,YY,M);


function [mustUSet, posMustU] = findMustU(model, minFluxesW, maxFluxesW, varargin)
% This function runs the second step of `optForce`, that is to solve a
% bilevel mixed integer linear programming  problem to find a first order
% `MustU` set.
%
% USAGE:
%
%    [mustUSet, posMustU] = findMustU(model, minFluxesW, maxFluxesW, varargin)
%
% INPUTS:
%    model:                      (structure) COBRA metabolic model
%                                with at least the following fields:
%
%                                 * .rxns - Reaction IDs in the model
%                                 * .mets - Metabolite IDs in the model
%                                 * .S -    Stoichiometric matrix (sparse)
%                                 * .b -    RHS of `Sv = b` (usually zeros)
%                                 * .c -    Objective coefficients
%                                 * .lb -   Lower bounds for fluxes
%                                 * .ub -   Upper bounds for fluxes
%    minFluxesW:                (double array) of size `n_rxns x 1`
%                               Minimum fluxes for each
%                               reaction in the model for wild-type strain.
%                               This can be obtained by running the
%                               function `FVAOptForce`.
%                               E.g.: `minFluxesW = [-90; -56];`
%    maxFluxesW:                (double array) of size `n_rxns x 1`
%                               Maximum fluxes for each
%                               reaction in the model for wild-type strain.
%                               This can be obtained by running the
%                               function `FVAOptForce`.
%
% OPTIONAL INPUTS:
%    constrOpt:                 (structure) structure containing
%                               additional contraints. Include here only
%                               reactions whose flux is fixed, i.e.,
%                               reactions whose lower and upper bounds have
%                               the same value. Do not include here
%                               reactions whose lower and upper bounds have
%                               different values. Such contraints should be
%                               defined in the lower and upper bounds of
%                               the model. The structure has the following
%                               fields:
%
%                                 * .rxnList - Reaction list (cell array)
%                                 * .values -  Values for constrained
%                                   reactions (double array)
%                                   E.g.: `struct('rxnList', {{'EX_gluc', 'R75', 'EX_suc'}}, 'values', [-100, 0, 155.5]');`
%    runID:                     (string) ID for identifying this run.
%                               Default: ['run' date hour].
%    outputFolder:              (string) name for folder in which results
%                               will be stored.
%                               Default: 'OutputsFindMustU'.
%    outputFileName:            (string) name for files in which results
%                               will be stored.
%                               Default: 'MustUSet'.
%    printExcel:                (double) boolean to describe wheter data
%                               must be printed in an excel file or not.
%                               Default: 1
%    printText:                 (double) boolean to describe wheter data
%                               must be printed in an plaint text file or not.
%                               Default: 1
%    printReport:               (double) 1 to generate a report in a plain
%                               text file. 0 otherwise.
%                               Default: 1
%    keepInputs:                (double) 1 to save inputs to run
%                               `findMustU.m`, 0 otherwise.
%                               Default: 1
%    printLevel:                (double) 1 to print results in console.
%                               0 otherwise.
%                               Default: 0
%
% OUTPUTS:
%    mustUSet:                  (cell array) Size: number of reactions found X 1
%                               cell array containing the
%                               reactions ID which belong to the `Must_U` Set
%    posMustU:                  (double array) Size: number of reactions found X 1
%                               double array containing the
%                               positions of reactions in the model.
%    outputFileName.xls         File containing one column array
%                               with identifiers for reactions in `MustU`. This
%                               file will only be generated if the user entered
%                               `printExcel = 1`. Note that the user can choose
%                               the name of this file entering the input
%                               `outputFileName = 'PutYourOwnFileNameHere';`
%    outputFileName.txt         File containing one column array
%                               with identifiers for reactions in MustU. This
%                               file will only be generated if the user entered
%                               `printText = 1`. Note that the user can choose
%                               the name of this file entering the input
%                               `outputFileName = 'PutYourOwnFileNameHere';`
%    outputFileName_Info.xls    File containing five column arrays.
%
%                                 * C1: identifiers for reactions in `MustU`
%                                 * C2: min fluxes for reactions according to FVA
%                                 * C3: max fluxes for reactions according to FVA
%                                 * C4: min fluxes achieved for reactions,
%                                   according to `findMustU`
%                                 * C5: max fluxes achieved for reactions,
%                                   according to `findMustU`
%                               This file will only be generated if the user
%                               entered `printExcel = 1`. Note that the user can
%                               choose the name of this file entering the input
%                               `outputFileName = 'PutYourOwnFileNameHere';`
%    outputFileName_Info.txt    File containing five column arrays.
%
%                                 * C1: identifiers for reactions in `MustU`.
%                                 * C2: min fluxes for reactions according to FVA
%                                 * C3: max fluxes for reactions according to FVA
%                                 * C4: min fluxes achieved for reactions,
%                                   according to `findMustU`
%                                 * C5: max fluxes achieved for reactions,
%                                   according to `findMustU`
%                               This file will only be generated if the user
%                               entered `printText = 1`. Note that the user can
%                               choose the name of this file entering the input
%                               `outputFileName = 'PutYourOwnFileNameHere';`
% NOTE:
%
%    This function is based in the GAMS files written by Sridhar
%    Ranganathan which were provided by the research group of Costas D.
%    Maranas. For a detailed description of the `optForce` procedure, please
%    see: `Ranganathan S, Suthers PF, Maranas CD (2010) OptForce: An
%    Optimization Procedure for Identifying All Genetic Manipulations
%    Leading to Targeted Overproductions. PLOS Computational Biology 6(4):
%    e1000744`. https://doi.org/10.1371/journal.pcbi.1000744
%
% .. Author: - Sebastian Mendoza, May 30th 2017, Center for Mathematical Modeling, University of Chile, snmendoz@uc.cl

optionalParameters = {'constrOpt', 'runID', 'outputFolder', 'outputFileName',  ...
    'printExcel', 'printText', 'printReport', 'keepInputs', 'verbose', 'printLevel'};

if (numel(varargin) > 0 && (~ischar(varargin{1}) || ~any(ismember(varargin{1},optionalParameters))))

    tempargin = cell(1,2*(numel(varargin)));
    for i = 1:numel(varargin)

        tempargin{2*(i-1)+1} = optionalParameters{i};
        tempargin{2*(i-1)+2} = varargin{i};
    end
    varargin = tempargin;

end

parser = inputParser();
parser.addRequired('model',@(x) isstruct(x) && isfield(x, 'S') && isfield(model, 'rxns')...
    && isfield(model, 'mets') && isfield(model, 'lb') && isfield(model, 'ub') && isfield(model, 'b')...
    && isfield(model, 'c'))
parser.addRequired('minFluxesW',@isnumeric)
parser.addRequired('maxFluxesW',@isnumeric)
parser.addParamValue('constrOpt', struct('rxnList', {{}}, 'values', []),@(x) isstruct(x) && isfield(x, 'rxnList') && isfield(x, 'values') ...
    && length(x.rxnList) == length(x.values) && length(intersect(x.rxnList, model.rxns)) == length(x.rxnList))
hour = clock; defaultRunID = ['run-' date '-' num2str(hour(4)) 'h' '-' num2str(hour(5)) 'm'];
parser.addParamValue('runID', defaultRunID, @(x) ischar(x))
parser.addParamValue('outputFolder', 'OutputsFindMustU', @(x) ischar(x))
parser.addParamValue('outputFileName', 'MustUSet', @(x) ischar(x))
parser.addParamValue('printExcel', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('printText', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('printReport', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('keepInputs', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('verbose', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('printLevel', 1, @(x) isnumeric(x) || islogical(x));

parser.parse(model, minFluxesW, maxFluxesW, varargin{:})
model = parser.Results.model;
minFluxesW = parser.Results.minFluxesW;
maxFluxesW = parser.Results.maxFluxesW;
constrOpt= parser.Results.constrOpt;
runID = parser.Results.runID;
outputFolder = parser.Results.outputFolder;
outputFileName = parser.Results.outputFileName;
printExcel = parser.Results.printExcel;
printText = parser.Results.printText;
printReport = parser.Results.printReport;
keepInputs = parser.Results.keepInputs;

printFlags = {'printLevel','verbose'};
%get the printLevel.
if all(~ismember(printFlags,parser.UsingDefaults))
    error('Either supply printLevel or verbose optional parameter')
else
    if any(~ismember(printFlags,parser.UsingDefaults))
        selected = ~ismember(printFlags,parser.UsingDefaults);
        printLevel = parser.Results.(printFlags{selected});
    else
        printLevel = parser.Results.printLevel;
    end
end

% correct size of constrOpt
if ~isempty(constrOpt.rxnList)
    if size(constrOpt.rxnList, 1) > size(constrOpt.rxnList,2); constrOpt.rxnList = constrOpt.rxnList'; end;
    if size(constrOpt.values, 1) > size(constrOpt.values,2); constrOpt.values = constrOpt.values'; end;
end

%current path
workingPath = pwd;
runID = [workingPath filesep runID];
%go to the path associate to the ID for this run.
if exist(runID, 'dir')~=7
    mkdir(runID);
end
cd(runID);
outputFolder = [runID filesep outputFolder];

% if the user wants to generate a report.
if printReport
    %create name for file.
    hour = clock;
    reportFileName = ['report-' date '-' num2str(hour(4)) 'h' '-' num2str(hour(5)) 'm.txt'];
    freport = fopen(reportFileName, 'w');
    % print date of running.
    fprintf(freport, ['findMustU.m executed on ' date ' at ' num2str(hour(4)) ':' num2str(hour(5)) '\n\n']);
    % print matlab version.
    fprintf(freport, ['MATLAB: Release R' version('-release') '\n']);

    %print each of the inputs used in this running.
    fprintf(freport, '\nThe following inputs were used to run OptForce: \n');
    fprintf(freport, '\n------INPUTS------\n');
    %print model.
    fprintf(freport, '\nModel:\n');
    for i = 1:length(model.rxns)
        rxn = printRxnFormula(model, model.rxns{i}, false);
        fprintf(freport, [model.rxns{i} ': ' rxn{1} '\n']);
    end
    %print lower and upper bounds, minimum and maximum values for each of
    %the reactions in wild-type and mutant strain
    fprintf(freport, '\nLB\tUB\tMin_WT\tMax_WT\n');
    for i = 1:length(model.rxns)
        fprintf(freport, '%6.4f\t%6.4f\t%6.4f\t%6.4f\n', model.lb(i), model.ub(i), minFluxesW(i), maxFluxesW(i));
    end

    %print constraints
    fprintf(freport,'\nConstrained reactions:\n');
    for i = 1:length(constrOpt.rxnList)
        fprintf(freport,'%s: fixed in %6.4f\n', constrOpt.rxnList{i}, constrOpt.values(i));
    end

    fprintf(freport,'\nrunID(Main Folder): %s \n\noutputFolder: %s \n\noutputFileName: %s \n',...
        runID, outputFolder, outputFileName);

    fprintf(freport,'\nprintExcel: %1.0f \n\nprintText: %1.0f \n\nprintReport: %1.0f \n\nkeepInputs: %1.0f  \n\nverbose: %1.0f \n',...
        printExcel, printText, printReport, keepInputs, printLevel);

end

% export inputs for running the optimization problem to find the MustU Set
inputFolder = [runID filesep 'InputsMustU'];
saveInputsMustSetsFirstOrder(model, minFluxesW, maxFluxesW, constrOpt, inputFolder);

% create a directory to save results if this don't exist
if ~exist(outputFolder, 'dir')
    mkdir(outputFolder);
end

%number of reactions
n_rxns = length(model.rxns);

% initilize sets can, must and empty arrays to store values.
can = zeros(n_rxns, 1);
can(minFluxesW ~= 0) = 1;
can(maxFluxesW ~= 0) = 1;
must = zeros(n_rxns, 1);
mustU = zeros(n_rxns, 1);
vmin = zeros(n_rxns, 1);
vmax = zeros(n_rxns, 1);

found = 0;
%while a solution is still found
while 1
    % create bilevel problem
    bilevelMILPproblem = buildBilevelMILPproblemForFindMustU(model, can, must, maxFluxesW, constrOpt);
    % solve problem
    MustUSol = solveCobraMILP(bilevelMILPproblem, 'printLevel', printLevel);

    if MustUSol.stat~=1
        break;
    else
        % if there is a solution
        found = 1;
        %find which reaction was found
        pos_actives = find(MustUSol.int);
        %update must sets
        must(pos_actives) = 1;
        mustU(pos_actives) = 1;
        %find minimum value for the reaction found
        vmin(pos_actives) = MustUSol.cont(pos_actives);
        %find maximum value for the reaction found
        model2 = changeObjective(model, model.rxns(pos_actives));
        fba = optimizeCbModel(model2);
        vmax(pos_actives) = fba.f;
    end
end

%print the results now.
if printReport; fprintf(freport, '\n------RESULTS------\n'); end;

%if user decide not to show inputs files for findMustU.gms
if ~keepInputs; rmdir(inputFolder,'s'); end;

if found
    %if a solution is found
    if printReport; fprintf(freport, '\na MustU set was found\n'); end;
    if printLevel; fprintf('a MustU set was found\n'); end;
    %find mustU sets
    posMustU = find(mustU);
    mustUSet = model.rxns(posMustU);
else
    %if no solution is found
    if printReport; fprintf(freport, '\na MustU set was not found\n'); end;
    if printLevel; fprintf('a MustU set was not found\n'); end;
    %initilize empty arrays
    mustUSet = {};
    posMustU = {};
end

% print info into an excel text file if required by the user
if printExcel
    if found
        currentFolder = pwd;
        cd(outputFolder);
        Info = [{'Reactions'},{'Min Flux in Wild-type strain'},{'Max Flux in Wild-type strain'},{'Min Flux in Mutant strain'},{'Max Flux in Mutant strain'}];
        Info = [Info; [model.rxns(posMustU), num2cell(minFluxesW(posMustU)), num2cell(maxFluxesW(posMustU)), num2cell(vmin(posMustU)), num2cell(vmax(posMustU))]];
        setupxlwrite();
        xlwrite([outputFileName '_Info'],Info);
        xlwrite(outputFileName, mustUSet);
        cd(currentFolder);
        if printLevel; fprintf(['MustU set was printed in ' outputFileName '.xls  \n']); end;
        if printReport; fprintf(freport, ['\nMustU set was printed in ' outputFileName '.xls  \n']); end;
    else
        if printLevel; fprintf('No mustU set was not found. Therefore, no excel file was generated\n'); end;
        if printReport; fprintf(freport, '\nNo mustU set was not found. Therefore, no excel file was generated\n'); end;
    end
end

% print info into a plain text file if required by the user
if printText
    if found
        currentFolder = pwd;
        cd(outputFolder);
        f = fopen([outputFileName '_Info.txt'], 'w');
        fprintf(f, 'Reactions\tMin Flux in Wild-type strain\tMax Flux in Wild-type strain\tMin Flux in Mutant strain\tMax Flux in Mutant strain\n');
        for i=1:length(posMustU)
            fprintf(f, '%s\t%4.4f\t%4.4f\t%4.4f\t%4.4f\n', model.rxns{posMustU(i)}, minFluxesW(posMustU(i)), maxFluxesW(posMustU(i)), vmin(posMustU(i)), vmax(posMustU(i)));
        end
        fclose(f);

        f = fopen([outputFileName '.txt'], 'w');
        for i = 1:length(posMustU)
            fprintf(f, '%s\n', mustUSet{i});
        end
        fclose(f);

        cd(currentFolder);
        if printLevel; fprintf(['MustU set was printed in ' outputFileName '.txt  \n']); end;
        if printReport; fprintf(freport, ['\nMustU set was printed in ' outputFileName '.txt  \n']); end;
    else
        if printLevel; fprintf('No mustU set was found. Therefore, no excel file was generated\n'); end;
        if printReport; fprintf(freport, '\nNo mustU set was found. Therefore, no excel file was generated\n'); end;
    end
end

%close file for saving report
if printReport; fclose(freport); end;
if printReport; movefile(reportFileName, outputFolder); end;

%go back to the original path
cd(workingPath);

end

function bilevelMILPproblem = buildBilevelMILPproblemForFindMustU(model, can, must, maxFluxesW, constrOpt)

if nargin < 5 || isempty(constrOpt.rxnList)
    ind_ic = [];
    b_ic = [];
    sel_ic = zeros(length(model.rxns), 1);
    sel_ic_b = zeros(length(model.rxns), 1);
else
    %get indices of rxns
    [~, ind_a, ind_b] = intersect(model.rxns, constrOpt.rxnList);
    aux = constrOpt.values(ind_b);
    %sort for rxn index
    [sorted, ind_sorted] = sort(ind_a);
    ind_ic = sorted;
    b_ic = aux(ind_sorted); if size(b_ic, 1) > size(b_ic, 2); b_ic = b_ic'; end;
    sel_ic = zeros(length(model.rxns), 1);
    sel_ic(ind_ic) = 1;
    sel_ic_b = zeros(length(model.rxns), 1);
    sel_ic_b(ind_ic) = b_ic;
end

%convert inputs
S = model.S;
ub = model.ub;
lb = model.lb;
% Dimensions
[n_mets, n_rxns] = size(S);

% indices of not contrained variables
ind_nic = setdiff(1:n_rxns, ind_ic);

% boolean vector for not constrained variables
sel_nic = zeros(n_rxns, 1);
sel_nic(ind_nic) = 1;
% boolean vector for integer variables
selRxns = ones(size(model.rxns));
sel_int = selRxns;
% bolean vector for reactions in can set and not in must set and not in
% constrained set of reactions
sel_c_nm_nc = can & ~must & sel_nic;

% Number of integer variables
n_int = sum(sel_int);
% Number of inner  constraints
n_ic = length(ind_ic);
% Number of inner variables not constrained
n_nic = length(ind_nic);

% Iic
Iic=selMatrix(sel_ic);
% Inic
Inic=selMatrix(sel_nic);

% Set variable types
vartype_bl(1: 7 * n_rxns + n_int + n_mets + 1) = 'C';
vartype_bl(n_rxns + 1:n_rxns + n_int) = 'B';

H = 1000;
bigM = 1000;

%   v(j)      y(j)      mu(j)     w(j)    deltam(j) deltap(j)  theta(j) thetap(j) labmda(i)    z
%|---------|---------|---------|---------|---------|---------|---------|---------|---------|---------|
%   n         n_int      n         n         n         n          n         n         m        1

% Set upper/lower bounds
lb_bl = [lb; zeros(6 * n_rxns + n_int + n_mets + 1, 1)]; %v(j)
ub_bl = [ub; H * ones(6 * n_rxns + n_int + n_mets + 1, 1)]; %v(j)
lb_bl(n_rxns + n_int + 1:n_rxns + n_int + n_rxns) = -1000; %mu(j)
lb_bl(2 * n_rxns + n_int + 1:2 * n_rxns + n_int + n_rxns) = -1000; %w(j)
lb_bl(7 * n_rxns + n_int + 1:7 * n_rxns + n_int + n_mets) = -1000; %lambda(i)
lb_bl(7 * n_rxns + n_int + n_mets + 1) = -1000; %z

%PRIMAL PROBLEM
%1) primal1 (n_mets equations)
%   S*v=0
A_p = [S zeros(n_mets, n_rxns * 6 + n_int + n_mets + 1)];
b_p = zeros(n_mets,1);
csense_p(1:n_mets) = 'E';

%2) primal 2, 3 and 7 (n_ic equations)
%   v_ic = b_ic
if n_ic > 0
    A_p = [A_p; Iic zeros(n_ic, n_rxns * 6 + n_int + n_mets + 1)];
    b_p = [b_p; b_ic'];
    csense_p(end + 1:end + n_ic) = 'E';
end

%3) primal 5 (n_ic equations)
%   -v(j) >= -ub(j)
A_p = [A_p; -Inic zeros(n_nic, n_rxns * 6 + n_int + n_mets + 1)];
b_p = [b_p; -ub(ind_nic)];
csense_p(end + 1:end + n_nic) = 'G';
%
%4) primal 6 (n_ic equations)
%   v(j) >= lb(j)
A_p = [A_p; Inic zeros(n_nic, n_rxns * 6 + n_int + n_mets + 1)];
b_p = [b_p; lb(ind_nic)];
csense_p(end + 1:end + n_nic) = 'G';

%DUAL PROBLEM
%1) dualcon1 (n_nic equations)
%   sum_i(lambda(i) * S(i,j))  +  deltam(j) -deltap(j) - y(j) =0
A_d = [];
b_d = [];
A_d = [A_d; zeros(n_nic, n_rxns) -Inic zeros(n_nic, n_rxns) zeros(n_nic, n_rxns) Inic -Inic zeros(n_nic, n_rxns) zeros(n_nic, n_rxns) S(:, ind_nic)' zeros(n_nic, 1)];
b_d = [b_d; zeros(n_nic, 1)];
csense_d(1:n_nic) = 'E';

%2) dualcon2 (n_ic equations)
%   sum_i(lambda(i) * S(i,j))  +  mu(j) - y(j) =0
A_d = [A_d; zeros(n_ic, n_rxns) -Iic Iic zeros(n_ic, n_rxns) zeros(n_ic, n_rxns) zeros(n_ic, n_rxns) zeros(n_ic, n_rxns) zeros(n_ic, n_rxns) S(:, ind_ic)' zeros(n_ic, 1)];
b_d = [b_d; zeros(n_ic, 1)];
csense_d(end + 1:end + n_ic) = 'E';

%OUTER PROBLEM
% bilevel_obj_up (1 equation)
%z=sum(w(j)-basemax(j) * y(j)) -> z -sum(w(j)) + sum(basemax(j) * y(j)) = 0 for all j in can y not in must and not in
%contraint_flux
A_bl = [zeros(1, n_rxns) (maxFluxesW.*sel_c_nm_nc)' zeros(1, n_rxns) -sel_c_nm_nc' zeros(1, 4 * n_rxns + n_mets) 1];
b_bl = 0;
csense_bl = 'E';

%primal_dual_up (1 equation)
% sum(w(j))  +  sum(b(j) * mu(j))  sum(deltap(j) * UB(j) - deltam(j) * LB(j)) = 0
A_bl = [A_bl; zeros(1, n_rxns + n_int) -sel_ic_b'  ones(1, n_rxns) -lb'.* sel_nic' ub'.*sel_nic' zeros(1, 2 * n_rxns + n_mets + 1)];
b_bl = [b_bl; 0];
csense_bl = [csense_bl, 'E'];

% bilevelcon0_up (1 equation)
%sum(w(j)-basemax(j) * y(j))> = 0
A_bl = [A_bl; zeros(1, n_rxns) (-maxFluxesW.*sel_c_nm_nc)' zeros(1, n_rxns) sel_c_nm_nc' zeros(1, 4 * n_rxns + n_mets + 1)];
b_bl = [b_bl; 0];
csense_bl = [csense_bl, 'G'];

% bilevelcon1 (j equations)
%w(j) - bigM*y(j) <= 0
A_bl = [A_bl; zeros(n_rxns, n_rxns) -bigM * speye(n_int) zeros(n_rxns, n_rxns) speye(n_rxns) zeros(n_rxns, 4 * n_rxns + n_mets + 1) ];
b_bl = [b_bl;zeros(n_rxns, 1)];
csense_bl(end + 1:end + n_rxns) = 'L';

% bilevelcon2 (j equations)
%w(j) + bigM * y(j) >= 0
A_bl = [A_bl; zeros(n_rxns, n_rxns) bigM * speye(n_rxns) zeros(n_rxns, n_rxns) speye(n_rxns) zeros(n_rxns, 4 * n_rxns + n_mets + 1) ];
b_bl = [b_bl;zeros(n_rxns, 1)];
csense_bl(end + 1:end + n_rxns) = 'G';

% bilevelcon3 (j equations)
%w(j) <= v(j)  +  bigM * (1-y(j))   ->    w(j) - v(j)  +  bigM * y(j)) <= bigM
A_bl = [A_bl; -speye(n_rxns) bigM * speye(n_rxns) zeros(n_rxns, n_rxns) speye(n_rxns) zeros(n_rxns, 4 * n_rxns + n_mets + 1) ];
b_bl = [b_bl;bigM * ones(n_rxns, 1)];
csense_bl(end + 1:end + n_rxns)  =  'L';

% bilevelcon4 (j equations)
%w(j) >= v(j) - bigM * (1-y(j))   ->    w(j) - v(j) - bigM * y(j)) >= -bigM
A_bl = [A_bl; -speye(n_rxns) -bigM * speye(n_rxns) zeros(n_rxns, n_rxns) speye(n_rxns) zeros(n_rxns, 4 * n_rxns + n_mets + 1) ];
b_bl = [b_bl; -bigM * ones(n_rxns, 1)];
csense_bl(end + 1:end + n_rxns)  =  'G';

% bilevelcon5 (1 equation)
%z >= 0.5
A_bl = [A_bl; zeros(1, 7 * n_rxns + n_int + n_mets) 1];
b_bl = [b_bl; 0.5];
csense_bl(end + 1)  =  'G';

%must_bin (1 equation)
% sum(y(j))=1
A_bl = [A_bl; zeros(1, n_rxns) sel_c_nm_nc' zeros(1, 6 * n_rxns + n_mets + 1) ];
b_bl = [b_bl; 1];
csense_bl = [csense_bl, 'E'];

%blocked_bin (1 equation)
% sum(y(j))=1
A_bl = [A_bl; zeros(1, n_rxns) ones(1, n_int) zeros(1, 6 * n_rxns + n_mets + 1)];
b_bl = [b_bl; 1];
csense_bl = [csense_bl, 'E'];

%Build bilevel matrices and vectors
A_bl_up = [A_bl; A_d; A_p];
b_bl_up = [b_bl; b_d; b_p];
csense_bl_up = [csense_bl, csense_d, csense_p];
c_bl_up = zeros(7 * n_rxns + n_int + n_mets + 1, 1); c_bl_up(end) = 1;

% Helper arrays for extracting solutions
sel_cont_sol = 1:n_rxns;
sel_int_sol = n_rxns + 1:n_rxns + n_int;

% Construct problem structure
bilevelMILPproblem.A = A_bl_up;
bilevelMILPproblem.b = b_bl_up;
bilevelMILPproblem.c = c_bl_up;
bilevelMILPproblem.csense = csense_bl_up;
bilevelMILPproblem.lb = lb_bl;
bilevelMILPproblem.ub = ub_bl;
bilevelMILPproblem.vartype = vartype_bl;
bilevelMILPproblem.contSolInd = sel_cont_sol;
bilevelMILPproblem.intSolInd = sel_int_sol;

% Initialize initial solution x0
bilevelMILPproblem.x0 = [];

% Maximize
bilevelMILPproblem.osense = -1;

% Set model for MILP problem
bilevelMILPproblem.model = model;

end


%==============================================================================
% This code is part of the Matlab-based toolbox
% FAIR - Flexible Algorithms for Image Registration.
% For details see
% - https://github.com/C4IR and
% - http://www.siam.org/books/fa06/
%==============================================================================
%
% Tutorial for FAIR: various distances and Multi-Level Parametric Image Registration
%
%   - data                 PETCT, Omega=(0,140)x(0,151), level=4:7, m=[128,128]
%   - viewer               viewImage2D
%   - interpolation        splineInter
%   - distance             {'SSD','NCC','MI','NGF'}
%   - transformation       affine2D
% see also E7_PETCT_MLPIR_ext
%==============================================================================

function E7(example)

if nargin == 0, example = 'PETCT';  end;
imagePath  = fullfile(FAIRpath,'temp',example);
switch example
  case 'HNSP'
    setup2DHNSPData;
    theta = 1;
  case 'MRIhead',
    setup3DMRIData;
    theta = 1;
  case 'PETCT',
    setup2DPETCTData;
    theta = 0;
  otherwise, return;
end
level = 6; omega = ML{level}.omega; m = ML{level}.m;

DM = {'SSD','NCC','MImex','NGFdot'};
viewImage('set',viewPara{:})
filename = fullfile(FAIRpath,'temp',sprintf('%s-%s-%s.mat',mfilename,'rotation',example));

% 2e1 MRIhead
imgModel('reset','imgModel','splineInter','regularizer','moments','theta',theta);
[T,R] = imgModel('coefficients',ML{level}.T,ML{level}.R,omega);
xc    = getCellCenteredGrid(omega,m);
xc    = reshape(xc,[],2);
Tin   = imgModel(T,omega,xc);
Rin   = imgModel(R,omega,xc);

%%  plots
k = figureh(1); clf; set(k,'color','w');
subplot(1,2,1); viewImage(Tin,omega,m);
subplot(1,2,2); viewImage(Rin,omega,m);
FAIRpause(1);

center = (omega(2:2:end)-omega(1:2:end))'/2;
trafo('reset','trafo','rotation2D','c',center);
trafo('w0');
if ~exist(filename,'file'),
  w = pi/4*linspace(-1,1,101)';
  save(filename,'w','DM');
else
  clear DM*
  load(filename)
end;

edge = 100;

for k=1:length(DM),
  variable = ['DM',DM{k}];
  var = whos('-file',filename);
  j = find(strcmp({var(:).name},variable)==1);

  if isempty(j),
    fprintf('============== %s ====================\n\n',variable)
    disp([variable,'=dm;']);
    dm = zeros(size(w));
    for j=1:length(w),
      Y  = trafo(w(j),xc(:));
      Tc = imgModel(T,omega,Y);
      dm(j) = feval(DM{k},Tc,Rin,omega,m,'edge',edge);
      if j== 1,
        figureh(3);
        ph = viewImage(Tc,omega,m);
      else
        set(ph,'cdata',reshape(Tc,m)'); drawnow
        FAIRpause(1/100)
      end;
      title(sprintf('%d/%d:%d/%d',k,length(DM),j,numel(w)));
    end;
    eval([variable,'=dm;']);
    save(filename,'-append',variable);
  end;
end;

%%
load(filename)

filename = fullfile(FAIRpath,'temp',...
  sprintf('%s-%s-%s.mat',mfilename,'rotation',example));

for k=1:4,
  variable = ['DM',DM{k}];
  eval(['dm=',variable,';']);
  dm = dm(1:length(w));
  [wOpt,j] = min(dm);
  figureh(k); clf; set(k,'color','w');
  ph = plot(w,dm,'-',w(j),dm(j),'*');
  set(ph,'linewidth',2,'color','k','markersize',20);
  set(gca,'fontsize',30);
  a = max(dm)-0.2*(max(dm)-min(dm));
  th = text(w(j),a,['$w^*=',num2str(w(j)),'$']);
  set(th,'fontsize',30,'interpreter','latex','horizontalalignment','center');
end;

%% finish
filename = fullfile(FAIRpath,'temp',...
  sprintf('%s-%s-%s.mat',mfilename,'translation',example));

trafo('reset','trafo','translation2D');
if ~exist(filename,'file'),
  [w1,w2] = ndgrid(0.1*(omega(2)-omega(1))*linspace(-1,1,21),...
                   0.2*(omega(4)-omega(3))*linspace(-1,1,21));
  save(filename,'w1','w2','DM');
else
  clear DM*
  load(filename)
end;

for k=1:length(DM),
  variable = ['DM',DM{k}];
  disp([variable,'=dm;']);

  var = whos('-file',filename);
  j = find(strcmp({var(:).name},variable)==1);

  if isempty(j),
    fprintf('============== %s ====================\n\n',variable)
    dm = zeros(size(w1));
    for j=1:numel(w1),
      Y = trafo([w1(j);w2(j)],xc(:));
      Tc = imgModel(T,omega,Y);
      dm(j) = feval(DM{k},Tc,Rin,omega,m,'edge',edge);
      if j== 1,
        figureh(3);
        ph = viewImage(Tc,omega,m);
      else
        set(ph,'cdata',reshape(Tc,m)'); drawnow
        pause(1/100)
      end;
      title(sprintf('%d/%d:%d/%d',k,length(DM),j,numel(w1)));
    end;
    eval([variable,'=dm;']);
    save(filename,'-append',variable);
  end;
end;

load(filename)

%figureh(1); close(1); figureh(1); clf; set(1,'position',position(800),'color','w');
%figureh(2); close(2); figureh(2); clf; set(2,'position',position(800),'color','w');
shift = [3000,0.5,-1,0.08];
Ztick = {[0:1000:4000],[0:0.1:0.3],[-1:0:1],[0:0.02:0.06]};

for k=1:4,

  variable = ['DM',DM{k}];
  eval(['dm=',variable,';']);

  fig = figureh(10+k);
  if ~isnumeric(fig), fig = fig.Number; end;
  close(fig); figureh(fig); set(fig,'color','w');
  ph=mesh(w1,w2,dm-shift(k)); grid off;
  set(gca,'fontsize',30);
  view(-135,25);
  set(ph,'linewidth',2)

  fig = figureh(20+k);
  if ~isnumeric(fig), fig = fig.Number; end;
  close(fig); figureh(fig); clf; set(fig,'color','w');
  contour(w1,w2,dm,10,'linewidth',2);
  set(gca,'fontsize',30);
end;
%==============================================================================