@knowvah/dot-engine 2.6.12 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +1 -1
  2. package/dist/api.js +18 -4
  3. package/dist/api.js.map +3 -3
  4. package/dist/common/htmltable-emit-fill.d.ts +20 -2
  5. package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
  6. package/dist/common/htmltable-emit.d.ts.map +1 -1
  7. package/dist/common/htmltable-lex-expat.d.ts.map +1 -1
  8. package/dist/common/htmltable-pos-runs.d.ts +7 -4
  9. package/dist/common/htmltable-pos-runs.d.ts.map +1 -1
  10. package/dist/common/htmltable.d.ts.map +1 -1
  11. package/dist/common/libm-trig.d.ts +5 -0
  12. package/dist/common/libm-trig.d.ts.map +1 -0
  13. package/dist/common/multicolor.d.ts +21 -13
  14. package/dist/common/multicolor.d.ts.map +1 -1
  15. package/dist/common/poly-gencode.d.ts +1 -2
  16. package/dist/common/poly-gencode.d.ts.map +1 -1
  17. package/dist/common/poly-init.d.ts.map +1 -1
  18. package/dist/common/splines-clip.d.ts +7 -0
  19. package/dist/common/splines-clip.d.ts.map +1 -1
  20. package/dist/gvc/context.d.ts +6 -0
  21. package/dist/gvc/context.d.ts.map +1 -1
  22. package/dist/gvc/device-cluster.d.ts +15 -0
  23. package/dist/gvc/device-cluster.d.ts.map +1 -1
  24. package/dist/gvc/device.d.ts.map +1 -1
  25. package/dist/gvc/first-layer.d.ts +9 -0
  26. package/dist/gvc/first-layer.d.ts.map +1 -0
  27. package/dist/gvc/gvrender-set-style.d.ts +28 -0
  28. package/dist/gvc/gvrender-set-style.d.ts.map +1 -0
  29. package/dist/gvc/usershape.d.ts +18 -0
  30. package/dist/gvc/usershape.d.ts.map +1 -1
  31. package/dist/index.js +2460 -901
  32. package/dist/index.js.map +4 -4
  33. package/dist/layout/dot/compound.d.ts.map +1 -1
  34. package/dist/layout/dot/edge-route-clip.d.ts +14 -4
  35. package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
  36. package/dist/layout/dot/edge-route-routing.d.ts.map +1 -1
  37. package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts +11 -0
  38. package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts.map +1 -0
  39. package/dist/layout/neato/__fixtures__/hier-imds.d.ts +36 -0
  40. package/dist/layout/neato/__fixtures__/hier-imds.d.ts.map +1 -0
  41. package/dist/layout/neato/__fixtures__/hier-majorization.d.ts +32 -0
  42. package/dist/layout/neato/__fixtures__/hier-majorization.d.ts.map +1 -0
  43. package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
  44. package/dist/layout/neato/compute-hierarchy.d.ts +24 -0
  45. package/dist/layout/neato/compute-hierarchy.d.ts.map +1 -0
  46. package/dist/layout/neato/conjgrad-mkernel.d.ts +20 -0
  47. package/dist/layout/neato/conjgrad-mkernel.d.ts.map +1 -0
  48. package/dist/layout/neato/conjgrad.d.ts +19 -0
  49. package/dist/layout/neato/conjgrad.d.ts.map +1 -1
  50. package/dist/layout/neato/constrained-majorization.d.ts +26 -0
  51. package/dist/layout/neato/constrained-majorization.d.ts.map +1 -0
  52. package/dist/layout/neato/dijkstra.d.ts +33 -16
  53. package/dist/layout/neato/dijkstra.d.ts.map +1 -1
  54. package/dist/layout/neato/gts-triangle.d.ts +14 -0
  55. package/dist/layout/neato/gts-triangle.d.ts.map +1 -0
  56. package/dist/layout/neato/init.d.ts +2 -12
  57. package/dist/layout/neato/init.d.ts.map +1 -1
  58. package/dist/layout/neato/kkutils.d.ts +20 -0
  59. package/dist/layout/neato/kkutils.d.ts.map +1 -0
  60. package/dist/layout/neato/make-graph-data.d.ts +43 -0
  61. package/dist/layout/neato/make-graph-data.d.ts.map +1 -0
  62. package/dist/layout/neato/matrix-ops.d.ts +46 -0
  63. package/dist/layout/neato/matrix-ops.d.ts.map +1 -1
  64. package/dist/layout/neato/opt-arrangement.d.ts +20 -0
  65. package/dist/layout/neato/opt-arrangement.d.ts.map +1 -0
  66. package/dist/layout/neato/quad-prog-solve.d.ts +44 -0
  67. package/dist/layout/neato/quad-prog-solve.d.ts.map +1 -0
  68. package/dist/layout/neato/smart-ini-x.d.ts +9 -0
  69. package/dist/layout/neato/smart-ini-x.d.ts.map +1 -0
  70. package/dist/layout/neato/splines.d.ts +1 -1
  71. package/dist/layout/neato/splines.d.ts.map +1 -1
  72. package/dist/layout/neato/start.d.ts +4 -1
  73. package/dist/layout/neato/start.d.ts.map +1 -1
  74. package/dist/layout/neato/stress.d.ts +8 -0
  75. package/dist/layout/neato/stress.d.ts.map +1 -1
  76. package/dist/model/edge.d.ts +1 -1
  77. package/dist/model/edge.d.ts.map +1 -1
  78. package/dist/model/lazy-attrs.d.ts +10 -0
  79. package/dist/model/lazy-attrs.d.ts.map +1 -0
  80. package/dist/model/node.d.ts +1 -1
  81. package/dist/model/node.d.ts.map +1 -1
  82. package/dist/ortho/index.d.ts.map +1 -1
  83. package/dist/render/dot/edge-draw.d.ts +1 -1
  84. package/dist/render/dot/edge-draw.d.ts.map +1 -1
  85. package/dist/render/svg-multicolor.d.ts +2 -2
  86. package/dist/render/svg-multicolor.d.ts.map +1 -1
  87. package/dist/render.js +2340 -781
  88. package/dist/render.js.map +4 -4
  89. package/package.json +1 -1
  90. package/src/common/graph-init.ts +1 -1
  91. package/src/common/htmltable-emit-fill.ts +56 -11
  92. package/src/common/htmltable-emit.ts +4 -3
  93. package/src/common/htmltable-lex-expat.ts +21 -3
  94. package/src/common/htmltable-pos-runs.ts +11 -6
  95. package/src/common/htmltable.ts +28 -116
  96. package/src/common/libm-trig.ts +43 -0
  97. package/src/common/multicolor.ts +88 -24
  98. package/src/common/poly-gencode.ts +16 -8
  99. package/src/common/poly-init.ts +19 -1
  100. package/src/common/postproc.ts +2 -2
  101. package/src/common/splines-clip.ts +15 -12
  102. package/src/common/splines-routespl.ts +2 -2
  103. package/src/common/textmeasure-lookup.ts +1 -1
  104. package/src/gvc/context.ts +8 -0
  105. package/src/gvc/device-cluster.ts +63 -8
  106. package/src/gvc/device.ts +16 -2
  107. package/src/gvc/first-layer.ts +26 -0
  108. package/src/gvc/gvrender-set-style.ts +114 -0
  109. package/src/gvc/usershape.ts +26 -0
  110. package/src/layout/dot/cluster.ts +1 -1
  111. package/src/layout/dot/compound.ts +3 -4
  112. package/src/layout/dot/conc.ts +1 -1
  113. package/src/layout/dot/edge-route-clip.ts +19 -7
  114. package/src/layout/dot/edge-route-routing.ts +11 -3
  115. package/src/layout/dot/fastgr.ts +2 -2
  116. package/src/layout/neato/__fixtures__/dijkstra-f.ts +17 -0
  117. package/src/layout/neato/__fixtures__/hier/c1554.gv +33 -0
  118. package/src/layout/neato/__fixtures__/hier/c1554.plain +26 -0
  119. package/src/layout/neato/__fixtures__/hier/c1554.xdot +161 -0
  120. package/src/layout/neato/__fixtures__/hier/cases/cycle.gv +1 -0
  121. package/src/layout/neato/__fixtures__/hier/cases/cycle.plain +10 -0
  122. package/src/layout/neato/__fixtures__/hier/cases/cycle.stderr +0 -0
  123. package/src/layout/neato/__fixtures__/hier/cases/dirnone.gv +1 -0
  124. package/src/layout/neato/__fixtures__/hier/cases/dirnone.plain +11 -0
  125. package/src/layout/neato/__fixtures__/hier/cases/dirnone.stderr +0 -0
  126. package/src/layout/neato/__fixtures__/hier/cases/epsilon.gv +1 -0
  127. package/src/layout/neato/__fixtures__/hier/cases/epsilon.plain +13 -0
  128. package/src/layout/neato/__fixtures__/hier/cases/epsilon.stderr +0 -0
  129. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.gv +1 -0
  130. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.plain +8 -0
  131. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.stderr +0 -0
  132. package/src/layout/neato/__fixtures__/hier/cases/mds.gv +1 -0
  133. package/src/layout/neato/__fixtures__/hier/cases/mds.plain +10 -0
  134. package/src/layout/neato/__fixtures__/hier/cases/mds.stderr +0 -0
  135. package/src/layout/neato/__fixtures__/hier/cases/multiedge.gv +1 -0
  136. package/src/layout/neato/__fixtures__/hier/cases/multiedge.plain +10 -0
  137. package/src/layout/neato/__fixtures__/hier/cases/multiedge.stderr +0 -0
  138. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.gv +1 -0
  139. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.plain +8 -0
  140. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.stderr +0 -0
  141. package/src/layout/neato/__fixtures__/hier/cases/startrandom.gv +1 -0
  142. package/src/layout/neato/__fixtures__/hier/cases/startrandom.plain +10 -0
  143. package/src/layout/neato/__fixtures__/hier/cases/startrandom.stderr +0 -0
  144. package/src/layout/neato/__fixtures__/hier/cases/startregular.gv +1 -0
  145. package/src/layout/neato/__fixtures__/hier/cases/startregular.plain +10 -0
  146. package/src/layout/neato/__fixtures__/hier/cases/startregular.stderr +0 -0
  147. package/src/layout/neato/__fixtures__/hier/cases/tdist.gv +1 -0
  148. package/src/layout/neato/__fixtures__/hier/cases/tdist.plain +13 -0
  149. package/src/layout/neato/__fixtures__/hier/cases/tdist.stderr +0 -0
  150. package/src/layout/neato/__fixtures__/hier/cases/undirected.gv +1 -0
  151. package/src/layout/neato/__fixtures__/hier/cases/undirected.plain +10 -0
  152. package/src/layout/neato/__fixtures__/hier/cases/undirected.stderr +0 -0
  153. package/src/layout/neato/__fixtures__/hier/hier-probe.gv +1 -0
  154. package/src/layout/neato/__fixtures__/hier/hier-probe.plain +16 -0
  155. package/src/layout/neato/__fixtures__/hier/hier-probe.xdot +62 -0
  156. package/src/layout/neato/__fixtures__/hier-imds.ts +151 -0
  157. package/src/layout/neato/__fixtures__/hier-majorization.ts +145 -0
  158. package/src/layout/neato/cdt-surface.ts +6 -16
  159. package/src/layout/neato/compute-hierarchy.ts +90 -0
  160. package/src/layout/neato/conjgrad-mkernel.ts +141 -0
  161. package/src/layout/neato/conjgrad.ts +208 -0
  162. package/src/layout/neato/constrained-majorization.ts +479 -0
  163. package/src/layout/neato/dijkstra.ts +218 -92
  164. package/src/layout/neato/edge-len.ts +2 -2
  165. package/src/layout/neato/fdp-adjust.ts +3 -3
  166. package/src/layout/neato/gts-triangle.ts +42 -0
  167. package/src/layout/neato/init.ts +57 -57
  168. package/src/layout/neato/kk.ts +2 -2
  169. package/src/layout/neato/kkutils.ts +96 -0
  170. package/src/layout/neato/make-graph-data.ts +166 -0
  171. package/src/layout/neato/matrix-ops.ts +138 -0
  172. package/src/layout/neato/opt-arrangement.ts +86 -0
  173. package/src/layout/neato/quad-prog-solve.ts +489 -0
  174. package/src/layout/neato/smart-ini-x.ts +503 -0
  175. package/src/layout/neato/splines.ts +4 -4
  176. package/src/layout/neato/start.ts +7 -6
  177. package/src/layout/neato/stress.ts +28 -0
  178. package/src/model/edge.ts +3 -2
  179. package/src/model/lazy-attrs.ts +36 -0
  180. package/src/model/node.ts +3 -2
  181. package/src/ortho/index.ts +2 -3
  182. package/src/parser/dot.js +1 -1
  183. package/src/parser/dot.pegjs +1 -1
  184. package/src/parser/index.ts +1 -1
  185. package/src/render/color-resolve.ts +2 -2
  186. package/src/render/dot/edge-draw.ts +6 -3
  187. package/src/render/dot.ts +1 -1
  188. package/src/render/svg-edge-split.ts +2 -2
  189. package/src/render/svg-multicolor.ts +4 -3
  190. package/src/render/svg-parallel-edge.ts +2 -2
@@ -30,126 +30,252 @@ export interface TermSgd {
30
30
  }
31
31
 
32
32
  // ---------------------------------------------------------------------------
33
- // Min-heap (keyed by Float32Array distances)
33
+ // Float Dijkstra: dijkstra_f and its indexed heap (shares NgHeap and the
34
+ // exchange / assign helpers with the DistType heap below)
34
35
  // ---------------------------------------------------------------------------
35
36
 
36
- /** Push vertex index `idx` into the heap. @internal */
37
- export function heapPush(
38
- heap: number[],
39
- dist: Float32Array,
40
- idx: number,
41
- ): void {
42
- let pos = heap.length;
43
- heap.push(idx);
44
- while (pos > 0) {
45
- const parent = (pos - 1) >> 1;
46
- if (dist[heap[parent]] <= dist[heap[pos]]) break;
47
- const tmp = heap[parent];
48
- heap[parent] = heap[pos];
49
- heap[pos] = tmp;
50
- pos = parent;
51
- }
37
+ /** C `FLT_MAX`: the "unreachable" distance of dijkstra_f. */
38
+ export const FLT_MAX = 3.4028234663852886e38;
39
+
40
+ /** @see lib/neatogen/dijkstra.c:greaterPriority_f */
41
+ function greaterPriorityF(h: NgHeap, i: number, j: number, dist: Float32Array): boolean {
42
+ return dist[h.data[i]!]! < dist[h.data[j]!]!;
52
43
  }
53
44
 
54
- /** Sift the root down to restore heap order after replacing it. @internal */
55
- export function heapSiftDown(heap: number[], dist: Float32Array): void {
56
- let pos = 0;
45
+ /** @see lib/neatogen/dijkstra.c:heapify_f */
46
+ function heapifyF(h: NgHeap, start: number, dist: Float32Array): void {
47
+ let i = start;
57
48
  for (;;) {
58
- const l = 2 * pos + 1;
59
- const r = 2 * pos + 2;
60
- let smallest = pos;
61
- if (l < heap.length && dist[heap[l]] < dist[heap[smallest]]) smallest = l;
62
- if (r < heap.length && dist[heap[r]] < dist[heap[smallest]]) smallest = r;
63
- if (smallest === pos) break;
64
- const tmp = heap[smallest];
65
- heap[smallest] = heap[pos];
66
- heap[pos] = tmp;
67
- pos = smallest;
49
+ const l = ngLeft(i);
50
+ const r = ngRight(i);
51
+ let largest = ngInsideHeap(h, l) && greaterPriorityF(h, l, i, dist) ? l : i;
52
+ if (ngInsideHeap(h, r) && greaterPriorityF(h, r, largest, dist)) largest = r;
53
+ if (largest === i) break;
54
+ ngExchange(h, largest, i);
55
+ i = largest;
68
56
  }
69
57
  }
70
58
 
71
- /** Pop and return the vertex with minimum distance. @internal */
72
- export function heapPop(heap: number[], dist: Float32Array): number {
73
- const top = heap[0];
74
- const last = heap.pop()!;
75
- if (heap.length === 0) return top;
76
- heap[0] = last;
77
- heapSiftDown(heap, dist);
78
- return top;
79
- }
80
-
81
- /** Build a min-heap from all nodes except `src`. @internal */
82
- export function heapBuildExcluding(
83
- src: number,
84
- n: number,
85
- dist: Float32Array,
86
- ): number[] {
87
- const heap: number[] = [];
59
+ /** @see lib/neatogen/dijkstra.c:initHeap_f */
60
+ function initHeapF(startVertex: number, dist: Float32Array, n: number): NgHeap {
61
+ const h: NgHeap = {
62
+ data: new Array<number>(Math.max(n - 1, 0)).fill(0),
63
+ heapSize: n - 1,
64
+ index: new Int32Array(n),
65
+ };
66
+ let count = 0;
88
67
  for (let i = 0; i < n; i++) {
89
- if (i !== src) heapPush(heap, dist, i);
68
+ if (i !== startVertex) {
69
+ h.data[count] = i;
70
+ h.index[i] = count;
71
+ count++;
72
+ }
90
73
  }
91
- return heap;
74
+ for (let j = Math.trunc((n - 1) / 2); j >= 0; j--) heapifyF(h, j, dist);
75
+ return h;
92
76
  }
93
77
 
94
- // ---------------------------------------------------------------------------
95
- // dijkstra helpers
96
- // ---------------------------------------------------------------------------
78
+ /**
79
+ * Pops the closest vertex; returns -1 (C `false`) when the heap is empty.
80
+ * @see lib/neatogen/dijkstra.c:extractMax_f
81
+ */
82
+ function extractMaxF(h: NgHeap, dist: Float32Array): number {
83
+ if (h.heapSize === 0) return -1;
84
+ const max = h.data[0]!;
85
+ h.data[0] = h.data[h.heapSize - 1]!;
86
+ h.index[h.data[0]] = 0;
87
+ h.heapSize--;
88
+ heapifyF(h, 0, dist);
89
+ return max;
90
+ }
97
91
 
98
- /** Initialise dist array from source edges. @internal */
99
- export function dijkstraInitDist(
100
- src: number,
101
- graph: VtxData[],
102
- n: number,
103
- dist: Float32Array,
92
+ /** @see lib/neatogen/dijkstra.c:increaseKey_f */
93
+ function increaseKeyF(
94
+ h: NgHeap, increasedVertex: number, newDist: number, dist: Float32Array,
104
95
  ): void {
105
- for (let i = 0; i < n; i++) dist[i] = Infinity;
106
- dist[src] = 0;
107
- const v = graph[src];
108
- for (let i = 1; i < v.nedges; i++) dist[v.edges[i]] = v.ewgts[i];
96
+ if (dist[increasedVertex]! <= newDist) return;
97
+ let i = h.index[increasedVertex]!;
98
+ dist[increasedVertex] = newDist;
99
+ while (i > 0 && dist[h.data[ngParent(i)]!]! > newDist) {
100
+ ngAssign(h, i, ngParent(i));
101
+ i = ngParent(i);
102
+ }
103
+ h.data[i] = increasedVertex;
104
+ h.index[increasedVertex] = i;
109
105
  }
110
106
 
111
- /** Relax all outgoing edges of `closest`. @internal */
112
- export function dijkstraRelax(
113
- closest: number,
114
- closestDist: number,
107
+ /**
108
+ * Weighted single-source shortest paths from `vertex`; assumes a connected
109
+ * graph, unreachable vertices keep `FLT_MAX`. All arithmetic is C `float`.
110
+ *
111
+ * @see lib/neatogen/dijkstra.c:dijkstra_f
112
+ */
113
+ export function dijkstra(
114
+ vertex: number,
115
115
  graph: VtxData[],
116
+ n: number,
116
117
  dist: Float32Array,
117
- heap: number[],
118
118
  ): void {
119
- const cv = graph[closest];
120
- for (let i = 1; i < cv.nedges; i++) {
121
- const nb = cv.edges[i];
122
- const nd = closestDist + cv.ewgts[i];
123
- if (nd < dist[nb]) {
124
- dist[nb] = nd;
125
- heapPush(heap, dist, nb);
119
+ for (let i = 0; i < n; i++) dist[i] = FLT_MAX;
120
+ dist[vertex] = 0;
121
+ const vd = graph[vertex]!;
122
+ for (let i = 1; i < vd.nedges; i++) dist[vd.edges[i]!] = vd.ewgts[i]!;
123
+
124
+ const h = initHeapF(vertex, dist, n);
125
+ for (let closest = extractMaxF(h, dist); closest >= 0; closest = extractMaxF(h, dist)) {
126
+ const closestDist = dist[closest]!;
127
+ if (closestDist === FLT_MAX) break;
128
+ const cd = graph[closest]!;
129
+ for (let i = 1; i < cd.nedges; i++) {
130
+ increaseKeyF(h, cd.edges[i]!, Math.fround(closestDist + Math.fround(cd.ewgts[i]!)), dist);
126
131
  }
127
132
  }
128
133
  }
129
134
 
130
135
  // ---------------------------------------------------------------------------
131
- // Public: dijkstra (float-weight, float-output)
136
+ // Integer (DistType) Dijkstra: ngdijkstra and its max-heap
132
137
  // ---------------------------------------------------------------------------
133
138
 
139
+ /** C `(DistType)INT_MAX`. @see lib/neatogen/dijkstra.c:MAX_DIST */
140
+ export const MAX_DIST = 2147483647;
141
+
134
142
  /**
135
- * Single-source shortest paths (weighted) from `src`.
136
- * Populates `dist` with Float32 distances; unreachable nodes get Infinity.
137
- * Equivalent to `dijkstra_f` in lib/neatogen/dijkstra.c.
138
- *
139
- * @see lib/neatogen/dijkstra.c:dijkstra_f
143
+ * The heap of dijkstra.c: `data[i] = vertexNum <=> index[vertexNum] = i`, with
144
+ * the 0-rooted `left = 2i`, `right = 2i + 1`, `parent = i / 2` numbering.
145
+ * @see lib/neatogen/dijkstra.c:heap
140
146
  */
141
- export function dijkstra(
142
- src: number,
143
- graph: VtxData[],
144
- n: number,
145
- dist: Float32Array,
147
+ export interface NgHeap {
148
+ data: number[];
149
+ heapSize: number;
150
+ index: Int32Array;
151
+ }
152
+
153
+ /** @see lib/neatogen/dijkstra.c:left */
154
+ function ngLeft(i: number): number { return 2 * i; }
155
+
156
+ /** @see lib/neatogen/dijkstra.c:right */
157
+ function ngRight(i: number): number { return 2 * i + 1; }
158
+
159
+ /** @see lib/neatogen/dijkstra.c:parent */
160
+ function ngParent(i: number): number { return Math.trunc(i / 2); }
161
+
162
+ /** @see lib/neatogen/dijkstra.c:insideHeap */
163
+ function ngInsideHeap(h: NgHeap, i: number): boolean { return i < h.heapSize; }
164
+
165
+ /** @see lib/neatogen/dijkstra.c:greaterPriority */
166
+ function ngGreaterPriority(h: NgHeap, i: number, j: number, dist: Int32Array): boolean {
167
+ return dist[h.data[i]!]! < dist[h.data[j]!]!;
168
+ }
169
+
170
+ /** @see lib/neatogen/dijkstra.c:assign */
171
+ function ngAssign(h: NgHeap, i: number, j: number): void {
172
+ h.data[i] = h.data[j]!;
173
+ h.index[h.data[i]!] = i;
174
+ }
175
+
176
+ /** @see lib/neatogen/dijkstra.c:exchange */
177
+ function ngExchange(h: NgHeap, i: number, j: number): void {
178
+ const t = h.data[i]!;
179
+ h.data[i] = h.data[j]!;
180
+ h.data[j] = t;
181
+ h.index[h.data[i]!] = i;
182
+ h.index[h.data[j]!] = j;
183
+ }
184
+
185
+ /** @see lib/neatogen/dijkstra.c:heapify */
186
+ function ngHeapify(h: NgHeap, start: number, dist: Int32Array): void {
187
+ let i = start;
188
+ for (;;) {
189
+ const l = ngLeft(i);
190
+ const r = ngRight(i);
191
+ let largest = ngInsideHeap(h, l) && ngGreaterPriority(h, l, i, dist) ? l : i;
192
+ if (ngInsideHeap(h, r) && ngGreaterPriority(h, r, largest, dist)) largest = r;
193
+ if (largest === i) break;
194
+ ngExchange(h, largest, i);
195
+ i = largest;
196
+ }
197
+ }
198
+
199
+ /** @see lib/neatogen/dijkstra.c:initHeap */
200
+ export function ngInitHeap(startVertex: number, dist: Int32Array, n: number): NgHeap {
201
+ const h: NgHeap = {
202
+ data: new Array<number>(Math.max(n - 1, 0)).fill(0),
203
+ heapSize: n - 1,
204
+ index: new Int32Array(n),
205
+ };
206
+ let count = 0;
207
+ for (let i = 0; i < n; i++) {
208
+ if (i !== startVertex) {
209
+ h.data[count] = i;
210
+ h.index[i] = count;
211
+ count++;
212
+ }
213
+ }
214
+ for (let j = Math.trunc((n - 1) / 2); j >= 0; j--) ngHeapify(h, j, dist);
215
+ return h;
216
+ }
217
+
218
+ /**
219
+ * Pops the closest vertex; returns -1 (C `false`) when the heap is empty.
220
+ * @see lib/neatogen/dijkstra.c:extractMax
221
+ */
222
+ export function ngExtractMax(h: NgHeap, dist: Int32Array): number {
223
+ if (h.heapSize === 0) return -1;
224
+ const max = h.data[0]!;
225
+ h.data[0] = h.data[h.heapSize - 1]!;
226
+ h.index[h.data[0]] = 0;
227
+ h.heapSize--;
228
+ ngHeapify(h, 0, dist);
229
+ return max;
230
+ }
231
+
232
+ /** @see lib/neatogen/dijkstra.c:increaseKey */
233
+ export function ngIncreaseKey(
234
+ h: NgHeap, increasedVertex: number, newDist: number, dist: Int32Array,
146
235
  ): void {
147
- dijkstraInitDist(src, graph, n, dist);
148
- const heap = heapBuildExcluding(src, n, dist);
149
- while (heap.length > 0) {
150
- const closest = heapPop(heap, dist);
151
- const closestDist = dist[closest];
152
- if (!isFinite(closestDist)) break;
153
- dijkstraRelax(closest, closestDist, graph, dist, heap);
236
+ if (dist[increasedVertex]! <= newDist) return;
237
+ let i = h.index[increasedVertex]!;
238
+ dist[increasedVertex] = newDist;
239
+ while (i > 0 && dist[h.data[ngParent(i)]!]! > newDist) {
240
+ ngAssign(h, i, ngParent(i));
241
+ i = ngParent(i);
242
+ }
243
+ h.data[i] = increasedVertex;
244
+ h.index[increasedVertex] = i;
245
+ }
246
+
247
+ /** C `(DistType)` of a float edge weight (truncation toward zero). */
248
+ function toDistType(w: number): number {
249
+ return Math.trunc(Math.fround(w));
250
+ }
251
+
252
+ /**
253
+ * Single-source shortest paths on the int-truncated edge weights `ewgts`;
254
+ * unreachable vertices get `prevClosestDist + 10` (INT_MAX when that would
255
+ * overflow).
256
+ * @see lib/neatogen/dijkstra.c:ngdijkstra
257
+ */
258
+ export function ngdijkstra(vertex: number, graph: VtxData[], n: number, dist: Int32Array): void {
259
+ let prevClosestDist = MAX_DIST;
260
+ for (let i = 0; i < n; i++) dist[i] = MAX_DIST;
261
+ dist[vertex] = 0;
262
+ const vd = graph[vertex]!;
263
+ for (let i = 1; i < vd.nedges; i++) dist[vd.edges[i]!] = toDistType(vd.ewgts[i]!);
264
+
265
+ const h = ngInitHeap(vertex, dist, n);
266
+ for (let closest = ngExtractMax(h, dist); closest >= 0; closest = ngExtractMax(h, dist)) {
267
+ const closestDist = dist[closest]!;
268
+ if (closestDist === MAX_DIST) break;
269
+ const cd = graph[closest]!;
270
+ for (let i = 1; i < cd.nedges; i++) {
271
+ ngIncreaseKey(h, cd.edges[i]!, (closestDist + toDistType(cd.ewgts[i]!)) | 0, dist);
272
+ }
273
+ prevClosestDist = closestDist;
274
+ }
275
+
276
+ for (let i = 0; i < n; i++) {
277
+ if (dist[i] === MAX_DIST) {
278
+ dist[i] = MAX_DIST - prevClosestDist < 10 ? MAX_DIST : prevClosestDist + 10;
279
+ }
154
280
  }
155
281
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  import type { Graph } from '../../model/graph.js';
11
11
  import type { Edge } from '../../model/edge.js';
12
- import { agerrLineOn } from '../../common/agerr.js';
12
+ import { agwarningf } from '../../common/agerr.js';
13
13
 
14
14
  /** `dfltlen` passed by scan_graph_mode. @see lib/neatogen/stuff.c:scan_graph_mode */
15
15
  export const DEFAULT_EDGE_LEN = 1.0;
@@ -25,7 +25,7 @@ export function lenattr(ep: Edge, g: Graph): number {
25
25
  const val = /^\s*[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/.exec(s);
26
26
  const len = val === null ? Number.NaN : parseFloat(val[0]);
27
27
  if (Number.isNaN(len) || len < 0 || len === 0) {
28
- agerrLineOn('warn', `bad edge len "${s}" in ${g.name} - setting to ${DEFAULT_EDGE_LEN.toFixed(2)}`);
28
+ agwarningf(`bad edge len "${s}" in ${g.name} - setting to ${DEFAULT_EDGE_LEN.toFixed(2)}`);
29
29
  return DEFAULT_EDGE_LEN;
30
30
  }
31
31
  return len;
@@ -26,7 +26,7 @@ import { vpscAdjust } from './vpsc-adjust.js';
26
26
  import { sepFactor } from './sep-factor.js';
27
27
  import { lateDouble } from '../../common/nodeinit.js';
28
28
  import { RenderError } from '../../errors.js';
29
- import { agerrLineOn } from '../../common/agerr.js';
29
+ import { agwarningf } from '../../common/agerr.js';
30
30
 
31
31
  /** @see lib/neatogen/adjust.c:DFLT_MARGIN (points) */
32
32
  const DFLT_MARGIN = 4;
@@ -162,7 +162,7 @@ export function warnUnrecognizedOverlap(flag: string | undefined): void {
162
162
  const s = flag.toLowerCase();
163
163
  if (s.startsWith('prism') || NAMED_MODES.has(s)) return;
164
164
  if (mapBoolDflt(flag, false) === mapBoolDflt(flag, true)) return;
165
- agerrLineOn('warn', `Unrecognized overlap value "${flag}" - using false`);
165
+ agwarningf(`Unrecognized overlap value "${flag}" - using false`);
166
166
  }
167
167
 
168
168
  /**
@@ -204,7 +204,7 @@ function adjustLate(g: Graph, am: AdjustData): number {
204
204
  case AM_IPSEP: return 0;
205
205
  case AM_VPSC: return vpscAdjust(g);
206
206
  default:
207
- agerrLineOn('warn', `Unhandled adjust option ${am.print}`);
207
+ agwarningf(`Unhandled adjust option ${am.print}`);
208
208
  return 0;
209
209
  }
210
210
  }
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: EPL-2.0
2
+ /**
3
+ * GTS's enclosing triangle, split from cdt-surface.ts to mirror the GTS source
4
+ * file it ports (the oracle links libgts 0.7.6 for neato's constrained
5
+ * Delaunay triangulation).
6
+ * @see gts-0.7.6 src/triangle.c
7
+ */
8
+
9
+ import { fma } from '../../common/fma.js';
10
+
11
+ /** `#define SQRT3 1.73205080757` (gts-0.7.6 src/triangle.c). */
12
+ const SQRT3 = 1.73205080757;
13
+
14
+ /**
15
+ * Vertices of the triangle enclosing points (px[0..n), py[0..n)). The oracle's
16
+ * libgts (arm64) fuses r*SQRT3 + xo, r*2 + yo, xo - r*SQRT3 and
17
+ * dx*dx + dy*dy (fmadd/fmsub in _gts_triangle_enclosing), so those are fma.
18
+ * @see gts-0.7.6 src/triangle.c:gts_triangle_enclosing
19
+ */
20
+ export function gtsTriangleEnclosing(
21
+ px: number[], py: number[], n: number, scale: number,
22
+ ): [[number, number], [number, number], [number, number]] {
23
+ let xmin = px[0]!; let xmax = px[0]!;
24
+ let ymin = py[0]!; let ymax = py[0]!;
25
+ for (let i = 1; i < n; i++) {
26
+ if (px[i]! > xmax) xmax = px[i]!;
27
+ else if (px[i]! < xmin) xmin = px[i]!;
28
+ if (py[i]! > ymax) ymax = py[i]!;
29
+ else if (py[i]! < ymin) ymin = py[i]!;
30
+ }
31
+ const xo = (xmax + xmin) / 2;
32
+ const yo = (ymax + ymin) / 2;
33
+ const dx = xmax - xo;
34
+ const dy = ymax - yo;
35
+ let r = scale * Math.sqrt(fma(dx, dx, dy * dy));
36
+ if (r === 0) r = scale;
37
+ return [
38
+ [fma(r, SQRT3, xo), yo - r],
39
+ [xo, fma(r, 2, yo)],
40
+ [fma(-r, SQRT3, xo), yo - r],
41
+ ];
42
+ }
@@ -16,15 +16,22 @@ import type { Edge } from '../../model/edge.js';
16
16
  import type { VtxData } from './dijkstra.js';
17
17
  import {
18
18
  DFLT_ITERATIONS,
19
+ OPT_SMART_INIT as STRESS_OPT_SMART_INIT,
19
20
  MODEL_MDS as STRESS_MODEL_MDS,
20
21
  MODEL_CIRCUIT as STRESS_MODEL_CIRCUIT,
21
22
  MODEL_SUBSET as STRESS_MODEL_SUBSET,
22
23
  } from './stress.js';
23
24
  import { sgdLayout } from './sgd.js';
24
25
  import { kkNeato } from './kk.js';
25
- import { checkStart, assertSupported, parseStart, INIT_RANDOM, INIT_REGULAR } from './start.js';
26
-
27
- export { checkStart };
26
+ import {
27
+ checkStart, assertSupported, parseStart, INIT_RANDOM, INIT_REGULAR, INIT_SELF,
28
+ } from './start.js';
29
+ import { makeGraphDataC, graphHasLen } from './make-graph-data.js';
30
+ import type { DirectedVtxData } from './opt-arrangement.js';
31
+ import { stressMajorizationWithHierarchy } from './constrained-majorization.js';
32
+ import { agerr } from '../../common/agerr.js';
33
+
34
+ export { makeGraphDataC, graphHasLen, checkStart };
28
35
  import { mapbool } from '../dot/rank.js';
29
36
  import {
30
37
  computeApspPacked,
@@ -329,72 +336,65 @@ export function neatoMaxIter(maxiter: string | undefined, mode: number, n: numbe
329
336
  return mode === MODE_SGD ? SGD_ITERATIONS : 100 * n;
330
337
  }
331
338
 
332
- /** Run stress majorization and write positions back. @see lib/neatogen/neatoinit.c:majorization */
333
- export function runMajorization(g: Graph, mode: number, model: number): void {
334
- void model; // only MODEL_SHORTPATH distances are exercised by the suite
335
- const nodeList = assignNodeIds(g);
339
+ /** `late_double(g, "levelsgap", 0.0, -DBL_MAX)`. @see lib/neatogen/neatoinit.c:majorization */
340
+ const levelsGapOf = (g: Graph): number =>
341
+ lateDouble(g.root.attrs.get('levelsgap'), 0.0, -Number.MAX_VALUE);
342
+
343
+ /** What `majorization` was called with, plus the options derived from it. */
344
+ interface MajorizationCtx {
345
+ readonly g: Graph;
346
+ readonly mode: number;
347
+ readonly model: number;
348
+ readonly haveLen: boolean;
349
+ /** `opt_*` bits from `checkExp` and `checkStart`. */
350
+ readonly opts: number;
351
+ }
352
+
353
+ /**
354
+ * The solver step of `majorization`: `stress_majorization_with_hierarchy` for
355
+ * MODE_HIER, else the kD_mkernel (MODE_IPSEP without constraints runs it too).
356
+ * Returns C's `rv`; the coordinates are left in `dCoords`.
357
+ * @see lib/neatogen/neatoinit.c:majorization
358
+ */
359
+ function solveStress(
360
+ c: MajorizationCtx, vtx: DirectedVtxData[], dCoords: Float64Array[], nodeList: Node[],
361
+ ): number {
362
+ const { g, mode, model, haveLen, opts } = c;
336
363
  const n = nodeList.length;
337
- const haveLen = graphHasLen(g);
338
- const vtx = makeGraphDataC(g, nodeList, haveLen, mode === MODE_HIER || mode === MODE_IPSEP);
339
- checkStart(g); // C: srand48(seed) immediately before initLayout draws
340
364
  const maxi = neatoMaxIter(g.attrs.get('maxiter'), mode, n);
341
365
  const epsilon = lateDouble(g.attrs.get('epsilon'), 1e-4, 0); // DFLT_TOLERANCE
366
+ if (mode === MODE_HIER) {
367
+ return stressMajorizationWithHierarchy(vtx, n, dCoords, nodeList, {
368
+ dim: DFLT_DIM, opts, model, maxi, epsilon, levelsGap: levelsGapOf(g),
369
+ });
370
+ }
342
371
  // C ipsep (stress_majorization_cola, constrained_majorization_ipsep.c:150) always uses
343
372
  // compute_apsp_packed; kD_mkernel uses the weighted APSP iff graph->ewgts (stress.c:855).
344
373
  const weighted = haveLen && mode !== MODE_IPSEP;
345
374
  const Dij = weighted ? computeWeightedApspPacked(vtx, n) : computeApspPacked(vtx, n);
346
- const dCoords = [new Float64Array(n), new Float64Array(n)];
347
- stressMajorizationKDMkernel(Dij, n, dCoords, nodeList, {
348
- dim: DFLT_DIM, opts: OPT_EXP_FLAG, maxi, epsilon,
375
+ return stressMajorizationKDMkernel(Dij, n, dCoords, nodeList, {
376
+ dim: DFLT_DIM, opts, maxi, epsilon,
349
377
  });
350
- for (let i = 0; i < n; i++) {
351
- nodeList[i]!.info.pos = [dCoords[0]![i]!, dCoords[1]![i]!];
352
- }
353
378
  }
354
379
 
355
- /** Any edge carries a len attr (C: agattr_text(g, AGEDGE, "len")). */
356
- export function graphHasLen(g: Graph): boolean {
357
- for (const e of g.edges) {
358
- if (e.attrs.has('len')) return true;
380
+ /** Run stress majorization and write positions back. @see lib/neatogen/neatoinit.c:majorization */
381
+ export function runMajorization(g: Graph, mode: number, model: number): void {
382
+ const nodeList = assignNodeIds(g);
383
+ const n = nodeList.length;
384
+ const haveLen = graphHasLen(g);
385
+ // C: srand48(seed) immediately before initLayout draws; hier starts from self
386
+ const init = checkStart(g, n, mode === MODE_HIER ? INIT_SELF : INIT_RANDOM);
387
+ const opts = init === INIT_SELF ? OPT_EXP_FLAG | STRESS_OPT_SMART_INIT : OPT_EXP_FLAG;
388
+ const haveDir = mode === MODE_HIER || mode === MODE_IPSEP;
389
+ const vtx = makeGraphDataC(g, nodeList, haveLen, haveDir, mode);
390
+ const dCoords = [new Float64Array(n), new Float64Array(n)];
391
+ if (solveStress({ g, mode, model, haveLen, opts }, vtx, dCoords, nodeList) < 0) {
392
+ agerr('prev', 'layout aborted');
393
+ return;
359
394
  }
360
- return false;
361
- }
362
-
363
- /**
364
- * C makeGraphData's vtx_data: per node out-edges then in-edges (agfstedge order), self at
365
- * index 0, duplicate neighbours merged (len keeps the max). ewgts: lens (default 1.0) if any
366
- * len attr, else 1.0 under haveDir (hier/ipsep), else absent (C NULL). eweights (haveWt)
367
- * and edists are not built: nothing on a supported path reads them.
368
- * @see lib/neatogen/neatoinit.c:makeGraphData
369
- * @see lib/neatogen/stuff.c:setEdgeLen
370
- */
371
- export function makeGraphDataC(
372
- g: Graph, nodeList: Node[], haveLen: boolean, haveDir: boolean,
373
- ): VtxData[] {
374
- const vtx: VtxData[] = nodeList.map((_, i) => ({
375
- nedges: 1, edges: [i], ewgts: haveLen || haveDir ? [0] : [],
376
- }));
377
- for (const np of nodeList) {
378
- const i = np.info.id!;
379
- const entry = vtx[i]!;
380
- const seen = new Map<number, number>();
381
- for (const ep of [...np.outEdges(g), ...np.inEdges(g)]) {
382
- if (ep.head === ep.tail) continue;
383
- const vp = ep.tail === np ? ep.head : ep.tail;
384
- const vid = vp.info.id!;
385
- const dist = haveLen ? lateDouble(ep.attrs.get('len'), 1.0, 0) : 1.0;
386
- const idx = seen.get(vid);
387
- if (idx !== undefined) {
388
- if (haveLen) entry.ewgts[idx] = Math.max(entry.ewgts[idx]!, dist);
389
- continue;
390
- }
391
- seen.set(vid, entry.nedges);
392
- entry.edges.push(vid);
393
- if (haveLen || haveDir) entry.ewgts.push(dist); // C neatoinit.c:827: haveDir 1.0
394
- entry.nedges++;
395
- }
395
+ for (let i = 0; i < n; i++) {
396
+ nodeList[i]!.info.pos = [dCoords[0]![i]!, dCoords[1]![i]!];
396
397
  }
397
- return vtx;
398
398
  }
399
399
 
400
400
  /** Dispatch to the layout solver for the mode. @see lib/neatogen/neatoinit.c:neatoLayout */
@@ -27,7 +27,7 @@ import { MODEL_MDS } from './init.js';
27
27
  import { solve } from './kk-solve.js';
28
28
  import { lenattr } from './edge-len.js';
29
29
  import { newPathState, shortestPath, mdsModel, type Neighbour } from './kk-paths.js';
30
- import { agerrLineOn } from '../../common/agerr.js';
30
+ import { agwarningf } from '../../common/agerr.js';
31
31
 
32
32
  /** `Ndim`: layout dimension (this port lays out in 2-D only). */
33
33
  const NDIM = 2;
@@ -290,7 +290,7 @@ function moveNode(st: KkState, m: number): void {
290
290
  function solveModel(g: Graph, st: KkState): void {
291
291
  for (let m = chooseNode(st); m >= 0; m = chooseNode(st)) moveNode(st, m);
292
292
  if (st.move === st.maxIter) {
293
- agerrLineOn('warn', `Max. iterations (${st.maxIter}) reached on graph ${g.name}`);
293
+ agwarningf(`Max. iterations (${st.maxIter}) reached on graph ${g.name}`);
294
294
  }
295
295
  }
296
296