|
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 = ({ |
|
|
|
|
|
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]; |
|
|
|
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 ); |
|
}; |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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( !directed ){ |
|
checkForEdgeReplacement(tgt, src, edge, tgtInfo, srcInfo, weight); |
|
} |
|
} |
|
|
|
if( !replacedEdge ){ break; } |
|
} |
|
|
|
if( replacedEdge ){ |
|
|
|
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 |
|
}; |
|
|
|
} |
|
|
|
}); |
|
|
|
export default elesfn; |
|
|