function [sig,delay_offset] = delayline(sig,dt,weight,conf)
%DELAYLINE (fractional) delay line with weights
%
%   Usage: [sig,delay_offset] = delayline(sig,dt,weight,conf)
%
%   Input parameter:
%       sig     - input signal (vector), can be in the form of [N C], or
%                 [M C N], where
%                     N ... samples
%                     C ... channels (most probably 2)
%                     M ... number of measurements
%                 If the input is [M C N], the length of dt and weight has to be
%                 1 or M*C. In the last case the first M entries in dt are
%                 applied to the first channel and so on.
%       dt      - delay / s
%       weight  - amplitude weighting factor
%       conf    - configuration struct (see SFS_config).
%
%   Output parameter:
%       sig             - delayed signal
%       delay_offset    - additional delay / s
%                         This is added by the fractional delayline filters to
%                         all channels. For integer delays this is 0.
%
%   DELAYLINE(sig,dt,weight,conf) implementes a delayline, that delays the given
%   signal by dt samples and applies an amplitude weighting factor. The delay is
%   implemented as integer delay or fractional delay filter, see delayline
%   section in SFS_config for possible settings. As default setting an integer
%   delayline is used.
%
%   See also: get_ir, driving_function_imp_wfs

%*****************************************************************************
% The MIT License (MIT)                                                      *
%                                                                            *
% Copyright (c) 2010-2019 SFS Toolbox Developers                             *
%                                                                            *
% Permission is hereby granted,  free of charge,  to any person  obtaining a *
% copy of this software and associated documentation files (the "Software"), *
% to deal in the Software without  restriction, including without limitation *
% the rights  to use, copy, modify, merge,  publish, distribute, sublicense, *
% and/or  sell copies of  the Software,  and to permit  persons to whom  the *
% Software is furnished to do so, subject to the following conditions:       *
%                                                                            *
% The above copyright notice and this permission notice shall be included in *
% all copies or substantial portions of the Software.                        *
%                                                                            *
% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
% IMPLIED, INCLUDING BUT  NOT LIMITED TO THE  WARRANTIES OF MERCHANTABILITY, *
% FITNESS  FOR A PARTICULAR  PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL *
% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
% LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT, TORT  OR OTHERWISE, ARISING *
% FROM,  OUT OF  OR IN  CONNECTION  WITH THE  SOFTWARE OR  THE USE  OR OTHER *
% DEALINGS IN THE SOFTWARE.                                                  *
%                                                                            *
% The SFS Toolbox  allows to simulate and  investigate sound field synthesis *
% methods like wave field synthesis or higher order ambisonics.              *
%                                                                            *
% https://sfs.readthedocs.io                            sfstoolbox@gmail.com *
%*****************************************************************************

%% ===== Configuration ===================================================
% Check for old configuration
if isfield(conf, 'usefracdelay')
    error(['%s: conf.usefracdelay is deprecated, please use conf.delayline', ...
           ' instead. See SFS_config for details.'],upper(mfilename));
end
fs = conf.fs;
delay = conf.delayline;

%% ===== Preparation =====================================================
% --- Reshape signals ---
% Check if the signal is an impulse response given in SOFA conventions [M C N],
% or in usual [N C] convention, where
% M ... number of measurements
% C ... number of channels
% N ... number of samples
if ndims(sig)==3
    [M,C,samples] = size(sig);
    channels = M * C;
    % Reshape [M C N] => [N C*M], this will be redone at the end of the function
    sig = reshape(sig,[channels,samples])';
    reshaped = true;
else
    % Assume standard format [N C]
    [samples,channels] = size(sig);
    reshaped = false;
end

%% ===== Resampling ======================================================
% The resampling is applied independently from the actual fractional/integer
% delay handling performed in the next step. The resampling is redone at the end
% of the file.
% If resampling is used together with the integer delay filter, this is already
% a usage of fractional delay due to the upsampling.
%
switch delay.resampling
case 'none'
    rfactor = 1.0;
    delay_offset = 0;
case 'matlab'
    rfactor = delay.resamplingfactor;
    delay_offset = 0;
    sig = resample(sig,rfactor,1);
case 'pm'
    % === Parks-McClellan linear phase FIR filter ===
    rfactor = delay.resamplingfactor;
    rfilt = pm_filter(rfactor*delay.resamplingorder, 0.9/rfactor, ...
      1/rfactor);
    delay_offset = delay.resamplingorder*rfactor/2;

    sig = reshape(sig,1,channels*samples);
    sig = [sig; zeros(rfactor-1,channels*samples)];
    sig = reshape(sig,rfactor*samples,channels);

    sig = convolution(rfactor*rfilt, sig);
otherwise
    error('%s: "%s": unknown resampling method',upper(mfilename), ...
        delay.resampling);
end

%% ===== Expansion of signals, delays or weights =========================
% --- Expand channels
if channels==1
    channels = max(length(dt),length(weight));
    sig = repmat(sig,[1 channels]);
end

% --- Expand dt and weight ---
% If only single valued time delay and weight is given, create vectors
if channels>1 && length(dt)==1, dt=repmat(dt,[1 channels]); end
if channels>1 && length(weight)==1, weight=repmat(weight,[1 channels]); end

%% ===== Conversion to integer delay =====================================
dt = dt.*rfactor.*fs;  % resampled delay / samples
samples = rfactor.*samples;  % length of resampled signals
switch delay.filter
case 'integer'
    % === Integer delays ===
    idt = round(dt);  % round to nearest integer delay
    delay_offset = delay_offset + 0;
case 'zoh'
    % === Zero-order hold ===
    idt = ceil(dt);  % round to next larger integer delay
    delay_offset = delay_offset + 0;
case 'lagrange'
    % === Lagrange polynomial interpolator ===
    if iseven(delay.filterorder)
        idt = round(dt);  % round delay for even order
    else
        idt = floor(dt);  % floor delay for odd order
    end
    fdt = dt - idt;  % fractional part of delays
    b = lagrange_filter(delay.filterorder,fdt);
    a = ones(1,channels);
    delay_offset = delay_offset + floor(delay.filterorder/2);
case 'thiran'
    % === Thiran's allpass filter for maximally flat group delay ===
    idt = round(dt);  % integer part of delays
    fdt = dt - idt;  % fractional part of delays
    [b,a] = thiran_filter(delay.filterorder,fdt);
    delay_offset = delay_offset + delay.filterorder;
case 'least_squares'
    % ==== Least squares interpolation filter ===
    idt = floor(dt);  % integer part of delays
    fdt = dt - idt;  % fractional part of delays
    b = zeros(delay.filterorder+1,channels);
    for ii=1:channels
        b(:,ii) = general_least_squares(delay.filterorder+1,fdt(ii),0.90);
    end
    a = ones(1,channels);
    delay_offset = delay_offset + floor(delay.filterorder/2);
case 'farrow'
    % === Farrow-structure ===
    % Based on the assumption, that each coefficient h(n) of the fractional
    % delay filter can be expressed as a polynomial in d (frac. delay), i.e.
    %            __
    %           \  NPol
    % h_d(n) ~=  >      c_m(n) d^m
    %           /__m=0
    %
    % For some Filter design methods, e.g. Lagrange Interpolators, this is
    % perfectly possible. For other, a uniform grid of test delays d_q is
    % used to fit the polynomials to the desired coefficient(n) find a set
    % polynomial which approximates each coefficient of the desired filter.
    % This structure allows to perform the convolution independently from
    % the delay and reuse the results of the filter for different delays.
    %                           __
    %                          \  NPol
    % y(n) = h_d(n) * x(n) ~=   >      ( c_m(n)*x(n) ) d^m
    %                          /__m=0
    %
    % The above representation shows that the convolution of the input
    % signal x can be performed by first convolving c_m and x and
    % incorporating the delay d afterwards.
    %
    % number of parallel filters, i.e. order of polynomial + 1
    % Nfilter = delay.filternumber;
    to_be_implemented(mfilename);
otherwise
    error('%s: \"%s\" is an unknown delayline filter', ...
        upper(mfilename),delay.filter);
end
% Apply filter if needed
if exist('a','var') && exist('b','var')
    for ii=1:channels
        sig(:,ii) = filter(b(:,ii),a(:,ii),sig(:,ii));
    end
end

%% ===== Integer delayline ===============================================
% Handling of too long delay values (returns vector of zeros)
idt(abs(idt)>samples) = samples;
% Handle positive or negative delays
for ii=1:channels
    if idt(ii)>=0
        sig(:,ii) = [zeros(idt(ii),1); weight(ii)*sig(1:end-idt(ii),ii)];
    else
        sig(:,ii) = [weight(ii)*sig(-idt(ii)+1:end,ii); zeros(-idt(ii),1)];
    end
end

%% ===== Postprocessing ==================================================
% --- Downsampling ---
if rfactor~=1
    sig = sig(1:rfactor:samples,:);
    delay_offset = delay_offset ./ rfactor;
end
% --- Undo reshape ---
% [N M*C] => [M C N]
if reshaped
    % C might have changed due to replication of single-channel input
    sig = reshape(sig',M,[],size(sig,1));
end
% --- delay_offset in seconds ---
delay_offset = delay_offset / fs;


function [gp, varargout] = gp_optim(gp, x, y, varargin)
%GP_OPTIM  Optimize paramaters of a Gaussian process
%
%  Description
%    GP = GP_OPTIM(GP, X, Y, OPTIONS) optimises the parameters of a
%    GP structure given matrix X of training inputs and vector
%    Y of training targets.
%
%    [GP, OUTPUT1, OUTPUT2, ...] = GP_OPTIM(GP, X, Y, OPTIONS)
%    optionally returns outputs of the optimization function.
%
%    OPTIONS is optional parameter-value pair
%      z      - optional observed quantity in triplet (x_i,y_i,z_i)
%               Some likelihoods may use this. For example, in case of
%               Poisson likelihood we have z_i=E_i, that is, expected
%               value for ith case.
%      optimf - function handle for an optimization function, which is
%               assumed to have similar input and output arguments
%               as usual fmin*-functions. Default is @fminscg.
%      opt    - options structure for the minimization function.
%               Use optimset to set these options. By default options
%               'GradObj' is 'on', 'LargeScale' is 'off'.
%      loss   - 'e' to minimize the marginal posterior energy (default) or
%               'loo' to minimize the negative leave-one-out lpd
%               'kfcv' to minimize the negative k-fold-cv lpd
%               'waic' to minimize the WAIC loss
%               only 'e' and 'loo' with Gaussian likelihood have gradients
%      k      - number of folds in kfcv
%
%  See also
%    GP_SET, GP_E, GP_G, GP_EG, FMINSCG, FMINLBFGS, OPTIMSET, DEMO_REGRESSION*
%
% Copyright (c) 2010-2012 Aki Vehtari

% This software is distributed under the GNU General Public
% License (version 3 or later); please refer to the file
% License.txt, included with the software, for details.

ip=inputParser;
ip.FunctionName = 'GP_OPTIM';
ip.addRequired('gp',@(x) isstruct(x) || isempty(x));
ip.addRequired('x', @(x) ~isempty(x) && isreal(x) && all(isfinite(x(:))))
ip.addRequired('y', @(x) ~isempty(x) && isreal(x) && all(isfinite(x(:))))
ip.addParamValue('z', [], @(x) isreal(x) && all(isfinite(x(:))))
ip.addParamValue('optimf', @fminscg, @(x) isa(x,'function_handle'))
ip.addParamValue('opt', [], @isstruct)
ip.addParamValue('loss', 'e', @(x) ismember(lower(x),{'e', 'loo', 'kfcv', 'waic' 'waic' 'waicv' 'waicg'}))
ip.addParamValue('k', 10, @(x) isreal(x) && isscalar(x) && isfinite(x) && x>0)
ip.parse(gp, x, y, varargin{:});
if isempty(gp)
  gp=gp_set();
end
if isempty(gp_pak(gp))
  % nothing to optimize
  return
end
z=ip.Results.z;
optimf=ip.Results.optimf;
opt=ip.Results.opt;
loss=ip.Results.loss;
k=ip.Results.k;

switch lower(loss)
  case 'e'
    fh_eg=@(ww) gp_eg(ww, gp, x, y, 'z', z);
    optdefault=struct('GradObj','on','LargeScale','off');
  case 'loo'
    fh_eg=@(ww) gp_looeg(ww, gp, x, y, 'z', z);
    if isfield(gp.lik.fh,'trcov') || isequal(gp.latent_method, 'EP')
      optdefault=struct('GradObj','on','LargeScale','off');
    else
      % Laplace-LOO does not have yet gradients
      optdefault=struct('Algorithm','interior-point');
      if ismember('optimf',ip.UsingDefaults)
        optimf=@fmincon;
      end
    end
  case 'kfcv'
    % kfcv does not have yet gradients
    fh_eg=@(ww) gp_kfcve(ww, gp, x, y, 'z', z, 'k', k);
    optdefault=struct('Algorithm','interior-point');
    if ismember('optimf',ip.UsingDefaults)
      optimf=@fmincon;
    end
  case {'waic' 'waicv'}
    % waic does not have yet gradients
    fh_eg=@(ww) -gp_waic(gp_unpak(gp,ww), x, y, 'z', z);
    optdefault=struct('Algorithm','interior-point');
    if ismember('optimf',ip.UsingDefaults)
      optimf=@fmincon;
    end
  case 'waicg'
    % waic does not have yet gradients
    fh_eg=@(ww) -gp_waic(gp_unpak(gp,ww), x, y, 'z', z, 'method', 'G');
    optdefault=struct('Algorithm','interior-point');
    if ismember('optimf',ip.UsingDefaults)
      optimf=@fmincon;
    end
end
opt=setOpt(optdefault,opt);
w=gp_pak(gp);
if isequal(lower(loss),'e') || (isequal(lower(loss),'loo')) && (isfield(gp.lik.fh,'trcov') || isequal(gp.latent_method, 'EP'))
  switch nargout
    case 6
      [w,fval,exitflag,output,grad,hessian] = optimf(fh_eg, w, opt);
      varargout={fval,exitflag,output,grad,hessian};
    case 5
      [w,fval,exitflag,output,grad] = optimf(fh_eg, w, opt);
      varargout={fval,exitflag,output,grad};
    case 4
      [w,fval,exitflag,output] = optimf(fh_eg, w, opt);
      varargout={fval,exitflag,output};
    case 3
      [w,fval,exitflag] = optimf(fh_eg, w, opt);
      varargout={fval,exitflag};
    case 2
      [w,fval] = optimf(fh_eg, w, opt);
      varargout={fval};
    case 1
      w = optimf(fh_eg, w, opt);
      varargout={};
  end
else
  lb=repmat(-8,size(w));
  ub=repmat(10,size(w));
  switch nargout
    case 6
      [w,fval,exitflag,output,grad,hessian] = optimf(fh_eg, w, [], [], [], [], lb, ub, [], opt);
      varargout={fval,exitflag,output,grad,hessian};
    case 5
      [w,fval,exitflag,output,grad] = optimf(fh_eg, w, [], [], [], [], lb, ub, [], opt);
      varargout={fval,exitflag,output,grad};
    case 4
      [w,fval,exitflag,output] = optimf(fh_eg, w, [], [], [], [], lb, ub, [], opt);
      varargout={fval,exitflag,output};
    case 3
      [w,fval,exitflag] = optimf(fh_eg, w, [], [], [], [], lb, ub, [], opt);
      varargout={fval,exitflag};
    case 2
      [w,fval] = optimf(fh_eg, w, [], [], [], [], lb, ub, [], opt);
      varargout={fval};
    case 1
      w = optimf(fh_eg, w, [], [], [], [], lb, ub, [], opt);
      varargout={};
  end
end
gp=gp_unpak(gp,w);
end

function opt=setOpt(optdefault, opt)
  % Set default options
  opttmp=optimset(optdefault,opt);

  % Set some additional options for @fminscg
  if isfield(opt,'lambda')
    opttmp.lambda=opt.lambda;
  end
  if isfield(opt,'lambdalim')
    opttmp.lambdalim=opt.lambdalim;
  end
  opt=opttmp;
end


%kkrect2pol 'Convert (real, imaginary) to (r, theta) Coordinates'
% This MatLab function was automatically generated by a converter (KhorosToMatLab) from the Khoros krect2pol.pane file
%
% Parameters:
% InputFile: i 'Input ', required: 'Input data object'
% OutputFile: o 'Output', required: 'Resulting output data object'
%
% Example: o = kkrect2pol(i, {'i','';'o',''})
%
% Khoros helpfile follows below:
%
%  PROGRAM
% krect2pol - Convert (real, imaginary) to (r, theta) Coordinates
%
%  DESCRIPTION
% The "Rect. to Polar" operator converts data from the rectangular to the
% polar coordinate system for each complex data point in the input data
% object, \fBInput".  The conversion from rectangular to polar will result
% in angles measured in radians.  The data type of
% the output data object, \fBOutput", will be the same as the data type
% of \fBInput".
%
% Executing "Rect. to Polar" runs the program \fIkcmplx\fP
% with the -r2p flag.
%
%  "Map Data" 5
% .cI $DATAMANIP/repos/shared/man/sections/map_1input
%
%  "Validity Mask" 5
% .cI $DATAMANIP/repos/shared/man/sections/mask_1input
%
%  "Location and Time Data" 5
% .cI $DATAMANIP/repos/shared/man/sections/loc_and_time_1input
%
%
%
%  EXAMPLES
%
%  "SEE ALSO"
% DATAMANIP::kcmplx, DATAMANIP::kpol2rect
%
%  RESTRICTIONS
%
%  REFERENCES
%
%  COPYRIGHT
% Copyright (C) 1993 - 1997, Khoral Research, Inc. ("KRI")  All rights reserved.
%

function varargout = kkrect2pol(varargin)
if nargin ==0
  Inputs={};arglist={'',''};
elseif nargin ==1
  Inputs=varargin{1};arglist={'',''};
elseif nargin ==2
  Inputs=varargin{1}; arglist=varargin{2};
else error('Usage: [out1,..] = kkrect2pol(Inputs,arglist).');
end
if size(arglist,2)~=2
  error('arglist must be of form {''ParameterTag1'',value1;''ParameterTag2'',value2}')
 end
narglist={'i', '__input';'o', '__output'};
maxval={0,0};
minval={0,0};
istoggle=[0,0];
was_set=istoggle * 0;
paramtype={'InputFile','OutputFile'};
% identify the input arrays and assign them to the arguments as stated by the user
if ~iscell(Inputs)
Inputs = {Inputs};
end
NumReqOutputs=1; nextinput=1; nextoutput=1;
  for ii=1:size(arglist,1)
  wasmatched=0;
  for jj=1:size(narglist,1)
   if strcmp(arglist{ii,1},narglist{jj,1})  % a given argument was matched to the possible arguments
     wasmatched = 1;
     was_set(jj) = 1;
     if strcmp(narglist{jj,2}, '__input')
      if (nextinput > length(Inputs))
        error(['Input ' narglist{jj,1} ' has no corresponding input!']);
      end
      narglist{jj,2} = 'OK_in';
      nextinput = nextinput + 1;
     elseif strcmp(narglist{jj,2}, '__output')
      if (nextoutput > nargout)
        error(['Output nr. ' narglist{jj,1} ' is not present in the assignment list of outputs !']);
      end
      if (isempty(arglist{ii,2}))
        narglist{jj,2} = 'OK_out';
      else
        narglist{jj,2} = arglist{ii,2};
      end

      nextoutput = nextoutput + 1;
      if (minval{jj} == 0)
         NumReqOutputs = NumReqOutputs - 1;
      end
     elseif isstr(arglist{ii,2})
      narglist{jj,2} = arglist{ii,2};
     else
        if strcmp(paramtype{jj}, 'Integer') & (round(arglist{ii,2}) ~= arglist{ii,2})
            error(['Argument ' arglist{ii,1} ' is of integer type but non-integer number ' arglist{ii,2} ' was supplied']);
        end
        if (minval{jj} ~= 0 | maxval{jj} ~= 0)
          if (minval{jj} == 1 & maxval{jj} == 1 & arglist{ii,2} < 0)
            error(['Argument ' arglist{ii,1} ' must be bigger or equal to zero!']);
          elseif (minval{jj} == -1 & maxval{jj} == -1 & arglist{ii,2} > 0)
            error(['Argument ' arglist{ii,1} ' must be smaller or equal to zero!']);
          elseif (minval{jj} == 2 & maxval{jj} == 2 & arglist{ii,2} <= 0)
            error(['Argument ' arglist{ii,1} ' must be bigger than zero!']);
          elseif (minval{jj} == -2 & maxval{jj} == -2 & arglist{ii,2} >= 0)
            error(['Argument ' arglist{ii,1} ' must be smaller than zero!']);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} < minval{jj})
            error(['Argument ' arglist{ii,1} ' must be bigger than ' num2str(minval{jj})]);
          elseif (minval{jj} ~= maxval{jj} & arglist{ii,2} > maxval{jj})
            error(['Argument ' arglist{ii,1} ' must be smaller than ' num2str(maxval{jj})]);
          end
        end
     end
     if ~strcmp(narglist{jj,2},'OK_out') &  ~strcmp(narglist{jj,2},'OK_in')
       narglist{jj,2} = arglist{ii,2};
     end
   end
   end
   if (wasmatched == 0 & ~strcmp(arglist{ii,1},''))
        error(['Argument ' arglist{ii,1} ' is not a valid argument for this function']);
   end
end
% match the remaining inputs/outputs to the unused arguments and test for missing required inputs
 for jj=1:size(narglist,1)
     if  strcmp(paramtype{jj}, 'Toggle')
        if (narglist{jj,2} ==0)
          narglist{jj,1} = '';
        end;
        narglist{jj,2} = '';
     end;
     if  ~strcmp(narglist{jj,2},'__input') && ~strcmp(narglist{jj,2},'__output') && istoggle(jj) && ~ was_set(jj)
          narglist{jj,1} = '';
          narglist{jj,2} = '';
     end;
     if strcmp(narglist{jj,2}, '__input')
      if (minval{jj} == 0)  % meaning this input is required
        if (nextinput > size(Inputs))
           error(['Required input ' narglist{jj,1} ' has no corresponding input in the list!']);
        else
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        end
      else  % this is an optional input
        if (nextinput <= length(Inputs))
          narglist{jj,2} = 'OK_in';
          nextinput = nextinput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end;
     else
     if strcmp(narglist{jj,2}, '__output')
      if (minval{jj} == 0) % this is a required output
        if (nextoutput > nargout & nargout > 1)
           error(['Required output ' narglist{jj,1} ' is not stated in the assignment list!']);
        else
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
          NumReqOutputs = NumReqOutputs-1;
        end
      else % this is an optional output
        if (nargout - nextoutput >= NumReqOutputs)
          narglist{jj,2} = 'OK_out';
          nextoutput = nextoutput + 1;
        else
          narglist{jj,1} = '';
          narglist{jj,2} = '';
        end;
      end
     end
  end
end
if nargout
   varargout = cell(1,nargout);
else
  varargout = cell(1,1);
end
global KhorosRoot
if exist('KhorosRoot') && ~isempty(KhorosRoot)
w=['"' KhorosRoot];
else
if ispc
  w='"C:\Program Files\dip\khorosBin\';
else
[s,w] = system('which cantata');
w=['"' w(1:end-8)];
end
end
[varargout{:}]=callKhoros([w 'kcmplx"  -r2p'],Inputs,narglist);


function [scd] = ft_scalpcurrentdensity(cfg, data)

% FT_SCALPCURRENTDENSITY computes an estimate of the SCD using the
% second-order derivative (the surface Laplacian) of the EEG potential
% distribution
%
% The relation between the surface Laplacian and the SCD is explained
% in more detail on http://tinyurl.com/ptovowl.
%
% Use as
%   [data] = ft_scalpcurrentdensity(cfg, data)
% or
%   [timelock] = ft_scalpcurrentdensity(cfg, timelock)
% where the input data is obtained from FT_PREPROCESSING or from
% FT_TIMELOCKANALYSIS. The output data has the same format as the input
% and can be used in combination with most other FieldTrip functions
% such as FT_FREQNALYSIS or FT_TOPOPLOTER.
%
% The configuration should contain
%   cfg.method       = 'finite' for finite-difference method or
%                      'spline' for spherical spline method
%                      'hjorth' for Hjorth approximation method
%   cfg.elec         = structure with electrode positions or filename, see FT_READ_SENS
%   cfg.trials       = 'all' or a selection given as a 1xN vector (default = 'all')
%   cfg.feedback     = string, 'no', 'text', 'textbar', 'gui' (default = 'text')
%
% The finite method require the following
%   cfg.conductivity = conductivity of the skin (default = 0.33 S/m)
%
% The spline and finite method require the following
%   cfg.conductivity = conductivity of the skin (default = 0.33 S/m)
%   cfg.lambda       = regularization parameter (default = 1e-05)
%   cfg.order        = order of the splines (default = 4)
%   cfg.degree       = degree of legendre polynomials (default for
%                       <=32 electrodes  = 9,
%                       <=64 electrodes  = 14,
%                       <=128 electrodes = 20,
%                       else             = 32
%
% The hjorth method requires the following
%   cfg.neighbours   = neighbourhood structure, see FT_PREPARE_NEIGHBOURS
%
% For the spline method you can specify the following
%   cfg.badchannel      = cell-array, see FT_CHANNELSELECTION for details (default = [])
%
% Note that the skin conductivity, electrode dimensions and the potential
% all have to be expressed in the same SI units, otherwise the units of
% the SCD values are not scaled correctly. The spatial distribution still
% will be correct.
%
% To facilitate data-handling and distributed computing you can use
%   cfg.inputfile   =  ...
%   cfg.outputfile  =  ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure.
%
% The 'finite' method implements
%   TF Oostendorp, A van Oosterom; The surface Laplacian of the potential:
%   theory and application. IEEE Trans Biomed Eng, 43(4): 394-405, 1996.
%   G Huiskamp; Difference formulas for the surface Laplacian on a
%   triangulated sphere. Journal of Computational Physics, 2(95): 477-496,
%   1991.
%
% The 'spline' method implements
%   F. Perrin, J. Pernier, O. Bertrand, and J. F. Echallier.
%   Spherical splines for scalp potential and curernt density mapping.
%   Electroencephalogr Clin Neurophysiol, 72:184-187, 1989
% including their corrections in
%   F. Perrin, J. Pernier, O. Bertrand, and J. F. Echallier.
%   Corrigenda: EEG 02274, Electroencephalography and Clinical
%   Neurophysiology 76:565.
%
% The 'hjorth' method implements
%   B. Hjort; An on-line transformation of EEG scalp potentials into
%   orthogonal source derivation. Electroencephalography and Clinical
%   Neurophysiology 39:526-530, 1975.
%
% See also FT_PREPROCESSING, FT_TIMELOCKANALYSIS, FT_FREQNALYSIS, FT_TOPOPLOTER.

% Copyright (C) 2004-2012, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip 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.
%
%    FieldTrip 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 FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin   = nargin;
ft_nargout  = nargout;

% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble debug
ft_preamble loadvar data
ft_preamble provenance data

% the ft_abort variable is set to true or false in ft_preamble_init
if ft_abort
  return
end

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'forbidden',  {'trial'}); % prevent accidental typos, see issue 1729

% set the defaults
cfg.method          = ft_getopt(cfg, 'method',       'spline');
cfg.conductivity    = ft_getopt(cfg, 'conductivity', 0.33); % in S/m
cfg.trials          = ft_getopt(cfg, 'trials',       'all', 1);
cfg.feedback        = ft_getopt(cfg, 'feedback',     'text');
cfg.badchannel      = ft_getopt(cfg, 'badchannel',     {});

switch cfg.method
  case 'hjorth'
    cfg = ft_checkconfig(cfg, 'required', {'neighbours'});
  case 'spline'
    cfg.lambda  = ft_getopt(cfg, 'lambda', 1e-5);
    cfg.order   = ft_getopt(cfg, 'order', 4);
    cfg.degree  = ft_getopt(cfg, 'degree', []);

    if isempty(cfg.degree) % determines degree of Legendre polynomials bases on number of electrodes
      nchan = numel(data.label);
      if nchan<=32
        cfg.degree = 9;
      elseif nchan<=64
        cfg.degree = 14;
      elseif nchan<=128
        cfg.degree = 20;
      else
        cfg.degree = 32;
      end
    end
  otherwise
    cfg = ft_checkconfig(cfg); % perform a simple consistency check
end

% store original datatype
dtype = ft_datatype(data);

% check if the input data is valid for this function
data = ft_checkdata(data, 'datatype', 'raw', 'feedback', 'yes', 'ismeg', []);

% get the electrode positions
tmpcfg = cfg;
tmpcfg.senstype = 'EEG';
elec = ft_fetch_sens(tmpcfg, data);

% select channels and trials of interest
tmpcfg = keepfields(cfg, {'trials', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
tmpcfg.channel = elec.label;
data = ft_selectdata(tmpcfg, data);
% restore the provenance information
[cfg, data] = rollback_provenance(cfg, data);

Ntrials = numel(data.trial);

if isempty(cfg.badchannel)
  % check if the first sample of the first trial contains NaNs; if so treat it as a bad channel
  cfg.badchannel = ft_channelselection(find(isnan(data.trial{1}(:,1))), data.label);
end

% match the order of the data channels with the channel positions, order them according to the data
[datindx, elecindx] = match_str(data.label, elec.label);
[goodindx, tmp]     = match_str(data.label, setdiff(data.label, cfg.badchannel, 'stable'));

if ~isempty(cfg.badchannel)
  ft_info('detected channel %s as bad\n', cfg.badchannel{:});
  tmpcfg         = [];
  tmpcfg.channel = data.label(goodindx);
  data           = ft_selectdata(tmpcfg, data);
end

allchanpos  = elec.chanpos(elecindx,:); % the position of all channels, ordered according to the data
goodchanpos = allchanpos(goodindx,:);   % the position of good channels

% compute SCD for each trial
if strcmp(cfg.method, 'spline')
  fprintf('Checking spherical fit... ');
  [c, r] = fitsphere(allchanpos);
  d = allchanpos - repmat(c, size(allchanpos,1), 1);
  d = sqrt(sum(d.^2, 2));
  d = mean(abs(d) / r);
  if abs(d-1) > 0.1
    ft_warning('bad spherical fit (residual: %.2f%%). The interpolation will be inaccurate.', 100*(d-1));
  elseif abs(d-1) < 0.01
    fprintf('perfect spherical fit (residual: %.1f%%)\n', 100*(d-1));
  else
    fprintf('good spherical fit (residual: %.1f%%)\n', 100*(d-1));
  end
  % Builds the spatial filter only once.
  fprintf('Calculating the filter to build the SCD.\n');
  [WVo, WLo] = sphsplint(goodchanpos, allchanpos, cfg.order, cfg.degree, cfg.lambda);
  % Creates a montage to apply the spatial filter.
  montage.tra      = WLo;
  montage.labelold = elec.label(elecindx(goodindx));
  montage.labelnew = elec.label(elecindx);
  % Applies the montage to both the data and electrode definition
  scd  = ft_apply_montage(data, montage);
  elec = ft_apply_montage(elec, montage);

elseif strcmp(cfg.method, 'finite')
  if ~isempty(cfg.badchannel)
    ft_error('the method "%s" does not support the specification of bad channels', cfg.method);
  end
  % the finite difference approach requires a triangulation
  prj = elproj(allchanpos);
  tri = delaunay(prj(:,1), prj(:,2));
  % the new electrode montage only needs to be computed once for all trials
  montage.tra = lapcal(allchanpos, tri);
  montage.labelold = data.label;
  montage.labelnew = data.label;
  % apply the montage to the data, also update the electrode definition
  scd  = ft_apply_montage(data, montage);
  elec = ft_apply_montage(elec, montage);

elseif strcmp(cfg.method, 'hjorth')
  if ~isempty(cfg.badchannel)
    ft_error('the method "%s" does not support the specification of bad channels', cfg.method);
  end
  % convert the neighbourhood structure into a montage
  labelnew = {};
  labelold = {};
  for i=1:length(cfg.neighbours)
    labelnew = cat(2, labelnew, cfg.neighbours(i).label);
    labelold = cat(2, labelold, cfg.neighbours(i).neighblabel(:)');
  end
  labelold = cat(2, labelnew, labelold);
  labelold = unique(labelold);
  tra = zeros(length(labelnew), length(labelold));
  for i=1:length(cfg.neighbours)
    thischan   = match_str(labelold, cfg.neighbours(i).label);
    thisneighb = match_str(labelold, cfg.neighbours(i).neighblabel);
    tra(i, thischan) = 1;
    tra(i, thisneighb) = -1/length(thisneighb);
  end
  % combine it in a montage
  montage.tra = tra;
  montage.labelold = labelold;
  montage.labelnew = labelnew;
  % apply the montage to the data, also update the electrode definition
  scd  = ft_apply_montage(data, montage);
  elec = ft_apply_montage(elec, montage);

else
  ft_error('unknown method "%s"', cfg.method);
end

if strcmp(cfg.method, 'spline') || strcmp(cfg.method, 'finite')
  % correct the units
  ft_warning('trying to correct the units, assuming uV and mm');
  for trlop=1:Ntrials
    % The surface laplacian is proportional to potential divided by squared distance which means that, if
    % - input potential is in uV, which is 10^6 too large
    % - units of electrode positions are in mm, which is 10^3 too large
    % these two cancel out against each other. Hence the computed laplacian
    % is in SI units (MKS).
    scd.trial{trlop} = cfg.conductivity * -1 * scd.trial{trlop};
  end
  fprintf('output surface laplacian is in V/m^2\n');
else
  fprintf('output Hjorth filtered potential is in uV\n');
end

% Adds the electrode definition to the data.
scd.elec = elec;

% convert back to input type if necessary
switch dtype
  case 'timelock'
    scd = ft_checkdata(scd, 'datatype', 'timelock');
  otherwise
    % keep the output as it is
end

% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble previous data

% rename the output variable to accomodate the savevar postamble
data = scd;

ft_postamble provenance data
ft_postamble history    data
ft_postamble savevar    data


%  Matlab (MatConvNet) implementation of our paper
%  DFM: A Performance Baseline for Deep Feature Matching
%  at CVPR 2021 Image Matching Workshop.
%
%  See details at https://github.com/ufukefe/DFM
%
% @authors: ufukefe, kutalmisince
% Created on March 23, 2021
% @Middle East Technical University, Center for Image Analysis
%  Last Edited on July 1, 2021

function [pointsA, pointsB] = DFM_VGG_stage1(imgA, imgB, model, ratios)

    resize_coeff_A = 1;
    resize_coeff_B = 1;
    % Check the image size
    if size(imgA,1)>1600 || size(imgA,2)>1600
        resize_coeff_A = 1600/max(size(imgA,1),size(imgA,2));
        imgA = imresize(imgA,resize_coeff_A);
    end

    if size(imgB,1)>1600 || size(imgB,2)>1600
        resize_coeff_B = 1600/max(size(imgB,1),size(imgB,2));
        imgB = imresize(imgB,resize_coeff_B);
    end

    size_org_A = size(imgA);
    size_org_B = size(imgB);

    % Normalize Images
    imgA = single(imgA);
    imgA = bsxfun(@minus, imgA, model.meta.normalization.averageImage);

    imgB = single(imgB);
    imgB = bsxfun(@minus, imgB, model.meta.normalization.averageImage);

    % zero padding for vgg (canvas should be a multiple of 16)
    imgA = ZeroPadding4VGG(imgA);
    imgB = ZeroPadding4VGG(imgB);

    % Assign layers to be used
    layers_to_use_A = {'conv5_2';'conv4_2';'conv3_2';'conv2_2';'conv1_2'};
    layers_to_use_B = {'conv5_2';'conv4_2';'conv3_2';'conv2_2';'conv1_2'};
    model = ArrangeNetwork(model,layers_to_use_A);

    % Assign ratio tests for layers
    ratios_s1 = ratios(5:-1:1);

    % get activations
    activationsA = GetActivations(imgA, model, layers_to_use_A);
    activationsB = GetActivations(imgB, model, layers_to_use_B);

    % initiate matches
    [pointsA, pointsB] = DenseFeatureMatching(activationsA{1,2}, activationsB{1,2},ratios_s1(1));
    [pointsA, pointsB] = insideImage(pointsA,pointsB,size_org_A,size_org_B,16);

    for k = 2:5
        [pointsA, pointsB] = RefinePoints(pointsA, pointsB, activationsA{k,2}, activationsB{k,2}, ratios_s1(k));
    end

    % Reject matches at the side of the images
    [pointsA,pointsB] = rejectSideMatches(pointsA,pointsB,size_org_A,size_org_B,16);

    if resize_coeff_A ~= 1
       pointsA = (1/resize_coeff_A)*(pointsA-0.5)+0.5;
    end

    if resize_coeff_B ~= 1
       pointsB = (1/resize_coeff_B)*(pointsB-0.5)+0.5;
    end

end


function [mcs, mcs_time] = calculateMCS(model_struct, n_mcs, max_len_mcs, varargin)
% Calculate Minimal Cut Sets (MCSs) using the warm-start strategy available
% in CPLEX, namely cplex.populate(), with or without selecting a given
% knockout, among all the reactions included in the model or a given subset
% of them. Tobalina et al., 2016 (Bioinformatics); von Kamp and Klamt, 2014
% (PLoS Computational Biology).
%
% USAGE:
%
%    [mcs, mcs_time] = populateMCS(model_struct, n_mcs, max_len_mcs, options)
%
% INPUTS:
%    model_struct:    Metabolic model structure (COBRA Toolbox format).
%    n_mcs:           Number of MCSs to calculate.
%    max_len_mcs:     Number of reactions in the largest MCS to be calculated.
%
% OPTIONAL INPUT:
% OPTIONAL INPUTS:
%    KO:                 Selected reaction knockout. (default = [])
%    rxn_set:            Cell array containing the set of reactions among which
%                        the MCSs are wanted to be calculated.
%                        taken from model.rxns
%                        (default = [], all reactions)
%    target_b:           Desired activity level of the metabolic task to be
%                        disrupted. (default = 1e-3)
%    forceLength:        1 if the constraint limiting the length of the
%                        MCSs is to be active (recommended for
%                        enumerating low order MCSs), 0 otherwise
%                        (default = 1)
%    timelimit:          Time limit for the calculation of MCSs each time
%                        the solver is called. (default = 1e75)
%    numWorkers:         Integer: is the maximun number of workers used
%                        by Cplex and GPR2models. 0 = automatic,
%                        1 = sequential, > 1 = parallel. (default = 0)
%    printLevel:         Integer. 1 if the process is wanted to be shown
%                        on the screen, 0 otherwise. (default = 1)
%
% OUTPUTS:
%    mcs:         Cell array containing the calculated MCSs.
%    mcs_time:    Calculation times of the different processes in
%                 the algorithm.
%
% EXAMPLE:
%    %With optional values
%    [mcs, mcs_time] = calculateMCS(modelR204, 100, 10, ...
%                                    'KO', 'r1651', ...
%                                    'rxn_set', {'r1652'; 'r1653'; 'r1654'}, ...
%                                    'timelimit', 300, ...
%                                    'target_b', 1e-4, ...
%                                    'forceLength', 0, ...
%                                    'printLevel', 0, ...
%
%    %Without optional values
%    [mcs, mcs_time] = calculateMCS(model, 100, 10)
%
% .. Authors:
%       - Inigo Apaolaza, 30/01/2017, University of Navarra, TECNUN School of Engineering.
%       - Luis V. Valcarcel, 19/11/2017, University of Navarra, TECNUN School of Engineering.
%       - Francisco J. Planes, 20/11/2017, University of Navarra, TECNUN School of Engineering.
% .. Revisions:
%       - Inigo Apaolaza, 10/04/2018, University of Navarra, TECNUN School of Engineering.
%       - Luis V. Valcarcel, 17/04/2018, University of Navarra, TECNUN School of Engineering.
%       - Luis V. Valcarcel, 30/06/2021, University of Navarra, TECNUN School of Engineering.
%       - Luis V. Valcarcel, 16/09/2022, University of Navarra, TECNUN School of Engineering.

% Check the installation of cplex
global SOLVERS;
global CBT_MILP_SOLVER;
if SOLVERS.ibm_cplex.installed && SOLVERS.ibm_cplex.working
    if ~strcmp(CBT_MILP_SOLVER,'ibm_cplex')
        warning('calculateMCS will use IBM CPLEX although it is not selected for MILP')
    end
else
    error('This version calculateMCS only works with IBM CPLEX. Newer versions will include more solvers included in COBRA Toolbox')
end

time_aa = tic;
% Set Parameters
p = inputParser;
% check required arguments
addRequired(p, 'model_struct');
addRequired(p, 'n_mcs', @isnumeric);
addRequired(p, 'max_len_mcs', @isnumeric);
% Add optional name-value pair argument
addParameter(p, 'KO', [], @(x)ischar(x)||isempty(x));
addParameter(p, 'rxn_set', [], @(x)iscell(x)||isempty(x));
addParameter(p, 'target_b', 1e-3, @(x)isnumeric(x)&&isscalar(x));
addParameter(p, 'timelimit', 1e75, @(x)isnumeric(x)&&isscalar(x));
addParameter(p, 'forceLength', true, @(x)islogical(x)||(isnumeric(x)&&isscalar(x)));
addParameter(p, 'numWorkers', 0, @(x)isnumeric(x)&&isscalar(x));
addParameter(p, 'printLevel', 1, @(x)isnumeric(x)&&isscalar(x));
% extract variables from parser
parse(p, model_struct, n_mcs, max_len_mcs, varargin{:});
model_struct = p.Results.model_struct;
n_mcs = p.Results.n_mcs;
max_len_mcs = p.Results.max_len_mcs;
KO = p.Results.KO;
rxn_set = p.Results.rxn_set;
target_b = p.Results.target_b;
timelimit = p.Results.timelimit;
forceLength = p.Results.forceLength;
numWorkers = p.Results.numWorkers;
printLevel = p.Results.printLevel;

integrality_tolerance = 1e-5;
M = 1e3;    % Big Value
alpha = 1;  % used to relate the lower bound of v variables with z variables
c = 1e-3;   % used to activate w variable
b = 1e-3;   % used to activate KnockOut constraint
phi = 1000; % b/c;

% Build the K Matrix
[~, n_ini_rxns] = size(model_struct.S);
K = speye(n_ini_rxns);

% Splitting
S = [model_struct.S -model_struct.S(:, model_struct.lb<0)];
K = [K K(:, model_struct.lb<0)];
K_ind = model_struct.rxns;
n_K_ind = length(K_ind);
[n_mets, n_rxns] = size(S);
nbio = find(model_struct.c);
t = zeros(n_rxns, 1);
t(nbio) = 1;

% Permit only KOs in rxn_set
if ~isempty(rxn_set)
    if ~isempty(KO)
        rxn_set = [rxn_set; {KO}];
    end
    rxn_set = unique(rxn_set);
    tmp_set = cellfun(@ismember, model_struct.rxns, repmat({rxn_set}, n_ini_rxns, 1), 'UniformOutput', false);
    pos_set = find(cell2mat(tmp_set));
    K = K(pos_set, :);
    K_ind = model_struct.rxns(pos_set);
    n_K_ind = length(K_ind);
end

if isempty(KO)
% ENUMERATE MCSs
% Define variables
    var.u = 1:n_mets;
    var.vp = var.u(end)+1:var.u(end)+n_K_ind;
    var.w = var.vp(end)+1:var.vp(end)+1;
    var.zp = var.w(end)+1:var.w(end)+n_K_ind;
    var.zw = var.zp(end)+1:var.zp(end)+1;
    n_vars = var.zw(end);
    var_group.v = [var.vp var.w];
    var_group.z = [var.zp var.zw];

% Define constraints
    cons.Ndual = 1:size(S, 2);
    cons.forceBioCons = cons.Ndual(end)+1:cons.Ndual(end)+1;
    cons.forceLength = cons.forceBioCons(end)+1:cons.forceBioCons(end)+1;
    n_cons = cons.forceLength(end);

% Cplex - A matrix
    A = sparse(zeros(n_cons, n_vars));
    A(cons.Ndual, var.u) = S';
    A(cons.Ndual, var.vp) = K';
    A(cons.Ndual, var.w) = -t;
    A(cons.forceBioCons, var.w) = -target_b;
    if forceLength == 1
        A(cons.forceLength, var.zp) = 1;
    end

% Cplex - rhs and lhs vectors
    rhs = zeros(n_cons, 1);
    rhs(cons.Ndual, 1) = inf;
    rhs(cons.forceBioCons) = -c;
    if forceLength == 1
        rhs(cons.forceLength) = 1;
    end
    lhs = zeros(n_cons, 1);
    lhs(cons.Ndual, 1) = 0;
    lhs(cons.forceBioCons) = -1000;
    if forceLength == 1
        lhs(cons.forceLength) = 1;
    end

% Cplex - ub and lb vectors
    ub(var.u, 1) = inf;
    ub(var.vp) = inf;
    ub(var.w) = inf;
    ub(var.zp) = 1;
    ub(var.zw) = 1;
    lb(var.u, 1) = -inf;
    lb(var.vp) = 0;
    lb(var.w) = 0;
    lb(var.zp) = 0;
    lb(var.zw) = 0;

% Cplex - obj vector
    obj(var.u, 1) = 0;
    obj(var.vp) = 0;
    obj(var.w) = 0;
    obj(var.zp) = 1;
    obj(var.zw) = 0;

% Cplex - ctype vector
    ctype(var.u) = 'C';
    ctype(var.vp) = 'C';
    ctype(var.w) = 'C';
    ctype(var.zp) = 'B';
    ctype(var.zw) = 'B';

% Cplex - sense of the optimization
    sense = 'minimize';

% Cplex - Introduce all data in a Cplex structure
    cplex = Cplex('MCS');
    Model = struct();
    [Model.A, Model.rhs, Model.lhs, Model.ub, Model.lb, Model.obj, Model.ctype, Model.sense] = deal(A, rhs, lhs, ub, lb, obj, ctype, sense);
    cplex.Model = Model;

% Cplex Indicators
    % z = 1  -->  v >= alpha
    for ivar = 1:length(var_group.z)
        a = zeros(n_vars, 1);
        a(var_group.v(ivar)) = 1;
        cplex.addIndicators(var_group.z(ivar), 0, a, 'G', alpha);
    end

% Cplex Indicators
    % z = 0  -->  v <= 0
    for ivar = 1:length(var_group.z)
        a = zeros(n_vars, 1);
        a(var_group.v(ivar)) = 1;
        cplex.addIndicators(var_group.z(ivar), 1, a, 'L', 0);
    end

% Cplex Parameters
    sP = struct();
    [sP.mip.tolerances.integrality, sP.mip.strategy.heuristicfreq, sP.mip.strategy.rinsheur] = deal(integrality_tolerance, 1000, 50);
    [sP.emphasis.mip, sP.output.clonelog, sP.timelimit, sP.threads] = deal(4, -1, max(10, timelimit), numWorkers);
    [sP.preprocessing.aggregator, sP.preprocessing.boundstrength, ...
        sP.preprocessing.coeffreduce, sP.preprocessing.dependency, ...
        sP.preprocessing.dual, sP.preprocessing.fill,...
        sP.preprocessing.linear, sP.preprocessing.numpass, ...
        sP.preprocessing.presolve, sP.preprocessing.reduce,..., ...
        sP.preprocessing.relax, sP.preprocessing.symmetry] = deal(50, 1, 2, 1, 1, 50, 1, 50, 1, 3, 1, 1);
    cplex = setCplexParam(cplex, sP);

    if printLevel == 0
        cplex.DisplayFunc = [];
    end

% Calculation of MCSs
    mcs_time{1, 1} = '------ TIMING ------';
    mcs_time{1, 2} = '--- MCSs ---';
    i = 0;
    k = 0;
    n_time = size(mcs_time, 1);
    mcs_time{n_time+1, 1} = 'Preparation';
    mcs_time{n_time+1, 2} = toc(time_aa);
    mcs = [];
    largest_mcs = 0;
    while largest_mcs <= max_len_mcs && k < n_mcs && cplex.Model.rhs(cons.forceLength) <= max_len_mcs
        ini_mcs_time = toc(time_aa);
        cplex.Param.mip.limits.populate.Cur = 40;
        cplex.Param.mip.pool.relgap.Cur = 0.1;
        cplex.populate();
        n_pool = size(cplex.Solution.pool.solution, 1);
        if n_pool ~= 0
            solution = cplex.Solution.pool.solution;
            for j = 1:n_pool
                k = k+1;
                mcs{k, 1} = K_ind((solution(j).x(var.zp))>0.9);
                n_cons = n_cons+1;
                sol = solution(j).x(var.zp)>0.9;
                cplex.Model.A(n_cons, var.zp) = sparse(double(sol));
                cplex.Model.rhs(n_cons) = sum(sol)-1;
                cplex.Model.lhs(n_cons) = 0;
            end
            i = i+1;
            mcsi_time = toc(time_aa)-ini_mcs_time;
            n_time = size(mcs_time, 1);
            mcs_time{n_time+1, 1} = ['POPULATE_ORDER_' num2str(cplex.Model.rhs(cons.forceLength))];
            mcs_time{n_time+1, 2} = mcsi_time;
        else
            mcsi_time = toc(time_aa)-ini_mcs_time;
            n_time = size(mcs_time, 1);
            mcs_time{n_time+1, 1} = ['POPULATE_ORDER_' num2str(cplex.Model.rhs(cons.forceLength)) 'NF'];
            mcs_time{n_time+1, 2} = mcsi_time;
            if forceLength == 1
                cplex.Model.rhs(cons.forceLength) = cplex.Model.rhs(cons.forceLength)+1;
                cplex.Model.lhs(cons.forceLength) = cplex.Model.lhs(cons.forceLength)+1;
            else
                n_time = size(mcs_time, 1);
                mcs_time{n_time+1, 1} = 'TOTAL MCSs';
                mcs_time{n_time+1, 2} = toc(time_aa);
                return;
            end
        end
        try save('tmp.mat', 'mcs', 'mcs_time'); end
        try largest_mcs = max(cellfun(@length, mcs)); end
    end
else
% CALCULATE MCSs WITH A GIVEN KNOCKOUT
% Select the row(s) in K_ind related to the KO under study
    tmp = repmat({KO}, n_K_ind, 1);
    dp = cellfun(@isequal, K_ind, tmp);

% Define variables
    var.u = 1:n_mets;
    var.vp = var.u(end)+1:var.u(end)+n_K_ind;
    var.w = var.vp(end)+1:var.vp(end)+1;
    var.zp = var.w(end)+1:var.w(end)+n_K_ind;
    var.zw = var.zp(end)+1:var.zp(end)+1;
    var.epsp = var.zw(end)+1:var.zw(end)+n_K_ind;
    var.epsw = var.epsp(end)+1:var.epsp(end)+1;
    var.delp = var.epsw(end)+1:var.epsw(end)+n_K_ind;
    var.delw = var.delp(end)+1:var.delp(end)+1;
    var.x = var.delw(end)+1:var.delw(end)+n_rxns+1;
    n_vars = var.x(end);
    var_group.v = [var.vp var.w];
    var_group.z = [var.zp var.zw];
    var_group.eps = [var.epsp var.epsw];
    var_group.del = [var.delp var.delw];

% Define constraints
    cons.Ndual = 1:size(S, 2);
    cons.forceBioCons = cons.Ndual(end)+1:cons.Ndual(end)+1;
    cons.forceKO = cons.forceBioCons(end)+1:cons.forceBioCons(end)+1;
    cons.linearComb = cons.forceKO(end)+1:cons.forceKO(end)+size(S, 1)+size(K, 1)+size(t, 2);
    cons.forceLength = cons.linearComb(end)+1:cons.linearComb(end)+1;
    n_cons = cons.forceLength(end);

% Cplex - A matrix
    A = sparse(zeros(n_cons, n_vars));
    A(cons.Ndual, var.u) = S';
    A(cons.Ndual, var.vp) = K';
    A(cons.Ndual, var.w) = -t;
    A(cons.forceBioCons, var.w) = -target_b;
    A(cons.forceKO, var.vp) = dp';
    A(cons.linearComb, var.x) = [S sparse(zeros(n_mets, 1)); K sparse(zeros(n_K_ind, 1)); -t' target_b];
    A(cons.linearComb, [var.epsp var.epsw]) = [sparse(zeros(n_mets, length(var.vp)+length(var.w))); -speye(length(var.vp)+length(var.w))];
    A(cons.linearComb, [var.delp var.delw]) = -[sparse(zeros(n_mets, length(var.vp)+length(var.w))); -speye(length(var.vp)+length(var.w))];
    if forceLength == 1
        A(cons.forceLength, var.zp) = 1;
    end

% Cplex - rhs and lhs vectors
    rhs = zeros(n_cons, 1);
    rhs(cons.Ndual, 1) = inf;
    rhs(cons.forceBioCons) = -c;
    rhs(cons.forceKO) = 10000;
    rhs(cons.linearComb) = [sparse(zeros(n_mets, 1)); dp; zeros(size(t, 2), 1)];
    if forceLength == 1
        rhs(cons.forceLength) = 1;
    end
    lhs = zeros(n_cons, 1);
    lhs(cons.Ndual, 1) = 0;
    lhs(cons.forceBioCons) = -1000;
    lhs(cons.forceKO) = b*10;
    lhs(cons.linearComb) = [sparse(zeros(n_mets, 1)); dp; zeros(size(t, 2), 1)];
    if forceLength == 1
        lhs(cons.forceLength) = 1;
    end

% Cplex - ub and lb vectors
    ub(var.u, 1) = inf;
    ub(var.vp) = inf;
    ub(var.w) = inf;
    ub(var.zp) = 1;
    ub(var.zw) = 1;
    ub(var.epsp) = inf;
    ub(var.epsw) = 0;
    ub(var.delp) = inf;
    ub(var.delw) = 0;
    ub(var.x) = inf;
    lb(var.u, 1) = -inf;
    lb(var.vp) = 0;
    lb(var.w) = 0;
    lb(var.zp) = 0;
    lb(var.zw) = 0;
    lb(var.epsp) = 0;
    lb(var.epsw) = 0;
    lb(var.delp) = 0;
    lb(var.delw) = 0;
    lb(var.x) = 0;
    lb(var.x(end)) = phi;

% Cplex - obj vector
    obj(var.u, 1) = 0;
    obj(var.vp) = 0;
    obj(var.w) = 0;
    obj(var.zp) = 1;
    obj(var.zw) = 0;
    obj(var.epsp) = 0;
    obj(var.epsw) = 0;
    obj(var.delp) = 0;
    obj(var.delw) = 0;
    obj(var.x) = 0;

% Cplex - ctype vector
    ctype(var.u) = 'C';
    ctype(var.vp) = 'C';
    ctype(var.w) = 'C';
    ctype(var.zp) = 'B';
    ctype(var.zw) = 'B';
    ctype(var.epsp) = 'C';
    ctype(var.epsw) = 'C';
    ctype(var.delp) = 'C';
    ctype(var.delw) = 'C';
    ctype(var.x) = 'C';

% Cplex - sense of the optimization
    sense = 'minimize';

% Cplex - Introduce all data in a Cplex structure
    cplex = Cplex('MCS');
    Model = struct();
    [Model.A, Model.rhs, Model.lhs, Model.ub, Model.lb, Model.obj, Model.ctype, Model.sense] = deal(A, rhs, lhs, ub, lb, obj, ctype, sense);
    cplex.Model = Model;

% Cplex Indicators
    % z = 1  -->  v >= alpha
    for ivar = 1:length(var_group.z)
        a = zeros(var.x(end), 1);
        a(var_group.v(ivar)) = 1;
        cplex.addIndicators(var_group.z(ivar), 0, a, 'G', alpha);
    end

% Cplex Indicators
    % z = 0  -->  v <= 0
    for ivar = 1:length(var_group.z)
        a = zeros(var.x(end), 1);
        a(var_group.v(ivar)) = 1;
        cplex.addIndicators(var_group.z(ivar), 1, a, 'L', 0);
    end

% Cplex Indicators
    % z = 1  -->  epsilon <= 0
    for ivar = 1:length(var_group.z)
        a = zeros(var.x(end), 1);
        a(var_group.eps(ivar)) = 1;
        cplex.addIndicators(var_group.z(ivar), 0, a, 'L', 0);
    end

% Cplex Indicators
    % z = 0  -->  epsilon <= M
    for ivar = 1:length(var_group.z)
        a = zeros(var.x(end), 1);
        a(var_group.eps(ivar)) = 1;
        cplex.addIndicators(var_group.z(ivar), 1, a, 'L', M);
    end

% Cplex Parameters
    sP = struct();
    [sP.mip.tolerances.integrality, sP.mip.strategy.heuristicfreq, sP.mip.strategy.rinsheur] = deal(integrality_tolerance, 1000, 50);
    [sP.emphasis.mip, sP.output.clonelog, sP.timelimit, sP.threads] = deal(4, -1, max(10, timelimit), numWorkers);
    [sP.preprocessing.aggregator, sP.preprocessing.boundstrength, ...
        sP.preprocessing.coeffreduce, sP.preprocessing.dependency, ...
        sP.preprocessing.dual, sP.preprocessing.fill,...
        sP.preprocessing.linear, sP.preprocessing.numpass, ...
        sP.preprocessing.presolve, sP.preprocessing.reduce,..., ...
        sP.preprocessing.relax, sP.preprocessing.symmetry] = deal(50, 1, 2, 1, 1, 50, 1, 50, 1, 3, 1, 1);
    cplex = setCplexParam(cplex, sP);
    if printLevel == 0
        cplex.DisplayFunc = [];
    end

% Calculation of MCSs
    mcs_time{1, 1} = '------ TIMING ------';
    mcs_time{1, 2} = '--- MCSs ---';
    i = 0;
    k = 0;
    n_time = size(mcs_time, 1);
    mcs_time{n_time+1, 1} = 'Preparation';
    mcs_time{n_time+1, 2} = toc(time_aa);
    mcs = [];
    largest_mcs = 0;
    while largest_mcs <= max_len_mcs && k < n_mcs && cplex.Model.rhs(cons.forceLength) <= max_len_mcs
        ini_mcs_time = toc(time_aa);
        cplex.Param.mip.limits.populate.Cur = 40;
        cplex.Param.mip.pool.relgap.Cur = 0.1;
        cplex.populate();
        n_pool = size(cplex.Solution.pool.solution, 1);
        if n_pool ~= 0
            solution = cplex.Solution.pool.solution;
            for j = 1:n_pool
                k = k+1;
                mcs{k, 1} = K_ind((solution(j).x(var.zp))>0.9);
                n_cons = n_cons+1;
                sol = solution(j).x(var.zp)>0.9;
                cplex.Model.A(n_cons, var.zp) = sparse(double(sol));
                cplex.Model.rhs(n_cons) = sum(sol)-1;
                cplex.Model.lhs(n_cons) = 0;
            end
            i = i+1;
            mcsi_time = toc(time_aa)-ini_mcs_time;
            n_time = size(mcs_time, 1);
            mcs_time{n_time+1, 1} = ['POPULATE_ORDER_' num2str(cplex.Model.rhs(cons.forceLength))];
            mcs_time{n_time+1, 2} = mcsi_time;
        else
            mcsi_time = toc(time_aa)-ini_mcs_time;
            n_time = size(mcs_time, 1);
            mcs_time{n_time+1, 1} = ['POPULATE_ORDER_' num2str(cplex.Model.rhs(cons.forceLength)) 'NF'];
            mcs_time{n_time+1, 2} = mcsi_time;
            if forceLength == 1
                cplex.Model.rhs(cons.forceLength) = cplex.Model.rhs(cons.forceLength)+1;
                cplex.Model.lhs(cons.forceLength) = cplex.Model.lhs(cons.forceLength)+1;
            else
                n_time = size(mcs_time, 1);
                mcs_time{n_time+1, 1} = 'TOTAL MCSs';
                mcs_time{n_time+1, 2} = toc(time_aa);
                return;
            end
        end
        try save('tmp.mat', 'mcs', 'mcs_time'); end
        try largest_mcs = max(cellfun(@length, mcs)); end
    end
end
n_time = size(mcs_time, 1);
mcs_time{n_time+1, 1} = 'TOTAL MCSs';
mcs_time{n_time+1, 2} = toc(time_aa);
end


%io_loadspec_data.m
%Jamie Near, McGill University 2014.
%
% USAGE:
% [out,out_w]=io_loadspec_data(filename,sw,Larmor,subspecs,te,tr);
%
% DESCRIPTION:
% Reads in philips MRS data (.data and .list files) using code adapted from
% PhilipsRead_data.m, provided as part of the Gannet software package by
% Richard Edden (gabamrs.blogspot.com).
%
% op_loadspec_data outputs the data in structure format, with fields
% corresponding to time scale, fids, frequency scale, spectra, and header
% fields containing information about the acquisition.  The resulting
% matlab structure can be operated on by the other functions in this MRS
% toolbox.  NOTE:  Since the Gannet code is geared towards edited GABA MRS
% data, this code may not be general enough to handle all types of MRS data.
% Suggestions are most welcome.
%
% INPUTS:
% filename   = filename of Philips .data file to be loaded.
% sw         = spectral width (Hz)
% Larmor     = Larmor frequency (Hz/ppm, ie.  127 for 3T)
% subspecs   = number of subspectra in the data (from spectral editing, ISIS, etc.)
% te         = echo time (ms).  Optional, default is [].
% tr         = repetition time (ms).  Optional, default is [].
%
% OUTPUTS:
% out        = Input water suppressed dataset in FID-A structure format.
% out_w      = Input water reference dataset in FID-A structure format.

function [out,out_w]=io_loadspec_data(filename,sw,Larmor,subspecs,te,tr);

if nargin<6
    if nargin<5
        te=[];
    end
    tr=[];
end

%read in the data using the philipsDataLoad.m (adapted from PhilipsRead_data.m)
[FullData,WaterData]=philipsDataLoad(filename);

%As far as I can tell, the data that comes out of the philipsDataLoad
%function is normally a N x Navgs matrix.  The coils have already been
%combined. The Navgs dimension contains all the subspectra, so we will
%split them now.  Note, that in the data-list format that I have seen, the
%edit-OFF subspectra appear in elements [1 2 5 6 9 10 13 14...] and the
%edit-ON subspectra appear in the elements [3 4 7 8 11 12 15 16...].  Other sequences
%may result in a different subspecs order, but for now we will separate the
%subspectra in this way.
%If the data has multiple subspectra
if subspecs>1
    %First make an vector that holds the indices of the ON subspectra:
    totalAvgs=size(FullData,2);
    OFFindices=[1:2:totalAvgs]-mod([0:(totalAvgs/2)-1],2);
    ONindices=[2:2:totalAvgs]+mod([1:totalAvgs/2],2);
    %Now split the subspectra out of the "averages" dimension:
    data(:,:,1)=FullData(:,OFFindices);
    data(:,:,2)=FullData(:,ONindices);
else
    data=FullData;
end

fids=squeeze(data);
fids_w=squeeze(WaterData)';

sz=size(fids);
sz_w=size(fids_w);

%Find the magnetic field strength:
Bo=Larmor/42.577;

%Find the number of averages:
Naverages=size(fids,2)*size(fids,3);
Naverages_w=size(fids_w,2)*size(fids_w,3);

%In Philips data/list format, coil channels have already been combined:
Ncoils=1;
Ncoils_w=1;

%Now create a record of the dimensions of the data array.
dims.t=1;
dims.coils=0;
dims.averages=2;
if subspecs>1
    dims.subSpecs=3;
else
    dims.subSpecs=0;
end

dims_w.t=1;
dims_w.coils=0;
dims_w.averages=2;
dims_w.subSpecs=0;

specs=fftshift(ifft(fids,[],dims.t),dims.t);
specs_w=fftshift(ifft(fids_w,[],dims_w.t),dims_w.t);

%Now get relevant scan parameters:*****************************

%Get Spectral width and Dwell Time
spectralwidth=sw;
dwelltime=1/spectralwidth;

%Get TxFrq
txfrq=Larmor*1e6;

%Leave date blank
date='';

%Find the number of averages.  'averages' will specify the current number
%of averages in the dataset as it is processed, which may be subject to
%change.  'rawAverages' will specify the original number of acquired
%averages in the dataset, which is unchangeable.
%FOR WATER SUPPRESSED DATA:
if dims.subSpecs ~=0
    if dims.averages~=0
        averages=sz(dims.averages)*sz(dims.subSpecs);
        rawAverages=averages;
    else
        averages=sz(dims.subSpecs);
        rawAverages=1;
    end
else
    if dims.averages~=0
        averages=sz(dims.averages);
        rawAverages=averages;
    else
        averages=1;
        rawAverages=1;
    end
end

%FOR WATER UNSUPPRESSED DATA:
if dims_w.subSpecs ~=0
    if dims_w.averages~=0
        averages_w=sz(dims_w.averages)*sz(dims_w.subSpecs);
        rawAverages_w=averages_w;
    else
        averages_w=sz(dims_w.subSpecs);
        rawAverages_w=1;
    end
else
    if dims_w.averages~=0
        averages_w=sz(dims_w.averages);
        rawAverages_w=averages_w;
    else
        averages_w=1;
        rawAverages_w=1;
    end
end

%Find the number of subspecs.  'subspecs' will specify the current number
%of subspectra in the dataset as it is processed, which may be subject to
%change.  'rawSubspecs' will specify the original number of acquired
%subspectra in the dataset, which is unchangeable.
%FOR WATER SUPPRESSED DATA:
if dims.subSpecs ~=0
    subspecs=sz(dims.subSpecs);
    rawSubspecs=subspecs;
else
    subspecs=1;
    rawSubspecs=subspecs;
end

%FOR WATER UNSUPPRESSED DATA:
if dims_w.subSpecs ~=0
    subspecs_w=sz(dims.subSpecs);
    rawSubspecs_w=subspecs_w;
else
    subspecs_w=1;
    rawSubspecs_w=subspecs_w;
end

%****************************************************************

%Calculate t and ppm arrays using the calculated parameters:
f=[(-spectralwidth/2)+(spectralwidth/(2*sz(1))):spectralwidth/(sz(1)):(spectralwidth/2)-(spectralwidth/(2*sz(1)))];
ppm=f/(Bo*42.577);
ppm=ppm+4.65;

t=[0:dwelltime:(sz(1)-1)*dwelltime];

%FOR WATER SUPPRESSED DATA
%FILLING IN DATA STRUCTURE
out.fids=fids;
out.specs=specs;
out.sz=sz;
out.ppm=ppm;
out.t=t;
out.spectralwidth=spectralwidth;
out.dwelltime=dwelltime;
out.txfrq=txfrq;
out.date=date;
out.dims=dims;
out.Bo=Bo;
out.averages=averages;
out.rawAverages=rawAverages;
out.subspecs=subspecs;
out.rawSubspecs=rawSubspecs;
out.seq='';
out.te=te;
out.tr=tr;
out.pointsToLeftshift=0;

%FILLING IN THE FLAGS
out.flags.writtentostruct=1;
out.flags.gotparams=1;
out.flags.leftshifted=0;
out.flags.filtered=0;
out.flags.zeropadded=0;
out.flags.freqcorrected=0;
out.flags.phasecorrected=0;
out.flags.averaged=0;
out.flags.addedrcvrs=0;
out.flags.subtracted=0;
out.flags.writtentotext=0;
out.flags.downsampled=0;
if out.dims.subSpecs==0
    out.flags.isFourSteps=0;
else
    out.flags.isFourSteps=(out.sz(out.dims.subSpecs)==4);
end

%FOR WATER UNSUPPRESSED DATA
%FILLING IN DATA STRUCTURE
out_w.fids=fids_w;
out_w.specs=specs_w;
out_w.sz=sz_w;
out_w.ppm=ppm;
out_w.t=t;
out_w.spectralwidth=spectralwidth;
out_w.dwelltime=dwelltime;
out_w.txfrq=txfrq;
out_w.date=date;
out_w.dims=dims_w;
out_w.Bo=Bo;
out_w.averages=averages_w;
out_w.rawAverages=rawAverages_w;
out_w.subspecs=subspecs_w;
out_w.rawSubspecs=rawSubspecs_w;
out_w.seq='';
out_w.te=te;
out_w.tr=tr;
out_w.pointsToLeftshift=0;

%FILLING IN THE FLAGS
out_w.flags.writtentostruct=1;
out_w.flags.gotparams=1;
out_w.flags.leftshifted=0;
out_w.flags.filtered=0;
out_w.flags.zeropadded=0;
out_w.flags.freqcorrected=0;
out_w.flags.phasecorrected=0;
out_w.flags.averaged=0;
out_w.flags.addedrcvrs=0;
out_w.flags.subtracted=0;
out_w.flags.writtentotext=0;
out_w.flags.downsampled=0;
if out_w.dims.subSpecs==0
    out_w.flags.isFourSteps=0;
else
    out_w.flags.isFourSteps=(out.sz(out.dims.subSpecs)==4);
end

%DONE


function [ cost, grad, numTotal, pred_cell ] = drdae_discrim_joint_kl_obj_gpu( theta, eI, data_cell, targets_cell, mixture_spectrum, fprop_only, pred_out)
% Copyright (c) 2014-present University of Illinois at Urbana-Champaign
% All rights reserved.
%
% Developed by:     Po-Sen Huang, Paris Smaragdis
%                   Department of Electrical and Computer Engineering
%                   Department of Computer Science
%
% discrim. training + joint masking using MATLAB GPU Toolbox
%
%PRNN_OBJ MinFunc style objective for Deep Recurrent Denoising Autoencoder
%   theta is the full parameter vector
%   eI contains experiment / network architecture
%   data_cell is a cell array of matrices. Each a distinct length is a cell
%             entry. Each matrix has a time series example in each column
%   targets_cell is parallel to data, but contains the labels for each time
%   fprop_only is a flag that only computes the cost, no gradient
%   numTotal is total number of frames evaluated
%   pred_out is a binary flag for whether pred_cell is populated
%            pred_cell only filled properly when utterances one per cell

import parallel.gpu.GPUArray

gtheta = gpuArray(theta);

%% Debug: Turns this into an identity-function for debugging rest of system
if isfield(eI, 'objReturnsIdentity') && eI.objReturnsIdentity
    cost = 0; grad = 0; numTotal = 0;
    for l = 1:numel(data_cell)
        numUtterances = size(data_cell{l}, 2);
        original_vector = reshape(data_cell{l}, eI.winSize*eI.featDim, []);
        midPnt = ceil(eI.winSize/2);
        original_vector = original_vector((midPnt-1)*14+1 : midPnt*14, :);
        pred_cell{l} = reshape(original_vector, [], numUtterances);
    end
    return;
end

%if isempty(return_activation),
  return_activation = 0;
%end

%% Load data from globals if not passed in (happens when run on RPC slave)
global g_data_cell;
global g_targets_cell;
isSlave = false;
if isempty(data_cell)
    data_cell = g_data_cell;
    targets_cell = g_targets_cell;
    isSlave = true;
end;
pred_cell = cell(1,numel(data_cell));
act_cell = cell(1,numel(data_cell));
%% default short circuits to false
if ~isfield(eI, 'shortCircuit')
    eI.shortCircuit = 0;
end;

%% default dropout to false
if ~isfield(eI, 'dropout')
  eI.dropout = 0;
end;

%% setup weights and accumulators
[stack, W_t] = rnn_params2stack(gtheta, eI);
cost = 0; numTotal = 0;
outputDim = eI.layerSizes(end);

%% setup structures to aggregate gradients
stackGrad = cell(1,numel(eI.layerSizes));
% W_t_grad = zeros(size(W_t));
if isfield(eI, 'fullRNN') && eI.fullRNN==1
   W_t_grad = cell(1,numel(eI.layerSizes)-1);
    for l = 1:numel(eI.layerSizes)-1
        W_t_grad{l}.W = gpuArray(zeros(size(W_t{l}.W)));
    end
else
   W_t_grad = gpuArray(zeros(size(W_t)));
end

for l = 1:numel(eI.layerSizes)
    stackGrad{l}.W = gpuArray(zeros(size(stack{l}.W)));
    stackGrad{l}.b = gpuArray(zeros(size(stack{l}.b)));
end
if eI.shortCircuit
    stackGrad{end}.W_ss = gpuArray(zeros(size(stack{end}.W_ss)));
end;
%% check options
if ~exist('fprop_only','var')
    fprop_only = false;
end;
if ~exist('pred_out','var')
    pred_out = false;
end;

% DROPOUT: vector of length of hidden layers with 0 or 1
% (to drop or keep activation unit) with prob=0.5
hActToDrop = cell(numel(eI.layerSizes-1),1);
for i=1:numel(eI.layerSizes)-1
 if eI.dropout
   hActToDrop{i} = gpuArray(1/eI.dropout * binornd(1,eI.dropout, eI.layerSizes(i),1));
   %hActToDrop{i} = gpuArray(round(rand(eI.layerSizes(i),1)));
 else
   hActToDrop{i} = gpuArray(ones(eI.layerSizes(i),1));
 end
end

%% loop over each distinct length
for c = 1:numel(data_cell)

    targets = {};
    if ~isempty(targets_cell), targets = targets_cell{c}; end;

    mbsz=min(size(data_cell{c},2), floor(200*1024^2/size(data_cell{c},1)/8)); % 200mb max
    if mbsz==0, continue; end
    nbat = floor(size(data_cell{c},2)/mbsz)+1;

     % convert different mini-bats to GPUs
    for bat=1:nbat

    data = gpuArray(data_cell{c}(:,1+(bat-1)*mbsz: min(size(data_cell{c},2), bat*mbsz)));
    targets = gpuArray(targets_cell{c}(:,1+(bat-1)*mbsz:  min(size(targets_cell{c},2), bat*mbsz)));
    mixtures=gpuArray(mixture_spectrum{c}(:,1+(bat-1)*mbsz: min(size(mixture_spectrum{c},2), bat*mbsz)));

    uttPred = [];
    T =size(data,1) / eI.inputDim;
    % store hidden unit activations at each time instant
    hAct = cell(numel(eI.layerSizes)-1, T);
    for t = 1:T
        %% forward prop all hidden layers
        for l = 1:numel(eI.layerSizes)-1
            if l == 1
                hAct{1,t} = stack{1}.W * data((t-1)*eI.inputDim+1:t*eI.inputDim, :);
            else
                hAct{l,t} = stack{l}.W * hAct{l-1,t};
            end;
            hAct{l,t} = hAct{l,t}+ repmat(stack{l}.b, 1, size(hAct{l,t},2));
            % temporal recurrence. limited to single layer for now
            if t > 1
                if isfield(eI, 'fullRNN') && eI.fullRNN==1
                    hAct{l,t} = hAct{l,t} + W_t{l}.W * hAct{l,t-1};
                elseif l == eI.temporalLayer
                    hAct{l,t} = hAct{l,t} + W_t * hAct{l,t-1};
                end
            end;

            % nonlinearity
            if strcmpi(eI.activationFn,'tanh')
                hAct{l,t} = tanh(hAct{l,t});
            elseif strcmpi(eI.activationFn,'logistic')
                hAct{l,t} = 1./(1+exp(-hAct{l,t}));
            elseif strcmpi(eI.activationFn,'RELU')
%                  maxg([ GPUsingle(zeros(size(hAct{1,t}))), hAct{l,t}], hAct{l,t});
%                 hAct{l,t} = maxg(0,hAct{l,t});
                hAct{l,t} = max(0,hAct{l,t});
            else
                error('unrecognized activation function: %s',eI.activationFn);
            end;
            %dropout (hActToDrop will be all ones if no dropout specified)
            hAct{1,t} = bsxfun(@times, hAct{1,t}, hActToDrop{l});
%              hAct{1,t} = hAct{1,t}.*hActToDrop{l};
        end;
        % forward prop top layer not done here to avoid caching it
    end;
    %% compute cost and backprop through time
    if  eI.temporalLayer
        if isfield(eI, 'fullRNN') && eI.fullRNN==1
            delta_t = cell(1, numel(eI.layerSizes)-1);
            for l = 1:numel(eI.layerSizes)-1
                delta_t{l} = gpuArray(zeros(eI.layerSizes(l),size(data,2)));
            end
        else
            delta_t = gpuArray(zeros(eI.layerSizes(eI.temporalLayer),size(data,2)));
        end
    end;

    y1_dim= 1:outputDim/2;
    y2_dim= outputDim/2+1:outputDim;

    for t = T:-1:1
        l = numel(eI.layerSizes);
        %% forward prop output layer for this timestep
        curPred = bsxfun(@plus, stack{l}.W * hAct{l-1,t}, stack{l}.b);

        if eI.outputnonlinear==1,
           if strcmpi(eI.activationFn,'tanh')
                curPred = tanh(curPred);
           elseif strcmpi(eI.activationFn,'logistic')
                curPred = 1./(1+exp(-curPred));
           elseif strcmpi(eI.activationFn,'RELU')
                curPred = max(0,curPred);
           else
                error('unrecognized activation function: %s',eI.activationFn);
           end
        end

        mixture=mixtures((t-1)*numel(y1_dim)+1:t*numel(y1_dim),:);
        a1 = curPred(y1_dim,:); a2 = curPred(y2_dim,:);

        const=eI.const;%1e-8 ;
        const2=eI.const2;% 1e-3;

        if strcmp(eI.opt,'softlinear'),
            y1= (a1)./((a1)+(a2)+1e-10).* mixture;
            y2= (a2)./((a1)+(a2)+1e-10).* mixture;
        elseif strcmp(eI.opt,'softabs'),
            abs_a1= abs(a1); abs_a2= abs(a2);
            y1= abs_a1./(abs_a1+abs_a2+1e-10).* mixture;
            y2= abs_a2./(abs_a1+abs_a2+1e-10).* mixture;
        elseif strcmp(eI.opt,'softabs_const')|| strcmp(eI.opt,'softabs_kl_const'),
            y1= abs(a1)./(abs(a1)+abs(a2)+const).* mixture;
            y2= abs(a2)./(abs(a1)+abs(a2)+const).* mixture;
        elseif strcmp(eI.opt, 'softquad')
            y1= (a1.^2)./((a1.^2)+(a2.^2)+1e-10).* mixture;
            y2= (a2.^2)./((a1.^2)+(a2.^2)+1e-10).* mixture;
        else
        end

        weighted_curPred=[y1; y2];
        % add short circuit to regression prediction if model has it
        if eI.shortCircuit
            weighted_curPred = weighted_curPred + stack{end}.W_ss ...
                * data((t-1)*eI.inputDim+1:t*eI.inputDim, :);
        end;
        if pred_out, uttPred = [weighted_curPred; uttPred]; end;
        % skip loss computation if no targets given
        if isempty(targets), continue; end;

        curTargets = targets((t-1)*outputDim+1:t*outputDim, :);
        curTargets_neg = [curTargets(outputDim/2+1:outputDim,:); curTargets(1:outputDim/2,:)];

        y_t = (1- eI.r) * weighted_curPred + eI.r * curTargets_neg - curTargets;

        ya_ta= y_t(y1_dim,:);
        yb_tb= y_t(y2_dim,:);

        if strcmp(eI.opt,'softlinear'),
            delta_y1 =  (ya_ta-yb_tb).* y2./(a1+a2+1e-10);
            delta_y2 = (-ya_ta+yb_tb) .* y1./ (a1+a2+1e-10);
        elseif strcmp(eI.opt,'softabs'),
            delta_y1 =  (ya_ta-yb_tb).* y2./(abs(a1)+abs(a2)+1e-10);
            delta_y2 = (-ya_ta+yb_tb) .* y1./ (abs(a1)+abs(a2)+1e-10);

            delta_y1(a1<0) = -delta_y1(a1<0);
            delta_y2(a2<0) = -delta_y2(a2<0);
        elseif strcmp(eI.opt,'softabs_const'),
             const_div=const./((abs(a1)+abs(a2)+const).^2).* mixture;
             delta_y1 =  (ya_ta-yb_tb).* y2./(abs(a1)+abs(a2)+const);
             delta_y1 = delta_y1+ ya_ta.* const_div;
             delta_y2 = (-ya_ta+yb_tb) .* y1./ (abs(a1)+abs(a2)+const);
             delta_y2= delta_y2+ yb_tb.* const_div;

             delta_y1(a1<0) =  -delta_y1(a1<0);
             delta_y2(a2<0) =  -delta_y2(a2<0);
        elseif strcmp(eI.opt,'softabs_kl_const'),
             y_target_a = curTargets(y1_dim, :);
             y_target_b = curTargets(y2_dim, :);
             y_target_neg_a = y_target_b;
             y_target_neg_b = y_target_a;

             y_pred_a = weighted_curPred(y1_dim, :);
             y_pred_b = weighted_curPred(y2_dim, :);

             const_div=const./((abs(a1)+abs(a2)+const).^2).* mixture;

             delta_y1 =  (-y_target_a./(y_pred_a+const2) + y_target_b./(y_pred_b+const2)).* y2./(abs(a1)+abs(a2)+const);
             delta_y1 = delta_y1+  (-y_target_a./(y_pred_a+const2)+1).* const_div;

             delta_y2 =  (y_target_a./(y_pred_a+const2) - y_target_b./(y_pred_b+const2)).* y1./(abs(a1)+abs(a2)+const);
             delta_y2= delta_y2+ (-y_target_b./(y_pred_b+const2)+1) .* const_div;

             % discrim part
             delta_y1 =  delta_y1- eI.r* (-y_target_neg_a./(y_pred_a+const2) + y_target_neg_b./(y_pred_b+const2)).* y2./(abs(a1)+abs(a2)+const);
             delta_y1 = delta_y1-   eI.r* (-y_target_neg_a./(y_pred_a+const2)+1).* const_div;

             delta_y2 = delta_y2- eI.r*(y_target_neg_a./(y_pred_a+const2) - y_target_neg_b./(y_pred_b+const2)).* y1./(abs(a1)+abs(a2)+const);
             delta_y2= delta_y2-  eI.r*(-y_target_neg_b./(y_pred_b+const2)+1) .* const_div;

             delta_y1(a1<0) =  -delta_y1(a1<0);
             delta_y2(a2<0) =  -delta_y2(a2<0);

        elseif strcmp(eI.opt, 'softquad')
            delta_y1 =  (ya_ta-yb_tb).* (2*a1.*y2)./ (a1.^2+a2.^2+1e-10); %  y1= (a1.^2)./((a1.^2)+(a2.^2)+1e-8);%.* mixture;
            delta_y2 =  (-ya_ta+yb_tb).* (2*a2.*y1)./ (a1.^2+a2.^2+1e-10);%         y2= (a2.^2)./((a1.^2)+(a2.^2)+1e-8);%.* mixture;
        else
        end

        delta = [ delta_y1; delta_y2 ];
        if strcmp(eI.opt,'softlinear') || strcmp(eI.opt,'softabs') || strcmp(eI.opt, 'softquad') || strcmp(eI.opt,'softabs_const'),
             cost = cost + 0.5 * ( sum( sum((weighted_curPred - curTargets).*(weighted_curPred - curTargets))) ...
                -  eI.r* sum( sum((weighted_curPred - curTargets_neg).*(weighted_curPred - curTargets_neg))));
        elseif strcmp(eI.opt,'softabs_kl_const'),
             cost = cost +...
             sum(sum( curTargets.*log( curTargets./(weighted_curPred + const2) + const2 )-curTargets+ weighted_curPred +const2))...
            -eI.r* sum(sum( curTargets_neg.*log( curTargets_neg./(weighted_curPred + const2) + const2 )-curTargets_neg+ weighted_curPred +const2));
        else

        end
        if eI.outputnonlinear==1,
             if strcmpi(eI.activationFn,'tanh')
                delta = delta .* (1 -curPred.^2);
            elseif strcmpi(eI.activationFn,'logistic')
                delta = delta .* curPred .* (1 - curPred);
            elseif strcmpi(eI.activationFn,'RELU')
                delta = delta .* double(curPred>0);
            else
                error('unrecognized activation function: %s',eI.activationFn);
            end;
        end

        if fprop_only, continue; end;
        %% regression layer gradient and delta
        stackGrad{l}.W = stackGrad{l}.W + delta * hAct{l-1,t}';
        stackGrad{l}.b = stackGrad{l}.b + sum(delta,2);
        % short circuit layer
        if eI.shortCircuit
            stackGrad{end}.W_ss = stackGrad{end}.W_ss + delta ...
                * data((t-1)*eI.inputDim+1:t*eI.inputDim, :)';
        end;
        delta = stack{l}.W' * delta;
        %% backprop through hidden layers
        for l = numel(eI.layerSizes)-1:-1:1
            % aggregate temporal delta term if this is the recurrent layer
            if isfield(eI, 'fullRNN') && eI.fullRNN==1
                delta = delta + delta_t{l};
            elseif l == eI.temporalLayer
                delta = delta + delta_t;
            else
            end
            % push delta through activation function for this layer
            % tanh unit choice assumed
            if strcmpi(eI.activationFn,'tanh')
                delta = delta .* (1 - hAct{l,t}.^2);
            elseif strcmpi(eI.activationFn,'logistic')
                delta = delta .* hAct{l,t} .* (1 - hAct{l,t});
            elseif strcmpi(eI.activationFn,'RELU')
                delta = delta .* gpuArray(hAct{l,t}>0);
            else
                error('unrecognized activation function: %s',eI.activationFn);
            end;

            % gradient of bottom-up connection for this layer
            if l > 1
                stackGrad{l}.W = stackGrad{l}.W + delta * hAct{l-1,t}';
            else
                stackGrad{l}.W = stackGrad{l}.W + delta * data((t-1)*eI.inputDim+1:t*eI.inputDim, :)';
            end;
            % gradient for bias
            stackGrad{l}.b = stackGrad{l}.b + sum(delta,2);

            % compute derivative and delta for temporal connections
            if t > 1
                if isfield(eI, 'fullRNN') && eI.fullRNN==1
                     W_t_grad{l}.W = W_t_grad{l}.W + delta * hAct{l,t-1}';
                     % push delta through temporal weights
                     delta_t{l} = W_t{l}.W' * delta;
                elseif l == eI.temporalLayer
                     W_t_grad = W_t_grad + delta * hAct{l,t-1}';
                     % push delta through temporal weights
                     delta_t = W_t' * delta;
                end;
            end
            % push delta through bottom-up weights
            if l > 1
                delta = stack{l}.W' * delta;
            end;
        end
    end
    pred_cell{c}=double(gather(uttPred));

    % Return the activations for this utterance.
    if return_activation,
      act_cell{c} = cell2mat(hAct);
    end
    % keep track of how many examples seen in total
    numTotal = numTotal + T * size(targets,2);
    end
end

%% stack gradients into single vector and compute weight cost
wCost = numTotal * eI.lambda * sum(gtheta.^2);
grad = rnn_stack2params(stackGrad, eI, W_t_grad, true);
grad = grad + 2 * numTotal * eI.lambda * gtheta;

%% clipping
if isfield(eI,'clip') && eI.clip~=0, % if eI.clip==0, no clip
  if eI.clip > 0 % method one -clip the whole
      norm_grad = norm(grad);
      fprintf('norm_grad:%f\n', norm_grad);
      % avoid numerial problem
      if norm_grad <0 || norm_grad > 1e15 || isnan(norm_grad) || isinf(norm_grad),
          grad = zeros(size(grad));
          fprintf('set gradient to zeros\n');
      end
      if norm_grad > eI.clip
         grad = eI.clip * grad/ norm_grad;
      end
  else % method two - clip each entry
      clip_value = -1*eI.clip;
      grad(grad > clip_value)=clip_value;
      grad(grad < -clip_value)=-clip_value;
  end
end
%%
cost = gather((cost));
grad = gather((grad));
wCost = gather((wCost));

avCost = cost/numTotal;
avWCost = wCost/numTotal;
cost = cost + wCost;

% print output
if ~isSlave && ~isempty(targets_cell)
    fprintf('loss:  %f  wCost:  %f \t',avCost, avWCost);

    if isfield(eI, 'fullRNN') && eI.fullRNN==1
        fprintf('wNorm: %f  rNorm: %f  oNorm: %f\n',sum(stack{1}.W(:).^2),...
            sum(W_t{1}.W(:).^2), sum(stack{end}.W(:).^2));
    else
        fprintf('wNorm: %f  rNorm: %f  oNorm: %f\n',sum(stack{1}.W(:).^2),...
            sum(W_t(:).^2), sum(stack{end}.W(:).^2));
    end
end;


classdef CLFQP < Controller
    % This class defines a class of Control Lyapunov Functions (CLFs) that
    % use quadratic programming (QP)
    %
    % @todo implement the CLF-QP controller
    %
    % @author ayonga @date 2016-10-14
    %
    % Copyright (c) 2016, AMBER Lab
    % All right reserved.
    %
    % Redistribution and use in source and binary forms, with or without
    % modification, are permitted only in compliance with the BSD 3-Clause
    % license, see
    % http://www.opensource.org/licenses/bsd-license.php

    properties

    end

    methods

        function obj = CLFQP(name)
            % The controller class constructor function
            %
            % Parameters:
            % name: the controller name @type char

            % call superclass constructor
            obj = obj@Controller(name);
            warning('P matrix from the Ricatti equation not verified for relative degree one outputs and not efficiently computed');
%             error('This class has not been completely defined yet.');

        end

        function u = calcControl(obj, t, x, vfc, gfc, plant, params, logger)
            % Computes the control Lyapunov function based control laws
            % control law for virtual constraints
            %
            % Parameters:
            % t: the time instant @type double
            % x: the states @type colvec
            % vfc: the vector field f(x) @type colvec
            % gfc: the vector field g(x) @type colvec
            % plant: the continuous domain @type DynamicalSystem
            % params: the control parameters @type struct
            % logger: the data logger object @type SimLogger
            %
            % Return values:
            % u: the computed torque @type colvec

            nx = plant.numState;
            if strcmp(plant.Type,'SecondOrder')
                q = x(1:nx);
                dq = x(nx+1:end);
            else
                q = x;
                dq = []; % will not affect any computation
            end

            y = struct2array(plant.VirtualConstraints);
            ny = length(y);
            y_a = cell(ny,1);
            y_d = cell(ny,1);
            tau = cell(ny,1);

            % total dimension of the virtual constraints
            dim_y = sum([y.Dimension]);
            % total dimension of outputs (including relative degrees)
            dim_eta = sum([y.Dimension.*y.RelativeDegree]);
            % some constants required for CLF
            F_mat = zeros(dim_eta);
            G_mat = zeros(dim_eta,dim_y);
            I_mat = eye(dim_eta);
            % partial derivative of the highest order of derivative (y^n-1) w.r.t.
            % the state variable 'x'
            DLfy = zeros(dim_y,length(x));   % A = DLfy*gfc; Lf = DLfy*vfc;
            ddy = zeros(dim_y,1);
            mu = zeros(dim_y,1);    % The derivatives mu = k(1) y + k(2) y' + ... k(n) y^(n-1)
            eta = zeros(dim_eta,1);
            idx = 1; % indexing of outputs
            etaidx = 1; % indexing of eta
            for i=1:ny
                y_i = y(i);

                % returns y, y', y'', y^(n-1), Jy^n-1

                % calculate the actual outputs
                %         [y_a{i}{:}] = calcActual(y_i,q,dq);
                offset_param = y_i.OffsetParamName;
                if y_i.hasOffset
                    offset = params.(offset_param);
                    y_a{i} = calcActual(y_i, q, dq, offset);
                else
                    y_a{i} = calcActual(y_i, q, dq);
                end
                % extract the parameter values

                output_param = y_i.OutputParamName; % desired output parameters
                phase_param  = y_i.PhaseParamName;  % phase variable parameters

                if isfield(params,output_param)
                    a = params.(output_param);
                else
                    error('The parameter %s has not been specified in the ''params'' argument.\n', output_param);
                end

                if ~isempty(phase_param)
                    if isfield(params,phase_param)
                        p = params.(phase_param);
                    else
                        error('The parameter %s has not been specified in the ''params'' argument.\n', phase_param);
                    end
                else
                    p = [];
                end
                % calculate the desired outputs
                y_d{i} = calcDesired(y_i, t, q, dq, a, p);
                % calculate the phase variable
                tau{i} = calcPhaseVariable(y_i, t, q, dq, p);

                % control gain (k0,k1,...kN-1) for the feedback term
                if isfield(params, 'epsilon')
                    ep = params.epsilon;
                    K = ones(1, y_i.RelativeDegree);
                    for l= 1:y_i.RelativeDegree
                        K(l) = nchoosek(y_i.RelativeDegree,l-1)*ep^(y_i.RelativeDegree - l + 1);
                    end

                else
                    error('The control gain %s has not been specified in the ''params'' argument.\n', control_param);
                end

                % stack the partial derivatives of all outputs
                y_indices = idx:idx+y_i.Dimension-1;
                eta_indices = etaidx:etaidx+y_i.Dimension-1;

                G_mat(eta_indices+(y_i.RelativeDegree-1)*numel(eta_indices),y_indices) = eye(y_i.Dimension);

                if y_i.RelativeDegree > 1 % only modify the first degree outputs (0th derivative). not higher derivatives
                    I_mat(eta_indices,eta_indices) = ep*eye(numel(eta_indices));
                end

                if strcmp(y_i.PhaseType, 'TimeBased')
                    DLfy(y_indices,:) = y_a{i}{end};
                    ddy(y_indices) = y_d{i}{end};
                else
                    DLfy(y_indices,:) = y_a{i}{end} - y_d{i}{end};
                end
                for j=1:y_i.RelativeDegree
                    mu(y_indices) = mu(y_indices) + K(j)*(y_a{i}{j}-y_d{i}{j});
                    eta(eta_indices+(j-1)*numel(eta_indices)) = y_a{i}{j} - y_d{i}{j};

                    if j < y_i.RelativeDegree
                        F_mat(eta_indices+(j-1)*numel(eta_indices),eta_indices+j*numel(eta_indices)) = eye(numel(eta_indices));
                    end
                end
                % update the starting index for the next output
                idx = idx+y_i.Dimension;
                etaidx = etaidx+y_i.Dimension*y_i.RelativeDegree;
            end

            %% here is where the CLF based controller is designed
            % decoupling matrix
            A_mat  = DLfy*gfc;
            % feedforward term
            Lf_mat = DLfy*vfc;

            % Lyapunov function
            P = care(F_mat,G_mat,eye(dim_eta));
            Pep = I_mat' * P * I_mat;
            Veta = eta' * Pep * eta;

%             % CLF V for mu
%             LgVetamu = 2*eta'*Pep*G_mat;
%             LfVetamu = eta'*(F_mat'*Pep + Pep*F_mat)*eta + 0.3660*ep*Veta;

            % CLF V for u
            LgVetau = 2*eta'*Pep*G_mat*A_mat;
            LfVetau = eta'*(F_mat'*Pep+Pep*F_mat)*eta+0.3660*ep*Veta+2*eta'*Pep*G_mat*Lf_mat-2*eta'*Pep*G_mat*ddy;

            Hmat = A_mat' * A_mat;

            % feedforward controller
            if strcmp(y_i.PhaseType, 'TimeBased')
                bmat = Lf_mat'*A_mat - ddy'*A_mat;
%                 u_ff = - A_mat \ (Lf_mat - ddy);
            else
                bmat = Lf_mat'*A_mat;
%                 u_ff = - A_mat \ Lf_mat;
            end

            options = optimoptions('quadprog','Algorithm','interior-point-convex','display','off');

%             muqp = quadprog(eye(6),[],LgVetamu,-LfVetamu,[],[],[],[],[],options);
%             disp(max(mu - muqp))
%             mu = muqp;
%             u_fb = A_mat \ mu;
%             u = u_ff + u_fb;

            u = quadprog(Hmat,bmat,LgVetau,-LfVetau,[],[],[],[],[],options);
%             disp(max(u - uqp))
            % feedback controller

            if ~isempty(logger)
                calc = logger.calc;

                calc.mu = mu;

                for i=1:ny
                    y_i = y(i);
                    output_name = y_i.Name;

                    for j=1:y_i.RelativeDegree

                        if j > 1
                            ya_name = ['d' num2str(j-1) 'ya_' output_name];
                            yd_name = ['d' num2str(j-1) 'yd_' output_name];
                            tau_name = ['d' num2str(j-1) 'tau_' output_name];
                        else
                            ya_name = ['ya_' output_name];
                            yd_name = ['yd_' output_name];
                            tau_name = ['tau_' output_name];
                        end
                        calc.(ya_name) = y_a{i}{j};
                        calc.(yd_name) = y_d{i}{j};
                        calc.(tau_name) = tau{i}{j};

                    end

                end
%                 calc.u_ff = u_ff;
%                 calc.u_fb = u_fb;
                calc.u = u;

                logger.calc = calc;
            end

        end
    end

end