import * as util from '../../util';
import * as math from '../../math';
import * as is from '../../is';
import clusteringDistance from './clustering-distances';
let defaults = util.defaults({
distance: 'euclidean', preference: 'median', damping: 0.8, maxIterations: 1000, minIterations: 100, attributes: [ ]
});
let setOptions = function( options ) {
let dmp = options.damping;
let pref = options.preference;
if( !(0.5 <= dmp && dmp < 1) ){
util.error(`Damping must range on [0.5, 1). Got: ${dmp}`);
}
let validPrefs = ['median', 'mean', 'min', 'max'];
if( !( validPrefs.some(v => v === pref) || is.number(pref) ) ){
util.error(`Preference must be one of [${validPrefs.map( p => `'${p}'` ).join(', ')}] or a number. Got: ${pref}`);
}
return defaults( options );
};
if( process.env.NODE_ENV !== 'production' ){
var printMatrix = function( M ) { let str = '';
let log = s => str = str + s + '\n';
let n = Math.sqrt(M.length);
for ( let i = 0; i < n; i++ ) {
let row = '';
for ( let j = 0; j < n; j++ ) {
row += M[i*n+j] + ' ';
}
log(row);
}
console.log(str);
};
}
let getSimilarity = function( type, n1, n2, attributes ) {
let attr = (n, i) => attributes[i](n);
return -clusteringDistance( type, attributes.length, i => attr(n1, i), i => attr(n2, i), n1, n2 );
};
let getPreference = function( S, preference ) { let p = null;
if( preference === 'median' ){
p = math.median( S );
} else if( preference === 'mean' ){
p = math.mean( S );
} else if ( preference === 'min' ){
p = math.min( S );
} else if ( preference === 'max' ){
p = math.max( S );
} else { p = preference;
}
return p;
};
let findExemplars = function( n, R, A ) {
let indices = [];
for ( let i = 0; i < n; i++ ) {
if ( R[i * n + i] + A[i * n + i] > 0 ) {
indices.push(i);
}
}
return indices;
};
let assignClusters = function( n, S, exemplars ) {
let clusters = [];
for ( let i = 0; i < n; i++ ) {
let index = -1;
let max = -Infinity;
for ( let ei = 0; ei < exemplars.length; ei++ ) {
let e = exemplars[ei];
if ( S[i * n + e] > max ) {
index = e;
max = S[i * n + e];
}
}
if( index > 0 ){
clusters.push(index);
}
}
for (let ei = 0; ei < exemplars.length; ei++) {
clusters[ exemplars[ei] ] = exemplars[ei];
}
return clusters;
};
let assign = function( n, S, exemplars ) {
let clusters = assignClusters( n, S, exemplars );
for ( let ei = 0; ei < exemplars.length; ei++ ) {
let ii = [];
for ( let c = 0; c < clusters.length; c++ ) {
if (clusters[c] === exemplars[ei]) {
ii.push(c);
}
}
let maxI = -1;
let maxSum = -Infinity;
for ( let i = 0; i < ii.length; i++ ) {
let sum = 0;
for ( let j = 0; j < ii.length; j++ ) {
sum += S[ii[j] * n + ii[i]];
}
if ( sum > maxSum ) {
maxI = i;
maxSum = sum;
}
}
exemplars[ei] = ii[maxI];
}
clusters = assignClusters( n, S, exemplars );
return clusters;
};
let affinityPropagation = function( options ) {
let cy = this.cy();
let nodes = this.nodes();
let opts = setOptions( options );
let id2position = {};
for( let i = 0; i < nodes.length; i++ ){
id2position[ nodes[i].id() ] = i;
}
let n; let n2; let S; let p; let R; let A;
n = nodes.length;
n2 = n * n;
S = new Array(n2);
for ( let i = 0; i < n2; i++ ) {
S[i] = -Infinity; }
for ( let i = 0; i < n; i++ ) {
for ( let j = 0; j < n; j++ ) {
if ( i !== j ) {
S[i * n + j] = getSimilarity( opts.distance, nodes[i], nodes[j], opts.attributes );
}
}
}
p = getPreference( S, opts.preference );
for ( let i = 0; i < n; i++ ) {
S[i * n + i] = p;
}
R = new Array(n2);
for ( let i = 0; i < n2; i++ ) {
R[i] = 0.0;
}
A = new Array(n2);
for ( let i = 0; i < n2; i++ ) {
A[i] = 0.0;
}
let old = new Array(n);
let Rp = new Array(n);
let se = new Array(n);
for ( let i = 0; i < n; i ++ ) {
old[i] = 0.0;
Rp[i] = 0.0;
se[i] = 0;
}
let e = new Array(n * opts.minIterations);
for ( let i = 0; i < e.length; i++ ) {
e[i] = 0;
}
let iter;
for ( iter = 0; iter < opts.maxIterations; iter++ ) {
for ( let i = 0; i < n; i++ ) {
let max = -Infinity,
max2 = -Infinity,
maxI = -1,
AS = 0.0;
for ( let j = 0; j < n; j++ ) {
old[j] = R[i * n + j];
AS = A[i * n + j] + S[i * n + j];
if ( AS >= max ) {
max2 = max;
max = AS;
maxI = j;
}
else if ( AS > max2 ) {
max2 = AS;
}
}
for ( let j = 0; j < n; j++ ) {
R[i * n + j] = (1 - opts.damping) * (S[i * n + j] - max) + opts.damping * old[j];
}
R[i * n + maxI] = (1 - opts.damping) * (S[i * n + maxI] - max2) + opts.damping * old[maxI];
}
for ( let i = 0; i < n; i++ ) {
let sum = 0;
for ( let j = 0; j < n; j++ ) {
old[j] = A[j * n + i];
Rp[j] = Math.max(0, R[j * n + i]);
sum += Rp[j];
}
sum -= Rp[i];
Rp[i] = R[i * n + i];
sum += Rp[i];
for ( let j = 0; j < n; j++ ) {
A[j * n + i] = (1 - opts.damping) * Math.min(0, sum - Rp[j]) + opts.damping * old[j];
}
A[i * n + i] = (1 - opts.damping) * (sum - Rp[i]) + opts.damping * old[i];
}
let K = 0;
for ( let i = 0; i < n; i++ ) {
let E = A[i * n + i] + R[i * n + i] > 0 ? 1 : 0;
e[(iter % opts.minIterations) * n + i] = E;
K += E;
}
if ( K > 0 && (iter >= opts.minIterations - 1 || iter == opts.maxIterations - 1) ) {
let sum = 0;
for ( let i = 0; i < n; i++ ) {
se[i] = 0;
for ( let j = 0; j < opts.minIterations; j++ ) {
se[i] += e[j * n + i];
}
if ( se[i] === 0 || se[i] === opts.minIterations ) {
sum++;
}
}
if ( sum === n ) { break;
}
}
}
let exemplarsIndices = findExemplars( n, R, A );
let clusterIndices = assign( n, S, exemplarsIndices, nodes, id2position );
let clusters = {};
for ( let c = 0; c < exemplarsIndices.length; c++ ) {
clusters[ exemplarsIndices[c] ] = [];
}
for (let i = 0; i < nodes.length; i++) {
let pos = id2position[ nodes[i].id() ];
let clusterIndex = clusterIndices[pos];
if( clusterIndex != null ){ clusters[ clusterIndex ].push( nodes[i] );
}
}
let retClusters = new Array(exemplarsIndices.length);
for ( let c = 0; c < exemplarsIndices.length; c++ ) {
retClusters[c] = cy.collection( clusters[ exemplarsIndices[c] ] );
}
return retClusters;
};
export default { affinityPropagation, ap: affinityPropagation };