function [minFlux, maxFlux, optsol, ret, fbasol, fvamin, fvamax, statussolmin, statussolmax] = fastFVA(model, optPercentage, osenseStr, solverName, rxnsList, matrixAS, cpxControl, strategy, rxnsOptMode, printLevel)
% Flux variablity analysis optimized for the CPLEX solver.
% Solves LPs of the form:
%
% .. math::
%
%    \forall ~ v_j: ~&~ max/min ~&~ v_j\\
%                 ~&~ s.t.    ~&~ Sv = b\\
%                 ~&~         ~&~ l_b \leq v \leq u_b
%
% If the optional fields are supplied, following LPs are solved
%
% .. math::
%
%    \forall ~ v_j: ~&~ max/min ~&~ v_j\\
%                 ~&~ s.t.    ~&~ Av (c_sense) b\\
%                 ~&~         ~&~ l_b \leq v \leq u_b
%
% fastFVA returns vectors for the initial FBA in FBASOL together with matrices FVAMIN and
% FVAMAX containing the flux values for each individual min/max problem.
% Note that for large models the memory requirements may become prohibitive.
% To save large `fvamin` and `fvamax` matrices, toggle v7.3 in Preferences -> General -> MAT-Files
%
% If a `rxnsList` vector is specified then only the corresponding entries in
% minFlux and maxFlux are defined (all remaining entries are zero).
%
% USAGE:
%
%    [minFlux, maxFlux, optsol, ret, fbasol, fvamin, fvamax, statussolmin, statussolmax] = fastFVA(model, optPercentage, osenseStr, solverName, rxnsList, matrixAS, cpxControl, strategy, rxnsOptMode)
%
% INPUTS:
%   model:             COBRA model structure
%
%                        * .S - (required) Stoichiometric matrix
%                        * .b - (required) Right hand side vector
%                        * .c - (required) Objective coefficients
%                        * .lb - (required) Lower bounds
%                        * .ub - (required) Upper bounds
%                        * .A - (optional) Stoichiometric matrix (with constraints)
%                        * .csense - (optional) Type of constraints, `csense` is a vector with elements `E` (equal), `L` (less than) or `G` (greater than).
%   optPercentage:     Only consider solutions that give you at least a certain
%                      percentage of the optimal solution (default = `100`, equivalent to optimal solutions only)
%   osenseStr:         Objective ('min' or 'max') (default 'max')
%   solverName:        name of the solver, default: `ibm_cplex`
%
% OPTIONAL INPUTS:
%   matrixAS:          `A` or `S` - choice of the model matrix, coupled (A) or uncoupled (S)
%   cpxControl:        Parameter set of CPLEX loaded externally
%   rxnsList:          List of reactions to analyze (default all rxns, i.e. `1:length(model.rxns)``)
%   strategy:          Paralell distribution strategy of reactions among workers
%
%                       * 0 = Blind splitting: default random distribution
%                       * 1 = Extremal dense-and-sparse splitting: every worker receives dense and sparse reactions, starting from both extremal indices of the sorted column density vector
%                       * 2 = Central dense-and-sparse splitting: every worker receives dense and sparse reactions, starting from the beginning and center indices of the sorted column density vector
%   rxnsOptMode:       List of min/max optimizations to perform:
%                       * 0 = only minimization;
%                       * 1 = only maximization;
%                       * 2 = minimization & maximization;
%   printLevel:        Verbose level (default: 1)
%                       * 0 = mute
%                       * 1 = default
%
% OUTPUTS:
%   minFlux:           Minimum flux for each reaction
%   maxFlux:           Maximum flux for each reaction
%   optsol:            Optimal solution (of the initial FBA)
%   ret:               Zero if success (global return code from FVA)
%
% OPTIONAL OUTPUTS:
%   fbasol:            Initial FBA in FBASOL
%   fvamin:            matrix with flux values for the minimization problem
%   fvamax:            matrix with flux values for the maximization problem
%   statussolmin:      vector of solution status for each reaction (minimization)
%   statussolmax:      vector of solution status for each reaction (maximization)
%
%
% EXAMPLE:
%
%    load modelRecon1Biomass.mat % Human reconstruction network (Recon1)
%    setWorkerCount(4);
%    [minFlux,maxFlux] = fastFVA(model, 90);
%
% NOTE:
%
%    S. Gudmundsson and I. Thiele, Computationally efficient
%    Flux Variability Analysis. BMC Bioinformatics, 2010, 11:489
%
% NOTE:
%
%    * Matlab R2014a fully tested on UNIX and DOS Systems
%    * Matlab R2015b throws compatibility errors with CPLEX 12.6.3 on DOS Systems
%    * Matlab R2016b and the MinGW64 compiler are not compatible with the CPLEX 12.6.3 library
%
%    The version of fastFVA only supports the CPLEX solver. The code has been tested with
%    CPLEX 12.6.2, 12.6.3, 12.7.0 and 12.7.1. Install
%    CPLEX (64-bit) as explained `here`_.
%    A particular interface, such as TOMLAB, is not needed in order to run fastFVA.
%    Please note that only the 64-bit versions of CPLEX 12.7.1 are supported.
%    In order to run the code on 32-bit systems, the appropriate MEX files need to be generated
%    using generateMexFastFVA().
%
% .. _here: https://opencobra.github.io/cobratoolbox/docs/solvers.html
%
% .. Authors:
%       - Original author: Steinn Gudmundsson.
%       - Contributor: Laurent Heirendt, LCSB
%
% .. Last updated: October 2016

global CBTDIR

% save the userpath
originalUserPath = path;

% save the current directory
currentDir = pwd;

% the root path must be the root directory as the path to the logFiles is hard-coded
cd(CBTDIR);

% determine the printLevel
if (nargin < 10 || isempty(printLevel))
    printLevel = 1;
end

% determine the latest installed CPLEX version
cplexVersion = getCobraSolverVersion('ibm_cplex', printLevel);

% check if the provided fastFVA binaries are compatible with the current system configuration
[newestCplexBin, throwBinGenerationError]= checkFastFVAbin(cplexVersion);

% set a random log filename to avoid overwriting ongoing runs
rng('shuffle');
filenameParfor = ['parfor_progress_', datestr(now, 30), '_', num2str(randi(9)), '.txt'];
filenameParfor = [CBTDIR filesep '.tmp' filesep filenameParfor];

% turn on the load balancing for large problems
loadBalancing = 0;  % 0: off; 1: on

% define if information about the work load distriibution will be shown or not
showSplitting = 1;

% define the input arguments
if (nargin < 8 || isempty(strategy))
    strategy = 0;
end
if (nargin < 7 || isempty(cpxControl))
    cpxControl = struct([]);
end
if (nargin < 6 || isempty(matrixAS)) || ~isfield(model,'C')
    matrixAS = 'S';
end
if (nargin < 5 || isempty(rxnsList))
    rxns = 1:length(model.rxns);
    rxnsList = model.rxns;
else
    % check here if the vector of rxns is sorted or not
    % this needs to be fixed to sort the flux vectors accordingly
    % as the find() function sorts the reactions automatically
    % ->> this is currently an issue on git
    [~, indexRxns] = ismember(model.rxns, rxnsList);
    nonZeroIndices = [];
    for i = 1:length(indexRxns)
        if indexRxns(i) ~= 0
            nonZeroIndices = [nonZeroIndices, indexRxns(i)];
        end
    end
    if issorted(nonZeroIndices) == 0
        error('\n-- ERROR:: Your input reaction vector is not sorted. Please sort your reaction vector first.\n\n')
    end

    rxns = find(ismember(model.rxns, rxnsList))';  % transpose rxns

end
if (nargin < 9 || isempty(rxnsOptMode))
    rxnsOptMode = 2 * ones(length(rxns), 1)';  % status = 2 (min & max) for all reactions
end
if (nargin < 4 || isempty(solverName))
    solverName = 'ibm_cplex';
end
if (nargin < 3 || isempty(osenseStr))
    osenseStr = 'max';
end
if (nargin < 2 || isempty(optPercentage))
    optPercentage = 100;
end

% define extra outputs if required
if nargout > 4 && nargout <= 7
    assert(nargout == 7);
    bExtraOutputs = true;
else
    bExtraOutputs = false;
end

% define extra outputs if required
if nargout > 7
    assert(nargout == 9);
    bExtraOutputs1 = true;
else
    bExtraOutputs1 = false;
end

% print a warning when output arguments are not defined.
if nargout ~= 4 && nargout ~= 7 && nargout ~= 9
    if printLevel > 0
        fprintf('\n-- Warning:: You may only ouput 4, 7 or 9 variables.\n\n')
    end
end

% define the osenseStr
if strcmpi(osenseStr, 'max')
    obj = -1;
elseif strcmpi(osenseStr, 'min')
    obj = 1;
else
    error('Unknown osenseStr');
end

% define the solverName
if strcmp('glpk', solverName)
    error('ERROR : GLPK is not (yet) supported as the binaries are not yet available.')
elseif strcmp('ibm_cplex', solverName)
    if throwBinGenerationError
        %attempt to use the newest cplex binaries available, even though
        %they may not work, it is better than nothing
        FVAc = str2func(['cplexFVA' newestCplexBin]);
    else
        FVAc = str2func(['cplexFVA' cplexVersion]);
    end
else
    error(['Solver ', solverName, ' not supported.']);
end
% define the CPLEX parameter set and the associated values - split the struct
namesCPLEXparams = fieldnames(cpxControl);
nCPLEXparams = length(namesCPLEXparams);
valuesCPLEXparams = zeros(nCPLEXparams, 1);
for i = 1:nCPLEXparams
    valuesCPLEXparams(i) = getfield(cpxControl, namesCPLEXparams{i});
end

% create an LP problem
LPproblem = buildLPproblemFromModel(model);

% define the stoichiometric matrix to be solved
if matrixAS == 'A'
    [A,b,csense,lb,ub,c] = deal(LPproblem.A,LPproblem.b,LPproblem.csense,LPproblem.lb,LPproblem.ub,LPproblem.c);
    if printLevel > 0
        fprintf(' >> Solving Model.A. (coupled) - Generalized\n');
    end
else
    if ~isfield(model,'csense')
        model.csense = repmat('E',size(model.S,1),1);
    end
    [A,b,csense,lb,ub,c] = deal(model.S,model.b,model.csense,model.lb,model.ub,model.c);
    if printLevel > 0
        fprintf(' >> Solving Model.S. (uncoupled) \n');
    end
end

if printLevel > 0
    fprintf(' >> The number of arguments is: input: %d, output %d.\n', nargin, nargout);
end

% define the matrix A as sparse in case it is not as
% C code assumes a sparse stochiometric matrix
if ~issparse(A)
    A = sparse(A);
end

% determine the size of the stoichiometric matrix
[m, n] = size(A);
if printLevel > 0
    fprintf(' >> Size of stoichiometric matrix: (%d,%d)\n', m, n);
end

% determine the number of reactions that are considered
nR = length(rxns);
if nR ~= n
    if printLevel > 0
        fprintf(' >> Only %d reactions of %d are solved (~ %1.2f%%).\n', nR, n, nR * 100 / n);
    end
    n = nR;
else
    if printLevel > 0
        fprintf(' >> All reactions are solved (%d reactions - 100%%).\n', n);
    end
end

% output how many reactions are min, max, or both
totalOptMode = length(find(rxnsOptMode == 0));
if totalOptMode == 1
    if printLevel > 0
        fprintf(' >> %d reaction out of %d is minimized (%1.2f%%).\n', totalOptMode, n, totalOptMode * 100 / n);
    end
else
    if printLevel > 0
        fprintf(' >> %d reactions out of %d are minimized (%1.2f%%).\n', totalOptMode, n, totalOptMode * 100 / n);
    end
end

totalOptMode = length(find(rxnsOptMode == 1));
if totalOptMode == 1
    if printLevel > 0
        fprintf(' >> %d reaction out of %d is maximized (%1.2f%%).\n', totalOptMode, n, totalOptMode * 100 / n);
    end
else
    if printLevel > 0
        fprintf(' >> %d reactions out of %d are maximized (%1.2f%%).\n', totalOptMode, n, totalOptMode * 100 / n);
    end
end

totalOptMode = length(find(rxnsOptMode == 2));
if totalOptMode == 1
    if printLevel > 0
        fprintf(' >> %d reaction out of %d is minimized and maximized (%1.2f%%).\n', totalOptMode, n, totalOptMode * 100 / n);
    end
else
    if printLevel > 0
        fprintf(' >> %d reactions out of %d are minimized and maximized (%1.2f%%).\n', totalOptMode, n, totalOptMode * 100 / n);
    end
end

% count the number of workers
poolobj = gcp('nocreate');  % If no pool, do not create new one.
if isempty(poolobj)
    nworkers = 1;
else
    nworkers = poolobj.NumWorkers;
end

% creates the directory where the log files will be generated
rootDirFastFVA = [CBTDIR filesep 'src' filesep 'analysis' filesep 'FVA' filesep 'fastFVA'];

% define the directory to the logFiles and results directories
logFileDir = [rootDirFastFVA filesep 'logFiles'];

% initialisations
istart(1) = 1;
maxFluxTmp = {};
minFluxTmp = {};

% launch fastFVA on 1 core
if nworkers <= 1

    % define the end of the index vector
    iend(1) = n;

    if ~isempty(rxnsList)
        rxnsKey = find(ismember(model.rxns, rxnsList));
    else
        rxnsKey = (1:n);
    end

    % sequential version
    if printLevel > 0
        fprintf(' \n WARNING: The Sequential Version might take a long time.\n\n');
    end
    if bExtraOutputs1
        [minFlux, maxFlux, optsol, ret, fbasol, fvamin, fvamax, statussolmin, statussolmax] = FVAc(c, A, b, csense, lb, ub, ...
                                                                                optPercentage, obj, rxnsKey, ...
                                                                                1, cpxControl, valuesCPLEXparams, rxnsOptMode, logFileDir, printLevel);
    elseif bExtraOutputs
        [minFlux, maxFlux, optsol, ret, fbasol, fvamin, fvamax] = FVAc(c, A, b, csense, lb, ub, ...
                                                                       optPercentage, obj, rxnsKey, ...
                                                                       1, cpxControl, valuesCPLEXparams, rxnsOptMode, logFileDir, printLevel);
    else
        [minFlux, maxFlux, optsol, ret] = FVAc(c, A, b, csense, lb, ub, ...
                                               optPercentage, obj, rxnsKey, ...
                                               1, cpxControl, valuesCPLEXparams, rxnsOptMode, logFileDir, printLevel);
    end

    if ret ~= 0 && printLevel > 0
        if printLevel > 0
            fprintf('Unable to complete the FVA, return code=%d\n', ret);
        end
    end

    % output the minimum and maximum fluxes
    minFluxTmp{1} = minFlux;
    maxFluxTmp{1} = maxFlux;

    % output the minimum and maximum flux vectors
    if bExtraOutputs || bExtraOutputs1
        fvaminRes{1} = fvamin;
        fvamaxRes{1} = fvamax;
        fbasolRes{1} = fbasol;
    end

    % output the solver status
    if bExtraOutputs1
        statussolminRes{1} = statussolmin;
        statussolmaxRes{1} = statussolmax;
    end
else
    % divide the reactions amongst workers

    % Note:
    %      The load balancing can be improved for certain problems, e.g. in case
    %      of problems involving E-type matrices, some workers will get mostly
    %      well-behaved LPs while others may get many badly scaled LPs.

    if n > 5000 && loadBalancing == 1
        % primitive load-balancing strategy for large problems
        nworkers = 4 * nworkers;
        if printLevel > 0
            fprintf(' >> The load is balanced and the number of virtual workers is %d.\n', nworkers);
        end
    end

    nrxn = repmat(fix(n / nworkers), nworkers, 1);
    i = 1;
    while sum(nrxn) < n
        nrxn(i) = nrxn(i) + 1;
        i = i + 1;
    end

    [Nmets, Nrxns] = size(A);
    assert(sum(nrxn) == n);
    iend(1) = nrxn(1);
    for i = 2:nworkers
        istart(i) = iend(i - 1) + 1;
        iend(i) = istart(i) + nrxn(i) - 1;
    end

    startMarker1 = istart;
    endMarker1 = iend;

    startMarker2 = istart;
    endMarker2 = iend;

    % calculate the column density and row density
    NrxnsList = length(rxnsList);

    % initialize the column density vector
    cdVect = zeros(NrxnsList, 1);

    for i = 1:NrxnsList
        tmpRxnID = findRxnIDs(model, rxnsList(i));

        columnDensity = nnz(A(:, tmpRxnID));
        columnDensity = columnDensity / Nmets * 100;
        cdVect(i) = columnDensity;
    end

    [~, indexcdVect] = sort(cdVect, 'descend');

    rxnsVect = linspace(1, NrxnsList, NrxnsList);

    sortedrxnsVect = rxnsVect(indexcdVect);

    if strategy > 0
        pRxnsHalfWorker = ceil(NrxnsList / (2 * nworkers));

        for i = 1:nworkers

            startMarker1(i) = (i - 1) * pRxnsHalfWorker + 1;
            endMarker1(i) = i * pRxnsHalfWorker;

            if strategy == 1
                startMarker2(i) = startMarker1(i) + ceil(NrxnsList / 2);
                endMarker2(i) = endMarker1(i) + ceil(NrxnsList / 2);
            elseif strategy == 2
                startMarker2(i) = ceil(NrxnsList / 2) + startMarker1(i);
                endMarker2(i) = startMarker2(i) + pRxnsHalfWorker + 1;
            end

            % avoid start indices beyond the total number of reactions
            if startMarker1(i) > NrxnsList
                startMarker1(i) = NrxnsList;
            end
            if startMarker2(i) > NrxnsList
                startMarker2(i) = NrxnsList;
            end

            % avoid end indices beyond the total number of reactions
            if endMarker1(i) > NrxnsList
                endMarker1(i) = NrxnsList;
            end
            if endMarker2(i) > NrxnsList
                endMarker2(i) = NrxnsList;
            end

            % avoid flipped chunks
            if startMarker1(i) > endMarker1(i)
                startMarker1(i) = endMarker1(i);
            end
            if startMarker2(i) > endMarker2(i)
                startMarker2(i) = endMarker2(i);
            end
        end
    end

    minFlux = zeros(length(model.rxns), 1);
    maxFlux = zeros(length(model.rxns), 1);
    iopt = zeros(nworkers, 1);
    iret = zeros(nworkers, 1);

    % initialize extra outputs
    if bExtraOutputs || bExtraOutputs1
        fvaminRes = {};
        fvamaxRes = {};
        fbasolRes = {};
    end

    if bExtraOutputs1
        statussolminRes = {};
        statussolmaxRes = {};
    end

    if printLevel > 0
        fprintf('\n -- Starting to loop through the %d workers. -- \n', nworkers);
        fprintf('\n -- The splitting strategy is %d. -- \n', strategy);
    end

    out = parfor_progress(nworkers, filenameParfor);

    parfor i = 1:nworkers

        rxnsKey = 0;  % silence warning

        % preparation of reactionKey
        if strategy == 1 || strategy == 2
            rxnsKey = [sortedrxnsVect(startMarker1(i):endMarker1(i)), sortedrxnsVect(startMarker2(i):endMarker2(i))];
        else
            rxnsKey = rxns(istart(i):iend(i));
        end

        t = getCurrentTask();
        if printLevel > 0
            fprintf('\n----------------------------------------------------------------------------------\n');
        end
        if strategy == 0
            if printLevel > 0
                fprintf('--  Task Launched // TaskID: %d / %d (LoopID = %d) <> [%d, %d] / [%d, %d].\n', ...
                    t.ID, nworkers, i, istart(i), iend(i), m, n);
            end
        else
            if printLevel > 0
                fprintf('--  Task Launched // TaskID: %d / %d (LoopID = %d) <> [%d:%d] & [%d:%d] / [%d, %d].\n', ...
                    t.ID, nworkers, i, startMarker1(i), endMarker1(i), ...
                    startMarker2(i), endMarker2(i), m, n);
            end
        end

        tstart = tic;

        minf = zeros(length(model.rxns), 1);
        maxf = zeros(length(model.rxns), 1);
        fvamin_single = 0; fvamax_single = 0; fbasol_single = 0; statussolmin_single = 0; statussolmax_single = 0;  % silence warnings

        if bExtraOutputs1
            [minf, maxf, iopt(i), iret(i), fbasol_single, fvamin_single, fvamax_single, ...
             statussolmin_single, statussolmax_single] = FVAc(model.c, A, b, csense, model.lb, model.ub, ...
                                                              optPercentage, obj, rxnsKey', ...
                                                              t.ID, cpxControl, valuesCPLEXparams, ...
                                                              rxnsOptMode(istart(i):iend(i)), logFileDir, printLevel);
        elseif bExtraOutputs
            [minf, maxf, iopt(i), iret(i), fbasol_single, fvamin_single, fvamax_single] = FVAc(model.c, A, b, csense, ...
                                                                                model.lb, model.ub, ...
                                                                                optPercentage, obj, rxnsKey', ...
                                                                                t.ID, cpxControl, valuesCPLEXparams, ...
                                                                                rxnsOptMode(istart(i):iend(i)), ...
                                                                                logFileDir, printLevel);
        else
            if printLevel > 0
                fprintf(' >> Number of reactions given to the worker: %d \n', length(rxnsKey));
            end

            [minf, maxf, iopt(i), iret(i)] = FVAc(model.c, A, b, csense, model.lb, model.ub, ...
                                                  optPercentage, obj, rxnsKey', ...
                                                  t.ID, cpxControl, valuesCPLEXparams, ...
                                                  rxnsOptMode(istart(i):iend(i)), logFileDir, printLevel);
        end
        if printLevel > 0
            fprintf(' >> Time spent in FVAc: %1.1f seconds.', toc(tstart));
        end

        if iret(i) ~= 0 && printLevel > 0
            if printLevel > 0
                fprintf('Problems solving partition %d, return code=%d\n', i, iret(i))
            end
        end

        minFluxTmp{i} = minf;
        maxFluxTmp{i} = maxf;

        if bExtraOutputs || bExtraOutputs1
            fvaminRes{i} = fvamin_single;
            fvamaxRes{i} = fvamax_single;
            fbasolRes{i} = fbasol_single;
        end

        if bExtraOutputs1
            statussolminRes{i} = statussolmin_single;
            statussolmaxRes{i} = statussolmax_single;
        end
        if printLevel > 0
            fprintf('\n----------------------------------------------------------------------------------\n');
        end

        % print out the percentage of the progress
        percout = parfor_progress(-1, filenameParfor);

        if percout < 100
            if printLevel > 0
                fprintf(' ==> %1.1f%% done. Please wait ...\n', percout);
            end
        else
            if printLevel > 0
                fprintf(' ==> 100%% done. Analysis completed.\n', percout);
            end
        end
    end

    % aggregate results
    optsol = iopt(1);
    ret = max(iret);
    out = parfor_progress(0, filenameParfor);
end

% aggregate the results for the maximum and minimum flux vectors
for i = 1:nworkers
    % preparation of reactionKey
    if strategy == 1 || strategy == 2
        indices = [sortedrxnsVect(startMarker1(i):endMarker1(i)), sortedrxnsVect(startMarker2(i):endMarker2(i))];
    else
        indices = rxns(istart(i):iend(i));
    end

    % store the minFlux
    tmp = maxFluxTmp{i};
    maxFlux(indices, 1) = tmp(indices);

    % store the maxFlux
    tmp = minFluxTmp{i};
    minFlux(indices, 1) = tmp(indices);
end

if bExtraOutputs || bExtraOutputs1

    if nworkers > 1
        fbasol = fbasolRes{1};  % initial FBA solutions are identical across workers
    end

    fvamin = zeros(length(model.rxns), length(model.rxns));
    fvamax = zeros(length(model.rxns), length(model.rxns));

    if nworkers > 1
        if bExtraOutputs1
            statussolmin = -1 + zeros(length(model.rxns), 1);
            statussolmax = -1 + zeros(length(model.rxns), 1);
        end
    end

    for i = 1:nworkers
        % preparation of reactionKey
        if strategy == 1 || strategy == 2
            indices = [sortedrxnsVect(startMarker1(i):endMarker1(i)), sortedrxnsVect(startMarker2(i):endMarker2(i))];
        else
            indices = rxns(istart(i):iend(i));
        end

        fvamin(:, indices) = fvaminRes{i};
        fvamax(:, indices) = fvamaxRes{i};

        if bExtraOutputs1
            tmp = statussolminRes{i}';
            statussolmin(indices, 1) = tmp(indices);
            tmp = statussolmaxRes{i}';
            statussolmax(indices, 1) = tmp(indices);
        end
    end
end

if strategy == 0 && ~isempty(rxnsList)
    if bExtraOutputs || bExtraOutputs1
        fvamin = fvamin(:, rxns);  % keep only nonzero columns
        fvamax = fvamax(:, rxns);
    end

    if bExtraOutputs1
        statussolmin = statussolmin(rxns);
        statussolmax = statussolmax(rxns);
    end

    minFlux(find(~ismember(model.rxns, rxnsList))) = [];
    maxFlux(find(~ismember(model.rxns, rxnsList))) = [];
end

% restore the original path
path(originalUserPath);
addpath(originalUserPath);

% change back to the original directory
cd(currentDir);

function [newestCplexBin, throwBinGenerationError] = checkFastFVAbin(cplexVersion)
% determine the version of the CPLEX binaries by
% browsing to the folder with the binaries and the .tmp folder (if it exists)
% and retrieve all versions
%
% USAGE:
%    checkFastFVAbin(cplexVersion)
%
% INPUT:
%    cplexVersion:    CPLEX version (string), obtained using `getCobraSolverVersion`
%

global CBTDIR

% retrieve the contents of the binary directory (architecture dependent)
aDir = [CBTDIR filesep 'binary' filesep computer('arch') filesep 'bin' filesep 'fastFVA'];
if 1 %debug
    cd(aDir);
end

d1 = dir(aDir);

% include CPLEX binaries that already have been generated using generateMexFastFVA
tmpDir = [CBTDIR filesep '.tmp'];
d2 = dir(tmpDir);

% create .tmp if not already present
if ~exist(tmpDir, 'dir')
    mkdir(tmpDir);
end

% concatenate both directories
d = {d1; d2};

for p = 1:length(d)
    tmpD = d{p};
    k = 1;
    for i = 1:numel(tmpD)
        if contains(tmpD(i).name, 'cplexFVA') && ~strcmpi(tmpD(i).name, '.') && ~strcmpi(tmpD(i).name, '..') && isempty(strfind(tmpD(i).name, '.txt'))
            tmpName = tmpD(i).name;
            tmpNameSplit = strsplit(tmpName, '.');
            tmpName = tmpNameSplit{1};
            %look for the binary versio
            binVersion{k} = tmpName(length('cplexFVA')+1:end);
            k = k + 1;
        end
    end
end

%compare the binary version of cplexFVA with the version of cplex
throwBinGenerationError = false;
for k = 1:length(binVersion)
    if ~strcmpi(cplexVersion, binVersion{k})
        throwBinGenerationError = true;
        kp = k;
    end
end

if throwBinGenerationError
    maxLenBinVersion=0;
    for k = 1:length(binVersion)
        if maxLenBinVersion<length(binVersion{k})
            maxLenBinVersion=length(binVersion{k});
        end
    end
    for k = 1:length(binVersion)
        tmp(1:maxLenBinVersion)='0';
        tmp(1:length(binVersion{k}))=binVersion{k};
        equalLenbinVersion(k)=str2num(tmp);
    end
    [equalLenbinVersionMax, equalLenbinVersionMaxInd]=max(equalLenbinVersion);
    newestCplexBin=binVersion{equalLenbinVersionMaxInd};

    if 0
    error(['Official binaries are only available for CPLEX version ', binVersion{kp}, '. ', ...
            'You have installed version ', cplexVersion, '. Please run: ', ...
            '>> generateMexFastFVA() in order to generate a new binary file.']);
    else
     warning(['The most recent cplexFVA binaries are only tested for CPLEX version ', newestCplexBin, '. ', ...
            'However, you have installed version ', cplexVersion, '. If you experience an error using fastFVA, you need to run: ', ...
            '>> generateMexFastFVA() in order to generate a new cplexFVA binary file that matches cplex version' cplexVersion]);
    end
end


%                                                     [lab,q,f] = eigK(x,K)
%
% EIGK  Computes the spectral values ("eigenvalues") or even the complete
%       spectral decomposition  of a vector x with respect to a self-dual
%       homogeneous cone K.
%
% > LAB = EIGK(x,K) This yield the spectral values of x with respect to
%       the self-dual homogeneous cone that you describe in the structure
%       K. Up to 3 fields can be used, called K.l, K.q and K.s, for
%       Linear, Quadratic and Semi-definite. Type `help sedumi' for more
%       details on this structure.
%
%       The length of the vector LAB is the order of the cone. Remark that
%       x in K if and only if LAB>=0, and x in int(K) if and only if LAB>0.
%
% > [LAB,Q,F] = EIGK(x,K) Also produces the eigenvectors for the symmetric and
%       Hermitian parts of x (corresponding to K.s), and the Lorentz frame F
%       for the Lorentz blocks in x (corresponding to K.q). This extended
%       version of EIGK is intended for INTERNAL USE BY SEDUMI.
%
% See also sedumi, mat, vec, eyeK.

function [lab,q,f] = eigK(x,K) %#ok

% THE M-FILE VERSION OF THIS FUNCTION IS HERE ONLY AS ILLUSTRATION.
% SEE THE C-SOURCE FOR THE MEX-VERSION.

%
% This file is part of SeDuMi 1.1 by Imre Polik and Oleksandr Romanko
% Copyright (C) 2005 McMaster University, Hamilton, CANADA  (since 1.1)
%
% Copyright (C) 2001 Jos F. Sturm (up to 1.05R5)
%   Dept. Econometrics & O.R., Tilburg University, the Netherlands.
%   Supported by the Netherlands Organization for Scientific Research (NWO).
%
% Affiliation SeDuMi 1.03 and 1.04Beta (2000):
%   Dept. Quantitative Economics, Maastricht University, the Netherlands.
%
% Affiliations up to SeDuMi 1.02 (AUG1998):
%   CRL, McMaster University, Canada.
%   Supported by the Netherlands Organization for Scientific Research (NWO).
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA
% 02110-1301, USA
%

disp('The SeDuMi binaries are not installed.')
disp('In Matlab, launch "install_sedumi" in the folder you put the SeDuMi files.')
disp('For more information see the file Install.txt.')
error(' ')

lab = zeros(K.l + 2*length(K.q) + sum(K.s),1); %#ok
% ----------------------------------------
% LP: lab = x
% ----------------------------------------
lab(1:K.l) = x(1:K.l);
firstk = K.l+1;
nextlab = K.l+1;
% ----------------------------------------
% LORENTZ: (lab, f) = qeig(x)
% ----------------------------------------
if nargout < 3
    for k=1:length(K.q)
        nk = K.q(k); lastk = firstk + nk - 1;
        lab(nextlab:nextlab+1) = qeig(x(firstk:lastk));
        firstk = lastk + 1; nextlab = nextlab + 2;
    end
else
    nextf = 1;
    for k=1:length(K.q)
        nk = K.q(k); lastk = firstk + nk - 1;
        [labk, fk] = qeig(x(firstk:lastk));
        lab(nextlab:nextlab+1) = labk;
        f(nextf:nextf+nk-2) = fk;
        firstk = lastk + 1; nextlab = nextlab + 2; nextf = nextf + nk-1;
    end
end
% ----------------------------------------
% SDP: [q,lab] = eig(x)
% ----------------------------------------
offq = firstk - 1;
q = zeros(length(x)-offq,1);
for k = 1:K.rsdpN
    nk = K.s(k); lastk = firstk + nk*nk - 1;
    Xk = mat(x(firstk:lastk),nk);
    Xk = (Xk + Xk') / 2;
    [Qk, Labk] = eig(Xk);
    lab(nextlab:nextlab+nk-1) = diag(Labk);
    q(firstk-offq:lastk-offq) = Qk;
    firstk = lastk + 1; nextlab = nextlab + nk;
end
for k = (1+K.rsdpN):length(K.s)
    nk = K.s(k); ifirstk = firstk + nk*nk; lastk = ifirstk + nk*nk - 1;
    Xk = mat(x(firstk:ifirstk-1)+sqrt(-1)*x(ifirstk:lastk),nk);
    [Qk, Labk] = eig(Xk);
    lab(nextlab:nextlab+nk-1) = real(diag(Labk));
    q(firstk-offq:ifirstk-1-offq) = real(Qk);
    q(ifirstk-offq:lastk-offq) = imag(Qk);
    firstk = lastk + 1; nextlab = nextlab + nk;
end


function DoFSERecon(Simuh)

global VSig;
global VCtl;
global VObj;
global VCoi;
global VImg;

SX=reshape(VSig.Sx,VCtl.ResFreq * VCtl.FSE_ETL,VCtl.FSE_ShotNum,VCtl.SliceNum,VCoi.RxCoilNum,VObj.TypeNum); % matlab col priority
SY=reshape(VSig.Sy,VCtl.ResFreq * VCtl.FSE_ETL,VCtl.FSE_ShotNum,VCtl.SliceNum,VCoi.RxCoilNum,VObj.TypeNum);

SX=sum(SX, 5); % sum signal from all spin types
SY=sum(SY, 5);
%% FSE k-space recon
% sort k-space & row flipping
Sx=zeros(VCtl.ResFreq,VCtl.FSE_ETL*VCtl.FSE_ShotNum,VCtl.SliceNum,VCoi.RxCoilNum);
Sy=zeros(VCtl.ResFreq,VCtl.FSE_ETL*VCtl.FSE_ShotNum,VCtl.SliceNum,VCoi.RxCoilNum);
for e = 1: VCtl.FSE_ETL
    Sx(:,(e-1)*VCtl.FSE_ShotNum+1: e*VCtl.FSE_ShotNum, :,:) = reshape(SX((e-1)*VCtl.ResFreq+1: e*VCtl.ResFreq, :,:,:), [VCtl.ResFreq ,VCtl.FSE_ShotNum ,VCtl.SliceNum,VCoi.RxCoilNum]);
    Sy(:,(e-1)*VCtl.FSE_ShotNum+1: e*VCtl.FSE_ShotNum, :,:) = reshape(SY((e-1)*VCtl.ResFreq+1: e*VCtl.ResFreq, :,:,:), [VCtl.ResFreq ,VCtl.FSE_ShotNum ,VCtl.SliceNum,VCoi.RxCoilNum]);
end

% default -KxMax -> KxMax, -KyMax -> KyMax
% remove the most positive Kx point for making even number of Kx sample points (used for Matlab default fft)
Sx(end,:,:,:)=[];
Sy(end,:,:,:)=[];
ResFreq = VCtl.ResFreq - 1;

if isfield(VCtl,'ZF_Kx') % Zero Filling of k-space for increasing matrix size, no increae of resolution
    Sx2=zeros(str2double(VCtl.ZF_Kx),str2double(VCtl.ZF_Ky),str2double(VCtl.ZF_Kz(2:end))*VCtl.SliceNum,VCoi.RxCoilNum);
    Sy2=zeros(str2double(VCtl.ZF_Kx),str2double(VCtl.ZF_Ky),str2double(VCtl.ZF_Kz(2:end))*VCtl.SliceNum,VCoi.RxCoilNum);
    for i = 1:VCoi.RxCoilNum
        Sx2(str2double(VCtl.ZF_Kx)/2-ResFreq/2+1:str2double(VCtl.ZF_Kx)/2-ResFreq/2+1+ResFreq-1, ...
            str2double(VCtl.ZF_Ky)/2-(VCtl.FSE_ETL*VCtl.FSE_ShotNum)/2+1:str2double(VCtl.ZF_Ky)/2-(VCtl.FSE_ETL*VCtl.FSE_ShotNum)/2+1+(VCtl.FSE_ETL*VCtl.FSE_ShotNum)-1, ...
            str2double(VCtl.ZF_Kz(2:end))*floor(VCtl.SliceNum/2)-floor(VCtl.SliceNum/2)+1:str2double(VCtl.ZF_Kz(2:end))*floor(VCtl.SliceNum/2)-floor(VCtl.SliceNum/2)+1+VCtl.SliceNum-1,i)=Sx(:,:,:,i);
        Sy2(str2double(VCtl.ZF_Kx)/2-ResFreq/2+1:str2double(VCtl.ZF_Kx)/2-ResFreq/2+1+ResFreq-1, ...
            str2double(VCtl.ZF_Ky)/2-(VCtl.FSE_ETL*VCtl.FSE_ShotNum)/2+1:str2double(VCtl.ZF_Ky)/2-(VCtl.FSE_ETL*VCtl.FSE_ShotNum)/2+1+(VCtl.FSE_ETL*VCtl.FSE_ShotNum)-1, ...
            str2double(VCtl.ZF_Kz(2:end))*floor(VCtl.SliceNum/2)-floor(VCtl.SliceNum/2)+1:str2double(VCtl.ZF_Kz(2:end))*floor(VCtl.SliceNum/2)-floor(VCtl.SliceNum/2)+1+VCtl.SliceNum-1,i)=Sy(:,:,:,i);
    end
    Sx=Sx2;
    Sy=Sy2;
    clear Sx2 Sy2;
end
Sx=permute(Sx,[2 1 3 4]);
Sy=permute(Sy,[2 1 3 4]);

% match XYZ orientation
switch VCtl.ScanPlane
    case 'Axial'
        if  strcmp(VCtl.FreqDir,'A/P')
            Sx=permute(Sx,[2 1 3 4]);
            Sy=permute(Sy,[2 1 3 4]);
        else
            Sx=permute(Sx,[1 2 3 4]);
            Sy=permute(Sy,[1 2 3 4]);
        end
    case 'Sagittal'
        if  strcmp(VCtl.FreqDir,'S/I')
            Sx=permute(Sx,[2 1 3 4]);
            Sy=permute(Sy,[2 1 3 4]);
        else
            Sx=permute(Sx,[1 2 3 4]);
            Sy=permute(Sy,[1 2 3 4]);
        end
    case 'Coronal'
        if  strcmp(VCtl.FreqDir,'L/R')
            Sx=permute(Sx,[1 2 3 4]);
            Sy=permute(Sy,[1 2 3 4]);
        else
            Sx=permute(Sx,[2 1 3 4]);
            Sy=permute(Sy,[2 1 3 4]);
        end
end

S=Sx+1i*Sy;
for i = 1: VCoi.RxCoilNum
    VImg.Real(:,:,:,i)=real(fftshift(ifftn(fftshift(S(:,:,:,i)))));
    VImg.Imag(:,:,:,i)=imag(fftshift(ifftn(fftshift(S(:,:,:,i)))));
    VImg.Mag(:,:,:,i)=abs(VImg.Real(:,:,:,i)+1i*VImg.Imag(:,:,:,i));
    VImg.Phase(:,:,:,i)=angle(VImg.Real(:,:,:,i)+1i*VImg.Imag(:,:,:,i));
end
VImg.Sx(:,:,:,:)=Sx;
VImg.Sy(:,:,:,:)=Sy;

guidata(Simuh.SimuPanel_figure, Simuh);

end


function [pdc, pdcvar, n] = ft_connectivity_pdc(inputdata, varargin)

% FT_CONNECTIVITY_PDC computes partial directed coherence. This function implements
% the metrices described in Baccala et al., Biological Cybernetics 2001, 84(6),
% 463-74. and in Baccala et al., 15th Int.Conf.on DSP 2007, 163-66.
%
% The implemented algorithm has been tested against the implementation in the
% SIFT-toolbox. It yields numerically identical results to what is known there as
% 'nPDC' (for PDC) and 'GPDC' for generalized pdc.
%
% Use as
%   [p, v, n] = ft_connectivity_pdc(inputdata, ...)
%
% The input data should be a spectral transfer matrix organized as
%   Nrpt x Nchan x Nchan x Nfreq (x Ntime),
% where Nrpt can be 1.
%
% Additional optional input arguments come as key-value pairs:
%   'hasjack'   = 0 (default) is a boolean specifying whether the input
%                 contains leave-one-outs, required for correct variance
%                 estimate
%   'invfun'    = 'inv' (default) or 'pinv', the function used to invert the
%                 transfer matrix to obtain the fourier transform of the
%                 MVAR coefficients. Use 'pinv' if the data are
%                 poorly-conditioned.
%   'noisecov'  = matrix containing the covariance of the residuals of the
%                 MVAR model. If this matrix is defined, the function
%                 returns the generalized partial directed coherence.
%   'feedback'  = 'none', 'text', 'textbar', 'dial', 'etf', 'gui' type of feedback showing progress of computation, see FT_PROGRESS
%
% Output arguments:
%   p = partial directed coherence matrix Nchan x Nchan x Nfreq (x Ntime).
%       If multiple observations in the input, the average is returned.
%   v = variance of p across observations.
%   n = number of observations.
%
% Typically, nrpt should be 1 (where the spectral transfer matrix is
% computed across observations. When nrpt>1 and hasjack is true the input
% is assumed to contain the leave-one-out estimates of H, thus a more
% reliable estimate of the relevant quantities.
%
% See also CONNECTIVITY, FT_CONNECTIVITYANALYSIS

% Copyright (C) 2009-2017, Jan-Mathijs Schoffelen
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

hasjack  = ft_getopt(varargin, 'hasjack', 0);
feedback = ft_getopt(varargin, 'feedback', 'none');
invfun   = ft_getopt(varargin, 'invfun', 'inv');
noisecov = ft_getopt(varargin, 'noisecov');

switch invfun
  case {'inv' 'pinv'}
    invfun = str2func(invfun);
  otherwise
    ft_error('unknown specification of inversion-function for the transfer matrix');
end

% crossterms are described by chan_chan_therest
siz = [size(inputdata) 1];
n   = siz(1);

if ~isempty(noisecov)
  ft_progress('init', feedback, 'computing generalized partial directed coherence...');
  scale = diag(sqrt(1./diag(noisecov))); % get the 1./sqrt(var) of the signals
else
  ft_progress('init', feedback, 'computing partial directed coherence...');
  scale = eye(siz(2));
end

% pre-allocate some variables
outsum = zeros(siz(2:end));
outssq = zeros(siz(2:end));

% the mathematics for pdc is most straightforward using the inverse of the
% transfer function
pdim     = prod(siz(4:end));
tmpinput = reshape(inputdata, [siz(1:3) pdim]);
ft_progress('init', feedback, 'inverting the transfer function...');
for k = 1:n
  ft_progress(k/n, 'inverting the transfer function for replicate %d from %d\n', k, n);
  tmp = reshape(tmpinput(k,:,:,:), [siz(2:3) pdim]);
  for m = 1:pdim
    tmp(:,:,m) = scale*invfun(tmp(:,:,m));
  end
  tmpinput(k,:,:,:) = tmp;
end
ft_progress('close');
inputdata = reshape(tmpinput, siz);

for j = 1:n
  ft_progress(j/n, 'computing metric for replicate %d from %d\n', j, n);
  invh   = reshape(inputdata(j,:,:,:,:), siz(2:end));

  den    = sum(abs(invh).^2,1);
  tmppdc = abs(invh)./sqrt(repmat(den, [siz(2) 1 1 1 1]));

  outsum = outsum + tmppdc;
  outssq = outssq + tmppdc.^2;
end
ft_progress('close');

pdc = outsum./n;

if n>1
  if hasjack
    bias = (n-1).^2;
  else
    bias = 1;
  end
  pdcvar = bias*(outssq - (outsum.^2)./n)./(n - 1);
else
  pdcvar = [];
end

% this is to achieve the same convention for all connectivity metrics:
% row -> column
for k = 1:prod(siz(4:end))
  pdc(:,:,k)    = transpose(pdc(:,:,k));
  if ~isempty(pdcvar)
    pdcvar(:,:,k) = transpose(pdcvar(:,:,k));
  end
end


function [CV,CE,I,CN,GD,D] = polygonize(V,F,fun,varargin)
  % Polygonize (contour) an implicit function in the spirit of "An Implicit
  % Surface Polygonizer" [Bloomenthal 1994]
  %
  % [CV,CE] = polygonize(V,F,fun)
  %
  % Inputs:
  %   V  #V by dim list of vertex positions of original mesh
  %   F  #F by dim+1 list of element indices into V
  %   fun  function handle so that zero is the desired level set
  % Outputs:
  %   CV  #CV by dim list of contour mesh vertices
  %   CE  #CE by dim list of facet indices into CV
  %   I  #CE list of indices into F
  %   CN  #CN by dim list of unit normal vectors at vertices
  %

  % default values
  delta = [];
  % Map of parameter names to variable names
  params_to_variables = containers.Map( ...
    {'Delta'}, ...
    {'delta'});
  v = 1;
  while v <= numel(varargin)
    param_name = varargin{v};
    if isKey(params_to_variables,param_name)
      assert(v+1<=numel(varargin));
      v = v+1;
      % Trick: use feval on anonymous funtion to use assignin to this workspace
      feval(@()assignin('caller',params_to_variables(param_name),varargin{v}));
    else
      error('Unsupported parameter: %s',varargin{v});
    end
    v=v+1;
  end

  flipped_order = flipped_tet_orders();

  max_iters = 10;
  D = fun(V);
  interval = @(DF) any(DF>0.0,2) & any(DF<=0.0,2);
  crosses = @(DE) ...
    (DE(:,1)>0 & DE(:,2)<= 0)*-1 + ...
    (DE(:,2)>0 & DE(:,1)<= 0)*1;
  crossing_F = find(interval(D(F)));
  FF = F(crossing_F,:);
  % simplex size
  ss = size(F,2);
  switch ss
  case 3
    allE = [FF(:,[2 3]);FF(:,[3 1]);FF(:,[1 2])];
  case 4
    allE = ...
      [FF(:,1) FF(:,2); ...
       FF(:,1) FF(:,3); ...
       FF(:,1) FF(:,4); ...
       FF(:,2) FF(:,3); ...
       FF(:,2) FF(:,4); ...
       FF(:,3) FF(:,4) ...
       ];
  end
  sE = sort(allE,2);
  [E,~,EMAP] = unique(sE,'rows');
  cross_dir = crosses(D(E));
  crossing = cross_dir ~= 0;
  J = (1:size(E,1))';
  EE = E(crossing,:);
  J(crossing) = 1:size(EE,1);
  % Blasphemy
  switch ss
  case 3
    CE = sort(reshape(crossing(EMAP),[],3).*reshape(J(EMAP),[],3),2);
    CE = CE(:,2:end);
  case 4
    % CE(f,i) = 0 if ith edge of element f does not cross, otherwise
    % CE(f,i) is the index of the unique edge that does cross
    CE = reshape(crossing(EMAP),[],6).*reshape(J(EMAP),[],6);
    % If 3 edges cross then we can surface with a single triangle
    crossE = reshape(cross_dir(EMAP),[],6);
    IT = find(sum(CE>0,2)==3);
    crossT = crossE(IT,:);
    [CT,ST] = sort(CE(IT,:),2);
    CT = CT(:,end-2:end);
    flippedT = [
      -1 -1 -1 0 0 0 4 5 6 1 3 2
      -1 -1 -1 0 0 0 4 5 6 2 1 3
      -1 0 0 1 1 0 2 3 6 1 5 4
      0 -1 0 -1 0 -1 1 3 5 2 4 6
      0 -1 0 -1 0 1 1 3 5 2 4 6
      0 -1 0 1 0 1 1 3 5 2 4 6
      0 0 -1 0 -1 -1 1 2 4 3 6 5
      0 0 -1 0 1 -1 1 2 4 3 6 5
      0 0 1 0 1 -1 1 2 4 3 5 6
      0 0 1 0 1 1 1 2 4 3 5 6
      0 1 0 -1 0 -1 1 3 5 2 6 4
      1 0 0 -1 -1 0 2 3 6 1 4 5
      1 0 0 1 -1 0 2 3 6 1 4 5
      1 0 0 1 1 0 2 3 6 1 4 5
      1 1 1 0 0 0 4 5 6 1 2 3
      1 1 1 0 0 0 4 5 6 2 3 1
    ];
    fT = ismember([crossT ST],flippedT,'rows');
    CT(fT,:) = fliplr(CT(fT,:));

    IQ = find(sum(CE>0,2)==4);
    crossQ = crossE(IQ,:);
    [CQ,SQ] = sort(CE(IQ,:),2);
    CQ = CQ(:,end-3:end);
    flippedQ = [
      -1 -1 0 0 -1 1 3 4 2 1 6 5
      -1 -1 0 0 1 1 3 4 2 1 6 5
      -1 0 -1 -1 0 -1 2 5 1 3 4 6
      -1 0 -1 1 0 -1 2 5 1 3 4 6
      -1 0 -1 1 0 1 2 5 1 3 4 6
      0 -1 -1 -1 -1 0 1 6 3 2 5 4
      0 1 1 -1 -1 0 1 6 2 3 4 5
      0 1 1 -1 1 0 1 6 2 3 4 5
      0 1 1 1 1 0 1 6 2 3 4 5
      1 0 1 1 0 1 2 5 3 1 6 4
      1 1 0 0 -1 -1 3 4 1 2 5 6
      1 1 0 0 -1 1 3 4 1 2 5 6
    ];
    fQ = ismember([crossQ SQ],flippedQ,'rows');
    %% huh. Need to flip _after_ creating triangles.
    %CQT([fQ;fQ],:) = fliplr(CQT([fQ;fQ],:));
    % or switch the middle two...
    CQ(fQ,:) = CQ(fQ,[1 3 2 4]);
    I = [IT;IQ;IQ];
    I = crossing_F(I);
  end
  % Upper and lower bound on barycenteric coordinate locating =0.5
  EEl= zeros(size(EE,1),1);
  EElV = V(EE(:,1),:);
  Dl = D(EE(:,1));
  EEu= ones(size(EE,1),1);
  Du = D(EE(:,2));
  EEuV = V(EE(:,2),:);
  for iter = 1:max_iters
    EEm = (EEl+EEu)/2;
    CV = 0.5*(EElV+EEuV);
    if iter < max_iters
      Dm = fun(CV);
      front = interval([Dl Dm]);
       EEu(front,:) =  EEm(front,:);
      EEuV(front,:) = CV(front,:);
       Du(front,:) =  Dm(front,:);
       EEl(~front,:) =  EEm(~front,:);
      EElV(~front,:) = CV(~front,:);
       Dl(~front,:) =  Dm(~front,:);
    end
  end

  switch ss
  case 4
    % Flip non-deluanay edges in quads... at least.
    CQT = [CQ(:,[3 1 4]);CQ(:,[2 4 1])];
    A = reshape(internalangles(CV,CQT),[],6);
    % Quads with Non-delaunay diagonals.
    nd = (A(:,1)+A(:,4))>pi;
    CQ(nd,:) = CQ(nd,[3 4 1 2]);
    CQ1 = CQ(:,[3 1 4]);
    CQ2 = CQ(:,[2 4 1]);
    CQ1(nd,:) = fliplr(CQ1(nd,:));
    CQ2(nd,:) = fliplr(CQ2(nd,:));
    CQT = [CQ1;CQ2];
    %% Double check: sum should be zero
    % A = reshape(internalangles(CV,CQT),[],6);
    % % Non-delaunay
    % nd = (A(:,1)+A(:,4))>pi;
    % sum(nd)
    CE = fliplr([CT;CQT]);
    assert(size(CE,2) == ss-1);

    %% How I determined what should be flipped:
    % S = sum(normals(CV,CT).*barycenter(CV,CT),2);
    % A = unique([crossT(S<0,:) ST(S<0,:)],'rows');
    % B = unique([crossT(S>0,:) ST(S>0,:)],'rows');
    % intersect(A,B,'rows');
    % fprintf('    flippedT = [\n');
    % fprintf('      %d %d %d %d %d %d %d %d %d %d %d %d\n',A');
    % fprintf('    ];\n');
    %S = sum(normals(CV,CQ(:,[1 4 3])).*barycenter(CV,CQ(:,[1 4 3])),2);
    %A = unique([crossQ(S<0,:) SQ(S<0,:)],'rows');
    %B = unique([crossQ(S>0,:) SQ(S>0,:)],'rows');
    %intersect(A,B,'rows')
    %fprintf('    flippedQ = [\n');
    %fprintf('      %d %d %d %d %d %d %d %d %d %d %d %d\n',A');
    %fprintf('    ];\n');
  end

  if nargout>=4
    if isempty(delta)
      %  following Bloomenthal's "An Implicit Surface Polygonizer" 1994
      %
      delta = avgedge(V,F)/max_iters^2;
      % More accurate...
      delta = delta*0.0001;
    end
    % Approximate the gradient with finite differences (it'd be cool if fun
    % could also provide the gradient)
    %
    % Combine FD calls into a single call to fun in case fun has a lot of
    % per-call precomputation.
    order = 2
    switch order
    case 1
      % Wasn't computed on the last iteration
      D = fun(CV);
      switch ss
      case 3
        GD = ...
          reshape(fun([CV+[1 0]*delta;CV+[0 1]*delta]),[],2);
      case 4
        GD = reshape( ...
          fun([CV+[1 0 0]*delta;CV+[0 1 0]*delta;CV+[0 0 1]*delta]), ...
          size(CV,1),3);
      end
      % Don't bother dividing by delta (we're normalizing anyway)
      G = GD-D;
    case 2
      delta = delta/2;
      switch ss
      case 3
        GD = ...
          reshape(fun([CV+[1 0]*delta;CV+[0 1]*delta;CV-[1 0]*delta;CV-[0 1]*delta]),[],2,2);
      case 4
        GD = reshape( ...
          fun([ ...
            CV+[1 0 0]*delta;CV+[0 1 0]*delta;CV+[0 0 1]*delta; ...
            CV-[1 0 0]*delta;CV-[0 1 0]*delta;CV-[0 0 1]*delta; ...
            ]), ...
          size(CV,1),3,2);
      end
      G = GD(:,:,1)-GD(:,:,2);
    end

    CN = -normalizerow(G);
  end

end


function [modelCoupled] = coupleRxnList2Rxn(model, rxnList, rxnC, c, u)
% This function adds coupling constraints to the fluxes `vi` of a given list of reactions
% (`RxnList`).The constraints are proportional to the flux `v` of a specified
% reaction `rxnC`, so that for all reactions in `RxnList` `vi ~ vrxnC`.
% For all reactions, a threshold `u` on flux is set (default value: 0.01).
%
% To add a coupling constraint to a reaction, a coupling vector `c` is
% determined (default value 1000). `c` is multiplied by `vrxnC`, so that for
% all irreversible reactions in `RxnList vi - c * vrxnC <= u`.
%
% For all reversible reactions, the following equation holds true for the
% reverse direction: `vi + c * vrxnC >= u`.
%
% The output is a coupled model (`modelCoupled`), in which for every new
% entry in `modelCoupled.b` a "slack" variable has been added
% to `modelCoupled.mets`.
%
% USAGE:
%
%    [modelCoupled] = coupleRxnList2Rxn(model, rxnList, rxnC, c, u)
%
% INPUTS:
%    model:           model structure
%    rxnList:         array of reaction names
%    rxnC:            reaction that should be coupled with each reaction in the
%                     reaction list
%    c:               vector of coupling factors for each rxn in rxnList (default c = 1000)
%    u:               vector of lower bounds one reaction couples (default u = 0.01)
%
% OUTPUT:
%    modelCoupled:    coupled model
%
% .. Authors:
%    - Sept 2011 AH/IT
%    - May 2012: Added a warning if the coupled reaction is not in model. AH

if ~exist('rxnList', 'var') || isempty(rxnList)
    rxnList = model.rxns;
end

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

if ~exist('c', 'var') || isempty(c)
    c = 1000;
end

if ~exist('u', 'var') || isempty(u)
    u = 0.01;
end

nRxnList = numel(rxnList);
% create the constraint IDs.
ctrs = [strcat('slack_',rxnList)';strcat('slack_',rxnList,'_R')'];
ctrs = ctrs(:);
% get those reactions which are not reversible.
[pres,pos] = ismember(rxnList,model.rxns);
revs = model.lb(pos(pres)) < 0;
toRemove = [false(1,nRxnList);~revs'];
toRemove = toRemove(:);

% if rxnC is not part of the rxnList, we add it to the end for constraint
% addition.
if isempty(intersect(rxnList,rxnC))
    rxnList(end+1) = rxnC;
end
% get the rxnC Position;
rxnCID = find(ismember(rxnList,rxnC));

plusminus = [ones(1,nRxnList);-ones(1,nRxnList)];
plusminus = plusminus(:);

% generate the coefficient matrix
coefs = sparse(2*nRxnList,nRxnList + numel(setdiff(rxnC,rxnList)));
% determine the indices for the coefficients.
rxnInd =  [1:nRxnList;1:nRxnList];
rxnInd = rxnInd(:);
constInd = 1:2*nRxnList;
coefs(sub2ind(size(coefs),constInd',rxnInd)) = 1;
% set the coupling coefficients
cs = - plusminus * c;
coefs(:,rxnCID ) = cs;
% determine the senses for the indices
dsenses = [repmat('L',1,nRxnList);repmat('G',1,nRxnList)];
dsenses = dsenses(:);
ds = plusminus * u;

% remove non reversible reactions
ds = ds(~toRemove);
ctrs = ctrs(~toRemove);
coefs = coefs(~toRemove,:);
dsenses = dsenses(~toRemove);

modelCoupled = addCOBRAConstraints(model,rxnList,ds,'c', coefs,'dsense',dsenses, 'ConstraintID',ctrs);


function pfplot(X,Factors,Weights,Option);
%PFPLOT plot parafac model
%
% See also:
% 'parafac'
%
%
% pfplot(X,Factors,Weights,Option);
% Different aspects for evaluation of the solution.
%
% Option # = 1
% 1	NOT ACCESIBLE
% 2	NOT ACCESIBLE
% 3	DIAGONALITY PLOT
% 4	PLOTS OF RESIDUAL VARIANCE
% 5	PLOTS OF LEVERAGE
% 6	RESIDUALS (STANDARD DEVIATION) VERSUS LEVERAGE
% 7	NORMAL PROBABILITY PLOT
% 8	LOADING PLOT
%
% You HAVE to input all four inputs. If you have no weights, just input [].
% The last input must be an 8-vector with ones if you want the plot and
% zeros else. E.g.
%
% pfplot(X,factors,[],[0 0 1 0 0 0 0 1]);
%
% to have the diagonality and the loading plot
%

% $ Version 1.02 $ Date 28. July 1998 $ Not compiled $
% $ Version 1.03 $ Date 6. October 1999 $ Changed to handle missing values correctly$
% $ Version 2.00 $ May 2001 $ Changed to array notation $ RB $ Not compiled $

% Copyright (C) 1995-2006  Rasmus Bro & Claus Andersson
% Copenhagen University, DK-1958 Frederiksberg, Denmark, rb@life.ku.dk
%
% This program is free software; you can redistribute it and/or modify it under
% the terms of the GNU General Public License as published by the Free Software
% Foundation; either version 2 of the License, or (at your option) any later version.
%
% This program is distributed in the hope that it will be useful, but WITHOUT
% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
% FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
% You should have received a copy of the GNU General Public License along with
% this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
% Street, Fifth Floor, Boston, MA  02110-1301, USA.

warning off

DimX = size(X);
X = reshape(X,DimX(1),prod(DimX(2:end)));

% Convert to old format
NewLoad = Factors;
ff = [];
for f=1:length(Factors)
  ff=[ff;Factors{f}(:)];
end
Factors = ff;

factors = Factors;
ord=length(DimX);
Fac=length(factors)/sum(DimX);
lidx(1,:)=[1 DimX(1)*Fac];
for i=2:ord
  lidx=[lidx;[lidx(i-1,2)+1 sum(DimX(1:i))*Fac]];
end
if Option(3)==1
  % ESTIMATE DIAGONALITY OF T3-CORE
  diagonality=corcond(reshape(X,DimX),NewLoad,Weights,1);
end
model=nmodel(NewLoad);
model = reshape(model,DimX(1),prod(DimX(2:end)));
if Option(4)==1
  % PLOTS OF RESIDUAL VARIANCE
  figure,eval(['set(gcf,''Name'',''Residual variance'');']);
  aa=ceil(sqrt(ord));bb=ceil(ord/aa);
  for i=1:ord
    r=nshape(reshape(X-model,DimX),i)';
    varian=stdnan(r).^2;
    subplot(aa,bb,i)
    plot(varian)
    if DimX(i)<30
      hold on
      plot(varian,'r+')
    end
    eval(['xlabel(''Mode ', num2str(i),''');']);
    ylabel('Residual variance');
  end
end
if Option(5)==1
  % PLOTS OF LEVERAGE
  figure
  eval(['set(gcf,''Name'',''Leverage'');']);
  aa=ceil(sqrt(ord));
  bb=ceil(ord/aa);
  for i=1:ord
    A=reshape(factors(lidx(i,1):lidx(i,2)),DimX(i),Fac);
    lev=diag(A*pinv(A'*A)*A');
    subplot(aa,bb,i)
    if std(lev)>eps
      plot(lev+100*eps,'+')
      for j=1:DimX(i)
        text(j,lev(j),num2str(j))
      end
    else
      warning('Leverage is constant')
    end
    eval(['xlabel(''Mode ', num2str(i),''');']);
    ylabel('Leverage');
  end
end
if Option(6)==1
  % RESIDUALS (STANDARD DEVIATION) VERSUS LEVERAGE
  figure
  eval(['set(gcf,''Name'',''Residuals vs. Leverages'');']);
  aa=ceil(sqrt(ord));
  bb=ceil(ord/aa);
  for i=1:ord
    subplot(aa,bb,i)
    A=reshape(factors(lidx(i,1):lidx(i,2)),DimX(i),Fac);
    lev=diag(A*pinv(A'*A)*A')'+100*eps;
    r=nshape(reshape(X-model,DimX),i)';
    stand=stdnan(r);
    if std(lev)>eps
      plot(lev,stand,'+')
      for j=1:DimX(i)
        text(lev(j),stand(j),num2str(j))
      end
      eval(['xlabel(''Leverage in mode ', num2str(i),''');']);
      ylabel('Standard deviation');
    else
      warning('Leverage is constant')
    end
  end
end
if Option(7)==1
  % NORMAL PROBABILITY PLOT
  if exist('normplot')
    disp(' ')
    disp(' Normal probability plots are time-consuming')
    disp(' They are made in the statistics toolbox though, so we can''t change that!')
    figure,
    eval(['set(gcf,''Name'',''Normal probability of residuals'');']);
    aa=ceil(sqrt(ord));
    bb=ceil(ord/aa);
    r=nshape(reshape(X-model,DimX),i)';
    r=r(:);
    normplot(r(find(~isnan(r))))
  end
end
if Option(8)==1
  % LOADING PLOT
  if sum(Option)>1
    figure
  end
  eval(['set(gcf,''Name'',''Loadings'');']);
  aa=ceil(sqrt(ord));
  bb=ceil(ord/aa);
  for i=1:ord
    subplot(aa,bb,i)
    A=reshape(factors(lidx(i,1):lidx(i,2)),DimX(i),Fac);
    plot(A)
    eval(['xlabel(''Mode ', num2str(i),''');']);
    ylabel('Loading');
  end
end
drawnow


%%******************************************************************
%% ops:
%%
%%   Z = ops(X,operand,Y,alpha);
%%
%%  INPUT:        X = a matrix or a scalar
%%                    or a CELL ARRAY consisting only of matrices
%%          operand = sym, transpose, triu, tril,
%%                    real, imag, sqrt, abs, max, min, nnz,
%%                    spdiags, ones, zeros, norm, sum, row-norm, blk-norm
%%                    rank1, rank1inv, inv
%%                    +,  -, *, .*,  ./, .^
%%     Y (optional) = a matrix or a scalar
%%                    or a CELL ARRAY consisting only of matrices
%% alpha (optional) = a scalar
%%                    or the variable blk.
%%
%% SDPT3: version 3.1
%% Copyright (c) 1997 by
%% K.C. Toh, M.J. Todd, R.H. Tutuncu
%% Last Modified: 16 Sep 2004
%%******************************************************************

function Z = ops(X,operand,Y,alpha)

spdensity = 0.4;

if (nargin == 2)
    if strcmp(operand,'sym');
        if ~iscell(X);
            [m,n] = size(X);
            if (m == n);
                Z = (X+X')/2;
            elseif (n == 1);
                Z = X;
            else
                error('X must be square matrix or a column vector');
            end;
        else
            Z = cell(size(X));
            for p = 1:length(X);
                [m,n] = size(X{p});
                if (m == n);
                    Z{p} = (X{p}+X{p}')/2;
                elseif (n == 1);
                    Z{p} = X{p};
                else
                    error('X{p} must be square matrix or a column vector');
                end;
            end;
        end;
    elseif strcmp(operand,'sqrt') || strcmp(operand,'abs') || ...
            strcmp(operand,'real') || strcmp(operand,'imag');
        if ~iscell(X);
            eval(['Z = ',operand,'(X);']);
        else
            Z = cell(size(X));
            for p = 1:length(X);
                eval(['Z{p} = ',operand,'(X{p});']);
            end;
        end;
    elseif strcmp(operand,'max') || strcmp(operand,'min') || ...
            strcmp(operand,'sum');
        if ~iscell(X);
            eval(['Z = ',operand,'(X);']);
        else
            Z = [];
            for p = 1:length(X);
                eval(['Z = [Z  ',operand,'(X{p})','  ];']);
            end;
        end;
        eval(['Z = ',operand,'(Z);']);
    elseif strcmp(operand,'transpose') || strcmp(operand,'triu') || ...
            strcmp(operand,'tril');
        if ~iscell(X);
            if (size(X,1) == size(X,2));
                Z = feval(operand,X);
            elseif (size(X,2) == 1);
                Z = X;
            else
                error('X must be square matrix or a column vector');
            end;
        else
            Z = cell(size(X));
            for p = 1:length(X);
                if (size(X{p},1) == size(X{p},2));
                    Z{p} = feval(operand,X{p});
                elseif (size(X{p},2) == 1);
                    Z{p} = X{p};
                else
                    error('X{p} must be square matrix or a column vector');
                end;
            end;
        end;
    elseif strcmp(operand,'norm');
        if ~iscell(X);
            Z = full(sqrt(sum(sum(X.*X))));
        else
            Z = 0;
            for p = 1:length(X); Z = Z + sum(sum(X{p}.*X{p})); end;
            Z = sqrt(Z);
        end;
    elseif strcmp(operand,'blk-norm');
        if ~iscell(X);
            Z = full(sqrt(sum(sum(X.*X))));
        else
            Z = zeros(length(X),1);
            for p = 1:length(X); Z(p) = sum(sum(X{p}.*X{p})); end;
            Z = sqrt(Z);
        end;
    elseif strcmp(operand,'inv');
        if ~iscell(X);
            [m,n] = size(X); n2 = n*n;
            if (m==n)
                Z = inv(X);
                if (nnz(Z) > spdensity*n2)
                    Z = full(Z);
                else
                    Z = sparse(Z);
                end
            elseif (m > 1 && n == 1);
                Z = 1./X;
                if (nnz(Z) > spdensity*n)
                    Z = full(Z);
                else
                    Z = sparse(Z);
                end
            end
        else
            Z = cell(size(X));
            for p = 1:length(X);
                [m,n] = size(X{p}); n2 = n*n;
                if (m==n)
                    Z{p} = inv(X{p});
                    if (nnz(Z{p}) > spdensity*n2)
                        Z{p} = full(Z{p});
                    else
                        Z{p} = sparse(Z{p});
                    end
                elseif (m > 1 && n == 1);
                    Z{p} = 1./X{p};
                    if (nnz(Z{p}) > spdensity*n)
                        Z{p} = full(Z{p});
                    else
                        Z{p} = sparse(Z{p});
                    end
                end
            end
        end
    elseif strcmp(operand,'getM');
        if ~iscell(X);
            Z = size(X,1);
        else
            for p = 1:length(X); Z(p) = size(X{p},1); end; %#ok
            Z = sum(Z);
        end;
    elseif strcmp(operand,'nnz');
        if ~iscell(X);
            Z = nnz(X);
        else
            for p = 1:length(X);
                Z(p) = nnz(X{p}); %#ok
            end;
            Z = sum(Z);
        end;
    elseif strcmp(operand,'ones');
        if ~iscell(X);
            Z = ones(size(X));
        else
            Z = cell(size(X));
            for p = 1:length(X);
                Z{p} = ones(size(X{p}));
            end
        end
    elseif strcmp(operand,'zeros');
        if ~iscell(X);
            [m,n] = size(X);
            Z = sparse(m,n);
        else
            Z = cell(size(X));
            for p = 1:length(X);
                [m,n] = size(X{p});
                Z{p} = sparse(m,n);
            end
        end
    elseif strcmp(operand,'identity');
        blk = X;
        Z = cell(size(blk,1),1);
        for p = 1:size(blk,1)
            pblk = blk(p,:); n = sum(pblk{2});
            if strcmp(pblk{1},'s')
                Z{p} = speye(n,n);
            elseif strcmp(pblk{1},'q')
                s = 1+[0, cumsum(pblk{2})];
                len = length(pblk{2});
                Z{p} = zeros(n,1);
                Z{p}(s(1:len)) = ones(len,1);
            elseif strcmp(pblk{1},'l')
                Z{p} = ones(n,1);
            elseif strcmp(pblk{1},'u')
                Z{p} = zeros(n,1);
            end
        end
    elseif strcmp(operand,'row-norm');
        if ~iscell(X);
            if (size(X,2) == size(X,1));
                Z = sqrt(sum((X.*conj(X))'))'; %#ok
            elseif (size(X,2) == 1);
                Z = abs(X);
            end
        else
            Z = cell(size(X));
            for p = 1:length(X);
                if (size(X{p},2) == size(X{p},1));
                    Z{p} = sqrt(sum((X{p}.*conj(X{p}))'))'; %#ok
                elseif (size(X{p},2) == 1);
                    Z{p} = abs(X{p});
                end
            end
        end
    end
end
%%
if (nargin == 3)
    if strcmp(operand,'spdiags');
        if ~iscell(Y);
            [m,n] = size(Y);
            if (m == n);
                Z = spdiags(X,0,m,n);
            else
                Z = X;
            end
        else
            Z = cell(size(Y));
            for p = 1:length(Y);
                [m,n] = size(Y{p});
                if (m == n);
                    Z{p} = spdiags(X{p},0,m,n);
                else
                    Z{p} = X{p};
                end
            end
        end
    elseif strcmp(operand,'inprod')
        if ~iscell(X) && ~iscell(Y)
            Z = (Y'*X)';
        elseif iscell(X) && iscell(Y)
            Z = zeros(size(X{1},2),1);
            for p=1:length(X)
                Z = Z + (Y{p}'*X{p})';
            end
        end
    elseif strcmp(operand,'+') || strcmp(operand,'-') || ...
            strcmp(operand,'/') || strcmp(operand,'./') || ...
            strcmp(operand,'*') || strcmp(operand,'.*') || ...
            strcmp(operand,'.^');
        if (~iscell(X) && ~iscell(Y));
            eval(['Z = X',operand,'Y;']);
        elseif (iscell(X) && iscell(Y))
            Z = cell(size(X));
            for p = 1:length(X);
                if (size(X{p},2) == 1) && (size(Y{p},2) == 1) && ...
                        (strcmp(operand,'*') || strcmp(operand,'/'));
                    eval(['Z{p} = X{p}.',operand,'Y{p};']);
                else
                    eval(['Z{p} = X{p} ',operand,'Y{p};']);
                end
            end
        elseif (iscell(X) && ~iscell(Y));
            if (length(Y) == 1); Y = Y*ones(length(X),1); end
            Z = cell(size(X));
            for p = 1:length(X);
                eval(['Z{p} = X{p}',operand,'Y(p);']);
            end
        elseif (~iscell(X) && iscell(Y));
            Z = cell(size(Y));
            if (length(X) == 1); X = X*ones(length(Y),1); end
            for p = 1:length(Y);
                eval(['Z{p} = X(p)',operand,'Y{p};']);
            end
        end
    else
        error([operand,' is not available, check input arguments']);
    end
end
%%
if (nargin == 4)
    if strcmp(operand,'rank1') || strcmp(operand,'rank1inv');
        Z = cell(size(alpha,1),1);
        for p = 1:size(alpha,1);
            if ~strcmp(alpha{p,1},'diag');
                blktmp = alpha{p,2};
                if (length(blktmp) == 1);
                    if strcmp(operand,'rank1');
                        Z{p} = (X{p}*Y{p}' + Y{p}*X{p}')/2;
                    else
                        Z{p} = 2./(X{p}*Y{p}' + Y{p}*X{p}');
                    end
                else
                    Xp = X{p};
                    Yp = Y{p};
                    n = sum(blktmp);
                    Zp = sparse(n,n);
                    s = [0 cumsum(blktmp)];
                    if strcmp(operand,'rank1');
                        for i = 1:length(blktmp)
                            pos = s(i)+1 : s(i+1);
                            x = Xp(pos);
                            y = Yp(pos);
                            Zp(pos,pos) = sparse((x*y' + y*x')/2); %#ok
                        end;
                        Z{p} = Zp;
                    else
                        for i = 1:length(blktmp)
                            pos = s(i)+1 : s(i+1);
                            x = Xp(pos);
                            y = Yp(pos);
                            Zp(pos,pos) = sparse(2./(x*y' + y*x')); %#ok
                        end
                        Z{p} = Zp;
                    end
                end
            elseif strcmp(alpha{p,1},'diag');
                if strcmp(operand,'rank1');
                    Z{p} = X{p}.*Y{p};
                else
                    Z{p} = 1./(X{p}.*Y{p});
                end
            end
        end
    elseif strcmp(operand,'+') || strcmp(operand,'-');
        if ~iscell(X) && ~iscell(Y);
            eval(['Z = X',operand,'alpha*Y;']);
        elseif (iscell(X) && iscell(Y));
            Z = cell(size(X));
            if (length(alpha) == 1);
                alpha = alpha*ones(length(X),1);
            end
            if strcmp(operand,'+'),
                for p = 1:length(X)
                    Z{p} = X{p} + alpha(p) * Y{p};
                end
            else
                for p = 1:length(X);
                    Z{p} = X{p} - alpha(p) * Y{p};
                end
            end
        else
            error('X, Y are different objects');
        end
    else
        error([operand,' is not available']);
    end
end
%%============================================================


% OP_F_V_TP: assemble the right-hand side vector r = [r(i)], with  r(i) = (f, v_i), exploiting the tensor product structure.
%
%   rhs = op_f_v_tp (spv, msh, coeff);
%
% INPUT:
%
%   spv:   object representing the function space (see sp_vector)
%   msh:   object defining the domain partition and the quadrature rule (see msh_cartesian)
%   coeff: function handle to compute the source function
%
% OUTPUT:
%
%   rhs: assembled right-hand side
%
% Copyright (C) 2011, 2017 Rafael Vazquez
%
%    This program is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.

%    This program is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with this program.  If not, see <http://www.gnu.org/licenses/>.

function rhs = op_f_v_tp (space, msh, coeff)

  for icomp = 1:space.ncomp_param
    for idim = 1:msh.ndim
      size1 = size (space.scalar_spaces{icomp}.sp_univ(idim).connectivity);
      if (size1(2) ~= msh.nel_dir(idim))
        error ('The discrete space is not associated to the mesh')
      end
    end
  end

  rhs = zeros (space.ndof, 1);

  for iel = 1:msh.nel_dir(1)
    msh_col = msh_evaluate_col (msh, iel);
    sp_col  = sp_evaluate_col (space, msh_col);

    for idim = 1:msh.rdim
      x{idim} = reshape (msh_col.geo_map(idim,:,:), msh_col.nqn, msh_col.nel);
    end

    rhs = rhs + op_f_v (sp_col, msh_col, coeff (x{:}));
  end

end


function [cost,costs] = crossvalidate(model, L, estfct,combinefct)

% Estimate the model performance of a model with [$ l$] -fold crossvalidation
%
% CAUTION!! Use this function only to obtain the value of the crossvalidation score
% function given the tuning parameters. Do not use this function together with
% 'tunelssvm', but use 'crossvalidatelssvm' instead. The latter is a faster
% implementation which uses previously computed results.
%
% >> cost = crossvalidate({Xtrain,Ytrain,type,gam,sig2})
% >> cost = crossvalidate( model)
%
% The data is once permutated randomly, then it is divided into L (by default 10)
% disjoint sets. In the i-th (i=1,...,l) iteration, the i-th set is used to estimate
% the performance ('validation set') of the model trained on the other l-1 sets ('training set').
% Finally, the l (denoted by L) different estimates of the performance are combined (by default by the 'mean').
% The assumption is made that the input data are distributed independent and identically over the
% input space. As additional output, the costs in the different folds ('costs') of the data are returned:
%
% >> [cost, costs] = crossvalidate(model)
%
% Some commonly used criteria are:
%
% >> cost = crossvalidate(model, 10, 'misclass', 'mean')
% >> cost = crossvalidate(model, 10, 'mse', 'mean')
% >> cost = crossvalidate(model, 10, 'mae', 'median')
%
% Full syntax
%
%     1. Using LS-SVMlab with the functional interface:
%
% >> [cost, costs] = crossvalidate({X,Y,type,gam,sig2,kernel,preprocess}, L, estfct, combinefct)
%
%       Outputs
%         cost          : Cost estimation of the L-fold cross validation
%         costs(*)      : L x 1 vector with costs estimated on the L different folds
%
%       Inputs
%         X             : Training input data used for defining the LS-SVM and the preprocessing
%         Y             : Training output data used for defining the LS-SVM and the preprocessing
%         type          : 'function estimation' ('f') or 'classifier' ('c')
%         gam           : Regularization parameter
%         sig2          : Kernel parameter (squared bandwidth in the case of the 'RBF_kernel')
%         kernel(*)     : Kernel type (by default 'RBF_kernel')
%         preprocess(*) : 'preprocess'(*) or 'original'
%         L(*)          : Number of folds (by default 10)
%         estfct(*)     : Function estimating the cost based on the residuals (by default mse)
%         combinefct(*) : Function combining the estimated costs on the different folds (by default mean)
%
%
%     2. Using the object oriented interface:
%
% >> [cost, costs] = crossvalidate(model, L, estfct, combinefct)
%
%       Outputs
%         cost          : Cost estimation of the L-fold cross validation
%         costs(*)      : L x 1 vector with costs estimated on the L different folds
%
%       Inputs
%         model         : Object oriented representation of the LS-SVM model
%         L(*)          : Number of folds (by default 10)
%         estfct(*)     : Function estimating the cost based on the residuals (by default mse)
%         combinefct(*) : Function combining the estimated costs on the different folds (by default mean)
%
%
%     3. Using other modeling techniques::
%
%
% See also:
% leaveoneout, gcrossvalidate, trainlssvm, simlssvm

% Copyright (c) 2011,  KULeuven-ESAT-SCD, License & help @ http://www.esat.kuleuven.be/sista/lssvmlab

% LS-SVMlab
eval('model = initlssvm(model{:});',' ');
eval('L;','L=min(ceil(sqrt(model.nb_data)),10);');
eval('estfct;','estfct=''mse'';');
eval('combinefct;','combinefct=''mean'';');

%
% initialisation and defaults
%
nb_data = size(model.ytrain,1);
d = size(model.xtrain,2);

if L==nb_data, p = 1:nb_data; else p = randperm(nb_data); end
px = model.xtrain(p,:);
py = model.ytrain(p,:);

[~,Y] = postlssvm(model,[],py);

%initialize: no incremental  memory allocation
costs = zeros(L,length(model.gam));
block_size = floor(nb_data/L);

% calculate matrix for LS-SVM once for the entire data
S = ones(nb_data,1);
Inb = eye(nb_data);
K = kernel_matrix(px,model.kernel_type,model.kernel_pars);
Atot = K+Inb./model.gam;

% Cholesky factor
try R = chol(Atot);
    % Solve full system
    q = R\(R'\[py S]);
    p = q(:,2); q = q(:,1);
    s  = 1/sum(p);
    bias  = s*sum(q);
    alpha  = q - p*bias;

    % Two expensive steps yet more efficient that using LINSOLVE on each fold
    Ri = R\Inb;
    C = Ri*Ri' - s*(p)*p';

catch %R = cholinc(sparse(Atot),1e-5);
    A = [K+Inb./model.gam S; S' 0];
    C = pinv(A);
    alpha = C*[py;0];
    %bias = alpha(nb_data+1);
    alpha = alpha(1:nb_data);
end

% Solve full system
q = R\(R'\[py S]);
p = q(:,2); q = q(:,1);
s  = 1/sum(p);
bias  = s*sum(q);
alpha  = q - p*bias;

% Two expensive steps yet more efficient that using LINSOLVE on each fold
Ri = R\Inb;
C = Ri*Ri' - s*(p)*p';

% start loop over l validations
for l = 1:L,
    % divide data in validation set and trainings data set
    if l==L,
        validation = block_size*(l-1)+1:nb_data;
    else
        validation = block_size*(l-1)+1:block_size*l;
    end
    % Submatrix of C to compute residuals for the l-th fold left out
    Ckk = C(validation,validation);
    % Solution of small linear system (block_size x block_size)
    try % faster
        Rkk = chol(Ckk+eps);
        betak = Rkk\(Rkk'\alpha(validation));
    catch
        betak = Ckk\alpha(validation);
    end
    % latent outputs for validation
    yh = py(validation) - betak;
    [~,yh] = postlssvm(model,[],yh);
    if ~(model.type(1)=='c')
        costs(l,1) = feval(estfct,yh - Y(validation,:));
    else
        costs(l,1) = feval(estfct,Y(validation,:),sign(yh));
    end
end
cost = feval(combinefct,costs);


function [F,dF,G,H,varF,dH,varGss,varG,varH,I_sk,J_sjk] = negelcbo_vbmc(theta,beta,vp,gp,Ns,compute_grad,compute_var,altent_flag,thetabnd,entropy_alpha)
%NEGELCBO_VBMC Negative evidence lower confidence bound objective
%
% Note that THETA is a vector of *transformed* variational parameters:
% [MU_1,...,MU_K,log(SIGMA)] or
% [MU_1,...,MU_K,log(SIGMA),log(LAMBDA)] or
% [MU_1,...,MU_K,log(SIGMA),log(LAMBDA),log(W)]

if nargin < 5 || isempty(Ns); Ns = 0; end
if nargin < 6 || isempty(compute_grad); compute_grad = nargout > 1; end
if nargin < 7; compute_var = []; end
if nargin < 8 || isempty(altent_flag); altent_flag = false; end
if nargin < 9; thetabnd = []; end
if nargin < 10 || isempty(entropy_alpha); entropy_alpha = 0; end
if isempty(beta) || ~isfinite(beta); beta = 0; end
if isempty(compute_var); compute_var = beta ~=0 || nargout > 4; end
separate_K = nargout > 9; % Return expected log joint per component

% altent_flag and entropy_alpha are unused (kept here for retrocompatibility)

if compute_grad && beta ~= 0 && compute_var ~= 2
    error('negelcbo_vbmc:vargrad', ...
        'Computation of the gradient of ELBO with full variance not supported.');
end

D = vp.D;
K = vp.K;

avg_flag = 1;       % Average over multiple GP hyperparameters if provided
jacobian_flag = 1;  % Variational parameters are transformed

% Reformat variational parameters from THETA
if vp.optimize_mu
    vp.mu(:,:) = reshape(theta(1:D*K),[D,K]);
    idx_start = D*K;
else
    idx_start = 0;
end
if vp.optimize_sigma
    vp.sigma(1,:) = exp(theta(idx_start+(1:K)));
    idx_start = idx_start + K;
end
if vp.optimize_lambda; vp.lambda(:,1) = exp(theta(idx_start+(1:D))); end
if vp.optimize_weights
    vp.eta(1,:) = theta(end-K+1:end);
    vp.w(1,:) = exp(vp.eta);
    vp.w = vp.w/sum(vp.w);
end

% Which gradients should be computed, if any?
grad_flags = compute_grad*[vp.optimize_mu,vp.optimize_sigma,vp.optimize_lambda,vp.optimize_weights];

% Only weight optimization?
onlyweights_flag = vp.optimize_weights && ~vp.optimize_mu && ~vp.optimize_sigma && ~vp.optimize_lambda;

if separate_K
    if compute_grad
        error('Computing the gradient of variational parameters and requesting per-component results at the same time.');
    end

    if onlyweights_flag
        if compute_var
            [G,~,varG,~,~,I_sk,J_sjk] = gplogjoint_weights(vp,0,avg_flag,jacobian_flag,compute_var);
        else
            [G,dG,~,~,~,I_sk] = gplogjoint_weights(vp,compute_grad,avg_flag,jacobian_flag,0);
            J_sjk = [];
        end
        varGss = NaN;
    else
        if compute_var
            [G,~,varG,~,varGss,I_sk,J_sjk] = gplogjoint(vp,gp,grad_flags,avg_flag,jacobian_flag,compute_var);
        else
            [G,dG,~,~,~,I_sk] = gplogjoint(vp,gp,grad_flags,avg_flag,jacobian_flag,0);
            varGss = 0; varG = 0; J_sjk = [];
        end
    end
else
    if onlyweights_flag
        if compute_var
            if compute_grad
                [G,dG,varG,dvarG] = gplogjoint_weights(vp,1,avg_flag,jacobian_flag,compute_var);
            else
                [G,~,varG] = gplogjoint_weights(vp,0,avg_flag,jacobian_flag,compute_var);
            end
        else
            [G,dG] = gplogjoint_weights(vp,compute_grad,avg_flag,jacobian_flag,0);
        end
        varGss = NaN;
    else
        if compute_var
            if compute_grad
                [G,dG,varG,dvarG,varGss] = gplogjoint(vp,gp,grad_flags,avg_flag,jacobian_flag,compute_var);
            else
                [G,~,varG,~,varGss] = gplogjoint(vp,gp,grad_flags,avg_flag,jacobian_flag,compute_var);
            end
        else
            [G,dG] = gplogjoint(vp,gp,grad_flags,avg_flag,jacobian_flag,0);
            varGss = 0; varG = 0;
        end
    end
end

% Entropy term
if Ns > 0
    % Monte Carlo approximation
    [H,dH] = entmc_vbmc(vp,Ns,grad_flags,jacobian_flag);
else
    % Deterministic approximation via lower bound on the entropy
    [H,dH] = entlb_vbmc(vp,grad_flags,jacobian_flag);
end

%H_check = gmment_num(theta,lambda);
%[H - H_check, (H - H_check)/H_check ]

% Negative ELBO and its gradient
F = -G - H;
if compute_grad; dF = -dG - dH; else; dF = []; end

varH = 0;   % For the moment use zero variance for entropy
if compute_var
    varF = varG + varH;
else
    varF = 0;
end

% Negative ELCBO (add confidence bound)
if beta ~= 0; F = F + beta*sqrt(varF); end
if beta ~= 0 && compute_grad
    dF = dF + 0.5*beta*dvarG/sqrt(varF);
end

% Additional loss for variational parameter bound violation (soft bounds)
% and for weight size (if optimizing mixture weights)
% Only done when optimizing the variational parameters, but not when
% computing the EL(C)BO at each iteration
if ~isempty(thetabnd)
    if compute_grad
        [L,dL] = vpbndloss(theta,vp,thetabnd,thetabnd.TolCon);
        dF = dF + dL;
    else
        L = vpbndloss(theta,vp,thetabnd,thetabnd.TolCon);
    end
    F = F + L;

    % Penalty to reduce weight size
    if vp.optimize_weights
        Thresh = thetabnd.WeightThreshold;
        %L = sum(vp.w)*thetabnd.WeightPenalty;
        % L = sum(sqrt(vp.w))*thetabnd.WeightPenalty;
        L = sum(vp.w.*(vp.w<Thresh) + Thresh*(vp.w>=Thresh))*thetabnd.WeightPenalty;
        F = F + L;
        if compute_grad
            %w_grad = thetabnd.WeightPenalty*ones(K,1);
            % w_grad = 0.5./sqrt(vp.w(:))*thetabnd.WeightPenalty;
            w_grad = thetabnd.WeightPenalty.*(vp.w(:)<Thresh);
            eta_sum = sum(exp(vp.eta));
            J_w = bsxfun(@times,-exp(vp.eta)',exp(vp.eta)/eta_sum^2) + diag(exp(vp.eta)/eta_sum);
            w_grad = J_w*w_grad;
            dL = zeros(size(dF));
            dL(end-K+1:end) = w_grad;
            dF = dF + dL;
        end
    end
end

end


function [ vClusterIdx, mC, vCostFun ] = ClusterKMeans( mX, mC, hDistFun, numIterations, stopTol, dispFig, saveFig, dispTime )
% ----------------------------------------------------------------------------------------------- %
% [ vClusterIdx, mC, vCostFun ] = ClusterKMeans( mX, mC, hDistFun, numIterations, stopTol, dispFig, saveFig, dispTime )
%   Vanilla implementation of the K-Means algorithm with support for any
%   distance function to generate the Distance Matrix.
% Input:
%   - mX            -   Data Matrix.
%                       Each data sample is a row of the matrix.
%                       Structure: Matrix (numVars x varDim).
%                       Type: 'Single' / 'Double'.
%                       Range: (-inf, inf).
% Output:
%   - mD            -   Distance Matrix.
%                       A symmetric matrix where 'mD(ii, jj) = dist(mX(ii,
%                       :), mX(jj, :))';.
%                       Structure: Matrix (numVars x numVars).
%                       Type: 'Single' / 'Double'.
%                       Range: [0, inf).
% References
%   1.  A
% Remarks:
%   1.  B
% TODO:
%   1.  C
% Release Notes:
%   -   1.0.000     01/01/2021  Royi Avital	RoyiAvital@yahoo.com
%       *   First release version.
% ----------------------------------------------------------------------------------------------- %

arguments
    mX (:, :) {mustBeNumeric, mustBeReal}
    mC (:, :) {mustBeNumeric, mustBeReal}
    hDistFun (1, 1) {mustBeFunctionHandler}
    numIterations (1, 1) {mustBeNumeric, mustBeReal, mustBePositive, mustBeInteger}
    stopTol (1, 1) {mustBeNumeric, mustBeReal, mustBePositive}
    dispFig (1, 1) {mustBeNumeric, mustBeReal, mustBeInteger, mustBeInRange(dispFig, 0, 1)} = 0
    saveFig (1, 1) {mustBeNumeric, mustBeReal, mustBeInteger, mustBeInRange(saveFig, 0, 1)} = 0
    dispTime (1, 1) {mustBeNumeric, mustBeReal, mustBeInRange(dispTime, 0, 10)} = 1
end

FALSE   = 0;
TRUE    = 1;

OFF     = 0;
ON      = 1;

if(dispFig)
    figureIdx           = 0;
    if(saveFig)
        figureCounterSpec   = '%04d';
    end
end

numClsuters = size(mC, 2);
% numDim      = size(mC, 1);
% numSamples  = size(mX, 1);

vCostFun    = -ones(numIterations, 1);

ii = 1;

mD                      = hDistFun(mX, mC);
[vMinDist, vClusterIdx] = min(mD, [], 2);
vCostFun(ii)            = sum(vMinDist);

if(dispFig)
    figureIdx = figureIdx + 1;
    [hF, hA] = DisplayClusterData(mX, mC, vClusterIdx, ii, vCostFun(ii));
    if(saveFig)
        print(hF, ['FigureKMeans', num2str(figureIdx, figureCounterSpec), '.png'], '-dpng', '-r0'); %<! Saves as Screen Resolution
    end
    if(dispTime > 0)
        pause(dispTime);
    end
    delete(hF);
end

for ii = 2:numIterations

    for jj = 1:numClsuters
        mC(:, jj) = mean(mX(:, vClusterIdx == jj), 2);
    end

    mD                      = hDistFun(mX, mC);
    [vMinDist, vClusterIdx] = min(mD, [], 2);
    vCostFun(ii)            = sum(vMinDist);

    if(dispFig)
        figureIdx = figureIdx + 1;
        [hF, hA] = DisplayClusterData(mX, mC, vClusterIdx, ii, vCostFun(ii));
        if(saveFig)
            print(hF, ['FigureKMeans', num2str(figureIdx, figureCounterSpec), '.png'], '-dpng', '-r0'); %<! Saves as Screen Resolution
        end
        if(dispTime > 0)
            pause(dispTime);
        end
        delete(hF);
    end

    if(abs(vCostFun(ii) - vCostFun(ii - 1)) < stopTol)
        break;
    end

end

end

function [ ] = mustBeFunctionHandler( hF )
% https://www.mathworks.com/matlabcentral/answers/107552
if ~isa(hF, 'function_handle')
    eid = 'mustBeFunctionHandler:notFunctionHandler';
    msg = 'The 2nd input must be a Function Handler';
    throwAsCaller(MException(eid, msg));
end

end


 function st = nufft_init(om, Nd, Jd, Kd, varargin)
%function st = nufft_init(om, Nd, Jd, Kd, [n_shift,] ...)
%|
%| Initialize structure for d-dimension NUFFT using KB interpolator,
%| particularly the interpolation matrix in sparse format.
%| caution: this routine can require a lot of memory!
%| in
%|	om [M d]	"digital" frequencies in radians
%|	Nd [d]		image dimensions (N1,N2,...,Nd)
%|	Jd [d]		# of neighbors used (in each direction)
%|	Kd [d]		FFT sizes (should be >= N1,N2,...)
%|
%| options
%|	n_shift [d]	n = 0-n_shift to N-1-n_shift (must be first)
%|	'minmax:kb'	minmax interpolator with excellent KB scaling!
%|				(minmax:kb is recommended, and used by default)
%|	'minmax:tuned'	minmax interpolator, somewhat numerically tuned
%|	'minmax:user'	minmax interpolator with user ({alpha}, {beta})
%|	'uniform'	uniform scaling factors (not recommended)
%|	'kaiser'	kaiser-bessel (KB) interpolator (minmax best alpha, m)
%|			or 'kaiser', [alpha], [m] to specify parameter (vectors)
%|	'linear'	linear interpolator (a terrible straw man)
%|	kernel		user-provided function handle interpolation kernel(k,J)
%|			(or a cell array of kernels, one for each dimension)
%|	'table'		use table-based interpolation rather than sparse matrix.
%|			this can save a lot of memory for large problems.
%|			example ..., 'table', 2^11, 'minmax:kb'
%|			where 2^11 is the table over-sampling factor.
%|
%| out
%|	st.p		[M *Kd]		sparse interpolation matrix
%|					(or empty if table-based)
%|	st.sn		[(Nd)]		scaling factors
%|	st.Nd,Jd,Kd,om	copies of inputs
%|
%| *Nd is shorthand for prod(Nd).
%| (Nd) is shorthand for (N1,N2,...,Nd)
%|
%| Like fft(), the NUFFT expects the signals to be x(0,0), ...
%| Use n_shift = [N1/2, N2/2, ...] for x(-N1/2,-N2/2,...), ...
%|
%| Copyright 2002-5-30, Jeff Fessler, University of Michigan

if nargin == 1 && streq(om, 'test'), nufft_init_test, return, end
if nargin < 4, ir_usage, end

% dimensionality of input space (usually 2 or 3)
dd = length(Nd);
if dd ~= length(Jd) || dd ~= length(Kd)
	fail 'inconsistent dim'
end
if any(Kd < Nd), warn 'Kd < Nd unlikely to work.  Try Kd=2*Nd', end
if any(Jd > Kd)
	Jd_new = min(Jd, Kd);
	fun = @(x) regexprep(num2str(x), '\s+', ' ');
	warn('Jd = [%s] > Kd = [%s]; changing to Jd = [%s]', ...
		fun(Jd), fun(Kd), fun(Jd_new))
	Jd = Jd_new; clear Jd_new fun
end

% special cases of input sampling pattern
if ischar(om)
	om = nufft_samples(om, Nd);
end
if dd ~= size(om,2), fail('omega needs %d columns', dd), end

%
% process optional arguments
%

% n_shift argument? (must be first)
if length(varargin) > 0 && isnumeric(varargin{1})
	n_shift = varargin{1};
	if dd ~= length(n_shift)
		fail('n_shift needs %d columns', dd)
	end
	varargin = {varargin{2:end}};
else
	n_shift = zeros(size(Nd));
end
st.n_shift = n_shift;

% default/recommended interpolator is minmax with KB scaling factors
if length(varargin) == 0
	varargin = {'minmax:kb'};
end

st.alpha = {};
st.beta = {};
is_kaiser_scale = false;

% table based?
if ischar(varargin{1}) && streq(varargin{1}, 'table')
	st = nufft_table_init(om, Nd, Jd, Kd, n_shift, varargin{2:end});
	return
end

ktype = varargin{1};

% cell array of kernel functions: {kernel1, kernel2, ..., kernelD}
if isa(ktype, 'cell')
	if isa(ktype{1}, 'inline') || isa(ktype{1}, 'function_handle')
		ktype = 'function_handle';
		if length(varargin) > 1, fail 'excess arguments?', end
		if length(varargin{1}) ~= dd, fail 'wrong # of kernels', end
		st.kernel = varargin{1};
	else
		fail 'cell array should be function_handles!?'
	end

% or a single function handle for all dimension
elseif isa(ktype, 'inline') || isa(ktype, 'function_handle')
	ktype = 'function_handle';
	if length(varargin) > 1, fail 'excess arguments?', end
	for id = 1:dd
		st.kernel{id} = varargin{1}; % all same
	end

% or a string that describes the type of interpolator
elseif ~ischar(ktype)
	fail 'non-string kernel type?'

end

st.ktype = ktype;

% set up whatever is needed for each interpolator
switch ktype
case 'function_handle'
	% already did it above

% linear interpolator straw man
case 'linear'
	ktype = 'function_handle';
	kernel = @(k,J) (1 - abs(k/(J/2))) .* (abs(k) < J/2);
	for id = 1:dd
		st.kernel{id} = kernel;
	end

% KB interpolator
case 'kaiser'
	is_kaiser_scale = true;

	% with minmax-optimized parameters
	if length(varargin) == 1
		for id = 1:dd
			[st.kernel{id} st.kb_alf(id) st.kb_m(id)] = ...
				kaiser_bessel('inline', Jd(id));
		end

	% with user-defined parameters
	elseif length(varargin) == 3
		alpha_list = varargin{2};
		m_list = varargin{3};
		if (length(alpha_list) ~= dd) || (length(m_list) ~= dd)
			fail('#alpha=%d #m=%d vs dd=%d', ...
				length(alpha_list), length(m_list), dd)
		end
		for id = 1:dd
			[st.kernel{id} st.kb_alf(id) st.kb_m(id)] = ...
				kaiser_bessel('inline', Jd(id), ...
					alpha_list(id), m_list(id));
		end
	else
		fail 'kaiser should have no arguments, or both alpha and m'
	end

% minmax interpolator with KB scaling factors (recommended default)
case 'minmax:kb'
	for id = 1:dd
		[st.alpha{id}, st.beta{id}] = ...
			nufft_alpha_kb_fit(Nd(id), Jd(id), Kd(id));
	end

% minmax interpolator with numerically "tuned" scaling factors
case 'minmax:tuned'
	for id = 1:dd
		[st.alpha{id}, st.beta{id}, ok] = ...
			nufft_best_alpha(Jd(id), 0, Kd(id)/Nd(id));
		if ~ok, fail 'unknown J,K/N', end
	end

% minmax interpolator with user-provided scaling factors
case 'minmax:user'
	if length(varargin) ~= 3, fail 'user must provide alpha/beta', end
	st.alpha = varargin{2};
	st.beta = varargin{3};
	if length(st.alpha) ~= dd || length(st.beta) ~= dd
		fail 'alpha/beta size mismatch'
	end

case 'uniform'
	for id = 1:dd
		st.alpha{id} = 1;
		st.beta{id} = 0;
	end

otherwise
	fail('unknown kernel type %s', ktype)
end

nufft_check_dft(om, Nd, ktype)

st.tol = 0;

st.Jd = Jd;
st.Nd = Nd;
st.Kd = Kd;

M = size(om,1);
st.M = M;
st.om = om;

% scaling factors: "outer product" of 1D vectors
st.sn = 1;
for id=1:dd
	if is_kaiser_scale
		nc = [0:Nd(id)-1]'-(Nd(id)-1)/2;
		tmp = 1 ./ kaiser_bessel_ft(...
			nc/Kd(id), Jd(id), st.kb_alf(id), st.kb_m(id), 1);
	elseif streq(ktype, 'function_handle')
		tmp = 1 ./ nufft_interp_zn(0, Nd(id), Jd(id), Kd(id), st.kernel{id});
	else
		tmp = nufft_scale(Nd(id), Kd(id), st.alpha{id}, st.beta{id});
	end
	st.sn = st.sn(:) * tmp';
end
if length(Nd) > 1
	st.sn = reshape(st.sn, Nd); % [(Nd)]
else
	st.sn = st.sn(:); % [(Nd)]
end

% [J? M] interpolation coefficient vectors.  will need kron of these later
for id=1:dd
	N = Nd(id);
	J = Jd(id);
	K = Kd(id);
	if isfield(st, 'kernel')
		[c, arg] = ...
		nufft_coef(om(:,id), J, K, st.kernel{id}); % [J? M]
	else
		alpha = st.alpha{id};
		beta = st.beta{id};
		T = nufft_T(N, J, K, st.tol, alpha, beta); % [J? J?]
		[r, arg] = ...
		nufft_r(om(:,id), N, J, K, alpha, beta); % [J? M]
		c = T * r;	clear T r
	end

	gam = 2*pi/K;
	phase_scale = 1i * gam * (N-1)/2;

	phase = exp(phase_scale * arg); % [J? M] linear phase
	ud{id} = phase .* c; % [J? M]

	% indices into oversampled FFT components
	koff = nufft_offset(om(:,id), J, K); % [M 1] to leftmost near nbr
	kd{id} = mod(outer_sum([1:J]', koff'), K) + 1; % [J? M] {1,...,K?}
	if id > 1 % trick: pre-convert these indices into offsets!
		kd{id} = (kd{id}-1) * prod(Kd(1:(id-1)));
	end

end, clear c arg gam phase phase_scale koff N J K

% build sparse matrix that is [M *Kd]
% with *Jd nonzero entries per frequency point
if dd >= 3
	tmp = prod(Jd)*M*8/10^9*2;
	if tmp > 1. % only display if more than 1GB
		printm('Needs at least %g Gbyte RAM', tmp)
	end
end

kk = kd{1}; % [J1 M]
uu = ud{1}; % [J1 M]
for id = 2:dd
	Jprod = prod(Jd(1:id));
	kk = block_outer_sum(kk, kd{id}); % outer sum of indices
	kk = reshape(kk, Jprod, M);
	uu = block_outer_prod(uu, ud{id}); % outer product of coefficients
	uu = reshape(uu, Jprod, M);
end % now kk and uu are [*Jd M]

% apply phase shift
% pre-do Hermitian transpose of interpolation coefficients
phase = exp(1i * (om * n_shift(:))).'; % [1 M]
uu = conj(uu) .* phase(ones(1,prod(Jd)),:); % [*Jd M]

mm = [1:M]; mm = mm(ones(prod(Jd),1),:); % [*Jd M]
% make sparse matrix, ensuring arguments are double for stupid matlab
st.p = sparse(mm(:), double(kk(:)), double(uu(:)), M, prod(Kd));
% sparse object, to better handle single precision operations!
st.p = Gsparse(st.p, 'odim', [M 1], 'idim', [prod(Kd) 1]);

% block_outer_sum()
%
% in
%	x1	[J1 M]
%	x2	[J2 M]
% out
%	y	[J1 J2 M]	y(i1,i2,m) = x1(i1,m) + x2(i2,m)
%
function y = block_outer_sum(x1, x2)
[J1 M] = size(x1);
[J2 M] = size(x2);
xx1 = reshape(x1, [J1 1 M]); % [J1 1 M] from [J1 M]
xx1 = xx1(:,ones(J2,1),:); % [J1 J2 M], emulating ndgrid
xx2 = reshape(x2, [1 J2 M]); % [1 J2 M] from [J2 M]
xx2 = xx2(ones(J1,1),:,:); % [J1 J2 M], emulating ndgrid
y = xx1 + xx2; % [J1 J2 M]

% block_outer_prod()
function y = block_outer_prod(x1, x2)
[J1 M] = size(x1);
[J2 M] = size(x2);
xx1 = reshape(x1, [J1 1 M]); % [J1 1 M] from [J1 M]
xx1 = xx1(:,ones(J2,1),:); % [J1 J2 M], emulating ndgrid
xx2 = reshape(x2, [1 J2 M]); % [1 J2 M] from [J2 M]
xx2 = xx2(ones(J1,1),:,:); % [J1 J2 M], emulating ndgrid
y = xx1 .* xx2; % [J1 J2 M]

% nufft_check_dft()
% see if they are DFT samples; if so, give warning
function nufft_check_dft(om, Nd, ktype)
kk = om / (2*pi) .* repmat(Nd(:)', [nrow(om) 1]);
tol = 1e-6;
if all(col(abs(round(kk) - kk)) < tol) ...
	&& any(om(:)) ... % trick: ignore all-zero om used in nufft_table_init()
	&& (streq(ktype, 'minmax:kb') || streq(ktype, 'kaiser'))
	warn('DFT samples with ktype="%s" has suboptimal accuracy', ktype)
end

% nufft_init_test()
% for a more complete test, use "nufft test"
function nufft_init_test
Nd = [20 10];
st = nufft_init('epi', Nd, [5 5], 2*Nd);
if 0
	clf
	om = st.om;
	plot(om(:,1), om(:,2), 'o-')
	axis_pipi set
end
st;
st.alpha{1};


function pot = recursive_combine_pots(pot1, pot2)
% RECURSIVE_COMBINE_POTS recursive combine two potentials
% pot = recursive_combine_pots(pot1, pot2)

pot1 = reduce_pot(pot1);
pot2 = reduce_pot(pot2);
% Recursion is stopped, if recusive-combination is defined by direct combination,
% i.e. if the domain of one potential is disjoint from the head of the other.
if (isempty(myintersect(pot1.domain,pot2.cheaddom))|...
    isempty(myintersect(pot1.cheaddom,pot2.domain)))
    pot = direct_combine_pots(pot1,pot2);
else
    % Test wether one of the set-differences is not empty
    % as defined in Lauritzen99 "Stable Local Computation with Conditional Gaussian Distributions"
    % on page 9
    D12 = mysetdiff(pot1.cheaddom, pot2.domain);
    D21 = mysetdiff(pot2.cheaddom, pot1.domain);
    if (isempty(D12) & isempty(D21))
       assert(0,'Recursive combination is not defined');
    end

    if ~isempty(D12)
        % Calculate the complementary potential for the set
        % D1\D12 as defined in Lauritzen 99, page 9
    keep = mysetdiff(pot1.domain,D12);
        [margpot, comppot] = complement_pot(pot1,keep);
        margpot = reduce_pot(margpot);
        comppot = reduce_pot(comppot);
        pot = direct_combine_pots( recursive_combine_pots(margpot, pot2), comppot);
    elseif ~isempty(D21)
        keep = mysetdiff(pot2.domain,D21);
        [margpot, comppot] = complement_pot(pot2,D21);
        margpot = reduce_pot(margpot);
        comppot = reduce_pot(comppot);
        pot = direct_combine_pots( recursive_combine_pots(pot1, margpot), comppot);
    end
end