function [reducedModel] = reduceModel(varargin)
% This function can be used to reduce linear models and follows the method 
% presented in "Sunnaker, Schmidt, Jirstrand, Cedersund (2010), Zooming of 
% states and parameters using a lumping approach including back-translation".
% 
% USAGE:
% ======
% [reducedModel] = SBAOlinearqsslumping(model) 
% [reducedModel] = SBAOlinearqsslumping(model,epsilon)
% [reducedModel] = SBAOlinearqsslumping(model,epsilon,coslow)
%
% model: SBmodel
% epsilon: Cut-off value for which reactions should be considered as
% fast in the epsilon decomposition                
% coslow: If A_ij and A_ji are both smaller than the coslow (cut-off slow) 
% value for all i,j, the corresponding state is slow  
%
% IMPORTANT INFORMATION:
% This program gives accurate results for linear systems only. 
% Functions are not allowed in the SBmodel to be reduced.
%
% DEFAULT VALUES:
% ===============
% epsilon = 100;
% coslow = 1E-6;
% 
% Output Arguments:
% =================
% reducedModel: SBmodel that constitutes a reduced version of the original
%               model
%
% Information:
% ============
% Copyright (C) 2009, Fraunhofer-Chalmers Centre
% Contact: mats.jirstrand@fcc.chalmers.se
%
% FOR ACADEMIC USE 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
% USA.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Start
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if nargin == 1,
     model = varargin{1}; 
     epsilon = 100;
     coslow = 1E-6;
elseif nargin == 2,
     model = varargin{1};
     epsilon = varargin{2};
     coslow = 1E-6;
elseif nargin == 3,
     model = varargin{1};
     epsilon = varargin{2};
     coslow = varargin{3};
else
    error('Incorrect number input of arguments.');
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Transform the model into the correct form and get model information
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%make all model reactions irreverisible
[model,messageIrreversible] = makeIrrev(model);
%get state information
[s.stateNames,s.stateFormulas,s.initialStateValues] = SBstates(model);
%get reaction information
[s.reactionNames,s.reactionFormulas] = SBreactions(model);
%get parameter information
[s.parameterNames,s.parameterValues] = SBparameters(model);
%get variables information
[s.variableNames,s.variableFormulas] = SBvariables(model);
%get the stoichiometric matrix
s.stoMat = SBstoichiometry(model);
%get a state names string
s.statesString = generateStatesString(s.stateNames);

%get the number of states
nrStates = length(s.stateNames);
%set the starting vector
startVector = ones(nrStates,1);
%get the jacobian A for the system (the state values do not matter since linear model)
A = SBjacobian(model,startVector);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Follow the five step method presented in the paper 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%1: use epsilon-decomposition to identify non-connected digraphs of fast
%reactions (DGFRs)
outputEpsilon = epsilonDecomposition(A,epsilon);

%2: identify the strong components in each of the DGFRs
outputStrong = strongComponents(outputEpsilon);

%3: classify the strong components as sinks clusters or non-sink SC
%check which reactions are fast
slowReactions = classifyReactions(s.reactionFormulas,s.stateNames,s.parameterNames,s.parameterValues,epsilon);
%set the columns in the stoichiometric matrix corresponding to slow
%reactions to 0
stoMatFast = s.stoMat;
stoMatFast(:,slowReactions) = 0;
%sink/non-sink SC classification
outputClassifySC = classifySC(stoMatFast,outputEpsilon,outputStrong);

%4: check if the non-sink SCs can be lumped with any of the sink clusters
%In this final step, the total lumping scheme is revealed...
outputLumpingScheme = checkNsSClumping(outputEpsilon,outputClassifySC,outputStrong,A,outputEpsilon.Afast);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%generate a reduced version of the model that follows the obtained lumping
%scheme
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%add the obtained subcluster structure information to the s-structure
s.outputEpsilon = outputEpsilon;
s.outputStrong = outputStrong;
s.outputClassifySC = outputClassifySC;
s.outputLumpingScheme = outputLumpingScheme;
%get the fractions for the different states
s = getFractionsNoInputOutput(s);
%get the reduced model, which is returned
reducedModel = reduceFastClusters(s,model);

%5: Identify slow states in the reduced model (coslow)
%get the number of states in the reduced model
nrRedStates = length(SBstates(reducedModel));
%set the starting vector
startVectorS = ones(length(SBstates(reducedModel)),1);
%get the jacobian A for the system (the state values do not matter since linear model)
As = SBjacobian(reducedModel,startVectorS);
%the slow states take the form of parameters, with values that are equal 
%to the initial condition of the state
reducedModel = identifySlowStates(reducedModel,As,nrRedStates,coslow);

return


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Other functions used by the program 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [outputLumpingScheme] = checkNsSClumping(outputEpsilon,outputClassifySC,outputStrong,A,Afast)
%check which states should be added to the sinks in each cluster
%This should be based on:
%1: if the states are in the same strong component (SC), note that two sink
%clusters can not be in the same SC by definition
%2: if the non-sink states can be reached from sink states by slow reactions
nrClusters=length(outputEpsilon.clusterIndex);
nrStates=length(outputEpsilon.clusters);
%check which states should be included in each lump, one cluster at a time
outputLumpingScheme=zeros(1,nrStates);
lumpsFound=0;
for i=1:nrClusters
    %get the indices of the states in the cluster
    clustInd=outputEpsilon.clusterIndex(i).indices;
    %get the total connectivity matrices for the cluster (with and without slow reactions)
    [McSub,McfSub]=getConnectivityMat(A(clustInd,clustInd),Afast(clustInd,clustInd));
    %get the indices for the sink clusters in the cluster (and non-sink state indices)    
    sinkNrs=find(outputClassifySC.cluster(i).sinkVect);
    nonSinkNrs=find(outputClassifySC.cluster(i).sinkVect==0);
    %get the vector that indicates strong components in the cluster 
    SCvect=outputStrong.cluster(i).SC;
    %get the states in the cluster
    statesInCluster=outputEpsilon.clusterIndex(i).indices;
    %get the number of sinks in the cluster
    nrSinks=length(sinkNrs);
    %check which states should be lumped with each of the sinks
    for j=1:nrSinks
        %another lump has been found
        lumpsFound=lumpsFound+1;
        %check which states are in the same strong component as the current sink
        membersOfStrong=find(SCvect==SCvect(sinkNrs(j)));
        %assign states in the same strong component, to be in the same lumping scheme 
        outputLumpingScheme(statesInCluster(membersOfStrong))=lumpsFound;
        %check which non-sink states can reach the sink by fast reactions
        fast2SinkStates=find(McfSub(sinkNrs(j),:));
        nrFastToSink=length(fast2SinkStates);
        %remove the states of the same strong component
        removeVect=zeros(1,nrFastToSink);
        %check which states of nrFastToSink are also in the strong
        %component, membersOfStrong 
        for k=1:nrFastToSink
            if(any(membersOfStrong==fast2SinkStates(k)))
                removeVect(k)=1;
            end
        end
        %remove the states
        fast2SinkStates(find(removeVect))=[];
        %for each of the states in fast2SinkStates, check if the state can
        %be reached from at most one sink by slow reactions
        for k=1:length(fast2SinkStates)
            %check which sinks/sink clusters that this state can NOT reach by fast reactions
            notReachStates=find(McfSub(sinkNrs,fast2SinkStates(k))==0);
            %save a temporary version on McSub
            McSubTemp=McSub;
            %set the columns of sinks that the state can not reach by fast reactions to zero
            McSubTemp(:,sinkNrs(notReachStates))=0;
            %for each of the states in fast2SinkStates, check if the state can
            %be reached from at most one sink by slow reactions
            nrSinksToBeReached=sum(McSubTemp(fast2SinkStates(k),sinkNrs),2);
            %a state is only added to a sink if there is a maximum number of paths
            %from the sink to the state, no more paths from one sink
            if(max(McSubTemp(fast2SinkStates(k),sinkNrs))==McSubTemp(fast2SinkStates(k),sinkNrs(j))&&nrSinksToBeReached<2)
                %assign these states, to be in the same lumping scheme
                outputLumpingScheme(statesInCluster(fast2SinkStates(k)))=lumpsFound;
            end
        end
    end
end
%states that have not been assigned to a sink/sink cluster constitute
%single state lumps
%each of these states are assigned an individual number
remainingStates=find(outputLumpingScheme==0);
for i=1:length(remainingStates) 
    %another single state lump has been found
    lumpsFound=lumpsFound+1;
    %assign the state lump a number
    outputLumpingScheme(remainingStates(i))=lumpsFound;
end  
return

function slowReactions = classifyReactions(reactionFormulas,stateNames,parameterNames,parameterValues,epsilon)
%check the number of states
nrStates=length(stateNames);
%use the symbolic toolbox for Matlab to substitute the state names for 1
addToStateNames='test';
modReactionFormulas=strcat(reactionFormulas,addToStateNames); %test can be replace by arbitrary name
modStateNames=strcat(stateNames,addToStateNames);
reactionParameters=subs(modReactionFormulas,modStateNames,num2cell(ones(1,nrStates)));
%substitute the reactions parameters for the parameter values
numReac=subs(reactionParameters,parameterNames,parameterValues);
%check which reactions are fast
slowReactions=find(numReac<epsilon);
return

function [classStr] = classifySC(stoMatFast,outputEpsilon,outputStrong)
%get the number of clusters
nrClusters=length(outputStrong.cluster);
%classify the strong components within each cluster as SCs or non-sink SCs
for i=1:nrClusters
    %get the strong components vector
    SCsVect=outputStrong.cluster(i).SC;
    %check the number of strong components in the cluster
    nrSCs=max(SCsVect);
    %get the states in the cluster
    statesInCluster=outputEpsilon.clusterIndex(i).indices;
    %initiate a vector that indicates if the strong components are sinks (1) or 
    %non-sink SCs (0) 
    isSinkVect=zeros(1,nrSCs);
    %check if each strong component is a sink or non-sink SC 
    for j=1:nrSCs
        %check which states are in the jth strong component
        statesInSC=find(outputStrong.cluster(i).SC==j);
        %add all rows to check if there are any fast reactions out of the
        %SC
        notSink=any(sum((stoMatFast(statesInCluster(statesInSC),:)),1)<0);
        %notSink=any(stoMatFast(statesInCluster(statesInSC),:)<0);
        %if all elements of sumOfSCols is >=0, the SC is a sink
        %if any elements of sumOfSCols is <0, the SC is a non-sink SC
        if(~notSink)
            isSinkVect(j)=1;
        end
    end
    %save the sink/non-sink indicative vector in classStr
    classStr.cluster(i).sinkVect=isSinkVect;
end
return

function [output] = conversion(input)
if iscell(input)
    for i = 1:length(input)
        if isnumeric(input{i})
            output(i) = input{i};
        else
            output(i) = sym(input{i});
        end
    end
elseif strcmp(class(input), 'sym')
    output = {};
    for i = 1:length(input)
        output{i} = char(input(i));
    end
else
    output = {};
    for i = 1:length(input)
        output{i} = input(i);
    end
end
return

function [output] = epsilonDecomposition(varargin)
if nargin == 2,
     A=varargin{1};
     epsilon= varargin{2};
else
    error('Incorrect number input of arguments.');
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Create the connectivity matrix from A   %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%remove all element smaller than epsilon
smallReac=find(abs(A)<epsilon);
A(smallReac)=0;
%for output
Afast=A;
%no elements should be negative
%A=abs(A);
%make sure that the matrix is symmetric
upperMat=triu(A);
lowerMat=tril(A);
A=A+lowerMat'+upperMat';
%all positive elements should be one
A=sign(A);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%find all clusters within the matrix   %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%create a vector for saving the result of different clusters
nrStates=size(A,1);
clusters=zeros(1, nrStates);
%initially no clusters are present
clustersFound=0;
%make a vector for which states are left to check
leftToCheck=1;
%create a vector for later use
newStatesVect=zeros(1,nrStates);
%vector for which have been main states
mainStatesVect=zeros(1,nrStates);
while(~isempty(leftToCheck))
    %set which state is the first
    firstState=leftToCheck(1);
    %one more cluster found, add one
    clustersFound=clustersFound+1;
    %set the starting state to the cluster number
    clusters(firstState)=clustersFound;
    %put the newToCheck vector to 0
    newToCheck=zeros(1,nrStates);
    newToCheck(firstState)=1;
    while(sum(newToCheck)>0)
        %check which states are already classified in clusters
        inMainVect=find(mainStatesVect);
        %latest to check
        statesToCheck=find(newToCheck);
        %save this state in a vector
        mainStatesVect(statesToCheck(1))=1;
        %set all elements in newStatesVect to 0
        newStatesVect(1:end)=0;
        %check which connection there are from the first state to check
        newStatesVect(find(A(statesToCheck(1),:)))=1;
        %add the old states to check to the new states to check
        newToCheck=newToCheck+newStatesVect;
        %set all states that are already in clusters to 0
        newToCheck(1,inMainVect)=0;
        %set all the new cluster elements to the clusterfound value for the
        %current cluster
        if(sum(newToCheck)~=0)
            clusters(find(newToCheck))=clustersFound;
        end
    end
    %update leftToCheck
    leftToCheck=find(clusters==0);
end
%initiate a vector to indicate if there is only one component in a cluster
multClusters=zeros(1,clustersFound);
output=[];
%save the indices for each of the clustermembers in a structure output
for i=1:clustersFound
    nrInCluster=find(clusters==i);
    output.clusterIndex(i).indices=nrInCluster;
    if(length(nrInCluster)>1)
        multClusters(nrInCluster)=1;
    end
end
%save the cluster vector in the structure output
output.clusters=clusters;
output.multClusters=multClusters;
output.Afast=Afast;
%output.smallReac=smallReac;
return

function [statesString] = generateStatesString(stateNames)
statesString = '{';
for k=1:length(stateNames)
    if k==1
        statesString = sprintf('%s%s', statesString,  stateNames{k});
    else
        statesString = sprintf('%s,%s', statesString, stateNames{k});
    end
end
statesString = strcat(statesString,'}');
return

function [Mc,Mcf] = getConnectivityMat(A,Afast)
%turn the A matrix into a symmetric matrix with 0s and 1s only
M=A;
M=abs(sign(M));
%turn the Afast matrix into a symmetric matrix with 0s and 1s only
Mf=Afast;
Mf=abs(sign(Mf));
%Get the total connectivity matrices
[nx,ny]=size(A);
Mc=M;
Mcf=Mf;
for i=2:nx
    %add the next term in the sum
    Mc=Mc+Mc*M;
    %add the next term in the sum
    Mcf=Mcf+Mcf*Mf;
end
Mc=sign(Mc);
Mcf=sign(Mcf);
return

function [s] = getFractionsNoInputOutput(s)
%this is a function for getting the input and output that does not take the
%input and output to a cluster into consideration
%delta is a perturbation to make the K matrix nonsingular
syms deltaPerturbation;
%check how many states there are
nrStates=length(s.stateNames);
%create a vector for saving the fraction results
fractionVect=cell(1,nrStates);
%get the K matrix for the system
K=getKmatrix(s);
%loop to check all clusters in the system
nrClusters=max(s.outputLumpingScheme);
for i=1:nrClusters
    %check how many members there are in each cluster
    positions=find(s.outputLumpingScheme==i);
    nrInCluster=length(positions);
    %first all the states that are not involved in a cluster are given
    %fraction one
    if(nrInCluster==1)
        fractionVect{positions}=1;
    else
        %create an input vector
        input=zeros(nrInCluster,1);
        input(1)=1;
        %take out the rows and columns corresponding to the states present in
        %the cluster
        tempK=K(positions,:);
        tempK=tempK(:,positions);
        %get the outputs for the cluster
        [outputs, noOutputs, outputPositions, outputReactions]=getOutputReactions(s,i);
        %create a zero vector that the outputs shoul be subs for
        outputsZero=zeros(1,length(outputReactions));
        %disturb the K matrix to make it nonsingular
        if(noOutputs)
            tempK(1,1)=tempK(1,1)-deltaPerturbation;
        end
        %get the first column of the inverse of the K matrix
        S=-(tempK\input);
        %alternatively take out the denominator and set outputs to 0
        %[den, num]=numden(S);
        fractionVect(positions)=simplify(S/sum(S));
        %set the outputs to 0
        if(noOutputs)
            for j=positions
                fractionVect(j)=conversion(simplify(subs(conversion(fractionVect(j)),deltaPerturbation,0)));
            end
        else
            %find which outputs are nonzero
            for j=positions
                %subs outputs for zero
                fractionVect(j)=conversion(subs(simplify(conversion(fractionVect(j))),outputReactions,outputsZero,0));
            end
        end
    end
end
%return the fraction vector
s.fractionVect=fractionVect;
return

function [K] = getKmatrix(s)
%function for getting the general K matrix for a specific system
%get the number of states
nrStates=length(s.stateNames);
%create a cellular matrix to represent the K-matrix
K=sym(zeros(nrStates));
%check which clusters have more than one component
multClustInd=find(s.outputEpsilon.multClusters);
%get the state formulas
stateDiffEquations=K(:,1);
%substitute the reactions in those formulas
stateDiffEquations(multClustInd)=subs(conversion(s.stateFormulas(multClustInd)),s.reactionNames,s.reactionFormulas);
%get the final states into a cellular form
finalStatesSym=conversion(s.stateNames);
%create a vector in which each element is set to 1 if that state is
%currently being checked
markedState=zeros(1,nrStates);
%only the states that are in clusters with size larger than one need to be
%checked
for i=multClustInd
    %find which are the states on the right hand side of the diff equation
    symInEq=regexp(findsym(stateDiffEquations(i)),'\w+','match');
    members=find(ismember(s.stateNames, symInEq)');
    for j=members     
        markedState(j)=1;
        K(i,j)=sym(subs(stateDiffEquations(i),finalStatesSym(members),markedState(members)));
        markedState(j)=0;
    end
end
return

function [outputs, noOutputs, outputPositions, outputReactions] = getOutputReactions(s,i)
%finds the input and output reactions for the specific cluster by
%checking the stoichiometric matrix. Sum all columns for the involved
%states and if 1 left=input, if -1 left=output 
%get the number of states
nrStates=length(s.stateNames);
%create a vector outputpositions
outputPositions=zeros(1,nrStates);
%get which states are in the specific cluster
statesInCluster=find(s.outputLumpingScheme==i);
nrStatesInCluster=length(statesInCluster);
%get the part of the stoichiomatric matrix that is represented by the
%states
stoMatForCluster=s.stoMat(statesInCluster,:);
%create an input and output vector to be returned 
outputs=cell(1,nrStatesInCluster);
outputReactions=[];
%sum all columns in the cluster stoichiometric matrix, all reactions that
%are going in or out of the lumped variable will have either 1 or -1 (this requires 
%that a specific reaction can only be at one place in the cluster). 
sumOfStoMatCluster=sum(stoMatForCluster);
inOutGoingReactions=find(sumOfStoMatCluster);
%used to count the number of output reactions
nrOutputReactions=0;
%check if each reaction is ingoing or outgoing and save the results in the
%corresponding vector for the correct state
for j=inOutGoingReactions
    %if we have an output 
    if(sumOfStoMatCluster(j)==-1)
        %check which state the output is from
        state=find(stoMatForCluster(:,j));
        %check the coefficient for state (taking away the state since it should be in another vector)
        coefficient=coeffs(sym(s.reactionFormulas{j}),s.stateNames(state)); %s.statesString has been replaced by s.stateNames(state)
        %if this output state is previously empty
        if(isempty(outputs{state}))
            outputs{state}=-coefficient;
        %if there is already an element at this position in the vector
        else
            outputs{state}=outputs{state}-coefficient;
        end
        nrOutputReactions=nrOutputReactions+1;
        outputReactions{nrOutputReactions}=coefficient;
    elseif((sumOfStoMatCluster(j)~=1)&&(sumOfStoMatCluster(j)~=1))
        disp(sprintf('Warning! A reaction is only allowed to occur at one position in the network.'));     
    end
end
emptyOutputs=0;
for i=1:nrStatesInCluster
    %check the output vector for empty positions
    if(isempty(outputs{i}))
        outputs{i}=0;
        emptyOutputs=emptyOutputs+1;
    else
        outputPositions(i)=1;
    end
end
if(emptyOutputs==nrStatesInCluster)
    noOutputs=1;
else
    noOutputs=0;
end
%get the output positions
outputPositions=find(outputPositions);
%convert the output reactions
outputReactions=conversion(outputReactions);
return

function [varargout] = makeIrrev(model)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% CHECK IF SBmodel
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if ~strcmp('SBmodel',class(model)),
    error('Function only defined for SBmodels.');
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SAVE ORIGINAL MODEL FOR LATER USE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
originalmodel = model;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% CHECK THAT ALL ODE EXPRESSIONS ARE DEFINED VIA REACTION TERMS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[Ntest,componentsTest] = SBstoichiometry(model,1);  % set rawFlag to 1 
componentsModel = SBstates(model);
if length(componentsTest) ~= length(componentsModel),
    error('Not all ODEs seem to be constructed by reaction terms. Therefor the full stoichiometric information is not possible to determine.');
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% GET NAMES AND DATA OF REVERSIBLE REACTIONS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
message='';
[names,formulas,reversibleFlag] = SBreactions(model);
% check if reversible reactions are present
if sum(reversibleFlag) == 0,
    message=[message 'The model does not contain any reversible reactions.'];
    if nargout < 2,
        varargout{1} = model;
        disp(sprintf(message));
    elseif nargout == 2,
        varargout{1} = model;
        varargout{2} = message;
    elseif(nargout>2)
        error('Wrong number of output arguments.');
    end
    return
end
% get indices of reversible reactions
reversibleIndices = find(reversibleFlag ~= 0);
reactionsStore = [];
for k = 1:length(reversibleIndices),
    reactionsStore(k).name = names{reversibleIndices(k)};
    reactionsStore(k).formula = formulas{reversibleIndices(k)};
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% CHECK SYNTAX OF REACTION RATES (R = Rf-Rr)
% AND SPLIT THEM UP INTO Rf and Rr
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
errorReversibleRateDefinitions = '';
for k = 1:length(reactionsStore),
    irreversibleRates = explodePCSB(reactionsStore(k).formula,'-');
    if length(irreversibleRates) ~= 2,
        % Need to have two parts that are separated by a '-' sign. (Forward
        % first, then reverse reaction kinetics).
        errorReversibleRateDefinitions = sprintf('%sError in rate definition of reaction rate ''%s''. It does not seem to be reversible.\n', errorReversibleRateDefinitions, reactionsStore(k).name);
    else
        % Seems fine ... save the different parts
        reactionsStore(k).forwardRate = irreversibleRates{1};
        reactionsStore(k).reverseRate = irreversibleRates{2};        
    end
end
if ~isempty(errorReversibleRateDefinitions),
    error(errorReversibleRateDefinitions);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% DELETE REVERSIBLE RATES AND ADD IRREVERSIBLE ONES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for k = 1:length(reactionsStore),
    model = deletereactionratesSB(model, reactionsStore(k).name);
    forwardReactionName = strcat(reactionsStore(k).name,'_forward');
    reverseReactionName = strcat(reactionsStore(k).name,'_reverse');
    reversibleFlag = 0;
    notes = '';
    model = addreactionrateSB(model, forwardReactionName, reactionsStore(k).forwardRate, notes, reversibleFlag);
    model = addreactionrateSB(model, reverseReactionName, reactionsStore(k).reverseRate, notes, reversibleFlag);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% GET AND SAVE COMPARTMENT INFORMATION (original model)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[allComponentNames,allODEs] = SBstates(originalmodel);
compartmentInformation = {}; % Strings ordered in the same way as states in the model
% check if a scaling by the compartment volume is done.
% in this case the expected syntax is 
% ODE = ("reactionterms")/compartmentvolume
% the compartment information is saved and used later to restore the ODEs
for k = 1:length(allODEs),     
    ODE = strtrim(allODEs{k});
    numberOpenParentheses = length(find(ODE == '('));
    numberClosedParentheses = length(find(ODE == ')'));
    % all eventual errorneous cases are caught in the beginning of this
    % function by calling SBstoichiometry
    if ODE(1) == '(',
        % if the first character is an open parenthesis then assume that 
        % this is due to a adjustement to compartment sizes. here we only
        % need to keep the compartment information.
        % cut out the content of the parentheses
        closeParenthesis = find(ODE == ')');
        compartmentInformation{k} = ODE(closeParenthesis+1:end);
    else
        compartmentInformation{k} = '';
    end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% UPDATE RIGHT HAND SIDE OF THE ODES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% get model structure
modelstruct = SBstruct(model);
% determine stoichiometric information of original model to 
% see where and with which stoichiometric coefficient the reversible
% reactions come into play. The stoichiometry function takes care of
% compartment factors by itself 
[N, componentNames] = SBstoichiometry(originalmodel,1); % set rawFlag to 1
% get the submatrix of N corresponding only to the irreversible reactions
% reversibleIndices is same as determined above and irreversibleIndices can
% be determined by
irreversibleIndices = setdiff(1:length(SBreactions(originalmodel)),reversibleIndices);
Nirrev = N(:,irreversibleIndices);
% get the submatrix of N corresponding only to the reversible reactions
% reversibleIndices is same as determined above
Nrev = N(:,reversibleIndices);
% clear all ODEs
for k = 1:length(modelstruct.states),
    modelstruct.states(k).ODE = '';
end
% cycle through the columns of Nirrev (irreversible reaction rates) and
% construct the corresponding ODEs
reactionNamesOriginal = SBreactions(originalmodel);
irrevReactionNames = reactionNamesOriginal(irreversibleIndices);
for k1 = 1:size(Nirrev,2),
    % get indices of state number where to add the terms
    addTermsIndices = find(Nirrev(:,k1) ~= 0);
    for k2 = 1:length(addTermsIndices),
        % construct expression
        stoichCoeff = Nirrev(addTermsIndices(k2),k1);
        if stoichCoeff > 0,
            if stoichCoeff ~= 1,
                addTerm = sprintf('+%g*%s',abs(stoichCoeff),irrevReactionNames{k1});
            else
                addTerm = sprintf('+%s',irrevReactionNames{k1});
            end
        elseif stoichCoeff < 0,
            if stoichCoeff ~= -1,
                addTerm = sprintf('-%g*%s',abs(stoichCoeff),irrevReactionNames{k1});
            else
                addTerm = sprintf('-%s',irrevReactionNames{k1});
            end
        else
            error('This can not happen :)');
        end
        % add the new term to the corresponding ODE
        modelstruct.states(addTermsIndices(k2)).ODE = strcat(modelstruct.states(addTermsIndices(k2)).ODE, addTerm);
    end
end
% cycle trough the columns of Nrev (reversible reaction rates) and add the
% corresponding terms to the corresponding ODE expressions
for k1 = 1:size(Nrev,2),
    % get indices of state number where to add the terms
    addTermsIndices = find(Nrev(:,k1) ~= 0);
    for k2 = 1:length(addTermsIndices),
        % construct expression
        stoichCoeff = Nrev(addTermsIndices(k2),k1);
        if stoichCoeff > 0,
            if stoichCoeff ~= 1,
                addTerm = sprintf('+%g*%s_forward-%g*%s_reverse',abs(stoichCoeff),reactionsStore(k1).name,abs(stoichCoeff),reactionsStore(k1).name);
            else
                addTerm = sprintf('+%s_forward-%s_reverse',reactionsStore(k1).name,reactionsStore(k1).name);
            end                
        elseif stoichCoeff < 0,
            if stoichCoeff ~= -1,
                addTerm = sprintf('-%g*%s_forward+%g*%s_reverse',abs(stoichCoeff),reactionsStore(k1).name,abs(stoichCoeff),reactionsStore(k1).name);
            else
                addTerm = sprintf('-%s_forward+%s_reverse',reactionsStore(k1).name,reactionsStore(k1).name);
            end
        else
            error('This can not happen :)');
        end
        % add the new term to the corresponding ODE
        modelstruct.states(addTermsIndices(k2)).ODE = strcat(modelstruct.states(addTermsIndices(k2)).ODE, addTerm);
    end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ADD EVENTUAL COMPARTMENT INFORMATION TO ODEs
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for k = 1:length(compartmentInformation),
    if ~isempty(compartmentInformation{k}),
        modelstruct.states(k).ODE = strcat('(',modelstruct.states(k).ODE,')',compartmentInformation{k});
    end
end
% convert structure to model again
model = SBmodel(modelstruct);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% PROCESS VARIABLE OUTPUT ARGUMENTS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if nargout < 2,
    varargout{1} = model;
elseif nargout == 2,
    varargout{1} = model;
    varargout{2} = message;
elseif(nargout>2)
    error('Wrong number of output arguments.');
end
return

function [newModel] = reduceFastClusters(s,model)
%first check so that there are fast clusters in the model, otherwise the
%previous model should just be returned
if(max(s.outputLumpingScheme)==length(s.outputLumpingScheme))
    %the same model is returned
    newModel=model;
    return
else
    %get the states string for the full model
    %statesString=generateStatesString(s.stateNames);
    nrStates=length(s.stateNames);
    %allStates=regexp(findsym(conversion(s.stateNames)),'\w+','match');
    % get the structure of the SBmodel
    modelStructure = SBstruct(model); % will contain the reduced information
    %create a translation vector to be used for translating the states into the
    %new states
    translationVect=cell(1,nrStates);
    % create new "states" entry and copy only the states that are left after
    % the lumping to it
    states = [];
    nrLumpedVariables=0;
    %get the total number of clusters
    nrClusters=max(s.outputLumpingScheme);
    for i = 1:nrClusters %max(s.outputEpsilon.clusters)
        %we are checking the ith cluster
        statesCount=i;
        %check which of the states are contained in the cluster
        statesInCluster=find(s.outputLumpingScheme==i);%s.outputEpsilon.clusterIndex(i).indices;       
        %if only a single state
        if(length(statesInCluster)==1)
            %save the elements in the translation vector
            translationVect(statesInCluster)=conversion(s.fractionVect(statesInCluster))*conversion(s.stateNames(statesInCluster));
            % copy this state information to new model
            states(statesCount).name = modelStructure.states(statesInCluster).name;
            states(statesCount).initialCondition = modelStructure.states(statesInCluster).initialCondition;
            states(statesCount).ODE = modelStructure.states(statesInCluster).ODE;
            states(statesCount).type = modelStructure.states(statesInCluster).type;
            states(statesCount).compartment = modelStructure.states(statesInCluster).compartment;
            states(statesCount).unittype = modelStructure.states(statesInCluster).unittype;
            states(statesCount).notes = modelStructure.states(statesInCluster).notes;
            %if we have a lumped variable
        else
            %one more lumped varible will now be treated
            nrLumpedVariables=nrLumpedVariables+1;
            %generate a new name for the lumped variable
            newStateName=strcat('LV',num2str(nrLumpedVariables));
            states(statesCount).name = newStateName;
            %save the elements in the translation vector
            translationVect(statesInCluster)=conversion(s.fractionVect(statesInCluster))*sym(newStateName);%simplify(conversion(s.fractionVect(statesInCluster))*sym(newStateName));
            %get the intital condition for the lumped variable
            initialValue=0;
            for j=1:length(statesInCluster)
                initialValue=initialValue+modelStructure.states(statesInCluster(j)).initialCondition;
            end
            states(statesCount).initialCondition = initialValue;
            %get the new ODE for the lumped variable
            diffEquation=0;
            for j=1:length(statesInCluster)
                diffEquation=diffEquation+sym(modelStructure.states(statesInCluster(j)).ODE);
            end
            states(statesCount).ODE = char(diffEquation);
            %just left as it was for now
            states(statesCount).type = '';
            %just left as it was for now
            states(statesCount).compartment = '';
            %just left as it was for now
            states(statesCount).unittype = '';
            %just left as it was for now
            stringPrevStates=createStringParanthesis(s.stateNames(statesInCluster));
            states(statesCount).notes = ['Lumped state' ' ' stringPrevStates];
        end
    end
    %save the results in the structure
    modelStructure.states=states;
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % create new "reactions" entry and copy only the states left after the
    % lumping to it
    reactions=[];
    %check if each reaction is still in the model
    nrReactions=length(s.reactionNames);
    %make a list of all reactions
    allReactions=s.reactionNames;
    %create a vector of zeros for each of the reactions
    reactionsInModel=zeros(nrReactions,1);
    for i=1:statesCount
        %make a cellular array of the symbolic expressions
        reactionsInODE=regexp(findsym(sym(states(i).ODE)),'\w+','match');
        %add the new reactions to the total vector
        reactionsInModel=reactionsInModel+ismember(s.reactionNames,reactionsInODE);
    end
    reactionsInModel=find(reactionsInModel)';   
    %for counting the reactions left in the model
    reactionsCount=0;
    %now save the reactions that are still in the model
    for i=reactionsInModel
        %one more reaction that exists in the lumped model has been found
        reactionsCount=reactionsCount+1;
        %call this reaction the same name as before
        reactions(reactionsCount).name = modelStructure.reactions(i).name;
        %substitute lambda*(lumped state), if the state is not kept
        reactions(reactionsCount).formula=modelStructure.reactions(i).formula;
        %indicate that the model is lumped in the notes
        reactions(reactionsCount).notes='Reaction in the reduced model';
        %since no reactions are reversible, we can set it to 0
        reactions(reactionsCount).reversible=0;
        %set all reactions to slow at the moment
        reactions(reactionsCount).fast=0;
    end
    %save the obtained new reactions in the structure for the model
    modelStructure.reactions=reactions;
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %first assume that all states that are lumped should be listed as variables
    %create a variables entity and set the variables in the structure to the
    %result after the process is finished
    variables=[];      
    %count the number of variables
    variablesCount=0;
    %now save the reactions that are still in the model
    for i=1:nrStates
        %condition: the element in the translationvector should not be the
        %same as the corresponding state name and it should be desired that
        %the element is saved
        if(~isequal(s.stateNames(i), translationVect(i)))%&s.keepStatesVector(i))
            %one more state that exists in the lumped model has been found
            variablesCount=variablesCount+1;
            %call this state the same name as before
            variables(variablesCount).name = char(conversion(s.stateNames(i)));
            %replace the previous states in the reaction with the new lumped states
            variables(variablesCount).formula=char(sym(translationVect(i)));  
            %set the type
            variables(variablesCount).type='';
            %set the compartment
            variables(variablesCount).compartment='';
            %set the unittype
            variables(variablesCount).unittype='';
            %indicate that the model is lumped in the notes
            variables(variablesCount).notes='Previously a state in the full model.';
        end
    end
    %now set the previous varibles back
    %get the number of previous varibles
    previousVariables=length(s.variableNames);
    for i=1:previousVariables
        %one more state that exists in the lumped model has been found
        variablesCount=variablesCount+1;
        %call this state the same name as before
        variables(variablesCount).name = char(conversion(s.variableNames(i)));
        %replace the previous states in the reaction with the new lumped
        %states 
        variables(variablesCount).formula=char(conversion(s.variableFormulas(i)));
        %set the type
        variables(variablesCount).type='';
        %set the compartment
        variables(variablesCount).compartment='';
        %set the unittype
        variables(variablesCount).unittype='';
        %indicate that the model is lumped in the notes
        variables(variablesCount).notes='';
    end
    %save the new variables in the structure
    modelStructure.variables=variables; 
    %take care of events in the model, if any
    events=[];
    if(~isempty(modelStructure.events))
        %get the number of events in the model
        nrEvents=length(modelStructure.events); 
        %check all events
        for i=1:nrEvents
            %get the name for the event, should not be changed
            events(i).name=modelStructure.events(i).name;
            %fix the triggers, can just be substituted
            events(i).trigger=char(subs(modelStructure.events(i).trigger,s.stateNames, translationVect));
            %fix the assignments, only formula can be substituted
            %variable has to be the same if allowed 
            nrVariables=size(modelStructure.events(i).assignment,2);
            for k=1:nrVariables
                %set the variables to the same as before
                events(i).assignment(k).variable=modelStructure.events(i).assignment(k).variable;
                %substitute variables into the formula expressions,
                %condition for if it is a number. Then the substitutions
                %should not be made (since subs destroys numbers)
                if(isempty(str2num(modelStructure.events(i).assignment(k).formula)))
                    events(i).assignment(k).formula=char(subs(modelStructure.events(i).assignment(k).formula,s.stateNames, translationVect));
                else
                    events(i).assignment(k).formula=modelStructure.events(i).assignment(k).formula;
                end
            end
            %set the notes for the event
            events(i).notes='Event in the reduced model.';
          end       
      end
    %save the new events in the structure
    modelStructure.events=events;
    %ends the condition for if we have fast clusters in the model
end
%get a new model from the new structure
newModel=SBmodel(modelStructure);
return

function [statesString] = createStringParanthesis(stateNames)
statesString = '(';
for k=1:length(stateNames)
    if k==1
        statesString = sprintf('%s%s', statesString,  stateNames{k});
    else
        statesString = sprintf('%s,%s', statesString, stateNames{k});
    end
end
statesString = strcat(statesString,')');
return

function [outputStrong] = strongComponents(outputEpsilon)
%this function finds the strong components
%for each group, get a matrix that tells if two components are stongly
%connected
nrClusters=max(outputEpsilon.clusters);
for i=1:nrClusters
    %get the numbers of the states that belong to the cluster
    clustInd=outputEpsilon.clusterIndex(i).indices;
    nrInClust=length(clustInd);
    %get the corresponding A matrix (0/1)
    Aclust=outputEpsilon.Afast(clustInd,clustInd);
    %naive implementation
    Atot=zeros(size(Aclust,1));
    %get the matrix that shows the total connectivity within the cluster
    for j=1:nrInClust
        Atot=Atot+Aclust^j;
    end   
    %vector to indicate which of the states below to the same strong
    %component
    indVect=zeros(1,nrInClust);
    toCheck=1;
    subNr=1;
    Atot2=Atot+eye(nrInClust);
    while(toCheck~=0)
        %check which states belong to the subcluster
        indVect(find(Atot2(toCheck,:).*Atot2(:,toCheck)'))=subNr;
        %check which states have not been assigned to a strong component
        toCheckVect=find(indVect==0);
        %select the next state to check
        if(~isempty(toCheckVect))
            toCheck=toCheckVect(1);
        else
            toCheck=0;
        end
        %add one to subNr for the next subcluster
        subNr=subNr+1;
    end
    %check which states belong to the subcluster
    outputStrong.cluster(i).SC=indVect;
end

function [reducedModel] = identifySlowStates(reducedModel,A,nrRedStates,coslow)
%vector to indicate if a state is slow or not
isSlow = zeros(1,nrRedStates);
%get the absolute value of the entries in A
Aabs = abs(A);

%check each individual state if it is slow or not
for i = 1:nrRedStates
    if(max(Aabs(:,i)) <= coslow && max(Aabs(i,:)) <= coslow)
        isSlow(i) = 1;
    end
end

%get the numbers of the slow states
%the vector is reversed so that the states are later removed from the model
%structure in the correct order
slowNr = fliplr(find(isSlow));

%get the model structure 
modStr = struct(reducedModel);

%remove the slow states and set them as parameters
for i = slowNr
    %create a new parameter, which corresponds to the slow state
    modStr.parameters(end+1).name = modStr.states(i).name;
    modStr.parameters(end).value = modStr.states(i).initialCondition;
    modStr.parameters(end).notes = 'This was a state in the original model that was identified as slow.';
    
    %remove the slow state
    modStr.states(i) = [];
end

%get the model back in the form of a SBmodel object
reducedModel = SBmodel(modStr);

return




