cytoscape
Version:
Graph theory (a.k.a. network) library for analysis and visualisation
203 lines (156 loc) • 5.33 kB
JavaScript
import * as is from '../../is';
import { warn, defaults } from '../../util';
import Map from '../../map';
const bellmanFordDefaults = defaults({
weight: edge => 1,
directed: false,
root: null
});
let elesfn = ({
// Implemented from pseudocode from wikipedia
bellmanFord: function( options ){
let { weight, directed, root } = bellmanFordDefaults(options);
let weightFn = weight;
let eles = this;
let cy = this.cy();
let { edges, nodes } = this.byGroup();
let numNodes = nodes.length;
let infoMap = new Map();
let hasNegativeWeightCycle = false;
let negativeWeightCycles = [];
root = cy.collection(root)[0]; // in case selector passed
edges.unmergeBy( edge => edge.isLoop() );
let numEdges = edges.length;
let getInfo = node => {
let obj = infoMap.get( node.id() );
if( !obj ){
obj = {};
infoMap.set( node.id(), obj );
}
return obj;
};
let getNodeFromTo = to => (is.string(to) ? cy.$(to) : to)[0];
let distanceTo = to => getInfo( getNodeFromTo(to) ).dist;
let pathTo = (to, thisStart = root) => {
let end = getNodeFromTo(to);
let path = [];
let node = end;
for( ;; ){
if( node == null ){ return this.spawn(); }
let { edge, pred } = getInfo( node );
path.unshift( node[0] );
if( node.same(thisStart) && path.length > 0 ){ break; }
if( edge != null ){
path.unshift( edge );
}
node = pred;
}
return eles.spawn( path );
};
// Initializations { dist, pred, edge }
for( let i = 0; i < numNodes; i++ ){
let node = nodes[i];
let info = getInfo( node );
if( node.same(root) ){
info.dist = 0;
} else {
info.dist = Infinity;
}
info.pred = null;
info.edge = null;
}
// Edges relaxation
let replacedEdge = false;
let checkForEdgeReplacement = (node1, node2, edge, info1, info2, weight) => {
let dist = info1.dist + weight;
if( dist < info2.dist && !edge.same(info1.edge) ){
info2.dist = dist;
info2.pred = node1;
info2.edge = edge;
replacedEdge = true;
}
};
for( let i = 1; i < numNodes; i++ ){
replacedEdge = false;
for( let e = 0; e < numEdges; e++ ){
let edge = edges[e];
let src = edge.source();
let tgt = edge.target();
let weight = weightFn(edge);
let srcInfo = getInfo(src);
let tgtInfo = getInfo(tgt);
checkForEdgeReplacement(src, tgt, edge, srcInfo, tgtInfo, weight);
// If undirected graph, we need to take into account the 'reverse' edge
if( !directed ){
checkForEdgeReplacement(tgt, src, edge, tgtInfo, srcInfo, weight);
}
}
if( !replacedEdge ){ break; }
}
if( replacedEdge ){
// Check for negative weight cycles
const negativeWeightCycleIds = [];
for( let e = 0; e < numEdges; e++ ){
let edge = edges[e];
let src = edge.source();
let tgt = edge.target();
let weight = weightFn(edge);
let srcDist = getInfo(src).dist;
let tgtDist = getInfo(tgt).dist;
if( srcDist + weight < tgtDist || (!directed && tgtDist + weight < srcDist) ){
if( !hasNegativeWeightCycle ){
warn('Graph contains a negative weight cycle for Bellman-Ford');
hasNegativeWeightCycle = true;
}
if( options.findNegativeWeightCycles !== false ){
const negativeNodes = [];
if( srcDist + weight < tgtDist ){
negativeNodes.push(src);
}
if( !directed && tgtDist + weight < srcDist ) {
negativeNodes.push(tgt);
}
const numNegativeNodes = negativeNodes.length;
for( let n = 0; n < numNegativeNodes; n++ ){
const start = negativeNodes[n];
let cycle = [start];
cycle.push(getInfo(start).edge);
let node = getInfo(start).pred;
while( cycle.indexOf(node) === -1 ){
cycle.push(node);
cycle.push(getInfo(node).edge);
node = getInfo(node).pred;
}
cycle = cycle.slice(cycle.indexOf(node));
let smallestId = cycle[0].id();
let smallestIndex = 0;
for( let c = 2; c < cycle.length; c+=2 ){
if( cycle[c].id() < smallestId ){
smallestId = cycle[c].id();
smallestIndex = c;
}
}
cycle = cycle.slice(smallestIndex)
.concat(cycle.slice(0, smallestIndex));
cycle.push(cycle[0]);
const cycleId = cycle.map(el => el.id()).join(",");
if( negativeWeightCycleIds.indexOf(cycleId) === -1 ){
negativeWeightCycles.push(eles.spawn(cycle));
negativeWeightCycleIds.push(cycleId);
}
}
} else {
break;
}
}
}
}
return {
distanceTo,
pathTo,
hasNegativeWeightCycle,
negativeWeightCycles
};
} // bellmanFord
}); // elesfn
export default elesfn;