@knowvah/dot-engine 1.9.0 → 2.0.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 +232 -30
- package/dist/api/builder.d.ts +3 -0
- package/dist/api/builder.d.ts.map +1 -1
- package/dist/api/edge-ops.d.ts +7 -0
- package/dist/api/edge-ops.d.ts.map +1 -1
- package/dist/api/geometry.d.ts +5 -2
- package/dist/api/geometry.d.ts.map +1 -1
- package/dist/api.js +159 -31
- package/dist/api.js.map +3 -3
- package/dist/async/collect.d.ts +49 -0
- package/dist/async/collect.d.ts.map +1 -0
- package/dist/async/fonts.d.ts +20 -0
- package/dist/async/fonts.d.ts.map +1 -0
- package/dist/async/render-async.d.ts +91 -0
- package/dist/async/render-async.d.ts.map +1 -0
- package/dist/async/render-into.d.ts +38 -0
- package/dist/async/render-into.d.ts.map +1 -0
- package/dist/async/sanitize.d.ts +28 -0
- package/dist/async/sanitize.d.ts.map +1 -0
- package/dist/common/htmltable-types.d.ts +3 -3
- package/dist/common/htmltable-types.d.ts.map +1 -1
- package/dist/common/make-label.d.ts.map +1 -1
- package/dist/common/poly-shapes.d.ts.map +1 -1
- package/dist/common/textmeasure-factory.d.ts +2 -0
- package/dist/common/textmeasure-factory.d.ts.map +1 -1
- package/dist/common/utils-inputscale.d.ts +19 -0
- package/dist/common/utils-inputscale.d.ts.map +1 -0
- package/dist/errors.d.ts +61 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/gvc/context.d.ts +36 -4
- package/dist/gvc/context.d.ts.map +1 -1
- package/dist/gvc/device.d.ts +5 -2
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/image-resolver.d.ts +16 -16
- package/dist/gvc/image-resolver.d.ts.map +1 -1
- package/dist/gvc/job.d.ts +1 -9
- package/dist/gvc/job.d.ts.map +1 -1
- package/dist/gvc/usershape.d.ts +2 -12
- package/dist/gvc/usershape.d.ts.map +1 -1
- package/dist/index.d.ts +27 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7560 -5446
- package/dist/index.js.map +4 -4
- package/dist/label/index.d.ts.map +1 -1
- package/dist/label/node.d.ts +0 -6
- package/dist/label/node.d.ts.map +1 -1
- package/dist/label/rectangle.d.ts +1 -7
- package/dist/label/rectangle.d.ts.map +1 -1
- package/dist/layout/circo/circular.d.ts +8 -5
- package/dist/layout/circo/circular.d.ts.map +1 -1
- package/dist/layout/dot/pack-components.d.ts +0 -19
- package/dist/layout/dot/pack-components.d.ts.map +1 -1
- package/dist/layout/dot/position.d.ts +7 -2
- package/dist/layout/dot/position.d.ts.map +1 -1
- package/dist/layout/fdp/derive.d.ts.map +1 -1
- package/dist/layout/fdp/index.d.ts.map +1 -1
- package/dist/layout/fdp/init.d.ts.map +1 -1
- package/dist/layout/fdp/layout.d.ts.map +1 -1
- package/dist/layout/fdp/ports.d.ts +0 -10
- package/dist/layout/fdp/ports.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts +0 -16
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/layout/neato/adjust-info.d.ts +117 -0
- package/dist/layout/neato/adjust-info.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
- package/dist/layout/neato/constraint-adjust.d.ts +40 -0
- package/dist/layout/neato/constraint-adjust.d.ts.map +1 -0
- package/dist/layout/neato/edge-len.d.ts +20 -0
- package/dist/layout/neato/edge-len.d.ts.map +1 -0
- package/dist/layout/neato/fdp-adjust.d.ts +32 -4
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
- package/dist/layout/neato/index.d.ts +9 -6
- package/dist/layout/neato/index.d.ts.map +1 -1
- package/dist/layout/neato/init.d.ts +4 -11
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/kk-paths.d.ts +50 -0
- package/dist/layout/neato/kk-paths.d.ts.map +1 -0
- package/dist/layout/neato/kk-solve.d.ts +14 -0
- package/dist/layout/neato/kk-solve.d.ts.map +1 -0
- package/dist/layout/neato/kk.d.ts +44 -0
- package/dist/layout/neato/kk.d.ts.map +1 -0
- package/dist/layout/neato/multispline-router.d.ts.map +1 -1
- package/dist/layout/neato/poly.d.ts +50 -0
- package/dist/layout/neato/poly.d.ts.map +1 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts +20 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts.map +1 -0
- package/dist/layout/neato/sgd.d.ts +14 -9
- package/dist/layout/neato/sgd.d.ts.map +1 -1
- package/dist/layout/neato/start.d.ts +51 -0
- package/dist/layout/neato/start.d.ts.map +1 -0
- package/dist/layout/neato/vpsc-adjust.d.ts +21 -0
- package/dist/layout/neato/vpsc-adjust.d.ts.map +1 -0
- package/dist/layout/sfdp/index.d.ts.map +1 -1
- package/dist/layout/sfdp/init.d.ts +0 -9
- package/dist/layout/sfdp/init.d.ts.map +1 -1
- package/dist/layout/sfdp/spring-driver.d.ts.map +1 -1
- package/dist/layout/twopi/circle.d.ts +0 -7
- package/dist/layout/twopi/circle.d.ts.map +1 -1
- package/dist/ortho/ortho-parallel.d.ts.map +1 -1
- package/dist/ortho/trap-query.d.ts.map +1 -1
- package/dist/parser/index.d.ts +9 -5
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/render/index.d.ts +2 -0
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/public.d.ts +9 -3
- package/dist/render/public.d.ts.map +1 -1
- package/dist/render/xdot-public.d.ts +7 -2
- package/dist/render/xdot-public.d.ts.map +1 -1
- package/dist/render.js +7355 -5476
- package/dist/render.js.map +4 -4
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/vpsc/Solver.d.ts +1 -0
- package/dist/vpsc/Solver.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/builder.ts +73 -5
- package/src/api/edge-ops.ts +19 -0
- package/src/api/geometry.ts +22 -5
- package/src/async/collect.ts +204 -0
- package/src/async/fonts.ts +61 -0
- package/src/async/render-async.ts +205 -0
- package/src/async/render-into.ts +115 -0
- package/src/async/sanitize.ts +150 -0
- package/src/common/htmltable-types.ts +4 -5
- package/src/common/make-label.ts +10 -1
- package/src/common/poly-shapes.ts +4 -1
- package/src/common/textmeasure-factory.ts +11 -0
- package/src/common/utils-inputscale.ts +31 -0
- package/src/errors.ts +188 -5
- package/src/gvc/context.ts +96 -17
- package/src/gvc/device.ts +11 -6
- package/src/gvc/image-resolver.ts +25 -2
- package/src/gvc/job.ts +3 -1
- package/src/gvc/usershape.ts +6 -0
- package/src/index.ts +59 -43
- package/src/label/index.ts +6 -2
- package/src/label/node.ts +2 -1
- package/src/label/rectangle.ts +4 -2
- package/src/layout/circo/circular.ts +9 -6
- package/src/layout/dot/pack-components.ts +6 -2
- package/src/layout/dot/position.ts +21 -3
- package/src/layout/fdp/derive.ts +7 -6
- package/src/layout/fdp/index.ts +45 -5
- package/src/layout/fdp/init.ts +7 -6
- package/src/layout/fdp/layout.ts +2 -1
- package/src/layout/fdp/ports.ts +3 -2
- package/src/layout/fdp/xlayout.ts +3 -1
- package/src/layout/neato/adjust-info.ts +332 -0
- package/src/layout/neato/cdt-surface.ts +50 -30
- package/src/layout/neato/constraint-adjust.ts +466 -0
- package/src/layout/neato/edge-len.ts +36 -0
- package/src/layout/neato/fdp-adjust.ts +117 -12
- package/src/layout/neato/index.ts +36 -54
- package/src/layout/neato/init.ts +21 -36
- package/src/layout/neato/kk-paths.ts +146 -0
- package/src/layout/neato/kk-solve.ts +64 -0
- package/src/layout/neato/kk.ts +322 -0
- package/src/layout/neato/multispline-router.ts +4 -3
- package/src/layout/neato/poly.ts +493 -0
- package/src/layout/neato/sgd-dijkstra.ts +125 -0
- package/src/layout/neato/sgd.ts +63 -40
- package/src/layout/neato/start.ts +222 -0
- package/src/layout/neato/vpsc-adjust.ts +93 -0
- package/src/layout/sfdp/index.ts +4 -5
- package/src/layout/sfdp/init.ts +40 -4
- package/src/layout/sfdp/spring-driver.ts +30 -1
- package/src/layout/twopi/circle.ts +2 -1
- package/src/ortho/ortho-parallel.ts +2 -1
- package/src/ortho/trap-query.ts +2 -1
- package/src/parser/index.ts +11 -11
- package/src/render/index.ts +5 -0
- package/src/render/public.ts +24 -24
- package/src/render/svg.ts +1 -1
- package/src/render/xdot-public.ts +19 -18
- package/src/util/xml.ts +24 -30
- package/src/vpsc/Solver.ts +5 -3
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Kamada-Kawai layout (`mode=KK`): the gradient-descent solver of
|
|
4
|
+
* `kkNeato` and the `stuff.c` code it drives — `scan_graph_mode` (KK
|
|
5
|
+
* branch), `initial_positions`, `diffeq_model`, `solve_model`,
|
|
6
|
+
* `choose_node`, `move_node`, `update_arrays`, `D2E` and `solve`.
|
|
7
|
+
*
|
|
8
|
+
* C's `GD_dist`/`GD_spring`/`GD_sum_t`/`GD_t` (arrays of row pointers) are flat
|
|
9
|
+
* typed arrays here; the file-statics (`Epsilon`, `Damping`, `MaxIter`,
|
|
10
|
+
* `Initial_dist`) are fields of one {@link KkState} per run.
|
|
11
|
+
*
|
|
12
|
+
* Not ported: `Verbose` output (`total_e`, timers), `test_toggle` tracing,
|
|
13
|
+
* and `Reduce` (`-x`, a command-line flag this library has no counterpart for).
|
|
14
|
+
*
|
|
15
|
+
* @see lib/neatogen/neatoinit.c:kkNeato
|
|
16
|
+
* @see lib/neatogen/stuff.c
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { Graph } from '../../model/graph.js';
|
|
20
|
+
import type { Node } from '../../model/node.js';
|
|
21
|
+
import type { Edge } from '../../model/edge.js';
|
|
22
|
+
import { buildOutEdgeIndex } from '../../model/node.js';
|
|
23
|
+
import { drand48 } from '../../common/random.js';
|
|
24
|
+
import { lateDouble } from '../../common/nodeinit.js';
|
|
25
|
+
import { checkStart, INIT_RANDOM, INIT_REGULAR } from './start.js';
|
|
26
|
+
import { MODEL_MDS } from './init.js';
|
|
27
|
+
import { solve } from './kk-solve.js';
|
|
28
|
+
import { lenattr } from './edge-len.js';
|
|
29
|
+
import { newPathState, shortestPath, mdsModel, type Neighbour } from './kk-paths.js';
|
|
30
|
+
|
|
31
|
+
/** `Ndim`: layout dimension (this port lays out in 2-D only). */
|
|
32
|
+
const NDIM = 2;
|
|
33
|
+
/** `Damping` when the root has no `Damping` attribute. @see stuff.c:scan_graph_mode */
|
|
34
|
+
const DEFAULT_DAMPING = 0.99;
|
|
35
|
+
/** `Epsilon` is this times nV unless `epsilon` is set. @see stuff.c:scan_graph_mode */
|
|
36
|
+
const EPSILON_PER_NODE = 0.0001;
|
|
37
|
+
/** `MaxIter` is this times nV unless `maxiter` is set. @see neatoinit.c:neatoLayout */
|
|
38
|
+
const ITERATIONS_PER_NODE = 100;
|
|
39
|
+
/** @see lib/common/const.h:Spring_coeff */
|
|
40
|
+
const SPRING_COEFF = 1.0;
|
|
41
|
+
|
|
42
|
+
/** Per-run solver state. @see lib/neatogen/stuff.c (GD_*, Epsilon2, Damping) */
|
|
43
|
+
export interface KkState {
|
|
44
|
+
readonly n: number;
|
|
45
|
+
readonly nodes: readonly Node[];
|
|
46
|
+
/** `GD_dist`, n*n. */
|
|
47
|
+
readonly dist: Float64Array;
|
|
48
|
+
/** `GD_spring`, n*n. */
|
|
49
|
+
readonly spring: Float64Array;
|
|
50
|
+
/** `GD_sum_t`, n*NDIM. */
|
|
51
|
+
readonly sumT: Float64Array;
|
|
52
|
+
/** `GD_t`, n*n*NDIM. */
|
|
53
|
+
readonly t: Float64Array;
|
|
54
|
+
readonly damping: number;
|
|
55
|
+
readonly epsilon: number;
|
|
56
|
+
readonly maxIter: number;
|
|
57
|
+
/** `GD_move`. */
|
|
58
|
+
move: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** C `atoi` on an attribute value (0 when it has no leading integer). */
|
|
62
|
+
function atoi(s: string): number {
|
|
63
|
+
const v = parseInt(s, 10);
|
|
64
|
+
return Number.isNaN(v) ? 0 : v;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set `ED_dist` for every out-edge of every node; returns the total length.
|
|
69
|
+
* @see lib/neatogen/stuff.c:setEdgeLen
|
|
70
|
+
*/
|
|
71
|
+
function setEdgeLens(g: Graph, nodes: readonly Node[]): { total: number; lens: Map<Edge, number> } {
|
|
72
|
+
const out = buildOutEdgeIndex(g);
|
|
73
|
+
const lens = new Map<Edge, number>();
|
|
74
|
+
let total = 0;
|
|
75
|
+
for (const np of nodes) {
|
|
76
|
+
for (const ep of out.get(np) ?? []) {
|
|
77
|
+
const len = lenattr(ep, g);
|
|
78
|
+
lens.set(ep, len);
|
|
79
|
+
total += len;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { total, lens };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** `agget(G, "defaultdist")` or the mean edge length scaled by sqrt(nV). */
|
|
86
|
+
function initialDist(g: Graph, total: number, epsilon: number): number {
|
|
87
|
+
const str = g.attrs.get('defaultdist');
|
|
88
|
+
if (str !== undefined && str !== '') return Math.max(epsilon, parseFloat(str) || 0);
|
|
89
|
+
const nE = g.edges.length;
|
|
90
|
+
return (total / (nE > 0 ? nE : 1)) * Math.sqrt(g.nodes.size) + 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `getdouble(G, "epsilon", &Epsilon)`. @see lib/common/utils.c:getdouble */
|
|
94
|
+
function epsilonOf(g: Graph, nV: number): number {
|
|
95
|
+
const v = parseFloat(g.attrs.get('epsilon') ?? '');
|
|
96
|
+
return Number.isNaN(v) ? EPSILON_PER_NODE * nV : v;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** `agfstedge` neighbours of every node: out-edges then in-edges, loops skipped. */
|
|
100
|
+
function neighbours(g: Graph, nodes: readonly Node[], lens: Map<Edge, number>): Neighbour[][] {
|
|
101
|
+
const out = buildOutEdgeIndex(g);
|
|
102
|
+
const inn = new Map<Node, Edge[]>();
|
|
103
|
+
for (const e of g.edges) {
|
|
104
|
+
if (e.head === e.tail) continue;
|
|
105
|
+
const l = inn.get(e.head);
|
|
106
|
+
if (l === undefined) inn.set(e.head, [e]);
|
|
107
|
+
else l.push(e);
|
|
108
|
+
}
|
|
109
|
+
return nodes.map((np) => {
|
|
110
|
+
const ins = (inn.get(np) ?? []).sort((a, b) => (a.tail.id - b.tail.id) || (a.seq - b.seq));
|
|
111
|
+
const outs = (out.get(np) ?? []).filter((e) => e.head !== e.tail);
|
|
112
|
+
return [...outs, ...ins].map((e) => ({
|
|
113
|
+
id: (e.tail === np ? e.head : e.tail).info.id!,
|
|
114
|
+
len: lens.get(e)!,
|
|
115
|
+
}));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Seed the matrices and return the state. @see lib/neatogen/stuff.c:scan_graph_mode */
|
|
120
|
+
function scanGraphMode(g: Graph, maxIter: number): { st: KkState; adj: Neighbour[][]; ini: number; lens: Map<Edge, number> } {
|
|
121
|
+
const nodes = [...g.nodes.values()];
|
|
122
|
+
const n = nodes.length;
|
|
123
|
+
nodes.forEach((np, i) => { np.info.id = i; np.info.heapindex = -1; });
|
|
124
|
+
const epsilon = epsilonOf(g, n);
|
|
125
|
+
const damp = g.root.attrs.get('Damping');
|
|
126
|
+
const { total, lens } = setEdgeLens(g, nodes);
|
|
127
|
+
const ini = initialDist(g, total, epsilon);
|
|
128
|
+
const st: KkState = {
|
|
129
|
+
n, nodes, dist: new Float64Array(n * n).fill(ini), spring: new Float64Array(n * n).fill(1),
|
|
130
|
+
sumT: new Float64Array(n * NDIM).fill(1), t: new Float64Array(n * n * NDIM),
|
|
131
|
+
damping: damp === undefined ? DEFAULT_DAMPING : parseFloat(damp) || 0,
|
|
132
|
+
epsilon, maxIter, move: 0,
|
|
133
|
+
};
|
|
134
|
+
return { st, adj: neighbours(g, nodes, lens), ini, lens };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** @see lib/neatogen/stuff.c:randompos (called with nG = 1 by initial_positions) */
|
|
138
|
+
function randompos(np: Node): void {
|
|
139
|
+
np.info.pos![0] = drand48();
|
|
140
|
+
np.info.pos![1] = drand48();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** @see lib/neatogen/stuff.c:initial_positions */
|
|
144
|
+
function initialPositions(g: Graph, st: KkState): void {
|
|
145
|
+
const init = checkStart(g, st.n, INIT_RANDOM);
|
|
146
|
+
if (init === INIT_REGULAR) return;
|
|
147
|
+
// C: start=self warns once per process; start.ts:checkStartAttr already did.
|
|
148
|
+
for (const np of st.nodes) {
|
|
149
|
+
if (np.info.posSet === true || np.info.pinned === true) continue;
|
|
150
|
+
randompos(np);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** `distvec`: returns |p0-p1| and leaves p0-p1 in `del`. @see stuff.c:distvec */
|
|
155
|
+
function distvec(p0: number[], p1: number[], del: Float64Array): number {
|
|
156
|
+
let dist = 0.0;
|
|
157
|
+
for (let k = 0; k < NDIM; k++) {
|
|
158
|
+
del[k] = p0[k]! - p1[k]!;
|
|
159
|
+
dist += del[k]! * del[k]!;
|
|
160
|
+
}
|
|
161
|
+
return Math.sqrt(dist);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** `ED_factor` of the first edge `agfindedge(G, a, b)` finds, else 1. */
|
|
165
|
+
function springFactors(g: Graph): (a: Node, b: Node) => number | undefined {
|
|
166
|
+
const byEnds = new Map<string, number>();
|
|
167
|
+
const undirected = g.kind.endsWith('undirected');
|
|
168
|
+
for (const e of g.edges) {
|
|
169
|
+
const key = `${e.tail.info.id}:${e.head.info.id}`;
|
|
170
|
+
if (!byEnds.has(key)) {
|
|
171
|
+
byEnds.set(key, e.info.factor ?? lateDouble(e.attrs.get('weight'), 1.0, 1.0));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return (a, b) => byEnds.get(`${a.info.id}:${b.info.id}`)
|
|
175
|
+
?? (undirected ? byEnds.get(`${b.info.id}:${a.info.id}`) : undefined);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Row `i` of `GD_t`/`GD_sum_t` for node `i`. @see lib/neatogen/stuff.c:diffeq_model */
|
|
179
|
+
function fillRow(st: KkState, i: number, del: Float64Array): void {
|
|
180
|
+
const vi = st.nodes[i]!;
|
|
181
|
+
for (let j = 0; j < st.n; j++) {
|
|
182
|
+
if (i === j) continue;
|
|
183
|
+
const dist = distvec(vi.info.pos!, st.nodes[j]!.info.pos!, del);
|
|
184
|
+
for (let k = 0; k < NDIM; k++) {
|
|
185
|
+
const v = st.spring[i * st.n + j]! * (del[k]! - (st.dist[i * st.n + j]! * del[k]!) / dist);
|
|
186
|
+
st.t[(i * st.n + j) * NDIM + k] = v;
|
|
187
|
+
st.sumT[i * NDIM + k]! += v;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** @see lib/neatogen/stuff.c:diffeq_model */
|
|
193
|
+
function diffeqModel(g: Graph, st: KkState): void {
|
|
194
|
+
const { n } = st;
|
|
195
|
+
const factor = springFactors(g);
|
|
196
|
+
for (let i = 0; i < n; i++) {
|
|
197
|
+
for (let j = 0; j < i; j++) {
|
|
198
|
+
let f = SPRING_COEFF / (st.dist[i * n + j]! * st.dist[i * n + j]!);
|
|
199
|
+
const w = factor(st.nodes[i]!, st.nodes[j]!);
|
|
200
|
+
if (w !== undefined) f *= w;
|
|
201
|
+
st.spring[i * n + j] = f;
|
|
202
|
+
st.spring[j * n + i] = f;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
st.sumT.fill(0);
|
|
206
|
+
const del = new Float64Array(NDIM);
|
|
207
|
+
for (let i = 0; i < n; i++) fillRow(st, i, del);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Recompute node `i`'s row and the mirrored column. @see stuff.c:update_arrays */
|
|
211
|
+
function updateArrays(st: KkState, i: number): void {
|
|
212
|
+
const { n } = st;
|
|
213
|
+
const del = new Float64Array(NDIM);
|
|
214
|
+
const vi = st.nodes[i]!;
|
|
215
|
+
for (let k = 0; k < NDIM; k++) st.sumT[i * NDIM + k] = 0.0;
|
|
216
|
+
for (let j = 0; j < n; j++) {
|
|
217
|
+
if (i === j) continue;
|
|
218
|
+
const dist = distvec(vi.info.pos!, st.nodes[j]!.info.pos!, del);
|
|
219
|
+
for (let k = 0; k < NDIM; k++) {
|
|
220
|
+
const tij = st.spring[i * n + j]! * (del[k]! - (st.dist[i * n + j]! * del[k]!) / dist);
|
|
221
|
+
st.t[(i * n + j) * NDIM + k] = tij;
|
|
222
|
+
st.sumT[i * NDIM + k]! += tij;
|
|
223
|
+
const old = st.t[(j * n + i) * NDIM + k]!;
|
|
224
|
+
st.t[(j * n + i) * NDIM + k] = -tij;
|
|
225
|
+
st.sumT[j * NDIM + k]! += -tij - old;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Hessian of node `m`'s energy into `M` (NDIM x NDIM). @see stuff.c:D2E */
|
|
231
|
+
function d2e(st: KkState, m: number, M: Float64Array): void {
|
|
232
|
+
const { n } = st;
|
|
233
|
+
const vn = st.nodes[m]!;
|
|
234
|
+
const t = new Float64Array(NDIM);
|
|
235
|
+
M.fill(0);
|
|
236
|
+
for (let i = 0; i < n; i++) {
|
|
237
|
+
if (m === i) continue;
|
|
238
|
+
const vi = st.nodes[i]!;
|
|
239
|
+
let sq = 0.0;
|
|
240
|
+
for (let k = 0; k < NDIM; k++) {
|
|
241
|
+
t[k] = vn.info.pos![k]! - vi.info.pos![k]!;
|
|
242
|
+
sq += t[k]! * t[k]!;
|
|
243
|
+
}
|
|
244
|
+
const scale = 1 / Math.pow(sq, 1.5);
|
|
245
|
+
const K = st.spring[m * n + i]!;
|
|
246
|
+
const D = st.dist[m * n + i]!;
|
|
247
|
+
for (let k = 0; k < NDIM; k++) {
|
|
248
|
+
for (let l = 0; l < k; l++) M[l * NDIM + k]! += K * D * t[k]! * t[l]! * scale;
|
|
249
|
+
M[k * NDIM + k]! += K * (1.0 - D * (sq - t[k]! * t[k]!) * scale);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (let k = 1; k < NDIM; k++) {
|
|
253
|
+
for (let l = 0; l < k; l++) M[k * NDIM + l] = M[l * NDIM + k]!;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Index of the unpinned node with the largest gradient, or -1. @see stuff.c:choose_node */
|
|
258
|
+
function chooseNode(st: KkState): number {
|
|
259
|
+
if (st.move >= st.maxIter) return -1;
|
|
260
|
+
let max = 0.0;
|
|
261
|
+
let choice = -1;
|
|
262
|
+
for (let i = 0; i < st.n; i++) {
|
|
263
|
+
if (st.nodes[i]!.info.pinned === true) continue;
|
|
264
|
+
let m = 0.0;
|
|
265
|
+
for (let k = 0; k < NDIM; k++) m += st.sumT[i * NDIM + k]! * st.sumT[i * NDIM + k]!;
|
|
266
|
+
if (m > max) { choice = i; max = m; }
|
|
267
|
+
}
|
|
268
|
+
return max < st.epsilon * st.epsilon ? -1 : choice;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Newton step for node `m`. @see lib/neatogen/stuff.c:move_node */
|
|
272
|
+
function moveNode(st: KkState, m: number): void {
|
|
273
|
+
const a = new Float64Array(NDIM * NDIM);
|
|
274
|
+
const b = new Float64Array(NDIM);
|
|
275
|
+
const c = new Float64Array(NDIM);
|
|
276
|
+
d2e(st, m, a);
|
|
277
|
+
for (let i = 0; i < NDIM; i++) c[i] = -st.sumT[m * NDIM + i]!;
|
|
278
|
+
solve(a, b, c, NDIM);
|
|
279
|
+
const pos = st.nodes[m]!.info.pos!;
|
|
280
|
+
for (let i = 0; i < NDIM; i++) {
|
|
281
|
+
b[i] = (st.damping + 2 * (1 - st.damping) * drand48()) * b[i]!;
|
|
282
|
+
pos[i]! += b[i]!;
|
|
283
|
+
}
|
|
284
|
+
st.move++;
|
|
285
|
+
updateArrays(st, m);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** @see lib/neatogen/stuff.c:solve_model */
|
|
289
|
+
function solveModel(g: Graph, st: KkState): void {
|
|
290
|
+
for (let m = chooseNode(st); m >= 0; m = chooseNode(st)) moveNode(st, m);
|
|
291
|
+
if (st.move === st.maxIter) {
|
|
292
|
+
console.warn(`Max. iterations (${st.maxIter}) reached on graph ${g.name}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** `mds_model` inputs: out-edges with their 1-based `AGSEQ` ends and length. */
|
|
297
|
+
function mdsEdges(
|
|
298
|
+
g: Graph, nodes: readonly Node[], lens: Map<Edge, number>,
|
|
299
|
+
): { tailSeq: number; headSeq: number; len: number }[] {
|
|
300
|
+
const out = buildOutEdgeIndex(g);
|
|
301
|
+
return nodes.flatMap((v) => (out.get(v) ?? []).map((e) => (
|
|
302
|
+
{ tailSeq: e.tail.id + 1, headSeq: e.head.id + 1, len: lens.get(e)! }
|
|
303
|
+
)));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Solve the layout with Kamada-Kawai gradient descent. The subset and (connected)
|
|
308
|
+
* circuit distance models stay loud in `start.ts:assertSupported`; a
|
|
309
|
+
* disconnected circuit falls back to shortest paths there too.
|
|
310
|
+
* @see lib/neatogen/neatoinit.c:kkNeato
|
|
311
|
+
*/
|
|
312
|
+
export function kkNeato(g: Graph, nG: number, model: number): void {
|
|
313
|
+
const maxiter = g.attrs.get('maxiter');
|
|
314
|
+
const maxIter = maxiter !== undefined ? atoi(maxiter) : ITERATIONS_PER_NODE * g.nodes.size;
|
|
315
|
+
if (nG < 2 || maxIter < 0) return;
|
|
316
|
+
const { st, adj, ini, lens } = scanGraphMode(g, maxIter);
|
|
317
|
+
shortestPath(newPathState(st.n, st.dist, adj, ini));
|
|
318
|
+
if (model === MODEL_MDS) mdsModel(st.dist, st.n, mdsEdges(g, st.nodes, lens));
|
|
319
|
+
initialPositions(g, st);
|
|
320
|
+
diffeqModel(g, st);
|
|
321
|
+
solveModel(g, st);
|
|
322
|
+
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import type { Point, Poly } from '../../pathplan/types.js';
|
|
20
20
|
import { wind } from '../../pathplan/visibility.js';
|
|
21
21
|
import { mkSurface } from './cdt-surface.js';
|
|
22
|
+
import { InternalError } from '../../errors.js';
|
|
22
23
|
|
|
23
24
|
/** index pair. @see multispline.c:ipair */
|
|
24
25
|
export interface Ipair { i: number; j: number }
|
|
@@ -165,7 +166,7 @@ export function mkRouter(obsp: Poly[]): Router {
|
|
|
165
166
|
const y = pts.map((p) => p.y);
|
|
166
167
|
const sf = mkSurface(x, y, npts, segs, npts);
|
|
167
168
|
if (sf === null) {
|
|
168
|
-
throw new
|
|
169
|
+
throw new InternalError('mkRouter: constrained triangulation failed');
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
/* map from obstacle side to its unique adjacent kept triangle */
|
|
@@ -193,7 +194,7 @@ export function mkRouter(obsp: Poly[]): Router {
|
|
|
193
194
|
/** @see multispline.c:findMap */
|
|
194
195
|
export function findMap(trimap: Map<number, number>, a: number, b: number): number {
|
|
195
196
|
const t = trimap.get(segKey(a, b));
|
|
196
|
-
if (t === undefined) throw new
|
|
197
|
+
if (t === undefined) throw new InternalError('findMap: no triangle for segment');
|
|
197
198
|
return t;
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -286,7 +287,7 @@ export function edgeToSeg(tg: Tgraph, i: number, j: number): Ipair {
|
|
|
286
287
|
const ep = tg.edges[k]!;
|
|
287
288
|
if (ep.t === j || ep.h === j) return ep.seg;
|
|
288
289
|
}
|
|
289
|
-
throw new
|
|
290
|
+
throw new InternalError('edgeToSeg: no edge between triangles');
|
|
290
291
|
}
|
|
291
292
|
|
|
292
293
|
// ---------------------------------------------------------------------------
|