feasible-tree.js 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import _ from 'lodash'
  2. import { Graph } from 'graphlibrary'
  3. import { slack } from './util'
  4. /*
  5. * Constructs a spanning tree with tight edges and adjusted the input node's
  6. * ranks to achieve this. A tight edge is one that is has a length that matches
  7. * its "minlen" attribute.
  8. *
  9. * The basic structure for this function is derived from Gansner, et al., "A
  10. * Technique for Drawing Directed Graphs."
  11. *
  12. * Pre-conditions:
  13. *
  14. * 1. Graph must be a DAG.
  15. * 2. Graph must be connected.
  16. * 3. Graph must have at least one node.
  17. * 5. Graph nodes must have been previously assigned a "rank" property that
  18. * respects the "minlen" property of incident edges.
  19. * 6. Graph edges must have a "minlen" property.
  20. *
  21. * Post-conditions:
  22. *
  23. * - Graph nodes will have their rank adjusted to ensure that all edges are
  24. * tight.
  25. *
  26. * Returns a tree (undirected graph) that is constructed using only "tight"
  27. * edges.
  28. */
  29. function feasibleTree (g) {
  30. const t = new Graph({ directed: false })
  31. // Choose arbitrary node from which to start our tree
  32. const start = g.nodes()[0]
  33. const size = g.nodeCount()
  34. t.setNode(start, {})
  35. let edge
  36. let delta
  37. while (tightTree(t, g) < size) {
  38. edge = findMinSlackEdge(t, g)
  39. delta = t.hasNode(edge.v) ? slack(g, edge) : -slack(g, edge)
  40. shiftRanks(t, g, delta)
  41. }
  42. return t
  43. }
  44. /*
  45. * Finds a maximal tree of tight edges and returns the number of nodes in the
  46. * tree.
  47. */
  48. function tightTree (t, g) {
  49. function dfs (v) {
  50. _.forEach(g.nodeEdges(v), function (e) {
  51. const edgeV = e.v
  52. const w = (v === edgeV) ? e.w : edgeV
  53. if (!t.hasNode(w) && !slack(g, e)) {
  54. t.setNode(w, {})
  55. t.setEdge(v, w, {})
  56. dfs(w)
  57. }
  58. })
  59. }
  60. _.forEach(t.nodes(), dfs)
  61. return t.nodeCount()
  62. }
  63. /*
  64. * Finds the edge with the smallest slack that is incident on tree and returns
  65. * it.
  66. */
  67. function findMinSlackEdge (t, g) {
  68. return _.minBy(g.edges(), function (e) {
  69. if (t.hasNode(e.v) !== t.hasNode(e.w)) {
  70. return slack(g, e)
  71. }
  72. })
  73. }
  74. function shiftRanks (t, g, delta) {
  75. _.forEach(t.nodes(), function (v) {
  76. g.node(v).rank += delta
  77. })
  78. }
  79. export default feasibleTree