find_loops_noscc

PURPOSE ^

Function to determine all feedback loops in an ordinary differential equation (ODE) model given by its Jacobian matrix. No strongly connected components are determined before cycle detection.

SYNOPSIS ^

function [loop_tab] = find_loops_noscc(A_in,max_num_loops)

DESCRIPTION ^

FIND_LOOPS_NOSCC Compute feedback loops from Jacobian or adjacency matrix.

 loop_tab = FIND_LOOPS_NOSCC(A_in, max_num_loops) returns a Matlab
 table with three columns, 'loop', 'length', 'sign', that represents 
 feedback loops as derived from matrix A_in.
 'loop' is a cell array returning the regulations, e.g. the output 
 1 2 3 1 means that variable 1 regulates variable 2, variable 2 regulates 
 variable 3 and variable 3 regulates variable 1.
 'length' is a positive integer indicating the number of variables forming
 the loop, i.e. the loop length.
 'sign' is a value in {-1,1} and indicates whether the feedback loop is 
 negative or positive, i.e. whether the number of negative interactions
 forming the loop is uneven or even.
 A_in is a (Jacobian) nxn-matrix that captures the regulations of n 
 variables on each other. Thereby, entry a_ij represents the regulation of 
 variable i by variable j. a_ij should be non-zero only if variable j 
 regulates variable i, and the direction of the regulation (inhibition vs.
 activation) and should be encoded in the sign of a_ij. 
 max_num_loops is an optional parameter indicating the maximal number of
 feedback loops that are detected and returned. It defaults to 1e6.
 Note that the number of reported loops might be larger if the number of
 self-loops is larger.

 In contrast to FIND_LOOPS(), FIND_LOOPS_NOSCC does not use decomposition 
 into strongly connected components and exclusively relies on Johnson's 
 algorithm as adapted from find_elem_circuits by cmaes on git.

 Example
 System of 4 variables with 4 self-loops (loops of length 1), all
 negative, 1 positive loop of length 3 and one negative loop of length 4
   J = [-1 0 0 -1; 1 -1 0 1; 0 1 -1 0; 0 0 1 -1];
   loop_t = find_loops_noscc(J)
   loop_t.loop{1} % delivers the first loop

 $Author: kabaum $  $Date: 2020/06/08 10:58 $ $Revision: 0.1 $
 Copyright: GNU GPLv3 K. Baum 2020

 See also: FIND_LOOPS

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SUBFUNCTIONS ^

SOURCE CODE ^

0001 function [loop_tab] = find_loops_noscc(A_in,max_num_loops)
0002 %FIND_LOOPS_NOSCC Compute feedback loops from Jacobian or adjacency matrix.
0003 %
0004 % loop_tab = FIND_LOOPS_NOSCC(A_in, max_num_loops) returns a Matlab
0005 % table with three columns, 'loop', 'length', 'sign', that represents
0006 % feedback loops as derived from matrix A_in.
0007 % 'loop' is a cell array returning the regulations, e.g. the output
0008 % 1 2 3 1 means that variable 1 regulates variable 2, variable 2 regulates
0009 % variable 3 and variable 3 regulates variable 1.
0010 % 'length' is a positive integer indicating the number of variables forming
0011 % the loop, i.e. the loop length.
0012 % 'sign' is a value in {-1,1} and indicates whether the feedback loop is
0013 % negative or positive, i.e. whether the number of negative interactions
0014 % forming the loop is uneven or even.
0015 % A_in is a (Jacobian) nxn-matrix that captures the regulations of n
0016 % variables on each other. Thereby, entry a_ij represents the regulation of
0017 % variable i by variable j. a_ij should be non-zero only if variable j
0018 % regulates variable i, and the direction of the regulation (inhibition vs.
0019 % activation) and should be encoded in the sign of a_ij.
0020 % max_num_loops is an optional parameter indicating the maximal number of
0021 % feedback loops that are detected and returned. It defaults to 1e6.
0022 % Note that the number of reported loops might be larger if the number of
0023 % self-loops is larger.
0024 %
0025 % In contrast to FIND_LOOPS(), FIND_LOOPS_NOSCC does not use decomposition
0026 % into strongly connected components and exclusively relies on Johnson's
0027 % algorithm as adapted from find_elem_circuits by cmaes on git.
0028 %
0029 % Example
0030 % System of 4 variables with 4 self-loops (loops of length 1), all
0031 % negative, 1 positive loop of length 3 and one negative loop of length 4
0032 %   J = [-1 0 0 -1; 1 -1 0 1; 0 1 -1 0; 0 0 1 -1];
0033 %   loop_t = find_loops_noscc(J)
0034 %   loop_t.loop{1} % delivers the first loop
0035 %
0036 % $Author: kabaum $  $Date: 2020/06/08 10:58 $ $Revision: 0.1 $
0037 % Copyright: GNU GPLv3 K. Baum 2020
0038 %
0039 % See also: FIND_LOOPS
0040 
0041 
0042 %set maximal number of reported loops (plus self-loops)
0043 if nargin < 2
0044     max_num_loops = 1e6;
0045 end
0046 
0047 %transpose (required for correct direction detection) and turn into sparse
0048 %representation
0049 %i.e. entry A_in(2,3) determines how species 2 is regulated by species 3
0050 if ~issparse(A_in)
0051     A = sparse(A_in');
0052 else
0053     A = A_in';
0054 end
0055 
0056 %preparatory functions for the matrix
0057 
0058 %determine self-loops and remove entries on the diagonal
0059     function[self_loop_tab,A_red] = determine_and_remove_self_loops(C)
0060         %determine self-loops, output as table
0061         self_loops=sign(diag(C));
0062         self_loops_ind=find(~(self_loops==0));
0063         self_loops=self_loops(self_loops_ind);
0064         A_red=C-diag(diag(C)); %remove diagonal entries
0065         self_loop_tab=table(... %write the input into result table
0066             mat2cell(cat(2,self_loops_ind,self_loops_ind),ones(length(self_loops_ind),1)),...
0067             ones(length(self_loops_ind),1),...
0068             self_loops,...
0069             'VariableNames',{'loop','length','sign'});
0070     end
0071 
0072 %detect nonviable nodes (i.e. sinks or sources of the network) and set the
0073 %matrix rows & columns to zero
0074     function[A_red]=set_nonviable_nodes_to_zero(C)
0075         %determine those variables that have no ingoing or no outgoing
0076         %edges; assumes that the diagonal entries have been set to zero
0077         nonviable_nodes=find(any(C)+any(C')<2);
0078         A_red=C;
0079         A_red(nonviable_nodes,:)=0; %set nonviable rows to zero
0080         A_red(:,nonviable_nodes)=0; %set nonviable columns to zero
0081     end
0082 
0083 %preprocess A
0084 [self_loop_tab,A] = determine_and_remove_self_loops(A); %determine self-loops and eradicate diag entries
0085 [A]=set_nonviable_nodes_to_zero(A);
0086 
0087 %functions for Johnson's algorithm (cmaes!) of simple circuit detection
0088 %require global variables stack, blocked, Blist, cycles
0089 
0090 n = size(A,1);
0091 Blist = cell(n,1); %this has something to do with blocking nodes
0092 blocked = false(1,n); %whether nodes are blocked or not
0093 stack=[]; %important for keeping track
0094 cycles = {}; %save the cycles in there, the function circuit alters this object
0095 numcycles=size(self_loop_tab,1); %count number of detected FBLs
0096 
0097     function unblock(u)
0098         blocked(u) = false;
0099         for w=Blist{u}
0100             if blocked(w)
0101                 unblock(w)
0102             end
0103         end
0104         Blist{u} = [];
0105     end
0106 
0107     function f = circuit(v, s, C,num_loops_max) %this function seems to detect paths from v to s in matrix C, saves them to "cycle"
0108         f = false;
0109         
0110         stack(end+1) = v;
0111         blocked(v) = true;
0112         
0113         for w=find(C(v,:)) %determine non-zero elements in row v of C
0114             if w == s
0115                 cycles{end+1} = [stack s];
0116                 numcycles=numcycles+1;
0117                 f = true;
0118             elseif ~blocked(w)
0119                 if circuit(w, s, C,num_loops_max) % if there is a path from w to s
0120                     f = true;
0121                 end
0122             end
0123             if numcycles>=num_loops_max %leave function if we have enough loops detected
0124                 return
0125             end
0126         end
0127              
0128         if f
0129             unblock(v)
0130         else
0131             for w = find(C(v,:))
0132                 if ~ismember(v, Blist{w})
0133                     Bnode = Blist{w};
0134                     Blist{w} = [Bnode v];
0135                 end
0136             end
0137         end
0138         
0139         stack(end) = [];
0140     end
0141    
0142 %go through every index
0143 s = 2; %start at node 2; we might go for starting at node n and revert the direction?
0144     while s <= n %go from s=2 up to n =length of A
0145     
0146         % Subgraph of G induced by {1,...,s,}
0147         F = A(1:s,1:s);
0148         blocked(1:s) = false;
0149         Blist(1:s) = cell(s,1);
0150         circuit(s, s, F,max_num_loops);
0151         s = s + 1; %go one species more
0152         if numcycles>=max_num_loops %leave the loop if we have enough FBLs detected
0153             break 
0154         end
0155 
0156     end
0157 
0158 %now we have all cycles or the maximally allowed number
0159 %start preparing the table
0160 
0161 %determine cycle signs
0162 
0163 loop_length=cellfun(@length,cycles)-1;
0164 function[sign_val]=loop_sign_func(cycinds, C)
0165     %determine sign from indices
0166     sign_val= mod(sum(sign(C(sub2ind(size(C),cycinds(1:(end-1)),cycinds(2:end))))<1),2)...
0167         *(-2)+1;
0168 end
0169 loop_sign=cellfun(@(x)loop_sign_func(x,A),cycles);
0170 
0171 %cast the loop table and add self-loops such that at most max_num_loops are
0172 %returned
0173 loop_tab=cat(1,table(cycles',loop_length',loop_sign','VariableNames',{'loop','length','sign'}),self_loop_tab(1:min(height(self_loop_tab),max_num_loops-numcycles),:));
0174 end
0175         
0176

Generated on Wed 24-Jun-2020 09:38:33 by m2html © 2005