@mark1russell7/highways 0.9.3 → 0.9.4
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 +49 -0
- package/build/index.d.ts +406 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3048 -443
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bench/screen/exercise.ts +2 -0
- package/src/bench/screen/set.ts +58 -1
- package/src/families/cases.ts +17 -9
- package/src/families/coverage.ts +502 -0
- package/src/families/index.ts +4 -1
- package/src/highways/bronchial/index.ts +144 -0
- package/src/highways/index.ts +2 -0
- package/src/highways/knee/index.ts +154 -0
- package/src/highways/knee/root.ts +19 -0
- package/src/index.ts +2 -0
- package/src/levels/venation/index.ts +191 -0
- package/src/routers/annealing/index.ts +260 -0
- package/src/routers/frames.ts +80 -16
- package/src/routers/gutter.ts +545 -0
- package/src/routers/index.ts +5 -2
- package/src/routers/shared.ts +40 -2
- package/src/routers/tight/index.ts +122 -0
- package/src/strategies/builtins.ts +57 -2
- package/src/strategies/schemas.ts +54 -0
- package/src/whole/portfolio/index.ts +108 -0
package/build/index.js
CHANGED
|
@@ -5212,6 +5212,533 @@ function sideEnds(input, options = {}) {
|
|
|
5212
5212
|
}
|
|
5213
5213
|
return out;
|
|
5214
5214
|
}
|
|
5215
|
+
// src/routers/gutter.ts
|
|
5216
|
+
/** The default cost of one bend of the gutter mode. */
|
|
5217
|
+
const GUTTER_BEND_COST = 16;
|
|
5218
|
+
const EPS$6 = 1e-6;
|
|
5219
|
+
/** The directions of a step: east, south, west and north, with the y axis down. */
|
|
5220
|
+
const DX = [
|
|
5221
|
+
1,
|
|
5222
|
+
0,
|
|
5223
|
+
-1,
|
|
5224
|
+
0
|
|
5225
|
+
];
|
|
5226
|
+
const DY = [
|
|
5227
|
+
0,
|
|
5228
|
+
1,
|
|
5229
|
+
0,
|
|
5230
|
+
-1
|
|
5231
|
+
];
|
|
5232
|
+
/**
|
|
5233
|
+
* This function gives the orthogonal route of each edge in the free room of a frame: the box `frame` minus the boxes of `nodes`.
|
|
5234
|
+
* Each route starts at the plug of the wall, and goes along the normal of the wall into the frame. It ends on a side of its end node.
|
|
5235
|
+
*
|
|
5236
|
+
* The route goes on a grid of lines. A line goes through the middle of each free band between the box edges, each plug and each entry.
|
|
5237
|
+
* A shortest path with a cost for each bend (`bendCost`) gives the route. Then the routes that share one gap get a lane each.
|
|
5238
|
+
* An edge without a path through the free room gets `undefined`.
|
|
5239
|
+
*/
|
|
5240
|
+
function gutterRoutes(frame, nodes, edges, options = {}) {
|
|
5241
|
+
const bend = options.bendCost ?? 16;
|
|
5242
|
+
const boxes = nodes.map((n) => n.box).filter((b) => b.w > EPS$6 && b.h > EPS$6);
|
|
5243
|
+
const entries = edges.map(entriesOf);
|
|
5244
|
+
const xs = /* @__PURE__ */ new Set();
|
|
5245
|
+
const ys = /* @__PURE__ */ new Set();
|
|
5246
|
+
for (const [lines, lo, size, key] of [[
|
|
5247
|
+
xs,
|
|
5248
|
+
frame.x,
|
|
5249
|
+
frame.w,
|
|
5250
|
+
"x"
|
|
5251
|
+
], [
|
|
5252
|
+
ys,
|
|
5253
|
+
frame.y,
|
|
5254
|
+
frame.h,
|
|
5255
|
+
"y"
|
|
5256
|
+
]]) {
|
|
5257
|
+
const cuts = [lo, lo + size];
|
|
5258
|
+
for (const b of boxes) for (const v of key === "x" ? [b.x, b.x + b.w] : [b.y, b.y + b.h]) if (v > lo && v < lo + size) cuts.push(v);
|
|
5259
|
+
cuts.sort((a, b) => a - b);
|
|
5260
|
+
for (let i = 1; i < cuts.length; i++) if (cuts[i] - cuts[i - 1] > EPS$6) lines.add((cuts[i] + cuts[i - 1]) / 2);
|
|
5261
|
+
}
|
|
5262
|
+
edges.forEach((e, i) => {
|
|
5263
|
+
for (const { point, normal } of [{
|
|
5264
|
+
point: e.plug.point,
|
|
5265
|
+
normal: e.plug.normal
|
|
5266
|
+
}, ...entries[i]]) (normal.x !== 0 ? ys : xs).add(normal.x !== 0 ? point.y : point.x);
|
|
5267
|
+
});
|
|
5268
|
+
const grid = new Grid$1(frame, boxes, [...xs].sort((a, b) => a - b), [...ys].sort((a, b) => a - b));
|
|
5269
|
+
return lanes(frame, boxes, edges, entries, edges.map((e, i) => stubOnly(e, boxes) ?? grid.path(e.plug, e.end.box, entries[i], bend)), options.laneGap ?? 5);
|
|
5270
|
+
}
|
|
5271
|
+
/** This function gives the entries of the end node. They are its fixed plug, or the middles of its west and east sides and two points on each long side. */
|
|
5272
|
+
function entriesOf(e) {
|
|
5273
|
+
if (e.port) return [{
|
|
5274
|
+
point: e.port.point,
|
|
5275
|
+
normal: e.port.normal,
|
|
5276
|
+
fixed: true
|
|
5277
|
+
}];
|
|
5278
|
+
const b = e.end.box;
|
|
5279
|
+
const cy = b.y + b.h / 2;
|
|
5280
|
+
const d = Math.min(6, b.w / 4);
|
|
5281
|
+
return [
|
|
5282
|
+
["west", {
|
|
5283
|
+
x: b.x,
|
|
5284
|
+
y: cy
|
|
5285
|
+
}],
|
|
5286
|
+
["east", {
|
|
5287
|
+
x: b.x + b.w,
|
|
5288
|
+
y: cy
|
|
5289
|
+
}],
|
|
5290
|
+
["north", {
|
|
5291
|
+
x: b.x + d,
|
|
5292
|
+
y: b.y
|
|
5293
|
+
}],
|
|
5294
|
+
["north", {
|
|
5295
|
+
x: b.x + b.w - d,
|
|
5296
|
+
y: b.y
|
|
5297
|
+
}],
|
|
5298
|
+
["south", {
|
|
5299
|
+
x: b.x + d,
|
|
5300
|
+
y: b.y + b.h
|
|
5301
|
+
}],
|
|
5302
|
+
["south", {
|
|
5303
|
+
x: b.x + b.w - d,
|
|
5304
|
+
y: b.y + b.h
|
|
5305
|
+
}]
|
|
5306
|
+
].filter(([s]) => e.side === void 0 || s === e.side).map(([s, point]) => ({
|
|
5307
|
+
point,
|
|
5308
|
+
normal: SIDE_NORMAL[s],
|
|
5309
|
+
fixed: false
|
|
5310
|
+
}));
|
|
5311
|
+
}
|
|
5312
|
+
/**
|
|
5313
|
+
* This function gives the route of an edge whose plug is level with its end node. The way straight in from the plug must meet that node first.
|
|
5314
|
+
* The route is the stub only: from the plug to the side of the node. A node that touches the wall gives a stub of length 0.
|
|
5315
|
+
*/
|
|
5316
|
+
function stubOnly(e, boxes) {
|
|
5317
|
+
if (e.port) return void 0;
|
|
5318
|
+
const p = e.plug.point;
|
|
5319
|
+
const b = e.end.box;
|
|
5320
|
+
const n = e.plug.normal;
|
|
5321
|
+
let q;
|
|
5322
|
+
if (n.x !== 0) {
|
|
5323
|
+
if (!(p.y > b.y + EPS$6 && p.y < b.y + b.h - EPS$6)) return void 0;
|
|
5324
|
+
q = {
|
|
5325
|
+
x: n.x > 0 ? b.x + b.w : b.x,
|
|
5326
|
+
y: p.y
|
|
5327
|
+
};
|
|
5328
|
+
if ((q.x - p.x) * n.x > EPS$6) return void 0;
|
|
5329
|
+
} else {
|
|
5330
|
+
if (!(p.x > b.x + EPS$6 && p.x < b.x + b.w - EPS$6)) return void 0;
|
|
5331
|
+
q = {
|
|
5332
|
+
x: p.x,
|
|
5333
|
+
y: n.y > 0 ? b.y + b.h : b.y
|
|
5334
|
+
};
|
|
5335
|
+
if ((q.y - p.y) * n.y > EPS$6) return void 0;
|
|
5336
|
+
}
|
|
5337
|
+
for (const o of boxes) if (o !== b && blocks(p, q, o)) return void 0;
|
|
5338
|
+
return [p, q];
|
|
5339
|
+
}
|
|
5340
|
+
/** This function tells if the axis-parallel segment `ab` meets the closed box `o` along a part longer than a tiny length. A segment along a side meets the box. */
|
|
5341
|
+
function blocks(a, b, o) {
|
|
5342
|
+
if (a.y === b.y) {
|
|
5343
|
+
if (a.y < o.y - EPS$6 || a.y > o.y + o.h + EPS$6) return false;
|
|
5344
|
+
return Math.min(Math.max(a.x, b.x), o.x + o.w) - Math.max(Math.min(a.x, b.x), o.x) > EPS$6;
|
|
5345
|
+
}
|
|
5346
|
+
if (a.x < o.x - EPS$6 || a.x > o.x + o.w + EPS$6) return false;
|
|
5347
|
+
return Math.min(Math.max(a.y, b.y), o.y + o.h) - Math.max(Math.min(a.y, b.y), o.y) > EPS$6;
|
|
5348
|
+
}
|
|
5349
|
+
/** This function gives the direction index of a unit normal: 0 east, 1 south, 2 west, 3 north. */
|
|
5350
|
+
function dirOf(n) {
|
|
5351
|
+
return n.x > 0 ? 0 : n.y > 0 ? 1 : n.x < 0 ? 2 : 3;
|
|
5352
|
+
}
|
|
5353
|
+
/** The grid of the free room: the crossings of the lines that are strictly in the frame and not in a closed box. */
|
|
5354
|
+
var Grid$1 = class {
|
|
5355
|
+
#xs;
|
|
5356
|
+
#ys;
|
|
5357
|
+
#free;
|
|
5358
|
+
#boxes;
|
|
5359
|
+
constructor(frame, boxes, xs, ys) {
|
|
5360
|
+
this.#xs = xs;
|
|
5361
|
+
this.#ys = ys;
|
|
5362
|
+
this.#boxes = boxes;
|
|
5363
|
+
this.#free = new Uint8Array(xs.length * ys.length);
|
|
5364
|
+
for (let i = 0; i < ys.length; i++) {
|
|
5365
|
+
const y = ys[i];
|
|
5366
|
+
if (y <= frame.y + EPS$6 || y >= frame.y + frame.h - EPS$6) continue;
|
|
5367
|
+
for (let j = 0; j < xs.length; j++) {
|
|
5368
|
+
const x = xs[j];
|
|
5369
|
+
if (x <= frame.x + EPS$6 || x >= frame.x + frame.w - EPS$6) continue;
|
|
5370
|
+
let free = true;
|
|
5371
|
+
for (const b of boxes) if (x >= b.x - EPS$6 && x <= b.x + b.w + EPS$6 && y >= b.y - EPS$6 && y <= b.y + b.h + EPS$6) {
|
|
5372
|
+
free = false;
|
|
5373
|
+
break;
|
|
5374
|
+
}
|
|
5375
|
+
if (free) this.#free[i * xs.length + j] = 1;
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5378
|
+
}
|
|
5379
|
+
#point(id) {
|
|
5380
|
+
const nx = this.#xs.length;
|
|
5381
|
+
return {
|
|
5382
|
+
x: this.#xs[id % nx],
|
|
5383
|
+
y: this.#ys[Math.floor(id / nx)]
|
|
5384
|
+
};
|
|
5385
|
+
}
|
|
5386
|
+
/** This method gives the neighbour of the crossing `id` in the direction `d`, or -1. Two free crossings next to each other on a line have no box between them. */
|
|
5387
|
+
#step(id, d) {
|
|
5388
|
+
const nx = this.#xs.length;
|
|
5389
|
+
const i = Math.floor(id / nx);
|
|
5390
|
+
const j = id % nx;
|
|
5391
|
+
const ni = i + DY[d];
|
|
5392
|
+
const nj = j + DX[d];
|
|
5393
|
+
if (ni < 0 || nj < 0 || ni >= this.#ys.length || nj >= nx) return -1;
|
|
5394
|
+
const k = ni * nx + nj;
|
|
5395
|
+
return this.#free[k] ? k : -1;
|
|
5396
|
+
}
|
|
5397
|
+
/** This method gives the first free crossing from the point `p` in the direction `d`. The way to that crossing must be clear. */
|
|
5398
|
+
#enter(p, d, skip) {
|
|
5399
|
+
const nx = this.#xs.length;
|
|
5400
|
+
const along = d % 2 === 0;
|
|
5401
|
+
const line = along ? this.#ys.indexOf(p.y) : this.#xs.indexOf(p.x);
|
|
5402
|
+
if (line < 0) return -1;
|
|
5403
|
+
const list = along ? this.#xs : this.#ys;
|
|
5404
|
+
const v = along ? p.x : p.y;
|
|
5405
|
+
const sign = d < 2 ? 1 : -1;
|
|
5406
|
+
let k = -1;
|
|
5407
|
+
if (sign > 0) {
|
|
5408
|
+
for (let m = 0; m < list.length; m++) if (list[m] > v + EPS$6) {
|
|
5409
|
+
k = m;
|
|
5410
|
+
break;
|
|
5411
|
+
}
|
|
5412
|
+
} else for (let m = list.length - 1; m >= 0; m--) if (list[m] < v - EPS$6) {
|
|
5413
|
+
k = m;
|
|
5414
|
+
break;
|
|
5415
|
+
}
|
|
5416
|
+
if (k < 0) return -1;
|
|
5417
|
+
const id = along ? line * nx + k : k * nx + line;
|
|
5418
|
+
if (!this.#free[id]) return -1;
|
|
5419
|
+
const q = this.#point(id);
|
|
5420
|
+
for (const b of this.#boxes) if (b !== skip && blocks(p, q, b)) return -1;
|
|
5421
|
+
return id;
|
|
5422
|
+
}
|
|
5423
|
+
/**
|
|
5424
|
+
* This method gives the shortest path from the plug into the end box, with the cost `bend` for each bend, or `undefined`.
|
|
5425
|
+
* The path leaves the plug along the inward normal, and it enters an entry along the inward normal of the side.
|
|
5426
|
+
*/
|
|
5427
|
+
path(plug, end, entries, bend) {
|
|
5428
|
+
const n = this.#free.length;
|
|
5429
|
+
const d0 = dirOf({
|
|
5430
|
+
x: -plug.normal.x,
|
|
5431
|
+
y: -plug.normal.y
|
|
5432
|
+
});
|
|
5433
|
+
const first = this.#enter(plug.point, d0);
|
|
5434
|
+
if (first < 0) return void 0;
|
|
5435
|
+
const goals = [];
|
|
5436
|
+
for (const e of entries) {
|
|
5437
|
+
const id = this.#enter(e.point, dirOf(e.normal), end);
|
|
5438
|
+
if (id >= 0) goals.push({
|
|
5439
|
+
id,
|
|
5440
|
+
dir: (dirOf(e.normal) + 2) % 4,
|
|
5441
|
+
point: e.point
|
|
5442
|
+
});
|
|
5443
|
+
}
|
|
5444
|
+
if (goals.length === 0) return void 0;
|
|
5445
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
5446
|
+
goals.forEach((g, k) => {
|
|
5447
|
+
const list = byNode.get(g.id);
|
|
5448
|
+
if (list) list.push(k);
|
|
5449
|
+
else byNode.set(g.id, [k]);
|
|
5450
|
+
});
|
|
5451
|
+
const states = 4 * n + goals.length;
|
|
5452
|
+
const cost = new Float64Array(states).fill(Infinity);
|
|
5453
|
+
const prev = new Int32Array(states).fill(-1);
|
|
5454
|
+
const heap = new Heap();
|
|
5455
|
+
const s0 = first * 4 + d0;
|
|
5456
|
+
cost[s0] = dist$1(plug.point, this.#point(first));
|
|
5457
|
+
heap.push(cost[s0], s0);
|
|
5458
|
+
let found = -1;
|
|
5459
|
+
while (heap.size > 0) {
|
|
5460
|
+
const [c, s] = heap.pop();
|
|
5461
|
+
if (c > cost[s]) continue;
|
|
5462
|
+
if (s >= 4 * n) {
|
|
5463
|
+
found = s;
|
|
5464
|
+
break;
|
|
5465
|
+
}
|
|
5466
|
+
const id = s >> 2;
|
|
5467
|
+
const d = s & 3;
|
|
5468
|
+
const here = this.#point(id);
|
|
5469
|
+
for (const k of byNode.get(id) ?? []) {
|
|
5470
|
+
const g = goals[k];
|
|
5471
|
+
if (g.dir === (d + 2) % 4) continue;
|
|
5472
|
+
const gs = 4 * n + k;
|
|
5473
|
+
const gc = c + dist$1(here, g.point) + (g.dir === d ? 0 : bend);
|
|
5474
|
+
if (gc < cost[gs]) {
|
|
5475
|
+
cost[gs] = gc;
|
|
5476
|
+
prev[gs] = s;
|
|
5477
|
+
heap.push(gc, gs);
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
for (let nd = 0; nd < 4; nd++) {
|
|
5481
|
+
if (nd === (d + 2) % 4) continue;
|
|
5482
|
+
const next = this.#step(id, nd);
|
|
5483
|
+
if (next < 0) continue;
|
|
5484
|
+
const ns = next * 4 + nd;
|
|
5485
|
+
const nc = c + dist$1(here, this.#point(next)) + (nd === d ? 0 : bend);
|
|
5486
|
+
if (nc < cost[ns]) {
|
|
5487
|
+
cost[ns] = nc;
|
|
5488
|
+
prev[ns] = s;
|
|
5489
|
+
heap.push(nc, ns);
|
|
5490
|
+
}
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
if (found < 0) return void 0;
|
|
5494
|
+
const pts = [goals[found - 4 * n].point];
|
|
5495
|
+
for (let s = prev[found]; s >= 0; s = prev[s]) pts.push(this.#point(s >> 2));
|
|
5496
|
+
pts.push(plug.point);
|
|
5497
|
+
return simplify(pts.reverse());
|
|
5498
|
+
}
|
|
5499
|
+
};
|
|
5500
|
+
function dist$1(a, b) {
|
|
5501
|
+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
5502
|
+
}
|
|
5503
|
+
/** This function takes out each point between two collinear legs, and each repeated point. */
|
|
5504
|
+
function simplify(pts) {
|
|
5505
|
+
const out = [];
|
|
5506
|
+
for (const p of pts) {
|
|
5507
|
+
const a = out[out.length - 1];
|
|
5508
|
+
if (a && a.x === p.x && a.y === p.y) continue;
|
|
5509
|
+
const b = out[out.length - 2];
|
|
5510
|
+
if (a && b && (b.x === a.x && a.x === p.x || b.y === a.y && a.y === p.y)) out[out.length - 1] = p;
|
|
5511
|
+
else out.push(p);
|
|
5512
|
+
}
|
|
5513
|
+
return out;
|
|
5514
|
+
}
|
|
5515
|
+
/** A binary min-heap of (cost, state) pairs. Equal costs come out by the state, so the order is deterministic. */
|
|
5516
|
+
var Heap = class {
|
|
5517
|
+
#c = [];
|
|
5518
|
+
#s = [];
|
|
5519
|
+
get size() {
|
|
5520
|
+
return this.#c.length;
|
|
5521
|
+
}
|
|
5522
|
+
#less(i, j) {
|
|
5523
|
+
return this.#c[i] < this.#c[j] || this.#c[i] === this.#c[j] && this.#s[i] < this.#s[j];
|
|
5524
|
+
}
|
|
5525
|
+
#swap(i, j) {
|
|
5526
|
+
[this.#c[i], this.#c[j]] = [this.#c[j], this.#c[i]];
|
|
5527
|
+
[this.#s[i], this.#s[j]] = [this.#s[j], this.#s[i]];
|
|
5528
|
+
}
|
|
5529
|
+
push(c, s) {
|
|
5530
|
+
this.#c.push(c);
|
|
5531
|
+
this.#s.push(s);
|
|
5532
|
+
for (let i = this.#c.length - 1; i > 0;) {
|
|
5533
|
+
const p = i - 1 >> 1;
|
|
5534
|
+
if (!this.#less(i, p)) break;
|
|
5535
|
+
this.#swap(i, p);
|
|
5536
|
+
i = p;
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
pop() {
|
|
5540
|
+
const top = [this.#c[0], this.#s[0]];
|
|
5541
|
+
const c = this.#c.pop();
|
|
5542
|
+
const s = this.#s.pop();
|
|
5543
|
+
if (this.#c.length > 0) {
|
|
5544
|
+
this.#c[0] = c;
|
|
5545
|
+
this.#s[0] = s;
|
|
5546
|
+
for (let i = 0;;) {
|
|
5547
|
+
const l = 2 * i + 1;
|
|
5548
|
+
const r = l + 1;
|
|
5549
|
+
let m = i;
|
|
5550
|
+
if (l < this.#c.length && this.#less(l, m)) m = l;
|
|
5551
|
+
if (r < this.#c.length && this.#less(r, m)) m = r;
|
|
5552
|
+
if (m === i) break;
|
|
5553
|
+
this.#swap(i, m);
|
|
5554
|
+
i = m;
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
return top;
|
|
5558
|
+
}
|
|
5559
|
+
};
|
|
5560
|
+
/**
|
|
5561
|
+
* This function gives each leg that shares a gap with legs of other routes its own lane. The lanes are evenly spaced in the gap,
|
|
5562
|
+
* at most `laneGap` apart, in an order that gives no crossing where it can. A leg from a plug of the wall and a leg into a fixed plug do not move.
|
|
5563
|
+
*/
|
|
5564
|
+
function lanes(frame, boxes, edges, entries, routes, laneGap) {
|
|
5565
|
+
const legs = [];
|
|
5566
|
+
routes.forEach((pts, r) => {
|
|
5567
|
+
if (!pts || pts.length < 2) return;
|
|
5568
|
+
const last = pts[pts.length - 1];
|
|
5569
|
+
const entry = entries[r].find((e) => e.point.x === last.x && e.point.y === last.y);
|
|
5570
|
+
for (let k = 0; k + 1 < pts.length; k++) {
|
|
5571
|
+
const a = pts[k];
|
|
5572
|
+
const b = pts[k + 1];
|
|
5573
|
+
if (a.x === b.x && a.y === b.y) continue;
|
|
5574
|
+
const horizontal = a.y === b.y;
|
|
5575
|
+
const [al, ac] = horizontal ? ["x", "y"] : ["y", "x"];
|
|
5576
|
+
const turn = (p) => p === void 0 ? 0 : Math.sign(p[ac] - a[ac]);
|
|
5577
|
+
const ta = turn(pts[k - 1]);
|
|
5578
|
+
const tb = turn(pts[k + 2]);
|
|
5579
|
+
const forward = b[al] > a[al];
|
|
5580
|
+
let [lo, hi] = free(frame, boxes, horizontal, a[ac], Math.min(a[al], b[al]), Math.max(a[al], b[al]));
|
|
5581
|
+
const end = k + 2 === pts.length;
|
|
5582
|
+
if (end && entry && !entry.fixed) {
|
|
5583
|
+
const e = edges[r].end.box;
|
|
5584
|
+
const [elo, size] = horizontal ? [e.y, e.h] : [e.x, e.w];
|
|
5585
|
+
const q = Math.min(2, size / 4);
|
|
5586
|
+
lo = Math.max(lo, elo + q);
|
|
5587
|
+
hi = Math.min(hi, elo + size - q);
|
|
5588
|
+
}
|
|
5589
|
+
legs.push({
|
|
5590
|
+
route: r,
|
|
5591
|
+
index: k,
|
|
5592
|
+
horizontal,
|
|
5593
|
+
across: a[ac],
|
|
5594
|
+
s0: Math.min(a[al], b[al]),
|
|
5595
|
+
s1: Math.max(a[al], b[al]),
|
|
5596
|
+
t0: forward ? ta : tb,
|
|
5597
|
+
t1: forward ? tb : ta,
|
|
5598
|
+
lo,
|
|
5599
|
+
hi,
|
|
5600
|
+
pinned: k === 0 || end && (entry === void 0 || entry.fixed)
|
|
5601
|
+
});
|
|
5602
|
+
}
|
|
5603
|
+
});
|
|
5604
|
+
const parent = legs.map((_, i) => i);
|
|
5605
|
+
const root = (i) => parent[i] === i ? i : parent[i] = root(parent[i]);
|
|
5606
|
+
for (let i = 0; i < legs.length; i++) for (let j = i + 1; j < legs.length; j++) {
|
|
5607
|
+
const a = legs[i];
|
|
5608
|
+
const b = legs[j];
|
|
5609
|
+
if (a.route === b.route || a.horizontal !== b.horizontal) continue;
|
|
5610
|
+
if (Math.min(a.s1, b.s1) - Math.max(a.s0, b.s0) <= -laneGap) continue;
|
|
5611
|
+
if (a.across < b.lo - EPS$6 || a.across > b.hi + EPS$6 || b.across < a.lo - EPS$6 || b.across > a.hi + EPS$6) continue;
|
|
5612
|
+
parent[root(i)] = root(j);
|
|
5613
|
+
}
|
|
5614
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5615
|
+
legs.forEach((_, i) => {
|
|
5616
|
+
const g = root(i);
|
|
5617
|
+
const list = groups.get(g);
|
|
5618
|
+
if (list) list.push(i);
|
|
5619
|
+
else groups.set(g, [i]);
|
|
5620
|
+
});
|
|
5621
|
+
const out = routes.map((pts) => pts ? pts.map((p) => ({ ...p })) : void 0);
|
|
5622
|
+
for (const members of groups.values()) {
|
|
5623
|
+
if (members.length < 2) continue;
|
|
5624
|
+
const group = members.map((i) => legs[i]);
|
|
5625
|
+
const at = place(group, laneGap);
|
|
5626
|
+
group.forEach((leg, m) => {
|
|
5627
|
+
const pts = out[leg.route];
|
|
5628
|
+
const key = leg.horizontal ? "y" : "x";
|
|
5629
|
+
pts[leg.index][key] = at[m];
|
|
5630
|
+
pts[leg.index + 1][key] = at[m];
|
|
5631
|
+
});
|
|
5632
|
+
}
|
|
5633
|
+
return out;
|
|
5634
|
+
}
|
|
5635
|
+
/** This function gives the free range across a leg: the nearest box sides or walls on each side, over the span of the leg. */
|
|
5636
|
+
function free(frame, boxes, horizontal, across, s0, s1) {
|
|
5637
|
+
let lo = horizontal ? frame.y : frame.x;
|
|
5638
|
+
let hi = horizontal ? frame.y + frame.h : frame.x + frame.w;
|
|
5639
|
+
for (const b of boxes) {
|
|
5640
|
+
const [bs, bl, cs, cl] = horizontal ? [
|
|
5641
|
+
b.x,
|
|
5642
|
+
b.w,
|
|
5643
|
+
b.y,
|
|
5644
|
+
b.h
|
|
5645
|
+
] : [
|
|
5646
|
+
b.y,
|
|
5647
|
+
b.h,
|
|
5648
|
+
b.x,
|
|
5649
|
+
b.w
|
|
5650
|
+
];
|
|
5651
|
+
if (Math.min(s1, bs + bl) - Math.max(s0, bs) <= EPS$6) continue;
|
|
5652
|
+
if (cs + cl <= across + EPS$6) lo = Math.max(lo, cs + cl);
|
|
5653
|
+
if (cs >= across - EPS$6) hi = Math.min(hi, cs);
|
|
5654
|
+
}
|
|
5655
|
+
return [lo, hi];
|
|
5656
|
+
}
|
|
5657
|
+
/** This function tells the side of the leg `b` for the leg `a`. It gives -1 for the smaller coordinate, +1 for the larger and 0 for no rule. */
|
|
5658
|
+
function want(a, b) {
|
|
5659
|
+
const side = (sa, sb, ta, tb, inner) => {
|
|
5660
|
+
if (inner(sa, sb)) return ta;
|
|
5661
|
+
if (inner(sb, sa)) return -tb;
|
|
5662
|
+
return ta === tb ? 0 : Math.sign(ta - tb);
|
|
5663
|
+
};
|
|
5664
|
+
const left = side(a.s0, b.s0, a.t0, b.t0, (x, y) => x > y + EPS$6);
|
|
5665
|
+
const right = side(a.s1, b.s1, a.t1, b.t1, (x, y) => x < y - EPS$6);
|
|
5666
|
+
if (left !== 0 && right !== 0 && left !== right) return left;
|
|
5667
|
+
return left || right;
|
|
5668
|
+
}
|
|
5669
|
+
/**
|
|
5670
|
+
* This function gives the lane of each leg of a group. A pinned leg keeps its place, and the pinned legs keep their order.
|
|
5671
|
+
* The other legs go in the order of `want`, evenly spaced between the pinned legs and the ends of the free range.
|
|
5672
|
+
*/
|
|
5673
|
+
function place(group, laneGap) {
|
|
5674
|
+
const n = group.length;
|
|
5675
|
+
const soft = group.map(() => []);
|
|
5676
|
+
const hard = group.map(() => []);
|
|
5677
|
+
const softIn = new Array(n).fill(0);
|
|
5678
|
+
const hardIn = new Array(n).fill(0);
|
|
5679
|
+
for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) {
|
|
5680
|
+
const a = group[i];
|
|
5681
|
+
const b = group[j];
|
|
5682
|
+
if (a.pinned && b.pinned) {
|
|
5683
|
+
if (Math.abs(a.across - b.across) <= EPS$6) continue;
|
|
5684
|
+
const [x, y] = a.across < b.across ? [i, j] : [j, i];
|
|
5685
|
+
hard[x].push(y);
|
|
5686
|
+
hardIn[y]++;
|
|
5687
|
+
continue;
|
|
5688
|
+
}
|
|
5689
|
+
const w = want(a, b);
|
|
5690
|
+
if (w === 0) continue;
|
|
5691
|
+
const [x, y] = w < 0 ? [i, j] : [j, i];
|
|
5692
|
+
soft[x].push(y);
|
|
5693
|
+
softIn[y]++;
|
|
5694
|
+
}
|
|
5695
|
+
const order = [];
|
|
5696
|
+
const done = new Array(n).fill(false);
|
|
5697
|
+
while (order.length < n) {
|
|
5698
|
+
let best = -1;
|
|
5699
|
+
for (let i = 0; i < n; i++) {
|
|
5700
|
+
if (done[i] || hardIn[i] > 0) continue;
|
|
5701
|
+
if (best < 0 || softIn[i] < softIn[best] || softIn[i] === softIn[best] && group[i].across < group[best].across - EPS$6) best = i;
|
|
5702
|
+
}
|
|
5703
|
+
done[best] = true;
|
|
5704
|
+
order.push(best);
|
|
5705
|
+
for (const y of soft[best]) softIn[y]--;
|
|
5706
|
+
for (const y of hard[best]) hardIn[y]--;
|
|
5707
|
+
}
|
|
5708
|
+
for (let k = 0; k < order.length; k++) {
|
|
5709
|
+
const a = group[order[k]];
|
|
5710
|
+
if (!a.pinned) continue;
|
|
5711
|
+
let to = k + 1;
|
|
5712
|
+
for (let m = k + 1; m < order.length; m++) {
|
|
5713
|
+
const b = group[order[m]];
|
|
5714
|
+
if (b.pinned && Math.abs(b.across - a.across) <= EPS$6) order.splice(to++, 0, ...order.splice(m, 1));
|
|
5715
|
+
}
|
|
5716
|
+
k = to - 1;
|
|
5717
|
+
}
|
|
5718
|
+
const at = new Array(n).fill(0);
|
|
5719
|
+
let start = 0;
|
|
5720
|
+
let from = -Infinity;
|
|
5721
|
+
for (let k = 0; k <= n; k++) {
|
|
5722
|
+
const i = order[k];
|
|
5723
|
+
const pin = i !== void 0 && group[i].pinned;
|
|
5724
|
+
if (k < n && !pin) continue;
|
|
5725
|
+
const run = order.slice(start, k);
|
|
5726
|
+
if (run.length > 0) {
|
|
5727
|
+
const lo = Math.max(from, ...run.map((r) => group[r].lo));
|
|
5728
|
+
const hi = Math.min(k < n ? group[i].across : Infinity, ...run.map((r) => group[r].hi));
|
|
5729
|
+
const room = hi - lo;
|
|
5730
|
+
const s = Math.min(laneGap, room / (run.length + 1));
|
|
5731
|
+
const width = (run.length - 1) * s;
|
|
5732
|
+
const mean = run.reduce((t, r) => t + group[r].across, 0) / run.length;
|
|
5733
|
+
const first = Math.min(Math.max(mean - width / 2, lo + s), hi - s - width);
|
|
5734
|
+
run.forEach((r, m) => at[r] = room > EPS$6 && s > 0 ? first + m * s : group[r].across);
|
|
5735
|
+
}
|
|
5736
|
+
if (k < n) at[i] = group[i].across;
|
|
5737
|
+
start = k + 1;
|
|
5738
|
+
if (k < n) from = group[i].across;
|
|
5739
|
+
}
|
|
5740
|
+
return at;
|
|
5741
|
+
}
|
|
5215
5742
|
// src/routers/frames.ts
|
|
5216
5743
|
/** The tiny gap between the bounds and the band of a wall, so that a way along the bounds stays clear of the wall. */
|
|
5217
5744
|
const EPS$5 = 1e-6;
|
|
@@ -5229,6 +5756,7 @@ function frameRoutes(graph, inner, options) {
|
|
|
5229
5756
|
if (list) list.push(n);
|
|
5230
5757
|
else children.set(n.parent, [n]);
|
|
5231
5758
|
}
|
|
5759
|
+
if (options.inner === "gutter") return gutterFrameRoutes(inner, options, byId, children);
|
|
5232
5760
|
const margin = options.margin ?? 5;
|
|
5233
5761
|
const inset = options.frameInset ?? margin;
|
|
5234
5762
|
const rooms = /* @__PURE__ */ new Map();
|
|
@@ -5236,8 +5764,7 @@ function frameRoutes(graph, inner, options) {
|
|
|
5236
5764
|
for (const { edge, frame, plug } of inner) {
|
|
5237
5765
|
const f = byId.get(frame);
|
|
5238
5766
|
const [own, far] = plug === "source" ? [edge.source, edge.target] : [edge.target, edge.source];
|
|
5239
|
-
const chain =
|
|
5240
|
-
for (let p = byId.get(far.node).parent; p !== frame; p = byId.get(p).parent) chain.push(p);
|
|
5767
|
+
const chain = chainOf(byId, frame, far.node);
|
|
5241
5768
|
const key = JSON.stringify([frame, ...chain]);
|
|
5242
5769
|
let room = rooms.get(key);
|
|
5243
5770
|
if (!room) rooms.set(key, room = makeRoom$1(f, chain, children, byId, inset, options));
|
|
@@ -5266,6 +5793,63 @@ function frameRoutes(graph, inner, options) {
|
|
|
5266
5793
|
}
|
|
5267
5794
|
return routes;
|
|
5268
5795
|
}
|
|
5796
|
+
/** This function gives the nodes between the frame and the far end, the nearest first. Each of them holds the far end, so it is not an obstacle. */
|
|
5797
|
+
function chainOf(byId, frame, far) {
|
|
5798
|
+
const chain = [];
|
|
5799
|
+
for (let p = byId.get(far).parent; p !== frame; p = byId.get(p).parent) chain.push(p);
|
|
5800
|
+
return chain;
|
|
5801
|
+
}
|
|
5802
|
+
/**
|
|
5803
|
+
* This function gives the inner routes of the mode `gutter`. The inner edges of one room route together, so that they share the gaps in lanes.
|
|
5804
|
+
* A route is orthogonal, in the free room of the frame box. An edge without a way through the free room gets no route.
|
|
5805
|
+
*/
|
|
5806
|
+
function gutterFrameRoutes(inner, options, byId, children) {
|
|
5807
|
+
const rooms = /* @__PURE__ */ new Map();
|
|
5808
|
+
const routes = inner.map(() => void 0);
|
|
5809
|
+
inner.forEach(({ edge, frame, plug }, at) => {
|
|
5810
|
+
const f = byId.get(frame);
|
|
5811
|
+
const [own, far] = plug === "source" ? [edge.source, edge.target] : [edge.target, edge.source];
|
|
5812
|
+
const chain = chainOf(byId, frame, far.node);
|
|
5813
|
+
const key = JSON.stringify([frame, ...chain]);
|
|
5814
|
+
let room = rooms.get(key);
|
|
5815
|
+
if (!room) rooms.set(key, room = {
|
|
5816
|
+
nodes: roomNodes(f, chain, children, byId, f.box, options),
|
|
5817
|
+
items: [],
|
|
5818
|
+
frame: f.box
|
|
5819
|
+
});
|
|
5820
|
+
const end = room.nodes.get(far.node);
|
|
5821
|
+
if (!end) return;
|
|
5822
|
+
const fp = placePlug(f, own, center(end.box));
|
|
5823
|
+
const port = fixedPortPoint(end, far) === void 0 ? void 0 : placePlug(end, far, fp.point);
|
|
5824
|
+
const side = far.port === void 0 ? void 0 : end.ports?.find((p) => p.id === far.port)?.side;
|
|
5825
|
+
room.items.push({
|
|
5826
|
+
at,
|
|
5827
|
+
edge: {
|
|
5828
|
+
plug: fp,
|
|
5829
|
+
end,
|
|
5830
|
+
...port ? { port } : {},
|
|
5831
|
+
...side ? { side } : {}
|
|
5832
|
+
}
|
|
5833
|
+
});
|
|
5834
|
+
});
|
|
5835
|
+
for (const room of rooms.values()) {
|
|
5836
|
+
const found = gutterRoutes(room.frame, [...room.nodes.values()], room.items.map((i) => i.edge), {
|
|
5837
|
+
laneGap: options.margin ?? 5,
|
|
5838
|
+
...options.bendCost === void 0 ? {} : { bendCost: options.bendCost }
|
|
5839
|
+
});
|
|
5840
|
+
room.items.forEach(({ at }, k) => {
|
|
5841
|
+
const points = found[k];
|
|
5842
|
+
if (!points) return;
|
|
5843
|
+
const { edge, frame, plug } = inner[at];
|
|
5844
|
+
routes[at] = {
|
|
5845
|
+
edge: edge.id,
|
|
5846
|
+
points: plug === "source" ? points : points.reverse(),
|
|
5847
|
+
frame
|
|
5848
|
+
};
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
return routes.filter((r) => r !== void 0);
|
|
5852
|
+
}
|
|
5269
5853
|
/** This function puts the plug of the wall before a way that starts on the bounds. A first point on the line of the stub goes. */
|
|
5270
5854
|
function stub(plug, way) {
|
|
5271
5855
|
const [a, b] = way;
|
|
@@ -5281,31 +5865,7 @@ function stub(plug, way) {
|
|
|
5281
5865
|
function makeRoom$1(f, chain, children, byId, inset, options) {
|
|
5282
5866
|
const margin = options.margin ?? 5;
|
|
5283
5867
|
const bounds = shrink(f.box, inset);
|
|
5284
|
-
const
|
|
5285
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
5286
|
-
for (const holder of [f.id, ...chain]) for (const n of children.get(holder) ?? []) {
|
|
5287
|
-
if (skip.has(n.id)) continue;
|
|
5288
|
-
const box = byId.get(holder).box;
|
|
5289
|
-
if (inside(n.box, box)) {
|
|
5290
|
-
nodes.set(n.id, n);
|
|
5291
|
-
continue;
|
|
5292
|
-
}
|
|
5293
|
-
const rule = options.outside ?? "throw";
|
|
5294
|
-
if (rule === "throw") throw new RangeError(`The box of the node "${n.id}" is not inside the box of its frame "${holder}". The option outside can skip or clamp it.`);
|
|
5295
|
-
if (rule === "skip") continue;
|
|
5296
|
-
const cut = cutBox(n.box, bounds);
|
|
5297
|
-
if (cut) nodes.set(n.id, {
|
|
5298
|
-
...n,
|
|
5299
|
-
box: cut,
|
|
5300
|
-
...n.ports ? { ports: n.ports.map((p) => p.at ? {
|
|
5301
|
-
...p,
|
|
5302
|
-
at: {
|
|
5303
|
-
x: p.at.x + n.box.x - cut.x,
|
|
5304
|
-
y: p.at.y + n.box.y - cut.y
|
|
5305
|
-
}
|
|
5306
|
-
} : p) } : {}
|
|
5307
|
-
});
|
|
5308
|
-
}
|
|
5868
|
+
const nodes = roomNodes(f, chain, children, byId, bounds, options);
|
|
5309
5869
|
const w = shrink(bounds, -(margin / 2 + EPS$5));
|
|
5310
5870
|
const t = 2 * margin + 1;
|
|
5311
5871
|
const walls = [
|
|
@@ -5342,6 +5902,38 @@ function makeRoom$1(f, chain, children, byId, inset, options) {
|
|
|
5342
5902
|
nodes
|
|
5343
5903
|
};
|
|
5344
5904
|
}
|
|
5905
|
+
/**
|
|
5906
|
+
* This function gives the obstacles of a room by id. They are the children of the frame and of each node of the chain, but not the chain.
|
|
5907
|
+
* It applies the rule `outside` to each box outside the box of its holder. The rule `clamp` cuts such a box to `bounds`.
|
|
5908
|
+
*/
|
|
5909
|
+
function roomNodes(f, chain, children, byId, bounds, options) {
|
|
5910
|
+
const skip = new Set(chain);
|
|
5911
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
5912
|
+
for (const holder of [f.id, ...chain]) for (const n of children.get(holder) ?? []) {
|
|
5913
|
+
if (skip.has(n.id)) continue;
|
|
5914
|
+
const box = byId.get(holder).box;
|
|
5915
|
+
if (inside(n.box, box)) {
|
|
5916
|
+
nodes.set(n.id, n);
|
|
5917
|
+
continue;
|
|
5918
|
+
}
|
|
5919
|
+
const rule = options.outside ?? "throw";
|
|
5920
|
+
if (rule === "throw") throw new RangeError(`The box of the node "${n.id}" is not inside the box of its frame "${holder}". The option outside can skip or clamp it.`);
|
|
5921
|
+
if (rule === "skip") continue;
|
|
5922
|
+
const cut = cutBox(n.box, bounds);
|
|
5923
|
+
if (cut) nodes.set(n.id, {
|
|
5924
|
+
...n,
|
|
5925
|
+
box: cut,
|
|
5926
|
+
...n.ports ? { ports: n.ports.map((p) => p.at ? {
|
|
5927
|
+
...p,
|
|
5928
|
+
at: {
|
|
5929
|
+
x: p.at.x + n.box.x - cut.x,
|
|
5930
|
+
y: p.at.y + n.box.y - cut.y
|
|
5931
|
+
}
|
|
5932
|
+
} : p) } : {}
|
|
5933
|
+
});
|
|
5934
|
+
}
|
|
5935
|
+
return nodes;
|
|
5936
|
+
}
|
|
5345
5937
|
/** This function gives the box, smaller by `d` on each side. A negative `d` makes it larger. */
|
|
5346
5938
|
function shrink(b, d) {
|
|
5347
5939
|
return {
|
|
@@ -7501,7 +8093,7 @@ function turn(from, base, to) {
|
|
|
7501
8093
|
* Thus the edges of a key come together on common ways. Each stretch that several edges share has the same points in each route.
|
|
7502
8094
|
* The result gives each stretch as a run, with the order of its lanes.
|
|
7503
8095
|
*/
|
|
7504
|
-
function sharedRouter(options, negotiation) {
|
|
8096
|
+
function sharedRouter(options, negotiation, hooks) {
|
|
7505
8097
|
const discount = Math.min(1, Math.max(.05, options.discount ?? .5));
|
|
7506
8098
|
const flow = options.cost === "flow";
|
|
7507
8099
|
const alpha = Math.min(1, Math.max(.1, options.alpha ?? .6));
|
|
@@ -7552,7 +8144,20 @@ function sharedRouter(options, negotiation) {
|
|
|
7552
8144
|
});
|
|
7553
8145
|
}
|
|
7554
8146
|
const discounted = /* @__PURE__ */ new Map();
|
|
7555
|
-
const
|
|
8147
|
+
const search = {
|
|
8148
|
+
graph,
|
|
8149
|
+
corners: corners.points.length,
|
|
8150
|
+
link,
|
|
8151
|
+
find: (w, cost, floor = 1) => {
|
|
8152
|
+
const way = corners.path(ways.out(w.ps), ways.out(w.pt), cost, floor);
|
|
8153
|
+
return way && {
|
|
8154
|
+
seq: way,
|
|
8155
|
+
ids: way.map((p, i) => i === 0 || i === way.length - 1 ? endId(p) : index.get(p))
|
|
8156
|
+
};
|
|
8157
|
+
}
|
|
8158
|
+
};
|
|
8159
|
+
const sorted = orderWays(all, options.order ?? "span", graph);
|
|
8160
|
+
const order = hooks?.order ? hooks.order(sorted, search) : sorted;
|
|
7556
8161
|
const top = /* @__PURE__ */ new Map();
|
|
7557
8162
|
const widths = /* @__PURE__ */ new Map();
|
|
7558
8163
|
const widthOf = (i, j) => {
|
|
@@ -7676,6 +8281,7 @@ function sharedRouter(options, negotiation) {
|
|
|
7676
8281
|
route(w);
|
|
7677
8282
|
}
|
|
7678
8283
|
}
|
|
8284
|
+
hooks?.refine?.(order, search);
|
|
7679
8285
|
if (options.merge === "snell") {
|
|
7680
8286
|
const pad = ways.margin / 2;
|
|
7681
8287
|
snellMerges(order, corners.points.length + ends.size, (a, b) => ways.obstacles.clear(a, b, pad), (f) => Math.min(.95, share(f)));
|
|
@@ -8009,72 +8615,503 @@ function asRouter(strategy, options) {
|
|
|
8009
8615
|
function routersOf(registry) {
|
|
8010
8616
|
return listStrategies(registry, "router").map((s) => asRouter(s));
|
|
8011
8617
|
}
|
|
8012
|
-
// src/routers/
|
|
8013
|
-
/** The default options of the router `
|
|
8014
|
-
const
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8618
|
+
// src/routers/annealing/index.ts
|
|
8619
|
+
/** The default options of the router `annealing`. */
|
|
8620
|
+
const ANNEALING_DEFAULTS = Object.freeze({
|
|
8621
|
+
candidates: 4,
|
|
8622
|
+
crossing: 4,
|
|
8623
|
+
temperature: 1,
|
|
8624
|
+
cooling: .95,
|
|
8625
|
+
budget: 50,
|
|
8626
|
+
seed: 1
|
|
8019
8627
|
});
|
|
8020
|
-
/** The declarations of the options of the router `
|
|
8021
|
-
const
|
|
8022
|
-
|
|
8628
|
+
/** The declarations of the options of the router `annealing` (RULE-021). */
|
|
8629
|
+
const ANNEALING_SCHEMA = {
|
|
8630
|
+
candidates: {
|
|
8023
8631
|
type: "integer",
|
|
8024
|
-
min:
|
|
8025
|
-
max:
|
|
8632
|
+
min: 3,
|
|
8633
|
+
max: 8,
|
|
8026
8634
|
step: 1,
|
|
8027
8635
|
marks: [
|
|
8028
|
-
2,
|
|
8029
8636
|
3,
|
|
8030
|
-
|
|
8637
|
+
4,
|
|
8638
|
+
6
|
|
8031
8639
|
],
|
|
8032
|
-
default:
|
|
8033
|
-
describe: "The number of
|
|
8640
|
+
default: 4,
|
|
8641
|
+
describe: "The number of candidate ways of each edge."
|
|
8034
8642
|
},
|
|
8035
|
-
|
|
8643
|
+
crossing: {
|
|
8036
8644
|
type: "number",
|
|
8037
8645
|
min: 0,
|
|
8038
|
-
max:
|
|
8039
|
-
step: .
|
|
8646
|
+
max: 20,
|
|
8647
|
+
step: .5,
|
|
8040
8648
|
marks: [
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8649
|
+
1,
|
|
8650
|
+
4,
|
|
8651
|
+
10
|
|
8044
8652
|
],
|
|
8045
|
-
default:
|
|
8046
|
-
describe: "The
|
|
8653
|
+
default: 4,
|
|
8654
|
+
describe: "The cost of one crossing, in units of the median box side."
|
|
8047
8655
|
},
|
|
8048
|
-
|
|
8656
|
+
temperature: {
|
|
8049
8657
|
type: "number",
|
|
8050
|
-
min:
|
|
8658
|
+
min: .1,
|
|
8051
8659
|
max: 10,
|
|
8052
|
-
step: .
|
|
8660
|
+
step: .1,
|
|
8053
8661
|
marks: [
|
|
8054
|
-
|
|
8662
|
+
.5,
|
|
8055
8663
|
1,
|
|
8056
8664
|
4
|
|
8057
8665
|
],
|
|
8058
8666
|
default: 1,
|
|
8059
|
-
describe: "The
|
|
8667
|
+
describe: "The start temperature, in units of the median box side."
|
|
8060
8668
|
},
|
|
8061
|
-
|
|
8669
|
+
cooling: {
|
|
8062
8670
|
type: "number",
|
|
8063
|
-
min:
|
|
8064
|
-
max:
|
|
8065
|
-
step:
|
|
8066
|
-
marks: [
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8671
|
+
min: .9,
|
|
8672
|
+
max: .99,
|
|
8673
|
+
step: .01,
|
|
8674
|
+
marks: [
|
|
8675
|
+
.9,
|
|
8676
|
+
.95,
|
|
8677
|
+
.98
|
|
8678
|
+
],
|
|
8679
|
+
default: .95,
|
|
8680
|
+
describe: "The factor of the temperature after each sweep of the edges."
|
|
8681
|
+
},
|
|
8682
|
+
budget: {
|
|
8683
|
+
type: "integer",
|
|
8684
|
+
min: 20,
|
|
8685
|
+
max: 200,
|
|
8686
|
+
step: 10,
|
|
8687
|
+
marks: [
|
|
8688
|
+
20,
|
|
8689
|
+
50,
|
|
8690
|
+
100
|
|
8691
|
+
],
|
|
8692
|
+
default: 50,
|
|
8693
|
+
describe: "The budget: the number of changes for each edge."
|
|
8694
|
+
},
|
|
8695
|
+
seed: {
|
|
8696
|
+
type: "integer",
|
|
8697
|
+
min: 0,
|
|
8698
|
+
max: 1e6,
|
|
8699
|
+
step: 1,
|
|
8700
|
+
marks: [
|
|
8701
|
+
1,
|
|
8702
|
+
2,
|
|
8703
|
+
3
|
|
8704
|
+
],
|
|
8705
|
+
default: 1,
|
|
8706
|
+
describe: "The seed of the random choices. The same seed gives the same routes."
|
|
8707
|
+
}
|
|
8708
|
+
};
|
|
8709
|
+
/** The share of the cost of a link that the edges of one key use already, as in `shared` with its default discount. */
|
|
8710
|
+
const DISCOUNT = .5;
|
|
8711
|
+
/** The cost factor of the links of the earlier candidates in the search of the next candidate. */
|
|
8712
|
+
const PENALTY = 3;
|
|
8713
|
+
/** This function tells if the segments `a b` and `c d` cross at one inner point of each. A touch at an end or a common line is not a crossing. */
|
|
8714
|
+
function properCross(a, b, c, d) {
|
|
8715
|
+
const o = (p, q, r) => (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x);
|
|
8716
|
+
const d1 = o(c, d, a);
|
|
8717
|
+
const d2 = o(c, d, b);
|
|
8718
|
+
const d3 = o(a, b, c);
|
|
8719
|
+
const d4 = o(a, b, d);
|
|
8720
|
+
return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
|
|
8721
|
+
}
|
|
8722
|
+
/** This function gives the median of the smaller side of the boxes, the unit of the costs. */
|
|
8723
|
+
function unitOf(graph) {
|
|
8724
|
+
const sides = graph.nodes.map((n) => Math.min(n.box.w, n.box.h)).sort((a, b) => a - b);
|
|
8725
|
+
const u = sides.length === 0 ? 1 : sides[Math.floor((sides.length - 1) / 2)];
|
|
8726
|
+
return u > 0 ? u : 1;
|
|
8727
|
+
}
|
|
8728
|
+
/**
|
|
8729
|
+
* This function anneals the choice of one candidate way for each edge (RULE-021, after Davidson and Harel, 1996).
|
|
8730
|
+
* The candidates of an edge are its way in `shared` and its shortest way. Other candidates are the shortest ways when the links of earlier candidates cost 3 times more.
|
|
8731
|
+
* The energy is `(1 - 0.5) ink + 0.5 Σ L + crossing u X`: the cost of `shared` plus a cost for each crossing `X` between the ways of two edges.
|
|
8732
|
+
* A change takes one edge to another candidate. A worse change stays with the rational chance `1 / (1 + y + y^2 / 2)`, with `y = ΔE / T`.
|
|
8733
|
+
* The temperature falls by `cooling` after each sweep. The ways at the end are the ways of the lowest energy of the run.
|
|
8734
|
+
*/
|
|
8735
|
+
function annealWays(ways, search, o) {
|
|
8736
|
+
const live = ways.filter((w) => w.found);
|
|
8737
|
+
const m = live.length;
|
|
8738
|
+
if (m < 2) return;
|
|
8739
|
+
const unit = unitOf(search.graph);
|
|
8740
|
+
const n = search.corners;
|
|
8741
|
+
let maxId = 0;
|
|
8742
|
+
for (const w of live) for (const i of w.ids) maxId = Math.max(maxId, i);
|
|
8743
|
+
const N = maxId + 1;
|
|
8744
|
+
const pairOf = (a, b) => a < b ? a * N + b : b * N + a;
|
|
8745
|
+
const make = (seq, ids) => {
|
|
8746
|
+
const links = [];
|
|
8747
|
+
const lengths = [];
|
|
8748
|
+
let length = 0;
|
|
8749
|
+
for (let i = 1; i < ids.length; i++) {
|
|
8750
|
+
const d = dist(seq[i - 1], seq[i]);
|
|
8751
|
+
links.push(pairOf(ids[i - 1], ids[i]));
|
|
8752
|
+
lengths.push(d);
|
|
8753
|
+
length += d;
|
|
8754
|
+
}
|
|
8755
|
+
return {
|
|
8756
|
+
seq,
|
|
8757
|
+
ids,
|
|
8758
|
+
links,
|
|
8759
|
+
lengths,
|
|
8760
|
+
length
|
|
8761
|
+
};
|
|
8762
|
+
};
|
|
8763
|
+
const k = Math.min(8, Math.max(1, Math.floor(o.candidates)));
|
|
8764
|
+
const cands = live.map((w) => {
|
|
8765
|
+
const list = [make(w.seq, w.ids)];
|
|
8766
|
+
const seen = /* @__PURE__ */ new Set([w.ids.join(",")]);
|
|
8767
|
+
const hit = /* @__PURE__ */ new Set();
|
|
8768
|
+
const mark = (c) => {
|
|
8769
|
+
for (let i = 1; i < c.ids.length; i++) if (c.ids[i - 1] < n && c.ids[i] < n) hit.add(search.link(c.ids[i - 1], c.ids[i]));
|
|
8770
|
+
};
|
|
8771
|
+
const add = (found) => {
|
|
8772
|
+
if (!found) return false;
|
|
8773
|
+
const key = found.ids.join(",");
|
|
8774
|
+
if (seen.has(key)) return false;
|
|
8775
|
+
seen.add(key);
|
|
8776
|
+
const c = make(found.seq, found.ids);
|
|
8777
|
+
list.push(c);
|
|
8778
|
+
mark(c);
|
|
8779
|
+
return true;
|
|
8780
|
+
};
|
|
8781
|
+
mark(list[0]);
|
|
8782
|
+
if (list.length < k) add(search.find(w));
|
|
8783
|
+
for (let tries = 0; list.length < k && tries < k; tries++) if (!add(search.find(w, (i, j, d) => hit.has(search.link(i, j)) ? d * PENALTY : d, 1))) break;
|
|
8784
|
+
return list;
|
|
8785
|
+
});
|
|
8786
|
+
const boundsOf = (seqs) => {
|
|
8787
|
+
let x0 = Infinity;
|
|
8788
|
+
let y0 = Infinity;
|
|
8789
|
+
let x1 = -Infinity;
|
|
8790
|
+
let y1 = -Infinity;
|
|
8791
|
+
for (const seq of seqs) for (const p of seq) {
|
|
8792
|
+
x0 = Math.min(x0, p.x);
|
|
8793
|
+
y0 = Math.min(y0, p.y);
|
|
8794
|
+
x1 = Math.max(x1, p.x);
|
|
8795
|
+
y1 = Math.max(y1, p.y);
|
|
8796
|
+
}
|
|
8797
|
+
return {
|
|
8798
|
+
x0,
|
|
8799
|
+
y0,
|
|
8800
|
+
x1,
|
|
8801
|
+
y1
|
|
8802
|
+
};
|
|
8803
|
+
};
|
|
8804
|
+
const apart = (A, B) => A.x1 < B.x0 || B.x1 < A.x0 || A.y1 < B.y0 || B.y1 < A.y0;
|
|
8805
|
+
const own = cands.map((list) => list.map((c) => boundsOf([c.seq])));
|
|
8806
|
+
const boxes = cands.map((list) => boundsOf(list.map((c) => c.seq)));
|
|
8807
|
+
const near = live.map(() => []);
|
|
8808
|
+
for (let e = 0; e < m; e++) for (let f = e + 1; f < m; f++) {
|
|
8809
|
+
if (apart(boxes[e], boxes[f])) continue;
|
|
8810
|
+
const ke = cands[e].length;
|
|
8811
|
+
const kf = cands[f].length;
|
|
8812
|
+
const table = new Int32Array(ke * kf);
|
|
8813
|
+
let any = false;
|
|
8814
|
+
for (let i = 0; i < ke; i++) {
|
|
8815
|
+
const p = cands[e][i].seq;
|
|
8816
|
+
for (let j = 0; j < kf; j++) {
|
|
8817
|
+
if (apart(own[e][i], own[f][j])) continue;
|
|
8818
|
+
const q = cands[f][j].seq;
|
|
8819
|
+
let x = 0;
|
|
8820
|
+
for (let s = 1; s < p.length; s++) for (let t = 1; t < q.length; t++) if (properCross(p[s - 1], p[s], q[t - 1], q[t])) x++;
|
|
8821
|
+
table[i * kf + j] = x;
|
|
8822
|
+
if (x > 0) any = true;
|
|
8823
|
+
}
|
|
8824
|
+
}
|
|
8825
|
+
if (!any) continue;
|
|
8826
|
+
near[e].push({
|
|
8827
|
+
f,
|
|
8828
|
+
table,
|
|
8829
|
+
mine: true
|
|
8830
|
+
});
|
|
8831
|
+
near[f].push({
|
|
8832
|
+
f: e,
|
|
8833
|
+
table,
|
|
8834
|
+
mine: false
|
|
8835
|
+
});
|
|
8836
|
+
}
|
|
8837
|
+
const keys = live.map((w) => w.key);
|
|
8838
|
+
const users = /* @__PURE__ */ new Map();
|
|
8839
|
+
const use = (e, c, step) => {
|
|
8840
|
+
let mine = users.get(keys[e]);
|
|
8841
|
+
if (!mine) users.set(keys[e], mine = /* @__PURE__ */ new Map());
|
|
8842
|
+
let ink = 0;
|
|
8843
|
+
for (let i = 0; i < c.links.length; i++) {
|
|
8844
|
+
const before = mine.get(c.links[i]) ?? 0;
|
|
8845
|
+
const after = before + step;
|
|
8846
|
+
if (after === 0) mine.delete(c.links[i]);
|
|
8847
|
+
else mine.set(c.links[i], after);
|
|
8848
|
+
if (before === 0 && after > 0) ink += c.lengths[i];
|
|
8849
|
+
if (before > 0 && after === 0) ink -= c.lengths[i];
|
|
8850
|
+
}
|
|
8851
|
+
return ink;
|
|
8852
|
+
};
|
|
8853
|
+
const choice = new Int32Array(m);
|
|
8854
|
+
for (let e = 0; e < m; e++) use(e, cands[e][0], 1);
|
|
8855
|
+
const crossCost = Math.max(0, o.crossing) * unit;
|
|
8856
|
+
const crossings = (e, i) => {
|
|
8857
|
+
let x = 0;
|
|
8858
|
+
for (const { f, table, mine } of near[e]) {
|
|
8859
|
+
const kf = cands[f].length;
|
|
8860
|
+
const ke = cands[e].length;
|
|
8861
|
+
x += mine ? table[i * kf + choice[f]] : table[choice[f] * ke + i];
|
|
8862
|
+
}
|
|
8863
|
+
return x;
|
|
8864
|
+
};
|
|
8865
|
+
const random = rng(Math.max(0, Math.floor(o.seed)));
|
|
8866
|
+
const cooling = Math.min(.99, Math.max(.9, o.cooling));
|
|
8867
|
+
let temperature = Math.max(1e-6, o.temperature) * unit;
|
|
8868
|
+
const moves = Math.max(1, Math.floor(o.budget)) * m;
|
|
8869
|
+
let energy = 0;
|
|
8870
|
+
let best = 0;
|
|
8871
|
+
let bestChoice = choice.slice();
|
|
8872
|
+
for (let s = 0; s < moves; s++) {
|
|
8873
|
+
if (s > 0 && s % m === 0) temperature *= cooling;
|
|
8874
|
+
const e = Math.floor(random() * m);
|
|
8875
|
+
const list = cands[e];
|
|
8876
|
+
if (list.length < 2) continue;
|
|
8877
|
+
const from = choice[e];
|
|
8878
|
+
const to = (from + 1 + Math.floor(random() * (list.length - 1))) % list.length;
|
|
8879
|
+
const dInk = use(e, list[from], -1) + use(e, list[to], 1);
|
|
8880
|
+
const dLength = list[to].length - list[from].length;
|
|
8881
|
+
const dCross = crossings(e, to) - crossings(e, from);
|
|
8882
|
+
const delta = .5 * dInk + DISCOUNT * dLength + crossCost * dCross;
|
|
8883
|
+
const y = delta / temperature;
|
|
8884
|
+
if (delta <= 0 || random() < 1 / (1 + y + y * y / 2)) {
|
|
8885
|
+
choice[e] = to;
|
|
8886
|
+
energy += delta;
|
|
8887
|
+
if (energy < best - 1e-9) {
|
|
8888
|
+
best = energy;
|
|
8889
|
+
bestChoice = choice.slice();
|
|
8890
|
+
}
|
|
8891
|
+
} else {
|
|
8892
|
+
use(e, list[to], -1);
|
|
8893
|
+
use(e, list[from], 1);
|
|
8894
|
+
}
|
|
8895
|
+
}
|
|
8896
|
+
live.forEach((w, e) => {
|
|
8897
|
+
const c = cands[e][bestChoice[e]];
|
|
8898
|
+
w.seq = c.seq;
|
|
8899
|
+
w.ids = c.ids;
|
|
8900
|
+
});
|
|
8901
|
+
}
|
|
8902
|
+
/**
|
|
8903
|
+
* This function makes the router `annealing` (RULE-021 with its rider WILD-028). It is the router `shared`, then the annealing of `annealWays`.
|
|
8904
|
+
* Each candidate is a way through the corner graph, as in `shared`, so each one keeps the hard rules.
|
|
8905
|
+
*/
|
|
8906
|
+
function annealingRouter(options) {
|
|
8907
|
+
const o = {
|
|
8908
|
+
...ANNEALING_DEFAULTS,
|
|
8909
|
+
...options
|
|
8910
|
+
};
|
|
8911
|
+
return sharedRouter({
|
|
8912
|
+
name: o.name,
|
|
8913
|
+
...o.rule === void 0 ? {} : { rule: o.rule },
|
|
8914
|
+
...o.maxNodes === void 0 ? {} : { maxNodes: o.maxNodes }
|
|
8915
|
+
}, void 0, { refine: (ways, search) => annealWays(ways, search, o) });
|
|
8916
|
+
}
|
|
8917
|
+
/**
|
|
8918
|
+
* The router strategy `annealing` (RULE-021), with the rule `byCollection` of `shared`. It is not a built-in router, so the benchmark and the hashes of `ROUTERS` do not change.
|
|
8919
|
+
* A screen module or a consumer adds it to a registry.
|
|
8920
|
+
*/
|
|
8921
|
+
const annealingStrategy = defineStrategy({
|
|
8922
|
+
step: "router",
|
|
8923
|
+
name: "annealing",
|
|
8924
|
+
describe: "Simulated annealing over candidate routes (RULE-021, with WILD-028). Each edge picks one of a few ways, for less ink and fewer crossings.",
|
|
8925
|
+
defaults: { ...ANNEALING_DEFAULTS },
|
|
8926
|
+
schema: ANNEALING_SCHEMA,
|
|
8927
|
+
maxNodes: 1e4,
|
|
8928
|
+
run: (graph, o) => annealingRouter({
|
|
8929
|
+
...o,
|
|
8930
|
+
name: "annealing",
|
|
8931
|
+
rule: byCollection
|
|
8932
|
+
}).route(graph)
|
|
8933
|
+
});
|
|
8934
|
+
// src/routers/tight/index.ts
|
|
8935
|
+
/** The values of `TightFirst`. */
|
|
8936
|
+
const TIGHT_FIRSTS = Object.freeze(["tight", "loose"]);
|
|
8937
|
+
/** The values of `TightTies`. */
|
|
8938
|
+
const TIGHT_TIES = Object.freeze(["id", "length"]);
|
|
8939
|
+
/** The default options of the router `tight`. */
|
|
8940
|
+
const TIGHT_DEFAULTS = Object.freeze({
|
|
8941
|
+
penalty: 3,
|
|
8942
|
+
first: "tight",
|
|
8943
|
+
ties: "length"
|
|
8944
|
+
});
|
|
8945
|
+
/** The declarations of the options of the router `tight` (WILD-023). */
|
|
8946
|
+
const TIGHT_SCHEMA = {
|
|
8947
|
+
penalty: {
|
|
8948
|
+
type: "number",
|
|
8949
|
+
min: 2,
|
|
8950
|
+
max: 10,
|
|
8951
|
+
step: .5,
|
|
8952
|
+
marks: [
|
|
8953
|
+
2,
|
|
8954
|
+
3,
|
|
8955
|
+
5
|
|
8956
|
+
],
|
|
8957
|
+
default: 3,
|
|
8958
|
+
describe: "The cost factor of the links of the first way in the search of the second way."
|
|
8959
|
+
},
|
|
8960
|
+
first: {
|
|
8961
|
+
type: "enum",
|
|
8962
|
+
values: ["tight", "loose"],
|
|
8963
|
+
default: "tight",
|
|
8964
|
+
describe: "The edges that go first: the edges with few good ways, or the edges with many good ways."
|
|
8965
|
+
},
|
|
8966
|
+
ties: {
|
|
8967
|
+
type: "enum",
|
|
8968
|
+
values: ["id", "length"],
|
|
8969
|
+
default: "length",
|
|
8970
|
+
describe: "The order of two edges with the same slack: by the edge id, or the longer edge first."
|
|
8971
|
+
}
|
|
8972
|
+
};
|
|
8973
|
+
/**
|
|
8974
|
+
* This function gives the slack of each way (WILD-023): `L2 / L1 - 1`. `L1` is the length of the shortest way.
|
|
8975
|
+
* `L2` is the cost of the shortest way when each link of the first way costs `penalty` times its length.
|
|
8976
|
+
* A way with no second way has the slack `penalty - 1`. A way with a second way of near-equal length has a slack near 0.
|
|
8977
|
+
*/
|
|
8978
|
+
function waySlacks(ways, search, penalty) {
|
|
8979
|
+
const out = /* @__PURE__ */ new Map();
|
|
8980
|
+
const n = search.corners;
|
|
8981
|
+
for (const w of ways) {
|
|
8982
|
+
const first = search.find(w);
|
|
8983
|
+
if (!first) {
|
|
8984
|
+
out.set(w, 0);
|
|
8985
|
+
continue;
|
|
8986
|
+
}
|
|
8987
|
+
const l1 = polylineLength(first.seq);
|
|
8988
|
+
const own = /* @__PURE__ */ new Set();
|
|
8989
|
+
for (let i = 1; i < first.ids.length; i++) {
|
|
8990
|
+
const a = first.ids[i - 1];
|
|
8991
|
+
const b = first.ids[i];
|
|
8992
|
+
if (a < n && b < n) own.add(search.link(a, b));
|
|
8993
|
+
}
|
|
8994
|
+
const cost = (i, j, d) => own.has(search.link(i, j)) ? d * penalty : d;
|
|
8995
|
+
const second = search.find(w, cost, 1);
|
|
8996
|
+
let l2 = l1;
|
|
8997
|
+
if (second) {
|
|
8998
|
+
l2 = 0;
|
|
8999
|
+
for (let i = 1; i < second.ids.length; i++) {
|
|
9000
|
+
const a = second.ids[i - 1];
|
|
9001
|
+
const b = second.ids[i];
|
|
9002
|
+
const d = dist(second.seq[i - 1], second.seq[i]);
|
|
9003
|
+
l2 += a < n && b < n ? cost(a, b, d) : d;
|
|
9004
|
+
}
|
|
9005
|
+
}
|
|
9006
|
+
out.set(w, l1 > 1e-9 ? l2 / l1 - 1 : 0);
|
|
9007
|
+
}
|
|
9008
|
+
return out;
|
|
9009
|
+
}
|
|
9010
|
+
/**
|
|
9011
|
+
* This function makes the router `tight` (WILD-023). It is the router `shared` with another order of the edges of each key.
|
|
9012
|
+
* With `first: tight`, the edges with the largest slack value go first: their second way costs much more, so they have few good ways.
|
|
9013
|
+
* The edges with many ways of near-equal cost go last. The keys keep the order of `shared`.
|
|
9014
|
+
*/
|
|
9015
|
+
function tightRouter(options) {
|
|
9016
|
+
const o = {
|
|
9017
|
+
...TIGHT_DEFAULTS,
|
|
9018
|
+
...options
|
|
9019
|
+
};
|
|
9020
|
+
const penalty = Math.min(10, Math.max(1, o.penalty));
|
|
9021
|
+
const sign = o.first === "loose" ? 1 : -1;
|
|
9022
|
+
return sharedRouter({
|
|
9023
|
+
name: o.name,
|
|
9024
|
+
...o.rule === void 0 ? {} : { rule: o.rule },
|
|
9025
|
+
...o.maxNodes === void 0 ? {} : { maxNodes: o.maxNodes }
|
|
9026
|
+
}, void 0, { order: (ways, search) => {
|
|
9027
|
+
const slack = waySlacks(ways, search, penalty);
|
|
9028
|
+
const span = (w) => dist(w.ps.point, w.pt.point);
|
|
9029
|
+
return [...ways].sort((p, q) => byText(p.key, q.key) || sign * (slack.get(p) - slack.get(q)) || (o.ties === "length" ? span(q) - span(p) : 0) || byText(p.edge.id, q.edge.id));
|
|
9030
|
+
} });
|
|
9031
|
+
}
|
|
9032
|
+
/**
|
|
9033
|
+
* The router strategy `tight` (WILD-023), with the rule `byCollection` of `shared`. It is not a built-in router, so the benchmark and the hashes of `ROUTERS` do not change.
|
|
9034
|
+
* A screen module or a consumer adds it to a registry.
|
|
9035
|
+
*/
|
|
9036
|
+
const tightStrategy = defineStrategy({
|
|
9037
|
+
step: "router",
|
|
9038
|
+
name: "tight",
|
|
9039
|
+
describe: "Tight edges first (WILD-023). The router `shared` routes first the edges whose second way costs much more than their first way.",
|
|
9040
|
+
defaults: { ...TIGHT_DEFAULTS },
|
|
9041
|
+
schema: TIGHT_SCHEMA,
|
|
9042
|
+
maxNodes: 1e4,
|
|
9043
|
+
run: (graph, o) => tightRouter({
|
|
9044
|
+
...o,
|
|
9045
|
+
name: "tight",
|
|
9046
|
+
rule: byCollection
|
|
9047
|
+
}).route(graph)
|
|
9048
|
+
});
|
|
9049
|
+
// src/routers/attraction/index.ts
|
|
9050
|
+
/** The default options of the router `attraction`. */
|
|
9051
|
+
const ATTRACTION_DEFAULTS = Object.freeze({
|
|
9052
|
+
passes: 3,
|
|
9053
|
+
attraction: .5,
|
|
9054
|
+
capacityWeight: 1,
|
|
9055
|
+
laneWidth: 4
|
|
9056
|
+
});
|
|
9057
|
+
/** The declarations of the options of the router `attraction` (HWY-044). */
|
|
9058
|
+
const ATTRACTION_SCHEMA = {
|
|
9059
|
+
passes: {
|
|
9060
|
+
type: "integer",
|
|
9061
|
+
min: 1,
|
|
9062
|
+
max: 10,
|
|
9063
|
+
step: 1,
|
|
9064
|
+
marks: [
|
|
9065
|
+
2,
|
|
9066
|
+
3,
|
|
9067
|
+
5
|
|
9068
|
+
],
|
|
9069
|
+
default: 3,
|
|
9070
|
+
describe: "The number of passes. Each later pass routes each edge again from no ways."
|
|
9071
|
+
},
|
|
9072
|
+
attraction: {
|
|
9073
|
+
type: "number",
|
|
9074
|
+
min: 0,
|
|
9075
|
+
max: 1,
|
|
9076
|
+
step: .05,
|
|
9077
|
+
marks: [
|
|
9078
|
+
.25,
|
|
9079
|
+
.5,
|
|
9080
|
+
.75
|
|
9081
|
+
],
|
|
9082
|
+
default: .5,
|
|
9083
|
+
describe: "The pull of the history of a link. A busy link of the passes before costs less."
|
|
9084
|
+
},
|
|
9085
|
+
capacityWeight: {
|
|
9086
|
+
type: "number",
|
|
9087
|
+
min: 0,
|
|
9088
|
+
max: 10,
|
|
9089
|
+
step: .5,
|
|
9090
|
+
marks: [
|
|
9091
|
+
0,
|
|
9092
|
+
1,
|
|
9093
|
+
4
|
|
9094
|
+
],
|
|
9095
|
+
default: 1,
|
|
9096
|
+
describe: "The cost weight of each edge over the capacity of a link."
|
|
9097
|
+
},
|
|
9098
|
+
laneWidth: {
|
|
9099
|
+
type: "number",
|
|
9100
|
+
min: 1,
|
|
9101
|
+
max: 12,
|
|
9102
|
+
step: 1,
|
|
9103
|
+
marks: [4, 6],
|
|
9104
|
+
default: 4,
|
|
9105
|
+
describe: "The width of one lane. The capacity of a link is its free width divided by this width."
|
|
9106
|
+
}
|
|
9107
|
+
};
|
|
9108
|
+
/**
|
|
9109
|
+
* This function makes the router `attraction` (HWY-044). It is the router `shared` with a negotiation over the passes.
|
|
9110
|
+
* After each pass, the history `h` of each link grows by `use / m`, with `m` the edges of the key. The history stops at `0.9 / a`.
|
|
9111
|
+
* In each pass, a link costs `len (1 - a h) (1 + b max(0, use - cap))` times the cost in `shared`.
|
|
9112
|
+
* The busy links of the passes before thus pull the edges, and a link over its capacity pushes them away.
|
|
9113
|
+
*/
|
|
9114
|
+
function attractionRouter(options) {
|
|
8078
9115
|
const o = {
|
|
8079
9116
|
...ATTRACTION_DEFAULTS,
|
|
8080
9117
|
...options
|
|
@@ -16779,41 +17816,352 @@ function interchangePlan(input, o) {
|
|
|
16779
17816
|
});
|
|
16780
17817
|
return tidyPlan({ highways });
|
|
16781
17818
|
}
|
|
16782
|
-
// src/
|
|
17819
|
+
// src/highways/knee/root.ts
|
|
16783
17820
|
/**
|
|
16784
|
-
* This function gives the
|
|
16785
|
-
*
|
|
17821
|
+
* This function gives the root of the tree of one source node (NAT-018, NAT-019): the start point nearest the center of the targets.
|
|
17822
|
+
* It also gives the lead of each edge, the way from its own start point to the root. A lead is `undefined` when the corner graph has no way.
|
|
16786
17823
|
*/
|
|
16787
|
-
function
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
17824
|
+
function rootOf(finder, starts, goals) {
|
|
17825
|
+
const c = {
|
|
17826
|
+
x: goals.reduce((s, p) => s + p.x, 0) / goals.length,
|
|
17827
|
+
y: goals.reduce((s, p) => s + p.y, 0) / goals.length
|
|
17828
|
+
};
|
|
17829
|
+
let root = starts[0];
|
|
17830
|
+
for (const s of starts) if (dist(s, c) < dist(root, c) - 1e-9) root = s;
|
|
17831
|
+
const leads = starts.map((s) => {
|
|
17832
|
+
if (dist(s, root) <= 1e-9) return [root];
|
|
17833
|
+
if (finder.obstacles.clearPath([s, root], finder.margin / 2)) return [s, root];
|
|
17834
|
+
const way = finder.corners.path(s, root);
|
|
17835
|
+
return way ? finder.corners.pull(way) : void 0;
|
|
17836
|
+
});
|
|
17837
|
+
return {
|
|
17838
|
+
root,
|
|
17839
|
+
leads
|
|
17840
|
+
};
|
|
17841
|
+
}
|
|
17842
|
+
// src/highways/knee/index.ts
|
|
17843
|
+
/** The values of `KneeRule`. */
|
|
17844
|
+
const KNEE_RULES = Object.freeze(["chord", "weights"]);
|
|
17845
|
+
/** The default options of the strategy `knee`. */
|
|
17846
|
+
const KNEE_DEFAULTS = Object.freeze({
|
|
17847
|
+
sweep: 11,
|
|
17848
|
+
rule: "chord",
|
|
17849
|
+
ink: .5,
|
|
17850
|
+
detour: 1.5,
|
|
17851
|
+
minSize: 2
|
|
17852
|
+
});
|
|
17853
|
+
/** This function gives the ink of a fan: the sum of the lengths of its links. */
|
|
17854
|
+
function fanInk(fan) {
|
|
17855
|
+
let sum = 0;
|
|
17856
|
+
for (const p of fan.points) if (p.parent >= 0) sum += dist(p.point, fan.points[p.parent].point);
|
|
17857
|
+
return sum;
|
|
16794
17858
|
}
|
|
16795
17859
|
/**
|
|
16796
|
-
* This function
|
|
16797
|
-
*
|
|
16798
|
-
* `amb = Σ_v count(w in N(v) : no edge v-w) / Σ_v count(N(v))`. The cost is quadratic, so a seeded sample serves above `exact` nodes.
|
|
17860
|
+
* This function adds a stub point at `stub` from the end of the last link of a way. The tree of NAT-017 joins a new branch only at an open point or on a link to one.
|
|
17861
|
+
* The last point of a way is not open, so without the stub no branch joins the last link.
|
|
16799
17862
|
*/
|
|
16800
|
-
function
|
|
16801
|
-
|
|
16802
|
-
const
|
|
16803
|
-
const
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
16807
|
-
|
|
17863
|
+
function withStub(way, stub) {
|
|
17864
|
+
if (way.length < 2) return way;
|
|
17865
|
+
const a = way[way.length - 2];
|
|
17866
|
+
const b = way[way.length - 1];
|
|
17867
|
+
const d = dist(a, b);
|
|
17868
|
+
if (d <= 2 * stub) return way;
|
|
17869
|
+
return [
|
|
17870
|
+
...way.slice(0, -1),
|
|
17871
|
+
{
|
|
17872
|
+
x: b.x + (a.x - b.x) * stub / d,
|
|
17873
|
+
y: b.y + (a.y - b.y) * stub / d
|
|
17874
|
+
},
|
|
17875
|
+
b
|
|
17876
|
+
];
|
|
17877
|
+
}
|
|
17878
|
+
/**
|
|
17879
|
+
* This function gives the index of the tree that the rule picks on the front of the trees (NAT-018).
|
|
17880
|
+
* The front holds the trees that no other tree beats on the ink and on the path lengths. The two values go to the range from 0 to 1.
|
|
17881
|
+
* With `chord`, the pick is the tree of the front farthest from the line between the two ends of the front.
|
|
17882
|
+
* With `weights`, the pick is the tree with the least `ink × L + (1 - ink) × S`. A tie goes to the earlier tree.
|
|
17883
|
+
*/
|
|
17884
|
+
function kneeOf(trees, rule, ink) {
|
|
17885
|
+
if (trees.length === 0) return -1;
|
|
17886
|
+
const front = trees.map((_, i) => i).filter((i) => !trees.some((q, j) => j !== i && q.ink <= trees[i].ink && q.paths <= trees[i].paths && (q.ink < trees[i].ink || q.paths < trees[i].paths)));
|
|
17887
|
+
const lo = {
|
|
17888
|
+
x: Math.min(...front.map((i) => trees[i].ink)),
|
|
17889
|
+
y: Math.min(...front.map((i) => trees[i].paths))
|
|
17890
|
+
};
|
|
17891
|
+
const hi = {
|
|
17892
|
+
x: Math.max(...front.map((i) => trees[i].ink)),
|
|
17893
|
+
y: Math.max(...front.map((i) => trees[i].paths))
|
|
17894
|
+
};
|
|
17895
|
+
const norm = (i) => ({
|
|
17896
|
+
x: hi.x - lo.x > 1e-9 ? (trees[i].ink - lo.x) / (hi.x - lo.x) : 0,
|
|
17897
|
+
y: hi.y - lo.y > 1e-9 ? (trees[i].paths - lo.y) / (hi.y - lo.y) : 0
|
|
17898
|
+
});
|
|
17899
|
+
let best = front[0];
|
|
17900
|
+
let score = rule === "chord" ? -Infinity : Infinity;
|
|
17901
|
+
if (rule === "weights") {
|
|
17902
|
+
const w = Math.min(1, Math.max(0, ink));
|
|
17903
|
+
for (const i of front) {
|
|
17904
|
+
const p = norm(i);
|
|
17905
|
+
const s = w * p.x + (1 - w) * p.y;
|
|
17906
|
+
if (s < score - 1e-12) [best, score] = [i, s];
|
|
17907
|
+
}
|
|
17908
|
+
return best;
|
|
16808
17909
|
}
|
|
16809
|
-
const
|
|
16810
|
-
const
|
|
16811
|
-
const
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
17910
|
+
const a = front.reduce((u, i) => trees[i].ink < trees[u].ink || trees[i].ink === trees[u].ink && trees[i].paths < trees[u].paths ? i : u);
|
|
17911
|
+
const b = front.reduce((u, i) => trees[i].paths < trees[u].paths || trees[i].paths === trees[u].paths && trees[i].ink < trees[u].ink ? i : u);
|
|
17912
|
+
const A = norm(a);
|
|
17913
|
+
const B = norm(b);
|
|
17914
|
+
const len = dist(A, B);
|
|
17915
|
+
if (len <= 1e-12) return a;
|
|
17916
|
+
for (const i of front) {
|
|
17917
|
+
const p = norm(i);
|
|
17918
|
+
const s = Math.abs((B.x - A.x) * (A.y - p.y) - (A.x - p.x) * (B.y - A.y)) / len;
|
|
17919
|
+
if (s > score + 1e-12) [best, score] = [i, s];
|
|
17920
|
+
}
|
|
17921
|
+
return best;
|
|
17922
|
+
}
|
|
17923
|
+
/**
|
|
17924
|
+
* This function gives the highways of the knee trees (NAT-018). The edges from one source node get one tree. Its root is the out point of the plug nearest the center of the targets.
|
|
17925
|
+
* A sweep of the balancing factor of NAT-017 gives trees from the least ink to the most direct paths.
|
|
17926
|
+
* The tree at the knee of the front of ink and path lengths is the pick. Each edge takes its path in the tree, when that path is short enough.
|
|
17927
|
+
* The stretches of the tree that two edges or more share become highways.
|
|
17928
|
+
*/
|
|
17929
|
+
function kneePlan(input, o) {
|
|
17930
|
+
const finder = new WayFinder(input.graph.nodes);
|
|
17931
|
+
const first = firstRoutes(input);
|
|
17932
|
+
const groups = /* @__PURE__ */ new Map();
|
|
17933
|
+
for (const p of edgePlugs(input.graph)) {
|
|
17934
|
+
const s = finder.out(p.ps);
|
|
17935
|
+
const t = finder.out(p.pt);
|
|
17936
|
+
const route = first.get(p.edge.id);
|
|
17937
|
+
const group = p.edge.source.node;
|
|
17938
|
+
const list = groups.get(group) ?? [];
|
|
17939
|
+
list.push({
|
|
17940
|
+
edge: p.edge.id,
|
|
17941
|
+
key: p.edge.source.node,
|
|
17942
|
+
s,
|
|
17943
|
+
t,
|
|
17944
|
+
base: route ? polylineLength(route) : dist(s, t)
|
|
17945
|
+
});
|
|
17946
|
+
groups.set(group, list);
|
|
17947
|
+
}
|
|
17948
|
+
const sweep = Math.min(21, Math.max(2, Math.floor(o.sweep)));
|
|
17949
|
+
const ways = [];
|
|
17950
|
+
for (const group of [...groups.keys()].sort()) {
|
|
17951
|
+
const list = groups.get(group);
|
|
17952
|
+
if (list.length < 2) continue;
|
|
17953
|
+
const goals = list.map((e) => e.t);
|
|
17954
|
+
const { root, leads } = rootOf(finder, list.map((e) => e.s), goals);
|
|
17955
|
+
const lost = /* @__PURE__ */ new Set();
|
|
17956
|
+
const branch = (from, t) => {
|
|
17957
|
+
const way = finder.corners.path(from, goals[t]);
|
|
17958
|
+
if (way) return withStub(finder.corners.pull(way), finder.margin);
|
|
17959
|
+
lost.add(t);
|
|
17960
|
+
return [from, goals[t]];
|
|
17961
|
+
};
|
|
17962
|
+
const trees = [];
|
|
17963
|
+
for (let k = 0; k < sweep; k++) {
|
|
17964
|
+
const a = (k + 1) / sweep;
|
|
17965
|
+
const bf = (1 - a) / a;
|
|
17966
|
+
const fan = balancedFan(root, goals, bf, branch);
|
|
17967
|
+
let paths = 0;
|
|
17968
|
+
for (const leaf of fan.leaf) paths += fan.points[leaf].pl;
|
|
17969
|
+
trees.push({
|
|
17970
|
+
bf,
|
|
17971
|
+
ink: fanInk(fan),
|
|
17972
|
+
paths,
|
|
17973
|
+
fan
|
|
17974
|
+
});
|
|
17975
|
+
}
|
|
17976
|
+
const pick = trees[kneeOf(trees, o.rule, o.ink)];
|
|
17977
|
+
list.forEach((e, t) => {
|
|
17978
|
+
const lead = leads[t];
|
|
17979
|
+
if (lost.has(t) || !lead) return;
|
|
17980
|
+
const points = [...lead, ...fanPath(pick.fan, pick.fan.leaf[t]).slice(1)];
|
|
17981
|
+
if (points.length < 2 || polylineLength(points) > o.detour * e.base) return;
|
|
17982
|
+
ways.push({
|
|
17983
|
+
edge: e.edge,
|
|
17984
|
+
key: e.key,
|
|
17985
|
+
points
|
|
17986
|
+
});
|
|
17987
|
+
});
|
|
17988
|
+
}
|
|
17989
|
+
return sharedStretches(ways, { minSize: Math.max(2, Math.floor(o.minSize)) });
|
|
17990
|
+
}
|
|
17991
|
+
// src/highways/bronchial/index.ts
|
|
17992
|
+
/** The values of `BronchialSplit`. */
|
|
17993
|
+
const BRONCHIAL_SPLITS = Object.freeze([
|
|
17994
|
+
"count",
|
|
17995
|
+
"weight",
|
|
17996
|
+
"gap"
|
|
17997
|
+
]);
|
|
17998
|
+
/** The default options of the strategy `bronchial`. */
|
|
17999
|
+
const BRONCHIAL_DEFAULTS = Object.freeze({
|
|
18000
|
+
split: "count",
|
|
18001
|
+
share: .5,
|
|
18002
|
+
smallest: 1,
|
|
18003
|
+
detour: 1.5,
|
|
18004
|
+
minSize: 2
|
|
18005
|
+
});
|
|
18006
|
+
/**
|
|
18007
|
+
* This function gives the diamond angle of the direction `(dx, dy)`: a value from 0 to 4 that grows with the angle, with no trigonometry.
|
|
18008
|
+
* The value 0 is the direction of the positive x axis.
|
|
18009
|
+
*/
|
|
18010
|
+
function diamondAngle(dx, dy) {
|
|
18011
|
+
const s = Math.abs(dx) + Math.abs(dy);
|
|
18012
|
+
if (s <= 1e-12) return 0;
|
|
18013
|
+
if (dy >= 0) return dx >= 0 ? dy / s : 1 - dx / s;
|
|
18014
|
+
return dx < 0 ? 2 - dy / s : 3 + dx / s;
|
|
18015
|
+
}
|
|
18016
|
+
/**
|
|
18017
|
+
* This function splits the targets of a tip in two halves (NAT-019). The targets go in the order of their angles around the tip.
|
|
18018
|
+
* The order starts after the widest gap of the angles, so each half is a sector. The rule of `split` then cuts the order in two.
|
|
18019
|
+
*/
|
|
18020
|
+
function splitTargets(tip, targets, ids, split) {
|
|
18021
|
+
const sorted = [...ids].sort((a, b) => diamondAngle(targets[a].x - tip.x, targets[a].y - tip.y) - diamondAngle(targets[b].x - tip.x, targets[b].y - tip.y) || a - b);
|
|
18022
|
+
const angle = (i) => diamondAngle(targets[sorted[i]].x - tip.x, targets[sorted[i]].y - tip.y);
|
|
18023
|
+
const n = sorted.length;
|
|
18024
|
+
const gaps = sorted.map((_, i) => i === n - 1 ? angle(0) + 4 - angle(i) : angle(i + 1) - angle(i));
|
|
18025
|
+
let widest = 0;
|
|
18026
|
+
for (let i = 1; i < n; i++) if (gaps[i] > gaps[widest] + 1e-12) widest = i;
|
|
18027
|
+
const order = [...sorted.slice(widest + 1), ...sorted.slice(0, widest + 1)];
|
|
18028
|
+
let cut = Math.floor(n / 2);
|
|
18029
|
+
if (split === "weight") {
|
|
18030
|
+
const w = order.map((i) => dist(tip, targets[i]));
|
|
18031
|
+
const total = w.reduce((s, x) => s + x, 0);
|
|
18032
|
+
let run = 0;
|
|
18033
|
+
let best = Infinity;
|
|
18034
|
+
for (let c = 1; c < n; c++) {
|
|
18035
|
+
run += w[c - 1];
|
|
18036
|
+
const off = Math.abs(2 * run - total);
|
|
18037
|
+
if (off < best - 1e-9) [best, cut] = [off, c];
|
|
18038
|
+
}
|
|
18039
|
+
} else if (split === "gap") {
|
|
18040
|
+
let best = -Infinity;
|
|
18041
|
+
const at = (k) => diamondAngle(targets[order[k]].x - tip.x, targets[order[k]].y - tip.y);
|
|
18042
|
+
for (let c = 1; c < n; c++) {
|
|
18043
|
+
const g = (at(c) - at(c - 1) + 4) % 4;
|
|
18044
|
+
if (g > best + 1e-12) [best, cut] = [g, c];
|
|
18045
|
+
}
|
|
18046
|
+
}
|
|
18047
|
+
return [order.slice(0, cut), order.slice(cut)];
|
|
18048
|
+
}
|
|
18049
|
+
/**
|
|
18050
|
+
* This function gives the highways of the bronchial trees (NAT-019, after Kitaoka and others, 1999). The edges from one source node get one tree.
|
|
18051
|
+
* The root of a tree is the out point of the plug nearest the center of the targets.
|
|
18052
|
+
* From the tip, the tree grows a share of the way to the center of its targets. Then it splits the targets in two and grows a branch for each half.
|
|
18053
|
+
* A branch that hits a box grows half as far, or not at all. A small set of targets gets its own ways from the tip, through the corner graph.
|
|
18054
|
+
* Each edge takes its path in the tree when that path is short enough, and the stretches that two edges or more share become highways.
|
|
18055
|
+
*/
|
|
18056
|
+
function bronchialPlan(input, o) {
|
|
18057
|
+
const finder = new WayFinder(input.graph.nodes);
|
|
18058
|
+
const pad = finder.margin / 2;
|
|
18059
|
+
const first = firstRoutes(input);
|
|
18060
|
+
const groups = /* @__PURE__ */ new Map();
|
|
18061
|
+
for (const p of edgePlugs(input.graph)) {
|
|
18062
|
+
const s = finder.out(p.ps);
|
|
18063
|
+
const t = finder.out(p.pt);
|
|
18064
|
+
const route = first.get(p.edge.id);
|
|
18065
|
+
const group = p.edge.source.node;
|
|
18066
|
+
const list = groups.get(group) ?? [];
|
|
18067
|
+
list.push({
|
|
18068
|
+
edge: p.edge.id,
|
|
18069
|
+
key: p.edge.source.node,
|
|
18070
|
+
s,
|
|
18071
|
+
t,
|
|
18072
|
+
base: route ? polylineLength(route) : dist(s, t)
|
|
18073
|
+
});
|
|
18074
|
+
groups.set(group, list);
|
|
18075
|
+
}
|
|
18076
|
+
const share = Math.min(.7, Math.max(.3, o.share));
|
|
18077
|
+
const smallest = Math.min(3, Math.max(1, Math.floor(o.smallest)));
|
|
18078
|
+
const ways = [];
|
|
18079
|
+
for (const group of [...groups.keys()].sort()) {
|
|
18080
|
+
const list = groups.get(group);
|
|
18081
|
+
if (list.length < 2) continue;
|
|
18082
|
+
const goals = list.map((e) => e.t);
|
|
18083
|
+
const { root, leads } = rootOf(finder, list.map((e) => e.s), goals);
|
|
18084
|
+
const paths = list.map(() => void 0);
|
|
18085
|
+
const grow = (trunk, ids) => {
|
|
18086
|
+
const tip = trunk[trunk.length - 1];
|
|
18087
|
+
if (ids.length <= smallest) {
|
|
18088
|
+
for (const i of ids) {
|
|
18089
|
+
const way = finder.corners.path(tip, goals[i]);
|
|
18090
|
+
if (way) paths[i] = [...trunk, ...finder.corners.pull(way).slice(1)];
|
|
18091
|
+
}
|
|
18092
|
+
return;
|
|
18093
|
+
}
|
|
18094
|
+
const c = {
|
|
18095
|
+
x: ids.reduce((s, i) => s + goals[i].x, 0) / ids.length,
|
|
18096
|
+
y: ids.reduce((s, i) => s + goals[i].y, 0) / ids.length
|
|
18097
|
+
};
|
|
18098
|
+
let next = trunk;
|
|
18099
|
+
for (let f = share; f > share / 8; f /= 2) {
|
|
18100
|
+
const p = {
|
|
18101
|
+
x: tip.x + f * (c.x - tip.x),
|
|
18102
|
+
y: tip.y + f * (c.y - tip.y)
|
|
18103
|
+
};
|
|
18104
|
+
if (dist(p, tip) <= 1e-6) break;
|
|
18105
|
+
if (!finder.obstacles.inside(p, pad) && finder.obstacles.clearPath([tip, p], pad)) {
|
|
18106
|
+
next = [...trunk, p];
|
|
18107
|
+
break;
|
|
18108
|
+
}
|
|
18109
|
+
}
|
|
18110
|
+
const [a, b] = splitTargets(next[next.length - 1], goals, ids, o.split);
|
|
18111
|
+
grow(next, a);
|
|
18112
|
+
grow(next, b);
|
|
18113
|
+
};
|
|
18114
|
+
grow([root], list.map((_, i) => i));
|
|
18115
|
+
list.forEach((e, i) => {
|
|
18116
|
+
const tail = paths[i];
|
|
18117
|
+
const lead = leads[i];
|
|
18118
|
+
if (!tail || !lead) return;
|
|
18119
|
+
const points = [...lead, ...tail.slice(1)];
|
|
18120
|
+
if (points.length < 2 || polylineLength(points) > o.detour * e.base) return;
|
|
18121
|
+
ways.push({
|
|
18122
|
+
edge: e.edge,
|
|
18123
|
+
key: e.key,
|
|
18124
|
+
points
|
|
18125
|
+
});
|
|
18126
|
+
});
|
|
18127
|
+
}
|
|
18128
|
+
return sharedStretches(ways, { minSize: Math.max(2, Math.floor(o.minSize)) });
|
|
18129
|
+
}
|
|
18130
|
+
// src/bench/measures/bundles.ts
|
|
18131
|
+
/**
|
|
18132
|
+
* This function gives the share of the ink that shared segments save (B1 of MEAS0): `inkSaved = 1 - ink(S) / Σ_e L(r_e)`.
|
|
18133
|
+
* `ink(S)` counts a shared segment one time, as `measureQuality` does. The scale is 0 to 1, and higher is better. The cost is `n log n`.
|
|
18134
|
+
*/
|
|
18135
|
+
function inkSaved(routes) {
|
|
18136
|
+
let total = 0;
|
|
18137
|
+
for (const r of routes) total += polylineLength(r.points);
|
|
18138
|
+
if (total <= 0) return 0;
|
|
18139
|
+
let ink = 0;
|
|
18140
|
+
for (const [a, b] of drawnSegments(routes)) ink += dist(a, b);
|
|
18141
|
+
return Math.max(0, 1 - ink / total);
|
|
18142
|
+
}
|
|
18143
|
+
/**
|
|
18144
|
+
* This function measures the ambiguity of the routes (B4 of MEAS0), with one step of neighbours.
|
|
18145
|
+
* `N(v)` holds the nodes whose routes share a drawn segment with a route of `v`.
|
|
18146
|
+
* `amb = Σ_v count(w in N(v) : no edge v-w) / Σ_v count(N(v))`. The cost is quadratic, so a seeded sample serves above `exact` nodes.
|
|
18147
|
+
*/
|
|
18148
|
+
function ambiguity(graph, routes, options = {}) {
|
|
18149
|
+
const n = graph.nodes.length;
|
|
18150
|
+
const index = new Map(graph.nodes.map((v, i) => [v.id, i]));
|
|
18151
|
+
const ends = /* @__PURE__ */ new Map();
|
|
18152
|
+
for (const e of graph.edges) {
|
|
18153
|
+
const s = index.get(e.source.node);
|
|
18154
|
+
const t = index.get(e.target.node);
|
|
18155
|
+
if (s !== void 0 && t !== void 0) ends.set(e.id, [s, t]);
|
|
18156
|
+
}
|
|
18157
|
+
const keyIds = /* @__PURE__ */ new Map();
|
|
18158
|
+
const nodesOf = [];
|
|
18159
|
+
const keysOf = /* @__PURE__ */ new Map();
|
|
18160
|
+
for (const r of routes) {
|
|
18161
|
+
const end = ends.get(r.edge);
|
|
18162
|
+
if (!end) continue;
|
|
18163
|
+
const own = [];
|
|
18164
|
+
for (let i = 1; i < r.points.length; i++) {
|
|
16817
18165
|
const k = segmentKey(r.points[i - 1], r.points[i]);
|
|
16818
18166
|
if (k === void 0) continue;
|
|
16819
18167
|
let id = keyIds.get(k);
|
|
@@ -19641,6 +20989,243 @@ function zipperPlan(input, o) {
|
|
|
19641
20989
|
}
|
|
19642
20990
|
return tidyPlan(plan);
|
|
19643
20991
|
}
|
|
20992
|
+
// src/levels/venation/index.ts
|
|
20993
|
+
/** The values of `VenationBackbone`. */
|
|
20994
|
+
const VENATION_BACKBONES = Object.freeze(["rng", "gabriel"]);
|
|
20995
|
+
/** The default options of the level strategy `venation`. */
|
|
20996
|
+
const VENATION_DEFAULTS = Object.freeze({
|
|
20997
|
+
backbone: "rng",
|
|
20998
|
+
discount: .4,
|
|
20999
|
+
detour: 1.5,
|
|
21000
|
+
maxItems: 400
|
|
21001
|
+
});
|
|
21002
|
+
/**
|
|
21003
|
+
* This function gives the links of the backbone of the points, as pairs of indices `i < j` (NAT-016).
|
|
21004
|
+
* With `rng`, the link `p q` exists when no third point `r` has `max(d(p, r), d(q, r)) < d(p, q)`.
|
|
21005
|
+
* With `gabriel`, the link exists when no third point is inside the circle with the diameter `p q`.
|
|
21006
|
+
*/
|
|
21007
|
+
function backboneLinks(points, backbone) {
|
|
21008
|
+
const out = [];
|
|
21009
|
+
const n = points.length;
|
|
21010
|
+
const d2 = (a, b) => (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
|
|
21011
|
+
for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) {
|
|
21012
|
+
const pq = d2(points[i], points[j]);
|
|
21013
|
+
let keep = true;
|
|
21014
|
+
for (let r = 0; r < n && keep; r++) {
|
|
21015
|
+
if (r === i || r === j) continue;
|
|
21016
|
+
const pr = d2(points[i], points[r]);
|
|
21017
|
+
const qr = d2(points[j], points[r]);
|
|
21018
|
+
if (backbone === "rng" ? Math.max(pr, qr) < pq - 1e-9 : pr + qr < pq - 1e-9) keep = false;
|
|
21019
|
+
}
|
|
21020
|
+
if (keep) out.push([i, j]);
|
|
21021
|
+
}
|
|
21022
|
+
return out;
|
|
21023
|
+
}
|
|
21024
|
+
/**
|
|
21025
|
+
* This function gives a way around the box `b`, inflated by `m`, from the point `a` to the point `c` of its boundary.
|
|
21026
|
+
* The way goes along the shorter side of the inflated box. It holds the two points at `m` out from `a` and `c`, and the corners between them.
|
|
21027
|
+
*/
|
|
21028
|
+
function aroundBox(b, m, a, c) {
|
|
21029
|
+
const x0 = b.x - m;
|
|
21030
|
+
const y0 = b.y - m;
|
|
21031
|
+
const x1 = b.x + b.w + m;
|
|
21032
|
+
const y1 = b.y + b.h + m;
|
|
21033
|
+
const w = x1 - x0;
|
|
21034
|
+
const h = y1 - y0;
|
|
21035
|
+
const per = 2 * (w + h);
|
|
21036
|
+
const onBoundary = (p) => {
|
|
21037
|
+
const cx = Math.min(x1, Math.max(x0, p.x));
|
|
21038
|
+
const cy = Math.min(y1, Math.max(y0, p.y));
|
|
21039
|
+
const dl = cx - x0;
|
|
21040
|
+
const dr = x1 - cx;
|
|
21041
|
+
const dt = cy - y0;
|
|
21042
|
+
const db = y1 - cy;
|
|
21043
|
+
const least = Math.min(dl, dr, dt, db);
|
|
21044
|
+
if (least === dt) return {
|
|
21045
|
+
p: {
|
|
21046
|
+
x: cx,
|
|
21047
|
+
y: y0
|
|
21048
|
+
},
|
|
21049
|
+
t: cx - x0
|
|
21050
|
+
};
|
|
21051
|
+
if (least === dr) return {
|
|
21052
|
+
p: {
|
|
21053
|
+
x: x1,
|
|
21054
|
+
y: cy
|
|
21055
|
+
},
|
|
21056
|
+
t: w + (cy - y0)
|
|
21057
|
+
};
|
|
21058
|
+
if (least === db) return {
|
|
21059
|
+
p: {
|
|
21060
|
+
x: cx,
|
|
21061
|
+
y: y1
|
|
21062
|
+
},
|
|
21063
|
+
t: w + h + (x1 - cx)
|
|
21064
|
+
};
|
|
21065
|
+
return {
|
|
21066
|
+
p: {
|
|
21067
|
+
x: x0,
|
|
21068
|
+
y: cy
|
|
21069
|
+
},
|
|
21070
|
+
t: 2 * w + h + (y1 - cy)
|
|
21071
|
+
};
|
|
21072
|
+
};
|
|
21073
|
+
const corners = [
|
|
21074
|
+
{
|
|
21075
|
+
p: {
|
|
21076
|
+
x: x0,
|
|
21077
|
+
y: y0
|
|
21078
|
+
},
|
|
21079
|
+
t: 0
|
|
21080
|
+
},
|
|
21081
|
+
{
|
|
21082
|
+
p: {
|
|
21083
|
+
x: x1,
|
|
21084
|
+
y: y0
|
|
21085
|
+
},
|
|
21086
|
+
t: w
|
|
21087
|
+
},
|
|
21088
|
+
{
|
|
21089
|
+
p: {
|
|
21090
|
+
x: x1,
|
|
21091
|
+
y: y1
|
|
21092
|
+
},
|
|
21093
|
+
t: w + h
|
|
21094
|
+
},
|
|
21095
|
+
{
|
|
21096
|
+
p: {
|
|
21097
|
+
x: x0,
|
|
21098
|
+
y: y1
|
|
21099
|
+
},
|
|
21100
|
+
t: 2 * w + h
|
|
21101
|
+
}
|
|
21102
|
+
];
|
|
21103
|
+
const A = onBoundary(a);
|
|
21104
|
+
const C = onBoundary(c);
|
|
21105
|
+
const ahead = (C.t - A.t + per) % per;
|
|
21106
|
+
const out = [A.p];
|
|
21107
|
+
if (ahead <= per / 2) {
|
|
21108
|
+
const list = corners.map((k) => ({
|
|
21109
|
+
...k,
|
|
21110
|
+
s: (k.t - A.t + per) % per
|
|
21111
|
+
})).filter((k) => k.s > 1e-9 && k.s < ahead - 1e-9);
|
|
21112
|
+
for (const k of list.sort((p, q) => p.s - q.s)) out.push(k.p);
|
|
21113
|
+
} else {
|
|
21114
|
+
const back = per - ahead;
|
|
21115
|
+
const list = corners.map((k) => ({
|
|
21116
|
+
...k,
|
|
21117
|
+
s: (A.t - k.t + per) % per
|
|
21118
|
+
})).filter((k) => k.s > 1e-9 && k.s < back - 1e-9);
|
|
21119
|
+
for (const k of list.sort((p, q) => p.s - q.s)) out.push(k.p);
|
|
21120
|
+
}
|
|
21121
|
+
out.push(C.p);
|
|
21122
|
+
return out.filter((p, i) => i === 0 || dist(p, out[i - 1]) > 1e-9);
|
|
21123
|
+
}
|
|
21124
|
+
/**
|
|
21125
|
+
* This function gives the links of one level along a closed venation backbone (NAT-016, after Runions and others, 2005).
|
|
21126
|
+
* The backbone links the items by their relative neighbourhood graph or their Gabriel graph. Each backbone link goes around the other items.
|
|
21127
|
+
* The level links go in the order of their counts, the largest first. Each one takes the cheapest way on the backbone, with the cost `L (1 - discount)` for a used backbone link.
|
|
21128
|
+
* The way goes around each item between its two ends. A way longer than `detour` times the direct link, or a direct backbone link, keeps the direct link.
|
|
21129
|
+
*/
|
|
21130
|
+
function venationLinks(items, direct, o) {
|
|
21131
|
+
if (direct.length < 2 || items.length < 3 || items.length > o.maxItems) return [...direct];
|
|
21132
|
+
const index = new Map(items.map((it, i) => [it.id, i]));
|
|
21133
|
+
const links = backboneLinks(items.map((it) => center(it.box)), o.backbone);
|
|
21134
|
+
const routed = levelLinks(items, links.map(([i, j]) => ({
|
|
21135
|
+
source: items[i].id,
|
|
21136
|
+
target: items[j].id,
|
|
21137
|
+
count: 1
|
|
21138
|
+
})), o);
|
|
21139
|
+
const n = items.length;
|
|
21140
|
+
const next = Array.from({ length: n }, () => []);
|
|
21141
|
+
const cost = routed.map((l) => polylineLength(l.points));
|
|
21142
|
+
routed.forEach((l, k) => {
|
|
21143
|
+
const i = index.get(l.source);
|
|
21144
|
+
const j = index.get(l.target);
|
|
21145
|
+
next[i].push({
|
|
21146
|
+
to: j,
|
|
21147
|
+
link: k
|
|
21148
|
+
});
|
|
21149
|
+
next[j].push({
|
|
21150
|
+
to: i,
|
|
21151
|
+
link: k
|
|
21152
|
+
});
|
|
21153
|
+
});
|
|
21154
|
+
const direct2 = new Set(links.map(([i, j]) => `${i},${j}`));
|
|
21155
|
+
const used = new Uint8Array(routed.length);
|
|
21156
|
+
const share = 1 - Math.min(.8, Math.max(0, o.discount));
|
|
21157
|
+
const order = direct.map((_, k) => k).sort((p, q) => direct[q].count - direct[p].count || direct[p].source.localeCompare(direct[q].source) || direct[p].target.localeCompare(direct[q].target));
|
|
21158
|
+
const margin = o.margin ?? 5;
|
|
21159
|
+
const out = [...direct];
|
|
21160
|
+
for (const k of order) {
|
|
21161
|
+
const l = direct[k];
|
|
21162
|
+
const s = index.get(l.source);
|
|
21163
|
+
const t = index.get(l.target);
|
|
21164
|
+
if (s === void 0 || t === void 0) continue;
|
|
21165
|
+
if (direct2.has(`${Math.min(s, t)},${Math.max(s, t)}`)) {
|
|
21166
|
+
const at = next[s].find((x) => x.to === t);
|
|
21167
|
+
if (at) used[at.link] = 1;
|
|
21168
|
+
continue;
|
|
21169
|
+
}
|
|
21170
|
+
const best = new Float64Array(n).fill(Infinity);
|
|
21171
|
+
const from = new Int32Array(n).fill(-1);
|
|
21172
|
+
const via = new Int32Array(n).fill(-1);
|
|
21173
|
+
const done = new Uint8Array(n);
|
|
21174
|
+
best[s] = 0;
|
|
21175
|
+
for (;;) {
|
|
21176
|
+
let u = -1;
|
|
21177
|
+
for (let i = 0; i < n; i++) if (!done[i] && best[i] < Infinity && (u < 0 || best[i] < best[u])) u = i;
|
|
21178
|
+
if (u < 0 || u === t) break;
|
|
21179
|
+
done[u] = 1;
|
|
21180
|
+
for (const x of next[u]) {
|
|
21181
|
+
const c = best[u] + cost[x.link] * (used[x.link] ? share : 1);
|
|
21182
|
+
if (c < best[x.to] - 1e-9) {
|
|
21183
|
+
best[x.to] = c;
|
|
21184
|
+
from[x.to] = u;
|
|
21185
|
+
via[x.to] = x.link;
|
|
21186
|
+
}
|
|
21187
|
+
}
|
|
21188
|
+
}
|
|
21189
|
+
if (from[t] < 0) continue;
|
|
21190
|
+
const hops = [];
|
|
21191
|
+
for (let v = t; v !== s; v = from[v]) hops.push({
|
|
21192
|
+
link: via[v],
|
|
21193
|
+
forward: routed[via[v]].target === items[v].id
|
|
21194
|
+
});
|
|
21195
|
+
hops.reverse();
|
|
21196
|
+
const points = [];
|
|
21197
|
+
hops.forEach((h, i) => {
|
|
21198
|
+
const p = routed[h.link].points;
|
|
21199
|
+
const seq = h.forward ? [...p] : [...p].reverse();
|
|
21200
|
+
if (i > 0) {
|
|
21201
|
+
const item = items[index.get(h.forward ? routed[h.link].source : routed[h.link].target)];
|
|
21202
|
+
const around = aroundBox(item.box, margin, points[points.length - 1], seq[0]);
|
|
21203
|
+
if (points.length > 1) points.pop();
|
|
21204
|
+
points.push(...around);
|
|
21205
|
+
seq.shift();
|
|
21206
|
+
}
|
|
21207
|
+
for (const q of seq) if (points.length === 0 || dist(q, points[points.length - 1]) > 1e-9) points.push(q);
|
|
21208
|
+
});
|
|
21209
|
+
if (points.length < 2 || polylineLength(points) > o.detour * polylineLength(l.points)) continue;
|
|
21210
|
+
for (const h of hops) used[h.link] = 1;
|
|
21211
|
+
out[k] = {
|
|
21212
|
+
...l,
|
|
21213
|
+
points
|
|
21214
|
+
};
|
|
21215
|
+
}
|
|
21216
|
+
return out;
|
|
21217
|
+
}
|
|
21218
|
+
/**
|
|
21219
|
+
* This function gives the zoom levels of the level strategy `venation` (NAT-016). The levels and their items are the levels of `depths`.
|
|
21220
|
+
* In each level with groups, the links take their ways along a closed venation backbone (`venationLinks`). The last level stays as in `depths`.
|
|
21221
|
+
*/
|
|
21222
|
+
function venationLevels(input, options) {
|
|
21223
|
+
const levels = depthLevels(input, options);
|
|
21224
|
+
return levels.map((level, i) => i === levels.length - 1 ? level : {
|
|
21225
|
+
...level,
|
|
21226
|
+
links: venationLinks(level.items, level.links, options)
|
|
21227
|
+
});
|
|
21228
|
+
}
|
|
19644
21229
|
// src/strategies/options.ts
|
|
19645
21230
|
/** The number of values of a sweep on a range when the call and the declaration give no number. */
|
|
19646
21231
|
const SWEEP_COUNT = 5;
|
|
@@ -19906,77 +21491,347 @@ function runPipeline(graph, options) {
|
|
|
19906
21491
|
result: asRouter(options.router, routerOptions).route(board)
|
|
19907
21492
|
};
|
|
19908
21493
|
}
|
|
19909
|
-
// src/
|
|
19910
|
-
/** The
|
|
19911
|
-
const
|
|
19912
|
-
|
|
19913
|
-
|
|
19914
|
-
|
|
19915
|
-
|
|
19916
|
-
|
|
19917
|
-
|
|
19918
|
-
|
|
19919
|
-
|
|
19920
|
-
|
|
19921
|
-
|
|
19922
|
-
|
|
19923
|
-
|
|
19924
|
-
|
|
19925
|
-
|
|
19926
|
-
|
|
19927
|
-
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
19934
|
-
|
|
19935
|
-
const
|
|
19936
|
-
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
|
|
19942
|
-
|
|
19943
|
-
|
|
19944
|
-
|
|
19945
|
-
|
|
19946
|
-
|
|
19947
|
-
|
|
19948
|
-
|
|
19949
|
-
|
|
19950
|
-
|
|
19951
|
-
|
|
19952
|
-
|
|
19953
|
-
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
|
|
19959
|
-
|
|
19960
|
-
|
|
19961
|
-
const
|
|
19962
|
-
|
|
19963
|
-
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
19974
|
-
|
|
19975
|
-
|
|
19976
|
-
|
|
19977
|
-
|
|
19978
|
-
}
|
|
19979
|
-
|
|
21494
|
+
// src/bench/quality.ts
|
|
21495
|
+
/** The clearance stops at this distance. */
|
|
21496
|
+
const CLEARANCE_CAP = 40;
|
|
21497
|
+
const TIGHT = 4;
|
|
21498
|
+
const EXACT_CROSSINGS = 2e4;
|
|
21499
|
+
const SAMPLE = 2e3;
|
|
21500
|
+
const key = (p) => `${Math.round(p.x * 8)},${Math.round(p.y * 8)}`;
|
|
21501
|
+
/** This function measures the quality of a result. */
|
|
21502
|
+
function measureQuality(graph, result) {
|
|
21503
|
+
const byId = nodeIndex(graph);
|
|
21504
|
+
const nodes = new Grid(180);
|
|
21505
|
+
for (const n of graph.nodes) nodes.add(n, n.box);
|
|
21506
|
+
const segs = [];
|
|
21507
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21508
|
+
let bends = 0;
|
|
21509
|
+
let infl = 0;
|
|
21510
|
+
let clear = 0;
|
|
21511
|
+
let tight = 0;
|
|
21512
|
+
let detour = 0;
|
|
21513
|
+
let detourN = 0;
|
|
21514
|
+
let angle = 0;
|
|
21515
|
+
let angleN = 0;
|
|
21516
|
+
for (const r of result.routes) {
|
|
21517
|
+
const p = r.points;
|
|
21518
|
+
for (let i = 1; i < p.length; i++) {
|
|
21519
|
+
const ka = key(p[i - 1]);
|
|
21520
|
+
const kb = key(p[i]);
|
|
21521
|
+
if (ka === kb) continue;
|
|
21522
|
+
const k = ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
|
|
21523
|
+
if (seen.has(k)) continue;
|
|
21524
|
+
seen.add(k);
|
|
21525
|
+
segs.push([p[i - 1], p[i]]);
|
|
21526
|
+
}
|
|
21527
|
+
bends += bendCount(p);
|
|
21528
|
+
infl += inflectionCount(p);
|
|
21529
|
+
const ends = endNodes(graph, byId, r.edge);
|
|
21530
|
+
let c = 40;
|
|
21531
|
+
for (let i = 1; i < p.length; i++) {
|
|
21532
|
+
const a = p[i - 1];
|
|
21533
|
+
const b = p[i];
|
|
21534
|
+
nodes.querySegment(a, b, c, (n) => {
|
|
21535
|
+
if (n !== ends.source && n !== ends.target) c = Math.min(c, segmentBoxDistance(a, b, n.box));
|
|
21536
|
+
});
|
|
21537
|
+
}
|
|
21538
|
+
clear += c;
|
|
21539
|
+
if (c < TIGHT) tight++;
|
|
21540
|
+
if (p.length >= 2) {
|
|
21541
|
+
const d = dist(p[0], p[p.length - 1]);
|
|
21542
|
+
if (d > 1) {
|
|
21543
|
+
detour += polylineLength(p) / d;
|
|
21544
|
+
detourN++;
|
|
21545
|
+
}
|
|
21546
|
+
for (const [at, next, node] of [[
|
|
21547
|
+
p[0],
|
|
21548
|
+
p[1],
|
|
21549
|
+
ends.source
|
|
21550
|
+
], [
|
|
21551
|
+
p[p.length - 1],
|
|
21552
|
+
p[p.length - 2],
|
|
21553
|
+
ends.target
|
|
21554
|
+
]]) {
|
|
21555
|
+
if (!node) continue;
|
|
21556
|
+
const a = sideAngle(node.box, at, next);
|
|
21557
|
+
if (a !== void 0) {
|
|
21558
|
+
angle += a;
|
|
21559
|
+
angleN++;
|
|
21560
|
+
}
|
|
21561
|
+
}
|
|
21562
|
+
}
|
|
21563
|
+
}
|
|
21564
|
+
const n = Math.max(1, result.routes.length);
|
|
21565
|
+
let ink = 0;
|
|
21566
|
+
for (const [a, b] of segs) ink += dist(a, b);
|
|
21567
|
+
const [crossings, crossingsEstimated] = countCrossings(segs);
|
|
21568
|
+
return {
|
|
21569
|
+
ink,
|
|
21570
|
+
crossings,
|
|
21571
|
+
crossingsEstimated,
|
|
21572
|
+
bends: bends / n,
|
|
21573
|
+
inflections: infl / n,
|
|
21574
|
+
clearance: clear / n,
|
|
21575
|
+
tight: tight / n,
|
|
21576
|
+
detour: detourN ? detour / detourN : 1,
|
|
21577
|
+
plugAngle: angleN ? angle / angleN : 0
|
|
21578
|
+
};
|
|
21579
|
+
}
|
|
21580
|
+
function endNodes(graph, byId, edgeId) {
|
|
21581
|
+
const e = edgeIndex(graph).get(edgeId);
|
|
21582
|
+
return {
|
|
21583
|
+
source: e && byId.get(e.source.node),
|
|
21584
|
+
target: e && byId.get(e.target.node)
|
|
21585
|
+
};
|
|
21586
|
+
}
|
|
21587
|
+
const edgeIndexes = /* @__PURE__ */ new WeakMap();
|
|
21588
|
+
function edgeIndex(graph) {
|
|
21589
|
+
let m = edgeIndexes.get(graph);
|
|
21590
|
+
if (!m) {
|
|
21591
|
+
m = /* @__PURE__ */ new Map();
|
|
21592
|
+
for (const e of graph.edges) m.set(e.id, e);
|
|
21593
|
+
edgeIndexes.set(graph, m);
|
|
21594
|
+
}
|
|
21595
|
+
return m;
|
|
21596
|
+
}
|
|
21597
|
+
/**
|
|
21598
|
+
* This function gives an angle in degrees: the angle between the segment from `at` to `next` and the outward normal.
|
|
21599
|
+
* The normal is the normal of the side of the box nearest to `at`. A route that starts inside the box (an interior route) gives no angle.
|
|
21600
|
+
*/
|
|
21601
|
+
function sideAngle(box, at, next) {
|
|
21602
|
+
const d = [
|
|
21603
|
+
[
|
|
21604
|
+
Math.abs(at.y - box.y),
|
|
21605
|
+
0,
|
|
21606
|
+
-1
|
|
21607
|
+
],
|
|
21608
|
+
[
|
|
21609
|
+
Math.abs(at.x - (box.x + box.w)),
|
|
21610
|
+
1,
|
|
21611
|
+
0
|
|
21612
|
+
],
|
|
21613
|
+
[
|
|
21614
|
+
Math.abs(at.y - (box.y + box.h)),
|
|
21615
|
+
0,
|
|
21616
|
+
1
|
|
21617
|
+
],
|
|
21618
|
+
[
|
|
21619
|
+
Math.abs(at.x - box.x),
|
|
21620
|
+
-1,
|
|
21621
|
+
0
|
|
21622
|
+
]
|
|
21623
|
+
].sort((a, b) => a[0] - b[0])[0];
|
|
21624
|
+
if (d[0] > 1) return void 0;
|
|
21625
|
+
const vx = next.x - at.x;
|
|
21626
|
+
const vy = next.y - at.y;
|
|
21627
|
+
const l = Math.hypot(vx, vy);
|
|
21628
|
+
if (l === 0) return void 0;
|
|
21629
|
+
const cos = Math.max(-1, Math.min(1, (vx * d[1] + vy * d[2]) / l));
|
|
21630
|
+
return Math.acos(cos) * 180 / Math.PI;
|
|
21631
|
+
}
|
|
21632
|
+
/**
|
|
21633
|
+
* This function counts the crossings of a set of segments with a grid of buckets.
|
|
21634
|
+
* Above 20 000 segments, it counts the crossings of a seeded sample of 2 000 segments and scales the count.
|
|
21635
|
+
*/
|
|
21636
|
+
function countCrossings(segs) {
|
|
21637
|
+
if (segs.length < 2) return [0, false];
|
|
21638
|
+
const len = segs.reduce((s, [a, b]) => s + dist(a, b), 0) / segs.length;
|
|
21639
|
+
const grid = new Grid(Math.max(20, len));
|
|
21640
|
+
segs.forEach(([a, b], i) => grid.addSegment(i, a, b));
|
|
21641
|
+
const exact = segs.length <= EXACT_CROSSINGS;
|
|
21642
|
+
const R = rng(segs.length);
|
|
21643
|
+
const pick = exact ? segs.map((_, i) => i) : Array.from({ length: SAMPLE }, () => Math.floor(R() * segs.length));
|
|
21644
|
+
let count = 0;
|
|
21645
|
+
for (const i of pick) {
|
|
21646
|
+
const [a, b] = segs[i];
|
|
21647
|
+
grid.querySegment(a, b, 0, (j) => {
|
|
21648
|
+
if (exact && j <= i) return;
|
|
21649
|
+
if (j === i) return;
|
|
21650
|
+
const [c, d] = segs[j];
|
|
21651
|
+
if (segmentsCross(a, b, c, d)) count++;
|
|
21652
|
+
});
|
|
21653
|
+
}
|
|
21654
|
+
return exact ? [count, false] : [Math.round(count * segs.length / (2 * pick.length)), true];
|
|
21655
|
+
}
|
|
21656
|
+
/** This function measures how much the routes changed between two results. */
|
|
21657
|
+
function measureStability(before, after) {
|
|
21658
|
+
const prev = new Map(before.routes.map((r) => [r.edge, r.points]));
|
|
21659
|
+
let sum = 0;
|
|
21660
|
+
let max = 0;
|
|
21661
|
+
let moved = 0;
|
|
21662
|
+
let n = 0;
|
|
21663
|
+
for (const r of after.routes) {
|
|
21664
|
+
const p = prev.get(r.edge);
|
|
21665
|
+
if (!p) continue;
|
|
21666
|
+
const f = frechet(p, r.points);
|
|
21667
|
+
sum += f;
|
|
21668
|
+
max = Math.max(max, f);
|
|
21669
|
+
if (f > 1) moved++;
|
|
21670
|
+
n++;
|
|
21671
|
+
}
|
|
21672
|
+
return {
|
|
21673
|
+
mean: n ? sum / n : 0,
|
|
21674
|
+
max,
|
|
21675
|
+
moved: n ? moved / n : 0
|
|
21676
|
+
};
|
|
21677
|
+
}
|
|
21678
|
+
// src/whole/portfolio/index.ts
|
|
21679
|
+
/** The clock in milliseconds. */
|
|
21680
|
+
const clock = () => globalThis.performance?.now() ?? Date.now();
|
|
21681
|
+
/** The default options of the strategy `board-portfolio`. */
|
|
21682
|
+
const BOARD_PORTFOLIO_DEFAULTS = Object.freeze({
|
|
21683
|
+
settings: 4,
|
|
21684
|
+
crossings: 1,
|
|
21685
|
+
detour: 1,
|
|
21686
|
+
ink: 1,
|
|
21687
|
+
budget: 0
|
|
21688
|
+
});
|
|
21689
|
+
/** This function gives the rank of each value from 0 (the best) to 1 (the worst). Equal values get the mean of their ranks. One value gets 0. */
|
|
21690
|
+
function rankShares(values, lowerIsBetter) {
|
|
21691
|
+
const n = values.length;
|
|
21692
|
+
if (n < 2) return values.map(() => 0);
|
|
21693
|
+
const order = values.map((_, i) => i).sort((a, b) => lowerIsBetter ? values[a] - values[b] : values[b] - values[a]);
|
|
21694
|
+
const rank = new Array(n).fill(0);
|
|
21695
|
+
for (let i = 0; i < n;) {
|
|
21696
|
+
let j = i;
|
|
21697
|
+
while (j + 1 < n && Math.abs(values[order[j + 1]] - values[order[i]]) <= 1e-9) j++;
|
|
21698
|
+
for (let k = i; k <= j; k++) rank[order[k]] = (i + j) / 2 / (n - 1);
|
|
21699
|
+
i = j + 1;
|
|
21700
|
+
}
|
|
21701
|
+
return rank;
|
|
21702
|
+
}
|
|
21703
|
+
/**
|
|
21704
|
+
* This function runs the portfolio for one board (WILD-045, after Rice, 1976). It reads only the board and the outputs of its own settings.
|
|
21705
|
+
* A setting whose router or highway strategy has a node limit below the size of the board does not run.
|
|
21706
|
+
* A setting with a break of a hard rule does not count. The score of a setting is `Σ w_i × the rank share of measure_i` over the settings.
|
|
21707
|
+
* The measures are the crossings (R1), the median detour (R5) and the ink saved (B1). The least score wins, and a tie goes to the earlier setting.
|
|
21708
|
+
*/
|
|
21709
|
+
function boardPortfolio(graph, list, o, now = clock) {
|
|
21710
|
+
const count = Math.max(1, Math.min(list.length, Math.floor(o.settings)));
|
|
21711
|
+
const t0 = now();
|
|
21712
|
+
const entries = [];
|
|
21713
|
+
let fallback;
|
|
21714
|
+
for (const s of list.slice(0, count)) {
|
|
21715
|
+
if (o.budget > 0 && fallback !== void 0 && now() - t0 > o.budget * 1e3) break;
|
|
21716
|
+
const limit = (s.pipeline.highway ?? s.pipeline.router).maxNodes;
|
|
21717
|
+
if (limit !== void 0 && graph.nodes.length > limit) continue;
|
|
21718
|
+
const output = runPipeline(graph, s.pipeline);
|
|
21719
|
+
fallback ??= {
|
|
21720
|
+
name: s.name,
|
|
21721
|
+
output
|
|
21722
|
+
};
|
|
21723
|
+
if (checkProperties(output.graph, output.result, 1).length > 0) continue;
|
|
21724
|
+
entries.push({
|
|
21725
|
+
name: s.name,
|
|
21726
|
+
output,
|
|
21727
|
+
...measuresOf(output.graph, output.result)
|
|
21728
|
+
});
|
|
21729
|
+
}
|
|
21730
|
+
if (entries.length === 0) {
|
|
21731
|
+
const first = fallback ?? {
|
|
21732
|
+
name: list[0].name,
|
|
21733
|
+
output: runPipeline(graph, list[0].pipeline)
|
|
21734
|
+
};
|
|
21735
|
+
return {
|
|
21736
|
+
output: first.output,
|
|
21737
|
+
pick: first.name,
|
|
21738
|
+
entries
|
|
21739
|
+
};
|
|
21740
|
+
}
|
|
21741
|
+
const r1 = rankShares(entries.map((e) => e.crossings), true);
|
|
21742
|
+
const r5 = rankShares(entries.map((e) => e.detour), true);
|
|
21743
|
+
const b1 = rankShares(entries.map((e) => e.ink), false);
|
|
21744
|
+
let best = 0;
|
|
21745
|
+
let score = Infinity;
|
|
21746
|
+
entries.forEach((_, i) => {
|
|
21747
|
+
const s = o.crossings * r1[i] + o.detour * r5[i] + o.ink * b1[i];
|
|
21748
|
+
if (s < score - 1e-9) [best, score] = [i, s];
|
|
21749
|
+
});
|
|
21750
|
+
return {
|
|
21751
|
+
output: entries[best].output,
|
|
21752
|
+
pick: entries[best].name,
|
|
21753
|
+
entries
|
|
21754
|
+
};
|
|
21755
|
+
}
|
|
21756
|
+
/** This function gives the three measures of the score of one output: the crossings, the median detour and the ink saved. */
|
|
21757
|
+
function measuresOf(graph, result) {
|
|
21758
|
+
return {
|
|
21759
|
+
crossings: measureQuality(graph, result).crossings,
|
|
21760
|
+
detour: detourSpread(result.routes).median,
|
|
21761
|
+
ink: inkSaved(result.routes)
|
|
21762
|
+
};
|
|
21763
|
+
}
|
|
21764
|
+
// src/strategies/schemas.ts
|
|
21765
|
+
/** The options of the way finder (`WayOptions`), for each router that goes around the nodes. */
|
|
21766
|
+
const WAY_SCHEMA = {
|
|
21767
|
+
margin: {
|
|
21768
|
+
type: "number",
|
|
21769
|
+
min: 1,
|
|
21770
|
+
max: 20,
|
|
21771
|
+
step: 1,
|
|
21772
|
+
default: 5,
|
|
21773
|
+
describe: "The distance from a box to the corners of the graph."
|
|
21774
|
+
},
|
|
21775
|
+
k: {
|
|
21776
|
+
type: "integer",
|
|
21777
|
+
min: 2,
|
|
21778
|
+
max: 16,
|
|
21779
|
+
step: 1,
|
|
21780
|
+
default: 8,
|
|
21781
|
+
describe: "The number of visible neighbours of each corner."
|
|
21782
|
+
},
|
|
21783
|
+
smooth: {
|
|
21784
|
+
type: "boolean",
|
|
21785
|
+
default: false,
|
|
21786
|
+
describe: "With `true`, a way in clear view uses the inflection rule, and the other ways get round corners."
|
|
21787
|
+
}
|
|
21788
|
+
};
|
|
21789
|
+
/** The declarations of the options of the stretch order (LANE-STRETCH): the method and the passes of `sifting`. A generator can list each value. */
|
|
21790
|
+
const LANE_STRETCH_SCHEMA = {
|
|
21791
|
+
method: {
|
|
21792
|
+
type: "enum",
|
|
21793
|
+
values: [
|
|
21794
|
+
"none",
|
|
21795
|
+
"consensus",
|
|
21796
|
+
"periphery",
|
|
21797
|
+
"sifting"
|
|
21798
|
+
],
|
|
21799
|
+
default: "sifting",
|
|
21800
|
+
describe: "The order of each stretch: the order of each run alone, the order of the pair relation, the order of the largest run, or the order of the pair relation after sifting."
|
|
21801
|
+
},
|
|
21802
|
+
passes: {
|
|
21803
|
+
type: "integer",
|
|
21804
|
+
min: 1,
|
|
21805
|
+
max: 4,
|
|
21806
|
+
step: 1,
|
|
21807
|
+
default: 2,
|
|
21808
|
+
describe: "The number of passes of the method `sifting`. In each pass, each edge moves to its best place."
|
|
21809
|
+
}
|
|
21810
|
+
};
|
|
21811
|
+
/**
|
|
21812
|
+
* The declaration of the grouping rule of the edges: a name of `GROUP_RULE_NAMES`. A new name of the list needs no change here.
|
|
21813
|
+
* The option also takes a rule of the caller (`custom`), so the old value form stays valid.
|
|
21814
|
+
* The `defaults` of a strategy keep the rule `byCollection`, and the declaration gives its name `collection` as its `default`.
|
|
21815
|
+
*/
|
|
21816
|
+
const EDGE_RULE = {
|
|
21817
|
+
type: "enum",
|
|
21818
|
+
values: GROUP_RULE_NAMES,
|
|
21819
|
+
custom: "function",
|
|
21820
|
+
describe: "The grouping rule."
|
|
21821
|
+
};
|
|
21822
|
+
/** The declaration of the lane order of the shared ways of the routers `trunks` and `shared`. */
|
|
21823
|
+
const LANE_ORDER = {
|
|
21824
|
+
type: "enum",
|
|
21825
|
+
values: ["bundle", "run"],
|
|
21826
|
+
default: "bundle",
|
|
21827
|
+
describe: "With `bundle`, two edges keep one lane order on each shared way of their common stretch. With `run`, each shared way gets its order alone."
|
|
21828
|
+
};
|
|
21829
|
+
/** The declaration of the lane order of the runs of the router `sections`. The default keeps the lane order of version 0.2.0. */
|
|
21830
|
+
const RUN_LANE_ORDER = {
|
|
21831
|
+
...LANE_ORDER,
|
|
21832
|
+
default: "run"
|
|
21833
|
+
};
|
|
21834
|
+
/** The declarations of the options of the routers `corners` and `corners-smooth`. */
|
|
19980
21835
|
const CORNERS_SCHEMA = {
|
|
19981
21836
|
...WAY_SCHEMA,
|
|
19982
21837
|
plugs: {
|
|
@@ -20018,6 +21873,20 @@ const CORNERS_SCHEMA = {
|
|
|
20018
21873
|
max: 20,
|
|
20019
21874
|
step: 1,
|
|
20020
21875
|
describe: "The distance from the walls of a frame to the bounds of its inner routes. Without a value, the inset is the margin."
|
|
21876
|
+
},
|
|
21877
|
+
inner: {
|
|
21878
|
+
type: "enum",
|
|
21879
|
+
values: ["corners", "gutter"],
|
|
21880
|
+
default: "corners",
|
|
21881
|
+
describe: "The mode of the inner routes. With `gutter`, each inner route is orthogonal in the gaps between the nodes of the frame, with a lane in each shared gap."
|
|
21882
|
+
},
|
|
21883
|
+
bendCost: {
|
|
21884
|
+
type: "number",
|
|
21885
|
+
min: 0,
|
|
21886
|
+
max: 100,
|
|
21887
|
+
step: 1,
|
|
21888
|
+
default: 16,
|
|
21889
|
+
describe: "The cost of one bend of an inner route of the mode `gutter`, in units of length. A larger cost gives fewer bends."
|
|
20021
21890
|
}
|
|
20022
21891
|
};
|
|
20023
21892
|
/** The declarations of the options of the router `trunks`. */
|
|
@@ -23235,6 +25104,235 @@ const INTERCHANGE_SCHEMA = {
|
|
|
23235
25104
|
describe: "An edge rides the network only when its way is this many times its first route or less."
|
|
23236
25105
|
}
|
|
23237
25106
|
};
|
|
25107
|
+
/** The declarations of the options of the highway strategy `knee` (NAT-018). */
|
|
25108
|
+
const KNEE_SCHEMA = {
|
|
25109
|
+
sweep: {
|
|
25110
|
+
type: "integer",
|
|
25111
|
+
min: 5,
|
|
25112
|
+
max: 21,
|
|
25113
|
+
step: 1,
|
|
25114
|
+
marks: [
|
|
25115
|
+
5,
|
|
25116
|
+
11,
|
|
25117
|
+
21
|
|
25118
|
+
],
|
|
25119
|
+
default: 11,
|
|
25120
|
+
describe: "The number of points of the sweep of the balancing factor."
|
|
25121
|
+
},
|
|
25122
|
+
rule: {
|
|
25123
|
+
type: "enum",
|
|
25124
|
+
values: ["chord", "weights"],
|
|
25125
|
+
default: "chord",
|
|
25126
|
+
describe: "The rule that picks one tree: the knee of the chord, or the least weighted sum."
|
|
25127
|
+
},
|
|
25128
|
+
ink: {
|
|
25129
|
+
type: "number",
|
|
25130
|
+
min: 0,
|
|
25131
|
+
max: 1,
|
|
25132
|
+
step: .05,
|
|
25133
|
+
marks: [
|
|
25134
|
+
.25,
|
|
25135
|
+
.5,
|
|
25136
|
+
.75
|
|
25137
|
+
],
|
|
25138
|
+
default: .5,
|
|
25139
|
+
describe: "The weight of the ink in the rule `weights`. The rest of the weight goes to the path length."
|
|
25140
|
+
},
|
|
25141
|
+
detour: {
|
|
25142
|
+
type: "number",
|
|
25143
|
+
min: 1,
|
|
25144
|
+
max: 3,
|
|
25145
|
+
step: .05,
|
|
25146
|
+
marks: [
|
|
25147
|
+
1.25,
|
|
25148
|
+
1.5,
|
|
25149
|
+
2
|
|
25150
|
+
],
|
|
25151
|
+
default: 1.5,
|
|
25152
|
+
describe: "An edge rides the tree only when its path in the tree is this many times its first route or less."
|
|
25153
|
+
},
|
|
25154
|
+
minSize: {
|
|
25155
|
+
type: "integer",
|
|
25156
|
+
min: 2,
|
|
25157
|
+
max: 16,
|
|
25158
|
+
step: 1,
|
|
25159
|
+
default: 2,
|
|
25160
|
+
describe: "A stretch of a tree with fewer edges than this number is not a highway."
|
|
25161
|
+
}
|
|
25162
|
+
};
|
|
25163
|
+
/** The declarations of the options of the highway strategy `bronchial` (NAT-019). */
|
|
25164
|
+
const BRONCHIAL_SCHEMA = {
|
|
25165
|
+
split: {
|
|
25166
|
+
type: "enum",
|
|
25167
|
+
values: [
|
|
25168
|
+
"count",
|
|
25169
|
+
"weight",
|
|
25170
|
+
"gap"
|
|
25171
|
+
],
|
|
25172
|
+
default: "count",
|
|
25173
|
+
describe: "The rule that splits the targets of a tip in two: by count, by weight, or at the widest gap."
|
|
25174
|
+
},
|
|
25175
|
+
share: {
|
|
25176
|
+
type: "number",
|
|
25177
|
+
min: .3,
|
|
25178
|
+
max: .7,
|
|
25179
|
+
step: .05,
|
|
25180
|
+
marks: [
|
|
25181
|
+
.3,
|
|
25182
|
+
.5,
|
|
25183
|
+
.7
|
|
25184
|
+
],
|
|
25185
|
+
default: .5,
|
|
25186
|
+
describe: "The share of the distance to the center of the targets that a branch grows."
|
|
25187
|
+
},
|
|
25188
|
+
smallest: {
|
|
25189
|
+
type: "integer",
|
|
25190
|
+
min: 1,
|
|
25191
|
+
max: 3,
|
|
25192
|
+
step: 1,
|
|
25193
|
+
default: 1,
|
|
25194
|
+
describe: "A set with this number of targets or fewer stops the splits."
|
|
25195
|
+
},
|
|
25196
|
+
detour: {
|
|
25197
|
+
type: "number",
|
|
25198
|
+
min: 1,
|
|
25199
|
+
max: 3,
|
|
25200
|
+
step: .05,
|
|
25201
|
+
marks: [
|
|
25202
|
+
1.25,
|
|
25203
|
+
1.5,
|
|
25204
|
+
2
|
|
25205
|
+
],
|
|
25206
|
+
default: 1.5,
|
|
25207
|
+
describe: "An edge rides the tree only when its path in the tree is this many times its first route or less."
|
|
25208
|
+
},
|
|
25209
|
+
minSize: {
|
|
25210
|
+
type: "integer",
|
|
25211
|
+
min: 2,
|
|
25212
|
+
max: 16,
|
|
25213
|
+
step: 1,
|
|
25214
|
+
default: 2,
|
|
25215
|
+
describe: "A stretch of a tree with fewer edges than this number is not a highway."
|
|
25216
|
+
}
|
|
25217
|
+
};
|
|
25218
|
+
/** The declarations of the options of the level strategy `venation` (NAT-016): the options of `depths`, and the options of the backbone. */
|
|
25219
|
+
const VENATION_SCHEMA = {
|
|
25220
|
+
...DEPTHS_SCHEMA,
|
|
25221
|
+
backbone: {
|
|
25222
|
+
type: "enum",
|
|
25223
|
+
values: ["rng", "gabriel"],
|
|
25224
|
+
default: "rng",
|
|
25225
|
+
describe: "The graph of the backbone: the relative neighbourhood graph, or the Gabriel graph."
|
|
25226
|
+
},
|
|
25227
|
+
discount: {
|
|
25228
|
+
type: "number",
|
|
25229
|
+
min: 0,
|
|
25230
|
+
max: .8,
|
|
25231
|
+
step: .05,
|
|
25232
|
+
marks: [
|
|
25233
|
+
0,
|
|
25234
|
+
.4,
|
|
25235
|
+
.8
|
|
25236
|
+
],
|
|
25237
|
+
default: .4,
|
|
25238
|
+
describe: "The share of the cost of a backbone link that a later level link saves when an earlier link uses it."
|
|
25239
|
+
},
|
|
25240
|
+
detour: {
|
|
25241
|
+
type: "number",
|
|
25242
|
+
min: 1,
|
|
25243
|
+
max: 3,
|
|
25244
|
+
step: .05,
|
|
25245
|
+
marks: [
|
|
25246
|
+
1.25,
|
|
25247
|
+
1.5,
|
|
25248
|
+
2
|
|
25249
|
+
],
|
|
25250
|
+
default: 1.5,
|
|
25251
|
+
describe: "A level link takes its way on the backbone only when that way is this many times its direct link or less."
|
|
25252
|
+
},
|
|
25253
|
+
maxItems: {
|
|
25254
|
+
type: "integer",
|
|
25255
|
+
min: 10,
|
|
25256
|
+
max: 2e3,
|
|
25257
|
+
step: 10,
|
|
25258
|
+
marks: [
|
|
25259
|
+
100,
|
|
25260
|
+
400,
|
|
25261
|
+
1e3
|
|
25262
|
+
],
|
|
25263
|
+
default: 400,
|
|
25264
|
+
describe: "A level with more items than this number keeps the links of `depths`."
|
|
25265
|
+
}
|
|
25266
|
+
};
|
|
25267
|
+
/** The declarations of the options of the whole strategy `board-portfolio` (WILD-045). */
|
|
25268
|
+
const BOARD_PORTFOLIO_SCHEMA = {
|
|
25269
|
+
settings: {
|
|
25270
|
+
type: "integer",
|
|
25271
|
+
min: 3,
|
|
25272
|
+
max: 12,
|
|
25273
|
+
step: 1,
|
|
25274
|
+
marks: [
|
|
25275
|
+
3,
|
|
25276
|
+
4,
|
|
25277
|
+
8,
|
|
25278
|
+
12
|
|
25279
|
+
],
|
|
25280
|
+
default: 4,
|
|
25281
|
+
describe: "The number of settings that the portfolio tries, from the start of its list."
|
|
25282
|
+
},
|
|
25283
|
+
crossings: {
|
|
25284
|
+
type: "number",
|
|
25285
|
+
min: 0,
|
|
25286
|
+
max: 4,
|
|
25287
|
+
step: .25,
|
|
25288
|
+
marks: [
|
|
25289
|
+
0,
|
|
25290
|
+
1,
|
|
25291
|
+
2
|
|
25292
|
+
],
|
|
25293
|
+
default: 1,
|
|
25294
|
+
describe: "The weight of the crossings (R1) in the score."
|
|
25295
|
+
},
|
|
25296
|
+
detour: {
|
|
25297
|
+
type: "number",
|
|
25298
|
+
min: 0,
|
|
25299
|
+
max: 4,
|
|
25300
|
+
step: .25,
|
|
25301
|
+
marks: [
|
|
25302
|
+
0,
|
|
25303
|
+
1,
|
|
25304
|
+
2
|
|
25305
|
+
],
|
|
25306
|
+
default: 1,
|
|
25307
|
+
describe: "The weight of the median detour (R5) in the score."
|
|
25308
|
+
},
|
|
25309
|
+
ink: {
|
|
25310
|
+
type: "number",
|
|
25311
|
+
min: 0,
|
|
25312
|
+
max: 4,
|
|
25313
|
+
step: .25,
|
|
25314
|
+
marks: [
|
|
25315
|
+
0,
|
|
25316
|
+
1,
|
|
25317
|
+
2
|
|
25318
|
+
],
|
|
25319
|
+
default: 1,
|
|
25320
|
+
describe: "The weight of the ink saved (B1) in the score."
|
|
25321
|
+
},
|
|
25322
|
+
budget: {
|
|
25323
|
+
type: "number",
|
|
25324
|
+
min: 0,
|
|
25325
|
+
max: 10,
|
|
25326
|
+
step: .1,
|
|
25327
|
+
marks: [
|
|
25328
|
+
0,
|
|
25329
|
+
1,
|
|
25330
|
+
10
|
|
25331
|
+
],
|
|
25332
|
+
default: 0,
|
|
25333
|
+
describe: "The time budget in seconds. The value 0 sets no budget."
|
|
25334
|
+
}
|
|
25335
|
+
};
|
|
23238
25336
|
// src/strategies/builtins.ts
|
|
23239
25337
|
/** This function makes a router strategy from a router factory and a preset of its options. Each call makes the router of its options. */
|
|
23240
25338
|
function routerStrategy(name, describe, maxNodes, defaults, make, schema) {
|
|
@@ -23968,6 +26066,24 @@ const highwayStrategies = Object.freeze({
|
|
|
23968
26066
|
maxNodes: 1e4,
|
|
23969
26067
|
run: (input, o) => interchangePlan(input, o)
|
|
23970
26068
|
}),
|
|
26069
|
+
knee: defineStrategy({
|
|
26070
|
+
step: "highway",
|
|
26071
|
+
name: "knee",
|
|
26072
|
+
describe: "The knee of the plant front (NAT-018). A sweep of the balancing factor of NAT-017 gives trees, and the tree at the knee of ink against path length wins.",
|
|
26073
|
+
defaults: { ...KNEE_DEFAULTS },
|
|
26074
|
+
schema: KNEE_SCHEMA,
|
|
26075
|
+
maxNodes: 1e4,
|
|
26076
|
+
run: (input, o) => kneePlan(input, o)
|
|
26077
|
+
}),
|
|
26078
|
+
bronchial: defineStrategy({
|
|
26079
|
+
step: "highway",
|
|
26080
|
+
name: "bronchial",
|
|
26081
|
+
describe: "Bronchial splits that fill the space (NAT-019). The tree grows toward the center of its targets, then splits the targets in two halves, again and again.",
|
|
26082
|
+
defaults: { ...BRONCHIAL_DEFAULTS },
|
|
26083
|
+
schema: BRONCHIAL_SCHEMA,
|
|
26084
|
+
maxNodes: 1e4,
|
|
26085
|
+
run: (input, o) => bronchialPlan(input, o)
|
|
26086
|
+
}),
|
|
23971
26087
|
"corridors+breakeven": gateStrategy(corridors, breakEvenGate),
|
|
23972
26088
|
"corridors+stretch": gateStrategy(corridors, stretchGate),
|
|
23973
26089
|
"corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
|
|
@@ -24014,26 +26130,74 @@ const plugStrategies = Object.freeze({
|
|
|
24014
26130
|
})
|
|
24015
26131
|
});
|
|
24016
26132
|
/** The built-in level rules: the strategies of the step `levels`. The router `sections` makes its zoom levels with its option `level`. */
|
|
24017
|
-
const levelStrategies = Object.freeze({
|
|
24018
|
-
|
|
24019
|
-
|
|
24020
|
-
|
|
24021
|
-
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
26133
|
+
const levelStrategies = Object.freeze({
|
|
26134
|
+
depths: defineStrategy({
|
|
26135
|
+
step: "levels",
|
|
26136
|
+
name: "depths",
|
|
26137
|
+
describe: "One zoom level for each depth of the tree, and a last level with each node alone. A link of a level goes around the other items. It is the default of the router `sections`.",
|
|
26138
|
+
defaults: {},
|
|
26139
|
+
schema: DEPTHS_SCHEMA,
|
|
26140
|
+
run: (input, o) => depthLevels(input, o)
|
|
26141
|
+
}),
|
|
26142
|
+
venation: defineStrategy({
|
|
26143
|
+
step: "levels",
|
|
26144
|
+
name: "venation",
|
|
26145
|
+
describe: "A closed venation backbone (NAT-016). The links of each level with groups take their ways along the relative neighbourhood graph of the items.",
|
|
26146
|
+
defaults: { ...VENATION_DEFAULTS },
|
|
26147
|
+
schema: VENATION_SCHEMA,
|
|
26148
|
+
run: (input, o) => venationLevels(input, o)
|
|
26149
|
+
})
|
|
26150
|
+
});
|
|
26151
|
+
/**
|
|
26152
|
+
* The fixed list of settings of the whole strategy `board-portfolio` (WILD-045), in order. The option `settings` takes the first ones.
|
|
26153
|
+
* The list holds routers and highway strategies with their defaults. It comes from the catalog of the strategies, not from a fit to the screen.
|
|
26154
|
+
*/
|
|
26155
|
+
const BOARD_PORTFOLIO_SETTINGS = Object.freeze([...[
|
|
26156
|
+
"shared",
|
|
26157
|
+
"corners",
|
|
26158
|
+
"trunks",
|
|
26159
|
+
"sections",
|
|
26160
|
+
"sections-tree",
|
|
26161
|
+
"corners-smooth"
|
|
26162
|
+
].map((name) => ({
|
|
26163
|
+
name,
|
|
26164
|
+
pipeline: { router: routerStrategies[name] }
|
|
26165
|
+
})), ...[
|
|
26166
|
+
"corridors",
|
|
26167
|
+
"shared-ways",
|
|
26168
|
+
"road-classes",
|
|
26169
|
+
"steiner",
|
|
26170
|
+
"knee",
|
|
26171
|
+
"bronchial"
|
|
26172
|
+
].map((name) => ({
|
|
26173
|
+
name: `highway:${name}`,
|
|
26174
|
+
pipeline: {
|
|
26175
|
+
router: routerStrategies.corners,
|
|
26176
|
+
highway: highwayStrategies[name]
|
|
26177
|
+
}
|
|
26178
|
+
}))].map((s) => Object.freeze(s)));
|
|
24025
26179
|
/**
|
|
24026
26180
|
* The built-in whole strategies: the strategies of the step `whole`. The strategy `pipeline` runs one strategy of each step, as the demo of the site does.
|
|
24027
26181
|
* Without options, it routes the graph with the router `corners`.
|
|
24028
26182
|
*/
|
|
24029
|
-
const wholeStrategies = Object.freeze({
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
|
|
24033
|
-
|
|
24034
|
-
|
|
24035
|
-
|
|
24036
|
-
|
|
26183
|
+
const wholeStrategies = Object.freeze({
|
|
26184
|
+
pipeline: defineStrategy({
|
|
26185
|
+
step: "whole",
|
|
26186
|
+
name: "pipeline",
|
|
26187
|
+
describe: "One setting of the steps. The room opens the board, then the router or the highway plan routes it with the rules that the router reads.",
|
|
26188
|
+
defaults: { router: routerStrategies.corners },
|
|
26189
|
+
schema: PIPELINE_SCHEMA,
|
|
26190
|
+
run: (graph, o) => runPipeline(graph, o)
|
|
26191
|
+
}),
|
|
26192
|
+
"board-portfolio": defineStrategy({
|
|
26193
|
+
step: "whole",
|
|
26194
|
+
name: "board-portfolio",
|
|
26195
|
+
describe: "A portfolio for each board (WILD-045). The pipeline tries a fixed list of settings, and the least weighted rank of crossings, detour and ink wins.",
|
|
26196
|
+
defaults: { ...BOARD_PORTFOLIO_DEFAULTS },
|
|
26197
|
+
schema: BOARD_PORTFOLIO_SCHEMA,
|
|
26198
|
+
run: (graph, o) => boardPortfolio(graph, BOARD_PORTFOLIO_SETTINGS, o).output
|
|
26199
|
+
})
|
|
26200
|
+
});
|
|
24037
26201
|
/** The built-in strategies of each step, in order. A consumer adds its own: `createRegistry([...BUILT_IN_STRATEGIES, mine])`. */
|
|
24038
26202
|
const BUILT_IN_STRATEGIES = Object.freeze([
|
|
24039
26203
|
...Object.values(groupStrategies),
|
|
@@ -24091,252 +26255,68 @@ function fixPorts(graph, rule, options = {}) {
|
|
|
24091
26255
|
const added = /* @__PURE__ */ new Map();
|
|
24092
26256
|
const taken = /* @__PURE__ */ new Map();
|
|
24093
26257
|
const portsOf = (node) => {
|
|
24094
|
-
let m = fixed.get(node.id);
|
|
24095
|
-
if (!m) fixed.set(node.id, m = /* @__PURE__ */ new Map());
|
|
24096
|
-
return m;
|
|
24097
|
-
};
|
|
24098
|
-
const fix = (node, plug, base) => ({
|
|
24099
|
-
...base,
|
|
24100
|
-
side: sideOf(plug, node),
|
|
24101
|
-
at: {
|
|
24102
|
-
x: plug.point.x - node.box.x,
|
|
24103
|
-
y: plug.point.y - node.box.y
|
|
24104
|
-
}
|
|
24105
|
-
});
|
|
24106
|
-
const outerEdges = new Set(outer.edges);
|
|
24107
|
-
const edges = graph.edges.map((e) => {
|
|
24108
|
-
if (!outerEdges.has(e)) return e;
|
|
24109
|
-
const pair = byEdge.get(e.id)?.shift();
|
|
24110
|
-
if (!pair) return e;
|
|
24111
|
-
const end = (which, plug) => {
|
|
24112
|
-
const own = e[which];
|
|
24113
|
-
const node = byId.get(own.node);
|
|
24114
|
-
if (!node) return own;
|
|
24115
|
-
if (own.port === void 0) {
|
|
24116
|
-
if (!options.newPorts) return own;
|
|
24117
|
-
let ids = taken.get(node.id);
|
|
24118
|
-
if (!ids) taken.set(node.id, ids = new Set((node.ports ?? []).map((p) => p.id)));
|
|
24119
|
-
const id = freeId(ids, `${e.id}:${which}`);
|
|
24120
|
-
const list = added.get(node.id) ?? [];
|
|
24121
|
-
list.push(fix(node, plug, { id }));
|
|
24122
|
-
added.set(node.id, list);
|
|
24123
|
-
return {
|
|
24124
|
-
...own,
|
|
24125
|
-
port: id
|
|
24126
|
-
};
|
|
24127
|
-
}
|
|
24128
|
-
const port = node.ports?.find((p) => p.id === own.port);
|
|
24129
|
-
if (!port || portOffset(node.box, port) !== void 0) return own;
|
|
24130
|
-
const m = portsOf(node);
|
|
24131
|
-
if (!m.has(port.id)) m.set(port.id, fix(node, plug, port));
|
|
24132
|
-
return own;
|
|
24133
|
-
};
|
|
24134
|
-
const source = end("source", pair.source);
|
|
24135
|
-
const target = end("target", pair.target);
|
|
24136
|
-
return source === e.source && target === e.target ? e : {
|
|
24137
|
-
...e,
|
|
24138
|
-
source,
|
|
24139
|
-
target
|
|
24140
|
-
};
|
|
24141
|
-
});
|
|
24142
|
-
const nodes = graph.nodes.map((n) => {
|
|
24143
|
-
const m = fixed.get(n.id);
|
|
24144
|
-
const more = added.get(n.id);
|
|
24145
|
-
if (!m && !more) return n;
|
|
24146
|
-
const ports = (n.ports ?? []).map((p) => m?.get(p.id) ?? p);
|
|
24147
|
-
return {
|
|
24148
|
-
...n,
|
|
24149
|
-
ports: more ? [...ports, ...more] : ports
|
|
24150
|
-
};
|
|
24151
|
-
});
|
|
24152
|
-
return {
|
|
24153
|
-
...graph,
|
|
24154
|
-
nodes,
|
|
24155
|
-
edges
|
|
24156
|
-
};
|
|
24157
|
-
}
|
|
24158
|
-
// src/bench/quality.ts
|
|
24159
|
-
/** The clearance stops at this distance. */
|
|
24160
|
-
const CLEARANCE_CAP = 40;
|
|
24161
|
-
const TIGHT = 4;
|
|
24162
|
-
const EXACT_CROSSINGS = 2e4;
|
|
24163
|
-
const SAMPLE = 2e3;
|
|
24164
|
-
const key = (p) => `${Math.round(p.x * 8)},${Math.round(p.y * 8)}`;
|
|
24165
|
-
/** This function measures the quality of a result. */
|
|
24166
|
-
function measureQuality(graph, result) {
|
|
24167
|
-
const byId = nodeIndex(graph);
|
|
24168
|
-
const nodes = new Grid(180);
|
|
24169
|
-
for (const n of graph.nodes) nodes.add(n, n.box);
|
|
24170
|
-
const segs = [];
|
|
24171
|
-
const seen = /* @__PURE__ */ new Set();
|
|
24172
|
-
let bends = 0;
|
|
24173
|
-
let infl = 0;
|
|
24174
|
-
let clear = 0;
|
|
24175
|
-
let tight = 0;
|
|
24176
|
-
let detour = 0;
|
|
24177
|
-
let detourN = 0;
|
|
24178
|
-
let angle = 0;
|
|
24179
|
-
let angleN = 0;
|
|
24180
|
-
for (const r of result.routes) {
|
|
24181
|
-
const p = r.points;
|
|
24182
|
-
for (let i = 1; i < p.length; i++) {
|
|
24183
|
-
const ka = key(p[i - 1]);
|
|
24184
|
-
const kb = key(p[i]);
|
|
24185
|
-
if (ka === kb) continue;
|
|
24186
|
-
const k = ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
|
|
24187
|
-
if (seen.has(k)) continue;
|
|
24188
|
-
seen.add(k);
|
|
24189
|
-
segs.push([p[i - 1], p[i]]);
|
|
24190
|
-
}
|
|
24191
|
-
bends += bendCount(p);
|
|
24192
|
-
infl += inflectionCount(p);
|
|
24193
|
-
const ends = endNodes(graph, byId, r.edge);
|
|
24194
|
-
let c = 40;
|
|
24195
|
-
for (let i = 1; i < p.length; i++) {
|
|
24196
|
-
const a = p[i - 1];
|
|
24197
|
-
const b = p[i];
|
|
24198
|
-
nodes.querySegment(a, b, c, (n) => {
|
|
24199
|
-
if (n !== ends.source && n !== ends.target) c = Math.min(c, segmentBoxDistance(a, b, n.box));
|
|
24200
|
-
});
|
|
24201
|
-
}
|
|
24202
|
-
clear += c;
|
|
24203
|
-
if (c < TIGHT) tight++;
|
|
24204
|
-
if (p.length >= 2) {
|
|
24205
|
-
const d = dist(p[0], p[p.length - 1]);
|
|
24206
|
-
if (d > 1) {
|
|
24207
|
-
detour += polylineLength(p) / d;
|
|
24208
|
-
detourN++;
|
|
24209
|
-
}
|
|
24210
|
-
for (const [at, next, node] of [[
|
|
24211
|
-
p[0],
|
|
24212
|
-
p[1],
|
|
24213
|
-
ends.source
|
|
24214
|
-
], [
|
|
24215
|
-
p[p.length - 1],
|
|
24216
|
-
p[p.length - 2],
|
|
24217
|
-
ends.target
|
|
24218
|
-
]]) {
|
|
24219
|
-
if (!node) continue;
|
|
24220
|
-
const a = sideAngle(node.box, at, next);
|
|
24221
|
-
if (a !== void 0) {
|
|
24222
|
-
angle += a;
|
|
24223
|
-
angleN++;
|
|
24224
|
-
}
|
|
24225
|
-
}
|
|
24226
|
-
}
|
|
24227
|
-
}
|
|
24228
|
-
const n = Math.max(1, result.routes.length);
|
|
24229
|
-
let ink = 0;
|
|
24230
|
-
for (const [a, b] of segs) ink += dist(a, b);
|
|
24231
|
-
const [crossings, crossingsEstimated] = countCrossings(segs);
|
|
24232
|
-
return {
|
|
24233
|
-
ink,
|
|
24234
|
-
crossings,
|
|
24235
|
-
crossingsEstimated,
|
|
24236
|
-
bends: bends / n,
|
|
24237
|
-
inflections: infl / n,
|
|
24238
|
-
clearance: clear / n,
|
|
24239
|
-
tight: tight / n,
|
|
24240
|
-
detour: detourN ? detour / detourN : 1,
|
|
24241
|
-
plugAngle: angleN ? angle / angleN : 0
|
|
24242
|
-
};
|
|
24243
|
-
}
|
|
24244
|
-
function endNodes(graph, byId, edgeId) {
|
|
24245
|
-
const e = edgeIndex(graph).get(edgeId);
|
|
24246
|
-
return {
|
|
24247
|
-
source: e && byId.get(e.source.node),
|
|
24248
|
-
target: e && byId.get(e.target.node)
|
|
24249
|
-
};
|
|
24250
|
-
}
|
|
24251
|
-
const edgeIndexes = /* @__PURE__ */ new WeakMap();
|
|
24252
|
-
function edgeIndex(graph) {
|
|
24253
|
-
let m = edgeIndexes.get(graph);
|
|
24254
|
-
if (!m) {
|
|
24255
|
-
m = /* @__PURE__ */ new Map();
|
|
24256
|
-
for (const e of graph.edges) m.set(e.id, e);
|
|
24257
|
-
edgeIndexes.set(graph, m);
|
|
24258
|
-
}
|
|
24259
|
-
return m;
|
|
24260
|
-
}
|
|
24261
|
-
/**
|
|
24262
|
-
* This function gives an angle in degrees: the angle between the segment from `at` to `next` and the outward normal.
|
|
24263
|
-
* The normal is the normal of the side of the box nearest to `at`. A route that starts inside the box (an interior route) gives no angle.
|
|
24264
|
-
*/
|
|
24265
|
-
function sideAngle(box, at, next) {
|
|
24266
|
-
const d = [
|
|
24267
|
-
[
|
|
24268
|
-
Math.abs(at.y - box.y),
|
|
24269
|
-
0,
|
|
24270
|
-
-1
|
|
24271
|
-
],
|
|
24272
|
-
[
|
|
24273
|
-
Math.abs(at.x - (box.x + box.w)),
|
|
24274
|
-
1,
|
|
24275
|
-
0
|
|
24276
|
-
],
|
|
24277
|
-
[
|
|
24278
|
-
Math.abs(at.y - (box.y + box.h)),
|
|
24279
|
-
0,
|
|
24280
|
-
1
|
|
24281
|
-
],
|
|
24282
|
-
[
|
|
24283
|
-
Math.abs(at.x - box.x),
|
|
24284
|
-
-1,
|
|
24285
|
-
0
|
|
24286
|
-
]
|
|
24287
|
-
].sort((a, b) => a[0] - b[0])[0];
|
|
24288
|
-
if (d[0] > 1) return void 0;
|
|
24289
|
-
const vx = next.x - at.x;
|
|
24290
|
-
const vy = next.y - at.y;
|
|
24291
|
-
const l = Math.hypot(vx, vy);
|
|
24292
|
-
if (l === 0) return void 0;
|
|
24293
|
-
const cos = Math.max(-1, Math.min(1, (vx * d[1] + vy * d[2]) / l));
|
|
24294
|
-
return Math.acos(cos) * 180 / Math.PI;
|
|
24295
|
-
}
|
|
24296
|
-
/**
|
|
24297
|
-
* This function counts the crossings of a set of segments with a grid of buckets.
|
|
24298
|
-
* Above 20 000 segments, it counts the crossings of a seeded sample of 2 000 segments and scales the count.
|
|
24299
|
-
*/
|
|
24300
|
-
function countCrossings(segs) {
|
|
24301
|
-
if (segs.length < 2) return [0, false];
|
|
24302
|
-
const len = segs.reduce((s, [a, b]) => s + dist(a, b), 0) / segs.length;
|
|
24303
|
-
const grid = new Grid(Math.max(20, len));
|
|
24304
|
-
segs.forEach(([a, b], i) => grid.addSegment(i, a, b));
|
|
24305
|
-
const exact = segs.length <= EXACT_CROSSINGS;
|
|
24306
|
-
const R = rng(segs.length);
|
|
24307
|
-
const pick = exact ? segs.map((_, i) => i) : Array.from({ length: SAMPLE }, () => Math.floor(R() * segs.length));
|
|
24308
|
-
let count = 0;
|
|
24309
|
-
for (const i of pick) {
|
|
24310
|
-
const [a, b] = segs[i];
|
|
24311
|
-
grid.querySegment(a, b, 0, (j) => {
|
|
24312
|
-
if (exact && j <= i) return;
|
|
24313
|
-
if (j === i) return;
|
|
24314
|
-
const [c, d] = segs[j];
|
|
24315
|
-
if (segmentsCross(a, b, c, d)) count++;
|
|
24316
|
-
});
|
|
24317
|
-
}
|
|
24318
|
-
return exact ? [count, false] : [Math.round(count * segs.length / (2 * pick.length)), true];
|
|
24319
|
-
}
|
|
24320
|
-
/** This function measures how much the routes changed between two results. */
|
|
24321
|
-
function measureStability(before, after) {
|
|
24322
|
-
const prev = new Map(before.routes.map((r) => [r.edge, r.points]));
|
|
24323
|
-
let sum = 0;
|
|
24324
|
-
let max = 0;
|
|
24325
|
-
let moved = 0;
|
|
24326
|
-
let n = 0;
|
|
24327
|
-
for (const r of after.routes) {
|
|
24328
|
-
const p = prev.get(r.edge);
|
|
24329
|
-
if (!p) continue;
|
|
24330
|
-
const f = frechet(p, r.points);
|
|
24331
|
-
sum += f;
|
|
24332
|
-
max = Math.max(max, f);
|
|
24333
|
-
if (f > 1) moved++;
|
|
24334
|
-
n++;
|
|
24335
|
-
}
|
|
26258
|
+
let m = fixed.get(node.id);
|
|
26259
|
+
if (!m) fixed.set(node.id, m = /* @__PURE__ */ new Map());
|
|
26260
|
+
return m;
|
|
26261
|
+
};
|
|
26262
|
+
const fix = (node, plug, base) => ({
|
|
26263
|
+
...base,
|
|
26264
|
+
side: sideOf(plug, node),
|
|
26265
|
+
at: {
|
|
26266
|
+
x: plug.point.x - node.box.x,
|
|
26267
|
+
y: plug.point.y - node.box.y
|
|
26268
|
+
}
|
|
26269
|
+
});
|
|
26270
|
+
const outerEdges = new Set(outer.edges);
|
|
26271
|
+
const edges = graph.edges.map((e) => {
|
|
26272
|
+
if (!outerEdges.has(e)) return e;
|
|
26273
|
+
const pair = byEdge.get(e.id)?.shift();
|
|
26274
|
+
if (!pair) return e;
|
|
26275
|
+
const end = (which, plug) => {
|
|
26276
|
+
const own = e[which];
|
|
26277
|
+
const node = byId.get(own.node);
|
|
26278
|
+
if (!node) return own;
|
|
26279
|
+
if (own.port === void 0) {
|
|
26280
|
+
if (!options.newPorts) return own;
|
|
26281
|
+
let ids = taken.get(node.id);
|
|
26282
|
+
if (!ids) taken.set(node.id, ids = new Set((node.ports ?? []).map((p) => p.id)));
|
|
26283
|
+
const id = freeId(ids, `${e.id}:${which}`);
|
|
26284
|
+
const list = added.get(node.id) ?? [];
|
|
26285
|
+
list.push(fix(node, plug, { id }));
|
|
26286
|
+
added.set(node.id, list);
|
|
26287
|
+
return {
|
|
26288
|
+
...own,
|
|
26289
|
+
port: id
|
|
26290
|
+
};
|
|
26291
|
+
}
|
|
26292
|
+
const port = node.ports?.find((p) => p.id === own.port);
|
|
26293
|
+
if (!port || portOffset(node.box, port) !== void 0) return own;
|
|
26294
|
+
const m = portsOf(node);
|
|
26295
|
+
if (!m.has(port.id)) m.set(port.id, fix(node, plug, port));
|
|
26296
|
+
return own;
|
|
26297
|
+
};
|
|
26298
|
+
const source = end("source", pair.source);
|
|
26299
|
+
const target = end("target", pair.target);
|
|
26300
|
+
return source === e.source && target === e.target ? e : {
|
|
26301
|
+
...e,
|
|
26302
|
+
source,
|
|
26303
|
+
target
|
|
26304
|
+
};
|
|
26305
|
+
});
|
|
26306
|
+
const nodes = graph.nodes.map((n) => {
|
|
26307
|
+
const m = fixed.get(n.id);
|
|
26308
|
+
const more = added.get(n.id);
|
|
26309
|
+
if (!m && !more) return n;
|
|
26310
|
+
const ports = (n.ports ?? []).map((p) => m?.get(p.id) ?? p);
|
|
26311
|
+
return {
|
|
26312
|
+
...n,
|
|
26313
|
+
ports: more ? [...ports, ...more] : ports
|
|
26314
|
+
};
|
|
26315
|
+
});
|
|
24336
26316
|
return {
|
|
24337
|
-
|
|
24338
|
-
|
|
24339
|
-
|
|
26317
|
+
...graph,
|
|
26318
|
+
nodes,
|
|
26319
|
+
edges
|
|
24340
26320
|
};
|
|
24341
26321
|
}
|
|
24342
26322
|
// src/bench/run.ts
|
|
@@ -29659,10 +31639,12 @@ const count = (name, min, max, value, meaning) => numberDimension({
|
|
|
29659
31639
|
default: value,
|
|
29660
31640
|
meaning
|
|
29661
31641
|
});
|
|
29662
|
-
/** This function gives the numbers of the full dimension values, by name. */
|
|
31642
|
+
/** This function gives the numbers of the full dimension values, by name. A dimension that is not `int` or `real` is not in them. */
|
|
29663
31643
|
function numbers(dimensions, dims) {
|
|
29664
|
-
return Object.fromEntries(dimensions.map((d) => [d.name, numberOf(dims, d.name)]));
|
|
31644
|
+
return Object.fromEntries(dimensions.filter((d) => d.kind === "int" || d.kind === "real").map((d) => [d.name, numberOf(dims, d.name)]));
|
|
29665
31645
|
}
|
|
31646
|
+
/** The default rule of the random numbers of a case family. */
|
|
31647
|
+
const CASE_SEEDING = "mulberry32 of the input seed. The fixed part of the board takes no number. Then one stream gives the random links.";
|
|
29666
31648
|
/** This function gives a case family. Its stream is mulberry32 of the input seed, and it uses only the stream, `Math.floor` and `Math.ceil`. */
|
|
29667
31649
|
function caseFamily(fields) {
|
|
29668
31650
|
const { name, meaning, dimensions } = fields;
|
|
@@ -29673,11 +31655,11 @@ function caseFamily(fields) {
|
|
|
29673
31655
|
owner: "highways",
|
|
29674
31656
|
meaning,
|
|
29675
31657
|
dimensions,
|
|
29676
|
-
seeding:
|
|
31658
|
+
seeding: fields.seeding ?? CASE_SEEDING,
|
|
29677
31659
|
crossEngine: "exact",
|
|
29678
31660
|
generate(seed, dims, context) {
|
|
29679
31661
|
return {
|
|
29680
|
-
graph: fields.make(numbers(dimensions, dims), seed, context.random),
|
|
31662
|
+
graph: fields.make(numbers(dimensions, dims), seed, context.random, dims),
|
|
29681
31663
|
generator: {
|
|
29682
31664
|
name: fields.generator,
|
|
29683
31665
|
version: 1,
|
|
@@ -29759,6 +31741,508 @@ const PACKED_GRID = caseFamily({
|
|
|
29759
31741
|
reach: v["reach"]
|
|
29760
31742
|
}, random)
|
|
29761
31743
|
});
|
|
31744
|
+
// src/families/coverage.ts
|
|
31745
|
+
/** A board while it grows. It adds no loop, and no second edge between the same two ends. */
|
|
31746
|
+
var Builder = class {
|
|
31747
|
+
nodes = [];
|
|
31748
|
+
edges = [];
|
|
31749
|
+
#keys = /* @__PURE__ */ new Set();
|
|
31750
|
+
node(id, x, y, w, h, extra = {}) {
|
|
31751
|
+
this.nodes.push({
|
|
31752
|
+
id,
|
|
31753
|
+
box: {
|
|
31754
|
+
x,
|
|
31755
|
+
y,
|
|
31756
|
+
w,
|
|
31757
|
+
h
|
|
31758
|
+
},
|
|
31759
|
+
...extra.parent !== void 0 ? { parent: extra.parent } : {},
|
|
31760
|
+
...extra.ports ? { ports: extra.ports } : {}
|
|
31761
|
+
});
|
|
31762
|
+
return id;
|
|
31763
|
+
}
|
|
31764
|
+
/** This method adds an edge. The key of an end is its node and its plug. It adds nothing for a loop or a second edge between the same two ends. */
|
|
31765
|
+
link(source, target) {
|
|
31766
|
+
if (source.node === target.node) return;
|
|
31767
|
+
const a = `${source.node}:${source.port ?? ""}`;
|
|
31768
|
+
const b = `${target.node}:${target.port ?? ""}`;
|
|
31769
|
+
const key = a < b ? `${a}|${b}` : `${b}|${a}`;
|
|
31770
|
+
if (this.#keys.has(key)) return;
|
|
31771
|
+
this.#keys.add(key);
|
|
31772
|
+
this.edges.push({
|
|
31773
|
+
id: `e${this.edges.length}`,
|
|
31774
|
+
source,
|
|
31775
|
+
target
|
|
31776
|
+
});
|
|
31777
|
+
}
|
|
31778
|
+
graph() {
|
|
31779
|
+
return {
|
|
31780
|
+
nodes: this.nodes,
|
|
31781
|
+
edges: this.edges
|
|
31782
|
+
};
|
|
31783
|
+
}
|
|
31784
|
+
};
|
|
31785
|
+
/** This function gives a whole number from `lo` to `hi`, both included. */
|
|
31786
|
+
const int = (random, lo, hi) => lo + Math.floor(random() * (hi - lo + 1));
|
|
31787
|
+
/** This function gives the least whole number `s` with `s × s` equal to `v` or more. It uses no square root, so each engine gives the same value. */
|
|
31788
|
+
function ceilSqrt(v) {
|
|
31789
|
+
let lo = 0;
|
|
31790
|
+
let hi = Math.max(1, v);
|
|
31791
|
+
while (lo < hi) {
|
|
31792
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
31793
|
+
if (mid * mid >= v) hi = mid;
|
|
31794
|
+
else lo = mid + 1;
|
|
31795
|
+
}
|
|
31796
|
+
return lo;
|
|
31797
|
+
}
|
|
31798
|
+
/** This function gives `k` of the indices 0 to `n - 1` at random, in increasing order. It uses a part of the shuffle of Fisher and Yates. */
|
|
31799
|
+
function choose(random, n, k) {
|
|
31800
|
+
const list = Array.from({ length: n }, (_, i) => i);
|
|
31801
|
+
const take = Math.min(k, n);
|
|
31802
|
+
for (let i = 0; i < take; i++) {
|
|
31803
|
+
const j = int(random, i, n - 1);
|
|
31804
|
+
[list[i], list[j]] = [list[j], list[i]];
|
|
31805
|
+
}
|
|
31806
|
+
return list.slice(0, take).sort((a, b) => a - b);
|
|
31807
|
+
}
|
|
31808
|
+
/** This function gives the indices 0 to `n - 1` in a random order, with the shuffle of Fisher and Yates. */
|
|
31809
|
+
function shuffle(random, n) {
|
|
31810
|
+
const list = Array.from({ length: n }, (_, i) => i);
|
|
31811
|
+
for (let i = n - 1; i > 0; i--) {
|
|
31812
|
+
const j = int(random, 0, i);
|
|
31813
|
+
[list[i], list[j]] = [list[j], list[i]];
|
|
31814
|
+
}
|
|
31815
|
+
return list;
|
|
31816
|
+
}
|
|
31817
|
+
/** The rule of the random numbers of each family of this file. */
|
|
31818
|
+
const SEEDING = "mulberry32 of the input seed. One stream gives the sizes and the places of the boxes, then the edges, in a fixed order.";
|
|
31819
|
+
/**
|
|
31820
|
+
* The family `frame-rows`: cards with full-width rows, as INTERIOR-ROUTE takes them. Each row has a fixed plug on the east wall and on the west wall of its card.
|
|
31821
|
+
* The links join a plug of a card to a plug of another card. An inner edge joins a row to a plug of its own card. The default has no inner edge.
|
|
31822
|
+
*/
|
|
31823
|
+
const FRAME_ROWS = caseFamily({
|
|
31824
|
+
name: "frame-rows",
|
|
31825
|
+
meaning: "Cards with full-width rows and fixed plugs on their walls, as tables of a schema diagram.",
|
|
31826
|
+
dimensions: Object.freeze([
|
|
31827
|
+
count("cards", 1, 24, 9, "The number of cards. Each card is a frame."),
|
|
31828
|
+
count("rows", 1, 12, 5, "The number of full-width rows in each card."),
|
|
31829
|
+
count("links", 0, 8, 4, "The number of tries for a link from a wall plug of a card to a wall plug of another card, for each card."),
|
|
31830
|
+
count("inner", 0, 12, 0, "The number of tries for an inner edge from a row to a wall plug of its card, for each card."),
|
|
31831
|
+
count("gap", 24, 240, 96, "The free space between two cards, in units.")
|
|
31832
|
+
]),
|
|
31833
|
+
generator: "family.frame-rows",
|
|
31834
|
+
seeding: SEEDING,
|
|
31835
|
+
make: (v, _seed, random) => {
|
|
31836
|
+
const b = new Builder();
|
|
31837
|
+
const cards = v["cards"];
|
|
31838
|
+
const rows = v["rows"];
|
|
31839
|
+
const gap = v["gap"];
|
|
31840
|
+
const cols = ceilSqrt(cards);
|
|
31841
|
+
const w = 220;
|
|
31842
|
+
const header = 28;
|
|
31843
|
+
const pitch = 30;
|
|
31844
|
+
const h = header + rows * pitch + 4;
|
|
31845
|
+
const ports = [];
|
|
31846
|
+
for (let j = 0; j < rows; j++) {
|
|
31847
|
+
const along = (header + j * pitch + 12) / h;
|
|
31848
|
+
ports.push({
|
|
31849
|
+
id: `e${j}`,
|
|
31850
|
+
side: "east",
|
|
31851
|
+
along
|
|
31852
|
+
}, {
|
|
31853
|
+
id: `w${j}`,
|
|
31854
|
+
side: "west",
|
|
31855
|
+
along
|
|
31856
|
+
});
|
|
31857
|
+
}
|
|
31858
|
+
const colOf = (i) => i % cols;
|
|
31859
|
+
for (let i = 0; i < cards; i++) {
|
|
31860
|
+
const x = colOf(i) * (w + gap);
|
|
31861
|
+
const y = Math.floor(i / cols) * (h + gap);
|
|
31862
|
+
const card = b.node(`f${i}`, x, y, w, h, { ports });
|
|
31863
|
+
for (let j = 0; j < rows; j++) b.node(`f${i}r${j}`, x + 10, y + header + j * pitch, 200, 24, { parent: card });
|
|
31864
|
+
}
|
|
31865
|
+
for (let n = 0; n < cards * v["links"]; n++) {
|
|
31866
|
+
const a = int(random, 0, cards - 1);
|
|
31867
|
+
const c = int(random, 0, cards - 1);
|
|
31868
|
+
const ra = int(random, 0, rows - 1);
|
|
31869
|
+
const rc = int(random, 0, rows - 1);
|
|
31870
|
+
const east = colOf(a) <= colOf(c);
|
|
31871
|
+
b.link({
|
|
31872
|
+
node: `f${a}`,
|
|
31873
|
+
port: `${east ? "e" : "w"}${ra}`
|
|
31874
|
+
}, {
|
|
31875
|
+
node: `f${c}`,
|
|
31876
|
+
port: `${east ? "w" : "e"}${rc}`
|
|
31877
|
+
});
|
|
31878
|
+
}
|
|
31879
|
+
for (let i = 0; i < cards; i++) for (let t = 0; t < v["inner"]; t++) {
|
|
31880
|
+
const row = int(random, 0, rows - 1);
|
|
31881
|
+
const side = random() < .5 ? "e" : "w";
|
|
31882
|
+
b.link({ node: `f${i}r${row}` }, {
|
|
31883
|
+
node: `f${i}`,
|
|
31884
|
+
port: `${side}${int(random, 0, rows - 1)}`
|
|
31885
|
+
});
|
|
31886
|
+
}
|
|
31887
|
+
return b.graph();
|
|
31888
|
+
}
|
|
31889
|
+
});
|
|
31890
|
+
/** The family `pipeline`: a long chain of stages in rows that turn at each end, with some links that skip stages. */
|
|
31891
|
+
const PIPELINE = caseFamily({
|
|
31892
|
+
name: "pipeline",
|
|
31893
|
+
meaning: "A long chain of stages in rows that turn at each end, with a few links that skip stages.",
|
|
31894
|
+
dimensions: Object.freeze([
|
|
31895
|
+
count("stages", 4, 240, 72, "The number of stages of the chain."),
|
|
31896
|
+
count("perRow", 2, 40, 12, "The number of stages in each row. The chain turns at the end of each row."),
|
|
31897
|
+
count("skips", 0, 80, 8, "The number of tries for a link that skips 1 to 3 stages."),
|
|
31898
|
+
count("gap", 16, 160, 56, "The free space between two stages, in units.")
|
|
31899
|
+
]),
|
|
31900
|
+
generator: "family.pipeline",
|
|
31901
|
+
seeding: SEEDING,
|
|
31902
|
+
make: (v, _seed, random) => {
|
|
31903
|
+
const b = new Builder();
|
|
31904
|
+
const stages = v["stages"];
|
|
31905
|
+
const perRow = v["perRow"];
|
|
31906
|
+
const gap = v["gap"];
|
|
31907
|
+
const cell = 120;
|
|
31908
|
+
for (let i = 0; i < stages; i++) {
|
|
31909
|
+
const r = Math.floor(i / perRow);
|
|
31910
|
+
const p = i % perRow;
|
|
31911
|
+
const col = r % 2 === 0 ? p : perRow - 1 - p;
|
|
31912
|
+
const w = int(random, 72, cell);
|
|
31913
|
+
b.node(`s${i}`, col * (cell + gap) + Math.floor((cell - w) / 2), r * (40 + gap), w, 40);
|
|
31914
|
+
}
|
|
31915
|
+
for (let i = 0; i + 1 < stages; i++) b.link({ node: `s${i}` }, { node: `s${i + 1}` });
|
|
31916
|
+
for (let n = 0; n < v["skips"]; n++) {
|
|
31917
|
+
const s = int(random, 0, stages - 1);
|
|
31918
|
+
const d = int(random, 2, 4);
|
|
31919
|
+
if (s + d < stages) b.link({ node: `s${s}` }, { node: `s${s + d}` });
|
|
31920
|
+
}
|
|
31921
|
+
return b.graph();
|
|
31922
|
+
}
|
|
31923
|
+
});
|
|
31924
|
+
/** This function gives the cells of the square ring `r` around the cell (0, 0), in a fixed order. */
|
|
31925
|
+
function ring(r) {
|
|
31926
|
+
const out = [];
|
|
31927
|
+
for (let dy = -r; dy <= r; dy++) for (let dx = -r; dx <= r; dx++) if (Math.max(Math.abs(dx), Math.abs(dy)) === r) out.push([dx, dy]);
|
|
31928
|
+
return out;
|
|
31929
|
+
}
|
|
31930
|
+
/** The family `fan-out`: one large hub in the middle, with a link to each leaf on the square rings around it. */
|
|
31931
|
+
const FAN_OUT = caseFamily({
|
|
31932
|
+
name: "fan-out",
|
|
31933
|
+
meaning: "A wide fan-out: one hub in the middle links to each leaf on the rings around it.",
|
|
31934
|
+
dimensions: Object.freeze([
|
|
31935
|
+
count("leaves", 4, 240, 96, "The number of leaves. The hub links to each leaf."),
|
|
31936
|
+
count("cross", 0, 120, 8, "The number of tries for a link between two leaves."),
|
|
31937
|
+
count("gap", 8, 120, 36, "The free space between two leaves, in units.")
|
|
31938
|
+
]),
|
|
31939
|
+
generator: "family.fan-out",
|
|
31940
|
+
seeding: SEEDING,
|
|
31941
|
+
make: (v, _seed, random) => {
|
|
31942
|
+
const b = new Builder();
|
|
31943
|
+
const leaves = v["leaves"];
|
|
31944
|
+
const gap = v["gap"];
|
|
31945
|
+
const cw = 80 + gap;
|
|
31946
|
+
const ch = 32 + gap;
|
|
31947
|
+
const cells = [];
|
|
31948
|
+
let r = 2;
|
|
31949
|
+
while (cells.length < leaves + Math.floor(leaves / 4)) cells.push(...ring(r++));
|
|
31950
|
+
const reach = r - 1;
|
|
31951
|
+
const at = (dx, dy) => [(dx + reach) * cw, (dy + reach) * ch];
|
|
31952
|
+
const [hx, hy] = at(-1, -1);
|
|
31953
|
+
const hub = b.node("hub", hx, hy, 2 * cw + 80, 2 * ch + 32);
|
|
31954
|
+
const ids = [];
|
|
31955
|
+
for (const i of choose(random, cells.length, leaves)) {
|
|
31956
|
+
const [x, y] = at(...cells[i]);
|
|
31957
|
+
const w = int(random, 48, 80);
|
|
31958
|
+
const h = int(random, 20, 32);
|
|
31959
|
+
ids.push(b.node(`l${ids.length}`, x, y, w, h));
|
|
31960
|
+
}
|
|
31961
|
+
for (const id of ids) b.link({ node: hub }, { node: id });
|
|
31962
|
+
for (let n = 0; n < v["cross"]; n++) b.link({ node: ids[int(random, 0, ids.length - 1)] }, { node: ids[int(random, 0, ids.length - 1)] });
|
|
31963
|
+
return b.graph();
|
|
31964
|
+
}
|
|
31965
|
+
});
|
|
31966
|
+
/** The family `two-hubs`: two tall hubs with many nodes between them. Each such node has a link from each hub. A few nodes of one hub are on its far side. */
|
|
31967
|
+
const TWO_HUBS = caseFamily({
|
|
31968
|
+
name: "two-hubs",
|
|
31969
|
+
meaning: "Two hubs that share many targets between them: two fans that overlap.",
|
|
31970
|
+
dimensions: Object.freeze([
|
|
31971
|
+
count("shared", 0, 160, 48, "The number of nodes between the hubs. Each of them has a link from each hub."),
|
|
31972
|
+
count("own", 0, 80, 12, "The number of nodes on the far side of each hub, with a link from that hub only."),
|
|
31973
|
+
count("span", 1, 12, 5, "The number of columns of nodes between the two hubs."),
|
|
31974
|
+
count("gap", 8, 120, 36, "The free space between two nodes, in units.")
|
|
31975
|
+
]),
|
|
31976
|
+
generator: "family.two-hubs",
|
|
31977
|
+
seeding: SEEDING,
|
|
31978
|
+
make: (v, _seed, random) => {
|
|
31979
|
+
const b = new Builder();
|
|
31980
|
+
const shared = v["shared"];
|
|
31981
|
+
const own = v["own"];
|
|
31982
|
+
const span = v["span"];
|
|
31983
|
+
const gap = v["gap"];
|
|
31984
|
+
const cw = 64 + gap;
|
|
31985
|
+
const ch = 28 + gap;
|
|
31986
|
+
const ceilDiv = (a, d) => Math.floor((a + d - 1) / d);
|
|
31987
|
+
const rows = Math.max(1, ceilDiv(shared + Math.floor(shared / 4), span), ceilDiv(own + Math.floor(own / 4), 2));
|
|
31988
|
+
const hubH = rows * ch - gap;
|
|
31989
|
+
const a = b.node("A", 2 * cw, 0, 64, hubH);
|
|
31990
|
+
const z = b.node("B", (span + 3) * cw, 0, 64, hubH);
|
|
31991
|
+
const place = (prefix, first, cols, n) => choose(random, cols * rows, n).map((c, i) => b.node(`${prefix}${i}`, (first + c % cols) * cw, Math.floor(c / cols) * ch, 64, 28));
|
|
31992
|
+
const mid = place("m", 3, span, shared);
|
|
31993
|
+
const left = place("a", 0, 2, own);
|
|
31994
|
+
const right = place("b", span + 4, 2, own);
|
|
31995
|
+
for (const id of mid) {
|
|
31996
|
+
b.link({ node: a }, { node: id });
|
|
31997
|
+
b.link({ node: z }, { node: id });
|
|
31998
|
+
}
|
|
31999
|
+
for (const id of left) b.link({ node: a }, { node: id });
|
|
32000
|
+
for (const id of right) b.link({ node: z }, { node: id });
|
|
32001
|
+
b.link({ node: a }, { node: z });
|
|
32002
|
+
return b.graph();
|
|
32003
|
+
}
|
|
32004
|
+
});
|
|
32005
|
+
/** The family `uneven-sizes`: a few large boxes and many small boxes in shelves, in a random order. Many edges end on a large box. */
|
|
32006
|
+
const UNEVEN_SIZES = caseFamily({
|
|
32007
|
+
name: "uneven-sizes",
|
|
32008
|
+
meaning: "Very uneven box sizes: a few large boxes among many small boxes.",
|
|
32009
|
+
dimensions: Object.freeze([
|
|
32010
|
+
count("small", 4, 300, 90, "The number of small boxes."),
|
|
32011
|
+
count("large", 1, 16, 4, "The number of large boxes."),
|
|
32012
|
+
count("ratio", 2, 10, 5, "The size of a large box, divided by the size of a small box."),
|
|
32013
|
+
numberDimension({
|
|
32014
|
+
name: "edgesPerNode",
|
|
32015
|
+
kind: "real",
|
|
32016
|
+
min: .5,
|
|
32017
|
+
max: 4,
|
|
32018
|
+
default: 1.5,
|
|
32019
|
+
meaning: "The number of tries for an edge, divided by the number of boxes."
|
|
32020
|
+
}),
|
|
32021
|
+
numberDimension({
|
|
32022
|
+
name: "toLarge",
|
|
32023
|
+
kind: "real",
|
|
32024
|
+
min: 0,
|
|
32025
|
+
max: 1,
|
|
32026
|
+
default: .5,
|
|
32027
|
+
meaning: "The share of the edges with a large box at one end."
|
|
32028
|
+
}),
|
|
32029
|
+
count("gap", 8, 120, 30, "The free space between two boxes, in units.")
|
|
32030
|
+
]),
|
|
32031
|
+
generator: "family.uneven-sizes",
|
|
32032
|
+
seeding: SEEDING,
|
|
32033
|
+
make: (v, _seed, random) => {
|
|
32034
|
+
const b = new Builder();
|
|
32035
|
+
const ratio = v["ratio"];
|
|
32036
|
+
const gap = v["gap"];
|
|
32037
|
+
const boxes = [];
|
|
32038
|
+
for (let i = 0; i < v["large"]; i++) boxes.push({
|
|
32039
|
+
id: `L${i}`,
|
|
32040
|
+
w: ratio * int(random, 48, 72),
|
|
32041
|
+
h: ratio * int(random, 24, 36)
|
|
32042
|
+
});
|
|
32043
|
+
for (let i = 0; i < v["small"]; i++) boxes.push({
|
|
32044
|
+
id: `s${i}`,
|
|
32045
|
+
w: int(random, 40, 80),
|
|
32046
|
+
h: int(random, 20, 40)
|
|
32047
|
+
});
|
|
32048
|
+
const order = shuffle(random, boxes.length);
|
|
32049
|
+
const area = boxes.reduce((s, x) => s + (x.w + gap) * (x.h + gap), 0);
|
|
32050
|
+
const width = Math.max(...boxes.map((x) => x.w), Math.floor(3 * ceilSqrt(area) / 2));
|
|
32051
|
+
const places = /* @__PURE__ */ new Map();
|
|
32052
|
+
let x = 0;
|
|
32053
|
+
let y = 0;
|
|
32054
|
+
let shelf = 0;
|
|
32055
|
+
for (const i of order) {
|
|
32056
|
+
const box = boxes[i];
|
|
32057
|
+
if (x > 0 && x + box.w > width) {
|
|
32058
|
+
x = 0;
|
|
32059
|
+
y += shelf + gap;
|
|
32060
|
+
shelf = 0;
|
|
32061
|
+
}
|
|
32062
|
+
places.set(box.id, [x, y]);
|
|
32063
|
+
x += box.w + gap + int(random, 0, gap);
|
|
32064
|
+
shelf = Math.max(shelf, box.h);
|
|
32065
|
+
}
|
|
32066
|
+
for (const box of boxes) {
|
|
32067
|
+
const [px, py] = places.get(box.id);
|
|
32068
|
+
b.node(box.id, px, py, box.w, box.h);
|
|
32069
|
+
}
|
|
32070
|
+
const ids = boxes.map((x) => x.id);
|
|
32071
|
+
const large = ids.slice(0, v["large"]);
|
|
32072
|
+
const tries = Math.round(v["edgesPerNode"] * ids.length);
|
|
32073
|
+
for (let n = 0; n < tries; n++) {
|
|
32074
|
+
const s = ids[int(random, 0, ids.length - 1)];
|
|
32075
|
+
const t = random() < v["toLarge"] ? large[int(random, 0, large.length - 1)] : ids[int(random, 0, ids.length - 1)];
|
|
32076
|
+
b.link({ node: s }, { node: t });
|
|
32077
|
+
}
|
|
32078
|
+
return b.graph();
|
|
32079
|
+
}
|
|
32080
|
+
});
|
|
32081
|
+
/** The family `thin-bars`: long thin bars in lanes, as a timeline or the rows of a table. The links join bars of lanes near each other. */
|
|
32082
|
+
const THIN_BARS = caseFamily({
|
|
32083
|
+
name: "thin-bars",
|
|
32084
|
+
meaning: "Long thin boxes in lanes, as a timeline or the rows of a table.",
|
|
32085
|
+
dimensions: Object.freeze([
|
|
32086
|
+
count("lanes", 2, 40, 16, "The number of lanes. Each lane is a row of a timeline."),
|
|
32087
|
+
count("bars", 1, 12, 5, "The number of bars in each lane."),
|
|
32088
|
+
count("links", 0, 240, 64, "The number of tries for a link between two bars of lanes near each other."),
|
|
32089
|
+
count("thick", 4, 32, 12, "The height of each bar, in units."),
|
|
32090
|
+
count("gap", 4, 80, 16, "The free space between two lanes, in units.")
|
|
32091
|
+
]),
|
|
32092
|
+
generator: "family.thin-bars",
|
|
32093
|
+
seeding: SEEDING,
|
|
32094
|
+
make: (v, _seed, random) => {
|
|
32095
|
+
const b = new Builder();
|
|
32096
|
+
const lanes = v["lanes"];
|
|
32097
|
+
const bars = v["bars"];
|
|
32098
|
+
const thick = v["thick"];
|
|
32099
|
+
for (let i = 0; i < lanes; i++) {
|
|
32100
|
+
let x = int(random, 0, 80);
|
|
32101
|
+
for (let j = 0; j < bars; j++) {
|
|
32102
|
+
const w = int(random, 120, 420);
|
|
32103
|
+
b.node(`t${i}-${j}`, x, i * (thick + v["gap"]), w, thick);
|
|
32104
|
+
x += w + int(random, 24, 120);
|
|
32105
|
+
}
|
|
32106
|
+
}
|
|
32107
|
+
for (let n = 0; n < v["links"]; n++) {
|
|
32108
|
+
const a = int(random, 0, lanes - 1);
|
|
32109
|
+
const c = Math.max(0, Math.min(lanes - 1, a + int(random, -3, 3)));
|
|
32110
|
+
b.link({ node: `t${a}-${int(random, 0, bars - 1)}` }, { node: `t${c}-${int(random, 0, bars - 1)}` });
|
|
32111
|
+
}
|
|
32112
|
+
return b.graph();
|
|
32113
|
+
}
|
|
32114
|
+
});
|
|
32115
|
+
/** The family `sparse-long`: few nodes far apart on a large board, and edges that go to far targets. */
|
|
32116
|
+
const SPARSE_LONG = caseFamily({
|
|
32117
|
+
name: "sparse-long",
|
|
32118
|
+
meaning: "A sparse board: few nodes far apart, with long edges across the board.",
|
|
32119
|
+
dimensions: Object.freeze([
|
|
32120
|
+
count("nodes", 4, 120, 36, "The number of nodes."),
|
|
32121
|
+
count("edges", 0, 160, 36, "The number of tries for a long edge."),
|
|
32122
|
+
count("spacing", 200, 1200, 480, "The size of the square cell of each node, in units."),
|
|
32123
|
+
count("far", 1, 8, 4, "The number of random targets of an edge. The edge takes the target farthest from its source.")
|
|
32124
|
+
]),
|
|
32125
|
+
generator: "family.sparse-long",
|
|
32126
|
+
seeding: SEEDING,
|
|
32127
|
+
make: (v, _seed, random) => {
|
|
32128
|
+
const b = new Builder();
|
|
32129
|
+
const n = v["nodes"];
|
|
32130
|
+
const spacing = v["spacing"];
|
|
32131
|
+
const cols = ceilSqrt(n);
|
|
32132
|
+
for (let i = 0; i < n; i++) {
|
|
32133
|
+
const w = int(random, 80, 120);
|
|
32134
|
+
const h = int(random, 32, 56);
|
|
32135
|
+
b.node(`n${i}`, i % cols * spacing + int(random, 0, spacing - w - 40), Math.floor(i / cols) * spacing + int(random, 0, spacing - h - 40), w, h);
|
|
32136
|
+
}
|
|
32137
|
+
const dist = (i, j) => Math.abs(i % cols - j % cols) + Math.abs(Math.floor(i / cols) - Math.floor(j / cols));
|
|
32138
|
+
for (let e = 0; e < v["edges"]; e++) {
|
|
32139
|
+
const s = int(random, 0, n - 1);
|
|
32140
|
+
let best = -1;
|
|
32141
|
+
let far = -1;
|
|
32142
|
+
for (let f = 0; f < v["far"]; f++) {
|
|
32143
|
+
const t = int(random, 0, n - 1);
|
|
32144
|
+
if (t !== s && dist(s, t) > far) {
|
|
32145
|
+
best = t;
|
|
32146
|
+
far = dist(s, t);
|
|
32147
|
+
}
|
|
32148
|
+
}
|
|
32149
|
+
if (best >= 0) b.link({ node: `n${s}` }, { node: `n${best}` });
|
|
32150
|
+
}
|
|
32151
|
+
return b.graph();
|
|
32152
|
+
}
|
|
32153
|
+
});
|
|
32154
|
+
/** The family `local-mesh`: a grid of small nodes with many short edges, each to a node of the 8 nodes around it. */
|
|
32155
|
+
const LOCAL_MESH = caseFamily({
|
|
32156
|
+
name: "local-mesh",
|
|
32157
|
+
meaning: "Many short local edges: each node links to nodes next to it in a grid.",
|
|
32158
|
+
dimensions: Object.freeze([
|
|
32159
|
+
count("cols", 2, 40, 12, "The number of columns of nodes."),
|
|
32160
|
+
count("rows", 2, 30, 9, "The number of rows of nodes."),
|
|
32161
|
+
count("degree", 1, 6, 3, "The number of tries for an edge to one of the 8 nodes around a node, for each node."),
|
|
32162
|
+
count("gap", 8, 80, 20, "The free space between two nodes, in units."),
|
|
32163
|
+
count("jitter", 0, 40, 8, "The largest random move of a node in its cell, in units. The move stays 8 units or more below the gap.")
|
|
32164
|
+
]),
|
|
32165
|
+
generator: "family.local-mesh",
|
|
32166
|
+
seeding: SEEDING,
|
|
32167
|
+
make: (v, _seed, random) => {
|
|
32168
|
+
const b = new Builder();
|
|
32169
|
+
const cols = v["cols"];
|
|
32170
|
+
const rows = v["rows"];
|
|
32171
|
+
const gap = v["gap"];
|
|
32172
|
+
const move = Math.min(v["jitter"], gap - 8);
|
|
32173
|
+
const id = (r, c) => `m${r}-${c}`;
|
|
32174
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) b.node(id(r, c), c * (48 + gap) + int(random, 0, move), r * (28 + gap) + int(random, 0, move), 48, 28);
|
|
32175
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) for (let t = 0; t < v["degree"]; t++) {
|
|
32176
|
+
const rr = r + int(random, -1, 1);
|
|
32177
|
+
const cc = c + int(random, -1, 1);
|
|
32178
|
+
if (rr >= 0 && rr < rows && cc >= 0 && cc < cols) b.link({ node: id(r, c) }, { node: id(rr, cc) });
|
|
32179
|
+
}
|
|
32180
|
+
return b.graph();
|
|
32181
|
+
}
|
|
32182
|
+
});
|
|
32183
|
+
/** The values of the dimension `layout` of `bipartite`. */
|
|
32184
|
+
const BIPARTITE_LAYOUTS = Object.freeze(["rows", "mixed"]);
|
|
32185
|
+
/**
|
|
32186
|
+
* The family `bipartite`: a complete bipartite part, with dense crossings that no plan can remove, and other nodes with random links.
|
|
32187
|
+
* At 3 nodes or more on each side, the part has no drawing without crossings.
|
|
32188
|
+
*/
|
|
32189
|
+
const BIPARTITE = caseFamily({
|
|
32190
|
+
name: "bipartite",
|
|
32191
|
+
meaning: "An adversarial board: a complete bipartite part whose crossings no plan can remove.",
|
|
32192
|
+
dimensions: Object.freeze([
|
|
32193
|
+
count("left", 2, 16, 6, "The number of nodes of the first side of the complete bipartite part."),
|
|
32194
|
+
count("right", 2, 16, 6, "The number of nodes of the second side. Each node of the first side links to each node of the second side."),
|
|
32195
|
+
count("noise", 0, 120, 24, "The number of other nodes, in a band below the bipartite part."),
|
|
32196
|
+
count("extra", 0, 160, 24, "The number of tries for a random link between two nodes of the board."),
|
|
32197
|
+
enumDimension("layout", BIPARTITE_LAYOUTS, "rows", "The places of the bipartite part. `rows`: each side in one row, one row above the other. `mixed`: all the nodes at random cells of one grid."),
|
|
32198
|
+
count("gap", 16, 160, 64, "The free space between two nodes, in units.")
|
|
32199
|
+
]),
|
|
32200
|
+
generator: "family.bipartite",
|
|
32201
|
+
seeding: SEEDING,
|
|
32202
|
+
make: (v, _seed, random, dims) => {
|
|
32203
|
+
const b = new Builder();
|
|
32204
|
+
const left = v["left"];
|
|
32205
|
+
const right = v["right"];
|
|
32206
|
+
const noise = v["noise"];
|
|
32207
|
+
const gap = v["gap"];
|
|
32208
|
+
const cw = 80 + gap;
|
|
32209
|
+
const ch = 36 + gap;
|
|
32210
|
+
const ids = [
|
|
32211
|
+
...Array.from({ length: left }, (_, i) => `a${i}`),
|
|
32212
|
+
...Array.from({ length: right }, (_, j) => `b${j}`),
|
|
32213
|
+
...Array.from({ length: noise }, (_, k) => `n${k}`)
|
|
32214
|
+
];
|
|
32215
|
+
if (dims["layout"] === "mixed") {
|
|
32216
|
+
const cols = ceilSqrt(ids.length + Math.floor(ids.length / 4));
|
|
32217
|
+
const cells = choose(random, cols * cols, ids.length);
|
|
32218
|
+
const order = shuffle(random, ids.length);
|
|
32219
|
+
ids.forEach((id, i) => {
|
|
32220
|
+
const c = cells[order[i]];
|
|
32221
|
+
b.node(id, c % cols * cw, Math.floor(c / cols) * ch, 80, 36);
|
|
32222
|
+
});
|
|
32223
|
+
} else {
|
|
32224
|
+
const cols = Math.max(left, right, ceilSqrt(noise));
|
|
32225
|
+
for (let i = 0; i < left; i++) b.node(`a${i}`, (i + Math.floor((cols - left) / 2)) * cw, 0, 80, 36);
|
|
32226
|
+
for (let j = 0; j < right; j++) b.node(`b${j}`, (j + Math.floor((cols - right) / 2)) * cw, 3 * ch, 80, 36);
|
|
32227
|
+
choose(random, cols * Math.max(1, Math.floor((noise + Math.floor(noise / 4) + cols - 1) / cols)), noise).forEach((c, k) => b.node(`n${k}`, c % cols * cw, (5 + Math.floor(c / cols)) * ch, 80, 36));
|
|
32228
|
+
}
|
|
32229
|
+
for (let i = 0; i < left; i++) for (let j = 0; j < right; j++) b.link({ node: `a${i}` }, { node: `b${j}` });
|
|
32230
|
+
for (let n = 0; n < v["extra"]; n++) b.link({ node: ids[int(random, 0, ids.length - 1)] }, { node: ids[int(random, 0, ids.length - 1)] });
|
|
32231
|
+
return b.graph();
|
|
32232
|
+
}
|
|
32233
|
+
});
|
|
32234
|
+
/** The 9 families of CORPUS-FAMILIES, in the order of `screen-v3`. */
|
|
32235
|
+
const COVERAGE_FAMILIES = Object.freeze([
|
|
32236
|
+
FRAME_ROWS,
|
|
32237
|
+
PIPELINE,
|
|
32238
|
+
FAN_OUT,
|
|
32239
|
+
TWO_HUBS,
|
|
32240
|
+
UNEVEN_SIZES,
|
|
32241
|
+
THIN_BARS,
|
|
32242
|
+
SPARSE_LONG,
|
|
32243
|
+
LOCAL_MESH,
|
|
32244
|
+
BIPARTITE
|
|
32245
|
+
]);
|
|
29762
32246
|
// src/families/fixtures.ts
|
|
29763
32247
|
/** The values of the dimension `layout`: each `FixtureKind`. */
|
|
29764
32248
|
const FIXTURE_LAYOUTS = Object.freeze([
|
|
@@ -29885,6 +32369,7 @@ const DENSE_GROUPS = fixtureFamily("dense-groups", "Dense clusters whose groups
|
|
|
29885
32369
|
// src/families/index.ts
|
|
29886
32370
|
/**
|
|
29887
32371
|
* The corpus families of highways, in the order of the screen sets. Each family at its defaults makes the board of its screen family.
|
|
32372
|
+
* The first 6 families are the families of `screen-v1` and `screen-v2`. The 9 families of `COVERAGE_FAMILIES` follow them (CORPUS-FAMILIES).
|
|
29888
32373
|
* An import registers nothing. A host makes a registry with `createFamilyRegistry(HIGHWAYS_FAMILIES)`, or with the lists of other engines too.
|
|
29889
32374
|
*/
|
|
29890
32375
|
const HIGHWAYS_FAMILIES = Object.freeze([
|
|
@@ -29893,7 +32378,8 @@ const HIGHWAYS_FAMILIES = Object.freeze([
|
|
|
29893
32378
|
DENSE_GROUPS,
|
|
29894
32379
|
HUB_CHAIN,
|
|
29895
32380
|
MANY_GROUPS,
|
|
29896
|
-
PACKED_GRID
|
|
32381
|
+
PACKED_GRID,
|
|
32382
|
+
...COVERAGE_FAMILIES
|
|
29897
32383
|
]);
|
|
29898
32384
|
/** This function gives a new registry of `HIGHWAYS_FAMILIES`. */
|
|
29899
32385
|
function highwaysRegistry() {
|
|
@@ -30159,13 +32645,130 @@ const SCREEN_V2 = {
|
|
|
30159
32645
|
} },
|
|
30160
32646
|
heldOut: SCREEN_V2_HELD_OUT
|
|
30161
32647
|
};
|
|
32648
|
+
/** This function gives the 9 families of CORPUS-FAMILIES, each with `seeds` members. Their base seeds are the primes from 13 to 43. */
|
|
32649
|
+
function coverageFamilies(seeds) {
|
|
32650
|
+
return [
|
|
32651
|
+
registryFamily("frame-rows", 13, "Cards with full-width rows and fixed plugs on their walls.", seeds, {
|
|
32652
|
+
...BASE,
|
|
32653
|
+
count: "100",
|
|
32654
|
+
edgeDensity: "sparse",
|
|
32655
|
+
edgeShape: "random",
|
|
32656
|
+
groups: "nested",
|
|
32657
|
+
ports: "fixed"
|
|
32658
|
+
}),
|
|
32659
|
+
registryFamily("pipeline", 17, "A long chain of stages in rows that turn.", seeds, {
|
|
32660
|
+
...BASE,
|
|
32661
|
+
count: "100",
|
|
32662
|
+
edgeDensity: "sparse",
|
|
32663
|
+
edgeShape: "tree",
|
|
32664
|
+
ports: "free"
|
|
32665
|
+
}),
|
|
32666
|
+
registryFamily("fan-out", 19, "A wide fan-out from one hub to the rings around it.", seeds, {
|
|
32667
|
+
...BASE,
|
|
32668
|
+
count: "100",
|
|
32669
|
+
edgeDensity: "medium",
|
|
32670
|
+
edgeShape: "bundled",
|
|
32671
|
+
ports: "free"
|
|
32672
|
+
}),
|
|
32673
|
+
registryFamily("two-hubs", 23, "Two hubs with many shared targets.", seeds, {
|
|
32674
|
+
...BASE,
|
|
32675
|
+
count: "100",
|
|
32676
|
+
edgeDensity: "medium",
|
|
32677
|
+
edgeShape: "bundled",
|
|
32678
|
+
ports: "free"
|
|
32679
|
+
}),
|
|
32680
|
+
registryFamily("uneven-sizes", 29, "A few large boxes among many small boxes.", seeds, {
|
|
32681
|
+
...BASE,
|
|
32682
|
+
sizes: "bimodal",
|
|
32683
|
+
count: "100",
|
|
32684
|
+
edgeDensity: "medium",
|
|
32685
|
+
edgeShape: "random",
|
|
32686
|
+
ports: "free"
|
|
32687
|
+
}),
|
|
32688
|
+
registryFamily("thin-bars", 31, "Long thin bars in lanes, as a timeline.", seeds, {
|
|
32689
|
+
...BASE,
|
|
32690
|
+
sizes: "extremeAspect",
|
|
32691
|
+
count: "100",
|
|
32692
|
+
edgeDensity: "medium",
|
|
32693
|
+
edgeShape: "random",
|
|
32694
|
+
ports: "free"
|
|
32695
|
+
}),
|
|
32696
|
+
registryFamily("sparse-long", 37, "Few nodes far apart, with long edges.", seeds, {
|
|
32697
|
+
...BASE,
|
|
32698
|
+
count: "10",
|
|
32699
|
+
edgeDensity: "sparse",
|
|
32700
|
+
edgeShape: "longRange",
|
|
32701
|
+
ports: "free"
|
|
32702
|
+
}),
|
|
32703
|
+
registryFamily("local-mesh", 41, "Many short edges between nodes next to each other.", seeds, {
|
|
32704
|
+
...BASE,
|
|
32705
|
+
count: "100",
|
|
32706
|
+
edgeDensity: "dense",
|
|
32707
|
+
edgeShape: "clustered",
|
|
32708
|
+
ports: "free"
|
|
32709
|
+
}),
|
|
32710
|
+
registryFamily("bipartite", 43, "A complete bipartite part: crossings that no plan removes.", seeds, {
|
|
32711
|
+
...BASE,
|
|
32712
|
+
count: "10",
|
|
32713
|
+
edgeDensity: "dense",
|
|
32714
|
+
edgeShape: "random",
|
|
32715
|
+
ports: "free"
|
|
32716
|
+
})
|
|
32717
|
+
];
|
|
32718
|
+
}
|
|
32719
|
+
/** The member seeds of each family of `screen-v3` that the split `default` holds out. */
|
|
32720
|
+
const SCREEN_V3_HELD_OUT = [
|
|
32721
|
+
7,
|
|
32722
|
+
8,
|
|
32723
|
+
9
|
|
32724
|
+
];
|
|
32725
|
+
const V3_FAMILIES = [...screenFamilies(10, 10), ...coverageFamilies(10)];
|
|
32726
|
+
/**
|
|
32727
|
+
* The screen set `screen-v3` (CORPUS-FAMILIES): the 15 families of `HIGHWAYS_FAMILIES`, each with the 10 members 0 to 9, thus 150 boards.
|
|
32728
|
+
* The first 6 families are the families of `screen-v2` with their seeds. Thus their boards are the boards of `screen-v2`, under the ids of `screen-v3`.
|
|
32729
|
+
* The 9 new families cover frames with rows, a long chain, a wide fan-out and two hubs.
|
|
32730
|
+
* They also cover uneven sizes, thin bars, long edges, short edges and a complete bipartite part.
|
|
32731
|
+
*
|
|
32732
|
+
* The manifest declares 10 inputs for each family. The split `default` holds out the members 7 to 9 of each family.
|
|
32733
|
+
* This set does not change. A new family goes into a new set. The tests compare it with the files in `corpus/screen-v3`.
|
|
32734
|
+
*/
|
|
32735
|
+
const SCREEN_V3 = {
|
|
32736
|
+
id: "screen-v3",
|
|
32737
|
+
families: V3_FAMILIES,
|
|
32738
|
+
claims: { declared: {
|
|
32739
|
+
minCount: 10,
|
|
32740
|
+
thin: [],
|
|
32741
|
+
counts: Object.fromEntries(V3_FAMILIES.map((f) => [f.name, f.seeds])),
|
|
32742
|
+
familyBy: SCREEN_FAMILY_BY,
|
|
32743
|
+
familyMin: 10
|
|
32744
|
+
} },
|
|
32745
|
+
heldOut: SCREEN_V3_HELD_OUT
|
|
32746
|
+
};
|
|
32747
|
+
/**
|
|
32748
|
+
* This function gives the power of the sign test over `families` families at the level `level`.
|
|
32749
|
+
* The power is the least count of families on one side that passes.
|
|
32750
|
+
* A family set without ties and with that count or more on one side passes the two-sided exact sign test, with the p below `level`.
|
|
32751
|
+
* The function gives `undefined` when no count passes, for example for 5 families or fewer at the level 0.05.
|
|
32752
|
+
*/
|
|
32753
|
+
function signTestPower(families, level = .05) {
|
|
32754
|
+
for (let k = Math.ceil(families / 2); k <= families; k++) {
|
|
32755
|
+
let tail = 0;
|
|
32756
|
+
let c = 1;
|
|
32757
|
+
for (let i = 0; i <= families - k; i++) {
|
|
32758
|
+
tail += c;
|
|
32759
|
+
c = c * (families - i) / (i + 1);
|
|
32760
|
+
}
|
|
32761
|
+
if (Math.min(1, 2 * tail / 2 ** families) < level) return k;
|
|
32762
|
+
}
|
|
32763
|
+
}
|
|
30162
32764
|
/**
|
|
30163
|
-
* The screen sets by id. `screen-v1
|
|
32765
|
+
* The screen sets by id. `screen-v1`, its larger version `screen-v2` (CORPUS-SEEDS) and `screen-v3` with 15 families (CORPUS-FAMILIES) have the boards of the screen.
|
|
30164
32766
|
* `exact-v1` has small boards with a known optimum (MEAS2). `exact-v1-bounds` has larger boards with bounds (EXACT-BOUNDS).
|
|
30165
32767
|
*/
|
|
30166
32768
|
const SCREEN_SETS = {
|
|
30167
32769
|
[SCREEN_V1.id]: SCREEN_V1,
|
|
30168
32770
|
[SCREEN_V2.id]: SCREEN_V2,
|
|
32771
|
+
[SCREEN_V3.id]: SCREEN_V3,
|
|
30169
32772
|
[EXACT_V1.id]: EXACT_V1,
|
|
30170
32773
|
[EXACT_V1_BOUNDS.id]: EXACT_V1_BOUNDS
|
|
30171
32774
|
};
|
|
@@ -30979,7 +33582,9 @@ const MOVE_NAMES = Object.freeze({
|
|
|
30979
33582
|
gaps: "gap",
|
|
30980
33583
|
streets: "street",
|
|
30981
33584
|
search: "searched",
|
|
30982
|
-
interchanges: "networkRoad"
|
|
33585
|
+
interchanges: "networkRoad",
|
|
33586
|
+
knee: "tree",
|
|
33587
|
+
bronchial: "tree"
|
|
30983
33588
|
});
|
|
30984
33589
|
/**
|
|
30985
33590
|
* The highway strategies that change the plan of another strategy (`from`). Their own move is a change of that plan, so they count no move.
|
|
@@ -33194,6 +35799,6 @@ const sharedStretchStrategy = defineStrategy({
|
|
|
33194
35799
|
name: "shared-stretch"
|
|
33195
35800
|
}).route(graph)
|
|
33196
35801
|
});
|
|
33197
|
-
export { AMBIGUITY_GUARD_DEFAULTS, ARC_SOURCES, AREA_DEFAULTS, AREA_WIDTHS, ATTRACTION_DEFAULTS, ATTRACTION_SCHEMA, BACKTRACK_MIN, BACKTRACK_SHARE, BREAK, BREAK_EVEN, BREAK_EVEN_DEFAULTS, BUILT_IN_STRATEGIES, BoundaryWalk, CANDIDATE_SOURCES, CAP_SWEEP, CASES, CHANNEL_REACH, CLEARANCE_CAP, COMPARE_ALL_STEPS, COMPARE_STEPS, CONFLUENT_DEFAULTS, CONSENSUS_DEFAULTS, CONSENSUS_LINKAGES, CORRIDOR_DEFAULTS, CROSSBAR_DEFAULTS, CUBU_DEFAULTS, CURVE_TYPES, CUT_DEFAULTS, CornerGraph, DEFAULT_RNG, DENSE_GROUPS, DENSITY_KERNELS, DIFFUSION_DEFAULTS, DISTORTION_NEIGHBOURS, DIVIDED_DEFAULTS, EDGE_DISTANCES, EDGE_PATH_DEFAULTS, EXACT_CLASSES, EXACT_EDGES_PER_NODE, EXACT_V1, EXACT_V1_BOUNDS, EXACT_V1_BOUNDS_SIZES, EXACT_V1_SEEDS, EXACT_V1_SIZES, FDEB_DEFAULTS, FIXTURES, FIXTURE_LAYOUTS, FIXTURE_LOCALITY, FLOW_CLUSTER_DEFAULTS, FLOW_FIELD_DEFAULTS, FRECHET_DEFAULTS, FRONT_OBJECTIVES, GAP_BASELINES, GAP_BOOTSTRAP, GAP_CAPS, GAP_DEFAULTS, GAP_RULES, GAP_VIEWS, GROUP_SCOPES, GROUP_SPINES, GROWTH_DEFAULTS, GROWTH_SWEEP, Grid, HDBSCAN_DEFAULTS, HIERARCHY_DEFAULTS, HIERARCHY_GROUPS, HIGHWAYS_ENGINE, HIGHWAYS_FAMILIES, HIGHWAY_BASES, HIGHWAY_BASE_ROUTERS, HOUGH_DEFAULTS, HUB_ALLOCATIONS, HUB_ARC_DEFAULTS, HUB_CANDIDATES, HUB_CHAIN, HYSTERESIS_DEFAULTS, INTERCHANGE_BASES, INTERCHANGE_DEFAULTS, KDEEB_DEFAULTS, KD_DEFAULTS, KENDALL_EXACT, LANES_DEFAULTS, LANE_ORDERS, LANE_STRETCH_DEFAULTS, LANE_STRETCH_ORDERS, LANE_TRACKS, LEVEL_HIGHWAY_DEFAULTS, LINKAGES, LINK_COMMUNITY_DEFAULTS, MANY_GROUPS, MEASURES, MEASURE_LANE_WIDTH, MEET_DEFAULTS, MEMBER_FORMS, MESH_DEFAULTS, MINGLE_DEFAULTS, MIN_EFFECTS, MLSEB_DEFAULTS, MLS_FITS, MODULE_RULES, MOVE_NAMES, NESTED_DEFAULTS, OD_CELL_DEFAULTS, OUTSIDE_SLACK, Obstacles, PACKED_GRID, PICK, PLAN_CHANGERS, PORTFOLIO_B1, PORTFOLIO_BANDS, PORTFOLIO_BASE_MEMBER, PORTFOLIO_CAPS, PORTFOLIO_FIT, PORTFOLIO_HASHES, PORTFOLIO_KS_API, PORTFOLIO_RULES, PORTFOLIO_SETS, POWER_DEFAULTS, PROPOSED_RULES, P_HUB_DEFAULTS, QUICKBUNDLES_DEFAULTS, RECORD_EPS, RESISTANCE_DEFAULTS, RIDGE_DEFAULTS, ROAD_CLASS_DEFAULTS, ROOM_FAMILIES, ROUTED_STEPS, ROUTERS, SAME_HIGHWAY, SBEB_DEFAULTS, SCALE_SPACE_DEFAULTS, SCREEN_FAMILY_BY, SCREEN_RULES, SCREEN_SETS, SCREEN_STEPS, SCREEN_V1, SCREEN_V2, SCREEN_V2_HELD_OUT, SCREEN_WRAPS, SEARCH_DEFAULTS, SEARCH_MOVES, SECTION_DEFAULTS, SELECT_DEFAULTS, SIDE_NORMAL, SKELETON_GROUPS, SKELETON_TARGETS, SLACK_SWEEP, SMALL_CLUSTERED, SOFT_KEY_DEFAULTS, SPACING_LIMIT, SPECTRAL_DEFAULTS, SPIRAL_DEFAULTS, SPREAD_DEFAULTS, STABLE_ID_DEFAULTS, STEINER_DEFAULTS, STEINER_GROUPS, STEINER_METHODS, STEINER_METRICS, STEPS, STRATEGIES, STREET_DEFAULTS, STREET_SOURCES, STRETCH_DEFAULTS, STRETCH_SWEEP, STUB_DEFAULTS, SUITE_COLUMNS, SoftGraph, SplitTree, TERRITORY_DEFAULTS, TERRITORY_POINTS, TINY_UNIFORM, TOLERANCE, TRACLUS_DEFAULTS, TRAFFIC_RULES, WEISZFELD_DEFAULTS, WSPD_DEFAULTS, WayFinder, ZIPPER_DEFAULTS, activationBase, activationReport, adjustedRandIndex, allSections, ambiguity, ambiguityGate, ambiguityPlan, arcCurve, areaPlan, asRouter, attractionRouter, attractionStrategy, backtrackOf, balance, basePlan, benchBendRoom, benchCurveStrategies, benchCurves, benchHighways, benchLevelRoom, benchOne, benchRoom, benchRun, benchTracks, bendCount, bendingEnergy, bicliqueGroups, bigGroups, boardGaps, boardUnit, boardWins, bootstrapMedian, boundsFamilyName, boxDisplacement, boxExit, boxPush, boxUnit, boxesOverlap, breakEvenGate, breakEvenPlan, buildSections, bundleStretch, bundledShare, byCollection, byDataGroup, candidateArcs, candidatePlan, candidateScore, canonicalJson, capSettings, caseSeed, cellSteps, center, chainClusters, changeGraph, changedDimensions, channelOverflows, channelWidth, characteristicPoints, checkFamilies, checkLevels, checkOptions, checkParents, checkPlan, checkPort, checkProperties, checkSchema, checkSections, clampOptions, clothoidCurve, clusters1d, coAssociation, commonEdges, compareAnyStep, compareMeasure, compareSpec, compareStep, compatibility, compatibilityMatrix, confluentPlan, consensusGroups, consensusLabels, consensusPlan, controlChain, cornersRouter, corpusManifest, corridorPlan, countCrossings, countOverlaps, createFamilyRegistry, createRegistry, crossbarPlan, crossesItself, crossingAngle, crossingAngles, cubicAt, cubuPlan, curvatureVariation, curveDeviation, curveFaults, curveJoints, curveOf, curveRuns, curveSpace, curveStrategies, curvesHash, cutGroups, cutShare, declaresFamily, defineStrategy, denseGroupsBoard, densityPlan, densityRidges, depthLevels, detourSpread, diffusionPlan, dimensionAt, dimensionUnit, dist, distanceDistortion, distinctPoints, divideHighway, dividedPlan, documentHash, dominates, drawnSegments, edgeBlocks, edgeEnds, edgeLengths, edgePathPlan, edgePaths, edgePlugs, edgeResistance, enumDimension, exactBoard, exactFamily, exactGenerator, exactSeed, exactStretch, exactStretches, exercised, facingEnds, familyCell, familyKind, familyOf, familySeed, farLevel, farLinks, fdebLines, fdebPlan, fft, findStrategy, finderOf, firstLines, fixPorts, fixedPortPoint, fixtureGroupOptions, fixtureParams, fixtureRngSeed, fixtureSpecOf, flowClusterPlan, flowClusters, flowDistance, flowFieldPlan, fnv1a32, follow, formatActivation, formatBendRoom, formatCapped, formatCurves, formatFaults, formatFronts, formatGap, formatHighways, formatInput, formatLevelRoom, formatLevels, formatMeasureTime, formatPaired, formatRoom, formatRules, formatScreen, formatSections, formatSummaries, formatTable, formatTracks, frameRoutes, frechet, frechetBundles, frechetPlan, freeLines, g1Defect, g2Defect, gapBaselines, gapCap, gapClosed, gapLaneCounts, gapLanes, gapNames, gapPlan, gapValues, gateStrategy, getStrategy, goesOutAndBack, graphInput, greedyBicliques, greedyCrossbars, groupEdges, groupFill, groupRun, groupSettings, groupStability, groupStrategies, groupedPlan, growthPlan, growthSettings, guardBreaks, hasCusp, hdbscanClusters, hdbscanLabels, hdbscanPlan, hierarchyPlan, highwayChurn, highwayCurve, highwayCurves, highwayEdits, highwayIdKeep, highwayMoves, highwayRouter, highwayRouterStrategy, highwayRouters, highwayStrategies, highwaysRegistry, houghLines, houghPeaks, houghPlan, hubArcPlan, hubCandidates, hubChainBoard, hysteresisGate, hysteresisPlan, inflate, inflectionCount, inflectionCurve, inflectionSector, inkSaved, innerPoints, inputGraph, inputId, interchangeCandidates, interchangePlan, isRoutedStep, itemOverlaps, itemSizes, jaccard, jaccardIndex, jogPeaks, joinReports, jsonSafe, kMeans, kdGroups, kdeebLines, kdeebPlan, keepMembers, kendallTau, keyGroups, keyedSections, keysPlan, labelGroups, laneArea, laneCrossings, laneFidelity, laneOrder, laneRoutes, laneSeparation, laneSettings, laneStretches, lanesPlan, lanesThrough, leafGroups, lengthBetween, levelCut, levelGrouping, levelHighwayPlan, levelMeasures, levelPairs, levelRatio, levelRoom, levelStrategies, levelsOf, lineDensity, linePoint, linkAgreement, linkCommunities, linkCommunityGroups, linkCommunityPlan, linkDensity, listOptions, listStrategies, makeFixture, makeRoom, manifestHash, manyGroupsBoard, matchHighways, maxStretch, mdf, meanLine, measureBoard, measureByKey, measureByName, measureHighways, measureJogs, measureLevel, measureLevelFaults, measureLinks, measureName, measureQuality, measureRouteFaults, measureStability, meetMember, meetPlan, meetingPoints, memberAt, memberStrategy, mergeAngle, meshPlan, meshWaypoints, minEffectOf, minHash, mingleBundles, minglePlan, mixedSegments, mixesCollections, mlsPoint, mlsebLines, mlsebPlan, modularity, modulesOf, mulberry32, namedObjectives, nearestCentre, nearestOnBoundary, nearestOnLine, nearestSide, nearestSkeleton, nestedGridBoard, nestedGroups, nestedPlan, nestingFaults, nestingViolations, nextRow, nodeAmbiguity, nodeIndex, nodeLinks, numberDimension, numberOf, occupiedArea, odCellPlan, odGroups, offsetLine, offsetPolyline, oneSteiner, openGaps, openLanes, optimaFile, optimumKey, optionDefaults, orderChanges, orderFragility, orderLanes, orderRows, orderStability, orderSuiteRows, orthogonality, outOfBoxes, outerGraph, outputGroups, outputHash, pHubPlan, pMedian, packedGridBoard, paramProblem, paretoFront, parseLines, partWithin, passSettings, pathPoints, peakCurvature, pickLevel, pickMoves, placePlug, planMoves, planOfResult, plugSpacing, plugStrategies, plugsRouter, pointAt, pointBoxDistance, pointDensity, pointGroups, pointOnSide, pointSegmentDistance, polylineLength, portOffset, portfolio, portfolioB1Spec, portfolioBand, portfolioFacts, portfolioKey, portfolioMember, portfolioMembers, portfolioName, portfolioRouter, portfolioStrategy, positionStability, powerPlan, pullApart, quadtreeGroups, quantile, quickBundles, quickBundlesPlan, randomInt, rayToBox, recordLines, recordMoves, rectScattered, regionBounds, register, repairResult, repairStrategy, representative, resample, resistanceClusters, resistancePlan, resolvePorts, resultForm, resultHash, ridgeLines, ridgePlan, rng, roadClassPlan, roomDemand, roomLinks, roomOrderFaults, roomStrategies, roomValues, round3, roundCorners, roundedCurve, routeCrossings, routeInStableOrder, routeMeasures, routePortfolio, routeSeparation, routeStability, routeSymmetry, routerStrategies, routersOf, ruleCounts, runBenchmark, runKey, runPipeline, runProfile, runSuite, sameData, sampleCurve, samplesOf, sbebLines, sbebPlan, scaleClusters, scaleSpaceGroups, scaleSpacePlan, screen, screenInputs, screenSplit, searchPlan, searchScore, sectionsRouter, segmentBounds, segmentBoxDistance, segmentClusters, segmentDistance, segmentHitsBox, segmentHitsRect, segmentKey, segmentsCross, segmentsOf, selectBest, selectPlan, setManifest, sha256Hex, shapeSkeleton, sharedRouter, sharedStretchStrategy, sharedStretches, sharedWaysAll, sharedWaysPlan, shuffleEdges, sideAngle, sideToward, silhouette, skeletonGroups, skeletonPath, slackSettings, smoothCurve, snapPlan, softDistance, softKeyClusters, softKeyPlan, spanningTree, spectralClusters, spectralLabels, spectralPlan, spinePart, spiralJoin, spiralPlan, spiralTree, splineCurve, splitFrames, splitPath, splitPoint, spreadEnds, stableIdGate, stableIdPlan, stableOrder, stableOrderStrategy, steinerPlan, stepRouterStrategy, stepRouters, stepSeed, straightCurve, straightRouter, strategyKey, streetEdges, streetPlan, streetPoint, streetRide, stretchGate, stretchLaneOrders, stretchPlan, stretchSettings, stubPlan, suiteFronts, sumFaults, summarize, summarizeGap, summarizeRows, sweepGrid, sweepOf, sweepRoom, sweepSections, sweepValues, symmetricEigen, territoryPlan, toSvgPath, totalTurn, tracePlan, traclusPlan, treeCost, treeDepth, trunksRouter, turns, unwrappedName, utf8, waypointWay, weberPoint, weiszfeldPlan, wholeBoard, wholeStrategies, withBends, withRepair, withStableOrder, wspdGroups, wspdPlan, zipLegs, zipperPlan };
|
|
35802
|
+
export { AMBIGUITY_GUARD_DEFAULTS, ANNEALING_DEFAULTS, ANNEALING_SCHEMA, ARC_SOURCES, AREA_DEFAULTS, AREA_WIDTHS, ATTRACTION_DEFAULTS, ATTRACTION_SCHEMA, BACKTRACK_MIN, BACKTRACK_SHARE, BIPARTITE, BIPARTITE_LAYOUTS, BOARD_PORTFOLIO_DEFAULTS, BOARD_PORTFOLIO_SETTINGS, BREAK, BREAK_EVEN, BREAK_EVEN_DEFAULTS, BRONCHIAL_DEFAULTS, BRONCHIAL_SPLITS, BUILT_IN_STRATEGIES, BoundaryWalk, CANDIDATE_SOURCES, CAP_SWEEP, CASES, CHANNEL_REACH, CLEARANCE_CAP, COMPARE_ALL_STEPS, COMPARE_STEPS, CONFLUENT_DEFAULTS, CONSENSUS_DEFAULTS, CONSENSUS_LINKAGES, CORRIDOR_DEFAULTS, COVERAGE_FAMILIES, CROSSBAR_DEFAULTS, CUBU_DEFAULTS, CURVE_TYPES, CUT_DEFAULTS, CornerGraph, DEFAULT_RNG, DENSE_GROUPS, DENSITY_KERNELS, DIFFUSION_DEFAULTS, DISTORTION_NEIGHBOURS, DIVIDED_DEFAULTS, EDGE_DISTANCES, EDGE_PATH_DEFAULTS, EXACT_CLASSES, EXACT_EDGES_PER_NODE, EXACT_V1, EXACT_V1_BOUNDS, EXACT_V1_BOUNDS_SIZES, EXACT_V1_SEEDS, EXACT_V1_SIZES, FAN_OUT, FDEB_DEFAULTS, FIXTURES, FIXTURE_LAYOUTS, FIXTURE_LOCALITY, FLOW_CLUSTER_DEFAULTS, FLOW_FIELD_DEFAULTS, FRAME_ROWS, FRECHET_DEFAULTS, FRONT_OBJECTIVES, GAP_BASELINES, GAP_BOOTSTRAP, GAP_CAPS, GAP_DEFAULTS, GAP_RULES, GAP_VIEWS, GROUP_SCOPES, GROUP_SPINES, GROWTH_DEFAULTS, GROWTH_SWEEP, GUTTER_BEND_COST, Grid, HDBSCAN_DEFAULTS, HIERARCHY_DEFAULTS, HIERARCHY_GROUPS, HIGHWAYS_ENGINE, HIGHWAYS_FAMILIES, HIGHWAY_BASES, HIGHWAY_BASE_ROUTERS, HOUGH_DEFAULTS, HUB_ALLOCATIONS, HUB_ARC_DEFAULTS, HUB_CANDIDATES, HUB_CHAIN, HYSTERESIS_DEFAULTS, INTERCHANGE_BASES, INTERCHANGE_DEFAULTS, KDEEB_DEFAULTS, KD_DEFAULTS, KENDALL_EXACT, KNEE_DEFAULTS, KNEE_RULES, LANES_DEFAULTS, LANE_ORDERS, LANE_STRETCH_DEFAULTS, LANE_STRETCH_ORDERS, LANE_TRACKS, LEVEL_HIGHWAY_DEFAULTS, LINKAGES, LINK_COMMUNITY_DEFAULTS, LOCAL_MESH, MANY_GROUPS, MEASURES, MEASURE_LANE_WIDTH, MEET_DEFAULTS, MEMBER_FORMS, MESH_DEFAULTS, MINGLE_DEFAULTS, MIN_EFFECTS, MLSEB_DEFAULTS, MLS_FITS, MODULE_RULES, MOVE_NAMES, NESTED_DEFAULTS, OD_CELL_DEFAULTS, OUTSIDE_SLACK, Obstacles, PACKED_GRID, PICK, PIPELINE, PLAN_CHANGERS, PORTFOLIO_B1, PORTFOLIO_BANDS, PORTFOLIO_BASE_MEMBER, PORTFOLIO_CAPS, PORTFOLIO_FIT, PORTFOLIO_HASHES, PORTFOLIO_KS_API, PORTFOLIO_RULES, PORTFOLIO_SETS, POWER_DEFAULTS, PROPOSED_RULES, P_HUB_DEFAULTS, QUICKBUNDLES_DEFAULTS, RECORD_EPS, RESISTANCE_DEFAULTS, RIDGE_DEFAULTS, ROAD_CLASS_DEFAULTS, ROOM_FAMILIES, ROUTED_STEPS, ROUTERS, SAME_HIGHWAY, SBEB_DEFAULTS, SCALE_SPACE_DEFAULTS, SCREEN_FAMILY_BY, SCREEN_RULES, SCREEN_SETS, SCREEN_STEPS, SCREEN_V1, SCREEN_V2, SCREEN_V2_HELD_OUT, SCREEN_V3, SCREEN_V3_HELD_OUT, SCREEN_WRAPS, SEARCH_DEFAULTS, SEARCH_MOVES, SECTION_DEFAULTS, SELECT_DEFAULTS, SIDE_NORMAL, SKELETON_GROUPS, SKELETON_TARGETS, SLACK_SWEEP, SMALL_CLUSTERED, SOFT_KEY_DEFAULTS, SPACING_LIMIT, SPARSE_LONG, SPECTRAL_DEFAULTS, SPIRAL_DEFAULTS, SPREAD_DEFAULTS, STABLE_ID_DEFAULTS, STEINER_DEFAULTS, STEINER_GROUPS, STEINER_METHODS, STEINER_METRICS, STEPS, STRATEGIES, STREET_DEFAULTS, STREET_SOURCES, STRETCH_DEFAULTS, STRETCH_SWEEP, STUB_DEFAULTS, SUITE_COLUMNS, SoftGraph, SplitTree, TERRITORY_DEFAULTS, TERRITORY_POINTS, THIN_BARS, TIGHT_DEFAULTS, TIGHT_FIRSTS, TIGHT_SCHEMA, TIGHT_TIES, TINY_UNIFORM, TOLERANCE, TRACLUS_DEFAULTS, TRAFFIC_RULES, TWO_HUBS, UNEVEN_SIZES, VENATION_BACKBONES, VENATION_DEFAULTS, WEISZFELD_DEFAULTS, WSPD_DEFAULTS, WayFinder, ZIPPER_DEFAULTS, activationBase, activationReport, adjustedRandIndex, allSections, ambiguity, ambiguityGate, ambiguityPlan, annealWays, annealingRouter, annealingStrategy, arcCurve, areaPlan, aroundBox, asRouter, attractionRouter, attractionStrategy, backboneLinks, backtrackOf, balance, basePlan, benchBendRoom, benchCurveStrategies, benchCurves, benchHighways, benchLevelRoom, benchOne, benchRoom, benchRun, benchTracks, bendCount, bendingEnergy, bicliqueGroups, bigGroups, boardGaps, boardPortfolio, boardUnit, boardWins, bootstrapMedian, boundsFamilyName, boxDisplacement, boxExit, boxPush, boxUnit, boxesOverlap, breakEvenGate, breakEvenPlan, bronchialPlan, buildSections, bundleStretch, bundledShare, byCollection, byDataGroup, candidateArcs, candidatePlan, candidateScore, canonicalJson, capSettings, caseSeed, cellSteps, center, chainClusters, changeGraph, changedDimensions, channelOverflows, channelWidth, characteristicPoints, checkFamilies, checkLevels, checkOptions, checkParents, checkPlan, checkPort, checkProperties, checkSchema, checkSections, clampOptions, clothoidCurve, clusters1d, coAssociation, commonEdges, compareAnyStep, compareMeasure, compareSpec, compareStep, compatibility, compatibilityMatrix, confluentPlan, consensusGroups, consensusLabels, consensusPlan, controlChain, cornersRouter, corpusManifest, corridorPlan, countCrossings, countOverlaps, createFamilyRegistry, createRegistry, crossbarPlan, crossesItself, crossingAngle, crossingAngles, cubicAt, cubuPlan, curvatureVariation, curveDeviation, curveFaults, curveJoints, curveOf, curveRuns, curveSpace, curveStrategies, curvesHash, cutGroups, cutShare, declaresFamily, defineStrategy, denseGroupsBoard, densityPlan, densityRidges, depthLevels, detourSpread, diamondAngle, diffusionPlan, dimensionAt, dimensionUnit, dist, distanceDistortion, distinctPoints, divideHighway, dividedPlan, documentHash, dominates, drawnSegments, edgeBlocks, edgeEnds, edgeLengths, edgePathPlan, edgePaths, edgePlugs, edgeResistance, enumDimension, exactBoard, exactFamily, exactGenerator, exactSeed, exactStretch, exactStretches, exercised, facingEnds, familyCell, familyKind, familyOf, familySeed, farLevel, farLinks, fdebLines, fdebPlan, fft, findStrategy, finderOf, firstLines, fixPorts, fixedPortPoint, fixtureGroupOptions, fixtureParams, fixtureRngSeed, fixtureSpecOf, flowClusterPlan, flowClusters, flowDistance, flowFieldPlan, fnv1a32, follow, formatActivation, formatBendRoom, formatCapped, formatCurves, formatFaults, formatFronts, formatGap, formatHighways, formatInput, formatLevelRoom, formatLevels, formatMeasureTime, formatPaired, formatRoom, formatRules, formatScreen, formatSections, formatSummaries, formatTable, formatTracks, frameRoutes, frechet, frechetBundles, frechetPlan, freeLines, g1Defect, g2Defect, gapBaselines, gapCap, gapClosed, gapLaneCounts, gapLanes, gapNames, gapPlan, gapValues, gateStrategy, getStrategy, goesOutAndBack, graphInput, greedyBicliques, greedyCrossbars, groupEdges, groupFill, groupRun, groupSettings, groupStability, groupStrategies, groupedPlan, growthPlan, growthSettings, guardBreaks, gutterRoutes, hasCusp, hdbscanClusters, hdbscanLabels, hdbscanPlan, hierarchyPlan, highwayChurn, highwayCurve, highwayCurves, highwayEdits, highwayIdKeep, highwayMoves, highwayRouter, highwayRouterStrategy, highwayRouters, highwayStrategies, highwaysRegistry, houghLines, houghPeaks, houghPlan, hubArcPlan, hubCandidates, hubChainBoard, hysteresisGate, hysteresisPlan, inflate, inflectionCount, inflectionCurve, inflectionSector, inkSaved, innerPoints, inputGraph, inputId, interchangeCandidates, interchangePlan, isRoutedStep, itemOverlaps, itemSizes, jaccard, jaccardIndex, jogPeaks, joinReports, jsonSafe, kMeans, kdGroups, kdeebLines, kdeebPlan, keepMembers, kendallTau, keyGroups, keyedSections, keysPlan, kneeOf, kneePlan, labelGroups, laneArea, laneCrossings, laneFidelity, laneOrder, laneRoutes, laneSeparation, laneSettings, laneStretches, lanesPlan, lanesThrough, leafGroups, lengthBetween, levelCut, levelGrouping, levelHighwayPlan, levelMeasures, levelPairs, levelRatio, levelRoom, levelStrategies, levelsOf, lineDensity, linePoint, linkAgreement, linkCommunities, linkCommunityGroups, linkCommunityPlan, linkDensity, listOptions, listStrategies, makeFixture, makeRoom, manifestHash, manyGroupsBoard, matchHighways, maxStretch, mdf, meanLine, measureBoard, measureByKey, measureByName, measureHighways, measureJogs, measureLevel, measureLevelFaults, measureLinks, measureName, measureQuality, measureRouteFaults, measureStability, meetMember, meetPlan, meetingPoints, memberAt, memberStrategy, mergeAngle, meshPlan, meshWaypoints, minEffectOf, minHash, mingleBundles, minglePlan, mixedSegments, mixesCollections, mlsPoint, mlsebLines, mlsebPlan, modularity, modulesOf, mulberry32, namedObjectives, nearestCentre, nearestOnBoundary, nearestOnLine, nearestSide, nearestSkeleton, nestedGridBoard, nestedGroups, nestedPlan, nestingFaults, nestingViolations, nextRow, nodeAmbiguity, nodeIndex, nodeLinks, numberDimension, numberOf, occupiedArea, odCellPlan, odGroups, offsetLine, offsetPolyline, oneSteiner, openGaps, openLanes, optimaFile, optimumKey, optionDefaults, orderChanges, orderFragility, orderLanes, orderRows, orderStability, orderSuiteRows, orthogonality, outOfBoxes, outerGraph, outputGroups, outputHash, pHubPlan, pMedian, packedGridBoard, paramProblem, paretoFront, parseLines, partWithin, passSettings, pathPoints, peakCurvature, pickLevel, pickMoves, placePlug, planMoves, planOfResult, plugSpacing, plugStrategies, plugsRouter, pointAt, pointBoxDistance, pointDensity, pointGroups, pointOnSide, pointSegmentDistance, polylineLength, portOffset, portfolio, portfolioB1Spec, portfolioBand, portfolioFacts, portfolioKey, portfolioMember, portfolioMembers, portfolioName, portfolioRouter, portfolioStrategy, positionStability, powerPlan, pullApart, quadtreeGroups, quantile, quickBundles, quickBundlesPlan, randomInt, rankShares, rayToBox, recordLines, recordMoves, rectScattered, regionBounds, register, repairResult, repairStrategy, representative, resample, resistanceClusters, resistancePlan, resolvePorts, resultForm, resultHash, ridgeLines, ridgePlan, rng, roadClassPlan, roomDemand, roomLinks, roomOrderFaults, roomStrategies, roomValues, round3, roundCorners, roundedCurve, routeCrossings, routeInStableOrder, routeMeasures, routePortfolio, routeSeparation, routeStability, routeSymmetry, routerStrategies, routersOf, ruleCounts, runBenchmark, runKey, runPipeline, runProfile, runSuite, sameData, sampleCurve, samplesOf, sbebLines, sbebPlan, scaleClusters, scaleSpaceGroups, scaleSpacePlan, screen, screenInputs, screenSplit, searchPlan, searchScore, sectionsRouter, segmentBounds, segmentBoxDistance, segmentClusters, segmentDistance, segmentHitsBox, segmentHitsRect, segmentKey, segmentsCross, segmentsOf, selectBest, selectPlan, setManifest, sha256Hex, shapeSkeleton, sharedRouter, sharedStretchStrategy, sharedStretches, sharedWaysAll, sharedWaysPlan, shuffleEdges, sideAngle, sideToward, signTestPower, silhouette, skeletonGroups, skeletonPath, slackSettings, smoothCurve, snapPlan, softDistance, softKeyClusters, softKeyPlan, spanningTree, spectralClusters, spectralLabels, spectralPlan, spinePart, spiralJoin, spiralPlan, spiralTree, splineCurve, splitFrames, splitPath, splitPoint, splitTargets, spreadEnds, stableIdGate, stableIdPlan, stableOrder, stableOrderStrategy, steinerPlan, stepRouterStrategy, stepRouters, stepSeed, straightCurve, straightRouter, strategyKey, streetEdges, streetPlan, streetPoint, streetRide, stretchGate, stretchLaneOrders, stretchPlan, stretchSettings, stubPlan, suiteFronts, sumFaults, summarize, summarizeGap, summarizeRows, sweepGrid, sweepOf, sweepRoom, sweepSections, sweepValues, symmetricEigen, territoryPlan, tightRouter, tightStrategy, toSvgPath, totalTurn, tracePlan, traclusPlan, treeCost, treeDepth, trunksRouter, turns, unwrappedName, utf8, venationLevels, venationLinks, waySlacks, waypointWay, weberPoint, weiszfeldPlan, wholeBoard, wholeStrategies, withBends, withRepair, withStableOrder, wspdGroups, wspdPlan, zipLegs, zipperPlan };
|
|
33198
35803
|
|
|
33199
35804
|
//# sourceMappingURL=index.js.map
|