function [minCostTuples,gain]=assign3DBB(C,maximize,boundType,initMethod,maxIter,epsVal)
%%ASSIGN3DBB Solve the data fusion axial 3D assignment problem using a
%         branch-and-bound algorithm. Such problems are NP-hard and thus
%         cannot be solved in polynomial time, so this function can only
%         solve problems of a moderate size. The optimization problem being
%         solved is minimize (or maximize)
%         \sum_{i=1}^{n1}\sum_{j=1}^{n2}\sum_{k=1}^{n3}C_{i,j,k}*\rho_{i,j,k}
%         subject to
%         \sum_{i=1}^{n1}\sum_{j=1}^{n2}\rho_{i,j,k}<=1 for all k
%         \sum_{i=1}^{n1}\sum_{k=1}^{n3}\rho_{i,j,k}<=1 for all j
%         \sum_{j=1}^{n2}\sum_{k=1}^{n3}\rho_{i,j,k} =1 for all i
%         \rho_{i,j,k} = 0 or 1
%         assuming that n1<=n2<=n3, and C is and n1Xn2Xn3 cost matrix.
%         This is equivalent to the optimization problem
%         min (or max) sum_{i} C(i,phi_2(i),phi_3(i))
%         where phi_2 and phi_3 are length n1 arrangements of n2 and n3
%         items over which the minimization is performed. In general, it is
%         not required that n1<=n2<=n3 (for any other ordering, the
%         equality constraint is on the dimension with the fewest
%         elements), so the solution is given as tuples solving the problem
%         min (or max) sum_{i} C(tuple(1,i),tuple(2,i),tuple(3,i))
%         where the tuples satisfy all of the above constraints.
%
%INPUTS: C An n1Xn2Xn3 cost hypermatrix. C cannot contain any NaNs and the
%          largest finite element minus the smallest element is a finite
%          quantity (does not overflow) when performing minimization and
%          where the smallest finite element minus the largest element is
%          finite when performing maximization.  Forbidden assignments can
%          be given costs of +Inf for minimization and -Inf for
%          maximization. During minimization, there should be no elements
%          with -Inf cost and no elements with +Inf cost during
%          maximization.
% maximize If true, the minimization problem is transformed into a
%          maximization problem. The default if this parameter is omitted
%          or an empty matrix is passed is false.
% boundType This selects the bound to use for the branch and bound method.
%          These correspond to the method input of the assign3DLB function.
%          The default if this parameter is omitted or an empty matrix is
%          passed is 2.
% initMethod A parameter indicating how the branch-and-bound algorithm
%          should be initialized. Possible values are:
%          0 (The default if omitted or an empty matrix is passed) Do not
%            use an initial estimate.
%          1 Use an initial solution (and lower bound) via algorithm 0 of
%            the assign3D function.
%  maxIter If initMethod=1, then this is the maximum number of iterations
%          to allow the initialization routine in assign3D to perform. If
%          initMethod~=1, then this parameter is ignored. The default if
%          omitted or an empty matrix is passed is 10.
%   epsVal If initMethod=1, then this is both the AbsTold and RelTol inputs
%          to assign3D to determine whether the function has converged in
%          terms of the relative duality gap. If initMethod~=1, then this
%          parameter is ignored. The default if omitted or an empty matrix
%          is passed is eps(1).
%
%OUTPUTS: tuples A 3Xn1 matrix where tuples(:,i) is the ith assigned tuple
%                in C as described above.
%           gain The cost value of the optimal assignment found. This is
%                the sum of the values of the assigned elements in C.
%
%The algorithm is similar to the branch-and bound procedure describes in
%[1], which uses the "classical" branching of [2]. There is a total of
%min([n1,n2,n3]) elements to choose out of the entire C matrix. Suppose
%that n1=min([n1,n2,n3]). Then, the tuples chosen will have the first index
%from 1 to n1 and the indices of the remaining two tuple components will
%vary. The branching here is on the tuple chosen for i=1 of C(i,j,k), then
%the next level is for i=2 until branching is done for i=n1. At each level
%of branching there are n2*n3 possibilities, as is illustrated in Fig. 1 of
%[1]. Each time a branch is taken, all tuples of (j,k) sharing any common
%tuples with the branch taken (and any previous branches taken) must be
%removed. At each level, lower bounds for all n2*n3 possible branches are
%computed. Those branches with lower bounds that are less than the best
%found solution are discarded.
%
%REFERENCES:
%[1] W. P. Pierskalla, "The multidimensional assignment problem,"
%    Operations Research, vol. 16, no. 2, pp. 422-431, Mar-Apr. 1968.
%[2] R. E. Burkard and R. Rudolf, "Computational investigations on 3-
%    dimensional axial assignment problems," Belgian Journal of Operations
%    Research, Statistics and Computer Science, vol. 32, no. 1-3, pp.
%    85-98, 1993.
%
%September 2017 David F. Crouse, Naval Research Laboratory, Washington D.C.
%(UNCLASSIFIED) DISTRIBUTION STATEMENT A. Approved for public release.

if(nargin<6||isempty(epsVal))
	epsVal=eps(1);
end

if(nargin<5||isempty(maxIter))
    maxIter=10;
end

if(nargin<4||isempty(initMethod))
	initMethod=0;
end

if(nargin<3||isempty(boundType))
    boundType=2;
end

if(nargin<2||isempty(maximize))
	maximize=false;
end

%For some types of lower bounds, the cost matrix must have all non-negative
%elements for the assignment algorithm to work. This forces all of the
%elements to be positive. The delta is added back in when computing the
%gain in the end.
if(maximize==true)
    CDelta=max(C(:));
    C=-C+CDelta;
else
    CDelta=min(C(:));
    C=C-CDelta;
end

%The algorthm was implemented assuming that n1<=n2<=n3 for the
%dimensionality of C. If a matrix having a different arrangement of indices
%is passed, one could permute the indices to make the assumptions below
%hold.
nVals=size(C);
%Deal with trailing singleton dimensions.
if(length(nVals)==2)
    nVals=[nVals,1];
elseif(length(nVals)==1)
    nVals=[nVals,1,1];
end

if(length(nVals)<3||~(nVals(1)<=nVals(2)&&nVals(2)<=nVals(3)))
    error('It is required that size(C,1)<=size(C,2)<=size(C,3)')
end

n1=nVals(1);
n2=nVals(2);
n3=nVals(3);

%Get an initial estimate.
switch(initMethod)
    case 0%Do not use an initial estimate.
        q=-Inf;
        gain=Inf;
        minCostTuples=[];
    case 1
        subgradMethod=0;
        maximize=false;
        [minCostTuples,gain,q,exitCode]=assign3D(C,false,subgradMethod,maximize,maxIter,epsVal,epsVal);
        if(exitCode==-3||exitCode==-1)
            %If no valid assignment was found, then we just start the
            %algorithm without initialization. This most commonly occurs
            %when the problem is infeasible and no solution will be found
            %in the end anyway.
            q=-Inf;
            gain=Inf;
            minCostTuples=[];
        end
    otherwise
        error('Invalid initMethod specified.');
end

%If the initial approximation returned a suboptimal solution.
if(abs(gain-q)>=epsVal*gain)
    numLevels=n1;%The number of things to assign.

    %There are n1*n2*n3 total tuples. However, we define a level by the
    %value of the first index of the tuple. In the initial level, there are
    %n2*n3 possible tuples (fix the value of the first index of C and the
    %other two are free). In subsequent levels, there are
    %(n2-curLevel+1)*(n3-curLevel+1) possibilities.
    maxTuplesPerLevel=n2*n3;

    %Generate all possible tuples of the second and third coordinates that
    %can be assigned. The assignment of the first coordinate is the level
    %of the recursion below.
    origFreeTuples=genAllTuples([n2-1;n3-1],false)+1;

    %The list of tuples that are candidates in the current level.
    freeTuples=zeros(2,maxTuplesPerLevel,numLevels);
    %Save the tuples that could possibly be assigned in the first level.
    freeTuples(:,:,1)=origFreeTuples;

    %These values link the modified tuples at each level to the original
    %tuples. These are important, because the ordering of the tuples in the
    %lower levels will be changed.
    freeTupleOrigIdx=zeros(maxTuplesPerLevel,numLevels);
    freeTupleOrigIdx(:,1)=1:maxTuplesPerLevel;

    %This keeps track of how many tuples are being considered at each
    %level after eliminating those that conflict with prior assignments and
    %eliminating those whose lower bounds are too high.
    numFreeTuples=zeros(numLevels,1);
    %In the top level, all possibilities are initially considered.
    numFreeTuples(1)=maxTuplesPerLevel;

    %The list of tuples that were candidates when entering the current
    %level. In the current level, some tuples might be removed due to a low
    %cost at that level. However, when going to a higher level, all tuples
    %that do not conflict with assignments at lower levels must be
    %considered. Thus, this stores the value of the tuples in the current
    %level before pruning.
    freeTuplesEntering=zeros(2,maxTuplesPerLevel,numLevels);
    freeTuplesEntering(:,:,1)=origFreeTuples;

    %Like freeTupleOrigIdx, these link the values in freeTuplesEntering to
    %the original set of tuples.
    freeTuplesEnteringOrigIdx=zeros(maxTuplesPerLevel,numLevels);
    freeTuplesEnteringOrigIdx(:,1)=1:maxTuplesPerLevel;

    %This keeps track of how many tuples are being considered at each
    %level after eliminating those that conflict with prior assignments but
    %not eliminating anything where the lower bounds are too high.
    numFreeTuplesEntering=zeros(numLevels,1);
    %In the top level, all possibilities are considered.
    numFreeTuplesEntering(1)=maxTuplesPerLevel;

    %The tuple that is assigned at each level, after the indexation has
    %been changed to relate it to the current cost submatrix at that level.
    %We don't need to save the first index here, because it is just the
    %level number. We don't need to save the assigned tuple in the maximum
    %level, because we use it right away.
    assignedTuples=zeros(2,numLevels-1);

    %This links the assigned tuple to the index of the tuple in
    %origFreeTuples. It is simpler to use this than to try to reconstruct
    %the tuple from assignedTuples, where each tuple has been modified
    %based on assignments at lower levels. Unlike assignedTuples, this
    %needs to be numLevels in size.
    assignedTupleOrigIdx=zeros(numLevels,1);

    %The cumulative assigned cost at each level. The final cost is computed
    %at the top level and does not need to be stored in this.
    cumAssignedCost=zeros(numLevels-1,1);

    %Allocate space for the bounded total costs for assigning something at
    %each level. Each level is given by the value in the first index that
    %is assigned. For each first index, there are n2*n3 values. There are
    %no bounds for the deepest level, because the cost matrix can be
    %directly used.
    boundVals=zeros(maxTuplesPerLevel,numLevels-1);

    %This holds what was the minimum cost value before going to a higher
    %level of recursion. Thus, when returning to a particular level, if the
    %minimum cost value changed, one should check the previous bounds and
    %see if any of the candidate tuples should be thrown out and never
    %visited.
    prevMinCost=zeros(numLevels-1,1);

    %Allocate space for the marginal costs at each level (the cost
    %submatrices). We could just allocate this like
    %CostMatCur=zeros(n1,n2,n3,numLevels); and then easily address each
    %level with the final index. However, that wastes a lot of space,
    %because each level removes one element from every dimension. The first
    %matrix is n1Xn2Xn3 in size, the next is (n1-1)*(n2-1)*(n3-1) in size
    %and so on until the final level which is 1X(n2-n1+1)X(n3-n1+1) in
    %size. To save space, we shall allocate the minimum amount of space
    %required, as well as an offset array, so that we know where the
    %elements of the matrices start and end so that we can later consider
    %each matrix individually.
    costMatCurStartIdx=zeros(numLevels+1,1);
    costMatCurStartIdx(1)=1;
    for curLevel=2:(numLevels+1)
        offset=curLevel-2;
        costMatCurStartIdx(curLevel)=costMatCurStartIdx(curLevel-1)+(n1-offset)*(n2-offset)*(n3-offset);
    end
    %The final value in costMatCurStartIdx is one past the end of the
    %matrix.
    CostMatCur=zeros(costMatCurStartIdx(numLevels+1)-1,1);
    %Assign the marginal cost matrix of the first level.
    CostMatCur(costMatCurStartIdx(1):(costMatCurStartIdx(2)-1))=C(:);

    %Enter the recursion (unrolled).
    curLevel=1;
    increasingLevelNumber=true;
    while(curLevel>0)
        if(curLevel==numLevels)
            %If we have reached the top level, the final assignment is just
            %choosing the most likely free tuple in the current cost
            %matrix.

            minVal=Inf;
            minIdx=1;
            for curTuple=1:numFreeTuples(curLevel)
                i=1;
                j=freeTuples(1,curTuple,curLevel);
                k=freeTuples(2,curTuple,curLevel);
                %The dimensionality of the cost matrix at the current
                %level.
                dims=[n1-curLevel+1;n2-curLevel+1;n3-curLevel+1];
                %The index within the cost matrix at the current level.
                idx=sub2ind(dims,i,j,k);
                curCost=CostMatCur(costMatCurStartIdx(curLevel)+idx-1);

                if(curCost<minVal)
                    minVal=curCost;
                    minIdx=curTuple;
                end
            end
            if(numLevels>1)
                minVal=cumAssignedCost(curLevel-1)+minVal;
            end

            if(minVal<gain)%If a new global minimum has been found.
                gain=minVal;

                assignedTupleOrigIdx(curLevel)=freeTupleOrigIdx(minIdx,curLevel);

                minCostTuples=zeros(3,numLevels);
                for i=1:numLevels
                    minCostTuples(:,i)=[i;origFreeTuples(:,assignedTupleOrigIdx(i))];
                end
            end

            increasingLevelNumber=false;
            curLevel=curLevel-1;
            continue;
        elseif(increasingLevelNumber==true)
            %We have just entered this level, so we have to first compute
            %the lower bounds for the current level.

            curTuple=1;
            while(curTuple<=numFreeTuples(curLevel))
                i=1;
                j=freeTuples(1,curTuple,curLevel);
                k=freeTuples(2,curTuple,curLevel);

                %The i,j,k indices of the tuple are with respect to indices
                %in the shrunken cost matrix CostMatCur(i,j,k,curLevel),
                %not to the set of tuples with respect to the global cost
                %matrix C. Since we are picking off rows one at a time, i
                %will always be 1.

                %The dimensionality of the cost matrix at the current
                %level.
                dims=[n1-curLevel+1;n2-curLevel+1;n3-curLevel+1];
                %The index within the cost matrix at the current level.
                idx=sub2ind(dims,i,j,k);
                curCost=CostMatCur(costMatCurStartIdx(curLevel)+idx-1);

                %If the assignment is not allowed.
                if(~isfinite(curCost))
                    curTuple=curTuple+1;
                    continue;
                end

                %Create the cost matrix at the next level up by removing
                %the appropriate entry from each dimension of the current
                %cost matrix. First, we extract the matrix for the current
                %level.
                CCur=reshape(CostMatCur(costMatCurStartIdx(curLevel):(costMatCurStartIdx(curLevel+1)-1)),[n1-curLevel+1,n2-curLevel+1,n3-curLevel+1]);
                %The indices of the cost matrix one level up.
                spanNext=costMatCurStartIdx(curLevel+1):(costMatCurStartIdx(curLevel+2)-1);
                CostMatCur(spanNext)=CCur(2:(n1-curLevel+1),[1:(j-1),(j+1):(n2-curLevel+1)],[1:(k-1),(k+1):(n3-curLevel+1)]);

                if(curLevel>1)
                    boundVals(curTuple,curLevel)=cumAssignedCost(curLevel-1)+curCost+assign3DLB(reshape(CostMatCur(spanNext),[n1-curLevel,n2-curLevel,n3-curLevel]),boundType);
                else
                    boundVals(curTuple,1)=curCost+assign3DLB(reshape(CostMatCur(spanNext),[n1-curLevel,n2-curLevel,n3-curLevel]),boundType);
                end

                %If the bound is no better than the current best solution,
                %then remove that tuple as a contender in this level. We
                %just overwrite it with the last tuple, decriment the
                %number of tuples present and then revisit the tuple with
                %the same index (which will now be the last tuple).
                if(boundVals(curTuple,curLevel)>=gain)
                    CostMatCur(costMatCurStartIdx(curLevel)+idx-1)=Inf;

                    freeTuples(:,curTuple,curLevel)=freeTuples(:,numFreeTuples(curLevel),curLevel);
                    freeTupleOrigIdx(curTuple,curLevel)=freeTupleOrigIdx(numFreeTuples(curLevel),curLevel);
                    numFreeTuples(curLevel)=numFreeTuples(curLevel)-1;
                else
                    curTuple=curTuple+1;
                end
            end

            %If there are not enough tuples left to make a full assignment.
            %The assumption here is that numLevels>1, so if we are here,
            %there must be one tuple left to assign after assigning the one
            %in this level.
            if(numFreeTuples(curLevel)==0)
                increasingLevelNumber=false;
                curLevel=curLevel-1;
                continue;
            end

            %Sort the tuples by cost.
            [boundVals(1:numFreeTuples(curLevel),curLevel),idx]=sort(boundVals(1:numFreeTuples(curLevel),curLevel),'descend');
            freeTuples(:,1:numFreeTuples(curLevel),curLevel)=freeTuples(:,idx,curLevel);
            freeTupleOrigIdx(1:numFreeTuples(curLevel),curLevel)=freeTupleOrigIdx(idx,curLevel);

            %Record the current minimum cost value.
            prevMinCost(curLevel)=gain;
        end

        %If we are here, then either increasingLevelNumber=false and we are
        %backtracking, or increasingLevelNumber=true, and we have just
        %entered this level.
        if(increasingLevelNumber==false)
            %If we are backtracking, then we remove the last tuple visited
            %in this level from consideration.
            i=1;
            j=assignedTuples(1,curLevel);
            k=assignedTuples(2,curLevel);

            %The dimensionality of the cost matrix at the current
            %level.
            dims=[n1-curLevel+1;n2-curLevel+1;n3-curLevel+1];
            %The index within the cost matrix at the current level.
            idx=sub2ind(dims,i,j,k);
            %freeTuples(:,curLevel,numFreeTuples(curLevel),curLevel)
            %contains the last assigned tuple for this level. Here, we
            %eliminate it from further consideration by setting
            %CostMatCur(i,j,k,curLevel) to Inf and reducing the count of
            %the tuple by one.
            CostMatCur(costMatCurStartIdx(curLevel)+idx-1)=Inf;
            numFreeTuples(curLevel)=numFreeTuples(curLevel)-1;

            %If the last tuple visited leaves too few tuples left for a
            %full assignment, then go up another level.
            if(numFreeTuples(curLevel)==0)
                increasingLevelNumber=false;
                curLevel=curLevel-1;
                continue;
            end

            %Also, if the minimum bound changed, remove all tuples whose
            %bounds are larger than the new minimum.
            if(prevMinCost(curLevel)~=gain)
                prevMinCost(curLevel)=gain;

                %Find all of the tuples where the bound is no better than
                %the current best solution and remove them as contenders in
                %this level.

                %Remember that when entering this level, the largest bounds
                %are at the beginning of the boundVals array, because it
                %has been sorted in decreasing order. Thus, we have to find
                %the first index where the bound is <gain.

                idx=find(boundVals(1:numFreeTuples(curLevel),curLevel)<gain,1);

                %If this gets rid of all tuples.
                if(isempty(idx))
                    curLevel=curLevel-1;
                    continue;
                elseif(idx>1)
                    %If here, remove the tuples with bounds that are too
                    %large.
                    sel1=1:(numFreeTuples(curLevel)-idx+1);
                    sel2=idx:numFreeTuples(curLevel);
                    numFreeTuples(curLevel)=numFreeTuples(curLevel)-idx+1;

                    boundVals(sel1,curLevel)=boundVals(sel2,curLevel);

                    %Set the entries in the cost matrix associated with the
                    %tuples that are being removed to Inf, so they won't be
                    %assigned.
                    for curTuple=1:(idx-1)
                        i=1;
                        j=freeTuples(1,curTuple,curLevel);
                        k=freeTuples(2,curTuple,curLevel);
                        %The dimensionality of the cost matrix at the
                        %current level.
                        dims=[n1-curLevel+1;n2-curLevel+1;n3-curLevel+1];
                        %The index within the cost matrix at the current
                        %level.
                        idx=sub2ind(dims,i,j,k);
                        CostMatCur(costMatCurStartIdx(curLevel)+idx-1)=Inf;
                    end

                    freeTuples(:,sel1,curLevel)=freeTuples(:,sel2,curLevel);
                    freeTupleOrigIdx(sel1,curLevel)=freeTupleOrigIdx(sel2,curLevel);
                end
            end

            increasingLevelNumber=true;
        end

        %The tuple having the lowest cost is visited first.
        %Assign the tuple in this current level.
        assignedTuples(:,curLevel)=freeTuples(:,numFreeTuples(curLevel),curLevel);
        assignedTupleOrigIdx(curLevel)=freeTupleOrigIdx(numFreeTuples(curLevel),curLevel);

        %Add the cost of the assigned tuple to the cumulative assigned
        %cost.
        i=1;
        j=assignedTuples(1,curLevel);
        k=assignedTuples(2,curLevel);
        %The dimensionality of the cost matrix at the current
        %level.
        dims=[n1-curLevel+1;n2-curLevel+1;n3-curLevel+1];
        %The index within the cost matrix at the current level.
        idx=sub2ind(dims,i,j,k);
        curCost=CostMatCur(costMatCurStartIdx(curLevel)+idx-1);

        if(curLevel>1)
            cumAssignedCost(curLevel)=cumAssignedCost(curLevel-1)+curCost;
        else
            cumAssignedCost(curLevel)=curCost;
        end

        %Construct the cost matrix for the next level. This means removing
        %the entire row, column, etc. in each dimension that contains the
        %assigned tuple.
        CCur=reshape(CostMatCur(costMatCurStartIdx(curLevel):(costMatCurStartIdx(curLevel+1)-1)),[n1-curLevel+1,n2-curLevel+1,n3-curLevel+1]);
        %The indices of the cost matrix one level up.
        spanNext=costMatCurStartIdx(curLevel+1):(costMatCurStartIdx(curLevel+2)-1);
        CostMatCur(spanNext)=CCur(2:(n1-curLevel+1),[1:(j-1),(j+1):(n2-curLevel+1)],[1:(k-1),(k+1):(n3-curLevel+1)]);

        %Copy the tuples that can be considered for the next and subsequent
        %levels into freeTuples(:,:,curLevel+1) and
        %freeTuplesEntering(:,:,curLevel+1).
        %This draws tuples for the next level down from
        %freeTuplesEntering(:,:,curLevel), because freeTuples(:,:,curLevel)
        %may have had tuples removed only for curLevel due to bounding. At
        %the same time as the tuples are added, the coordinates of all
        %valid tuples are adjusted to deal with the removed elements of
        %CostMatCur.

        numFreeTuples(curLevel+1)=0;
        for curTuple=1:numFreeTuplesEntering(curLevel)
            if(any(freeTuplesEntering(:,curTuple,curLevel)==assignedTuples(:,curLevel)))
                continue;
            else
                numFreeTuples(curLevel+1)=numFreeTuples(curLevel+1)+1;

                %Free tuples are indexed with respect to the shrunken
                %matrix of costs in CostMatCur(:,:,:,curLevel+1). This
                %means that for index values > the assigned index, one must
                %be subtracted.
                freeTuples(:,numFreeTuples(curLevel+1),curLevel+1)=freeTuplesEntering(:,curTuple,curLevel)-(freeTuplesEntering(:,curTuple,curLevel)>assignedTuples(:,curLevel));
                freeTupleOrigIdx(numFreeTuples(curLevel+1),curLevel+1)=freeTuplesEnteringOrigIdx(curTuple,curLevel);
            end
        end
        numFreeTuplesEntering(curLevel+1)=numFreeTuples(curLevel+1);
        freeTuplesEntering(:,1:numFreeTuples(curLevel+1),curLevel+1)=freeTuples(:,1:numFreeTuples(curLevel+1),curLevel+1);
        freeTuplesEnteringOrigIdx(1:numFreeTuples(curLevel+1),curLevel+1)=freeTupleOrigIdx(1:numFreeTuples(curLevel+1),curLevel+1);

        %Go to the next level.
        curLevel=curLevel+1;
    end
end

%Adjust the gain for the initial offset of the cost matrix.
if(maximize==true)
    gain=-gain+CDelta*n1;
else
    gain=gain+CDelta*n1;
end
end

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


function [ err ] = residual_KR( X1, X2, K1, K2, R)

% homopraphy matrix
H = K2*R/K1;
n = size(X1,2);

x1 = X1(1,:); y1 = X1(2,:);
x2 = X2(1,:); y2 = X2(2,:);

w = H(3,1)*x1 + H(3,2)*y1 + H(3,3);
x1_2 = (H(1,1)*x1 + H(1,2)*y1 + H(1,3)) ./ w;
y1_2 = (H(2,1)*x1 + H(2,2)*y1 + H(2,3)) ./ w;
err1_2 = [x1_2 - x2; y1_2 - y2];

err = reshape(err1_2,[2*n,1]);

end


function r = loggmpdf(X, model)
% Compute log pdf of a Gaussian mixture model.
% Written by Mo Chen (sth4nth@gmail.com).
mu = model.mu;
Sigma = model.Sigma;
w = model.weight;

n = size(X,2);
k = size(mu,2);
logRho = zeros(k,n);

for i = 1:k
    logRho(i,:) = loggausspdf(X,mu(:,i),Sigma(:,:,i));
end
r = logsumexp(bsxfun(@plus,logRho,log(w)'),1);

function y = loggausspdf(X, mu, Sigma)
d = size(X,1);
X = bsxfun(@minus,X,mu);
[U,p]= chol(Sigma);
if p ~= 0
    error('ERROR: Sigma is not PD.');
end
Q = U'\X;
q = dot(Q,Q,1);  % quadratic term (M distance)
c = d*log(2*pi)+2*sum(log(diag(U)));   % normalization constant
y = -(c+q)/2;


function [x, y] = circle(senter, radius, n_points)

	%Funksjon brukes til å lage sirkler ved å bruke polarkoordinater

    s = senter;
    r = radius;
    n = n_points;

    theta = linspace(0,2*pi, n);
    rho = ones(1, n)*r;
    [x, y] = pol2cart(theta, rho);
    x = x + s(1);
    y = y + s(2);

end


function W=calcKalmanGain(R,PzPred,otherInfo)
%%CALCKALMANGAIN Using the output of the Kalman filter measurement
%           prediction function KalmanMeasPred and the covariance
%           matrix of a measurement R, compute the gain of a Kalman filter.
%           This function can be useful for creating a gain based on some
%           type of "maximum" covariance matrix for a scene for purposes of
%           giving it to the calcMissedGateCov function to determine how
%           the covariance matrix of the target state prediction should be
%           increased for a missed-detection event when gating is
%           performed.
%
%INPUTS: R The zDimXzDim measurement covariance matrix.
%   PzPred The zDimXzDim covariance matrix of the measurement predicted by
%          the filter.
% otherInfo The structure returned by the KalmanMeasPred function that
%          includes various terms that can be reused.
%
%OUTPUTS: W The xDimXzDim gain matrix used in the filter.
%
%This function produces the gain in the same manner as is done in
%KalmanUpdateWithPred and KalmanUpdate. See the comments to those functions
%for more information.
%
%EXAMPLE:
%Here, we demonstrate that the gain used in a complete update is the same
%as that obtained using just the prediction via KalmanMeasPred and then
%calcKalmanGain for the gain without using the measurement z itself.
% xPred=[1e3;-2e3;100;200];
% PPred=[28,   3.5,    6,  8.5;
%      3.5,    23,  8.5,   11;
%        6,   8.5,   18, 13.5;
%      8.5,    11, 13.5,   13];
% z=1e3*[-5.498856156296510;
%        1.199241491470584];
% R=eye(2);
% H=[0, 4, 9, 8;
%    6, 3, 0, 6];
% [~,~,~,~,W]=KalmanUpdate(xPred,PPred,z,R,H);
% [~,PzPred,otherInfo]=KalmanMeasPred(xPred,PPred,H);
% W1=calcKalmanGain(R,PzPred,otherInfo);
% %One will see that the result below is true (1).
% all(W1(:)==W(:))
%
%June 2018 David F. Crouse, Naval Research Laboratory, Washington D.C.
%(UNCLASSIFIED) DISTRIBUTION STATEMENT A. Approved for public release.

Pxz=otherInfo.Pxz;

Pzz=PzPred+R;
W=Pxz/Pzz;

end

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


function [pnt, tri] = mesh_cube()

% MESH_CUBE creates a triangulated cube
%
% Use as
%   [pos, tri] = mesh_cube()
%
% See also MESH_TETRAHEDRON, MESH_OCTAHEDRON, MESH_ICOSAHEDRON, MESH_SPHERE, MESH_CONE

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

pnt = [
  -1 -1 -1
  -1  1 -1
   1  1 -1
   1 -1 -1
  -1 -1  1
  -1  1  1
   1  1  1
   1 -1  1
  ];

tri = [
  1 2 4
  2 3 4
  1 5 6
  1 6 2
  2 6 7
  2 7 3
  3 7 8
  3 8 4
  4 8 5
  4 5 1
  5 6 8
  6 7 8
  ];


%SF_TET_P5 Fifth order Lagrange shape functions for tetrahedra (P5).
%
%   [ VBASE, NLDOF, XLDOF, SFUN ] = SF_TET_P5( I_EVAL, N_SDIM, N_VERT, I_DOF, XI, AINVJAC, VBASE )
%   Evaluates conforming fifth order P5 Lagrange shape functions on 3D tetrahedral elements
%   with values defined in the nodes, edges, faces, and cell center. XI are Barycentric coordinates.
%
%       Input       Value/[Size]           Description
%       -----------------------------------------------------------------------------------
%       i_eval      scalar:  1             Evaluate function values
%                           >1             Evaluate values of derivatives
%       n_sdim      scalar: 3              Number of space dimensions
%       n_vert      scalar: 4              Number of vertices per cell
%       i_dof       scalar: 1-56           Local basis function to evaluate
%       xi          array [4,1]            Local coordinates of evaluation point
%       aInvJac     [n,12]                 Inverse of transformation Jacobian
%       vBase       [n]                    Preallocated output vector
%                                                                                .
%       Output      Value/[Size]           Description
%       -----------------------------------------------------------------------------------
%       vBase       [n]                    Evaluated function values
%       nLDof       [4]                    Number of local degrees of freedom on
%                                          vertices, edges, faces, and cell interiors
%       xLDof       [4,n_ldof]             Local coordinates of local dofs
%       sfun        string                 Function name of called shape function
%
%   See also SF_TET_P1

% Copyright 2013-2022 Precise Simulation, Ltd.


function [logRS,logERS,V]=RSana(x,n,method,q)
%Syntax: [logRS,logERS,V]=RSana(x,n,method,q)
%____________________________________________
%
% Performs R/S analysis on a time series.
%
% logRS is the log(R/S).
% logERS is the Expectation of log(R/S).
% V is the V statistic.
% x is the time series.
% n is the vector with the sub-periods.
% method can take one of the following values
%  'Hurst' for the Hurst-Mandelbrot variation.
%  'Lo' for the Lo variation.
%  'MW' for the Moody-Wu variation.
%  'Parzen' for the Parzen variation.
% q can be either
%  a (non-negative) integer.
%  'auto' for the Lo's suggested value.
%
%
% References:
%
% Peters E (1991): Chaos and Order in the Capital Markets. Willey
%
% Peters E (1996): Fractal Market Analysis. Wiley
%
% Lo A (1991): Long term memory in stock market prices. Econometrica
% 59: 1279-1313
%
% Moody J, Wu L (1996): Improved estimates for Rescaled Range and Hurst
% exponents. Neural Networks in Financial Engineering, eds. Refenes A-P
% Abu-Mustafa Y, Moody J, Weigend A: 537-553, Word Scientific
%
% Hauser M (1997): Semiparametric and nonparametric testing for long
% memory: A Monte Carlo study. Empirical Economics 22: 247-271
%
%
% Alexandros Leontitsis
% Department of Education
% University of Ioannina
% 45110 - Dourouti
% Ioannina
% Greece
%
% University e-mail: me00743@cc.uoi.gr
% Lifetime e-mail: leoaleq@yahoo.com
% Homepage: http://www.geocities.com/CapeCanaveral/Lab/1421
%
% 1 Jan 2004.

if nargin<1 | isempty(x)==1
   error('You should provide a time series.');
else
   % x must be a vector
   if min(size(x))>1
      error('Invalid time series.');
   end
   x=x(:);
   % N is the time series length
   N=length(x);
end

if nargin<2 | isempty(n)==1
   n=1;
else
   % n must be either a scalar or a vector
   if min(size(n))>1
      error('n must be either a scalar or a vector.');
   end
   % n must be integer
   if n-round(n)~=0
       error('n must be integer.');
   end
   % n must be positive
   if n<=0
      error('n must be positive.');
   end
end

if nargin<4 | isempty(q)==1
   q=0;
else
    if q=='auto'
        t=autocorr(x,1);
        t=t(2);
        q=((3*N/2)^(1/3))*(2*t/(1-t^2))^(2/3);
    else
        % q must be a scalar
        if sum(size(q))>2
            error('q must be scalar.');
        end
        % q must be integer
        if q-round(q)~=0
            error('q must be integer.');
        end
        % q must be positive
        if q<0
            error('q must be positive.');
        end
    end
end

for i=1:length(n)

    % Calculate the sub-periods
    a=floor(N/n(i));

    % Make the sub-periods matrix
    X=reshape(x(1:a*n(i)),n(i),a);

    % Estimate the mean of each sub-period
    ave=mean(X);

    % Remove the mean from each sub-period
    cumdev=X-ones(n(i),1)*ave;

    % Estimate the cumulative deviation from the mean
    cumdev=cumsum(cumdev);

    % Estimate the standard deviation
    switch method
    case 'Hurst'
        % Hurst-Mandelbrot variation
        stdev=std(X);
    case 'Lo'
        % Lo variation
        for j=1:a
            sq=0;
            for k=0:q
                v(k+1)=sum(X(k+1:n(i),j)'*X(1:n(i)-k,j))/(n(i)-1);
                if k>0
                    sq=sq+(1-k/(q+1))*v(k+1);
                end
            end
            stdev(j)=sqrt(v(1)+2*sq);
        end
    case 'MW'
        % Moody-Wu variation
        for j=1:a
            sq1=0;
            sq2=0;
            for k=0:q
                v(k+1)=sum(X(k+1:n(i),j)'*X(1:n(i)-k,j))/(n(i)-1);
                if k>0
                    sq1=sq1+(1-k/(q+1))*(n(i)-k)/n(i)/n(i);
                    sq2=sq2+(1-k/(q+1))*v(k+1);
                end
            end
            stdev(j)=sqrt((1+2*sq1)*v(1)+2*sq2);
        end
    case 'Parzen'
        % Parzen variation
        if mod(q,2)~=0
            error('For the "Parzen" variation q must be dived by 2.');
        end
        for j=1:a
            sq1=0;
            sq2=0;
            for k=0:q
                v(k+1)=sum(X(k+1:n(i),j)'*X(1:n(i)-k,j))/(n(i)-1);
                if k>0 & k<=q/2
                    sq1=sq1+(1-6*(k/q)^2+6*(k/q)^3)*v(k+1);
                elseif k>0 & k>q/2
                    sq2=sq2+(1-(k/q)^3)*v(k+1);
                end
            end
            stdev(j)=sqrt(v(1)+2*sq1+2*sq2);
        end
    otherwise
        error('You should provide another value for "method".');
    end

    % Estiamte the rescaled range
    rs=(max(cumdev)-min(cumdev))./stdev;

    clear stdev

    % Take the logarithm of the mean R/S
    logRS(i,1)=log10(mean(rs));

    if nargout>1

        % Initial calculations fro the log(E(R/S))
        j=1:n(i)-1;
        s=sqrt((n(i)-j)./j);
        s=sum(s);

        % The estimation of log(E(R/S))
        logERS(i,1)=log10(s/sqrt(n(i)*pi/2));

        % Other estimations of log(E(R/S))
        %logERS(i,1)=log10((n(i)-0.5)/n(i)*s/sqrt(n(i)*pi/2));
        %logERS(i,1)=log10(sqrt(n(i)*pi/2));

    end

    if nargout>2
        % Estimate V
        V(i,1)=mean(rs)/sqrt(n(i));
    end

end


% sgwt_kernel_abspline3 : Monic polynomial / cubic spline / power law decay kernel
%
% function r = sgwt_kernel_abspline3(x,alpha,beta,t1,t2)
%
% defines function g(x) with g(x) = c1*x^alpha for 0<x<x1
% g(x) = c3/x^beta for x>t2
% cubic spline for t1<x<t2,
% Satisfying g(t1)=g(t2)=1
%
% Inputs :
% x : array of independent variable values
% alpha : exponent for region near origin
% beta : exponent decay
% t1, t2 : determine transition region
%
% Outputs :
% r - result (same size as x)

% This file is part of the SGWT toolbox (Spectral Graph Wavelet Transform toolbox)
% Copyright (C) 2010, David K. Hammond.
%
% The SGWT toolbox 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.
%
% The SGWT toolbox 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 the SGWT toolbox.  If not, see <http://www.gnu.org/licenses/>.

function r = sgwt_kernel_abspline3(x,alpha,beta,t1,t2)
  r=zeros(size(x));
  % compute spline coefficients
  % M a = v
  M=[[1 t1 t1^2 t1^3];...
     [1 t2 t2^2 t2^3];...
     [0 1 2*t1 3*t1^2];...
     [0 1 2*t2 3*t2^2]];
  %v=[t1^alpha ; t2^(-beta) ; alpha*t1^(alpha-1) ; -beta*t2^(-beta-1)];
  v=[1 ; 1 ; t1^(-alpha)*alpha*t1^(alpha-1) ; -beta*t2^(-beta-1)*t2^beta];
  a=M\v;

  r1=find(x>=0 & x<t1);
  r2=find(x>=t1 & x<t2);
  r3=find(x>=t2);
  r(r1)=x(r1).^alpha*t1^(-alpha);
  r(r3)=x(r3).^(-beta)*t2^(beta);

  x2=x(r2);
  r(r2)=a(1)+a(2)*x2+a(3)*x2.^2+a(4)*x2.^3;
%  tmp=polyval(flipud(a),x2);
%  keyboard


function[z1,z2,z3] = primtoriem(u1,u2,u3)
	% Transformation from primitive to Riemann invariants
global gamma
u = u2./u1; 			       c2 = gamma*(gamma-1)*(u3./u1 - 0.5*u.^2);
z2 = log((1/gamma)*c2.*u1.^(1-gamma)); c2 = sqrt(c2);
z1 = u - 2*c2/(gamma-1);	       z3 = u + 2*c2/(gamma-1);


function value = p05_f ( n, x )

%*****************************************************************************80
%
%% P05_F evaluates the function for problem p05.
%
%  Discussion:
%
%    Step function.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    07 August 2012
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Input, integer N, the number of evaluation points.
%
%    Input, real X(N,1), the evaluation points.
%
%    Output, real VALUE(N,1), the function values.
%
  value =       cos (  7.0 * x ) ...
        + 5.0 * cos ( 11.2 * x ) ...
        - 2.0 * cos ( 14.0 * x ) ...
        + 5.0 * cos ( 31.5 * x ) ...
        + 7.0 * cos ( 63.0 * x );

  return
end


function [dist]=find3DNeighbourDists(mesh,scaling);
% function [dist]=find3DNeighbourDists(mesh,scaling);
% AUTHOR: WADE
% DATE : 062700
% PURPOSE : Return a list of the distances between each node and its neighbours.
% If there are n connections in the mesh, there should be n entries in distSQ
% See also find2DNeighboutDists - does the same thing with the 2d mesh.
% ARW 031201 - Now takes a scaling argument (equiv of dimdist for mrManDist)
% This scales distances along the appropriate matrix dimensions
% ARW 082702 - Now returns the abs distance not the squared one.
if (~exist('scaling'))
	scaling=[1 1 1];
end

spX=mesh.connectionMatrix;
spY=mesh.connectionMatrix;
spZ=mesh.connectionMatrix;
nVerts=length(mesh.connectionMatrix);

% What am I doing here?
 %   S = SPARSE(i,j,s,m,n,nzmax) uses the rows of [i,j,s] to generate an
 %   m-by-n sparse matrix with space allocated for nzmax nonzeros.  The
 %   two integer index vectors, i and j, and the real or complex entries
 %   vector, s, all have the same length, nnz, which is the number of
 %   nonzeros in the resulting sparse matrix S .  Any elements of s
 %   which have duplicate values of i and j are added together.

xI=sparse((1:nVerts),(1:nVerts),mesh.uniqueVertices(:,1),nVerts,nVerts)*scaling(1);
yI=sparse((1:nVerts),(1:nVerts),mesh.uniqueVertices(:,2),nVerts,nVerts)*scaling(2);
zI=sparse((1:nVerts),(1:nVerts),mesh.uniqueVertices(:,3),nVerts,nVerts)*scaling(3);
% So we're creating 3 diagonal sparse matrices.
% They contain the x,y,z coordinates of the uniqueVertices on their diagonals.

spX=spX*xI;
spY=spY*yI;
spZ=spZ*zI;

% What does this mean? If there's a connection at between nodes i and j, there will be a '1' in
% the conmat at i,j (and j,i...).
% The above multiplication means that all the '1's (connections) in column j will be
% replaced by the X, Y, or Z ordinates of the j'th node.

% Cols of sp(X,Y,Z) are now ordinates of corresponding vertices
spX=spX-spX'; % Smart eh? So the i,jth entry is the X ordinate of the j'th node. And the j,i'th entry is the X ordinate ofthe
              % ith node. So this subtraction calculates the X distance between them.
spY=spY-spY';
spZ=spZ-spZ';
dist=sqrt(spX.^2+spY.^2+spZ.^2); % And this just sums the squares of the X,Y and Z distances. Take the sqrt of this matrix to get the
                               % edge distances.


function [isConsistent, m, model] = checkStoichiometricConsistency(model, printLevel, method)
% Verification of stoichiometric consistency by checking for at least one
% strictly positive basis in the left nullspace of `S`.
% If `S` is not stoichiometrically consistent detect conserved and unconserved
% metabolites, by returning a maximal conservation vector, which is
% a non-negative basis with as many strictly positive entries as possible.
% This omits rows of `S` that are entirely zero, when any exchange reactions
% are removed.
% The strictly positive and zero entries in `m` correspond to the conserved
% and unconserved metabolites respectively.
%
% Verification of stoichiometric consistency is as initially described in:
% `A. Gevorgyan, M. G. Poolman, and D. A. Fell
% Detection of stoichiometric inconsistencies in biomolecular
% models. Bioinformatics, 24(19):2245–2251, 2008`.
%
% Detection of conserved and unconserved metabolites based on a new
% implementation by Nikos Vlassis & Ronan Fleming.
%
% USAGE:
%
%    [isConsistent, m, model] = checkStoichiometricConsistency(model, printLevel, method)
%
% INPUT:
%    model:         structure with fields:
%
%                     * model.S - `m` x `n` stoichiometric matrix
%                     * model.mets - if exists, but `SIntRxnBool does not exist, then
%                       `findSExRxnBool` will attempt to identify exchange
%                       reactions. (optional)
%                     * model.SIntMetBool - `m` x 1 boolean vector indicating the metabolites that
%                       are thought to be exclusively involved in non-exchange
%                       reactions (optional)
%                     * model.SIntRxnBool  `n` x 1 boolean vector indicating the reactions that are
%                       thought to be non-exchange reactions. If this is not present,
%                       then we will attempt to identify such reactions,
%                       `model.rxns` exists, otherwise, we will assume all
%                       reactions are supposed to be non-exchange reactions. (optional)
%
% OPTIONAL INPUTS:
%    printLevel:    {(0), 1}
%    method:        structure with fields:
%
%                     * method.interface - {('SDCCO'),'LP', 'MILP', 'DCCO'} interface called to do the consistency check
%                     * method.solver - {(default solver as specified by CBT_LP_SOLVER), or any other CBT compatible LP solver}
%                     * method.param - solver specific parameter structure
%
% OUTPUTS:
%    isConsistent:        Solver status in standardized form:
%
%                     * 1 - Optimal solution (Stoichiometrically consistent)
%                     * 2 - Unbounded solution (Should never happen)
%                     * 0 - Infeasible (Stoichiometrically INconsistent)
%                     * -1 - No solution reported (timelimit, numerical problem etc)
%
%    m:             `m` x 1 strictly positive vector in left nullspace
%                   (empty if it does not exist)
%    model:         structure with fields:
%
%                     * .SConsistentMetBool - m x 1 boolean vector indicating metabolites involved
%                       in the maximal consistent vector
%                     * .SConsistentRxnBool - `n` x 1 boolean vector non-exchange reaction involving
%                       a stoichiometrically consistent metabolite
%
% .. Author: - Ronan Fleming   2012 initial coding
%                              2013 update with detection of conserved metabolites based on an algorithm by Nikos Vlassis.
%                              2014 update to omit trivial rows corresponding to S row that are all zero
%                              2022 interface with findStoichConsistentSubset

if ~exist('printLevel','var')
    printLevel=0;
end

resetSolver = 0;
if exist('method','var')
    if ~isfield(method,'interface')
        method.interface = 'SDCCO';
    end
    if isfield(method,'solver')
        global CBT_LP_SOLVER
        oldLPSolver = CBT_LP_SOLVER;
        resetSolver = 1;
        %set the solver and solver parameters
        solverOK = changeCobraSolver(method.solver,'LP');
    else
        global CBT_LP_SOLVER
        method.solver=CBT_LP_SOLVER;
    end
else
    method.interface='SDCCO';
    global CBT_LP_SOLVER
    method.solver=CBT_LP_SOLVER;
end

%set parameters according to feastol
feasTol = getCobraSolverParams('LP', 'feasTol');
epsilon = feasTol*10;

[nMet,nRxn]=size(model.S);
if ~isfield(model,'mets')
    %assume all reactions are internal
    model.SIntRxnBool=true(nRxn,1);
    intR='';
else
    if ~isfield(model,'SIntRxnBool') || ~isfield(model,'SIntMetBool')
        %Requires the openCOBRA toolbox
        model=findSExRxnInd(model);
        intR='internal reaction ';
    else
        intR='';
    end
end

% Check the stoichiometric consistency of the network by
% solving the following linear problem
%       min sum(m_i)
%           s.t     S'*m = 0
%                   m >= 1
% where l  is is a  mx1 vector of the molecular mass of m molecular species
N=model.S(:,model.SIntRxnBool);
LPproblem.A=N';
LPproblem.b=zeros(size(LPproblem.A,1),1);
LPproblem.lb=ones(size(LPproblem.A,2),1);
LPproblem.ub=inf*ones(size(LPproblem.A,2),1);
LPproblem.c=1*ones(size(LPproblem.A,2),1);
LPproblem.osense=1;
LPproblem.csense(1:size(LPproblem.A,1),1)='E';

%Requires the openCOBRA toolbox
solution = solveCobraLP(LPproblem,'printLevel',printLevel);

%OUTPUT
% solution Structure containing the following fields describing a LP
% solution
%  full     Full LP solution vector
%  obj      Objective value
%  rcost    Reduced costs
%  dual     Dual solution
%  solver   Solver used to solve LP problem
%
%  stat     Solver status in standardized form
%            1   Optimal solution
%            2   Unbounded solution
%            0   Infeasible
%           -1   No solution reported (timelimit, numerical problem etc)

isConsistent=solution.stat;

% If the network is not stoichiometrically consistent then one maximizes
% the number of  positive component of the molecular masses vector
if isConsistent~=1
    switch method.interface
        case 'none'
            warning(['Stoichiometrically INconsistent ' intR 'stoichiometry.']);
        case 'SDCCO'
            tic
            massBalanceCheck=0;
            fileName=[];
%             [SConsistentMetBool, SConsistentRxnBool, SInConsistentMetBool, SInConsistentRxnBool, unknownSConsistencyMetBool, unknownSConsistencyRxnBool, model, stoichConsistModel] =...
%                 findStoichConsistentSubset(model, massBalanceCheck, printLevel, fileName, epsilon)
             [~, ~, ~, ~, ~, ~, model, ~] = findStoichConsistentSubset(model, massBalanceCheck, printLevel,fileName, epsilon);
            %TODO
            m = 1*model.SConsistentMetBool;
            solution = [];
            timetaken=toc;
        case 'cvx'
            cvx_solver(method.solver)

            [nMet,~]=size(N);
            %maximal conservation vector
            %cvx code from Nikos Vlassis
            tic
            cvx_begin quiet

            variable m(nMet);
            variable z(nMet);

            maximize( ones(1,nMet) * z );

            z>=0; z<=epsilon;

            m>=z; m<=(1/epsilon);

            N'*m==0;

            cvx_end
            timetaken=toc;
            if any(isnan(m))
                error('NaN in maximal conservation vector')
            end
            %boolean indicating metabolites involved in the maximal consistent vector
            model.SConsistentMetBool=m>epsilon & model.SIntMetBool;
        case 'LP'

            [nMet,~]=size(N);

            % Solve the linear problem
            %   max sum(z_i)
            %       s.t S'*m = 0
            %           z <= m
            %           0 <= m <= 1/epsilon
            %           0 <= z <= epsilon
            nInt=nnz(model.SIntRxnBool);
            LPproblem.A=[N'      , sparse(nInt,nMet);
                         speye(nMet),      -speye(nMet)];

            LPproblem.b=zeros(nInt+nMet,1);

            LPproblem.lb=[zeros(nMet,1);zeros(nMet,1)];
            LPproblem.ub=[ones(nMet,1)*(1/epsilon);ones(nMet,1)*epsilon];

            LPproblem.c=zeros(nMet+nMet,1);
            LPproblem.c(nMet+1:2*nMet,1)=1;
            LPproblem.osense=-1;
            LPproblem.csense(1:nInt,1)='E';
            LPproblem.csense(nInt+1:nInt+nMet,1)='G';

            %Requires the COBRA toolbox
            tic
            if isfield(method,'param')
                solution = solveCobraLP(LPproblem,'printLevel',printLevel-1,method.param);
            else
                solution = solveCobraLP(LPproblem,'printLevel',printLevel-1);
            end
            timetaken=toc;

            if solution.stat==1
                m=solution.full(1:nMet,1);
                z=solution.full(nMet+1:end,1);
                if isfield(model,'SIntMetBool') && 0
                    %boolean indicating metabolites involved in the maximal consistent vector
                    model.SConsistentMetBool=m>epsilon & model.SIntMetBool;
                else
                    %boolean indicating metabolites involved in the maximal consistent vector
                    model.SConsistentMetBool=m>epsilon;
                end
            else
                disp(solution)
                error('solve for maximal conservation vector failed')
            end

            %mosek
            algorithm=solution.algorithm;
            %change back the solver
%            solverOK = changeCobraSolver(oldSolver,'LP');
        case 'DCCO'

            tic
            [~, ~, solution] = maxCardinalityConservationVector(N);
            timetaken=toc;
            method.solver='cappedL1';
            if solution.stat==1
                m = solution.l;
                %dummy z
                z = zeros(nMet,1);
                if isfield(model,'SIntMetBool')  && 0
                    %boolean indicating metabolites involved in the maximal consistent vector
                    model.SConsistentMetBool=m>epsilon & model.SIntMetBool;
                else
                    %boolean indicating metabolites involved in the maximal consistent vector
                    model.SConsistentMetBool=m>epsilon;
                end
            else
                disp(solution)
                error('solve for maximal conservation vector failed')
            end
        case 'MILP'
            [nMet,~]=size(N);

            % Solve the MILP problem
            %   max sum(z_i)
            %       s.t S'*m = 0
            %           z <= m
            %           z binary
            nInt=nnz(model.SIntRxnBool);
            MILPproblem.A=[N'      , sparse(nInt,nMet);
                         speye(nMet),      -speye(nMet)];

            MILPproblem.b=zeros(nInt+nMet,1);

            MILPproblem.lb=[zeros(nMet,1);zeros(nMet,1)];
            MILPproblem.ub=[ones(nMet,1)*(1/epsilon);ones(nMet,1)];

            MILPproblem.c=zeros(nMet+nMet,1);
            MILPproblem.c(nMet+1:2*nMet,1)=1;
            MILPproblem.osense=-1;
            MILPproblem.csense(1:nInt,1)='E';
            MILPproblem.csense(nInt+1:nInt+nMet,1)='G';
            MILPproblem.vartype(1:nMet,1)='C';
            MILPproblem.vartype(nMet+1:nMet+nMet,1)='B';
            MILPproblem.x0 = zeros(nMet+nMet,1);

%             solverOK = changeCobraSolver('gurobi','MILP');
            %Requires the COBRA toolbox
            tic
            if isfield(method,'param')
                solution = solveCobraMILP(MILPproblem,'printLevel',printLevel-1,method.param);
            else
                solution = solveCobraMILP(MILPproblem,'printLevel',printLevel-1);
            end
            timetaken=toc;

            if solution.stat==1
                m=solution.full(1:nMet,1);
                z=solution.full(nMet+1:end,1);
                if isfield(model,'SIntMetBool')  && 0
                    %boolean indicating metabolites involved in the maximal consistent vector
                    model.SConsistentMetBool=m>epsilon & model.SIntMetBool;
                else
                    %boolean indicating metabolites involved in the maximal consistent vector
                    model.SConsistentMetBool=m>epsilon;
                end
            else
                disp(solution)
                error('solve for maximal conservation vector failed')
            end
        case 'maxEnt'
            %does not work very well
            tic
            m=maxEntConsVector(N,printLevel);
            timetaken=toc;
            if isfield(model,'SIntMetBool')  && 0
                %boolean indicating metabolites involved in the maximal consistent vector
                model.SConsistentMetBool=m>epsilon & model.SIntMetBool;
            else
                %boolean indicating metabolites involved in the maximal consistent vector
                model.SConsistentMetBool=m>epsilon;
            end
            z=zeros(nMet,1);
        otherwise
            error(['unregognised method.interface = ' method.interface]);
    end
    if any(m < -feasTol)
        error('m should be greater than or equal to zero')
    end
    m(m<0)=0;
    if printLevel>0
        if isfield(method,'param')
            fprintf('%s%s%s%s%s%s%s%g%s\n','Maximal conservation vector, using ', method.interface, ' ', method.solver,' ',algorithm,', in time ',timetaken,' sec.')
        else
            fprintf('%s%s%s%s%s%g%s\n','Maximal conservation vector, using ', method.interface, ' ', method.solver,', in time ',timetaken,' sec.')
        end
        fprintf('%10f%s\n',ones(1,nMet) * m,' = Optimal objective (i.e. 1''*m)')
        fprintf('%10d%s\n', nnz(model.SConsistentMetBool),' = Number of stoichiometrically consistent rows')
        fprintf('%10g%s\n',norm(m'*N),' = || S''*m ||_inf for non-exchange reactions of S')
    end
else
    m=solution.full;
    %The only consistent rows are those corresponding to non-exchange reactions
    model.SConsistentMetBool=model.SIntMetBool;
    if printLevel>0
        fprintf('%s\n','--- Summary of stoichiometric consistency ----')
        fprintf('%6s\t%6s\n','#mets','#rxns')
        fprintf('%6u\t%6u\t%s\n',nMet,nRxn,' totals.')
        fprintf('%6u\t%6u\t%s\n',nnz(~model.SIntMetBool),nnz(~model.SIntRxnBool),' heuristically external.')
        fprintf('%6u\t%6u\t%s\n',nnz(model.SIntMetBool),nnz(model.SIntRxnBool),' heuristically internal:')
        fprintf('%6u\t%6u\t%s\n',nnz(model.SConsistentMetBool),nnz(model.SIntRxnBool),' ... of which are stoichiometrically consistent.')
    end
end

isConsistent = all(model.SConsistentMetBool==1);

if ~isfield(model,'SConsistentRxnBool')

    %OLD - incorrect way July 14th 2016 - Ronan.
    % %find every non-exchange reaction involving a stoichiometrically consistent metabolite
    % model.SConsistentRxnBool =(sum(model.S(model.SConsistentMetBool,:)~=0,1)~=0)';
    % model.SConsistentRxnBool(~model.SIntRxnBool)=0;

    %corresponding reactions exclusively involving consistent metabolites
    model.SConsistentRxnBool = ~any(model.S(~model.SConsistentMetBool, :), 1)' & model.SIntRxnBool;

    model.SConsistentRxnBool = getCorrespondingCols(model.S,model.SConsistentMetBool,model.SIntRxnBool,'inclusive');

end

if resetSolver
    %reset the solver
    solverOK = changeCobraSolver(oldLPSolver,'LP');
end


function pde = elasticitydata(para)
%% ELASTICITYDATA data for elasticity problem
%
% - mu \Delta u - (mu + lambda) grad(div u) = f    in \Omega
%                                         u = g_D  on \partial \Omega
%
% Created by Huayi Wei Monday, 27 June 2011.
%
% Copyright (C) Long Chen. See COPYRIGHT.txt for details.

if nargin == 0
    lambda = 1;
    mu = 1;
else
    if ~isstruct(para)
        exit('we need a struct data');
    end
    if ~isfield(para,'lambda') || isempty(para.lambda)
        lambda = 1;
    else
        lambda = para.lambda;
    end
    if ~isfield(para,'mu') || isempty(para.mu)
        mu = 1;
    else
        mu = para.mu;
    end
end

pde = struct('lambda',lambda,'mu',mu, 'f', @f, 'exactu',@exactu,'g_D',@g_D);
%%%%%% subfunctions %%%%%%
    function z = f(p)
       z = mu*2*pi^2.*exactu(p);
    end
    function z = exactu(p)
       x = p(:,1); y = p(:,2);
       z = [cos(pi*x).*cos(pi*y), sin(pi*x).*sin(pi*y)];
    end
    function z = g_D(p)
       z = exactu(p);
    end
end


function hrf = boyntonHIRF(t, n, tau, delay)
%
%    hrf = boyntonHIRF(t, [n=3], [tau=1.08], [delay=2.05]])
%
%Purpose:
%   Compute the Boynton et al. HIRF function.  The return values include
% both the hrf and the values of time and the parameters used in the
% computation.
%
% Equation:  (Eq 3 from Boynton & Heeger, J Neurosci 1996)
%   h(t) = [(t/tau) ^ (n-1) * exp(-t/tau)] / [tau(n-1)!]
%
% Inputs:
%   t: t should be in units of SECONDS, and reflect the time window in
%   which to estimate the HRF.
%
%   n: exponent in eq. above. (corresponds to z in standard gamma
%   functions).
%
%   tau: time constant in eq.
%
%   delay: additional delay before onset of gamma function. This is an
%   added heuristic, which may vary from subject to subject.
%
%
% Outputs:
%   hrf: estimate of the HRF sampled at t seconds.
%   IMPORTANT: If you are going to convolve
%   this for fMRI analysis, and the TR of your data is not 1, you will
%   need to resample both the input t and hrf to match the MR frames.
%
% written 2005 by wandell.
% ras, 01/2007: heavily modified: no parms struct, each arg can be
% specified separately; clarifies difference between seconds and frames,
% doesn't modify t.
if notDefined('n'),     n = 3;          end
if notDefined('tau'),   tau = 1.08;     end
if notDefined('delay'), delay = 2.05;   end

% ras 01/07: this is sometimes nice, but let's just turn it off
% altogether for now
% verbose = prefsVerboseCheck;
% if verbose,
%     disp('Boynton HIRF')
% end

% initialize the HRF to be zeros, the same size as t:
hrf = zeros( size(t) );

% The HRF is not specified for t < 0 secs. In addition, any
% values before the delay should also be zero.
% So, we only sample the HRF below, for time points after [0+delay].
% We call this sampling vector x to distinguish it from t.
x = t - delay;
x = x(x>0);  % not defined for x<0

% main computation (per Boynton & Heeger, 1996)
tmpHrf = (x/tau).^(n-1) .* exp(-(x/tau)) / (tau*(factorial(n-1)));

% paste in tmpHRF into appropriate indices in hrf, corresponding
% to the (shifted after delay) time points:
hrf(end-length(tmpHrf)+1:end) = tmpHrf;

return;


function [X]=missmult(A,B)

%MISSMULT product of two matrices containing NaNs
%
%[X]=missmult(A,B)
%This function determines the product of two matrices containing NaNs
%by finding X according to
%     X = A*B
%If there are columns in A or B that are pur missing values,
%then there will be entries in X that are missing too.
%
%The result is standardized, that is, corrected for the lower
%number of contributing terms.
%
%Missing elements should be denoted by 'NaN's

% 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.

%INBOUNDS
%REALONLY

[ia ja]=size(A);
[ib jb]=size(B);
X=zeros(ia,jb);

one_arry=ones(ia,1);
for j=1:jb,
   p=one_arry*B(:,j)';
   tmpMat=A.*p;
   X(:,j)=misssum(tmpMat')';
end;


function err = getHcurlerror3NE(node,elem,curlE,Eh,markedElem)
%% GETHCURLERROR3NE Hcurl norm of approximation error for the lowest order Nedelect element in 3-D.
%
% err = getHcurlerror3NE(node,elem,curlE,Eh,markedElem);
%
% Example
%
%     node = [-1,-1,-1; 1,-1,-1; 1,1,-1; -1,1,-1; -1,-1,1; 1,-1,1; 1,1,1; -1,1,1];
%     elem = [1,2,3,7; 1,6,2,7; 1,5,6,7; 1,8,5,7; 1,4,8,7; 1,3,4,7];
%     maxIt = 4;
%     HcurlErr = zeros(maxIt,1);
%     N = zeros(maxIt,1);
%     for k = 1:maxIt
%         [node,elem] = uniformbisect3(node,elem);
%         [elem2dof,edge] = dof3edge(elem);
%         pde = Maxwelldata2;
%         uI = edgeinterpolate(pde.exactu,node,edge);
%         HcurlErr(k) = getHcurlerror3NE(node,elem,pde.curlu,uI);
%         N(k) = length(uI);
%     end
%     r = showrate(N,HcurlErr,1,'b-+');
%     legend('||u-u_I||_{curl}',['N^{' num2str(r) '}'],'LOCATION','Best');
%
% See also getHcurlerror3NE1, getHcurlerror3NE2, getL2error3NE
%
% Copyright (C) Long Chen. See COPYRIGHT.txt for details.

%% Sort elem to ascend ordering
elem = sort(elem,2);

%% Construct Data Structure
elem2dof = dof3edge(elem);
NT = size(elem,1);% Ndof = max(elem2dof(:)); %N = size(node,1);
% compute curl phi
[Dlambda,volume] = gradbasis3(node,elem);
% curl phi = 2*Dlambda_i cross Dlambda_j;
curlPhi(:,:,6) = 2*cross(Dlambda(:,:,3),Dlambda(:,:,4),2);
curlPhi(:,:,1) = 2*cross(Dlambda(:,:,1),Dlambda(:,:,2),2);
curlPhi(:,:,2) = 2*cross(Dlambda(:,:,1),Dlambda(:,:,3),2);
curlPhi(:,:,3) = 2*cross(Dlambda(:,:,1),Dlambda(:,:,4),2);
curlPhi(:,:,4) = 2*cross(Dlambda(:,:,2),Dlambda(:,:,3),2);
curlPhi(:,:,5) = 2*cross(Dlambda(:,:,2),Dlambda(:,:,4),2);
curlEhp = zeros(NT,3);
for k = 1:6
    curlEhp = curlEhp + ...
    repmat(Eh(elem2dof(:,k)),1,3).*curlPhi(:,:,k);
end

%% compute Hcurl error element-wise
[lambda,w] = quadpts3(2);
nQuad = size(lambda,1);
err = zeros(NT,1);
for p = 1:nQuad
    % quadrature points in the x-y-z coordinate
    pxy = lambda(p,1)*node(elem(:,1),:) ...
        + lambda(p,2)*node(elem(:,2),:) ...
        + lambda(p,3)*node(elem(:,3),:) ...
        + lambda(p,4)*node(elem(:,4),:);
    if isnumeric(curlE) % a constant vector
        curlEp = repmat(curlE,NT,1);
    else % function handel
        curlEp = curlE(pxy);
    end
%     curlEp = curlE(pxy);
    % compute Ehp at quadrature points
    err = err + w(p)*sum((curlEp - curlEhp).^2,2);
end
err = err.*volume;
% modify the error
err(isnan(err)) = 0; % remove the singular part
if (nargin == 5) && ~isempty(markedElem)
    err = err(markedElem); % error on some marked region
end
err = sqrt(sum(err));
%% TODO write more M-lint


function window = mandelbrotViewer()
%mandelbrotViewer  view and explore the Mandelbrot set using a GPU
%
%   mandelbrotViewer() opens a MATLAB figure window showing the Mandelbrot
%   set. Use the usual zoom and pan controls from the figure window toolbar
%   to navigate around and explore, or click "animate" to see a pre-defined
%   path through the set. You can move back to the initial view at any time
%   by clicking the "reset" button or add the current view to the animation
%   list using "add".
%
%   The control panel can be hidden using the right-hand toolbar button.
%
%   A selector allows a choice of four ways to calculate each frame:
%
%   1. CPU: All calculations are performed by MATLAB on the host CPU. The
%   algorithm is fully vectorized and avoids indexing to give an efficient
%   calculation. Even so, this may take a few seconds to calculate each
%   frame.
%
%   2. GPU (simple): The same algorithm as (1) is used but the input
%   coordinates are switched to being stored on the GPU. This causes
%   MATLAB to operate on the resulting data array on the GPU with no other
%   code change. This gives some speedup at virtually no coding cost.
%
%   3. GPU Arrayfun: Now we change the code so that instead of many
%   operations running on the full data matrix, we now specify a single
%   "calculateElement" operation and run it for each element. Roughly
%   speaking, MATLAB translates one element into one thread on the GPU,
%   giving huge speedups for large arrays. Note that all code is still
%   written in MATLAB and the user needs no knowledge of how GPU kernels
%   are constructed and executed.
%
%   4. CUDAKernel: Taking things to the limit, we now hand-craft the
%   element-wise algorithm used in (3) in CUDA C++. The resulting kernel is
%   called from MATLAB using the CUDAKernel system, requiring the user to
%   specify the thread and block arrangements to use.
%
%   Note that version 3 gets us most of the speedup achieved by the
%   hand-crafted CUDA (version 4) but without any need to leave the comfort
%   of MATLAB!
%
%   See also:  gpuArray, mandelbrotViewerProcessElement

%   Copyright 2010-2011 The Mathworks, Inc.

% Check that we are running in R2011a or above and have a GPU
matlabVersionCheck();
gpuCheck();

% Define some global (to this file) data structures so that they can be
% used by all the helper functions.
data = createData();
gui = createGUI();
% Make sure the image is updated now that the window is onscreen
redraw();

% Return the window handle if requested
if nargout
    window = gui.Window;
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Here are the four different Mandelbrot Set computations

    function logCount = computeMandelbrotCPU( xlim, numx, ylim, numy, maxIters )
        % Create a view of the Mandelbrot set using only the CPU.
        % This is the base-line version of the algorithm that is adapted to
        % run on the GPU in different ways in the functions below.

        % Create the input arrays
        escapeRadius2 = 400; % Square of escape radius
        x = linspace( xlim(1),  xlim(2), numx );
        y = linspace( ylim(1),  ylim(2), numy );
        [x0,y0] = meshgrid(x, y);
        count = zeros( size( x0 ) );
        z0 = complex( x0, y0 );
        z = z0;

        % Calculate
        for n = 0:maxIters
            inside = ((real(z).^2 + imag(z).^2) <= escapeRadius2);
            count = count + inside;
            z = inside.*(z.*z + z0) + (1-inside).*z;
        end
        magZ2 = real(z).^2 + imag(z).^2;
        logCount = log( count + 1 - log( log( max(magZ2,escapeRadius2) ) / 2 ) / log(2) );
    end % computeMandelbrotCPU

    function logCount = computeMandelbrotGPU( xlim, numx, ylim, numy, maxIters )
        % Compute using GPUArray overloads.
        % In this version the main calculation is exactly as it was for the
        % CPU version, we have simply changed the input grid to be on the
        % GPU. When MATLAB encounters GPU data it tries to run any
        % functions on the GPU. This provides a simple way to see if the
        % GPU helps without altering your code.

        % Setup the input grid on the GPU
        escapeRadius2 = 400; % Square of escape radius
        x = parallel.gpu.GPUArray.linspace( xlim(1),  xlim(2), numx );
        y = parallel.gpu.GPUArray.linspace( ylim(1),  ylim(2), numy );
        count = parallel.gpu.GPUArray.zeros( numy, numx );
        [x0,y0] = meshgrid(x, y);
        z0 = complex( x0, y0 );

        % Calculate
        z = z0;
        for n = 0:maxIters
            inside = ((real(z).^2 + imag(z).^2) <= escapeRadius2);
            count = count + inside;
            z = inside.*(z.*z + z0) + (1-inside).*z;
        end
        magZ2 = real(z).^2 + imag(z).^2;
        logCount = log( count + 1 - log( log( max(magZ2,escapeRadius2) ) / 2 ) / log(2) );

        % Gather the result back to the CPU
        logCount = gather( logCount );
    end % computeMandelbrotGPU

    function logCount = computeMandelbrotArrayFun( xlim, numx, ylim, numy, maxIters )
        % Compute using GPU arrayfun.
        % The second way in which MATLAB can use the GPU is by placing your
        % algorithm inside a helper function and calling it using ARRAYFUN
        % with some GPU data as input. The helper function is converted
        % into native GPU code (PTX) and each element of the input array is
        % processed in a separate GPU thread. The helper function must
        % operate only one scalars. Here we have taken the "calculate" code
        % above and put it in a helper "mandelbrotViewerProcessElement"
        % that will be converted into native code and run on the GPU.

        % Create the input arrays
        escapeRadius2 = 400;
        x = parallel.gpu.GPUArray.linspace( xlim(1),  xlim(2), numx );
        y = parallel.gpu.GPUArray.linspace( ylim(1),  ylim(2), numy );
        [x0,y0] = meshgrid(x, y);

        % Calculate
        [logCount] = arrayfun( @mandelbrotViewerProcessElement, x0, y0, ...
            escapeRadius2, maxIters );

        % Gather the result back to the CPU
        logCount = gather( logCount );
    end % computeMandelbrotCPU

    function logCount = computeMandelbrotCUDAKernel( xlim, numx, ylim, numy, maxIters )
        % Use pre-existing CUDA/C++ code.
        % The final way in which MATLAB can use the GPU is by calling some
        % hand-written CUDA code. The "CUDAKernel" interface allows the
        % function to be specified along with the number of threads and
        % blocks to use. This requires some knowledge of how GPUs work, but
        % does allow you to easily use existing CUDA kernels with MATLAB
        % data.

        % Create the input arrays
        escapeRadius = 20;
        x = parallel.gpu.GPUArray.linspace( xlim(1),  xlim(2), numx );
        y = parallel.gpu.GPUArray.linspace( ylim(1),  ylim(2), numy );
        [x0,y0] = meshgrid(x, y);

        % Make sure we have sufficient blocks to cover the whole array
        numElements = numel( x0 );
        data.Kernel.ThreadBlockSize = [data.Kernel.MaxThreadsPerBlock,1,1];
        data.Kernel.GridSize = [ceil(numElements/data.Kernel.MaxThreadsPerBlock),1];

        % Call the kernel
        logCount = parallel.gpu.GPUArray.zeros( size( x0 ) );
        logCount = feval( data.Kernel, logCount, ...
            x0, y0, ...
            escapeRadius, maxIters, numElements );
        logCount = gather( logCount );
    end % computeMandelbrotCUDAKernel

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Everything else is a callback or helper function.

    function out = createData()
        out = struct( ...
            'MaxIterations', 5000, ...
            'OrigXLim', [-2 1], ...
            'OrigY', 0, ...
            'XLim', [-2 1], ...
            'Y', 0, ...
            'CalculationMethods', {{
            'CPU'
            'GPU (simple)'
            'GPU ArrayFun'
            'CUDAKernel'
            }}, ...
            'IsAnimating', false, ...
            'NextLocation', 1, ...
            'LastFrameTime', now(), ...
            'WindowPixelSize', [100 100], ...
            'SelectedCalculationMethod', 'CUDAKernel', ...
            'ControlsVisible', true, ...
            'WriteVideo', false, ...
            'VideoWriter',  [] );

        % open writer
        if out.WriteVideo
            out.VideoWriter = VideoWriter('out.avi');
            out.VideoWriter.FrameRate = 20;
            out.VideoWriter.Quality = 90;
            open( out.VideoWriter );
        end

        % Read the location list from a local file
        out.LocationList = readLocationList();

        % Load the CUDA kernel
        out.Kernel = loadKernel();
    end % createData

    function out = createGUI()
        % Create the GUI, storing handles in the global GUI structure
        out.Window = figure( ...
            'Name', 'Mandelbrot viewer v1.1', ...
            'NumberTitle', 'off', ...
            'HandleVisibility', 'off', ...
            'MenuBar', 'none', ...
            'ToolBar', 'figure', ...
            'Renderer', 'ZBuffer' ); % Can't use painters as colormaps are broken for >256 colors!
        out.MainAxes = axes( ...
            'Parent', out.Window, ...
            'Position', [0 0 1 1], ...
            'XLim', data.XLim, ...
            'YLim', [-1 1], ...
            'CLim', log([1 data.MaxIterations]), ...
            'XTick', [], 'YTick', [], ...
            'DataAspectRatio', [1 1 1] );
        out.Image = image( ...
            'XData', [0 1], ...
            'YData', [0 1], ...
            'XLimInclude', 'off', ...
            'YLimInclude', 'off', ...
            'CData', nan, ...
            'CDataMapping', 'Scaled', ...
            'HandleVisibility', 'off', ...
            'Parent', out.MainAxes );
        % Add a line so that zooming works. Strange but true.
        line( 'Parent', out.MainAxes, 'XData', [-2 2], 'YData', [-2 2], ...
            'Visible', 'off', ...
            'HitTest', 'off' );
        set( out.MainAxes, 'XLimMode', 'manual', 'YLimMode', 'manual', ...
            'CLim', [0 1] );
        colormap( out.MainAxes, jet2(1000) );

        out.ControlPanel = uipanel( ...
            'Parent', out.Window, ...
            'BackgroundColor', 'k', ...
            'Units', 'Pixels', ...
            'Position', [10 10 155 78] );

        % Some text for showing compute time
        out.ComputeText = uicontrol( ...
            'Style', 'Text', ...
            'String', 'Computed in 0ms', ...
            'BackgroundColor', 'k', ...
            'ForegroundColor', 'g', ...
            'FontSize', 7, ...
            'Parent', out.ControlPanel, ...
            'Position', [5 16 145 14] );
        out.FrameRateText = uicontrol( ...
            'Style', 'Text', ...
            'String', 'Displaying at 0fps', ...
            'BackgroundColor', 'k', ...
            'ForegroundColor', 'g', ...
            'FontSize', 7, ...
            'Parent', out.ControlPanel, ...
            'Position', [5 2 145 14] );

        % Create a drop-down for selecting the calculation method
        out.MethodSelector = uicontrol( ...
            'Style', 'PopupMenu', ...
            'String', data.CalculationMethods, ...
            'Value', numel( data.CalculationMethods ), ...
            'FontSize', 7, ...
            'Parent', out.ControlPanel, ...
            'BackgroundColor', 0.8*[1 1 1], ...
            'Position', [5 54 145 16], ...
            'Callback', @onCalculationMethodChanged );
        out.AddButton = uicontrol( ...
            'Style', 'ToggleButton', ...
            'String', 'Add', ...
            'FontSize', 7, ...
            'Parent', out.ControlPanel, ...
            'BackgroundColor', 0.6*[1 1 1], ...
            'Position', [5 30 45 16], ...
            'TooltipString', 'Add the current location to the animation list', ...
            'Callback', @onAddPressed );
        out.ResetButton = uicontrol( ...
            'Style', 'ToggleButton', ...
            'String', 'Reset', ...
            'FontSize', 7, ...
            'Parent', out.ControlPanel, ...
            'BackgroundColor', 0.6*[1 1 1], ...
            'Position', [55 30 45 16], ...
            'TooltipString', 'Reset the view to the top', ...
            'Callback', @onResetPressed );
        out.AnimateButton = uicontrol( ...
            'Style', 'ToggleButton', ...
            'String', 'Animate', ...
            'FontSize', 7, ...
            'Parent', out.ControlPanel, ...
            'BackgroundColor', 0.6*[1 1 1], ...
            'Position', [105 30 45 16], ...
            'TooltipString', 'Start/stop animating between stored locations', ...
            'Callback', @onPlayPressed );

        % Remove some things we don't want from the toolbar and add a
        % toggle to the toolbar to hide the controls
        tb = findall( out.Window, 'Type', 'uitoolbar' );
        delete( findall( tb, 'Tag', 'Standard.FileOpen' ) );
        delete( findall( tb, 'Tag', 'Standard.NewFigure' ) );
        delete( findall( tb, 'Tag', 'Standard.EditPlot' ) );
        delete( findall( tb, 'Tag', 'Exploration.Brushing' ) );
        delete( findall( tb, 'Tag', 'Exploration.DataCursor' ) );
        delete( findall( tb, 'Tag', 'Exploration.Rotate' ) );
        delete( findall( tb, 'Tag', 'DataManager.Linking' ) );
        delete( findall( tb, 'Tag', 'Plottools.PlottoolsOn' ) );
        delete( findall( tb, 'Tag', 'Plottools.PlottoolsOff' ) );
        out.AnimateToggle = uitoggletool( ...
            'Parent', tb, ...
            'CData', readIcon( 'icon_play.png' ), ...
            'TooltipString', 'Start/stop animating between stored locations', ...
            'State', 'off', ...
            'Separator', 'on', ...
            'ClickedCallback', @onPlayToolbarPressed );
        out.ShowControlsToggle = uitoggletool( ...
            'Parent', tb, ...
            'CData', readIcon( 'icon_mandelControls.png' ), ...
            'TooltipString', 'Show/hide the Mandelbrot control panel', ...
            'State', 'on', ...
            'ClickedCallback', @onControlsTogglePressed );

        % Add listeners so that we can redraw when the axes are moved
        axHandle = handle( out.MainAxes );
        out.Listeners = [
            handle.listener( axHandle, findprop( axHandle, 'YLim' ), 'PropertyPostSet', @onLimitsChanged )
            ]; %#ok<NBRAK>
        % Also redraw if resized
        set( out.Window, 'ResizeFcn', @onFigureResize, ...
            'CloseRequestFcn', @onFigureClose );
    end % createGUI

    function onLimitsChanged( ~, ~ )
        redraw();
    end % onLimitsChanged

    function onFigureResize( ~, ~ )
        % Change the axes limits to exactly fit the figure
        pos = get( gui.Window, 'Position' );
        xlim = get( gui.MainAxes, 'XLim' );
        ylim = get( gui.MainAxes, 'YLim' );
        delta_ylim = ( diff( xlim )*pos(4)/pos(3) - diff( ylim ) ) / 2;
        data.WindowPixelSize = pos(3:4);
        % Set the YLim to give the correct aspect. This will trigger a
        % redraw
        set( gui.MainAxes, 'YLim', ylim + delta_ylim*[-1 1] );
    end % onFigureResize

    function onFigureClose( ~, ~ )
        % Clear up
        data.IsAnimating = false;
        if data.WriteVideo
            close( data.VideoWriter );
        end
        delete( gui.Window );
    end % onFigureClose

    function onCalculationMethodChanged( ~, ~ )
        idx = get( gui.MethodSelector, 'Value' );
        data.SelectedCalculationMethod = data.CalculationMethods{idx};
        redraw();
    end % onCalculationMethodChanged

    function onAddPressed( ~, ~ )
        disp('Add')
        fprintf( 'Adding location [%1.15f, %1.15f], %1.15f\n', ...
            data.XLim, data.Y );
        idx = numel( data.LocationList ) + 1;
        data.LocationList(idx).XLim = data.XLim;
        data.LocationList(idx).Y = data.Y;
        writeLocationList( data.LocationList );
        % Release the button
        set( gui.AddButton, 'Value', 0 );
    end % onAddPressed

    function onResetPressed( ~, ~ )
        disp('Reset')
        fprintf( 'Leaving location [%1.15f, %1.15f], %1.15f\n', ...
            data.XLim, data.Y );
        pos = get( gui.Window, 'Position' );
        aspect = pos(4)/pos(3);
        ylim = diff( data.OrigXLim ) * aspect / 2 * [-1 1];
        set( gui.MainAxes, 'XLim', data.OrigXLim, 'YLim', data.OrigY + ylim );
        set( gui.ResetButton, 'Value', 0 );
    end % onResetPressed

    function onPlayPressed( ~, ~ )
        disp('Play')
        if get( gui.AnimateButton, 'Value' )==1
            updateAnimationControls(true);
            while ishandle(gui.AnimateButton) && (get( gui.AnimateButton, 'Value' )==1)
                newXLim = data.LocationList(data.NextLocation).XLim;
                newY    = data.LocationList(data.NextLocation).Y;
                animatedMove( newXLim, newY );
                if numel( data.LocationList )>1
                    % Choose a random location
                    thisLocation = data.NextLocation;
                    while data.NextLocation == thisLocation
                        data.NextLocation = randi( numel( data.LocationList ), 1 );
                    end
                    fprintf( 'Next location: %d\n', data.NextLocation )
                else
                    % Only one location, so stop
                    data.IsAnimating = false;
                    updateAnimationControls( false );
                end
            end
        else
            data.IsAnimating = false;
            updateAnimationControls( false );
        end
    end % onPlayPressed

    function onPlayToolbarPressed( ~, evt )
        ison = strcmpi(get( gui.AnimateToggle, 'State' ), 'on');
        updateAnimationControls( ison );
        onPlayPressed(gui.AnimateButton, evt);
    end % onPlayToolbarPressed

    function updateAnimationControls( isAnimating )
        if isAnimating
            set( gui.AnimateButton, 'Value', 1 );
            set( gui.AnimateToggle, 'State', 'on' );
        else
            set( gui.AnimateButton, 'Value', 0 );
            set( gui.AnimateToggle, 'State', 'off' );
        end
        drawnow();
    end % updateAnimationControls

    function onControlsTogglePressed( ~, ~ )
        % Toggle the control panel on and off
        disp('Toggle controls')
        pos = get( gui.ControlPanel, 'Position' );
        if strcmpi( get( gui.ShowControlsToggle, 'State' ), 'off' )
            % Turn it off (move offscreen)
            pos(1) = -pos(3)-10;
        else
            % Turn it on (move onscreen)
            pos(1) = 10;
        end
        set( gui.ControlPanel, 'Position', pos );

    end

    function animatedMove( targetXLim, targetY )
        % Form a zoom path between the two
        data.IsAnimating = true;
        if isequal( data.XLim, targetXLim ) && isequal( data.Y, targetY )
            data.IsAnimating = false;
            return;
        end

        % Perform a zoom and translate arc
        maxNumSteps = 1000;
        distTravelled = sqrt( (mean( data.XLim ) - mean( targetXLim )).^2 ...
            + (data.Y - targetY).^2 );
        adjustRatio = exp( -10*linspace(-3,3,maxNumSteps).^2 );
        adjustRatio = adjustRatio - min(adjustRatio);
        ratio = cumsum( adjustRatio ); ratio = ratio / ratio(end);
        minXPath = interp1( [0,1], [data.XLim(1),targetXLim(1)], ratio );
        maxXPath = interp1( [0,1], [data.XLim(2),targetXLim(2)], ratio );
        maxXRange = max( maxXPath - maxXPath );
        xlimAdjust = max(0, 0.3*distTravelled - maxXRange);
        minXPath = minXPath - xlimAdjust*adjustRatio;
        maxXPath = maxXPath + xlimAdjust*adjustRatio;

        % Cull the ends if there's negligable motion. This helps to keep
        % things smooth but without long periods of no apparant motion.
        tolerance = 0.001;
        xRange = maxXPath - minXPath;
        firstGood = find( (xRange > (1+tolerance)*xRange(1)) | (xRange < (1-tolerance)*xRange(1)), 1, 'first' );
        if ~isempty( firstGood ) && firstGood > 2
            toCull = 2:firstGood-1;
        else
            toCull = [];
        end
        lastGood = find( (xRange > (1+tolerance)*xRange(end)) | (xRange < (1-tolerance)*xRange(end)), 1, 'last' );
        if ~isempty( lastGood ) && lastGood < numel(xRange)-1
            toCull = [toCull, lastGood:numel(xRange)-1];
        end
        ratio(toCull) = [];
        minXPath(toCull) = [];
        maxXPath(toCull) = [];
        xRange(toCull) = [];

        if ~isempty( ratio )
            % Work out the aspect ratio
            pos = get( gui.Window, 'Position' );
            aspect = pos(4)/pos(3);

            YPath = interp1( [0,1], [data.Y,targetY], ratio );
            heightPath = aspect*xRange;
            minYPath = YPath - 0.5*heightPath;
            maxYPath = YPath + 0.5*heightPath;
            for ii=1:numel(ratio)
                % Setting the limits will cause a redraw
                set( gui.MainAxes, ...
                    'XLim', [minXPath(ii),maxXPath(ii)], ...
                    'YLim', [minYPath(ii),maxYPath(ii)] );
                if data.IsAnimating == false
                    break;
                end
            end
        end
        data.IsAnimating = false;
        % Do a final redraw at full res
        redraw();
    end % animatedMove

    function kernel = loadKernel()
        thisDir = fileparts( mfilename( 'fullpath' ) );
        baseName = 'mandelbrotViewerProcessElement';
        data.CUDAFile = fullfile( thisDir, [baseName,'.cu'] );
        ptxname = [baseName,'.',parallel.gpu.ptxext];
        data.PTXFile = fullfile( thisDir, ptxname );
        if exist( data.PTXFile, 'file' ) ~= 2
            close( gui.Window );
            error( 'mandelbrotViewer:MissingPTX', 'Could not find ''%s''. Please use NVCC to compile it', ptxname );
        end
        kernel = parallel.gpu.CUDAKernel( data.PTXFile, data.CUDAFile );
    end % loadKernel

    function redraw()
        % Protect against the window closing
        if ~ishandle(gui.MainAxes)
            return;
        end
        % To work out what to draw and at what resolution we need the axis
        % limits and pixel counts.
        xlim = get(gui.MainAxes,'XLim');
        ylim = get(gui.MainAxes,'YLim');
        data.XLim = xlim;
        data.Y = mean( ylim );
        imWidth = data.WindowPixelSize(1);
        imHeight = data.WindowPixelSize(2);
        if data.IsAnimating && (imWidth*imHeight>600000)
            % To speed up animations with large windows, subsample by 2
            imWidth = round(imWidth/2);
            imHeight = round(imHeight/2);
        end

        zoomLevel = imWidth / diff( xlim );
        maxIterations = min( data.MaxIterations, 200 + 0.1*sqrt(zoomLevel) );

        % Call the computation
        t = tic;
        switch( data.SelectedCalculationMethod )
            case 'CUDAKernel'
                logCount = computeMandelbrotCUDAKernel( xlim, imWidth, ...
                    ylim, imHeight, ...
                    maxIterations );

            case 'CPU'
                logCount = computeMandelbrotCPU( xlim, imWidth, ...
                    ylim, imHeight, ...
                    maxIterations );

            case 'GPU (simple)'
                logCount = computeMandelbrotGPU( xlim, imWidth, ...
                    ylim, imHeight, ...
                    maxIterations );

            case 'GPU ArrayFun'
                logCount = computeMandelbrotArrayFun( xlim, imWidth, ...
                    ylim, imHeight, ...
                    maxIterations );

            otherwise
                error( 'mandelbrotViewer:BadMethod', 'Unrecognised calculation method ''%s''', data.SelectedCalculationMethod );
        end

        computeTime = toc(t);

        minCount = min( logCount(:) );
        logCount = (logCount - minCount) ./ (log(maxIterations+1)-minCount);
        % Guard against a closed window
        if ~ishandle( gui.Image )
            return;
        end
        set( gui.Image, ...
            'XData', xlim, ...
            'YData', ylim, ...
            'CData', logCount );
        if data.ControlsVisible
            set( gui.ComputeText, 'String', sprintf( 'Computed in %dms', round(1000*computeTime) ) )

            % Capture the current time for frame-rate calculations
            thisFrameTime = now();
            framerate = 1 / (86400*(thisFrameTime - data.LastFrameTime)); % convert days to seconds
            set( gui.FrameRateText, 'String', sprintf( 'Displaying at %dfps', round(framerate) ) )
            data.LastFrameTime = thisFrameTime;

            % Force a redraw
            drawnow();
        end

        % Capture!
        if data.WriteVideo
            t0 = now();
            currFrame = getframe( gui.Window );
            writeVideo( data.VideoWriter, currFrame );
            % Also reset the frame time to exclude the video writing
            delta_t = now() - t0;
            data.LastFrameTime = data.LastFrameTime + delta_t;
        end

    end % redraw

    function locations = readLocationList()
        fid = fopen( 'locations.csv', 'rt' );
        if fid<0
            close( gui.Window );
            error( 'mandelbrotViewer:BadLocationRead', 'Could not open location list for reading: ''locations.csv''' );
        end

        locData = textscan( fid, '%f,%f,%f' );
        N = size( locData{1}, 1 );
        if N<1
            close( gui.Window );
            error( 'mandelbrotViewer:EmptyLocationFile', 'No locations found in: ''locations.csv''' );
        end
        locations = struct( ...
            'XLim', cell( N, 1 ), ...
            'Y', cell( N, 1 ) );
        for ii=1:N
            locations(ii).XLim = [locData{1}(ii), locData{2}(ii)];
            locations(ii).Y = locData{3}(ii);
        end

        fclose( fid );
    end % readLocationList

    function writeLocationList( locations )
        fid = fopen( 'locations.csv', 'wt' );
        if fid<0
            error( 'mandelbrotViewer:BadLocationWrite', 'Could not open location list for writing: ''locations.csv''' );
        end
        N = numel( locations );
        for ii=1:N
            fprintf( fid, '%1.15f,%1.15f,%1.15f\n', ...
                locations(ii).XLim(1), ...
                locations(ii).XLim(2), ...
                locations(ii).Y );
        end

        fclose( fid );
    end % writeLocationList

    function cdata = readIcon( filename )
        [cdata,~,alpha] = imread( filename );
        idx = find( ~alpha );
        page = size(cdata,1)*size(cdata,2);
        cdata = double( cdata ) / 255;
        cdata(idx) = nan;
        cdata(idx+page) = nan;
        cdata(idx+2*page) = nan;
    end % readIcon

    function matlabVersionCheck()
        % R2011a is v7.12
        majorMinor = sscanf( version, '%d.%d' );
        if (majorMinor(1)<7) || (majorMinor(1)==7 && majorMinor(2)<13)
            error( 'mandelbrotViewer:MATLABTooOld', 'mandelbrotViewer requires MATLAB R2011b or above.' );
        end
    end % matlabVersionCheck

    function gpuCheck()
        try
            d = gpuDevice();
        catch err
            error( 'mandelbrotViewer:NoGPU', 'mandelbrotViewer requires a GPU and none appear to be availble. Type "gpuDevice" for more information.' );
        end
        if ~d.DeviceSupported
            error( 'mandelbrotViewer:GPUNotSupported', 'The selected GPU is not supported. Type "gpuDevice" for more information.' );
        end
    end % matlabVersionCheck

end % mandelbrotViewer


% [INPUT]
% y = A vector of floats (-Inf,Inf) of length t representing the residuals.
% a = A float (0,1) representing the significance level (optional, default=0.05).
%
% [OUTPUT]
% h0 = A boolean representing the null hypothesis (residuals are normally distributed) rejection outcome.
% pval = A float [0,1] representing the test p-value.
% stat = A float (-Inf,Inf) representing the normalized test statistic.
%
% [NOTES]
% When residuals are leptokurtic, the Shapiro-Francia test is performed.
% When residuals are platykurtic, the Shapiro-Wilk test is performed.

function [h0,pval,stat] = shapiro_test(varargin)

    persistent ip;

    if (isempty(ip))
        ip = inputParser();
        ip.addRequired('y',@(x)validateattributes(x,{'double'},{'real' 'finite' 'vector' 'nonempty'}));
        ip.addOptional('a',0.05,@(x)validateattributes(x,{'double'},{'real' 'finite' '>' 0 '<' 1 'scalar'}));
    end

    ip.parse(varargin{:});

    ipr = ip.Results;
    y = validate_input(ipr.y);
    a = ipr.a;

    nargoutchk(2,3);

    [h0,pval,stat] = shapiro_test_internal(y,a);

end

function [h0,pval,stat] = shapiro_test_internal(x,alpha)

    x = sort(x);

    if (kurtosis(x) > 3)
        [pval,stat] = shapiro_francia(x);
    else
        [pval,stat] = shapiro_wilks(x);
    end

    h0  = (alpha >= pval);

end

function [pval,stat] = shapiro_francia(x)

    n = numel(x);

    x0 = x - mean(x);
    m = norminv(((1:n).' - 0.375) ./ (n + 0.25));
    w = (1 / sqrt(m.' * m)) .* m;
    k = (w.' * x)^2 / (x0.' * x0);

    nu = log(n);
    u1 = log(nu) - nu;
    u2 = log(nu) + (2 / nu);

    mu = -1.27250 + (1.05210 * u1);
    sigma = 1.03080 - (0.26758 * u2);

    stat = (log(1 - k) - mu) / sigma;
    pval = 1 - normcdf(stat,0,1);

end

function [pval,stat] = shapiro_wilks(x)

    n = numel(x);

    x0 = x - mean(x);
    m = norminv(((1:n).' - 0.375) ./ (n + 0.25));
    c = (1 / sqrt(m.' * m)) .* m;
    u = 1 / sqrt(n);

    pc_1 = [-2.7060560  4.434685 -2.071190 -0.147981 0.221157 c(n)];
    pc_2 = [-3.5826330  5.682633 -1.752461 -0.293762 0.042981 c(n - 1)];
    pc_3 = [-0.0006714  0.025054 -0.399780  0.544000];
    pc_4 = [-0.0020322  0.062767 -0.778570  1.382200];
    pc_5 = [ 0.0038915 -0.083751 -0.310820 -1.586100];
    pc_6 = [ 0.0030302 -0.082676 -0.480300];
    pc_7 = [ 0.459 -2.273];

    w = zeros(n,1);
    w(n) = polyval(pc_1,u);
    w(1) = -w(n);

    if (n >= 6)
        off = 3:n-3+1;

        w(n-1) = polyval(pc_2,u);
        w(2) = -w(n-1);

        phi = ((m.' * m) - (2 * m(n)^2) - (2 * m(n-1)^2)) /  (1 - (2 * w(n)^2) - (2 * w(n-1)^2));
    else
        off = 2:n-2+1;

        if (n == 3)
            w(1) = 1 / sqrt(2);
            w(n) = -w(1);

            phi = 1;
        else
            phi = ((m' * m) - (2 * m(n)^2)) / (1 - (2 * w(n)^2));
        end
    end

    w(off) = m(off) ./ sqrt(phi);

    k = (w.' * x)^2 / (x0.' * x0);

    if (n == 3)
        mu = 0;
        sigma = 1;
        kn = 0;
    elseif ((n >= 4) && (n <= 11))
        mu = polyval(pc_3,n);
        sigma = exp(polyval(pc_4,n));
        gamma = polyval(pc_7,n);
        kn = -log(gamma - log(1 - k));
    else
        ln = log(n);
        mu = polyval(pc_5,ln);
        sigma = exp(polyval(pc_6,ln));
        kn = log(1 - k);
    end

    stat = (kn - mu) / sigma;

    if (n == 3)
        pval = (6 / pi()) * (asin(sqrt(k)) - asin(sqrt(0.75)));
    else
        pval = 1 - normcdf(stat,0,1);
    end

end

function y = validate_input(y)

    y = y(:);
    t = numel(y);

    if (t < 5)
        error('The value of ''y'' is invalid. Expected input to be a vector containing at least 5 elements.');
    end

end


function a = gk324_inverse ( n, x )

%*****************************************************************************80
%
%% GK324_INVERSE returns the inverse of the GK324 matrix.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%   18 October 2007
%
%  Author:
%
%    John Burkardt
%
%  Reference:
%
%    Robert Gregory, David Karney,
%    A Collection of Matrices for Testing Computational Algorithms,
%    Wiley, 1969, page 51,
%    LC: QA263.G68.
%
%  Parameters:
%
%    Input, integer N, the order of the matrix.
%
%    Input, real X(N-1), the first N-1 entries of the last row.
%
%    Output, real A(N,N), the matrix.
%
  a = zeros ( n, n );

  for i = 1 : n
    for j = 1 : n

      if ( i < n )

        if ( j < i )
          a(i,j) = 0.0;
        elseif ( j == i )
          a(i,j) = 1.0 / ( 1.0 - x(i) );
        elseif ( j == i + 1 )
          a(i,j) = - 1.0 / ( 1.0 - x(i) );
        elseif ( i + 1 < j )
          a(i,j) = 0.0;
        end

      elseif ( i == n )

        if ( j == 1 )

          a(i,j) = - x(1) / ( 1.0 - x(1) );

        elseif ( j < n )

          a(i,j) = ( x(j-1) - x(j) ) / ( 1.0 - x(j) ) / ( 1.0 - x(j-1) );

        elseif ( j == n )

          a(i,j) = 1.0 / ( 1.0 - x(n-1) );

        end

      end

    end
  end

  return
end