import { error } from '../../util';
const sqrt2 = Math.sqrt(2);
const collapse = function( edgeIndex, nodeMap, remainingEdges ){
if( remainingEdges.length === 0 ){
error(`Karger-Stein must be run on a connected (sub)graph`);
}
let edgeInfo = remainingEdges[ edgeIndex ];
let sourceIn = edgeInfo[1];
let targetIn = edgeInfo[2];
let partition1 = nodeMap[ sourceIn ];
let partition2 = nodeMap[ targetIn ];
let newEdges = remainingEdges;
for( let i = newEdges.length - 1; i >=0; i-- ){
let edge = newEdges[i];
let src = edge[1];
let tgt = edge[2];
if(
( nodeMap[ src ] === partition1 && nodeMap[ tgt ] === partition2 ) ||
( nodeMap[ src ] === partition2 && nodeMap[ tgt ] === partition1 )
){
newEdges.splice(i, 1);
}
}
for( let i = 0; i < newEdges.length; i++ ){
let edge = newEdges[i];
if( edge[1] === partition2 ){ newEdges[i] = edge.slice(); newEdges[i][1] = partition1;
} else if( edge[2] === partition2 ){ newEdges[i] = edge.slice(); newEdges[i][2] = partition1;
}
}
for( let i = 0; i < nodeMap.length; i++ ){
if( nodeMap[i] === partition2 ){
nodeMap[i] = partition1;
}
}
return newEdges;
};
const contractUntil = function( metaNodeMap, remainingEdges, size, sizeLimit ){
while( size > sizeLimit ){
let edgeIndex = Math.floor( (Math.random() * remainingEdges.length) );
remainingEdges = collapse( edgeIndex, metaNodeMap, remainingEdges );
size--;
}
return remainingEdges;
};
const elesfn = ({
kargerStein: function(){
let { nodes, edges } = this.byGroup();
edges.unmergeBy(edge => edge.isLoop());
let numNodes = nodes.length;
let numEdges = edges.length;
let numIter = Math.ceil( Math.pow( Math.log( numNodes ) / Math.LN2, 2 ) );
let stopSize = Math.floor( numNodes / sqrt2 );
if( numNodes < 2 ){
error( 'At least 2 nodes are required for Karger-Stein algorithm' );
return undefined;
}
let edgeIndexes = [];
for( let i = 0; i < numEdges; i++ ){
let e = edges[ i ];
edgeIndexes.push([ i, nodes.indexOf(e.source()), nodes.indexOf(e.target()) ]);
}
let minCutSize = Infinity;
let minCutEdgeIndexes = [];
let minCutNodeMap = new Array(numNodes);
let metaNodeMap = new Array(numNodes);
let metaNodeMap2 = new Array(numNodes);
let copyNodesMap = (from, to) => {
for( let i = 0; i < numNodes; i++ ){
to[i] = from[i];
}
};
for( let iter = 0; iter <= numIter; iter++ ){
for( let i = 0; i < numNodes; i++ ){ metaNodeMap[i] = i; }
let edgesState = contractUntil( metaNodeMap, edgeIndexes.slice(), numNodes, stopSize );
let edgesState2 = edgesState.slice();
copyNodesMap(metaNodeMap, metaNodeMap2);
let res1 = contractUntil( metaNodeMap, edgesState, stopSize, 2 );
let res2 = contractUntil( metaNodeMap2, edgesState2, stopSize, 2 );
if( res1.length <= res2.length && res1.length < minCutSize ){
minCutSize = res1.length;
minCutEdgeIndexes = res1;
copyNodesMap(metaNodeMap, minCutNodeMap);
} else if( res2.length <= res1.length && res2.length < minCutSize ){
minCutSize = res2.length;
minCutEdgeIndexes = res2;
copyNodesMap(metaNodeMap2, minCutNodeMap);
}
}
let cut = this.spawn( minCutEdgeIndexes.map(e => edges[e[0]]) );
let partition1 = this.spawn();
let partition2 = this.spawn();
let witnessNodePartition = minCutNodeMap[0];
for( let i = 0; i < minCutNodeMap.length; i++ ){
let partitionId = minCutNodeMap[i];
let node = nodes[i];
if( partitionId === witnessNodePartition ){
partition1.merge( node );
} else {
partition2.merge( node );
}
}
const constructComponent = (subset) => {
const component = this.spawn();
subset.forEach(node => {
component.merge(node);
node.connectedEdges().forEach(edge => {
if (this.contains(edge) && !cut.contains(edge)) {
component.merge(edge);
}
});
});
return component;
};
const components = [
constructComponent(partition1),
constructComponent(partition2)
];
let ret = {
cut,
components,
partition1,
partition2
};
return ret;
}
});
export default elesfn;