UNPKG

6 kBJavaScriptView Raw
1// Implemented by Zoe Xi @zoexi for GSOC 2016
2// https://github.com/cytoscape/cytoscape.js-markov-cluster
3
4// Implemented from Stijn van Dongen's (author of MCL algorithm) documentation: http://micans.org/mcl/
5// and lecture notes: https://www.cs.ucsb.edu/~xyan/classes/CS595D-2009winter/MCL_Presentation2.pdf
6
7import * as util from '../../util';
8
9/* eslint-disable no-unused-vars */
10let defaults = util.defaults({
11 expandFactor: 2, // affects time of computation and cluster granularity to some extent: M * M
12 inflateFactor: 2, // affects cluster granularity (the greater the value, the more clusters): M(i,j) / E(j)
13 multFactor: 1, // optional self loops for each node. Use a neutral value to improve cluster computations.
14 maxIterations: 20, // maximum number of iterations of the MCL algorithm in a single run
15 attributes: [ // attributes/features used to group nodes, ie. similarity values between nodes
16 function(edge) {
17 return 1;
18 }
19 ]
20});
21/* eslint-enable */
22
23let setOptions = ( options ) => defaults( options );
24
25/* eslint-disable no-unused-vars, no-console */
26if( process.env.NODE_ENV !== 'production' ){
27 var printMatrix = function( M ) { // used for debugging purposes only
28 let n = Math.sqrt(M.length);
29 for ( let i = 0; i < n; i++ ) {
30 let row = '';
31 for ( let j = 0; j < n; j++ ) {
32 row += Number(M[i*n+j]).toFixed(3) + ' ';
33 }
34 console.log(row);
35 }
36 console.log('');
37 };
38}
39/* eslint-enable */
40
41let getSimilarity = function( edge, attributes ) {
42 let total = 0;
43 for ( let i = 0; i < attributes.length; i++ ) {
44 total += attributes[i]( edge );
45 }
46 return total;
47};
48
49let addLoops = function( M, n, val ) {
50 for (let i = 0; i < n; i++) {
51 M[i * n + i] = val;
52 }
53};
54
55let normalize = function( M, n ) {
56 let sum;
57 for ( let col = 0; col < n; col++ ) {
58 sum = 0;
59 for ( let row = 0; row < n; row++ ) {
60 sum += M[row * n + col];
61 }
62 for ( let row = 0; row < n; row++ ) {
63 M[row * n + col] = M[row * n + col] / sum;
64 }
65 }
66};
67
68// TODO: blocked matrix multiplication?
69let mmult = function( A, B, n ) {
70 let C = new Array( n * n );
71
72 for ( let i = 0; i < n; i++ ) {
73 for ( let j = 0; j < n; j++ ) {
74 C[i * n + j] = 0;
75 }
76
77 for ( let k = 0; k < n; k++ ) {
78 for ( let j = 0; j < n; j++ ) {
79 C[i * n + j] += A[i * n + k] * B[k * n + j];
80 }
81 }
82 }
83 return C;
84};
85
86let expand = function( M, n, expandFactor /** power **/ ) {
87 let _M = M.slice(0);
88
89 for ( let p = 1; p < expandFactor; p++ ) {
90 M = mmult( M, _M, n );
91 }
92 return M;
93};
94
95let inflate = function( M, n, inflateFactor /** r **/ ) {
96 let _M = new Array( n * n );
97
98 // M(i,j) ^ inflatePower
99 for ( let i = 0; i < n * n; i++ ) {
100 _M[i] = Math.pow( M[i], inflateFactor );
101 }
102
103 normalize( _M, n );
104
105 return _M;
106};
107
108let hasConverged = function( M, _M, n2, roundFactor ) {
109 // Check that both matrices have the same elements (i,j)
110 for ( let i = 0; i < n2; i++ ) {
111 let v1 = Math.round( M[i] * Math.pow(10, roundFactor) ) / Math.pow(10, roundFactor); // truncate to 'roundFactor' decimal places
112 let v2 = Math.round( _M[i] * Math.pow(10, roundFactor) ) / Math.pow(10, roundFactor);
113
114 if ( v1 !== v2 ) {
115 return false;
116 }
117 }
118 return true;
119};
120
121let assign = function( M, n, nodes, cy ) {
122 let clusters = [];
123
124 for ( let i = 0; i < n; i++ ) {
125 let cluster = [];
126 for ( let j = 0; j < n; j++ ) {
127 // Row-wise attractors and elements that they attract belong in same cluster
128 if ( Math.round( M[i * n + j] * 1000 ) / 1000 > 0 ) {
129 cluster.push( nodes[j] );
130 }
131 }
132 if ( cluster.length !== 0 ) {
133 clusters.push( cy.collection(cluster) );
134 }
135 }
136 return clusters;
137};
138
139let isDuplicate = function( c1, c2 ) {
140 for (let i = 0; i < c1.length; i++) {
141 if (!c2[i] || c1[i].id() !== c2[i].id()) {
142 return false;
143 }
144 }
145 return true;
146};
147
148let removeDuplicates = function( clusters ) {
149
150 for (let i = 0; i < clusters.length; i++) {
151 for (let j = 0; j < clusters.length; j++) {
152 if (i != j && isDuplicate(clusters[i], clusters[j])) {
153 clusters.splice(j, 1);
154 }
155 }
156 }
157 return clusters;
158};
159
160let markovClustering = function( options ) {
161 let nodes = this.nodes();
162 let edges = this.edges();
163 let cy = this.cy();
164
165 // Set parameters of algorithm:
166 let opts = setOptions( options );
167
168 // Map each node to its position in node array
169 let id2position = {};
170 for( let i = 0; i < nodes.length; i++ ){
171 id2position[ nodes[i].id() ] = i;
172 }
173
174 // Generate stochastic matrix M from input graph G (should be symmetric/undirected)
175 let n = nodes.length, n2 = n * n;
176 let M = new Array( n2 ), _M;
177 for ( let i = 0; i < n2; i++ ) {
178 M[i] = 0;
179 }
180
181 for ( let e = 0; e < edges.length; e++ ) {
182 let edge = edges[e];
183 let i = id2position[ edge.source().id() ];
184 let j = id2position[ edge.target().id() ];
185
186 let sim = getSimilarity( edge, opts.attributes );
187
188 M[i * n + j] += sim; // G should be symmetric and undirected
189 M[j * n + i] += sim;
190 }
191
192 // Begin Markov cluster algorithm
193
194 // Step 1: Add self loops to each node, ie. add multFactor to matrix diagonal
195 addLoops( M, n, opts.multFactor );
196
197 // Step 2: M = normalize( M );
198 normalize( M, n );
199
200 let isStillMoving = true;
201 let iterations = 0;
202 while ( isStillMoving && iterations < opts.maxIterations ) {
203
204 isStillMoving = false;
205
206 // Step 3:
207 _M = expand( M, n, opts.expandFactor );
208
209 // Step 4:
210 M = inflate( _M, n, opts.inflateFactor );
211
212 // Step 5: check to see if ~steady state has been reached
213 if ( ! hasConverged( M, _M, n2, 4 ) ) {
214 isStillMoving = true;
215 }
216
217 iterations++;
218 }
219
220 // Build clusters from matrix
221 let clusters = assign( M, n, nodes, cy );
222
223 // Remove duplicate clusters due to symmetry of graph and M matrix
224 clusters = removeDuplicates( clusters );
225
226 return clusters;
227};
228
229export default {
230 markovClustering,
231 mcl: markovClustering
232};