@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.
- package/README.md +1 -1
- package/dist/api.js +18 -4
- package/dist/api.js.map +3 -3
- package/dist/common/htmltable-emit-fill.d.ts +20 -2
- package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
- package/dist/common/htmltable-emit.d.ts.map +1 -1
- package/dist/common/htmltable-lex-expat.d.ts.map +1 -1
- package/dist/common/htmltable-pos-runs.d.ts +7 -4
- package/dist/common/htmltable-pos-runs.d.ts.map +1 -1
- package/dist/common/htmltable.d.ts.map +1 -1
- package/dist/common/libm-trig.d.ts +5 -0
- package/dist/common/libm-trig.d.ts.map +1 -0
- package/dist/common/multicolor.d.ts +21 -13
- package/dist/common/multicolor.d.ts.map +1 -1
- package/dist/common/poly-gencode.d.ts +1 -2
- package/dist/common/poly-gencode.d.ts.map +1 -1
- package/dist/common/poly-init.d.ts.map +1 -1
- package/dist/common/splines-clip.d.ts +7 -0
- package/dist/common/splines-clip.d.ts.map +1 -1
- package/dist/gvc/context.d.ts +6 -0
- package/dist/gvc/context.d.ts.map +1 -1
- package/dist/gvc/device-cluster.d.ts +15 -0
- package/dist/gvc/device-cluster.d.ts.map +1 -1
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/first-layer.d.ts +9 -0
- package/dist/gvc/first-layer.d.ts.map +1 -0
- package/dist/gvc/gvrender-set-style.d.ts +28 -0
- package/dist/gvc/gvrender-set-style.d.ts.map +1 -0
- package/dist/gvc/usershape.d.ts +18 -0
- package/dist/gvc/usershape.d.ts.map +1 -1
- package/dist/index.js +2460 -901
- package/dist/index.js.map +4 -4
- package/dist/layout/dot/compound.d.ts.map +1 -1
- package/dist/layout/dot/edge-route-clip.d.ts +14 -4
- package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
- package/dist/layout/dot/edge-route-routing.d.ts.map +1 -1
- package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts +11 -0
- package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts.map +1 -0
- package/dist/layout/neato/__fixtures__/hier-imds.d.ts +36 -0
- package/dist/layout/neato/__fixtures__/hier-imds.d.ts.map +1 -0
- package/dist/layout/neato/__fixtures__/hier-majorization.d.ts +32 -0
- package/dist/layout/neato/__fixtures__/hier-majorization.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
- package/dist/layout/neato/compute-hierarchy.d.ts +24 -0
- package/dist/layout/neato/compute-hierarchy.d.ts.map +1 -0
- package/dist/layout/neato/conjgrad-mkernel.d.ts +20 -0
- package/dist/layout/neato/conjgrad-mkernel.d.ts.map +1 -0
- package/dist/layout/neato/conjgrad.d.ts +19 -0
- package/dist/layout/neato/conjgrad.d.ts.map +1 -1
- package/dist/layout/neato/constrained-majorization.d.ts +26 -0
- package/dist/layout/neato/constrained-majorization.d.ts.map +1 -0
- package/dist/layout/neato/dijkstra.d.ts +33 -16
- package/dist/layout/neato/dijkstra.d.ts.map +1 -1
- package/dist/layout/neato/gts-triangle.d.ts +14 -0
- package/dist/layout/neato/gts-triangle.d.ts.map +1 -0
- package/dist/layout/neato/init.d.ts +2 -12
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/kkutils.d.ts +20 -0
- package/dist/layout/neato/kkutils.d.ts.map +1 -0
- package/dist/layout/neato/make-graph-data.d.ts +43 -0
- package/dist/layout/neato/make-graph-data.d.ts.map +1 -0
- package/dist/layout/neato/matrix-ops.d.ts +46 -0
- package/dist/layout/neato/matrix-ops.d.ts.map +1 -1
- package/dist/layout/neato/opt-arrangement.d.ts +20 -0
- package/dist/layout/neato/opt-arrangement.d.ts.map +1 -0
- package/dist/layout/neato/quad-prog-solve.d.ts +44 -0
- package/dist/layout/neato/quad-prog-solve.d.ts.map +1 -0
- package/dist/layout/neato/smart-ini-x.d.ts +9 -0
- package/dist/layout/neato/smart-ini-x.d.ts.map +1 -0
- package/dist/layout/neato/splines.d.ts +1 -1
- package/dist/layout/neato/splines.d.ts.map +1 -1
- package/dist/layout/neato/start.d.ts +4 -1
- package/dist/layout/neato/start.d.ts.map +1 -1
- package/dist/layout/neato/stress.d.ts +8 -0
- package/dist/layout/neato/stress.d.ts.map +1 -1
- package/dist/model/edge.d.ts +1 -1
- package/dist/model/edge.d.ts.map +1 -1
- package/dist/model/lazy-attrs.d.ts +10 -0
- package/dist/model/lazy-attrs.d.ts.map +1 -0
- package/dist/model/node.d.ts +1 -1
- package/dist/model/node.d.ts.map +1 -1
- package/dist/ortho/index.d.ts.map +1 -1
- package/dist/render/dot/edge-draw.d.ts +1 -1
- package/dist/render/dot/edge-draw.d.ts.map +1 -1
- package/dist/render/svg-multicolor.d.ts +2 -2
- package/dist/render/svg-multicolor.d.ts.map +1 -1
- package/dist/render.js +2340 -781
- package/dist/render.js.map +4 -4
- package/package.json +1 -1
- package/src/common/graph-init.ts +1 -1
- package/src/common/htmltable-emit-fill.ts +56 -11
- package/src/common/htmltable-emit.ts +4 -3
- package/src/common/htmltable-lex-expat.ts +21 -3
- package/src/common/htmltable-pos-runs.ts +11 -6
- package/src/common/htmltable.ts +28 -116
- package/src/common/libm-trig.ts +43 -0
- package/src/common/multicolor.ts +88 -24
- package/src/common/poly-gencode.ts +16 -8
- package/src/common/poly-init.ts +19 -1
- package/src/common/postproc.ts +2 -2
- package/src/common/splines-clip.ts +15 -12
- package/src/common/splines-routespl.ts +2 -2
- package/src/common/textmeasure-lookup.ts +1 -1
- package/src/gvc/context.ts +8 -0
- package/src/gvc/device-cluster.ts +63 -8
- package/src/gvc/device.ts +16 -2
- package/src/gvc/first-layer.ts +26 -0
- package/src/gvc/gvrender-set-style.ts +114 -0
- package/src/gvc/usershape.ts +26 -0
- package/src/layout/dot/cluster.ts +1 -1
- package/src/layout/dot/compound.ts +3 -4
- package/src/layout/dot/conc.ts +1 -1
- package/src/layout/dot/edge-route-clip.ts +19 -7
- package/src/layout/dot/edge-route-routing.ts +11 -3
- package/src/layout/dot/fastgr.ts +2 -2
- package/src/layout/neato/__fixtures__/dijkstra-f.ts +17 -0
- package/src/layout/neato/__fixtures__/hier/c1554.gv +33 -0
- package/src/layout/neato/__fixtures__/hier/c1554.plain +26 -0
- package/src/layout/neato/__fixtures__/hier/c1554.xdot +161 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.plain +11 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.plain +13 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.plain +8 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.plain +8 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.plain +13 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.plain +16 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.xdot +62 -0
- package/src/layout/neato/__fixtures__/hier-imds.ts +151 -0
- package/src/layout/neato/__fixtures__/hier-majorization.ts +145 -0
- package/src/layout/neato/cdt-surface.ts +6 -16
- package/src/layout/neato/compute-hierarchy.ts +90 -0
- package/src/layout/neato/conjgrad-mkernel.ts +141 -0
- package/src/layout/neato/conjgrad.ts +208 -0
- package/src/layout/neato/constrained-majorization.ts +479 -0
- package/src/layout/neato/dijkstra.ts +218 -92
- package/src/layout/neato/edge-len.ts +2 -2
- package/src/layout/neato/fdp-adjust.ts +3 -3
- package/src/layout/neato/gts-triangle.ts +42 -0
- package/src/layout/neato/init.ts +57 -57
- package/src/layout/neato/kk.ts +2 -2
- package/src/layout/neato/kkutils.ts +96 -0
- package/src/layout/neato/make-graph-data.ts +166 -0
- package/src/layout/neato/matrix-ops.ts +138 -0
- package/src/layout/neato/opt-arrangement.ts +86 -0
- package/src/layout/neato/quad-prog-solve.ts +489 -0
- package/src/layout/neato/smart-ini-x.ts +503 -0
- package/src/layout/neato/splines.ts +4 -4
- package/src/layout/neato/start.ts +7 -6
- package/src/layout/neato/stress.ts +28 -0
- package/src/model/edge.ts +3 -2
- package/src/model/lazy-attrs.ts +36 -0
- package/src/model/node.ts +3 -2
- package/src/ortho/index.ts +2 -3
- package/src/parser/dot.js +1 -1
- package/src/parser/dot.pegjs +1 -1
- package/src/parser/index.ts +1 -1
- package/src/render/color-resolve.ts +2 -2
- package/src/render/dot/edge-draw.ts +6 -3
- package/src/render/dot.ts +1 -1
- package/src/render/svg-edge-split.ts +2 -2
- package/src/render/svg-multicolor.ts +4 -3
- 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
|
+
}
|