@knowvah/dot-engine 2.6.11 → 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 (270) 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/async/render-async.d.ts.map +1 -1
  5. package/dist/common/agerr.d.ts +37 -0
  6. package/dist/common/agerr.d.ts.map +1 -0
  7. package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
  8. package/dist/common/color.d.ts.map +1 -1
  9. package/dist/common/crand.d.ts +0 -20
  10. package/dist/common/crand.d.ts.map +1 -1
  11. package/dist/common/emit-once.d.ts +6 -0
  12. package/dist/common/emit-once.d.ts.map +1 -0
  13. package/dist/common/graph-init.d.ts.map +1 -1
  14. package/dist/common/htmltable-attr-warn.d.ts.map +1 -1
  15. package/dist/common/htmltable-emit-fill.d.ts +29 -2
  16. package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
  17. package/dist/common/htmltable-emit-rules.d.ts.map +1 -1
  18. package/dist/common/htmltable-emit.d.ts.map +1 -1
  19. package/dist/common/htmltable-lex-expat.d.ts +1 -6
  20. package/dist/common/htmltable-lex-expat.d.ts.map +1 -1
  21. package/dist/common/htmltable-pos-runs.d.ts +7 -4
  22. package/dist/common/htmltable-pos-runs.d.ts.map +1 -1
  23. package/dist/common/htmltable.d.ts +1 -7
  24. package/dist/common/htmltable.d.ts.map +1 -1
  25. package/dist/common/libm-trig.d.ts +5 -0
  26. package/dist/common/libm-trig.d.ts.map +1 -0
  27. package/dist/common/multicolor.d.ts +21 -13
  28. package/dist/common/multicolor.d.ts.map +1 -1
  29. package/dist/common/poly-gencode.d.ts +1 -2
  30. package/dist/common/poly-gencode.d.ts.map +1 -1
  31. package/dist/common/poly-init.d.ts.map +1 -1
  32. package/dist/common/postproc.d.ts.map +1 -1
  33. package/dist/common/random.d.ts.map +1 -1
  34. package/dist/common/render-state.d.ts +8 -0
  35. package/dist/common/render-state.d.ts.map +1 -0
  36. package/dist/common/splines-clip.d.ts +7 -0
  37. package/dist/common/splines-clip.d.ts.map +1 -1
  38. package/dist/common/splines-geom.d.ts +8 -0
  39. package/dist/common/splines-geom.d.ts.map +1 -1
  40. package/dist/common/splines-routespl.d.ts.map +1 -1
  41. package/dist/common/textmeasure-lookup.d.ts.map +1 -1
  42. package/dist/common/textmeasure.d.ts.map +1 -1
  43. package/dist/gvc/anchor.d.ts.map +1 -1
  44. package/dist/gvc/context.d.ts +6 -0
  45. package/dist/gvc/context.d.ts.map +1 -1
  46. package/dist/gvc/device-cluster.d.ts +15 -0
  47. package/dist/gvc/device-cluster.d.ts.map +1 -1
  48. package/dist/gvc/device-labels.d.ts +14 -0
  49. package/dist/gvc/device-labels.d.ts.map +1 -1
  50. package/dist/gvc/device.d.ts.map +1 -1
  51. package/dist/gvc/first-layer.d.ts +9 -0
  52. package/dist/gvc/first-layer.d.ts.map +1 -0
  53. package/dist/gvc/gvrender-set-style.d.ts +28 -0
  54. package/dist/gvc/gvrender-set-style.d.ts.map +1 -0
  55. package/dist/gvc/usershape.d.ts +18 -0
  56. package/dist/gvc/usershape.d.ts.map +1 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +3169 -1296
  59. package/dist/index.js.map +4 -4
  60. package/dist/layout/dot/cluster.d.ts.map +1 -1
  61. package/dist/layout/dot/compound.d.ts.map +1 -1
  62. package/dist/layout/dot/conc.d.ts.map +1 -1
  63. package/dist/layout/dot/decomp.d.ts.map +1 -1
  64. package/dist/layout/dot/edge-route-clip.d.ts +32 -14
  65. package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
  66. package/dist/layout/dot/edge-route-routing.d.ts.map +1 -1
  67. package/dist/layout/dot/mincross-build.d.ts.map +1 -1
  68. package/dist/layout/dot/mincross-cross.d.ts.map +1 -1
  69. package/dist/layout/dot/rank.d.ts.map +1 -1
  70. package/dist/layout/dot/splines.d.ts.map +1 -1
  71. package/dist/layout/fdp/index.d.ts.map +1 -1
  72. package/dist/layout/fdp/tlayout-parms.d.ts.map +1 -1
  73. package/dist/layout/fdp/xlayout.d.ts.map +1 -1
  74. package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts +11 -0
  75. package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts.map +1 -0
  76. package/dist/layout/neato/__fixtures__/hier-imds.d.ts +36 -0
  77. package/dist/layout/neato/__fixtures__/hier-imds.d.ts.map +1 -0
  78. package/dist/layout/neato/__fixtures__/hier-majorization.d.ts +32 -0
  79. package/dist/layout/neato/__fixtures__/hier-majorization.d.ts.map +1 -0
  80. package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
  81. package/dist/layout/neato/compute-hierarchy.d.ts +24 -0
  82. package/dist/layout/neato/compute-hierarchy.d.ts.map +1 -0
  83. package/dist/layout/neato/conjgrad-mkernel.d.ts +20 -0
  84. package/dist/layout/neato/conjgrad-mkernel.d.ts.map +1 -0
  85. package/dist/layout/neato/conjgrad.d.ts +19 -0
  86. package/dist/layout/neato/conjgrad.d.ts.map +1 -1
  87. package/dist/layout/neato/constrained-majorization.d.ts +26 -0
  88. package/dist/layout/neato/constrained-majorization.d.ts.map +1 -0
  89. package/dist/layout/neato/dijkstra.d.ts +33 -16
  90. package/dist/layout/neato/dijkstra.d.ts.map +1 -1
  91. package/dist/layout/neato/edge-len.d.ts.map +1 -1
  92. package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
  93. package/dist/layout/neato/gts-triangle.d.ts +14 -0
  94. package/dist/layout/neato/gts-triangle.d.ts.map +1 -0
  95. package/dist/layout/neato/init.d.ts +6 -22
  96. package/dist/layout/neato/init.d.ts.map +1 -1
  97. package/dist/layout/neato/kk.d.ts.map +1 -1
  98. package/dist/layout/neato/kkutils.d.ts +20 -0
  99. package/dist/layout/neato/kkutils.d.ts.map +1 -0
  100. package/dist/layout/neato/make-graph-data.d.ts +43 -0
  101. package/dist/layout/neato/make-graph-data.d.ts.map +1 -0
  102. package/dist/layout/neato/matrix-ops.d.ts +46 -0
  103. package/dist/layout/neato/matrix-ops.d.ts.map +1 -1
  104. package/dist/layout/neato/multispline-router.d.ts.map +1 -1
  105. package/dist/layout/neato/multispline.d.ts.map +1 -1
  106. package/dist/layout/neato/opt-arrangement.d.ts +20 -0
  107. package/dist/layout/neato/opt-arrangement.d.ts.map +1 -0
  108. package/dist/layout/neato/poly.d.ts.map +1 -1
  109. package/dist/layout/neato/quad-prog-solve.d.ts +44 -0
  110. package/dist/layout/neato/quad-prog-solve.d.ts.map +1 -0
  111. package/dist/layout/neato/sgd.d.ts.map +1 -1
  112. package/dist/layout/neato/smart-ini-x.d.ts +9 -0
  113. package/dist/layout/neato/smart-ini-x.d.ts.map +1 -0
  114. package/dist/layout/neato/splines.d.ts +1 -1
  115. package/dist/layout/neato/splines.d.ts.map +1 -1
  116. package/dist/layout/neato/start.d.ts +4 -1
  117. package/dist/layout/neato/start.d.ts.map +1 -1
  118. package/dist/layout/neato/stress.d.ts +8 -0
  119. package/dist/layout/neato/stress.d.ts.map +1 -1
  120. package/dist/model/edge.d.ts +1 -1
  121. package/dist/model/edge.d.ts.map +1 -1
  122. package/dist/model/lazy-attrs.d.ts +10 -0
  123. package/dist/model/lazy-attrs.d.ts.map +1 -0
  124. package/dist/model/node.d.ts +1 -1
  125. package/dist/model/node.d.ts.map +1 -1
  126. package/dist/ortho/index.d.ts.map +1 -1
  127. package/dist/parser/index.d.ts.map +1 -1
  128. package/dist/render/color-resolve.d.ts +49 -1
  129. package/dist/render/color-resolve.d.ts.map +1 -1
  130. package/dist/render/dot/edge-draw.d.ts +1 -1
  131. package/dist/render/dot/edge-draw.d.ts.map +1 -1
  132. package/dist/render/public.d.ts.map +1 -1
  133. package/dist/render/svg-edge-split.d.ts +2 -0
  134. package/dist/render/svg-edge-split.d.ts.map +1 -1
  135. package/dist/render/svg-multicolor.d.ts +2 -2
  136. package/dist/render/svg-multicolor.d.ts.map +1 -1
  137. package/dist/render/xdot-public.d.ts.map +1 -1
  138. package/dist/render.js +2799 -927
  139. package/dist/render.js.map +4 -4
  140. package/package.json +1 -1
  141. package/src/async/render-async.ts +6 -0
  142. package/src/common/agerr.ts +96 -0
  143. package/src/common/arrows-shapes-poly.ts +19 -5
  144. package/src/common/color.ts +38 -10
  145. package/src/common/crand.ts +8 -2
  146. package/src/common/emit-once.ts +27 -0
  147. package/src/common/graph-init.ts +2 -1
  148. package/src/common/htmltable-attr-warn.ts +3 -4
  149. package/src/common/htmltable-emit-fill.ts +104 -11
  150. package/src/common/htmltable-emit-rules.ts +2 -0
  151. package/src/common/htmltable-emit.ts +4 -3
  152. package/src/common/htmltable-lex-expat.ts +96 -23
  153. package/src/common/htmltable-pos-runs.ts +11 -6
  154. package/src/common/htmltable.ts +28 -117
  155. package/src/common/libm-trig.ts +43 -0
  156. package/src/common/multicolor.ts +88 -24
  157. package/src/common/poly-gencode.ts +30 -10
  158. package/src/common/poly-init.ts +20 -1
  159. package/src/common/postproc.ts +4 -1
  160. package/src/common/random.ts +11 -1
  161. package/src/common/render-state.ts +36 -0
  162. package/src/common/splines-clip.ts +15 -12
  163. package/src/common/splines-geom.ts +8 -4
  164. package/src/common/splines-routespl.ts +2 -1
  165. package/src/common/textmeasure-lookup.ts +9 -2
  166. package/src/common/textmeasure.ts +45 -2
  167. package/src/gvc/anchor.ts +1 -12
  168. package/src/gvc/context.ts +8 -0
  169. package/src/gvc/device-cluster.ts +93 -10
  170. package/src/gvc/device-labels.ts +27 -0
  171. package/src/gvc/device.ts +34 -8
  172. package/src/gvc/first-layer.ts +26 -0
  173. package/src/gvc/gvrender-set-style.ts +114 -0
  174. package/src/gvc/usershape.ts +26 -0
  175. package/src/index.ts +3 -0
  176. package/src/layout/dot/cluster.ts +2 -1
  177. package/src/layout/dot/compound.ts +3 -4
  178. package/src/layout/dot/conc.ts +6 -5
  179. package/src/layout/dot/decomp.ts +3 -0
  180. package/src/layout/dot/edge-route-clip.ts +46 -28
  181. package/src/layout/dot/edge-route-routing.ts +11 -3
  182. package/src/layout/dot/fastgr.ts +2 -2
  183. package/src/layout/dot/mincross-build.ts +2 -0
  184. package/src/layout/dot/mincross-cross.ts +2 -0
  185. package/src/layout/dot/rank.ts +2 -0
  186. package/src/layout/dot/splines.ts +2 -1
  187. package/src/layout/fdp/index.ts +2 -1
  188. package/src/layout/fdp/tlayout-parms.ts +9 -2
  189. package/src/layout/fdp/xlayout.ts +10 -2
  190. package/src/layout/neato/__fixtures__/dijkstra-f.ts +17 -0
  191. package/src/layout/neato/__fixtures__/hier/c1554.gv +33 -0
  192. package/src/layout/neato/__fixtures__/hier/c1554.plain +26 -0
  193. package/src/layout/neato/__fixtures__/hier/c1554.xdot +161 -0
  194. package/src/layout/neato/__fixtures__/hier/cases/cycle.gv +1 -0
  195. package/src/layout/neato/__fixtures__/hier/cases/cycle.plain +10 -0
  196. package/src/layout/neato/__fixtures__/hier/cases/cycle.stderr +0 -0
  197. package/src/layout/neato/__fixtures__/hier/cases/dirnone.gv +1 -0
  198. package/src/layout/neato/__fixtures__/hier/cases/dirnone.plain +11 -0
  199. package/src/layout/neato/__fixtures__/hier/cases/dirnone.stderr +0 -0
  200. package/src/layout/neato/__fixtures__/hier/cases/epsilon.gv +1 -0
  201. package/src/layout/neato/__fixtures__/hier/cases/epsilon.plain +13 -0
  202. package/src/layout/neato/__fixtures__/hier/cases/epsilon.stderr +0 -0
  203. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.gv +1 -0
  204. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.plain +8 -0
  205. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.stderr +0 -0
  206. package/src/layout/neato/__fixtures__/hier/cases/mds.gv +1 -0
  207. package/src/layout/neato/__fixtures__/hier/cases/mds.plain +10 -0
  208. package/src/layout/neato/__fixtures__/hier/cases/mds.stderr +0 -0
  209. package/src/layout/neato/__fixtures__/hier/cases/multiedge.gv +1 -0
  210. package/src/layout/neato/__fixtures__/hier/cases/multiedge.plain +10 -0
  211. package/src/layout/neato/__fixtures__/hier/cases/multiedge.stderr +0 -0
  212. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.gv +1 -0
  213. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.plain +8 -0
  214. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.stderr +0 -0
  215. package/src/layout/neato/__fixtures__/hier/cases/startrandom.gv +1 -0
  216. package/src/layout/neato/__fixtures__/hier/cases/startrandom.plain +10 -0
  217. package/src/layout/neato/__fixtures__/hier/cases/startrandom.stderr +0 -0
  218. package/src/layout/neato/__fixtures__/hier/cases/startregular.gv +1 -0
  219. package/src/layout/neato/__fixtures__/hier/cases/startregular.plain +10 -0
  220. package/src/layout/neato/__fixtures__/hier/cases/startregular.stderr +0 -0
  221. package/src/layout/neato/__fixtures__/hier/cases/tdist.gv +1 -0
  222. package/src/layout/neato/__fixtures__/hier/cases/tdist.plain +13 -0
  223. package/src/layout/neato/__fixtures__/hier/cases/tdist.stderr +0 -0
  224. package/src/layout/neato/__fixtures__/hier/cases/undirected.gv +1 -0
  225. package/src/layout/neato/__fixtures__/hier/cases/undirected.plain +10 -0
  226. package/src/layout/neato/__fixtures__/hier/cases/undirected.stderr +0 -0
  227. package/src/layout/neato/__fixtures__/hier/hier-probe.gv +1 -0
  228. package/src/layout/neato/__fixtures__/hier/hier-probe.plain +16 -0
  229. package/src/layout/neato/__fixtures__/hier/hier-probe.xdot +62 -0
  230. package/src/layout/neato/__fixtures__/hier-imds.ts +151 -0
  231. package/src/layout/neato/__fixtures__/hier-majorization.ts +145 -0
  232. package/src/layout/neato/cdt-surface.ts +12 -22
  233. package/src/layout/neato/compute-hierarchy.ts +90 -0
  234. package/src/layout/neato/conjgrad-mkernel.ts +141 -0
  235. package/src/layout/neato/conjgrad.ts +208 -0
  236. package/src/layout/neato/constrained-majorization.ts +479 -0
  237. package/src/layout/neato/dijkstra.ts +218 -92
  238. package/src/layout/neato/edge-len.ts +2 -1
  239. package/src/layout/neato/fdp-adjust.ts +3 -2
  240. package/src/layout/neato/gts-triangle.ts +42 -0
  241. package/src/layout/neato/init.ts +68 -68
  242. package/src/layout/neato/kk.ts +2 -1
  243. package/src/layout/neato/kkutils.ts +96 -0
  244. package/src/layout/neato/make-graph-data.ts +166 -0
  245. package/src/layout/neato/matrix-ops.ts +138 -0
  246. package/src/layout/neato/multispline-router.ts +3 -3
  247. package/src/layout/neato/multispline.ts +3 -2
  248. package/src/layout/neato/opt-arrangement.ts +86 -0
  249. package/src/layout/neato/poly.ts +3 -2
  250. package/src/layout/neato/quad-prog-solve.ts +489 -0
  251. package/src/layout/neato/sgd.ts +5 -2
  252. package/src/layout/neato/smart-ini-x.ts +503 -0
  253. package/src/layout/neato/splines.ts +4 -4
  254. package/src/layout/neato/start.ts +19 -14
  255. package/src/layout/neato/stress.ts +28 -0
  256. package/src/model/edge.ts +3 -2
  257. package/src/model/lazy-attrs.ts +36 -0
  258. package/src/model/node.ts +3 -2
  259. package/src/ortho/index.ts +2 -1
  260. package/src/parser/dot.js +4 -1
  261. package/src/parser/dot.pegjs +5 -1
  262. package/src/parser/index.ts +4 -3
  263. package/src/render/color-resolve.ts +193 -4
  264. package/src/render/dot/edge-draw.ts +6 -3
  265. package/src/render/dot.ts +1 -1
  266. package/src/render/public.ts +2 -0
  267. package/src/render/svg-edge-split.ts +6 -4
  268. package/src/render/svg-multicolor.ts +4 -3
  269. package/src/render/svg-parallel-edge.ts +2 -2
  270. package/src/render/xdot-public.ts +2 -0
@@ -0,0 +1,479 @@
1
+ // SPDX-License-Identifier: EPL-2.0
2
+
3
+ /**
4
+ * Dense k-D stress majorization under hierarchy constraints (`mode=hier`):
5
+ * partitions the nodes into levels (compute_y_coords + compute_hierarchy),
6
+ * then runs the majorization loop, solving the y axis by constrained
7
+ * majorization (quadratic programming over the levels) and every other axis
8
+ * by conjugate gradient.
9
+ *
10
+ * `Verbose` timing and `fprintf` lines are not ported (the port has no Verbose).
11
+ * The C function takes nine parameters; the trailing scalars travel as
12
+ * {@link HierarchyOpts} to stay within the five-parameter limit.
13
+ *
14
+ * @see lib/neatogen/constrained_majorization.c
15
+ */
16
+
17
+ import { RenderError } from '../../errors.js';
18
+ import type { Node } from '../../model/node.js';
19
+ import { conjugateGradientMkernelFused, rightMultWithVectorFFFused } from './conjgrad-mkernel.js';
20
+ import { computeHierarchy, type Hierarchy } from './compute-hierarchy.js';
21
+ import type { VtxData } from './dijkstra.js';
22
+ import { MODEL_CIRCUIT, MODEL_MDS, MODEL_SUBSET } from './init.js';
23
+ import {
24
+ fma, invertSqrtVec, invertVec, orthog1, sqrtVecF, squareVec, vectorsInnerProductF,
25
+ } from './matrix-ops.js';
26
+ import type { DirectedVtxData } from './opt-arrangement.js';
27
+ import { computeYCoords } from './opt-arrangement.js';
28
+ import {
29
+ constrainedMajorizationNewWithGaps, initConstrainedMajorization, type CMajEnv,
30
+ } from './quad-prog-solve.js';
31
+ import { imdsGivenDim } from './smart-ini-x.js';
32
+ import {
33
+ computeApspPacked, computeWeightedApspPacked, initLayout, stressMajorizationKDMkernel,
34
+ } from './stress-kernel.js';
35
+ import { mdsModel, OPT_SMART_INIT, TOLERANCE_CG } from './stress.js';
36
+
37
+ const fr = Math.fround;
38
+
39
+ /** `#define localConstrMajorIterations` @see lib/neatogen/constrained_majorization.c */
40
+ const LOCAL_CONSTR_MAJOR_ITERATIONS = 15;
41
+ /** `#define levels_sep_tol` @see lib/neatogen/constrained_majorization.c */
42
+ const LEVELS_SEP_TOL = 1e-1;
43
+ /** `abs_tol`: gaps smaller than this are noise. @see lib/neatogen/constrained_majorization.c */
44
+ const ABS_TOL = 1e-2;
45
+ /** Iteration cap of the (dim-1)-D layout that seeds dim > 2. */
46
+ const SUBSPACE_ITERATIONS = 15;
47
+ /** `FLT_MAX` @see float.h */
48
+ const FLT_MAX = 3.4028234663852886e38;
49
+ /** `DBL_MAX` @see float.h */
50
+ const DBL_MAX = Number.MAX_VALUE;
51
+ /** Level count below which there is no hierarchy to impose. */
52
+ const MIN_LEVELS = 1;
53
+ /** The y axis, solved by quadratic programming. */
54
+ const Y_AXIS = 1;
55
+
56
+ /** The scalar parameters of `stress_majorization_with_hierarchy`. */
57
+ export interface HierarchyOpts {
58
+ /** Dimensionality of the layout. */
59
+ dim: number;
60
+ /** `opt_*` bits. */
61
+ opts: number;
62
+ /** `MODEL_*` (neato.h values). */
63
+ model: number;
64
+ /** Maximum number of iterations. */
65
+ maxi: number;
66
+ /** Minimum separation of consecutive levels (`levelsgap`). */
67
+ levelsGap: number;
68
+ /** The `Epsilon` global: convergence tolerance (`epsilon` attribute). */
69
+ epsilon: number;
70
+ }
71
+
72
+ /** The scalar parameters of `stress_majorization_kD_mkernel`. */
73
+ interface KdOpts {
74
+ dim: number;
75
+ opts: number;
76
+ model: number;
77
+ maxi: number;
78
+ epsilon: number;
79
+ }
80
+
81
+ /** Throw where C runs a distance model or initialisation this port does not have. */
82
+ function unsupported(what: string): never {
83
+ throw new RenderError(`hierarchical stress majorization: ${what} is not supported yet`, 'UNSUPPORTED_FEATURE');
84
+ }
85
+
86
+ /**
87
+ * Distance model dispatch shared by both drivers: the MDS model is ported
88
+ * (`mdsModel`, `null` when the graph has no lengths); the subset and circuit
89
+ * models need C helpers that are not ported (AD-6, stop 12) and stay loud.
90
+ * @see lib/neatogen/stress.c:stress_majorization_kD_mkernel
91
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
92
+ */
93
+ function modelDistances(graph: VtxData[], n: number, model: number): Float32Array | null {
94
+ if (model === MODEL_SUBSET) unsupported('the subset distance model');
95
+ if (model === MODEL_CIRCUIT) unsupported('the circuit distance model');
96
+ return model === MODEL_MDS ? mdsModel(graph, n) : null;
97
+ }
98
+
99
+ /**
100
+ * `stress_majorization_kD_mkernel`: the distance matrix per model, then the
101
+ * ported kernel. C takes `smart_ini` through `sparse_stress_subspace_majorization_kD`,
102
+ * which is not ported.
103
+ * @see lib/neatogen/stress.c:stress_majorization_kD_mkernel
104
+ */
105
+ function unconstrainedMajorization(
106
+ graph: VtxData[], n: number, dCoords: Float64Array[], nodes: Node[], o: KdOpts,
107
+ ): number {
108
+ if (o.maxi < 0) return 0;
109
+ const weighted = (graph[0]?.ewgts.length ?? 0) > 0;
110
+ const Dij = modelDistances(graph, n, o.model)
111
+ ?? (weighted ? computeWeightedApspPacked(graph, n) : computeApspPacked(graph, n));
112
+ if ((o.opts & OPT_SMART_INIT) !== 0 && n > 1) unsupported('the smart initial layout');
113
+ return stressMajorizationKDMkernel(Dij, n, dCoords, nodes, {
114
+ dim: o.dim, opts: o.opts, maxi: o.maxi, epsilon: o.epsilon,
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Does any edge carry a direction? (`edists` absent, or all zero, means none.)
120
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
121
+ */
122
+ function hasDirectionality(graph: DirectedVtxData[], n: number): boolean {
123
+ if (!graph[0]?.edists) return false;
124
+ let directionalityExist = false;
125
+ for (let i = 0; i < n; i++) {
126
+ const edists = graph[i]!.edists;
127
+ for (let j = 1; j < graph[i]!.nedges; j++) {
128
+ directionalityExist ||= edists![j] !== 0;
129
+ }
130
+ }
131
+ return directionalityExist;
132
+ }
133
+
134
+ /**
135
+ * Push the levels apart: each level moves up by the shortfall to `levelsGap`.
136
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
137
+ */
138
+ function separateLevels(y: Float64Array, h: Hierarchy, n: number, levelsGap: number): void {
139
+ const { ordering, levels, numLevels } = h;
140
+ let displacement = 0;
141
+ for (let i = 0; i < numLevels; i++) {
142
+ const shortfall = levelsGap - (y[ordering[levels[i]!]!]! + displacement - y[ordering[levels[i]! - 1]!]!);
143
+ displacement += 0 > shortfall ? 0 : shortfall; // C MAX(a, b): `a > b ? a : b`
144
+ const stop = i < numLevels - 1 ? levels[i + 1]! : n;
145
+ for (let j = levels[i]!; j < stop; j++) y[ordering[j]!]! += displacement;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * The smart-init layering: y from `compute_y_coords`, levels from it, the
151
+ * levels separated, then x from `IMDS_given_dim`. Returns the C return value
152
+ * when the function ends here, else the hierarchy.
153
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
154
+ */
155
+ function smartInitLayers(
156
+ graph: DirectedVtxData[], n: number, dCoords: Float64Array[], nodes: Node[], o: HierarchyOpts,
157
+ ): Hierarchy | number {
158
+ const { dim } = o;
159
+ if (dim > 2) {
160
+ // the dim==2 case is handled below
161
+ const sub = { dim: dim - 1, opts: o.opts, model: o.model, maxi: SUBSPACE_ITERATIONS, epsilon: o.epsilon };
162
+ if (unconstrainedMajorization(graph, n, dCoords.slice(1), nodes, sub) < 0) return -1;
163
+ // now copy the y-axis into the (dim-1)-axis
164
+ dCoords[dim - 1]!.set(dCoords[1]!.subarray(0, n));
165
+ }
166
+ const x = dCoords[0]!;
167
+ const y = dCoords[1]!;
168
+ if (computeYCoords(graph, n, y, n)) return -1;
169
+ const h = computeHierarchy(graph, n, ABS_TOL, LEVELS_SEP_TOL, y);
170
+ if (h === null) return -1;
171
+ if (h.numLevels < MIN_LEVELS) {
172
+ // no hierarchy found, use faster algorithm
173
+ return unconstrainedMajorization(graph, n, dCoords, nodes, o);
174
+ }
175
+ if (o.levelsGap > 0) separateLevels(y, h, n, o.levelsGap); // ensure the initial layout separates levels
176
+ if (dim === 2 && imdsGivenDim(graph, n, y, x, o.epsilon)) return -1;
177
+ return h;
178
+ }
179
+
180
+ /**
181
+ * Layering without smart init: the ordinary initial layout, levels from
182
+ * `compute_y_coords` (which draws from the libc `rand()` stream).
183
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
184
+ */
185
+ function plainInitLayers(
186
+ graph: DirectedVtxData[], n: number, dCoords: Float64Array[], nodes: Node[], dim: number,
187
+ ): Hierarchy | number {
188
+ initLayout(n, dim, dCoords, nodes);
189
+ const h = computeHierarchy(graph, n, ABS_TOL, LEVELS_SEP_TOL, null);
190
+ return h === null ? -1 : h;
191
+ }
192
+
193
+ /**
194
+ * Euclidean distance between nodes `i` and `j` in `dim` dimensions.
195
+ * @see lib/neatogen/kkutils.c:distance_kD
196
+ */
197
+ function distanceKD(coords: Float64Array[], dim: number, i: number, j: number): number {
198
+ let sum = 0;
199
+ for (let k = 0; k < dim; k++) {
200
+ const d = coords[k]![i]! - coords[k]![j]!;
201
+ sum = fma(d, d, sum);
202
+ }
203
+ return Math.sqrt(sum);
204
+ }
205
+
206
+ /**
207
+ * For numerical stability, scale the layout down (no jiggling: it might
208
+ * conflict with the constraints).
209
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
210
+ */
211
+ function scaleDownLayout(dCoords: Float64Array[], n: number, dim: number): void {
212
+ let max = 1;
213
+ for (let i = 0; i < dim; i++) {
214
+ for (let j = 0; j < n; j++) {
215
+ const a = Math.abs(dCoords[i]![j]!);
216
+ if (a > max) max = a; // fmax ignores a NaN operand, as `>` does
217
+ }
218
+ }
219
+ for (let i = 0; i < dim; i++) {
220
+ for (let j = 0; j < n; j++) dCoords[i]![j]! *= 10 / max;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Scale the distances so the layout's average stretch is one.
226
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
227
+ */
228
+ function scaleDistances(Dij: Float32Array, dCoords: Float64Array[], n: number, dim: number): void {
229
+ const length = n + Math.trunc((n * (n - 1)) / 2);
230
+ const sum1 = Math.trunc((n * (n - 1)) / 2);
231
+ let sum2 = 0;
232
+ for (let count = 0, i = 0; i < n - 1; i++) {
233
+ count++; // skip self distance
234
+ for (let j = i + 1; j < n; j++, count++) {
235
+ sum2 += distanceKD(dCoords, dim, i, j) / Dij[count]!;
236
+ }
237
+ }
238
+ const scaleRatio = fr(sum2 / sum1);
239
+ for (let i = 0; i < length; i++) Dij[i] = Dij[i]! * scaleRatio;
240
+ }
241
+
242
+ /**
243
+ * Centre every axis; y is not centred but translated so that `y[0] = 0`.
244
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
245
+ */
246
+ function centerLayout(dCoords: Float64Array[], n: number, dim: number): void {
247
+ for (let i = 0; i < dim; i++) orthog1(n, dCoords[i]!);
248
+ const y0 = dCoords[Y_AXIS]![0]!;
249
+ for (let i = 0; i < n; i++) dCoords[Y_AXIS]![i]! -= y0;
250
+ }
251
+
252
+ /**
253
+ * The row sums of the off-diagonal entries of `lap2`, as the negated degrees.
254
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
255
+ */
256
+ function negatedDegrees(lap2: Float32Array, n: number): Float64Array {
257
+ const degrees = new Float64Array(n);
258
+ for (let i = 0, count = 0; i < n - 1; i++) {
259
+ let degree = 0;
260
+ count++; // skip main diag entry
261
+ for (let j = 1; j < n - i; j++, count++) {
262
+ const val = lap2[count]!;
263
+ degree += val;
264
+ degrees[i + j]! -= val;
265
+ }
266
+ degrees[i]! -= degree;
267
+ }
268
+ return degrees;
269
+ }
270
+
271
+ /**
272
+ * Turn the packed distances into the negated Laplacian `lap2`, in place:
273
+ * off-diagonals `1/d^2`, diagonals minus the row sums.
274
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
275
+ */
276
+ function buildLap2(lap2: Float32Array, n: number): void {
277
+ const lapLength = n + Math.trunc((n * (n - 1)) / 2);
278
+ squareVec(lapLength, lap2);
279
+ invertVec(lapLength, lap2); // off-diagonal entries
280
+ const degrees = negatedDegrees(lap2, n);
281
+ for (let step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
282
+ lap2[count] = degrees[i]!;
283
+ }
284
+ }
285
+
286
+ /** Working storage of the majorization loop. */
287
+ interface LoopState {
288
+ n: number;
289
+ dim: number;
290
+ lap1: Float32Array;
291
+ lap2: Float32Array;
292
+ coords: Float32Array[];
293
+ b: Float32Array[];
294
+ tmpCoords: Float32Array;
295
+ distAccumulator: Float32Array;
296
+ degrees: Float64Array;
297
+ }
298
+
299
+ /**
300
+ * Squared distances between node `i` and `i+1..n-1`, summed over the axes,
301
+ * then inverted to `1/d_ij` (zero where undefined or overflowing).
302
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
303
+ */
304
+ function inverseDistances(st: LoopState, i: number, len: number): void {
305
+ const { coords, tmpCoords, distAccumulator } = st;
306
+ distAccumulator.fill(0, 0, st.n);
307
+ for (let k = 0; k < st.dim; k++) {
308
+ const ck = coords[k]!;
309
+ for (let j = 0; j < len; j++) tmpCoords[j] = ck[i]! - ck[i + 1 + j]!;
310
+ for (let j = 0; j < len; j++) tmpCoords[j] = tmpCoords[j]! * tmpCoords[j]!;
311
+ for (let j = 0; j < len; j++) distAccumulator[j] = tmpCoords[j]! + distAccumulator[j]!;
312
+ }
313
+ invertSqrtVec(len, distAccumulator); // convert to 1/d_{ij}
314
+ for (let j = 0; j < len; j++) {
315
+ // detect overflows
316
+ if (distAccumulator[j]! >= FLT_MAX || distAccumulator[j]! < 0) distAccumulator[j] = 0;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * The Laplacian of `1/(d_ij*|p_i-p_j|)` at the current coordinates.
322
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
323
+ */
324
+ function buildLap1(st: LoopState): void {
325
+ const { n, lap1, lap2, degrees, distAccumulator } = st;
326
+ degrees.fill(0);
327
+ sqrtVecF(n + Math.trunc((n * (n - 1)) / 2), lap2, lap1);
328
+ let count = 0;
329
+ for (let i = 0; i < n - 1; i++) {
330
+ const len = n - i - 1;
331
+ inverseDistances(st, i, len);
332
+ count++; // save place for the main diagonal entry
333
+ let degree = 0;
334
+ for (let j = 0; j < len; j++, count++) {
335
+ const val = fr(lap1[count]! * distAccumulator[j]!);
336
+ lap1[count] = val;
337
+ degree += val;
338
+ degrees[i + j + 1]! -= val;
339
+ }
340
+ degrees[i]! -= degree;
341
+ }
342
+ for (let step = n, count2 = 0, i = 0; i < n; i++, count2 += step, step--) {
343
+ lap1[count2] = degrees[i]!;
344
+ }
345
+ }
346
+
347
+ /**
348
+ * The stress at the current coordinates; also leaves `b[k] = lap1*coords[k]`.
349
+ * The Laplacians are negated, so the second sum is subtracted.
350
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
351
+ */
352
+ function computeNewStress(st: LoopState, constantTerm: number): number {
353
+ const { n, dim, lap1, lap2, coords, b, tmpCoords } = st;
354
+ for (let k = 0; k < dim; k++) rightMultWithVectorFFFused(lap1, n, coords[k]!, b[k]!);
355
+ let newStress = 0;
356
+ for (let k = 0; k < dim; k++) newStress += vectorsInnerProductF(n, coords[k]!, b[k]!);
357
+ newStress *= 2;
358
+ newStress += constantTerm; // only after mult by 2
359
+ for (let k = 0; k < dim; k++) {
360
+ rightMultWithVectorFFFused(lap2, n, coords[k]!, tmpCoords);
361
+ newStress -= vectorsInnerProductF(n, coords[k]!, tmpCoords);
362
+ }
363
+ return newStress;
364
+ }
365
+
366
+ /** Everything the loop needs besides its working storage. */
367
+ interface SolveCtx {
368
+ cMajEnv: CMajEnv;
369
+ levelsGap: number;
370
+ maxi: number;
371
+ epsilon: number;
372
+ }
373
+
374
+ /**
375
+ * Optimise `trace(X'LX)+X'B` per axis: constrained majorization in y, conjugate
376
+ * gradient elsewhere. Returns true when conjugate gradient fails.
377
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
378
+ */
379
+ function solveAxes(st: LoopState, ctx: SolveCtx): boolean {
380
+ for (let k = 0; k < st.dim; k++) {
381
+ if (k === Y_AXIS) {
382
+ // use quad solver in the y-dimension
383
+ constrainedMajorizationNewWithGaps(
384
+ ctx.cMajEnv, st.b[k]!, st.coords, k, LOCAL_CONSTR_MAJOR_ITERATIONS, ctx.levelsGap,
385
+ );
386
+ } else if (conjugateGradientMkernelFused(st.lap2, st.coords[k]!, st.b[k]!, st.n, { tol: TOLERANCE_CG, maxIterations: st.n })) {
387
+ // use conjugate gradient for all dimensions except y
388
+ return true;
389
+ }
390
+ }
391
+ return false;
392
+ }
393
+
394
+ /**
395
+ * The majorization loop. Returns the iteration count, or -1 when conjugate
396
+ * gradient failed (coordinates are then not written back).
397
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
398
+ */
399
+ function majorize(st: LoopState, ctx: SolveCtx, dCoords: Float64Array[]): number {
400
+ const constantTerm = Math.trunc((st.n * (st.n - 1)) / 2);
401
+ let oldStress = DBL_MAX; // at least one iteration
402
+ let iterations = 0;
403
+ for (let converged = false; iterations < ctx.maxi && !converged; iterations++) {
404
+ buildLap1(st);
405
+ const newStress = computeNewStress(st, constantTerm);
406
+ // check for convergence
407
+ converged = Math.abs(newStress - oldStress) / Math.abs(oldStress + 1e-10) < ctx.epsilon;
408
+ // in first iteration we allowed stress increase, which might result by imposing constraints
409
+ converged ||= iterations > 1 && newStress > oldStress;
410
+ oldStress = newStress;
411
+ if (solveAxes(st, ctx)) return -1;
412
+ }
413
+ for (let i = 0; i < st.dim; i++) {
414
+ for (let j = 0; j < st.n; j++) dCoords[i]![j] = st.coords[i]![j]!;
415
+ }
416
+ return iterations;
417
+ }
418
+
419
+ /**
420
+ * The `float` working copy of the layout.
421
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
422
+ */
423
+ function toFloatCoords(dCoords: Float64Array[], n: number, dim: number): Float32Array[] {
424
+ const coords: Float32Array[] = [];
425
+ for (let i = 0; i < dim; i++) coords.push(Float32Array.from(dCoords[i]!.subarray(0, n)));
426
+ return coords;
427
+ }
428
+
429
+ /**
430
+ * Fresh working storage for the loop.
431
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
432
+ */
433
+ function newLoopState(lap2: Float32Array, coords: Float32Array[], n: number): LoopState {
434
+ const dim = coords.length;
435
+ return {
436
+ n, dim, lap2, coords,
437
+ lap1: new Float32Array(n + Math.trunc((n * (n - 1)) / 2)),
438
+ b: Array.from({ length: dim }, () => new Float32Array(n)),
439
+ tmpCoords: new Float32Array(n),
440
+ distAccumulator: new Float32Array(n),
441
+ degrees: new Float64Array(n),
442
+ };
443
+ }
444
+
445
+ /**
446
+ * Stress majorization of the layout under hierarchy constraints. Writes the
447
+ * layout to `dCoords` and returns the iteration count (C's return value; -1
448
+ * on failure). Graphs without directed edges, and layouts that find no
449
+ * hierarchy, go to the unconstrained stress majorization.
450
+ * @see lib/neatogen/constrained_majorization.c:stress_majorization_with_hierarchy
451
+ */
452
+ export function stressMajorizationWithHierarchy(
453
+ graph: DirectedVtxData[], n: number, dCoords: Float64Array[], nodes: Node[], o: HierarchyOpts,
454
+ ): number {
455
+ if (!hasDirectionality(graph, n)) return unconstrainedMajorization(graph, n, dCoords, nodes, o);
456
+
457
+ // First, partition nodes into layers: these are our constraints
458
+ const smartIni = (o.opts & OPT_SMART_INIT) !== 0;
459
+ const layers = smartIni
460
+ ? smartInitLayers(graph, n, dCoords, nodes, o)
461
+ : plainInitLayers(graph, n, dCoords, nodes, o.dim);
462
+ if (typeof layers === 'number') return layers;
463
+ if (n === 1) return 0;
464
+
465
+ // Compute the all-pairs-shortest-distances matrix
466
+ if (o.maxi === 0) return 0;
467
+ const Dij = modelDistances(graph, n, o.model) ?? computeApspPacked(graph, n);
468
+ if (!smartIni) scaleDownLayout(dCoords, n, o.dim);
469
+ if (o.levelsGap > 0) scaleDistances(Dij, dCoords, n, o.dim);
470
+
471
+ // Layout initialization, Laplacian computation
472
+ centerLayout(dCoords, n, o.dim);
473
+ const st = newLoopState(Dij, toFloatCoords(dCoords, n, o.dim), n);
474
+ buildLap2(Dij, n);
475
+
476
+ // Layout optimization
477
+ const cMajEnv = initConstrainedMajorization(Dij, n, layers.ordering, layers.levels, layers.numLevels);
478
+ return majorize(st, { cMajEnv, levelsGap: o.levelsGap, maxi: o.maxi, epsilon: o.epsilon }, dCoords);
479
+ }