import * as util from '../../util';
let defaults = util.defaults({
expandFactor: 2, inflateFactor: 2, multFactor: 1, maxIterations: 20, attributes: [ function(edge) {
return 1;
}
]
});
let setOptions = ( options ) => defaults( options );
if( process.env.NODE_ENV !== 'production' ){
var printMatrix = function( M ) { let n = Math.sqrt(M.length);
for ( let i = 0; i < n; i++ ) {
let row = '';
for ( let j = 0; j < n; j++ ) {
row += Number(M[i*n+j]).toFixed(3) + ' ';
}
console.log(row);
}
console.log('');
};
}
let getSimilarity = function( edge, attributes ) {
let total = 0;
for ( let i = 0; i < attributes.length; i++ ) {
total += attributes[i]( edge );
}
return total;
};
let addLoops = function( M, n, val ) {
for (let i = 0; i < n; i++) {
M[i * n + i] = val;
}
};
let normalize = function( M, n ) {
let sum;
for ( let col = 0; col < n; col++ ) {
sum = 0;
for ( let row = 0; row < n; row++ ) {
sum += M[row * n + col];
}
for ( let row = 0; row < n; row++ ) {
M[row * n + col] = M[row * n + col] / sum;
}
}
};
let mmult = function( A, B, n ) {
let C = new Array( n * n );
for ( let i = 0; i < n; i++ ) {
for ( let j = 0; j < n; j++ ) {
C[i * n + j] = 0;
}
for ( let k = 0; k < n; k++ ) {
for ( let j = 0; j < n; j++ ) {
C[i * n + j] += A[i * n + k] * B[k * n + j];
}
}
}
return C;
};
let expand = function( M, n, expandFactor ) {
let _M = M.slice(0);
for ( let p = 1; p < expandFactor; p++ ) {
M = mmult( M, _M, n );
}
return M;
};
let inflate = function( M, n, inflateFactor ) {
let _M = new Array( n * n );
for ( let i = 0; i < n * n; i++ ) {
_M[i] = Math.pow( M[i], inflateFactor );
}
normalize( _M, n );
return _M;
};
let hasConverged = function( M, _M, n2, roundFactor ) {
for ( let i = 0; i < n2; i++ ) {
let v1 = Math.round( M[i] * Math.pow(10, roundFactor) ) / Math.pow(10, roundFactor); let v2 = Math.round( _M[i] * Math.pow(10, roundFactor) ) / Math.pow(10, roundFactor);
if ( v1 !== v2 ) {
return false;
}
}
return true;
};
let assign = function( M, n, nodes, cy ) {
let clusters = [];
for ( let i = 0; i < n; i++ ) {
let cluster = [];
for ( let j = 0; j < n; j++ ) {
if ( Math.round( M[i * n + j] * 1000 ) / 1000 > 0 ) {
cluster.push( nodes[j] );
}
}
if ( cluster.length !== 0 ) {
clusters.push( cy.collection(cluster) );
}
}
return clusters;
};
let isDuplicate = function( c1, c2 ) {
for (let i = 0; i < c1.length; i++) {
if (!c2[i] || c1[i].id() !== c2[i].id()) {
return false;
}
}
return true;
};
let removeDuplicates = function( clusters ) {
for (let i = 0; i < clusters.length; i++) {
for (let j = 0; j < clusters.length; j++) {
if (i != j && isDuplicate(clusters[i], clusters[j])) {
clusters.splice(j, 1);
}
}
}
return clusters;
};
let markovClustering = function( options ) {
let nodes = this.nodes();
let edges = this.edges();
let cy = this.cy();
let opts = setOptions( options );
let id2position = {};
for( let i = 0; i < nodes.length; i++ ){
id2position[ nodes[i].id() ] = i;
}
let n = nodes.length, n2 = n * n;
let M = new Array( n2 ), _M;
for ( let i = 0; i < n2; i++ ) {
M[i] = 0;
}
for ( let e = 0; e < edges.length; e++ ) {
let edge = edges[e];
let i = id2position[ edge.source().id() ];
let j = id2position[ edge.target().id() ];
let sim = getSimilarity( edge, opts.attributes );
M[i * n + j] += sim; M[j * n + i] += sim;
}
addLoops( M, n, opts.multFactor );
normalize( M, n );
let isStillMoving = true;
let iterations = 0;
while ( isStillMoving && iterations < opts.maxIterations ) {
isStillMoving = false;
_M = expand( M, n, opts.expandFactor );
M = inflate( _M, n, opts.inflateFactor );
if ( ! hasConverged( M, _M, n2, 4 ) ) {
isStillMoving = true;
}
iterations++;
}
let clusters = assign( M, n, nodes, cy );
clusters = removeDuplicates( clusters );
return clusters;
};
export default {
markovClustering,
mcl: markovClustering
};