function fem = update11nbr(fem_struct,jj)
% update11nbr takes a node with 11 elements connected to it
%  (which defines a region) and addes 4 nodes and 8 elements
%  to that region then regroups so that no node has more than
%  six elements connected to it. The new nodes and elements are sprung.
%
% NOTE:  fem_struct.nei must be a component.
%
%  Variables
%  fem_struct -- finite element structure from opnml.
%  jj -- the node number that has 11 elements connected to it.
%  fem -- the updated finite element structure.
%        Note:  Only fem.x,fem.y,fem.z,fem.e, and fem.nei
%               get updated.  fem.bnd does not need to be updated.
%
%  Usage -- fem = update11nbr(fem_struct,jj)
%
%  Name: update11nbr.m
%  Written by: Ben Holladay
%  Date: June 22,2004
%  Updated:  Aug. 27, 2004 -- Chris Massey Fixed bug in .nei for the
%                                          3rd new node.
% Last Modifed:
%         Sept. 19, 2006 -- Chris Massey, NRL Code 7322, S.S.C. MS 39529
%                      % Add test to make sure the correct nodal neighbor
%                        connectivity existed for the requested update
%                        and added a test to create the nodal neighbor list
%                        if not present.
%         Aug. 13, 2007 -- Chris Massey, NRL Code 7322
%                     Fixed a bug in the code that updates nodal neighbor
%                     list for nodes getting extra connectivity.
%         July 14, 2008 -- Chris Massey, NRL Code 7322
%               moved test for 4 neighbors to after the test for nei.
%
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%Checks if the fem_struct has an nei. If not one is generated.
try
    nei = fem_struct.nei;
catch
    fem_struct.nei = ele2nei(fem_struct.e,fem_struct.x,fem_struct.y);
    nei = fem_struct.nei;
end

tempnc = sum(~ismember((fem_struct.nei(jj,:)),0));
if tempnc ~= 11
    error(['There are ',num2str(tempnc),' nodal neighbors for node number ',...
           num2str(jj),'. This routine only works for 11 nodal neighbors.']);
end

%Sets the intial fem_struct variables.
x = fem_struct.x;
y = fem_struct.y;
z = fem_struct.z;
enodes = fem_struct.e;
vod = [1:11,1:11];

%Adds new nodes and elements and labels the bad node, neighboring nodes,
%and neighboring elements.
[nnodes,nc] = size(nei);
nelems = size(enodes,1);
newnode = nnodes + [1:4];
newelem = nelems + [1:8];
badnode = jj;
nbrnode = nei(jj,1:11);
[nbrelem,~] = find(enodes == badnode);
inrad = max(sqrt((x(nbrnode)-x(badnode)).^2 + (y(nbrnode)-y(badnode)).^2));

%First guess of the new coordinates and bathymetry.
Bx = sum(x([(nbrnode([1:3])),badnode]))/4;
By = sum(y([(nbrnode([1:3])),badnode]))/4;
Bz = sum(z([(nbrnode([1:3])),badnode]))/4;

Ax = sum([(x([(nbrnode([10,11,1]))]))',Bx])/4;
Ay = sum([(y([(nbrnode([10,11,1]))]))',By])/4;
Az = sum([(z([(nbrnode([10,11,1]))]))',Bz])/4;

Cx = sum([(x([(nbrnode([3:6]))]))',Bx])/5;
Cy = sum([(y([(nbrnode([3:6]))]))',By])/5;
Cz = sum([(z([(nbrnode([3:6]))]))',Bz])/5;

Ex = sum(x([(nbrnode([7:10])),badnode]))/5;
Ey = sum(y([(nbrnode([7:10])),badnode]))/5;
Ez = sum(z([(nbrnode([7:10])),badnode]))/5;

Dx = sum([(x([(nbrnode([6:7]))]))',Bx,Ax,Cx,Ex])/6;
Dy = sum([(y([(nbrnode([6:7]))]))',By,Ay,Cy,Ey])/6;
Dz = sum([(z([(nbrnode([6:7]))]))',Bz,Az,Cz,Ez])/6;

x(badnode) = Ax;
y(badnode) = Ay;
z(badnode) = Az;
x(newnode(1)) = Bx;
y(newnode(1)) = By;
z(newnode(1)) = Bz;
x(newnode(2)) = Cx;
y(newnode(2)) = Cy;
z(newnode(2)) = Cz;
x(newnode(3)) = Dx;
y(newnode(3)) = Dy;
z(newnode(3)) = Dz;
x(newnode(4)) = Ex;
y(newnode(4)) = Ey;
z(newnode(4)) = Ez;

%Updates the effected elements. J variables are used to represent which
%elements are connected each node.
i = 1;
while i <= 9
    j1 = ismember((enodes((nbrelem),:)),(nbrnode(vod(i))));
    j2 = ismember((enodes((nbrelem),:)),(nbrnode(vod(i+1))));
    spb = [1,1,2,2,2,3,4,4,4];
    j = sum([j1,j2],2);
    temp = nbrelem(find (j == 2));
    if ~isempty(temp)
        enodes(temp,:) = ([nbrnode(vod(i)),nbrnode(vod(i+1)),newnode(spb(i))]);
    end
    i = i + 1;
    clear j j1 j2 temp;
end

%Addes the 8 new elements.
enodes(newelem(1),:) = [nbrnode(1),newnode(1),badnode];
enodes(newelem(2),:) = [nbrnode(3),newnode(2),newnode(1)];
enodes(newelem(3),:) = [newnode(1),newnode(2),newnode(3)];
enodes(newelem(4),:) = [newnode(1),newnode(3),badnode];
enodes(newelem(5),:) = [badnode,newnode(3),newnode(4)];
enodes(newelem(6),:) = [nbrnode(10),badnode,newnode(4)];
enodes(newelem(7),:) = [nbrnode(6),newnode(3),newnode(2)];
enodes(newelem(8),:) = [nbrnode(7),newnode(4),newnode(3)];

M(1,:) = ([Ax,Ay,Az]);
M(2,:) = ([Bx,By,Bz]);
M(3,:) = ([Cx,Cy,Cz]);
M(4,:) = ([Dx,Dy,Dz]);
M(5,:) = ([Ex,Ey,Ez]);

%Springs the region.
i = 1;
imax = 20;
stoptol = 10e-8 * inrad;
tol = stoptol + 10;
while i <= imax && tol > stoptol
    M2(2,1) = sum([(x([(nbrnode([1:3])),badnode]))',M(3,1),M(4,1)]);
    M2(2,2) = sum([(y([(nbrnode([1:3])),badnode]))',M(3,2),M(4,2)]);
    M2(2,3) = sum([(z([(nbrnode([1:3])),badnode]))',M(3,3),M(4,3)]);

    M2(1,1) = sum([(x([(nbrnode([10,11,1]))]))',M(2,1),M(4,1),M(5,1)]);
    M2(1,2) = sum([(y([(nbrnode([10,11,1]))]))',M(2,2),M(4,2),M(5,2)]);
    M2(1,3) = sum([(z([(nbrnode([10,11,1]))]))',M(2,3),M(4,3),M(5,3)]);

    M2(3,1) = sum([(x([(nbrnode([3:6]))]))',M(2,1),M(4,1)]);
    M2(3,2) = sum([(y([(nbrnode([3:6]))]))',M(2,2),M(4,2)]);
    M2(3,3) = sum([(z([(nbrnode([3:6]))]))',M(2,3),M(4,3)]);

    M2(5,1) = sum([(x([(nbrnode([7:10]))]))',M(4,1),M(1,1)]);
    M2(5,2) = sum([(y([(nbrnode([7:10]))]))',M(4,2),M(1,2)]);
    M2(5,3) = sum([(z([(nbrnode([7:10]))]))',M(4,3),M(1,3)]);

    M2(4,1) = sum([(x([(nbrnode([6:7]))]))',M(2,1),M(1,1),M(3,1),M(5,1)]);
    M2(4,2) = sum([(y([(nbrnode([6:7]))]))',M(2,2),M(1,2),M(3,2),M(5,2)]);
    M2(4,3) = sum([(z([(nbrnode([6:7]))]))',M(2,3),M(1,3),M(3,3),M(5,3)]);
    M2 = M2./ 6;

    tol = max(sqrt((M2(:,1)-M(:,1)).^2 + (M2(:,2)-M(:,2)).^2));

    M = M2;
    x([badnode,([newnode(1:4)])]) = ([M(:,1)]);
    y([badnode,([newnode(1:4)])]) = ([M(:,2)]);
    z([badnode,([newnode(1:4)])]) = ([M(:,3)]);
    i = i + 1;
end

%Creates the output structure.
fem = fem_struct;
fem.e = enodes;
fem.x = x;
fem.y = y;
fem.z = z;
fem.nei = nei;

%Update the connectivity for the bad node and adds the connections for
%the new nodes.
fem.nei(badnode,1:nc) = ([newnode([1,3,4]),nbrnode([10,11,1]),zeros(1,nc-6)]);
fem.nei(newnode(1),1:nc) = ([badnode,nbrnode([1:3]),newnode([2,3]),zeros(1,nc-6)]);
fem.nei(newnode(2),1:nc) = ([newnode(1),nbrnode([3:6]),newnode(3),zeros(1,nc-6)]);
fem.nei(newnode(3),1:nc) = ([badnode,newnode([1,2]),nbrnode([6:7]),newnode(4),zeros(1,nc-6)]); %TCM 8/27/04
fem.nei(newnode(4),1:nc) = ([nbrnode([7:10]),badnode,newnode(3),zeros(1,nc-6)]);

%Updates the nei for neighbor nodes. These nodes get an additional
%connector.
spb = ([1,3,6,7,10]);
for i = 1:5
    addconn = ([badnode,newnode([1:4]),badnode]);
    tmp = nei(nbrnode(spb(i)),:);
	ij = find(tmp == badnode);
    ik = min((find(tmp == 0)) + 1);
    if isempty(ik)  % TCM 08/13/2007 -- Added this check in case ik was empty.
       ik = size(nei,2)+1;
    end
    fem.nei(nbrnode(spb(i)),1:ik) = ([tmp(1:(ij-1)),addconn(i+1),addconn(i),tmp((ij+1):(ik-1))]);
end

%Updates the nei for neighbor nodes. These nodes get different connector.
spn = ([2,4,5,8,9]);
for i = 1:5
    addnode = ([newnode(1),newnode(2),newnode(2),newnode(4),newnode(4)]);
    tmp = nei(nbrnode(spn(i)),:);
    ij = find(tmp == badnode);
    fem.nei(nbrnode(spn(i)),1:length(tmp)) = ([tmp(1:(ij-1)),addnode(i),tmp((ij+1):end)]);
end

%Determine the triqual for the final form to see if has very low quality
%elements.
tmp1 = (nbrelem);
tempL3=(fem.x(fem.e(tmp1,1))-fem.x(fem.e(tmp1,2))).^2+(fem.y(fem.e(tmp1,1))-...
   fem.y(fem.e(tmp1,2))).^2;
tempL1=(fem.x(fem.e(tmp1,2))-fem.x(fem.e(tmp1,3))).^2+(fem.y(fem.e(tmp1,2))-...
   fem.y(fem.e(tmp1,3))).^2;
tempL2=(fem.x(fem.e(tmp1,3))-fem.x(fem.e(tmp1,1))).^2+(fem.y(fem.e(tmp1,3))-...
   fem.y(fem.e(tmp1,1))).^2;
tempLs = ([tempL1 + tempL2 + tempL3]);
xnodes = fem.x(fem.e(tmp1,:));
ynodes = fem.y(fem.e(tmp1,:));
temparea = 0.5*(xnodes(:,1).*(ynodes(:,2)-ynodes(:,3))+xnodes(:,2).*...
   (ynodes(:,3)-ynodes(:,1))+xnodes(:,3).*(ynodes(:,1)-ynodes(:,2)));
fem.ar(tmp1) = temparea;
tempq = (4 * sqrt(3) * temparea) ./ tempLs;
clear tempL3 tempL2 tempL1 tempLs xnodes ynodes temparea;

fem1 = fem;
%Identify very low quality elements and attempts to use a line swap to fix.
poor = find(tempq < .1);
nflag = 1;good = 1;
if ~isempty(poor)
   for it = 1:length(poor)
      je = tmp1(poor(it));
      nodes = fem.e(je,:);
      nflag = 0;

      %Determine the angles to see if an edge can be fliped
      a2 = (x(enodes(je,3))-x(enodes(je,2))).^2+(y(enodes(je,3))-y(enodes(je,2))).^2;
      b2 = (x(enodes(je,1))-x(enodes(je,3))).^2+(y(enodes(je,1))-y(enodes(je,3))).^2;
      c2 = (x(enodes(je,2))-x(enodes(je,1))).^2+(y(enodes(je,2))-y(enodes(je,1))).^2;
      A = (180/pi)*acos((b2+c2-a2)./(2*sqrt(b2).*sqrt(c2)));
      B = (180/pi)*acos((c2+a2-b2)./(2*sqrt(c2).*sqrt(a2)));
      C = (180/pi)*acos((a2+b2-c2)./(2*sqrt(a2).*sqrt(b2)));
      [test,ind] = max([A,B,C]);
      if test > 160
         %Find the element numbers to flip the edge.
         nogood = nodes(ind);
         swap = setdiff(nodes,nogood);
         [temp1,tc] = find(enodes == swap(1) | enodes == swap(2));
         [b,j,k] = unique(temp1);
         temp2 = setdiff(1:length(temp1),j);
         swap = temp1(temp2);
         try
            fem1 = line_swap(fem,swap(1),swap(2));
         catch
            fem1 = fem;
         end

         %Spring the new mesh after the line swap.
         for itt = 1:2
             temp = find(fem1.nei(newnode(1),:) ~= 0);
             tempnei = fem1.nei(newnode(1),temp);
             fem1.x(newnode(1)) = mean(fem1.x(tempnei));
             fem1.y(newnode(1)) = mean(fem1.y(tempnei));
             fem1.z(newnode(1)) = mean(fem1.z(tempnei));
             temp = find(fem1.nei(newnode(2),:) ~= 0);
             tempnei = fem1.nei(newnode(2),temp);
             fem1.x(newnode(2)) = mean(fem1.x(tempnei));
             fem1.y(newnode(2)) = mean(fem1.y(tempnei));
             fem1.z(newnode(2)) = mean(fem1.z(tempnei));
             temp = find(fem1.nei(newnode(3),:) ~= 0);
             tempnei = fem1.nei(newnode(3),temp);
             fem1.x(newnode(3)) = mean(fem1.x(tempnei));
             fem1.y(newnode(3)) = mean(fem1.y(tempnei));
             fem1.z(newnode(3)) = mean(fem1.z(tempnei));
             temp = find(fem1.nei(newnode(4),:) ~= 0);
             tempnei = fem1.nei(newnode(4),temp);
             fem1.x(newnode(4)) = mean(fem1.x(tempnei));
             fem1.y(newnode(4)) = mean(fem1.y(tempnei));
             fem1.z(newnode(4)) = mean(fem1.z(tempnei));
             temp = find(fem1.nei(badnode,:) ~= 0);
             tempnei = fem1.nei(badnode,temp);
             fem1.x(badnode) = mean(fem1.x(tempnei));
             fem1.y(badnode) = mean(fem1.y(tempnei));
             fem1.z(badnode) = mean(fem1.z(tempnei));
         end

         %Use triqual to determine if the new mesh is better quality.
         tmp1 = nbrelem;
         tempL3=(fem1.x(fem1.e(tmp1,1))-fem1.x(fem1.e(tmp1,2))).^2+(fem1.y(fem1.e(tmp1,1))-...
            fem1.y(fem1.e(tmp1,2))).^2;
         tempL1=(fem1.x(fem1.e(tmp1,2))-fem1.x(fem1.e(tmp1,3))).^2+(fem1.y(fem1.e(tmp1,2))-...
            fem1.y(fem1.e(tmp1,3))).^2;
         tempL2=(fem1.x(fem1.e(tmp1,3))-fem1.x(fem1.e(tmp1,1))).^2+(fem1.y(fem1.e(tmp1,3))-...
            fem1.y(fem1.e(tmp1,1))).^2;
         tempLs = ([tempL1 + tempL2 + tempL3]);
         xnodes = fem1.x(fem1.e(tmp1,:));
         ynodes = fem1.y(fem1.e(tmp1,:));
         temparea = 0.5*(xnodes(:,1).*(ynodes(:,2)-ynodes(:,3))+xnodes(:,2).*...
            (ynodes(:,3)-ynodes(:,1))+xnodes(:,3).*(ynodes(:,1)-ynodes(:,2)));
         fem1.ar(tmp1) = temparea;
         tempq = (4 * sqrt(3) * temparea) ./ tempLs;
         clear tempL3 tempL2 tempL1 tempLs xnodes ynodes temparea;
         if min(tempq) > .4
            fem = fem1;
            nflag = 1;
         else
            nflag = 0;
            good = 6;
         end
      end
   end
end

% If problems with new mesh, then retriangulate

%Correct output if line swap failed to run
if sum(nflag) == 0
   fem = patch_update(fem_struct,fem1,jj);
   good = 6;
end

% Correct output if invalid elements were created.
bad = find(fem.ar < 0);
if ~isempty(bad)
    fem = patch_update(fem_struct,fem1,jj);
    good = 6;
end

%Display message if invalid elements where created.
if good == 6
   disp('The nodally updated mesh contained invalid or badly conditioned');
   disp('elements, therefore the patch was retriangulated which should');
   disp('reduce the connecitivity but is not guaranteed to do so.');
   disp(' ');
end

return


%% kalman tracking
function k = klmf(k)
for i      = 1 : size(k, 2)                         % for every kalman filter
k(i).s     = kalmanf(k(i).s);                       % update kalman filter
end
end
%% update kalman
function s = kalmanf(s)
s.x        = s.A * s.x;                             % prediction for state vector and covariance
s.P        = s.A * s.P * s.A' + s.Q;                % covariance of the state vector estimate
K          = s.P * s.H' / (s.H * s.P * s.H' + s.R); % compute Kalman gain factor
s.x        = s.x + K * (s.z - s.H * s.x);           % correction based on observation
s.P        = s.P - K * s.H * s.P;
end


function out = SD_SurrogateTest(x,surrMeth,numSurrs,extrap,theTestStat,randomSeed)
% SD_SurrogateTest   Analyzes test statistics obtained from surrogate time series
%
% This function is based on information found in:
% "Surrogate data test for nonlinearity including nonmonotonic transforms"
% D. Kugiumtzis Phys. Rev. E 62(1) R25 (2000)
%
% The generation of surrogates is done by the periphery function,
% SD_MakeSurrogates
%
%---INPUTS:
% x, the input time series
%
% surrMeth, the method for generating surrogate time series:
%       (i) 'RP': random phase surrogates that maintain linear correlations in
%                 the data but destroy any nonlinear structure through phase
%                 randomization
%       (ii) 'AAFT': the amplitude-adjusted Fourier transform method maintains
%                    linear correlations but destroys nonlinear structure
%                    through phase randomization, yet preserves the approximate
%                    amplitude distribution,
%       (iii) 'TFT': preserves low-frequency phases but randomizes high-frequency phases (as a way of dealing
%                    with non-stationarity, cf.:
%               "A new surrogate data method for nonstationary time series",
%                   D. L. Guarin Lopez et al., arXiv 1008.1804 (2010)
%
% numSurrs, the number of surrogates to compute (default is 99 for a 0.01
%         significance level 1-sided test)
%
% extrap, extra parameter, the cut-off frequency for 'TFT'
%
% theTestStat, the test statistic to evalute on all surrogates and the original
%           time series. Can specify multiple options in a cell and will return
%           output for each specified test statistic:
%           (i) 'ami': the automutual information at lag 1, cf.
%                 "Testing for nonlinearity in irregular fluctuations with
%                 long-term trends" T. Nakamura and M. Small and Y. Hirata,
%                 Phys. Rev. E 74(2) 026205 (2006)
%           (ii) 'fmmi': the first minimum of the automutual information
%                       function
%           (iii) 'o3': a third-order statistic used in: "Surrogate time
%                 series", T. Schreiber and A. Schmitz, Physica D 142(3-4) 346
%                 (2000)
%           (iv) 'tc3': a time-reversal asymmetry measure. Outputs of the
%                 function include a z-test between the two distributions, and
%                 some comparative rank-based statistics.
%
% randomSeed, whether (and how) to reset the random seed, using BF_ResetSeed

% ------------------------------------------------------------------------------
% Copyright (C) 2020, Ben D. Fulcher <ben.d.fulcher@gmail.com>,
% <http://www.benfulcher.com>
%
% If you use this code for your research, please cite the following two papers:
%
% (1) B.D. Fulcher and N.S. Jones, "hctsa: A Computational Framework for Automated
% Time-Series Phenotyping Using Massive Feature Extraction, Cell Systems 5: 527 (2017).
% DOI: 10.1016/j.cels.2017.10.001
%
% (2) B.D. Fulcher, M.A. Little, N.S. Jones, "Highly comparative time-series
% analysis: the empirical structure of time series and their methods",
% J. Roy. Soc. Interface 10(83) 20130048 (2013).
% DOI: 10.1098/rsif.2013.0048
%
% This function 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/>.
% ------------------------------------------------------------------------------

doPlot = 0; % plot outputs to a figure

% ------------------------------------------------------------------------------
%% Check inputs
% ------------------------------------------------------------------------------
if nargin < 2 || isempty(surrMeth)
    surrMeth = 'RP'; % randomize phases
end

if nargin < 3 || isempty(numSurrs)
    numSurrs = 99; % create 99 surrogates for a 0.01 significance level 1-sided test
end

if nargin < 4
    extrap = [];
end

if nargin < 5 || isempty(theTestStat)
    theTestStat = 'AMI'; % automutual information
end

if ischar(theTestStat)
    theTestStat = {theTestStat};
end

% randomSeed: how to treat the randomization
if nargin < 6
    randomSeed = [];
end

N = length(x); % time-series length

% ------------------------------------------------------------------------------
%% Generate surrogate time series using SD_MakeSurrogates:
% ------------------------------------------------------------------------------
z = SD_MakeSurrogates(x,surrMeth,numSurrs,extrap,randomSeed);

% z is matrix where each column is a surrogate time series

% ------------------------------------------------------------------------------
% Evaluate test statistic on each surrogate
% ------------------------------------------------------------------------------
if ismember('ami1',theTestStat)
    % Investigate AMI(1) of surrogates compared to that of signal itself
    % This statistic is used by Nakamura et al. (2006), PRE
    % Could use CO_HistogramAMI or TSTL, but I'll use IN_AutoMutualInfo with
    % Gaussian kernel…
    % FYI: for a histogram method, there are upper and lower bounds on the number of
    % bins to use: [1+log_2(N)], [sqrt(N)].

    % Use the gaussian approximation to estimate automutual information using the
    % Information Dynamics Toolkit:
    ami_fn = @(timeSeries,timeDelay) IN_AutoMutualInfo(timeSeries,timeDelay,'gaussian');

    AMIx = ami_fn(x,1);
    AMIsurr = zeros(numSurrs,1);
    for i = 1:numSurrs
        AMIsurr(i) = ami_fn(z(:,i),1);
    end
    % So we have a value AMIx, and a distribution for the surrogates
    % AMIsurr -- we must compare and return something meaningful
    % surrogates should always have lower AMI than original signal
    someStats = SDgivemestats(AMIx,AMIsurr,'right');
    fnames = fieldnames(someStats);
    for i = 1:length(fnames)
        out.(sprintf('ami_%s',fnames{i})) = someStats.(fnames{i});
    end
end

if ismember('fmmi',theTestStat)
    % Investigate the first minimum of mutual information of surrogates compared to
    % that of signal itself
    fmmix = CO_FirstMin(x,'mi');
    fmmiSurr = zeros(numSurrs,1);
    for i = 1:numSurrs
        try
            fmmiSurr(i) = CO_FirstMin(z(:,i),'mi');
        catch
            fmmiSurr(i) = NaN;
        end
    end
    if any(isnan(fmmiSurr))
        error('fmmi failed');
    end

    % FMMI should be higher for signal than surrogates
    someStats = SDgivemestats(fmmix,fmmiSurr,'right');
    fnames = fieldnames(someStats);
    for i = 1:length(fnames)
        out.(sprintf('fmmi_%s',fnames{i})) = someStats.(fnames{i});
    end
end

if ismember('o3',theTestStat)
    % Third-order statistic in Schreiber, Schmitz (Physica D)
    tau = 1;
    o3x = 1/(N-tau)*sum((x(1+tau:end) - x(1:end-tau)).^3);
    o3surr = zeros(numSurrs,1);
    for i = 1:numSurrs
        o3surr(i) = 1/(N-tau)*sum((z(1+tau:end,i) - z(1:end-tau,i)).^3);
    end
    someStats = SDgivemestats(o3x,o3surr,'both');
    fnames = fieldnames(someStats);
    for i = 1:length(fnames)
        out.(sprintf('o3_%s',fnames{i})) = someStats.(fnames{i});
    end
end

if ismember('tc3',theTestStat)
    % TC3 statistic -- another time-reversal asymmetry measure
    tau = 1;
    tmp = CO_tc3(x,tau);
    tc3x = tmp.raw;
    tc3surr = zeros(numSurrs,1);
    for i = 1:numSurrs
        tmp = CO_tc3(z(:,i),tau);
        tc3surr(i) = tmp.raw;
    end

    someStats = SDgivemestats(tc3x,tc3surr,'both');
    fnames = fieldnames(someStats);
    for i = 1:length(fnames)
        out.(sprintf('tc3_%s',fnames{i})) = someStats.(fnames{i});
    end
end

if ismember('nlpe',theTestStat)
    % Locally constant phase space prediction error
    warning('''nlpe'' can be very time consuming...')
    de = 3; tau = 1; % embedding parameters: fixed like a dummy!
    tmp = NL_MS_nlpe(x,de,tau);
    nlpex = tmp.msqerr;
    nlpesurr = zeros(numSurrs,1);
    for i = 1:numSurrs
        res = MS_nlpe(z(:,i),de,tau);
        msqerr = sum(res.^2);
        nlpesurr(i) = msqerr;
    end

    someStats = SDgivemestats(nlpex,nlpesurr,'right'); % NLPE should be higher than surrogates
    fnames = fieldnames(someStats);
    for i = 1:length(fnames)
        out.(sprintf('nlpe_%s',fnames{i})) = someStats.(fnames{i});
    end
end

if ismember('fnn',theTestStat)
    warning('fnn takes like *literally forever*...')

    % false nearest neighbours at d=2;
    tmp = NL_MS_fnn(x,2,1,5,1);
    fnnx = tmp.pfnn_2;
    fnnsurr = zeros(numSurrs,1);
    for i = 1:numSurrs
        tmp = NL_MS_fnn(z(:,i),2,1,5,1);
        fnnsurr(i) = tmp.pfnn_2;
    end

    someStats = SDgivemestats(fnnx,fnnsurr,'right'); % FNN(2) should be higher than surrogates?
    fnames = fieldnames(someStats);
    for i = 1:length(fnames)
        out.(sprintf('fnn_%s',fnames{i})) = someStats.(fnames{i});
    end
end

% ------------------------------------------------------------------------------
function someStats = SDgivemestats(statx,statsurr,leftrightboth)
    numSurrs = length(statsurr);
    if any(isnan(statsurr))
        error('SDgivemestats failed');
    end
%         leftrightboth = {'left','right','both'}
    % have a distribution of some statistic with samples statsurr
    % and we have the value of the statistic for a given process in
    % statx. Want to return measures of how consistant the measured
    % statistic is with the sample statsurr.

    % ASSUME GAUSSIAN DISTRIBUTION:
    % so can use 1/2-sided z-statistic
    [~, p, ~, zStat] = ztest(statx, mean(statsurr), std(statsurr), 0.05, leftrightboth);
    someStats.p = p; % pvalue
    someStats.zscore = zStat; % z-statistic

    % fit a kernel distribution to zscored distribution:
    if std(statsurr) == 0
        % all surrogates have same value of this statistic
        % cannot do a meaningful zscore -- do it raw
        [f, xi] = ksdensity(statsurr);
        % find where the statx value would be:
        if statx < min(xi) || statx > max(xi)
            someStats.f = 0; % out of range -- assume p=0 here
        else
            [~, minhere] = min(abs(statx-xi));
            someStats.f = f(minhere); % return probability density where the point is
        end
    else
        zscstatsurr = (statsurr-mean(statsurr))/std(statsurr);
        zscstatx = (statx-mean(statsurr))/std(statsurr);
        [f, xi] = ksdensity(zscstatsurr);

        % find where the statx value would be:
        if (zscstatx < min(xi)) || (zscstatx > max(xi))
            someStats.f = 0; % out of range -- assume p=0 here
        else
            [~, minhere] = min(abs(zscstatx-xi));
            someStats.f = f(minhere); % return probability density where the point is
        end
    end

    % What fraction of the range is the sample in?
    medsurr = median(statsurr);
    iqrsurr = iqr(statsurr);
    if iqrsurr == 0
        someStats.mediqr = NaN;
    else
        someStats.mediqr = abs(statx-medsurr)/iqrsurr;
    end

    % rank statistic
    [~, ix] = sort([statx; statsurr]);
    xfitshere = find(ix == 1) - 1;
    if strcmp(leftrightboth,'right') % x statistic smaller than distribution
        xfitshere = numSurrs + 1 - xfitshere; % how far from highest ranked value
    elseif strcmp(leftrightboth,'both')
        xfitshere = min(xfitshere,numSurrs+1-xfitshere);
    end

    if isempty(xfitshere)
        someStats.prank = 1/(numSurrs+1); % rank-based p-value
    else
        someStats.prank = (1+xfitshere)/(numSurrs+1); % rank-based p-value
    end
    if strcmp(leftrightboth,'both')
        someStats.prank = someStats.prank*2; % I think this factor should be in here
    end

    % DO PLOTTING:
    if doPlot
        figure('color','w')
        plot(statsurr,ones(numSurrs,1),'.k');
        hold('on');
        plot(statx*ones(2,1),[0,2],'r')
    end
end
% ------------------------------------------------------------------------------

end


function [xUpdate,SUpdate,innov,Szz,W]=sqrtEKFUpdateWithPred(z,SR,zPred,otherInfo,innovTrans,stateTrans)
%%SQRTEKFUPDATEWITHPRED Given the output of the measurement prediction step
%           from sqrtEKFMeasPred and a measurement, complete the measurement
%           update step of the first-order square root extended Kalman
%           filter. Separating the measurement prediction step from the
%           rest of the update step can make the creation of multiple
%           measurement association hypotheses from a single target
%           prediction more efficient. The full measurement update function
%           is sqrtEKFUpdate.
%
%INPUTS: z The zDimX1 measurement vector.
%       SR The zDimXzDim lower-triangular square root of the measurement
%          covariance matrix in the native coordinate system of the
%          measurement.
%    zPred The zDimXnumComp measurement prediction from the filter.
% otherInfo The intermediate results returned in the otherInfo output of
%          the sqrtEKFMeasPred function.
% innovTrans An optional function handle that computes and optionally
%          transforms the value of the difference between the observation
%          and any predicted points. This is called as innovTrans(a,b) and
%          the default if omitted or an empty matrix is passed is
%          @(a,b)bsxfun(@minus,a,b). This must be able to handle sets of
%          values. For a zDimX1 measurement, either of the inputs could be
%          zDimXN in size while one of the inputs could be zDimX1 in size.
%          This only needs to be supplied when a measurement difference
%          must be restricted to a certain range. For example, the
%          innovation between two angles will be 2*pi if one angle is zero
%          and the other 2*pi, even though they are the same direction. In
%          such an instance, a function handle to the
%          wrapRange(bsxfun(@minus,a,b),-pi,pi) function with the
%          appropriate parameters should be passed for innovTrans.
% stateTrans An optional function that takes a state estimate and
%          transforms it. This is useful if one wishes the elements of the
%          state to be bound to a certain domain. For example, if an
%          element of the state is an angle, one should generally want to
%          bind it to the region +/-pi.
%
%OUTPUTS: xUpdate The xDimXnumComp updated state vectors.
%         SUpdate The updated xDimXxDimXnumComp lower-triangular square-
%                 root state covariance matrices.
%      innov, Szz The zDimXnumComp innovations and the zDimXzDimXnumComp
%                 square-root innovation covariance matrices are returned
%                 in case one wishes to analyze the consistency of the
%                 estimator or use those values in gating or likelihood
%                 evaluation.
%               W The xDimXzDimXnumComp gains used in the update.
%
%See the comments to the function sqrtEKFMeasPred for an example of usage
%of this function. See the comments to sqrtEKFUpdate for more information
%on the algorithm.
%
%June 2018 David F. Crouse, Naval Research Laboratory, Washington D.C.
%(UNCLASSIFIED) DISTRIBUTION STATEMENT A. Approved for public release.

if(nargin<6||isempty(stateTrans))
    stateTrans=@(x)x;
end

if(nargin<5||isempty(innovTrans))
    innovTrans=@(a,b)bsxfun(@minus,a,b);
end

H=otherInfo.H;
Pxz=otherInfo.Pxz;
xPred=otherInfo.xPred;
SPred=otherInfo.SPred;

xDim=size(xPred,1);
numComp=size(xPred,2);
zDim=size(z,1);

xUpdate=zeros(xDim,numComp);
SUpdate=zeros(xDim,xDim,numComp);
innov=zeros(zDim,numComp);
Szz=zeros(zDim,zDim,numComp);
W=zeros(xDim,zDim,numComp);
for k=1:numComp
    Szz(:,:,k)=tria([H(:,:,k)*SPred(:,:,k),SR]);
    W(:,:,k)=(Pxz(:,:,k)/Szz(:,:,k)')/Szz(:,:,k);

    innov(:,k)=innovTrans(z,zPred(:,k));
    xUpdate(:,k)=stateTrans(xPred(:,:,k)+W(:,:,k)*innov(:,k));

    temp=W(:,:,k)*H(:,:,k);
    SUpdate=tria([(eye(size(temp))-temp)*SPred(:,:,k),W(:,:,k)*SR]);
end
end

%LICENSE:
%
%The source code is in the public domain and not licensed or under
%copyright. The information and software may be used freely by the public.
%As required by 17 U.S.C. 403, third parties producing copyrighted works
%consisting predominantly of the material produced by U.S. government
%agencies must provide notice with such work(s) identifying the U.S.
%Government material incorporated and stating that such material is not
%subject to copyright protection.
%
%Derived works shall not identify themselves in a manner that implies an
%endorsement by or an affiliation with the Naval Research Laboratory.
%
%RECIPIENT BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE
%SOFTWARE AND ANY RELATED MATERIALS, AND AGREES TO INDEMNIFY THE NAVAL
%RESEARCH LABORATORY FOR ALL THIRD-PARTY CLAIMS RESULTING FROM THE ACTIONS
%OF RECIPIENT IN THE USE OF THE SOFTWARE.


% gps.m
%   Compute the output of gps sensor
%
%  Revised:
%   3/5/2010 - RB
%   5/14/2010 - RB

function y = gps(uu, P)

    % relabel the inputs
    Va      = uu(1);
%    alpha   = uu(2);
%    beta    = uu(3);
    wn      = uu(4);
    we      = uu(5);
%    wd      = uu(6);
    pn      = uu(7);
    pe      = uu(8);
    pd      = uu(9);
%    u       = uu(10);
%    v       = uu(11);
%    w       = uu(12);
%    phi     = uu(13);
%    theta   = uu(14);
    psi     = uu(15);
%    p       = uu(16);
%    q       = uu(17);
%    r       = uu(18);
    t       = uu(19);

    persistent v_n;
    persistent v_e;
    persistent v_h;

    if t==0
        v_n = 0;
        v_e = 0;
        v_h = 0;
    else
        v_n = exp(-P.k_gps * P.Ts_gps) * v_n + P.sigma_gps_n * randn;
        v_e = exp(-P.k_gps * P.Ts_gps) * v_e + P.sigma_gps_e * randn;
        v_h = exp(-P.k_gps * P.Ts_gps) * v_h + P.sigma_gps_altitude * randn;
    end

    % construct North, East, and altitude GPS measurements
    y_gps_n = pn + v_n;
    y_gps_e = pe + v_e;
    y_gps_h = -pd + v_h;

    % construct groundspeed and course measurements
    V_n = Va * cos(psi) + wn;
    V_e = Va * sin(psi) + we;
    V_g = sqrt(V_n^2 + V_e^2);

    sigma_course = P.sigma_gps_V_g / V_g;

    y_gps_Vg     = sqrt(V_n^2 + V_e^2) + P.sigma_gps_V_g * randn;
    y_gps_course = atan2(V_e, V_n) + sigma_course * randn;

    % construct total output
    y = [...
        y_gps_n;...
        y_gps_e;...
        y_gps_h;...
        y_gps_Vg;...
        y_gps_course;...
        ];

end


function [rsvn,rsvp,rsvnSS,rsvpSS]=realized_semivariance(price,time,timeType,samplingType,samplingInterval,subsamples)
% Estimates Realized Semivariance and subsampled Realized Semivariance
%
% USAGE:
%   [RSVN,RSVP,RSVNSS,RSVPSS] = realized_semivariance(PRICE,TIME,TIMETYPE,SAMPLINGTYPE,SAMPLINGINTERVAL,SUBSAMPLES)
%
% INPUTS:
%   PRICE            - m by 1 vector of high frequency prices
%   TIME             - m by 1 vector of times where TIME(i) corresponds to PRICE(i)
%   TIMETYPE         - String describing the way times are measured
%                       'wall'    24-hour clock of the form HHMMSS, e.g. 101543 or 153217
%                       'seconds' Time measured in seconds past midnight on the first day.
%                       'unit'  Unit normalized date format, e.g. .1, .234, .9
%                         Unit normalized times are more general than the other types and can be
%                         applied to data from more than one calendar day
%   SAMPLINGTYPE     - String describing the type of sampling to use when
%                        filtering PRICE
%                        'CalendarTime' - Sample in calendar time using observations separated by SAMPLINGINTERVAL seconds
%                        'CalendarUniform' - Sample in calendar time using SAMPLINGINTERVAL
%                          observations spread uniformly between TIME(1) and TIME(m)
%                        'BusinessTime' - Sample in business (tick) time using observation separated
%                          by SAMPLINGINTERVAL ticks
%                        'BusinessUniform' - Sample in business (tick) time using observations
%                          uniformly spaced in business time.
%                        'Fixed' - Sample at specific points in time. When using fixed,
%                          SAMPLINGINTERVAL must be a n by 1 vector of times with the same TIMETYPE
%                          as TIME (i.e. seconds if TIME is in seconds)
%   SAMPLINGINTERVAL  - Scalar integer or n by 1 vector whose meaning depends on the selected SAMPLINGTYPE
%   SUBSAMPLES        - [OPTIONAL] Scalar integer indicating the number of subsample realized
%                         semivariance estimators to average with the original realized semivariance.
%                         Subsample realized semivariances are based on prices uniformly spaced between
%                         the times (Calendar sampling) or ticks (Business sampling).  SUBSAMPLES=1
%                         will compute a subsample realized semivariance using the mid-point of the
%                         price sample points, 2 will use 1/3 and 2/3, and so on. In general this
%                         number should be small so the subsample estimators will be "sparse".
%
% OUTPUTS:
%   RSVN              - Negative-part realized semivariance estimate
%   RSVP              - Positive-part realized semivariance estimate
%   RSVNSS            - Subsample averaged negative-part realized semivariance estimate
%   RSVPSS            - Subsample averaged positive-part realized semivariance estimate
%
% COMMENTS:
%
% EXAMPLES:
%  % 5-minute realized semivariance using wall time returns and fixed increments
%  fixedInterval = seconds2wall(wall2seconds(93000):300:wall2seconds(160000));
%  [RSVN,RSVP] = realized_semivariance(PRICE,TIME,'wall','Fixed',fixedInterval)
%
%  % 10-tick realized semivariance
%  [RSVN,RSVP] = realized_semivariance(PRICE,TIME,'wall','BusinessTime',10)
%
%  % 5-minute realized semivariance with subsampling every minute
%  fixedInterval = seconds2wall(wall2seconds(93000):300:wall2seconds(160000));
%  [RSVN,RSVP,RSVNSS,RSVPSS] = realized_semivariance(PRICE,TIME,'wall','Fixed',fixedInterval,5)
%
%  See also REALIZED_VARIANCE, REALIZED_KERNEL, REALIZED_QUANTILE_VARIANCE, REALIZED_RANGE,
%  REALIZED_PRICE_FILTER

% Copyright: Kevin Sheppard
% kevin.sheppard@economics.ox.ac.uk
% Revision: 1    Date: 5/1/2008

%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Input Checking
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if nargin<5 || nargin>6
    error('Five or Six inputs required.')
end
if size(price,2)>size(price,1)
    price=price';
end
if size(price,2)>1
    error('PRICE must be a m by 1 vector.')
end
if size(time,2)>size(time,1)
    time=time';
end
if any(diff(time)<0)
    error('TIME must be sorted and increasing')
end
if size(time,2)>1 || length(time)~=length(price)
    error('TIME must be a m by 1 vector.')
end
% Inserted to protect against inputing integer times
time = double(time);

timeType=lower(timeType);
if ~ismember(timeType,{'wall','seconds','unit'})
    error('TIMETYPE must be one of ''wall'', ''seconds'' or ''unit''.');
end
samplingType=lower(samplingType);
if ~ismember(samplingType,{'calendartime','calendaruniform','businesstime','businessuniform','fixed'})
    error('SAMPLINGTYPE must be one of ''CalendarTime'', ''CalendarUniform'', ''BusinessTime'', ''BusinessUniform'' or ''Fixed''.');
end

m=size(price,1);
t0=time(1);
tT=time(m);
if ismember(samplingType,{'calendartime','calendaruniform','businesstime','businessuniform'})
    % Must be a scalar integer if timeType is seconds or wall
    if ismember(timeType,{'wall','seconds'})
        if ~isscalar(samplingInterval) || floor(samplingInterval)~=samplingInterval || samplingInterval<1
            error('SAMPLINGINTERVAL must be a positive integer for the SAMPLINGTYPE selected when using ''wall'' or ''seconds'' as TIMETYPE.')
        end
    else
        if ~isscalar(samplingInterval) || samplingInterval<0
            error('SAMPLINGINTERVAL must be a positive value for the SAMPLINGTYPE selected when using ''unit'' as TIMETYPE.')
        end
    end
else
    if size(samplingInterval,2)>size(samplingInterval,1)
        samplingInterval=samplingInterval';
    end
    if ~(any(samplingInterval>=t0) && any(samplingInterval<=tT))
        error('At least one sampling interval must be between min(TIME) and max(TIME) when using ''Fixed'' as SAMPLINGTYPE.')
    end
    if any(diff(samplingInterval)<=0)
        error('When using ''Fixed'' as SAMPLINGTYPE the vector of sampling times in SAMPLINGINTERVAL must be sorted and strictly increasing.')
    end
end

if nargin<6 || isempty(subsamples)
    subsamples = 1;
end
if nargin==6 && ~isempty(subsamples)
    if ~isscalar(subsamples) || subsamples<0 || floor(subsamples)~=subsamples
        error('SUBSAMPLES must be a non-negative scalar.')
    end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Input Checking
%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Convert times to unit if not already unit
if ~strcmp(timeType,'unit')
    [time,~,~,samplingInterval]=realized_convert2unit(time,timeType,samplingType,samplingInterval);
end
% Since everything is converted to unit, set timeType to unit
timeType='unit';

% Filter prices and compute the RV
logPrice = log(price);
filteredLogPrice = realized_price_filter(logPrice,time,timeType,samplingType,samplingInterval);
returns = diff(filteredLogPrice);
rsvn=sum((returns.^2).*(returns<0));
rsvp=sum((returns.^2).*(returns>0));

subsampledLogPrices = realized_subsample(logPrice,time,timeType,samplingType,samplingInterval,subsamples);
rsvns = zeros(subsamples,1);
rsvps = zeros(subsamples,1);
totalCount = 0;
for i=1:subsamples
    returns = diff(subsampledLogPrices{i});
    rsvns(i) = sum((returns.^2).*(returns<0));
    rsvps(i) = sum((returns.^2).*(returns>0));
     if i==1
        baseCount = length(returns);
    end
    totalCount = totalCount + length(returns);
end
rsvnSS = sum(rsvns)  * (baseCount / totalCount);
rsvpSS = sum(rsvps)  * (baseCount / totalCount);


function [mustUL, pos_mustUL, mustUL_linear, pos_mustUL_linear] = findMustULWithGAMS(model, minFluxesW, maxFluxesW, constrOpt, excludedRxns, mustSetFirstOrder, solverName, runID, outputFolder, outputFileName, printExcel, printText, printReport, keepInputs, keepGamsOutputs, verbose)
% This function runs the second step of `optForce`, that is to solve a
% bilevel mixed integer linear programming  problem to find a second order
% MustUL set.
%
% USAGE:
%
%    [mustUL, pos_mustUL, mustUL_linear, pos_mustUL_linear] = findMustULWithGAMS(model, minFluxesW, maxFluxesW, varargin)
%
% INPUTS:
%    model:                      (structure) a 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`. E.g.:
%                                `maxFluxesW = [90; 56];`
%
% 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]');`
%    excludedRxns:               (cell array) Reactions to be excluded to
%                                the `MustUL` set. This could be used to
%                                avoid finding transporters or exchange
%                                reactions in the set. Default = empty.
%    mustSetFirstOrder:          (cell array) Reactions that belong to
%                                `MustU` and `MustL` (first order sets).
%                                Default = empty.
%    solverName:                 (string) Name of the solver used in
%                                GAMS. Default = 'cplex'.
%    runID:                      (string) ID for identifying this run.
%                                Default = ['run' date hour].
%    outputFolder:               (string) name for folder in which
%                                results will be stored.
%                                Default = 'OutputsFindMustUL'.
%    outputFileName:             (string) name for files in which
%                                results. will be stored
%                                Default = 'MustULSet'.
%    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 mantain folder with
%                                inputs to run `findMustUL.gms`. 0 otherwise.
%                                Default = 1
%    keepGamsOutputs:            (double) 1 to mantain files returned by
%                                `findMustUL.gms`. 0 otherwise. Default = 1
%    verbose:                    (double) 1 to print results in console.
%                                0 otherwise. Default = 0
%
% OUTPUTS:
%    mustUL:                     (cell array of size number of sets found X
%                                2) Cell array containing the reactions IDs
%                                which belong to the `MustUL` set. Each row
%                                contain a couple of reactions that must
%                                decrease their flux.
%    pos_mustUL:                 (double array of size number of sets found
%                                X 2) double array containing the positions
%                                of each reaction in `mustUL` with regard to
%                                model.rxns
%    mustUL_linear:              (cell array of size number of unique
%                                reactions found X 1) Cell array containing
%                                the unique reactions ID which belong to
%                                the `MustUL` Set
%    pos_mustUL_linear:          (double array of size number of unique
%                                reactions found X 1) double array
%                                containing positions for reactions in
%                                mustUL_linear. with regard to `model.rxns`
%    outputFileName.xls:         (file) File containing one column
%                                array with identifiers for reactions in
%                                MustUL. 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) File containing one column
%                                array with identifiers for reactions in
%                                MustUL. 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) File containing one column
%                                array. In each row the user will find a
%                                couple of reactions. Each couple of
%                                reaction was found in one iteration of
%                                `FindMustUL.gms`. 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) File containing one column
%                                array. In each row the user will find a
%                                couple of reactions. Each couple of
%                                reaction was found in one iteration of
%                                `FindMustUL.gms`. 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';`
%    findMustUL.lst:             (file) file autogenerated by GAMS. It
%                                contains information about equations,
%                                variables, parameters as well as
%                                information about the running (values at
%                                each iteration). This file only will be
%                                saved in the output folder is the user
%                                entered `keepGamsOutputs = 1`
%    GtoMUL.gdx:                 (file) file containing values for
%                                variables, parameters, etc. which were
%                                found by GAMS when solving `findMustUL.gms`.
%                                This file only will be saved in the output
%                                folder is the user entered `keepInputs = 1`
%
% 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', 'excludedRxns', 'mustSetFirstOrder', 'solverName', 'runID', 'outputFolder', 'outputFileName',  ...
    'printExcel', 'printText', 'printReport', 'keepInputs', 'keepGamsOutputs', 'verbose'};

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))
parser.addParamValue('excludedRxns', {}, @(x) iscell(x) && length(intersect(x, model.rxns)) == length(x))
parser.addParamValue('mustSetFirstOrder', {}, @(x) iscell(x) && length(intersect(x, model.rxns)) == length(x))
solvers = checkGAMSSolvers('MIP');
if isempty(solvers)
    error('there is no GAMS solvers available to solve Mixed Integer Programming problems') ;
else
    if ismember('cplex', lower(solvers))
        defaultSolverName = 'cplex';
    else
        defaultSolverName = lower(solvers(1));
    end
end

parser.addParamValue('solverName', defaultSolverName, @(x) ischar(x))
hour = clock; defaultRunID = ['run-' date '-' num2str(hour(4)) 'h' '-' num2str(hour(5)) 'm'];
parser.addParamValue('runID', defaultRunID, @(x) ischar(x))
parser.addParamValue('outputFolder', 'OutputsFindMustUL', @(x) ischar(x))
parser.addParamValue('outputFileName', 'MustULSet', @(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('keepGamsOutputs', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('verbose', 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;
excludedRxns= parser.Results.excludedRxns;
mustSetFirstOrder = parser.Results.mustSetFirstOrder;
solverName = parser.Results.solverName;
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;
keepGamsOutputs = parser.Results.keepGamsOutputs;
verbose = parser.Results.verbose;

% 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

% first, verify that GAMS is installed in your system
gamsPath = which('gams');
if isempty(gamsPath); error('OptForce: GAMS is not installed in your system. Please install GAMS.'); end;

%name of the function to solve the optimization problem in GAMS
gamsMustULFunction = 'findMustUL.gms';
%path of that function
pathGamsFunction = which(gamsMustULFunction);
if isempty(pathGamsFunction); error(['OptForce: ' gamsMustULFunction ' not in MATLAB path.']); end;
%current path
workingPath = pwd;
%go to the path associate to the ID for this run.
if ~isdir(runID); mkdir(runID); end; cd(runID);

% 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');
    reportClosed = 0;
    % print date of running.
    fprintf(freport, ['findMustULWithGAMS.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 gams version.
    fprintf(freport, ['GAMS: ' regexprep(gamsPath, '\\', '\\\') '\n']);
    % print solver used in GAMS to solve optForce.
    fprintf(freport, ['GAMS solver: ' solverName '\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, '\nExcluded Reactions:\n');
    for i = 1:length(excludedRxns)
        rxn = printRxnFormula(model, excludedRxns{i}, false);
        fprintf(freport, [excludedRxns{i} ': ' rxn{1} '\n']);
    end

    fprintf(freport, '\nReactions from first order sets(MustU and MustL):\n');
    for i = 1:length(mustSetFirstOrder)
        rxn = printRxnFormula(model, mustSetFirstOrder{i}, false);
        fprintf(freport, [mustSetFirstOrder{i} ': ' rxn{1} '\n']);
    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\nkeepGamsOutputs: %1.0f \n\nverbose: %1.0f \n',...
        printExcel, printText, printReport, keepInputs, keepGamsOutputs, verbose);

end

copyfile(pathGamsFunction);

% export inputs for running the optimization problem in GAMS to find the
% MustUL Set
inputFolder = 'InputsMustUL';
exportInputsMustOrder2ToGAMS(model, 'UL', minFluxesW, maxFluxesW, constrOpt, excludedRxns, mustSetFirstOrder, inputFolder)

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

%run
if verbose
    run = system(['gams ' gamsMustULFunction ' lo=3 --myroot=' inputFolder '/ --solverName=' solverName ' gdx=GtoMUL']);
else
    run=system(['gams ' gamsMustULFunction ' --myroot=' inputFolder '/ --solverName=' solverName ' gdx=GtoMUL']);
end

if printReport; fprintf(freport, '\n------RESULTS------\n'); end;

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

%if findMustUL.gms was executed correctly "run" should be 0
if run == 0

    if printReport; fprintf(freport, '\nGAMS was executed correctly\n'); end;
    if verbose; fprintf('GAMS was executed correctly\nSummary of information exported by GAMS:\n'); end;
    %show GAMS report in MATLAB console
    if verbose; gdxWhos GtoMUL; end;
    try
        findMustUL.name = 'findMustUL';
        rgdx('GtoMUL', findMustUL); %if do not exist the variable findMustUL in GtoMUL, an error will ocurr.
        if printReport; fprintf(freport, '\nGAMS variables were read by MATLAB correctly\n'); end;
        if verbose; fprintf('GAMS variables were read by MATLAB correctly\n'); end;

        %Using GDXMRW to read solutions found by findMustUL.gms
        %extract matrix 1 found by findMustII.gms. This matrix contains the
        %first reaction in each couple of reactions
        m1.name = 'matrix1';
        m1.compress = 'true';
        m1 = rgdx('GtoMUL', m1);
        uels_m1 = m1.uels{2};

        if ~isempty(uels_m1)
            %if the uel array for m1 is not empty, at least 1 couple of reations was found.
            if printReport; fprintf(freport, '\na MustUL set was found\n'); end;
            if verbose; fprintf('a MustUL set was found\n'); end;

            %find values for matrix 1
            val_m1 = m1.val;
            m1_full = full(sparse(val_m1(:,1), val_m1(:,2:end-1), val_m1(:,3)));

            %find values for matrix 2
            m2.name = 'matrix2';
            m2.compress = 'true';
            m2 = rgdx('GtoMUL', m2);
            uels_m2 = m2.uels{2};
            val_m2 = m2.val;
            m2_full = full(sparse(val_m2(:,1), val_m2(:,2:end-1), val_m2(:,3)));

            %initialize empty array for storing
            n_mustSet = size(m1_full,1);
            mustUL = cell(n_mustSet, 2);
            pos_mustUL = zeros(size(mustUL));
            mustUL_linear = {};

            %write each couple of reactions.
            for i = 1:n_mustSet
                rxn1 = uels_m1(m1_full(i,:) == 1);
                rxn2 = uels_m2(m2_full(i,:) == 1);
                mustUL(i,1) = rxn1;
                mustUL(i,2) = rxn2;
                pos_mustUL(i,1) = find(strcmp(model.rxns, rxn1));
                pos_mustUL(i,2) = find(strcmp(model.rxns, rxn2));
                mustUL_linear = union(mustUL_linear, [rxn1;rxn2]);
            end
            pos_mustUL_linear = cell2mat(arrayfun(@(x)find(strcmp(x, model.rxns)), mustUL_linear, 'UniformOutput', false))';
        else
            %if the uel array for m1 is empty, no couple of reations was found.
            if printReport; fprintf(freport, '\na MustUL set was not found\n'); end;
            if verbose; fprintf('a MustUL set was not found\n'); end;

            %initialize arrays to be returned by this function
            mustUL = {};
            pos_mustUL = [];
            mustUL_linear = {};
            pos_mustUL_linear = [];
        end

        % print info into an excel file if required by the user
        if printExcel
            if  ~isempty(uels_m1)
                currentFolder = pwd;
                cd(outputFolder);
                must = cell(size(mustUL,1), 1);
                for i = 1:size(mustUL, 1)
                    must{i} = strjoin(mustUL(i,:), ' or ');
                end
                xlswrite([outputFileName '_Info'],[{'Reactions'};must]);
                xlswrite(outputFileName, mustUL_linear);
                cd(currentFolder);
                if verbose
                    fprintf(['MustUL set was printed in ' outputFileName '.xls  \n']);
                    fprintf(['MustUL set was also printed in ' outputFileName '_Info.xls  \n']);
                end
                if printReport
                    fprintf(freport, ['\nMustUL set was printed in ' outputFileName '.xls  \n']);
                    fprintf(freport, ['\nMustUL set was printed in ' outputFileName '_Info.xls  \n']);
                end
            else
                if verbose; fprintf('No mustUL set was found. Therefore, no excel file was generated\n'); end;
                if printReport; fprintf(freport, '\nNo mustUL set was 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 ~isempty(uels_m1)
                currentFolder = pwd;
                cd(outputFolder);
                f = fopen([outputFileName '_Info.txt'], 'w');
                fprintf(f,'Reactions\n');
                for i = 1:size(mustUL,1)
                    fprintf(f, '%s or %s\n', mustUL{i,1}, mustUL{i,2});
                end
                fclose(f);

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

                cd(currentFolder);
                if verbose
                    fprintf(['MustUL set was printed in ' outputFileName '.txt  \n']);
                    fprintf(['MustUL set was also printed in ' outputFileName '_Info.txt  \n']);
                end
                if printReport
                    fprintf(freport, ['\nMustUL set was printed in ' outputFileName '.txt  \n']);
                    fprintf(freport, ['\nMustUL set was printed in ' outputFileName '_Info.txt  \n']);
                end

            else
                if verbose; fprintf('No mustUL set was found. Therefore, no plain text file was generated\n'); end;
                if printReport; fprintf(freport, '\nNo mustUL set was found. Therefore, no plain text file was generated\n'); end;
            end
        end

        %close file for saving report
        if printReport; fclose(freport); reportClosed = 1; end;
        if printReport; movefile(reportFileName, outputFolder); end;
        delete(gamsMustULFunction);

        %remove or move additional files that were generated during running
        if keepGamsOutputs
            if ~isdir(outputFolder); mkdir(outputFolder); end;
            movefile('GtoMUL.gdx', outputFolder);
            movefile(regexprep(gamsMustULFunction, 'gms', 'lst'), outputFolder);
        else
            delete('GtoMUL.gdx');
            delete(regexprep(gamsMustULFunction, 'gms', 'lst'));
        end

        %go back to the original path
        cd(workingPath);
    catch
        %GAMS variables were not read correctly by MATLAB
        if verbose; fprintf('GAMS variables were not read by MATLAB corretly\n'); end;
        if printReport && ~reportClosed; fprintf(freport, '\nGAMS variables were not read by MATLAB corretly\n'); fclose(freport); end;
        cd(workingPath);
        error('OptForce: GAMS variables were not read by MATLAB corretly');

    end

    %if findMustUL.gms was not executed correctly "run" should be different from 0
else
    %if GAMS was not executed correcttly
    if printReport && ~reportClosed; fprintf(freport, '\nGAMS was not executed correctly\n'); fclose(freport); end;
    if verbose; fprintf('GAMS was not executed correctly\n'); end;
    cd(workingPath);
    error('OptForce: GAMS was not executed correctly');

end

end


function r8sp_print_some ( m, n, nz_num, row, col, a, ilo, jlo, ihi, jhi, title )

%*****************************************************************************80
%
%% R8SP_PRINT_SOME prints some of a R8SP matrix.
%
%  Discussion:
%
%    This version of R8SP_PRINT_SOME has been specifically modified to allow,
%    and correctly handle, the case in which a single matrix location
%    A(I,J) is referenced more than once by the sparse matrix structure.
%    In such cases, the routine prints out the sum of all the values.
%
%    The R8SP storage format stores the row, column and value of each nonzero
%    entry of a sparse matrix.
%
%    It is possible that a pair of indices (I,J) may occur more than
%    once.  Presumably, in this case, the intent is that the actual value
%    of A(I,J) is the sum of all such entries.  This is not a good thing
%    to do, but I seem to have come across this in MATLAB.
%
%    The R8SP format is used by CSPARSE ("sparse triplet"), DLAP/SLAP
%    ("nonsymmetric SLAP triad"), by MATLAB, and by SPARSEKIT ("COO" format).
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    06 September 2006
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, integer M, N, the number of rows and columns of the matrix.
%
%    Input, integer NZ_NUM, the number of nonzero elements in the matrix.
%
%    Input, integer ROW(NZ_NUM), COL(NZ_NUM), the row and column indices
%    of the nonzero elements.
%
%    Input, real A(NZ_NUM), the nonzero elements of the matrix.
%
%    Input, integer ILO, JLO, IHI, JHI, the first row and
%    column, and the last row and column to be printed.
%
%    Input, string TITLE, a title.
%
  fprintf ( 1, '\n' );
  fprintf ( 1, '%s\n', title );

  incx = 5;
%
%  Print the columns of the matrix, in strips of 5.
%
  for j2lo = jlo: incx: jhi

    j2hi = j2lo + incx - 1;
    j2hi = min ( j2hi, n );
    j2hi = min ( j2hi, jhi );

    inc = j2hi + 1 - j2lo;

    fprintf ( 1, '\n' );
    fprintf ( 1, '  Col: ' );

    for j = j2lo : j2hi
      fprintf ( 1, '%7d       ', j );
    end

    fprintf ( 1, '\n' );
    fprintf ( 1, '  Row\n' );
    fprintf ( 1, '  ---\n' );
%
%  Determine the range of the rows in this strip.
%
    i2lo = max ( ilo, 1 );
    i2hi = min ( ihi, m );

    for i = i2lo : i2hi
%
%  Print out (up to) 5 entries in row I, that lie in the current strip.
%
      nonzero = 0;

      aij(1:inc) = 0.0;

      for k = 1 : nz_num

        if ( i == row(k) && j2lo <= col(k) && col(k) <= j2hi )

          j2 = col(k) - j2lo + 1;

          if ( a(k) ~= 0.0 )
            nonzero = 1;
            aij(j2) = aij(j2) + a(k);
          end

        end

      end

      if ( nonzero )
        fprintf ( 1, '%4d', i );
        for j = 1 : inc
          fprintf ( 1, '  %12g', aij(j) );
        end
        fprintf ( 1, '\n' );
      end

    end

  end

  return
end


function g = gibbsKernGradient(kern, x, varargin)

% GIBBSKERNGRADIENT Gradient of GIBBS kernel's parameters.
% FORMAT
% DESC computes the gradient of functions with respect to the
% Mark Gibbs's non-stationary
% kernel's parameters. As well as the kernel structure and the
% input positions, the user provides a matrix PARTIAL which gives
% the partial derivatives of the function with respect to the
% relevant elements of the kernel matrix.
% ARG kern : the kernel structure for which the gradients are being
% computed.
% ARG x : the input locations for which the gradients are being
% computed.
% ARG partial : matrix of partial derivatives of the function of
% interest with respect to the kernel matrix. The argument takes
% the form of a square matrix of dimension  numData, where numData is
% the number of rows in X.
% RETURN g : gradients of the function of interest with respect to
% the kernel parameters. The ordering of the vector should match
% that provided by the function kernExtractParam.
%
% FORMAT
% DESC computes the derivatives as above, but input locations are
% now provided in two matrices associated with rows and columns of
% the kernel matrix.
% ARG kern : the kernel structure for which the gradients are being
% computed.
% ARG x1 : the input locations associated with the rows of the
% kernel matrix.
% ARG x2 : the input locations associated with the columns of the
% kernel matrix.
% ARG partial : matrix of partial derivatives of the function of
% interest with respect to the kernel matrix. The matrix should
% have the same number of rows as X1 and the same number of columns
% as X2 has rows.
% RETURN g : gradients of the function of interest with respect to
% the kernel parameters.
%
% SEEALSO gibbsKernParamInit, kernGradient, gibbsKernDiagGradient, kernGradX
%
% COPYRIGHT : Neil D. Lawrence, 2006, 2009

% KERN

%  pretty(simple(diff((2*(l_i*l_j)/(l_i*l_i + l_j*l_j))^(d/2)*exp(-r*r/(l_i*l_i + l_j*l_j)),l_i)))

%                                         2
%       /    l_i l_j  \(1/2 d)           r               4        4        2  2
%   1/2 |2 -----------|        exp(- -----------) (-d l_i  + d l_j  + 4 l_i  r )
%       |     2      2|                 2      2
%       \  l_i  + l_j /              l_i  + l_j

%            /      2      2 2
%           /  ((l_i  + l_j )  l_i)
%          /
% >> pretty(simple(diff((2*(l_i*l_i)/(l_i*l_i + l_i*l_i))^(d/2)*exp(-r*r/(l_i*l_i + l_i*l_i)),l_i)))

%                                              2
%                                2            r
%                               r  exp(- 1/2 ----)
%                                               2
%                                            l_i
%                               ------------------
%                                         3
%                                      l_i
% >> pretty(simple(diff((2*(l_i*l_i)/(l_i*l_i + l_i*l_i))^(d/2)*exp(-r*r/(l_i*l_i + l_i*l_i)),l_i)))

fhandle = str2func([kern.lengthScaleTransform, 'Transform']);
g = zeros(1, kern.nParams);
% The last argument is covGrad
if length(varargin)<2
  [k, sk, n2, w2, l] = gibbsKernCompute(kern, x);
  gOut = modelOutputGrad(kern.lengthScaleFunc, x);
  gradFact = fhandle(l, 'gradfact');
  L1 = repmat(l, 1, size(l, 1));
  L2 = L1';
  covGrad = varargin{end};
  covGrad(1:size(covGrad, 1)+1:end) = 0;
  base = covGrad.*k;
  base2 = base.*(kern.inputDimension/2*(L2.^4 - L1.^4) + 2*L1.*L1.*n2)./(w2.*w2.*L1);
  for i = 1:size(g, 2)-1
    g(i) = g(i) + 2*sum(sum(base2.*repmat(gOut(:, i).*gradFact, 1, size(x, 1))));
  end
%/~
%  covGrad = varargin{end};
%  covGradDiag = diag(covGrad);
%  covGrad(1:size(covGrad, 1)+1:end) = 0;
%  base = covGradDiag.*diag(k)./(l.^3).*diag(n2);
%  for i = 1:size(g, 2) - 1
%    g(i) = g(i) + sum(base.*gOut(:, i));
%  end
%  base = covGrad.*k;
%  base = base.*(d/2*(L2.^4 - L1.^4) + 2*L1.*L1.*n2)./(w2.*w2.*L1);
%  for i = 1:size(g, 2)-1
%    g(i) = g(i) + sum(sum(base.*repmat(gOut(:, i), 1, size(x, 1))));
%  end
%~/
else
  [k, sk, n2, w2, l, l2] = gibbsKernCompute(kern, x, varargin{1});
  gOut = modelOutputGrad(kern.lengthScaleFunc, x);
  gradFact = fhandle(l, 'gradfact');
  gOut2 = modelOutputGrad(kern.lengthScaleFunc, varargin{1});
  gradFact2 = fhandle(l2, 'gradfact');
  L1 = repmat(l, 1, size(l2, 1));
  L2 = repmat(l2, 1, size(l, 1))';
  base = varargin{end}.*k;
  base2 = base.*(kern.inputDimension/2*(L2.^4 - L1.^4) + 2*L1.*L1.*n2)./(w2.*w2.*L1);
  for i = 1:size(g, 2)-1
    g(i) = g(i) + sum(sum(base2.*repmat(gOut(:, i).*gradFact, 1, size(varargin{1}, ...
                                                      1))));
  end
  base2 = base.*(kern.inputDimension/2*(L1.^4 - L2.^4) + 2*L2.*L2.* ...
                 n2)./(w2.*w2.*L2);
  for i = 1:size(g, 2)-1
    g(i) = g(i) + sum(sum(base2.*repmat(gOut2(:, i)'.*gradFact2', size(x, ...
                                                      1), 1)));
  end

end
g(end) =  sum(sum(varargin{end}.*sk));


function [z,output] = nlsb_gndl(F,dF,lb,ub,z0,options)
%NLSB_GNDL Bound-constrained NLS by projected Gauss-Newton dogleg TR.
%   [z,output] = nlsb_gndl(F,dF,lb,ub,z0) starts at z0 and attempts to find
%   a local minimizer of the real-valued function f(z), which is the
%   nonlinear least squares objective function f(z) := 0.5*(F(z)'*F(z))
%   subject to the constraints real(lb) <= real(z) <= real(ub) and
%   imag(lb) <= imag(z) <= imag(ub). The input variable z (and lb, ub) may
%   be a scalar, vector, matrix, tensor or even a (nested) cell array of
%   tensors and its contents may be real or complex. This method may be
%   applied in the following ways:
%
%   1. F is function of both z and conj(z).
%
%      Method 1: general medium-scale problems.
%      nlsb_gndl(F,dF,lb,ub,z0) where F(z) returns a column vector of
%      complex. Set dF equal to the string 'Jacobian-C' for automatic
%      numerical approximation of the complex Jacobian, or supply the
%      complex Jacobian manually with a structure dF containing:
%
%         dF.dzc     - The function dF.dzc(zk) should return the complex
%                      Jacobian [dF(zk)/d(z^T) dF(zk)/d(conj(z)^T)], which
%                      is defined as the matrix in which the m-th row is
%                      equal to [(dFm(zk)/dz); (dFm(zk)/d(conj(z)))]^T,
%                      where Fm is the m-th component of F.
%
%      Method 2: general large-scale problems.
%      nlsb_gndl(F,dF,lb,ub,z0) where F(z) returns a column vector of
%      complex residuals and dF is a structure containing:
%
%         dF.dzx     - The function dF.dzx(zk,x,'notransp') should return
%                      the matrix-vector product [dF(zk)/d(z^T)]*x and
%                      dF.dzx(zk,x,'transp') should return the
%                      matrix-vector product [dF(zk)/d(z^T)]'*x.
%         dF.dconjzx - The function dF.dconjzx(zk,x,'notransp') should
%                      return the matrix-vector product
%                      [dF(zk)/d(conj(z)^T)]*x and
%                      dF.dconjzx(zk,x,'transp') should return the matrix-
%                      vector product [dF(zk)/d(conj(z)^T)]'*x.
%
%   2. F is function only of z.
%
%      Method 1: analytic medium-scale problems.
%      nlsb_gndl(F,dF,lb,ub,z0) where F(z) returns a column vector of
%      complex residuals. Set dF equal to the string 'Jacobian' for
%      automatic numerical approximation of the Jacobian, respectively. Or,
%      supply the Jacobian manually with a structure dF containing:
%
%         dF.dz      - The function dF.dz(zk) should return the Jacobian
%                      dF(zk)/d(z^T), which is defined as the matrix in
%                      which the m-th row is equal to (dFm(zk)/dz)^T, where
%                      Fm is the m-th component of F.
%
%      Method 2: analytic large-scale problems.
%      nlsb_gndl(F,dF,lb,ub,z0) where F(z) returns a column vector of
%      complex residuals and dF is a structure containing:
%
%         dF.dzx     - The function dF.dzx(zk,x,'notransp') should return
%                      the matrix-vector product [dF(zk)/d(z^T)]*x and
%                      dF.dzx(zk,x,'transp') should return the matrix-
%                      vector product [dF(zk)/d(z^T)]'*x.
%
%      Method 3: analytic problems in a modest number of variables z and
%                large number of residuals F(z).
%      nlsb_gndl(f,dF,lb,ub,z0) where f(z) := 0.5*(F(z)'*F(z)) and dF is a
%      structure containing:
%
%         dF.JHF     - The function dF.JHF(zk) should return
%                      [dF(zk)/d(z^T)]'*F(zk), which is also equal to
%                      2*df(zk)/d(conj(z)) = 2*conj(df(zk)/d(z)) if z is
%                      complex, or equal to df(xk)/dx if it is real.
%         dF.JHJ     - The function dF.JHF(zk) should return the Gramian
%                      [dF(zk)/d(z^T)]'*[dF(zk)/d(z^T)].
%
%      Method 4: analytic problems in a large number of variables z and
%                large number of residuals F(z).
%      nlsb_gndl(f,dF,lb,ub,z0) where f(z) := 0.5*(F(z)'*F(z)) and dF is a
%      structure containing:
%
%         dF.JHF     - The function dF.JHF(zk) should return
%                      [dF(zk)/d(z^T)]'*F(zk), which is also equal to
%                      2*df(zk)/d(conj(z)) = 2*conj(df(zk)/d(z)) if z is
%                      complex, or equal to df(xk)/dx if it is real.
%         dF.JHJx    - The function dF.JHF(zk,x) should return the matrix-
%                      vector product ([dF(zk)/d(z^T)]'*[dF(zk)/d(z^T)])*x.
%
%   The structure output returns additional information:
%
%      output.alpha        - The plane search step lengths in every
%                            iteration, if a plane search is selected.
%      output.cgiterations - The number of CG/LSQR iterations to compute
%                            the Gauss-Newton step in every iteration.
%                            (large-scale methods only).
%      output.cgrelres     - The relative residual norm of the computed
%                            Gauss-Newton step (large-scale methods only).
%      output.delta        - The trust region radius at every step attempt.
%      output.fval         - The value of the objective function f in every
%                            iteration.
%      output.info         - The circumstances under which the procedure
%                            terminated:
%                               1: Objective function tolerance reached.
%                               2: Step size tolerance reached.
%                               3: Maximum number of iterations reached.
%      output.infops       - The circumstances under which the plane search
%                            terminated in every iteration.
%      output.iterations   - The number of iterations.
%      output.relfval      - The difference in objective function value
%                            between every two successive iterates,
%                            relativeto its initial value.
%      output.relstep      - The step size relative to the norm of the
%                            current iterate in every iteration.
%      output.rho          - The trustworthiness at every step attempt.
%
%   nlsb_gndl(F,dF,lb,ub,z0,options) may be used to set the following
%   options:
%
%      options.CGMaxIter = 15     - The maximum number of CG/LSQR
%                                   iterations for computing the
%                                   Gauss-Newton step (large-scale methods
%                                   only).
%      options.CGTol = 1e-6       - The tolerance for the CG/LSQR method to
%                                   compute the Gauss-Newton step
%                                   (large-scale methods only).
%      options.Delta =            - The initial trust region radius. If
%      0.3*max(1,norm(z0))          equal to NaN, the initial radius will
%                                   be equal to length of the first
%                                   Gauss-Newton step.
%      options.Display = 1        - Displays the objective function value,
%                                   its difference with the previous
%                                   iterate relative to the first iterate
%                                   and the relative step size each
%                                   options.Display iterations. Set to 0 to
%                                   disable.
%      options.JHasFullRank       - If set to true, the Gauss-Newton step
%      = false                      is computed as a least squares
%                                   solution, if possible. Otherwise, it is
%                                   computed using a more expensive
%                                   pseudo-inverse.
%      options.MaxIter = 200      - The maximum number of iterations.
%      options.PlaneSearch        - The plane search used to minimize the
%      = false                      objective function in the plane spanned
%                                   by the steepest descent direction and
%                                   the Gauss-Newton step. Disables dogleg
%                                   trust region strategy. The method
%                                   should have the function signature
%                                   options.PlaneSearch(F,dF,z,p1,p2, ...
%                                   state,options.PlaneSearchOptions).
%      options.PlaneSearchOptions - The options structure passed to the
%                                   plane search search routine.
%      options.TolFun = 1e-12     - The tolerance for output.relfval. Note
%                                   that because the objective function is
%                                   a squared norm, TolFun can be as small
%                                   as eps^2.
%      options.TolX = 1e-6        - The tolerance for output.relstep.

%   Authors: Laurent Sorber (Laurent.Sorber@cs.kuleuven.be)
%            Marc Van Barel (Marc.VanBarel@cs.kuleuven.be)
%            Lieven De Lathauwer (Lieven.DeLathauwer@kuleuven-kulak.be)
%
%   References:
%   [1] L. Sorber, M. Van Barel, L. De Lathauwer, "Unconstrained
%       optimization of real functions in complex variables", SIAM J. Opt.,
%       Vol. 22, No. 3, 2012, pp. 879-898.
%   [2] C.T. Kelley, "Iterative methods for optimization," SIAM Frontiers
%       in Applied Mathematics, No. 18, 1999.

% Check the objective function f, derivative dF and first iterate z0.
if ~isa(F,'function_handle')
    error('nlsb_gndl:F','The first argument must be a function.');
end
if ischar(dF)
    type = dF;
    if strcmp(type,'Jacobian-C'), fld = 'dzc'; else fld = 'dz'; end
    dF = struct(fld,@derivjac);
end
if ~isstruct(dF)
    error('nlsb_gndl:dF','Second argument not valid.');
else
    if isfield(dF,'dzc')
        method = 'F+dFdzc';
    elseif isfield(dF,'dzx') && isfield(dF,'dconjzx')
        method = 'F+dFdzx+dFdconjzx';
    elseif isfield(dF,'dz')
        method = 'F+dFdz';
    elseif isfield(dF,'dzx')
        method = 'F+dFdzx';
    elseif isfield(dF,'JHJ')  && isfield(dF,'JHF')
        method = 'f+JHJ+JHF';
        f = F;
    elseif isfield(dF,'JHJx') && isfield(dF,'JHF')
        method = 'f+JHJx+JHF';
        f = F;
    else
        error('nlsb_gndl:dF', ...
             ['The structure dF should supply [dF.dzc] or ' ...
              '[dF.dzx and dF.dconjzx] or [dF.dz] or [dF.dzx] or ' ...
              '[dF.JHJ and dF.JHF] or [dF.JHJx and dF.JHF].']);
    end
end

% Define projection and reduction operators.
function z = proj(z)
    z = median([real(lb) real(z) real(ub)],2)+ ...
        median([imag(lb) imag(z) imag(ub)],2)*1i;
end
function H = red(H,A)
    H = bsxfun(@times,H,~A(1:size(H,1)));
    H = bsxfun(@times,H,~A(1:size(H,1)).');
    H(1:size(H,1)+1:end) = H(1:size(H,1)+1:end)+A(1:size(H,1));
end

% Evaluate the function value at z0.
lb = serialize(lb);
ub = serialize(ub);
dim = structure(z0);
z0 = proj(serialize(z0));
A = [real(z0)<=real(lb) | real(z0)>=real(ub) ...
     imag(z0)<=imag(lb) | imag(z0)>=imag(ub)];
z = deserialize(z0,dim);
switch method
    case {'F+dFdzc','F+dFdzx+dFdconjzx','F+dFdz','F+dFdzx'}
        Fval = F(z); Fval = Fval(:);
        fval = 0.5*sum(Fval'*Fval);
    case {'f+JHJ+JHF','f+JHJx+JHF'}
        fval = f(z);
end

% Numerical approximaton of complex derivatives.
function J = derivjac(zk)
    J = deriv(F,zk,Fval,type);
end

% In the case 'F+dFdzx+dFdconjzx', compute a reduced version of J'*(J*x).
function y = JH_Jx(x)
    x1 = ~A(:,1).*x(1:end/2)+~A(:,2).*x(end/2+1:end)*1i;
    dFdzx = dF.dzx(z,x1,'notransp');
    dFdconjzconjx = dF.dconjzx(z,conj(x1),'notransp');
    y = real(dFdzx)+real(dFdconjzconjx)+ ...
        (imag(dFdzx)+imag(dFdconjzconjx))*1i;
	dFdzx = dF.dzx(z,y,'transp');
    dFdconjzconjx = dF.dconjzx(z,y,'transp');
    y = [~A(:,1).*(real(dFdzx)+real(dFdconjzconjx)); ...
         ~A(:,2).*(imag(dFdzx)-imag(dFdconjzconjx))];
    y(A(:)) = x(A(:));
end

% In the case 'F+dFdzx', compute a reduced version of dFdz'*(dFdz*x).
function y = dFdzH_dFdzx(x)
    x1 = ~A(:,1).*x(1:end/2)+~A(:,2).*x(end/2+1:end)*1i;
    y = dF.dzx(z,x1,'notransp');
    y = dF.dzx(z,y,'transp');
    y = [~A(:,1).*real(y);~A(:,2).*imag(y)];
    y(A(:)) = x(A(:));
end

% In the case 'f+JHJx+JHF', compute a reduced version of JHJ*x.
function y = JHJx(x)
    x1 = ~A(:,1).*x(1:end/2)+~A(:,2).*x(end/2+1:end)*1i;
    y = dF.JHJx(z,x1);
    y = [~A(:,1).*real(y);~A(:,2).*imag(y)];
    y(A(:)) = x(A(:));
end

% Modify the preconditioner, if available.
if isfield(dF,'M') && ~isempty(dF.M), dF.PC = @PC; else dF.PC = []; end
function x = PC(b)
    x = dF.M(z,b(1:end/2)+b(end/2+1:end)*1i);
    x = [real(x);imag(x)];
end

% Check the options structure.
if nargin < 6, options = struct; end
if ~isfield(options,'CGMaxIter'), options.CGMaxIter = 15; end
if ~isfield(options,'CGTol'), options.CGTol = 1e-6; end
if ~isfield(options,'Delta'), options.Delta = 0.3*max(1,norm(z0)); end
if ~isfield(options,'Display'), options.Display = 1; end
if ~isfield(options,'JHasFullRank'), options.JHasFullRank = false; end
if ~isfield(options,'MaxIter'), options.MaxIter = 200; end
if ~isfield(options,'PlaneSearch'), options.PlaneSearch = false; end
if ~isfield(options,'PlaneSearchOptions')
    options.PlaneSearchOptions = struct;
end
if ~isfield(options,'TolFun'), options.TolFun = 1e-12; end
if ~isfield(options,'TolX'), options.TolX = 1e-6; end

% Gauss-Newton with dogleg trust region.
output.alpha = [];
output.cgiterations = [];
output.cgrelres = [];
output.delta = options.Delta;
output.fval = fval;
output.info = false;
output.infops = [];
output.iterations = 0;
output.relfval = [];
output.relstep = [];
output.rho = [];
while ~output.info

    % Compute the (in)exact Gauss-Newton step pgn.
    switch method
        case 'F+dFdzc'
            % Compute the Gauss-Newton step pgn.
            dFdzc = dF.dzc(z);
            dFdz = dFdzc(:,1:end/2);
            dFdconjz = dFdzc(:,end/2+1:end);
            J = [real(dFdz)+real(dFdconjz),imag(dFdconjz)-imag(dFdz); ...
                 imag(dFdz)+imag(dFdconjz),real(dFdz)-real(dFdconjz)];
            grad = dFdz'*Fval+dFdconjz.'*conj(Fval);
            if options.JHasFullRank || issparse(J)
                pgn = red(J'*J,A)\[-real(grad);-imag(grad)];
            else
                pgn = pinv(red(J'*J,A))*[-real(grad);-imag(grad)];
            end
            pgn = pgn(1:end/2)+pgn(end/2+1:end)*1i;
            % Compute the Cauchy point pcp = -alpha*grad.
            gg = grad'*grad;
            gBg = dFdz*grad+dFdconjz*conj(grad);
            gBg = gBg'*gBg;
            alpha = gg/gBg;
        case 'F+dFdzx+dFdconjzx'
            % Compute the Cauchy point pcp = -alpha*grad.
            grad = dF.dzx(z,Fval,'transp')+ ...
                   conj(dF.dconjzx(z,Fval,'transp'));
            gg = grad'*grad;
            gBg = dF.dzx(z,grad,'notransp')+ ...
                  dF.dconjzx(z,conj(grad),'notransp');
            gBg = gBg'*gBg;
            alpha = gg/gBg;
            if ~isfinite(alpha), alpha = 1; end;
            % Compute the Gauss-Newton step pgn.
            [pgn,~,output.cgrelres(end+1),output.cgiterations(end+1)] = ...
                mpcg(@JH_Jx,[-real(grad);-imag(grad)], ...
                     options.CGTol,options.CGMaxIter,dF.PC,[], ...
                     -alpha*[real(grad);imag(grad)]);
            pgn = pgn(1:end/2)+pgn(end/2+1:end)*1i;
        case 'F+dFdz'
            % Compute the Gauss-Newton step pgn.
            dFdz = dF.dz(z);
            grad = dFdz'*Fval;
            JHJr = dFdz'*dFdz;
            gradr = -grad;
            JHJIsReal = isreal(JHJr);
            if ~JHJIsReal
                JHJr = [real(JHJr) -imag(JHJr); imag(JHJr) real(JHJr)];
                gradr = [-real(grad);-imag(grad)];
            end
            if options.JHasFullRank || issparse(dFdz)
                pgn = red(JHJr,A)\gradr;
            else
                pgn = pinv(red(JHJr,A))*gradr;
            end
            if ~JHJIsReal, pgn = pgn(1:end/2)+pgn(end/2+1:end)*1i; end
            % Compute the Cauchy point pcp = -alpha*grad.
            gg = grad'*grad;
            gBg = dFdz*grad;
            gBg = gBg'*gBg;
            alpha = gg/gBg;
        case 'F+dFdzx'
            % Compute the Cauchy point pcp = -alpha*grad.
            grad = dF.dzx(z,Fval,'transp');
            gg = grad'*grad;
            gBg = dF.dzx(z,grad,'notransp');
            gBg = gBg'*gBg;
            alpha = gg/gBg;
            if ~isfinite(alpha), alpha = 1; end;
            % Compute the Gauss-Newton step pgn.
            [pgn,~,output.cgrelres(end+1),output.cgiterations(end+1)] = ...
                mpcg(@dFdzH_dFdzx,-[real(grad);imag(grad)], ...
                     options.CGTol,options.CGMaxIter,dF.PC,[], ...
                     -alpha*[real(grad);imag(grad)]);
            pgn = pgn(1:end/2)+pgn(end/2+1:end)*1i;
        case 'f+JHJ+JHF'
            % Compute the Gauss-Newton step pgn.
            grad = serialize(dF.JHF(z));
            JHJ = dF.JHJ(z);
            JHJr = JHJ;
            gradr = -grad;
            JHJIsReal = isreal(JHJr);
            if ~JHJIsReal
                JHJr = [real(JHJr) -imag(JHJr); imag(JHJr) real(JHJr)];
                gradr = [-real(grad);-imag(grad)];
            end
            if options.JHasFullRank || issparse(JHJ)
                pgn = red(JHJr,A)\gradr;
            else
                pgn = pinv(red(JHJr,A))*gradr;
            end
            if ~JHJIsReal, pgn = pgn(1:end/2)+pgn(end/2+1:end)*1i; end
            % Compute the Cauchy point pcp = -alpha*grad.
            gg = grad'*grad;
            gBg = real(grad'*JHJ*grad);
            alpha = gg/gBg;
        case 'f+JHJx+JHF'
            % Compute the Cauchy point pcp = -alpha*grad.
            grad = serialize(dF.JHF(z));
            gg = grad'*grad;
            gBg = real(grad'*dF.JHJx(z,grad));
            alpha = gg/gBg;
            if ~isfinite(alpha), alpha = 1; end;
            % Compute the Gauss-Newton step pgn.
            [pgn,~,output.cgrelres(end+1),output.cgiterations(end+1)] = ...
                mpcg(@JHJx,-[real(grad);imag(grad)], ...
                     options.CGTol,options.CGMaxIter,dF.PC,[], ...
                     -alpha*[real(grad);imag(grad)]);
            pgn = pgn(1:end/2)+pgn(end/2+1:end)*1i;
    end

    % Project pgn and pcp.
    if ~all(isfinite(pgn)), pgn = -alpha*grad; end
    pgn = proj(z0+pgn)-z0;
    pcp = proj(z0-alpha*grad)-z0;
    gg = pcp'*pcp;

    % Plane search in the plane spanned by {pgn,pcp}.
    if ~all(isfinite(pgn)), pgn = -alpha*grad; end
    if isa(options.PlaneSearch,'function_handle')
        state = output; state.grad = grad;
        [alpha,outputps] = options.PlaneSearch( ...
            F,dF,z,deserialize(pgn,dim),deserialize(pcp,dim), ...
            state,options.PlaneSearchOptions);
        output.alpha(:,end+1) = alpha;
        if length(alpha) < 3, alpha(3) = 1; end
        p = alpha(1)*pgn+alpha(2)*pcp;
        z1 = deserialize(alpha(3)*(z0+p),dim);
        relstep = norm(p)/norm(z0); if isnan(relstep), relstep = 0; end
        if isfield(outputps,'fval'), fval = outputps.fval;
        else
            switch method
                case {'F+dFdzc','F+dFdzx+dFdconjzx','F+dFdz','F+dFdzx'}
                    Fval = F(z); fval = 0.5*sum(Fval(:)'*Fval(:));
                case {'f+JHJ+JHF','f+JHJx+JHF'}
                    fval = f(z);
            end
        end
        if isfield(outputps,'info')
            output.infops(end+1) = outputps.info;
        end
        rho = 1;
    else
        rho = -inf;
    end

    % Dogleg trust region.
    normpgn = norm(pgn);
    if isnan(output.delta(end)), output.delta(end) = max(1,normpgn); end
    while rho <= 0

        % Compute the dogleg step p.
        % Assume the projection did not alter the pgn or pcp too much,
        % computing dfval's would otherwise be quite expensive.
        delta = output.delta(end);
        if normpgn <= delta
            p = pgn;
        elseif sqrt(gg) >= delta
            p = delta/sqrt(gg)*pcp;
        else
            bma = pgn-pcp; bmabma = bma'*bma;
            c = real(pcp'*bma);
            if c <= 0
                beta = (-c+sqrt(c^2+bmabma*(delta^2-gg)))/bmabma;
            else
                beta = (delta^2-gg)/(c+sqrt(c^2+bmabma*(delta^2-gg)));
            end
            p = pcp+beta*bma;
        end

        % Estimate objective function improvement.
        % Because of projection, we can not avoid a matrix-vector product.
        switch method
            case 'F+dFdzc'
                dfval = dFdz*p+dFdconjz*conj(p);
                dfval = -real(p'*grad)-0.5*(dfval'*dfval);
            case 'F+dFdzx+dFdconjzx'
                dfval = dF.dzx(z,p,'notransp')+ ...
                        dF.dconjzx(z,conj(p),'notransp');
                dfval = -real(p'*grad)-0.5*(dfval'*dfval);
            case 'F+dFdz'
                dfval = dFdz*p;
                dfval = -real(p'*grad)-0.5*(dfval'*dfval);
            case 'F+dFdzx'
                dfval = dF.dzx(z,p,'notransp');
                dfval = -real(p'*grad)-0.5*(dfval'*dfval);
            case 'f+JHJ+JHF'
                dfval = -real(p'*grad)-0.5*real(p'*JHJ*p);
            case 'f+JHJx+JHF'
                dfval = -real(p'*grad)-0.5*real(p'*dF.JHJx(z,p));
        end

        % Compute the trustworthiness rho.
        if dfval > 0
            z1 = deserialize(z0+p,dim);
            switch method
                case {'F+dFdzc','F+dFdzx+dFdconjzx','F+dFdz','F+dFdzx'}
                    Fval = F(z1); Fval = Fval(:);
                    fval = 0.5*sum(Fval'*Fval);
                case {'f+JHJ+JHF','f+JHJx+JHF'}
                    fval = f(z1);
            end
            rho = (output.fval(end)-fval)/dfval;
            if isnan(rho), rho = -inf; end
            output.rho(end+1) = rho;
        end

        % Update trust region radius delta.
        if rho > 0.5
            output.delta(end+1) = max(delta,2*norm(p));
        else
            sigma = (1-0.25)/(1+exp(-14*(rho-0.25)))+0.25;
            if normpgn < sigma*delta && rho < 0
                e = ceil(log2(normpgn/delta)/log2(sigma));
                output.delta(end+1) = sigma^e*delta;
            else
                output.delta(end+1) = sigma*delta;
            end
        end

        % Check for convergence.
        relstep = norm(p)/norm(z0); if isnan(relstep), relstep = 0; end
        if rho <= 0 && relstep <= options.TolX
            output.rho(end+1) = rho;
            fval = output.fval(end);
            break;
        end

    end

    % Save current state.
    if rho > 0
        z = z1;
        z0 = serialize(z);
        A = [real(z0)<=real(lb) | real(z0)>=real(ub) ...
             imag(z0)<=imag(lb) | imag(z0)>=imag(ub)];
    end

    % Update the output structure.
    output.fval(end+1) = fval;
    output.iterations = output.iterations+1;
    output.relfval(end+1) = ...
        abs(diff(output.fval(end:-1:end-1)))/abs(output.fval(1));
    output.relstep(end+1) = relstep;
    if output.relfval(end) <= options.TolFun, output.info = 1; end
    if output.relstep(end) <= options.TolX, output.info = 2; end
    if output.iterations >= options.MaxIter, output.info = 3; end

    % Display progress.
    if options.Display > 0 && (output.iterations == 1 || output.info || ...
       mod(output.iterations,options.Display) == 0)
        if output.iterations == 1
            bold = '%s';
            [~,~,~,~,v] = regexp(version('-release'),'([0-9]+)([ab])');
            if usejava('Desktop') && str2double(v{1}{1}) > 2011 || ...
               (str2double(v{1}{1}) == 2011 && strcmpi(v{1}{2},'b'))
                bold = '<strong>%s</strong>';
            end
        end
        if output.iterations == 1 || ...
           mod(output.iterations,15*options.Display) == 0
            fprintf('\n%7s%s','',sprintf(bold,'fval'));
            fprintf('%13s%s','',sprintf(bold,'relfval'));
            fprintf('%10s%s','',sprintf(bold,'relstep'));
            if isa(options.PlaneSearch,'function_handle')
                fprintf('%10s%s','',sprintf(bold,'alpha'));
            else
                fprintf('%10s%s','',sprintf(bold,'delta'));
                fprintf('%8s%s','',sprintf(bold,'rho'));
            end
            fprintf('\n%21s%9s = %4.e %6s = %4.e\n\n','=1/2*norm(F)^2', ...
                    'TolFun',options.TolFun,'TolX',options.TolX);
        end
        if output.iterations == 1
            fprintf('%4i: % 14.8e |\n',0,output.fval(1));
        end
        if isa(options.PlaneSearch,'function_handle')
            stralpha = [repmat('%10.4e ',1,size(output.alpha,1)) '\n'];
            fprintf(['%4i: % 14.8e | %14.8e | %14.8e | ' stralpha], ...
                    output.iterations,output.fval(end), ...
                    output.relfval(end),output.relstep(end), ...
                    abs(output.alpha(:,end)));
        else
            fprintf(['%4i: % 14.8e | %14.8e | %14.8e | ' ...
                     '%10.4e | %10.4e\n'],...
                    output.iterations,output.fval(end), ...
                    output.relfval(end),output.relstep(end), ...
                    output.delta(end),output.rho(end));
        end
    end

end

% Display termination message.
if options.Display > 0
    ahref = '\n%s\n\n';
    x = round(linspace(0,output.iterations,min(500,output.iterations)));
    if length(bold) > 2
        ahref = sprintf(['\n<a href="matlab:semilogy(%s,%s);' ...
            'xlabel(''iteration'');legend(''fval'',' ...
            '''relfval'',''relstep'')">%%s</a>\n\n'],mat2str(x'), ...
            mat2str([output.fval(x+1)' [nan output.relfval(x(2:end))]' ...
                    [nan output.relstep(x(2:end))]'],3));
    end
    switch output.info
        case 1, fprintf(ahref,'Objective function tolerance reached.');
        case 2, fprintf(ahref,'Step size tolerance reached.');
        case 3, fprintf(ahref,'Maximum number of iterations reached.');
    end
end

end

function [z,offset] = deserialize(z,dim,offset)
    if iscell(dim)
        v = z;
        z = cell(size(dim));
        if nargin < 3, offset = 0; end
        for i = 1:numel(z)
            if iscell(dim{i})
                [z{i},offset] = deserialize(v,dim{i},offset);
            else
                n = prod(dim{i}(:));
                z{i} = reshape(v(offset+(1:n)),dim{i});
                offset = offset+n;
            end
        end
    elseif ~isempty(dim)
        z = reshape(z,dim);
    end
end

function z = serialize(z)
    if iscell(z)
        for i = find(cellfun(@iscell,z(:).'))
            z{i} = serialize(z{i});
        end
        s = cellfun(@numel,z(:)); o = [0; cumsum(s)];
        c = z; z = zeros(o(end),1);
        for i = 1:length(s), z(o(i)+(1:s(i))) = c{i}(:); end
    else
        z = z(:);
    end
end

function dim = structure(z)
    if iscell(z)
        dim = cellfun(@size,z,'UniformOutput',false);
        for i = find(cellfun(@iscell,z(:).'))
            dim{i} = structure(z{i});
        end
    else
        dim = size(z);
        if numel(z) == dim(1), dim = []; end
    end
end