@knowvah/dot-engine 2.6.12 → 2.7.1

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 (198) 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/attr-get.d.ts +30 -0
  5. package/dist/common/attr-get.d.ts.map +1 -0
  6. package/dist/common/htmltable-emit-fill.d.ts +20 -2
  7. package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
  8. package/dist/common/htmltable-emit.d.ts.map +1 -1
  9. package/dist/common/htmltable-lex-expat.d.ts.map +1 -1
  10. package/dist/common/htmltable-pos-runs.d.ts +7 -4
  11. package/dist/common/htmltable-pos-runs.d.ts.map +1 -1
  12. package/dist/common/htmltable.d.ts.map +1 -1
  13. package/dist/common/libm-trig.d.ts +5 -0
  14. package/dist/common/libm-trig.d.ts.map +1 -0
  15. package/dist/common/make-label.d.ts.map +1 -1
  16. package/dist/common/multicolor.d.ts +21 -13
  17. package/dist/common/multicolor.d.ts.map +1 -1
  18. package/dist/common/poly-gencode.d.ts +1 -2
  19. package/dist/common/poly-gencode.d.ts.map +1 -1
  20. package/dist/common/poly-init.d.ts +2 -12
  21. package/dist/common/poly-init.d.ts.map +1 -1
  22. package/dist/common/splines-clip.d.ts +7 -0
  23. package/dist/common/splines-clip.d.ts.map +1 -1
  24. package/dist/gvc/context.d.ts +6 -0
  25. package/dist/gvc/context.d.ts.map +1 -1
  26. package/dist/gvc/device-cluster.d.ts +15 -0
  27. package/dist/gvc/device-cluster.d.ts.map +1 -1
  28. package/dist/gvc/device.d.ts.map +1 -1
  29. package/dist/gvc/first-layer.d.ts +9 -0
  30. package/dist/gvc/first-layer.d.ts.map +1 -0
  31. package/dist/gvc/gvrender-set-style.d.ts +28 -0
  32. package/dist/gvc/gvrender-set-style.d.ts.map +1 -0
  33. package/dist/gvc/usershape.d.ts +18 -0
  34. package/dist/gvc/usershape.d.ts.map +1 -1
  35. package/dist/index.js +2478 -912
  36. package/dist/index.js.map +4 -4
  37. package/dist/layout/dot/compound.d.ts.map +1 -1
  38. package/dist/layout/dot/edge-route-clip.d.ts +14 -4
  39. package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
  40. package/dist/layout/dot/edge-route-routing.d.ts.map +1 -1
  41. package/dist/layout/dot/graph-label.d.ts.map +1 -1
  42. package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts +11 -0
  43. package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts.map +1 -0
  44. package/dist/layout/neato/__fixtures__/hier-imds.d.ts +36 -0
  45. package/dist/layout/neato/__fixtures__/hier-imds.d.ts.map +1 -0
  46. package/dist/layout/neato/__fixtures__/hier-majorization.d.ts +32 -0
  47. package/dist/layout/neato/__fixtures__/hier-majorization.d.ts.map +1 -0
  48. package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
  49. package/dist/layout/neato/compute-hierarchy.d.ts +24 -0
  50. package/dist/layout/neato/compute-hierarchy.d.ts.map +1 -0
  51. package/dist/layout/neato/conjgrad-mkernel.d.ts +20 -0
  52. package/dist/layout/neato/conjgrad-mkernel.d.ts.map +1 -0
  53. package/dist/layout/neato/conjgrad.d.ts +19 -0
  54. package/dist/layout/neato/conjgrad.d.ts.map +1 -1
  55. package/dist/layout/neato/constrained-majorization.d.ts +26 -0
  56. package/dist/layout/neato/constrained-majorization.d.ts.map +1 -0
  57. package/dist/layout/neato/dijkstra.d.ts +33 -16
  58. package/dist/layout/neato/dijkstra.d.ts.map +1 -1
  59. package/dist/layout/neato/gts-triangle.d.ts +14 -0
  60. package/dist/layout/neato/gts-triangle.d.ts.map +1 -0
  61. package/dist/layout/neato/init.d.ts +2 -12
  62. package/dist/layout/neato/init.d.ts.map +1 -1
  63. package/dist/layout/neato/kkutils.d.ts +20 -0
  64. package/dist/layout/neato/kkutils.d.ts.map +1 -0
  65. package/dist/layout/neato/make-graph-data.d.ts +43 -0
  66. package/dist/layout/neato/make-graph-data.d.ts.map +1 -0
  67. package/dist/layout/neato/matrix-ops.d.ts +46 -0
  68. package/dist/layout/neato/matrix-ops.d.ts.map +1 -1
  69. package/dist/layout/neato/opt-arrangement.d.ts +20 -0
  70. package/dist/layout/neato/opt-arrangement.d.ts.map +1 -0
  71. package/dist/layout/neato/quad-prog-solve.d.ts +44 -0
  72. package/dist/layout/neato/quad-prog-solve.d.ts.map +1 -0
  73. package/dist/layout/neato/smart-ini-x.d.ts +9 -0
  74. package/dist/layout/neato/smart-ini-x.d.ts.map +1 -0
  75. package/dist/layout/neato/splines.d.ts +1 -1
  76. package/dist/layout/neato/splines.d.ts.map +1 -1
  77. package/dist/layout/neato/start.d.ts +4 -1
  78. package/dist/layout/neato/start.d.ts.map +1 -1
  79. package/dist/layout/neato/stress.d.ts +8 -0
  80. package/dist/layout/neato/stress.d.ts.map +1 -1
  81. package/dist/model/edge.d.ts +1 -1
  82. package/dist/model/edge.d.ts.map +1 -1
  83. package/dist/model/lazy-attrs.d.ts +10 -0
  84. package/dist/model/lazy-attrs.d.ts.map +1 -0
  85. package/dist/model/node.d.ts +1 -1
  86. package/dist/model/node.d.ts.map +1 -1
  87. package/dist/ortho/index.d.ts.map +1 -1
  88. package/dist/render/dot/edge-draw.d.ts +1 -1
  89. package/dist/render/dot/edge-draw.d.ts.map +1 -1
  90. package/dist/render/svg-multicolor.d.ts +2 -2
  91. package/dist/render/svg-multicolor.d.ts.map +1 -1
  92. package/dist/render.js +2358 -792
  93. package/dist/render.js.map +4 -4
  94. package/package.json +1 -1
  95. package/src/common/attr-get.ts +46 -0
  96. package/src/common/graph-init.ts +1 -1
  97. package/src/common/htmltable-emit-fill.ts +56 -11
  98. package/src/common/htmltable-emit.ts +4 -3
  99. package/src/common/htmltable-lex-expat.ts +21 -3
  100. package/src/common/htmltable-pos-runs.ts +11 -6
  101. package/src/common/htmltable.ts +28 -116
  102. package/src/common/libm-trig.ts +43 -0
  103. package/src/common/make-label.ts +14 -2
  104. package/src/common/multicolor.ts +88 -24
  105. package/src/common/poly-gencode.ts +16 -8
  106. package/src/common/poly-init.ts +23 -19
  107. package/src/common/postproc.ts +2 -2
  108. package/src/common/splines-clip.ts +15 -12
  109. package/src/common/splines-routespl.ts +2 -2
  110. package/src/common/textmeasure-lookup.ts +1 -1
  111. package/src/gvc/context.ts +8 -0
  112. package/src/gvc/device-cluster.ts +63 -8
  113. package/src/gvc/device.ts +16 -2
  114. package/src/gvc/first-layer.ts +26 -0
  115. package/src/gvc/gvrender-set-style.ts +114 -0
  116. package/src/gvc/usershape.ts +26 -0
  117. package/src/layout/dot/cluster.ts +1 -1
  118. package/src/layout/dot/compound.ts +3 -4
  119. package/src/layout/dot/conc.ts +1 -1
  120. package/src/layout/dot/edge-route-clip.ts +19 -7
  121. package/src/layout/dot/edge-route-routing.ts +11 -3
  122. package/src/layout/dot/fastgr.ts +2 -2
  123. package/src/layout/dot/graph-label.ts +1 -15
  124. package/src/layout/neato/__fixtures__/dijkstra-f.ts +17 -0
  125. package/src/layout/neato/__fixtures__/hier/c1554.gv +33 -0
  126. package/src/layout/neato/__fixtures__/hier/c1554.plain +26 -0
  127. package/src/layout/neato/__fixtures__/hier/c1554.xdot +161 -0
  128. package/src/layout/neato/__fixtures__/hier/cases/cycle.gv +1 -0
  129. package/src/layout/neato/__fixtures__/hier/cases/cycle.plain +10 -0
  130. package/src/layout/neato/__fixtures__/hier/cases/cycle.stderr +0 -0
  131. package/src/layout/neato/__fixtures__/hier/cases/dirnone.gv +1 -0
  132. package/src/layout/neato/__fixtures__/hier/cases/dirnone.plain +11 -0
  133. package/src/layout/neato/__fixtures__/hier/cases/dirnone.stderr +0 -0
  134. package/src/layout/neato/__fixtures__/hier/cases/epsilon.gv +1 -0
  135. package/src/layout/neato/__fixtures__/hier/cases/epsilon.plain +13 -0
  136. package/src/layout/neato/__fixtures__/hier/cases/epsilon.stderr +0 -0
  137. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.gv +1 -0
  138. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.plain +8 -0
  139. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.stderr +0 -0
  140. package/src/layout/neato/__fixtures__/hier/cases/mds.gv +1 -0
  141. package/src/layout/neato/__fixtures__/hier/cases/mds.plain +10 -0
  142. package/src/layout/neato/__fixtures__/hier/cases/mds.stderr +0 -0
  143. package/src/layout/neato/__fixtures__/hier/cases/multiedge.gv +1 -0
  144. package/src/layout/neato/__fixtures__/hier/cases/multiedge.plain +10 -0
  145. package/src/layout/neato/__fixtures__/hier/cases/multiedge.stderr +0 -0
  146. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.gv +1 -0
  147. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.plain +8 -0
  148. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.stderr +0 -0
  149. package/src/layout/neato/__fixtures__/hier/cases/startrandom.gv +1 -0
  150. package/src/layout/neato/__fixtures__/hier/cases/startrandom.plain +10 -0
  151. package/src/layout/neato/__fixtures__/hier/cases/startrandom.stderr +0 -0
  152. package/src/layout/neato/__fixtures__/hier/cases/startregular.gv +1 -0
  153. package/src/layout/neato/__fixtures__/hier/cases/startregular.plain +10 -0
  154. package/src/layout/neato/__fixtures__/hier/cases/startregular.stderr +0 -0
  155. package/src/layout/neato/__fixtures__/hier/cases/tdist.gv +1 -0
  156. package/src/layout/neato/__fixtures__/hier/cases/tdist.plain +13 -0
  157. package/src/layout/neato/__fixtures__/hier/cases/tdist.stderr +0 -0
  158. package/src/layout/neato/__fixtures__/hier/cases/undirected.gv +1 -0
  159. package/src/layout/neato/__fixtures__/hier/cases/undirected.plain +10 -0
  160. package/src/layout/neato/__fixtures__/hier/cases/undirected.stderr +0 -0
  161. package/src/layout/neato/__fixtures__/hier/hier-probe.gv +1 -0
  162. package/src/layout/neato/__fixtures__/hier/hier-probe.plain +16 -0
  163. package/src/layout/neato/__fixtures__/hier/hier-probe.xdot +62 -0
  164. package/src/layout/neato/__fixtures__/hier-imds.ts +151 -0
  165. package/src/layout/neato/__fixtures__/hier-majorization.ts +145 -0
  166. package/src/layout/neato/cdt-surface.ts +6 -16
  167. package/src/layout/neato/compute-hierarchy.ts +90 -0
  168. package/src/layout/neato/conjgrad-mkernel.ts +141 -0
  169. package/src/layout/neato/conjgrad.ts +208 -0
  170. package/src/layout/neato/constrained-majorization.ts +479 -0
  171. package/src/layout/neato/dijkstra.ts +218 -92
  172. package/src/layout/neato/edge-len.ts +2 -2
  173. package/src/layout/neato/fdp-adjust.ts +3 -3
  174. package/src/layout/neato/gts-triangle.ts +42 -0
  175. package/src/layout/neato/init.ts +57 -57
  176. package/src/layout/neato/kk.ts +2 -2
  177. package/src/layout/neato/kkutils.ts +96 -0
  178. package/src/layout/neato/make-graph-data.ts +166 -0
  179. package/src/layout/neato/matrix-ops.ts +138 -0
  180. package/src/layout/neato/opt-arrangement.ts +86 -0
  181. package/src/layout/neato/quad-prog-solve.ts +489 -0
  182. package/src/layout/neato/smart-ini-x.ts +503 -0
  183. package/src/layout/neato/splines.ts +4 -4
  184. package/src/layout/neato/start.ts +7 -6
  185. package/src/layout/neato/stress.ts +28 -0
  186. package/src/model/edge.ts +3 -2
  187. package/src/model/lazy-attrs.ts +36 -0
  188. package/src/model/node.ts +3 -2
  189. package/src/ortho/index.ts +2 -3
  190. package/src/parser/dot.js +1 -1
  191. package/src/parser/dot.pegjs +1 -1
  192. package/src/parser/index.ts +1 -1
  193. package/src/render/color-resolve.ts +2 -2
  194. package/src/render/dot/edge-draw.ts +6 -3
  195. package/src/render/dot.ts +1 -1
  196. package/src/render/svg-edge-split.ts +2 -2
  197. package/src/render/svg-multicolor.ts +4 -3
  198. package/src/render/svg-parallel-edge.ts +2 -2
@@ -0,0 +1,489 @@
1
+ // SPDX-License-Identifier: EPL-2.0
2
+
3
+ /**
4
+ * Constrained majorization with level gaps (DiG-CoLa, `mode=hier`) — a
5
+ * float32-faithful port of `lib/neatogen/quad_prog_solve.c`. The C works on
6
+ * `float`; every float operation is rounded with `Math.fround` and arrays are
7
+ * `Float32Array`, as `stress-kernel.ts` does.
8
+ *
9
+ * `deleteCMajEnv` is not ported: it only `free`s the environment's arrays, which
10
+ * the garbage collector does here, so it has no observable effect.
11
+ *
12
+ * The C `constrained_majorization_new_with_gaps` is one 270-line function; it is
13
+ * split here at its own phase boundaries (block growth, desired places, level
14
+ * ordering, prefix/suffix stats, best split, move) to satisfy the complexity
15
+ * limits. Statement order and float rounding points are kept.
16
+ *
17
+ * @see lib/neatogen/quad_prog_solve.c
18
+ * @see lib/neatogen/quad_prog_solver.h
19
+ */
20
+
21
+ import { quicksortPlacef } from './kkutils.js';
22
+ import { orthog1f } from './matrix-ops.js';
23
+
24
+ const fr = Math.fround;
25
+
26
+ /** `equals` tolerance, a C `const float`. @see lib/neatogen/quad_prog_solve.c:equals */
27
+ const TOLERANCE = fr(1e-2);
28
+ /** "No lower bound / upper bound" sentinel. @see lib/neatogen/quad_prog_solve.c */
29
+ const BOUND = 1e9;
30
+ /** Block-membership slack in `constrained_majorization_new_with_gaps`. */
31
+ const BLOCK_EPSILON = 1e-9;
32
+ /** C `SIZE_MAX` sentinel for `best_i`. */
33
+ const NO_SPLIT = -1;
34
+
35
+ /**
36
+ * Constrained-majorization environment.
37
+ * @see lib/neatogen/quad_prog_solver.h:CMajEnv
38
+ */
39
+ export interface CMajEnv {
40
+ /** Unpacked symmetric matrix, `A[i][j]`. */
41
+ A: Float32Array[];
42
+ n: number;
43
+ fArray1: Float32Array;
44
+ fArray2: Float32Array;
45
+ fArray3: Float32Array;
46
+ fArray4: Float32Array;
47
+ ordering: Int32Array;
48
+ levels: Int32Array;
49
+ numLevels: number;
50
+ }
51
+
52
+ /** Mutable state shared by the phase helpers of one solve. */
53
+ interface SolveCtx {
54
+ e: CMajEnv;
55
+ place: Float32Array;
56
+ b: Float32Array;
57
+ /** Level at position `i` (C indexes it by position in the block tests). */
58
+ lev: Int32Array;
59
+ levelsGap: number;
60
+ /** Current block (nodes connected by active constraints). */
61
+ block: number[];
62
+ lowerBound: number;
63
+ converged: boolean;
64
+ }
65
+
66
+ /** Big-endian view used to step a double by one ulp (high word at 0, low at 4). */
67
+ const BITS = new DataView(new ArrayBuffer(8));
68
+
69
+ /** The double adjacent to `s`, one ulp away from or toward zero (`s`'s low bit is 0). */
70
+ function oddNeighbour(s: number, awayFromZero: boolean): number {
71
+ BITS.setFloat64(0, s);
72
+ const hi = BITS.getUint32(0);
73
+ const lo = BITS.getUint32(4);
74
+ if (awayFromZero) {
75
+ BITS.setUint32(4, lo + 1);
76
+ } else if (lo === 0) {
77
+ BITS.setUint32(0, hi - 1);
78
+ BITS.setUint32(4, 0xffffffff);
79
+ } else {
80
+ BITS.setUint32(4, lo - 1);
81
+ }
82
+ return BITS.getFloat64(0);
83
+ }
84
+
85
+ /**
86
+ * Float fused multiply-add, round(a*b + c) with a single rounding to float, as the
87
+ * arm64 `fmadd s` the 16.1 oracle build emits. The product of two floats is exact
88
+ * in a double; the double sum is made round-to-odd so the final rounding to float
89
+ * cannot double-round.
90
+ */
91
+ export function fmaf(a: number, b: number, c: number): number {
92
+ const p = a * b;
93
+ const s = p + c;
94
+ const bv = s - p;
95
+ const err = (p - (s - bv)) + (c - bv); /* Knuth two-sum: s + err == p + c */
96
+ if (err === 0 || !Number.isFinite(s)) return fr(s);
97
+ BITS.setFloat64(0, s);
98
+ if ((BITS.getUint32(4) & 1) !== 0) return fr(s);
99
+ /* even: step one ulp toward the exact value to land on the odd neighbour */
100
+ return fr(oddNeighbour(s, (err > 0) === (s > 0)));
101
+ }
102
+
103
+ /**
104
+ * `block_deg*des_place_block + (-lap)*desired + tbc*cur_place`, contracted as the
105
+ * oracle does: fnmul, then two chained float fmadd.
106
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
107
+ */
108
+ function blockPlaceNumerator(
109
+ blockDeg: number, desPlaceBlock: number, negLapTimesDesired: number,
110
+ toBlockConnectivity: number, curPlace: number,
111
+ ): number {
112
+ const t = fmaf(blockDeg, desPlaceBlock, negLapTimesDesired);
113
+ return fmaf(toBlockConnectivity, curPlace, t);
114
+ }
115
+
116
+ /** Are two values equal, within a reasonable tolerance? @see lib/neatogen/quad_prog_solve.c:equals */
117
+ function equals(a: number, b: number): boolean {
118
+ return Math.abs(fr(a - b)) < TOLERANCE;
119
+ }
120
+
121
+ /** C `fminf`: a NaN operand yields the other operand. */
122
+ export function fminf(a: number, b: number): number {
123
+ if (Number.isNaN(a)) return b;
124
+ if (Number.isNaN(b)) return a;
125
+ return a < b ? a : b;
126
+ }
127
+
128
+ /** C `fmaxf`: a NaN operand yields the other operand. */
129
+ export function fmaxf(a: number, b: number): number {
130
+ if (Number.isNaN(a)) return b;
131
+ if (Number.isNaN(b)) return a;
132
+ return a > b ? a : b;
133
+ }
134
+
135
+ /**
136
+ * Expand an upper-triangular packed symmetric matrix into rows.
137
+ * @see lib/neatogen/quad_prog_solve.c:unpackMatrix
138
+ */
139
+ export function unpackMatrix(packedMat: Float32Array, n: number): Float32Array[] {
140
+ const flat = new Float32Array(n * n);
141
+ const mat: Float32Array[] = new Array<Float32Array>(n);
142
+ for (let i = 0; i < n; i++) mat[i] = flat.subarray(i * n, (i + 1) * n);
143
+ for (let i = 0, k = 0; i < n; i++) {
144
+ for (let j = i; j < n; j++, k++) {
145
+ mat[j]![i] = mat[i]![j] = packedMat[k]!;
146
+ }
147
+ }
148
+ return mat;
149
+ }
150
+
151
+ /**
152
+ * Ensure levels are separated in the initial layout and places are monotonic
153
+ * increasing within a layer.
154
+ * @see lib/neatogen/quad_prog_solve.c:ensureMonotonicOrderingWithGaps
155
+ */
156
+ function ensureMonotonicOrderingWithGaps(
157
+ place: Float32Array, n: number, ordering: Int32Array,
158
+ levels: Int32Array, numLevels: number, levelsGap: number,
159
+ ): void {
160
+ let lowerBound = fr(-1e9);
161
+ let level = -1;
162
+ let maxInLevel = 0;
163
+ for (let i = 0; i < n; i++) {
164
+ if (i >= maxInLevel) {
165
+ /* we are entering a new level */
166
+ level++;
167
+ maxInLevel = level === numLevels ? n : levels[level]!;
168
+ lowerBound = i > 0 ? fr(place[ordering[i - 1]!]! + levelsGap) : fr(-1e9);
169
+ quicksortPlacef(place, ordering, i, maxInLevel - 1);
170
+ }
171
+ const node = ordering[i]!;
172
+ if (place[node]! < lowerBound) place[node] = lowerBound;
173
+ }
174
+ }
175
+
176
+ /** Level of each node (C's `lev` array). @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps */
177
+ function computeNodeLevels(e: CMajEnv): Int32Array {
178
+ const lev = new Int32Array(e.n);
179
+ let level = -1;
180
+ let maxInLevel = 0;
181
+ for (let i = 0; i < e.n; i++) {
182
+ if (i >= maxInLevel) {
183
+ /* we are entering a new level */
184
+ level++;
185
+ maxInLevel = level === e.numLevels ? e.n : e.levels[level]!;
186
+ }
187
+ lev[e.ordering[i]!] = level;
188
+ }
189
+ return lev;
190
+ }
191
+
192
+ /**
193
+ * Grow the block `ordering[left]..ordering[right-1]` of nodes connected with
194
+ * active constraints; fills `fArray4` (gap) and returns `right`.
195
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
196
+ */
197
+ function growBlock(c: SolveCtx, left: number, curPlace: number): number {
198
+ const { place, lev, levelsGap, e } = c;
199
+ const gap = e.fArray4;
200
+ let targetPlace = curPlace;
201
+ gap[e.ordering[left]!] = 0;
202
+ let right: number;
203
+ for (right = left + 1; right < e.n; right++) {
204
+ if (lev[right]! > lev[right - 1]!) {
205
+ /* we are entering a new level; 'levels_gap' may be negative */
206
+ targetPlace = fr(targetPlace + levelsGap);
207
+ }
208
+ const node = e.ordering[right]!;
209
+ if (Math.abs(fr(place[node]! - targetPlace)) > BLOCK_EPSILON) break;
210
+ gap[node] = fr(place[node]! - curPlace);
211
+ }
212
+ return right;
213
+ }
214
+
215
+ /**
216
+ * Desired place of the block's reference point according to each node.
217
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
218
+ */
219
+ function computeDesiredPlaces(c: SolveCtx, left: number, right: number): void {
220
+ const { e, place, b } = c;
221
+ for (let i = left; i < right; i++) {
222
+ const node = e.ordering[i]!;
223
+ let newPlaceI = -b[node]!;
224
+ const lapNode = e.A[node]!;
225
+ for (let j = 0; j < e.n; j++) {
226
+ if (j === node) continue;
227
+ newPlaceI = fmaf(lapNode[j]!, place[j]!, newPlaceI);
228
+ }
229
+ e.fArray1[node] = fr(fr(newPlaceI / -lapNode[node]!) - e.fArray4[node]!);
230
+ }
231
+ }
232
+
233
+ /** Append the nodes in positions `[from, to)` whose desired place passes `test`. */
234
+ function collectWhere(
235
+ c: SolveCtx, from: number, to: number, test: (desired: number) => boolean,
236
+ ): void {
237
+ for (let j = from; j < to; j++) {
238
+ const node = c.e.ordering[j]!;
239
+ if (test(c.e.fArray1[node]!)) c.block.push(node);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Reorder the block by levels, and within levels by "relaxed" desired position.
245
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
246
+ */
247
+ function orderBlockByLevels(
248
+ c: SolveCtx, left: number, right: number, curPlace: number,
249
+ ): void {
250
+ const { e } = c;
251
+ c.block.length = 0;
252
+ let firstNextLevel = 0;
253
+ for (let i = left; i < right; i = firstNextLevel) {
254
+ const level = c.lev[e.ordering[i]!]!;
255
+ firstNextLevel = level === e.numLevels ? right : Math.min(right, e.levels[level]!);
256
+ /* smaller than, then equal to, then greater than the current place */
257
+ collectWhere(c, i, firstNextLevel, (d) => d < curPlace);
258
+ collectWhere(c, i, firstNextLevel, (d) => d === curPlace);
259
+ collectWhere(c, i, firstNextLevel, (d) => d > curPlace);
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Desired places of the block's prefixes.
265
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
266
+ */
267
+ function computePrefixDesiredPlaces(c: SolveCtx, curPlace: number): void {
268
+ const { e, block } = c;
269
+ let desPlaceBlock = 0;
270
+ let blockDeg = 0;
271
+ for (let i = 0; i < block.length; i++) {
272
+ const node = block[i]!;
273
+ let toBlockConnectivity = 0;
274
+ const lapNode = e.A[node]!;
275
+ for (let j = 0; j < i; j++) {
276
+ toBlockConnectivity = fr(toBlockConnectivity - lapNode[block[j]!]!);
277
+ }
278
+ toBlockConnectivity = fr(toBlockConnectivity * 2);
279
+ /* update block stats */
280
+ const num = blockPlaceNumerator(
281
+ blockDeg, desPlaceBlock, fr(-lapNode[node]! * e.fArray1[node]!),
282
+ toBlockConnectivity, curPlace,
283
+ );
284
+ desPlaceBlock = fr(num / fr(fr(blockDeg - lapNode[node]!) + toBlockConnectivity));
285
+ e.fArray2[i] = desPlaceBlock;
286
+ blockDeg = fr(blockDeg + fr(toBlockConnectivity - lapNode[node]!));
287
+ }
288
+ if (e.n >= 0 && block.length === e.n) {
289
+ /* fix is needed since denominator was 0 in this case */
290
+ e.fArray2[e.n - 1] = curPlace; /* a "neutral" value */
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Desired places of the block's suffixes.
296
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
297
+ */
298
+ function computeSuffixDesiredPlaces(c: SolveCtx, curPlace: number): void {
299
+ const { e, block } = c;
300
+ let desPlaceBlock = 0;
301
+ let blockDeg = 0;
302
+ for (let i = block.length - 1; i >= 0; i--) {
303
+ const node = block[i]!;
304
+ let toBlockConnectivity = 0;
305
+ const lapNode = e.A[node]!;
306
+ for (let j = i + 1; j < block.length; j++) {
307
+ toBlockConnectivity = fr(toBlockConnectivity - lapNode[block[j]!]!);
308
+ }
309
+ toBlockConnectivity = fr(toBlockConnectivity * 2);
310
+ /* update block stats */
311
+ const num = blockPlaceNumerator(
312
+ blockDeg, desPlaceBlock, fr(-lapNode[node]! * e.fArray1[node]!),
313
+ toBlockConnectivity, curPlace,
314
+ );
315
+ desPlaceBlock = fr(num / fr(fr(blockDeg - lapNode[node]!) + toBlockConnectivity));
316
+ e.fArray3[i] = desPlaceBlock;
317
+ blockDeg = fr(blockDeg + fr(toBlockConnectivity - lapNode[node]!));
318
+ }
319
+ if (e.n >= 0 && block.length === e.n) {
320
+ /* fix is needed since denominator was 0 in this case */
321
+ e.fArray3[0] = curPlace; /* a "neutral" value? */
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Scratch result of {@link limitMoves}: `[prefix, suffix]`. Module-level to avoid
327
+ * allocating in the hot loop; the solver is synchronous and non-reentrant.
328
+ */
329
+ const LIMITED = new Float64Array(2);
330
+
331
+ /**
332
+ * Limit moves to ensure that the prefix is placed before the suffix.
333
+ * Writes the adjusted `[prefix, suffix]` into {@link LIMITED}.
334
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
335
+ */
336
+ function limitMoves(prefix: number, suffix: number, curPlace: number): void {
337
+ if (suffix < prefix) {
338
+ if (suffix < curPlace) {
339
+ if (prefix > curPlace) prefix = curPlace;
340
+ suffix = prefix;
341
+ } else if (prefix > curPlace) {
342
+ prefix = suffix;
343
+ }
344
+ }
345
+ LIMITED[0] = prefix;
346
+ LIMITED[1] = suffix;
347
+ }
348
+
349
+ /**
350
+ * Find the best place to split the block; `NO_SPLIT` when nothing moves.
351
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
352
+ */
353
+ function findBestSplit(c: SolveCtx, curPlace: number): number {
354
+ const { e, block } = c;
355
+ let bestI = NO_SPLIT;
356
+ let maxMovement = 0;
357
+ for (let i = 0; i < block.length; i++) {
358
+ const suffixDesPlace = e.fArray3[i]!;
359
+ const prefixDesPlace = i > 0 ? e.fArray2[i - 1]! : suffixDesPlace;
360
+ limitMoves(prefixDesPlace, suffixDesPlace, curPlace);
361
+ const movement = (block.length - i) * Math.abs(fr(LIMITED[1]! - curPlace))
362
+ + i * Math.abs(fr(LIMITED[0]! - curPlace));
363
+ if (movement > maxMovement) {
364
+ maxMovement = movement;
365
+ bestI = i;
366
+ }
367
+ }
368
+ return bestI;
369
+ }
370
+
371
+ /** Right border of the feasible move, relative to the block's reference point. */
372
+ function computeUpperBound(c: SolveCtx, right: number): number {
373
+ const { e, place, lev, levelsGap, block } = c;
374
+ if (right >= e.n) return BOUND; /* no nodes after current block */
375
+ /* deduct 'gap[block[block_len-1]]': all computations are relative to the
376
+ * block's reference point */
377
+ const lastGap = e.fArray4[block[block.length - 1]!]!;
378
+ const nextPlace = place[e.ordering[right]!]!;
379
+ if (lev[e.ordering[right]!]! > lev[e.ordering[right - 1]!]!) {
380
+ return fr(fr(nextPlace - levelsGap) - lastGap);
381
+ }
382
+ return fr(nextPlace - lastGap);
383
+ }
384
+
385
+ /** Lower bound for the next block. */
386
+ function nextLowerBound(c: SolveCtx, right: number): number {
387
+ const { e, place, lev, levelsGap, block } = c;
388
+ const last = place[block[block.length - 1]!]!;
389
+ if (right < e.n && lev[e.ordering[right]!]! > lev[e.ordering[right - 1]!]!) {
390
+ return fr(last + levelsGap);
391
+ }
392
+ return last;
393
+ }
394
+
395
+ /**
396
+ * Actually move prefix and suffix of the block, update the bound for the next
397
+ * block and the ordering.
398
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
399
+ */
400
+ function moveSplitBlock(
401
+ c: SolveCtx, bestI: number, left: number, right: number, curPlace: number,
402
+ ): void {
403
+ const { e, place, block } = c;
404
+ let suffixDesPlace = e.fArray3[bestI]!;
405
+ let prefixDesPlace = bestI > 0 ? e.fArray2[bestI - 1]! : suffixDesPlace;
406
+ suffixDesPlace = fminf(suffixDesPlace, computeUpperBound(c, right));
407
+ prefixDesPlace = fmaxf(prefixDesPlace, c.lowerBound);
408
+ limitMoves(prefixDesPlace, suffixDesPlace, curPlace);
409
+ prefixDesPlace = LIMITED[0]!;
410
+ suffixDesPlace = LIMITED[1]!;
411
+ for (let i = 0; i < bestI; i++) {
412
+ place[block[i]!] = fr(prefixDesPlace + e.fArray4[block[i]!]!);
413
+ }
414
+ for (let i = bestI; i < block.length; i++) {
415
+ place[block[i]!] = fr(suffixDesPlace + e.fArray4[block[i]!]!);
416
+ }
417
+ c.lowerBound = nextLowerBound(c, right);
418
+ /* reorder 'ordering' to reflect change of places; enough for the split level */
419
+ for (let i = left; i < right; i++) e.ordering[i] = block[i - left]!;
420
+ c.converged = c.converged
421
+ && equals(prefixDesPlace, curPlace) && equals(suffixDesPlace, curPlace);
422
+ }
423
+
424
+ /** One block of one majorization sweep; returns the block's `right` border. */
425
+ function processBlock(c: SolveCtx, left: number): number {
426
+ const curPlace = c.place[c.e.ordering[left]!]!;
427
+ const right = growBlock(c, left, curPlace);
428
+ computeDesiredPlaces(c, left, right);
429
+ orderBlockByLevels(c, left, right, curPlace);
430
+ computePrefixDesiredPlaces(c, curPlace);
431
+ computeSuffixDesiredPlaces(c, curPlace);
432
+ const bestI = findBestSplit(c, curPlace);
433
+ if (bestI !== NO_SPLIT) {
434
+ moveSplitBlock(c, bestI, left, right, curPlace);
435
+ } else {
436
+ /* no movement */
437
+ c.lowerBound = nextLowerBound(c, right);
438
+ }
439
+ return right;
440
+ }
441
+
442
+ /**
443
+ * Solve one majorization step along `curAxis` subject to level constraints,
444
+ * mutating `coords[curAxis]` and `e.ordering` in place.
445
+ * @see lib/neatogen/quad_prog_solve.c:constrained_majorization_new_with_gaps
446
+ */
447
+ export function constrainedMajorizationNewWithGaps(
448
+ e: CMajEnv, b: Float32Array, coords: Float32Array[],
449
+ curAxis: number, maxIterations: number, levelsGap: number,
450
+ ): void {
451
+ const place = coords[curAxis]!;
452
+ if (maxIterations <= 0) return;
453
+ const gap = fr(levelsGap);
454
+ ensureMonotonicOrderingWithGaps(place, e.n, e.ordering, e.levels, e.numLevels, gap);
455
+ /* 'ordering' stays sorted by layers, and within a layer by 'place' */
456
+ const c: SolveCtx = {
457
+ e, place, b, lev: computeNodeLevels(e), levelsGap: gap,
458
+ block: [], lowerBound: fr(-BOUND), converged: false,
459
+ };
460
+ for (let counter = 0; counter < maxIterations && !c.converged; counter++) {
461
+ c.converged = true;
462
+ c.lowerBound = fr(-BOUND); /* no lower bound for first level */
463
+ for (let left = 0, right: number; left < e.n; left = right) {
464
+ right = processBlock(c, left);
465
+ }
466
+ orthog1f(e.n, place); /* for numerical stability, keep ||place|| small */
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Build the constrained-majorization environment from a packed matrix.
472
+ * @see lib/neatogen/quad_prog_solve.c:initConstrainedMajorization
473
+ */
474
+ export function initConstrainedMajorization(
475
+ packedMat: Float32Array, n: number, ordering: Int32Array,
476
+ levels: Int32Array, numLevels: number,
477
+ ): CMajEnv {
478
+ return {
479
+ A: unpackMatrix(packedMat, n),
480
+ n,
481
+ fArray1: new Float32Array(n),
482
+ fArray2: new Float32Array(n),
483
+ fArray3: new Float32Array(n),
484
+ fArray4: new Float32Array(n),
485
+ ordering,
486
+ levels,
487
+ numLevels,
488
+ };
489
+ }