@mark1russell7/highways 0.9.1 → 0.9.2

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/build/index.js CHANGED
@@ -402,6 +402,68 @@ function fixedPortPoint(node, end) {
402
402
  y: node.box.y + at.y
403
403
  };
404
404
  }
405
+ /** This function throws a `RangeError` when the parent of a node names no node, or when the parents of a node make a cycle. */
406
+ function checkParents(graph, byId = nodeIndex(graph)) {
407
+ const done = /* @__PURE__ */ new Set();
408
+ for (const n of graph.nodes) {
409
+ const seen = /* @__PURE__ */ new Set();
410
+ for (let m = n; m?.parent !== void 0 && !done.has(m.id);) {
411
+ seen.add(m.id);
412
+ const p = byId.get(m.parent);
413
+ if (!p) throw new RangeError(`The parent "${m.parent}" of the node "${m.id}" names no node of the graph.`);
414
+ if (seen.has(p.id)) throw new RangeError(`The parents of the node "${n.id}" make a cycle through the node "${p.id}".`);
415
+ m = p;
416
+ }
417
+ for (const id of seen) done.add(id);
418
+ }
419
+ }
420
+ /** This function tells if the node `frame` holds the node `id`, at any depth. A node does not hold itself. */
421
+ function holds$3(byId, frame, id) {
422
+ for (let p = byId.get(id)?.parent; p !== void 0; p = byId.get(p)?.parent) if (p === frame) return true;
423
+ return false;
424
+ }
425
+ /**
426
+ * This function checks the parents of a graph (`checkParents`), and takes its inner edges out.
427
+ * An inner edge of the frame F has a plug of F (`End.port`) at one end. F holds the node at the other end.
428
+ * The graph that it gives keeps each node and the other edges. A graph without `parent` comes back as the same object, with no inner edge.
429
+ */
430
+ function splitFrames(graph) {
431
+ if (!graph.nodes.some((n) => n.parent !== void 0)) return {
432
+ graph,
433
+ inner: []
434
+ };
435
+ const byId = nodeIndex(graph);
436
+ checkParents(graph, byId);
437
+ const inner = [];
438
+ const edges = graph.edges.filter((e) => {
439
+ if (e.source.port !== void 0 && holds$3(byId, e.source.node, e.target.node)) inner.push({
440
+ edge: e,
441
+ frame: e.source.node,
442
+ plug: "source"
443
+ });
444
+ else if (e.target.port !== void 0 && holds$3(byId, e.target.node, e.source.node)) inner.push({
445
+ edge: e,
446
+ frame: e.target.node,
447
+ plug: "target"
448
+ });
449
+ else return true;
450
+ return false;
451
+ });
452
+ return {
453
+ graph: inner.length === 0 ? graph : {
454
+ ...graph,
455
+ edges
456
+ },
457
+ inner
458
+ };
459
+ }
460
+ /**
461
+ * This function gives the graph of a router without frames. It has the plugs of `resolvePorts`, and no inner edges (`splitFrames`).
462
+ * Each router except `corners` uses it at the start of `route`. So an inner edge gets no route from such a router.
463
+ */
464
+ function outerGraph(graph) {
465
+ return splitFrames(resolvePorts(graph)).graph;
466
+ }
405
467
  // src/fixtures.ts
406
468
  /** This function gives a seeded random source with values in [0, 1) (mulberry32). */
407
469
  function rng(seed) {
@@ -5036,20 +5098,181 @@ function sidePlugs(ways, s, es, t, et, options = {}) {
5036
5098
  if (!way) return [ps, pt];
5037
5099
  return [from[way.from].plug, to[way.to].plug];
5038
5100
  }
5101
+ // src/routers/frames.ts
5102
+ /** The tiny gap between the bounds and the band of a wall, so that a way along the bounds stays clear of the wall. */
5103
+ const EPS$5 = 1e-6;
5104
+ /**
5105
+ * This function gives the inner routes of the inner edges (`splitFrames`). Each route stays in the bounds of its frame:
5106
+ * the box of the frame, smaller by `frameInset`. It goes around the other nodes of the frame.
5107
+ * It leaves the plug of the frame into the frame, along the normal of the wall. Each route has the id of its frame in `frame`.
5108
+ */
5109
+ function frameRoutes(graph, inner, options) {
5110
+ const byId = nodeIndex(graph);
5111
+ const children = /* @__PURE__ */ new Map();
5112
+ for (const n of graph.nodes) {
5113
+ if (n.parent === void 0) continue;
5114
+ const list = children.get(n.parent);
5115
+ if (list) list.push(n);
5116
+ else children.set(n.parent, [n]);
5117
+ }
5118
+ const margin = options.margin ?? 5;
5119
+ const inset = options.frameInset ?? margin;
5120
+ const rooms = /* @__PURE__ */ new Map();
5121
+ const routes = [];
5122
+ for (const { edge, frame, plug } of inner) {
5123
+ const f = byId.get(frame);
5124
+ const [own, far] = plug === "source" ? [edge.source, edge.target] : [edge.target, edge.source];
5125
+ const chain = [];
5126
+ for (let p = byId.get(far.node).parent; p !== frame; p = byId.get(p).parent) chain.push(p);
5127
+ const key = JSON.stringify([frame, ...chain]);
5128
+ let room = rooms.get(key);
5129
+ if (!room) rooms.set(key, room = makeRoom$1(f, chain, children, byId, inset, options));
5130
+ const end = room.nodes.get(far.node);
5131
+ if (!end) continue;
5132
+ const fp = placePlug(f, own, center(end.box));
5133
+ const inward = {
5134
+ x: -fp.normal.x,
5135
+ y: -fp.normal.y
5136
+ };
5137
+ const start = {
5138
+ point: {
5139
+ x: fp.point.x + inward.x * inset,
5140
+ y: fp.point.y + inward.y * inset
5141
+ },
5142
+ normal: inward
5143
+ };
5144
+ const ep = placePlug(end, far, fp.point);
5145
+ const ends = /* @__PURE__ */ new Set([end]);
5146
+ const points = plug === "source" ? stub(fp.point, room.ways.between(start, ep, ends)) : stub(fp.point, room.ways.between(ep, start, ends).reverse()).reverse();
5147
+ routes.push({
5148
+ edge: edge.id,
5149
+ points,
5150
+ frame
5151
+ });
5152
+ }
5153
+ return routes;
5154
+ }
5155
+ /** 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. */
5156
+ function stub(plug, way) {
5157
+ const [a, b] = way;
5158
+ if (!a || dist(a, plug) === 0) return way;
5159
+ if (b && Math.abs((a.x - plug.x) * (b.y - plug.y) - (a.y - plug.y) * (b.x - plug.x)) < 1e-9 && (a.x - plug.x) * (b.x - a.x) + (a.y - plug.y) * (b.y - a.y) > 0) return [plug, ...way.slice(1)];
5160
+ return [plug, ...way];
5161
+ }
5162
+ /**
5163
+ * This function makes the room of a frame for the far ends below the nodes of `chain` (the nearest first).
5164
+ * The obstacles are the children of the frame and of each node of the chain, except the nodes of the chain.
5165
+ * Four walls close the bounds, so that no way leaves them.
5166
+ */
5167
+ function makeRoom$1(f, chain, children, byId, inset, options) {
5168
+ const margin = options.margin ?? 5;
5169
+ const bounds = shrink(f.box, inset);
5170
+ const skip = new Set(chain);
5171
+ const nodes = /* @__PURE__ */ new Map();
5172
+ for (const holder of [f.id, ...chain]) for (const n of children.get(holder) ?? []) {
5173
+ if (skip.has(n.id)) continue;
5174
+ const box = byId.get(holder).box;
5175
+ if (inside(n.box, box)) {
5176
+ nodes.set(n.id, n);
5177
+ continue;
5178
+ }
5179
+ const rule = options.outside ?? "throw";
5180
+ 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.`);
5181
+ if (rule === "skip") continue;
5182
+ const cut = cutBox(n.box, bounds);
5183
+ if (cut) nodes.set(n.id, {
5184
+ ...n,
5185
+ box: cut,
5186
+ ...n.ports ? { ports: n.ports.map((p) => p.at ? {
5187
+ ...p,
5188
+ at: {
5189
+ x: p.at.x + n.box.x - cut.x,
5190
+ y: p.at.y + n.box.y - cut.y
5191
+ }
5192
+ } : p) } : {}
5193
+ });
5194
+ }
5195
+ const w = shrink(bounds, -(margin / 2 + EPS$5));
5196
+ const t = 2 * margin + 1;
5197
+ const walls = [
5198
+ {
5199
+ x: w.x - t,
5200
+ y: w.y - t,
5201
+ w: w.w + 2 * t,
5202
+ h: t
5203
+ },
5204
+ {
5205
+ x: w.x - t,
5206
+ y: w.y + w.h,
5207
+ w: w.w + 2 * t,
5208
+ h: t
5209
+ },
5210
+ {
5211
+ x: w.x - t,
5212
+ y: w.y,
5213
+ w: t,
5214
+ h: w.h
5215
+ },
5216
+ {
5217
+ x: w.x + w.w,
5218
+ y: w.y,
5219
+ w: t,
5220
+ h: w.h
5221
+ }
5222
+ ].map((box, i) => ({
5223
+ id: `\u0000wall${i}`,
5224
+ box
5225
+ }));
5226
+ return {
5227
+ ways: new WayFinder([...nodes.values(), ...walls], options),
5228
+ nodes
5229
+ };
5230
+ }
5231
+ /** This function gives the box, smaller by `d` on each side. A negative `d` makes it larger. */
5232
+ function shrink(b, d) {
5233
+ return {
5234
+ x: b.x + d,
5235
+ y: b.y + d,
5236
+ w: Math.max(0, b.w - 2 * d),
5237
+ h: Math.max(0, b.h - 2 * d)
5238
+ };
5239
+ }
5240
+ /** This function tells if the box `a` is inside the box `b`. */
5241
+ function inside(a, b) {
5242
+ return a.x >= b.x && a.y >= b.y && a.x + a.w <= b.x + b.w && a.y + a.h <= b.y + b.h;
5243
+ }
5244
+ /** This function gives the part of the box `a` inside the box `b`, or `undefined` when they have no common area. */
5245
+ function cutBox(a, b) {
5246
+ const x = Math.max(a.x, b.x);
5247
+ const y = Math.max(a.y, b.y);
5248
+ const w = Math.min(a.x + a.w, b.x + b.w) - x;
5249
+ const h = Math.min(a.y + a.h, b.y + b.h) - y;
5250
+ return w > 0 && h > 0 ? {
5251
+ x,
5252
+ y,
5253
+ w,
5254
+ h
5255
+ } : void 0;
5256
+ }
5039
5257
  // src/routers/corners.ts
5040
5258
  /**
5041
5259
  * This function makes a router that goes around the nodes.
5042
5260
  * Each edge leaves its plug along the normal of the side. It goes a shortest way (A*) through a sparse visibility graph
5043
5261
  * of the box corners, without the needless corners. It enters the target plug along its normal.
5262
+ * An inner edge (`splitFrames`) gets an inner route in the bounds of its frame (`frameRoutes`), after the outer routes.
5044
5263
  */
5045
5264
  function cornersRouter(options) {
5046
5265
  const router = {
5047
5266
  name: options.name,
5048
5267
  route(input) {
5049
- const graph = resolvePorts(input);
5268
+ const { graph, inner } = splitFrames(resolvePorts(input));
5050
5269
  const byId = nodeIndex(graph);
5051
5270
  const ways = new WayFinder(graph.nodes, options);
5052
5271
  const routes = [];
5272
+ const done = () => {
5273
+ if (inner.length > 0) routes.push(...frameRoutes(graph, inner, options));
5274
+ return { routes };
5275
+ };
5053
5276
  if (!options.plug && (options.plugs ?? "facing") === "facing" && !((options.plugGap ?? 0) > 0)) {
5054
5277
  for (const e of graph.edges) {
5055
5278
  const s = byId.get(e.source.node);
@@ -5062,7 +5285,7 @@ function cornersRouter(options) {
5062
5285
  points: ways.between(ps, pt, /* @__PURE__ */ new Set([s, t]))
5063
5286
  });
5064
5287
  }
5065
- return { routes };
5288
+ return done();
5066
5289
  }
5067
5290
  const ends = [];
5068
5291
  const placed = options.plug ? plugsByEdge(options.plug.run({
@@ -5088,7 +5311,7 @@ function cornersRouter(options) {
5088
5311
  edge: e.id,
5089
5312
  points: ways.between(ps, pt, /* @__PURE__ */ new Set([s, t]))
5090
5313
  });
5091
- return { routes };
5314
+ return done();
5092
5315
  }
5093
5316
  };
5094
5317
  return options.maxNodes === void 0 ? router : {
@@ -5177,7 +5400,7 @@ function spacePlugs(ends, gap) {
5177
5400
  const plugsRouter = {
5178
5401
  name: "plugs",
5179
5402
  route(input) {
5180
- const graph = resolvePorts(input);
5403
+ const graph = outerGraph(input);
5181
5404
  const byId = nodeIndex(graph);
5182
5405
  const routes = [];
5183
5406
  for (const e of graph.edges) {
@@ -5359,7 +5582,7 @@ const LEVEL_LINK_DEFAULTS = Object.freeze({
5359
5582
  plugInset: 6,
5360
5583
  throughCost: 8
5361
5584
  });
5362
- const EPS$3 = 1e-6;
5585
+ const EPS$4 = 1e-6;
5363
5586
  /**
5364
5587
  * This function removes the repeated points, the points in the middle of a straight run,
5365
5588
  * and the points where the way turns back on itself.
@@ -5374,15 +5597,15 @@ function tidy(p) {
5374
5597
  function tidyPoints(p) {
5375
5598
  const out = [];
5376
5599
  for (const q of p) {
5377
- if (out.length > 0 && dist(q, out[out.length - 1]) <= EPS$3) continue;
5600
+ if (out.length > 0 && dist(q, out[out.length - 1]) <= EPS$4) continue;
5378
5601
  while (out.length >= 2) {
5379
5602
  const a = out[out.length - 2];
5380
5603
  const b = out[out.length - 1];
5381
5604
  const cross = (b.x - a.x) * (q.y - b.y) - (b.y - a.y) * (q.x - b.x);
5382
- if (Math.abs(cross) > EPS$3 * (1 + dist(a, b) * dist(b, q))) break;
5605
+ if (Math.abs(cross) > EPS$4 * (1 + dist(a, b) * dist(b, q))) break;
5383
5606
  out.pop();
5384
5607
  }
5385
- if (out.length > 0 && dist(q, out[out.length - 1]) <= EPS$3) continue;
5608
+ if (out.length > 0 && dist(q, out[out.length - 1]) <= EPS$4) continue;
5386
5609
  out.push(q);
5387
5610
  }
5388
5611
  return out;
@@ -5755,7 +5978,7 @@ function freeSpans(lo, hi, covered) {
5755
5978
  if (x >= hi) break;
5756
5979
  }
5757
5980
  if (x < hi) out.push([x, hi]);
5758
- return out.filter(([a, b]) => b - a > EPS$3);
5981
+ return out.filter(([a, b]) => b - a > EPS$4);
5759
5982
  }
5760
5983
  /**
5761
5984
  * This function gives the shortest way between the free plugs of the two nodes, or `undefined` when the second corner graph has none.
@@ -5887,7 +6110,7 @@ function squarePull(p, obstacles, pad, first, last, lookahead = 16) {
5887
6110
  out.push(...step.legs);
5888
6111
  const end = out[out.length - 1];
5889
6112
  const before = out[out.length - 2];
5890
- axis = Math.abs(end.y - before.y) < EPS$3 ? "x" : Math.abs(end.x - before.x) < EPS$3 ? "y" : void 0;
6113
+ axis = Math.abs(end.y - before.y) < EPS$4 ? "x" : Math.abs(end.x - before.x) < EPS$4 ? "y" : void 0;
5891
6114
  i = step.j;
5892
6115
  }
5893
6116
  return out;
@@ -5897,7 +6120,7 @@ function squarePull(p, obstacles, pad, first, last, lookahead = 16) {
5897
6120
  * Of two clear corners, it picks the corner that continues the axis `from` and arrives along the axis `to`.
5898
6121
  */
5899
6122
  function square(a, b, obstacles, pad, from, to) {
5900
- if (Math.abs(a.x - b.x) < EPS$3 || Math.abs(a.y - b.y) < EPS$3) return obstacles.clear(a, b, pad) ? [b] : void 0;
6123
+ if (Math.abs(a.x - b.x) < EPS$4 || Math.abs(a.y - b.y) < EPS$4) return obstacles.clear(a, b, pad) ? [b] : void 0;
5901
6124
  const options = [{
5902
6125
  c: {
5903
6126
  x: b.x,
@@ -6007,7 +6230,7 @@ function sectionsRouter(options) {
6007
6230
  const router = {
6008
6231
  name: options.name,
6009
6232
  route(input) {
6010
- const graph = resolvePorts(input);
6233
+ const graph = outerGraph(input);
6011
6234
  const stretch = options.stretch ?? SECTIONS_DEFAULTS.stretch;
6012
6235
  const stretchPad = options.stretchPad ?? SECTIONS_DEFAULTS.stretchPad;
6013
6236
  const hubSlack = options.hubSlack ?? SECTIONS_DEFAULTS.hubSlack;
@@ -7049,7 +7272,7 @@ function trunksRouter(options) {
7049
7272
  const router = {
7050
7273
  name: options.name,
7051
7274
  route(input) {
7052
- const graph = resolvePorts(input);
7275
+ const graph = outerGraph(input);
7053
7276
  const byId = nodeIndex(graph);
7054
7277
  const ways = new WayFinder(graph.nodes, options);
7055
7278
  const points = /* @__PURE__ */ new Map();
@@ -7245,7 +7468,7 @@ function sharedRouter(options, negotiation) {
7245
7468
  const router = {
7246
7469
  name: options.name,
7247
7470
  route(input) {
7248
- const graph = resolvePorts(input);
7471
+ const graph = outerGraph(input);
7249
7472
  const byId = nodeIndex(graph);
7250
7473
  const ways = new WayFinder(graph.nodes, options);
7251
7474
  const corners = ways.corners;
@@ -7611,7 +7834,7 @@ function boxExit(box, toward) {
7611
7834
  const straightRouter = {
7612
7835
  name: "straight",
7613
7836
  route(input) {
7614
- const graph = resolvePorts(input);
7837
+ const graph = outerGraph(input);
7615
7838
  const byId = nodeIndex(graph);
7616
7839
  const routes = [];
7617
7840
  for (const e of graph.edges) {
@@ -8378,7 +8601,7 @@ const LANE_TRACKS = [
8378
8601
  "left-edge",
8379
8602
  "free"
8380
8603
  ];
8381
- const EPS$2 = 1e-6;
8604
+ const EPS$3 = 1e-6;
8382
8605
  const ROUNDS = 8;
8383
8606
  /**
8384
8607
  * This function gives each link that runs along a gap its own lane in the gap. The lanes of a gap are `spacing` apart, around its middle line.
@@ -8403,7 +8626,7 @@ function laneGaps(items, links, options) {
8403
8626
  })));
8404
8627
  const counts = [];
8405
8628
  for (const g of boardGaps(items, options.snap)) {
8406
- if (g.width <= EPS$2) continue;
8629
+ if (g.width <= EPS$3) continue;
8407
8630
  const across = g.axis;
8408
8631
  const along = g.axis === "x" ? "y" : "x";
8409
8632
  const lo = g.at - g.width;
@@ -8414,8 +8637,8 @@ function laneGaps(items, links, options) {
8414
8637
  for (let i = 2; i <= p.length - 2; i++) {
8415
8638
  const a = p[i - 1];
8416
8639
  const b = p[i];
8417
- if (Math.abs(a[across] - b[across]) > EPS$2 || a[across] <= lo + EPS$2 || a[across] >= hi - EPS$2) continue;
8418
- if (Math.abs(a[along] - b[along]) <= EPS$2) continue;
8640
+ if (Math.abs(a[across] - b[across]) > EPS$3 || a[across] <= lo + EPS$3 || a[across] >= hi - EPS$3) continue;
8641
+ if (Math.abs(a[along] - b[along]) <= EPS$3) continue;
8419
8642
  const side = (q, r) => Math.sign(r[across] - q[across]);
8420
8643
  pieces.push({
8421
8644
  link: li,
@@ -8491,7 +8714,7 @@ function leftEdgeLanes(list, constrained) {
8491
8714
  const now = [];
8492
8715
  let end = -Infinity;
8493
8716
  for (const i of byStart) {
8494
- if (lane[i] >= 0 || wait[i] > 0 || list[i].lo < end - EPS$2) continue;
8717
+ if (lane[i] >= 0 || wait[i] > 0 || list[i].lo < end - EPS$3) continue;
8495
8718
  now.push(i);
8496
8719
  end = list[i].hi;
8497
8720
  }
@@ -8514,15 +8737,15 @@ function density(list) {
8514
8737
  }
8515
8738
  return most;
8516
8739
  }
8517
- const overlaps = (P, Q) => Math.min(P.hi, Q.hi) - Math.max(P.lo, Q.lo) > EPS$2;
8740
+ const overlaps = (P, Q) => Math.min(P.hi, Q.hi) - Math.max(P.lo, Q.lo) > EPS$3;
8518
8741
  /**
8519
8742
  * This function counts the crossings when the piece `P` is on the near side of the piece `Q`.
8520
8743
  * A turn of `P` to the far side crosses `Q` when it is inside the span of `Q`. A turn of `Q` to the near side crosses `P` in the same way.
8521
8744
  */
8522
8745
  function cost$1(P, Q) {
8523
8746
  let c = 0;
8524
- for (const [y, s] of P.turns) if (s > 0 && y > Q.lo + EPS$2 && y < Q.hi - EPS$2) c++;
8525
- for (const [y, s] of Q.turns) if (s < 0 && y > P.lo + EPS$2 && y < P.hi - EPS$2) c++;
8747
+ for (const [y, s] of P.turns) if (s > 0 && y > Q.lo + EPS$3 && y < Q.hi - EPS$3) c++;
8748
+ for (const [y, s] of Q.turns) if (s < 0 && y > P.lo + EPS$3 && y < P.hi - EPS$3) c++;
8526
8749
  return c;
8527
8750
  }
8528
8751
  /** This function gives the pieces of one gap in lane order, from the near side to the far side. */
@@ -8837,7 +9060,7 @@ function slice(stops, on, off) {
8837
9060
  * The same graph, plan and options give the same result.
8838
9061
  */
8839
9062
  function follow(input, plan, options = {}) {
8840
- const graph = resolvePorts(input);
9063
+ const graph = outerGraph(input);
8841
9064
  const byId = nodeIndex(graph);
8842
9065
  const ways = new WayFinder(graph.nodes, options);
8843
9066
  const base = new Map((options.routes?.routes ?? []).map((r) => [r.edge, r.points]));
@@ -19894,6 +20117,23 @@ const CORNERS_SCHEMA = {
19894
20117
  plugOptions: {
19895
20118
  type: "object",
19896
20119
  describe: "The options of the strategy `plug`."
20120
+ },
20121
+ outside: {
20122
+ type: "enum",
20123
+ values: [
20124
+ "throw",
20125
+ "skip",
20126
+ "clamp"
20127
+ ],
20128
+ default: "throw",
20129
+ describe: "The rule for a box outside the box of its frame: throw a `RangeError`, skip the box and its inner edges, or cut the box to the bounds."
20130
+ },
20131
+ frameInset: {
20132
+ type: "number",
20133
+ min: 0,
20134
+ max: 20,
20135
+ step: 1,
20136
+ describe: "The distance from the walls of a frame to the bounds of its inner routes. Without a value, the inset is the margin."
19897
20137
  }
19898
20138
  };
19899
20139
  /** The declarations of the options of the router `trunks`. */
@@ -23831,11 +24071,11 @@ const BUILT_IN_STRATEGIES = Object.freeze([
23831
24071
  /** The registry of the built-in strategies. The benchmark and the site take their lists from it. */
23832
24072
  const STRATEGIES = createRegistry(BUILT_IN_STRATEGIES);
23833
24073
  // src/bench/measures/curves.ts
23834
- const EPS$1 = 1e-9;
24074
+ const EPS$2 = 1e-9;
23835
24075
  const DEG = 180 / Math.PI;
23836
24076
  const unit = (x, y) => {
23837
24077
  const l = Math.hypot(x, y);
23838
- return l > EPS$1 ? {
24078
+ return l > EPS$2 ? {
23839
24079
  x: x / l,
23840
24080
  y: y / l
23841
24081
  } : void 0;
@@ -23854,7 +24094,7 @@ function frame(from, s, t) {
23854
24094
  x: -Math.sin(a) * sign,
23855
24095
  y: Math.cos(a) * sign
23856
24096
  },
23857
- curvature: s.radius > EPS$1 ? sign / s.radius : 0
24097
+ curvature: s.radius > EPS$2 ? sign / s.radius : 0
23858
24098
  };
23859
24099
  }
23860
24100
  const [p0, p1, p2, p3] = [
@@ -23878,7 +24118,7 @@ function frame(from, s, t) {
23878
24118
  y: 6 * (p1.y - 2 * p2.y + p3.y)
23879
24119
  };
23880
24120
  const speed = Math.hypot(d.x, d.y);
23881
- if (speed > EPS$1) return {
24121
+ if (speed > EPS$2) return {
23882
24122
  tangent: {
23883
24123
  x: d.x / speed,
23884
24124
  y: d.y / speed
@@ -23947,18 +24187,71 @@ function g2Defect(segments, u) {
23947
24187
  */
23948
24188
  function bendingEnergy(points, tolerance = .25) {
23949
24189
  const q = [];
23950
- for (const v of points) if (q.length === 0 || dist(q[q.length - 1], v) > EPS$1) q.push(v);
24190
+ for (const v of points) if (q.length === 0 || dist(q[q.length - 1], v) > EPS$2) q.push(v);
23951
24191
  if (q.length < 3) return 0;
23952
24192
  const phi = turns(q);
23953
24193
  let sum = 0;
23954
24194
  for (let i = 1; i + 1 < q.length; i++) {
23955
24195
  const t = Math.abs(phi[i - 1]);
23956
- if (t < EPS$1) continue;
24196
+ if (t < EPS$2) continue;
23957
24197
  const l = Math.min((dist(q[i - 1], q[i]) + dist(q[i], q[i + 1])) / 2, 8 * tolerance / t);
23958
24198
  sum += t * t / l;
23959
24199
  }
23960
24200
  return polylineLength(q) * sum;
23961
24201
  }
24202
+ /**
24203
+ * This function gives the curvature variation of a sampled curve (C4 of MEAS0, Moreton 1993): `V = L^3 · ∫ (dκ/ds)^2 ds`.
24204
+ * Each inner sample gets the curvature `κ = φ / l` of `bendingEnergy`, with its sign. `L` is the length of the curve.
24205
+ * The sum is `L^3 · Σ (κ_(i+1) - κ_i)^2 / d_i`, with `d_i` the distance of two inner samples in sequence.
24206
+ *
24207
+ * The value has no unit, and lower is better. A straight line and an arc of a circle give 0. Fewer than 4 points give 0. The cost is linear.
24208
+ */
24209
+ function curvatureVariation(points, tolerance = .25) {
24210
+ const q = [];
24211
+ for (const v of points) if (q.length === 0 || dist(q[q.length - 1], v) > EPS$2) q.push(v);
24212
+ if (q.length < 4) return 0;
24213
+ const phi = turns(q);
24214
+ const kappa = [];
24215
+ for (let i = 1; i + 1 < q.length; i++) {
24216
+ const t = phi[i - 1];
24217
+ const l = Math.min((dist(q[i - 1], q[i]) + dist(q[i], q[i + 1])) / 2, 8 * tolerance / Math.max(EPS$2, Math.abs(t)));
24218
+ kappa.push(Math.abs(t) < EPS$2 ? 0 : t / l);
24219
+ }
24220
+ let sum = 0;
24221
+ for (let i = 0; i + 1 < kappa.length; i++) sum += (kappa[i + 1] - kappa[i]) ** 2 / dist(q[i + 1], q[i + 2]);
24222
+ return polylineLength(q) ** 3 * sum;
24223
+ }
24224
+ /** This function gives the points of a polyline with no leg longer than `step`. It keeps each vertex. */
24225
+ function densify(points, step) {
24226
+ const out = [];
24227
+ for (let i = 0; i < points.length; i++) {
24228
+ const b = points[i];
24229
+ if (i > 0) {
24230
+ const a = points[i - 1];
24231
+ const n = Math.ceil(dist(a, b) / step);
24232
+ for (let k = 1; k < n; k++) out.push({
24233
+ x: a.x + (b.x - a.x) * k / n,
24234
+ y: a.y + (b.y - a.y) * k / n
24235
+ });
24236
+ }
24237
+ out.push(b);
24238
+ }
24239
+ return out;
24240
+ }
24241
+ /** The most points of each of the two lists of `curveDeviation`. More points give a longer step. */
24242
+ const DEVIATION_POINTS = 400;
24243
+ /**
24244
+ * This function gives the deviation of a curve from its route (C8 of MEAS0).
24245
+ * The value is the Fréchet distance of the samples of the curve and the polyline of the route.
24246
+ * Each list gets points with no leg longer than `step`, or longer when a list has more than 400 points. Then the discrete Fréchet distance is near the true distance.
24247
+ *
24248
+ * The value is in units of the board, and lower is better. The caller divides it by the unit of the board. The cost is quadratic for each route.
24249
+ */
24250
+ function curveDeviation(samples, polyline, step) {
24251
+ const long = Math.max(polylineLength(samples), polylineLength(polyline));
24252
+ const h = Math.max(step, long / DEVIATION_POINTS, EPS$2);
24253
+ return frechet(densify(samples, h), densify(polyline, h));
24254
+ }
23962
24255
  /** This function tells if a polyline crosses itself. Two legs next to each other do not count. The cost is `n log n`. */
23963
24256
  function crossesItself(p) {
23964
24257
  if (p.length < 4) return false;
@@ -25329,6 +25622,22 @@ function linkAgreement(graph, levels, routes, unit) {
25329
25622
  }
25330
25623
  return n > 0 ? sum / n : void 0;
25331
25624
  }
25625
+ /**
25626
+ * This function gives the level ratio of the levels (Z5 of MEAS0, Mondal 2018): `lr_d = items_(d+1) / items_d`, in the order of depth.
25627
+ * A level without items does not count. Fewer than 2 levels give `undefined`. Lower is better for both values. The cost is linear.
25628
+ */
25629
+ function levelRatio(levels) {
25630
+ const counts = byDepth(levels).map((l) => l.items.length).filter((n) => n > 0);
25631
+ if (counts.length < 2) return void 0;
25632
+ const ratios = counts.slice(1).map((n, d) => n / counts[d]);
25633
+ const logs = ratios.map((r) => Math.log(r));
25634
+ const mean = logs.reduce((s, x) => s + x, 0) / logs.length;
25635
+ const spread = Math.sqrt(logs.reduce((s, x) => s + (x - mean) ** 2, 0) / logs.length);
25636
+ return {
25637
+ max: Math.max(...ratios),
25638
+ spread
25639
+ };
25640
+ }
25332
25641
  // src/bench/measures/groups.ts
25333
25642
  /** This function gives the grouping of one level: each node goes to the id of its item. */
25334
25643
  function levelGrouping(level) {
@@ -25440,6 +25749,73 @@ function leafGroups(root, count) {
25440
25749
  function groupStability(before, after, count) {
25441
25750
  return adjustedRandIndex(leafGroups(before, count), leafGroups(after, count));
25442
25751
  }
25752
+ /**
25753
+ * This function gives the fill of the groups of one level (G4 of MEAS0): the mean of `fill(c) = Σ_(v in c) area(box(v)) / area(box(c))`.
25754
+ * `box(c)` is the box of the item. Only an item with 2 or more nodes counts, and a fill above 1 counts as 1.
25755
+ * The scale is 0 to 1, and higher is better. A level without such an item gives `undefined`. The cost is linear.
25756
+ */
25757
+ function groupFill(graph, level) {
25758
+ const area = new Map(graph.nodes.map((n) => [n.id, n.box.w * n.box.h]));
25759
+ const fills = [];
25760
+ for (const item of level.items) {
25761
+ if (item.nodes.length < 2) continue;
25762
+ const own = item.box.w * item.box.h;
25763
+ const inside = item.nodes.reduce((s, v) => s + (area.get(v) ?? 0), 0);
25764
+ fills.push(own > 0 ? Math.min(1, inside / own) : 1);
25765
+ }
25766
+ return fills.length === 0 ? void 0 : fills.reduce((s, f) => s + f, 0) / fills.length;
25767
+ }
25768
+ /**
25769
+ * This function counts the overlaps of the items of the levels (G5 of MEAS0).
25770
+ * An overlap is a pair of items of one level whose boxes overlap by more than `TOLERANCE`.
25771
+ * It is the sum over the levels of `countOverlaps`. Lower is better. The cost is `n log n` for each level.
25772
+ */
25773
+ function itemOverlaps(levels) {
25774
+ return levels.reduce((n, level) => n + countOverlaps(level.items.map((i) => i.box)), 0);
25775
+ }
25776
+ /** Up to this number of nodes, `silhouette` examines each node. Above it, two seeded samples of this size give an estimate. */
25777
+ const SILHOUETTE_NODES = 2e3;
25778
+ /**
25779
+ * This function gives the silhouette of the groups of one level (G7 of MEAS0, Rousseeuw 1987): the mean of `s(v) = (b_v - a_v) / max(a_v, b_v)`.
25780
+ * `a_v` is the mean distance from the center of `v` to the centers of the other nodes of its item. `b_v` is the smallest mean distance to the nodes of another item.
25781
+ * A node alone in its item gets `s(v) = 0`. The scale is -1 to 1, and higher is better.
25782
+ * A level with fewer than 2 items gives `undefined`. The cost is quadratic up to 2000 nodes.
25783
+ * Above 2000 nodes, a seeded sample of 2000 nodes gets `s(v)`, against a second seeded sample of 2000 nodes. Thus the value is an estimate.
25784
+ */
25785
+ function silhouette(graph, level) {
25786
+ const at = new Map(graph.nodes.map((n) => [n.id, center(n.box)]));
25787
+ const all = level.items.flatMap((item, k) => item.nodes.flatMap((v) => at.has(v) ? [{
25788
+ p: at.get(v),
25789
+ k
25790
+ }] : []));
25791
+ if (new Set(all.map((x) => x.k)).size < 2) return void 0;
25792
+ const R = rng(all.length);
25793
+ const sample = () => all.length <= SILHOUETTE_NODES ? all : Array.from({ length: SILHOUETTE_NODES }, () => all[Math.floor(R() * all.length)]);
25794
+ const chosen = sample();
25795
+ const ref = sample();
25796
+ const groups = /* @__PURE__ */ new Map();
25797
+ for (const { p, k } of ref) {
25798
+ const g = groups.get(k);
25799
+ if (g) g.push(p);
25800
+ else groups.set(k, [p]);
25801
+ }
25802
+ let sum = 0;
25803
+ for (const { p, k } of chosen) {
25804
+ const own = groups.get(k);
25805
+ if (!own || own.length < 2) continue;
25806
+ let a = 0;
25807
+ let b = Number.POSITIVE_INFINITY;
25808
+ for (const [j, g] of groups) {
25809
+ const d = g.reduce((s, q) => s + dist(p, q), 0);
25810
+ if (j === k) a = d / (g.length - 1);
25811
+ else b = Math.min(b, d / g.length);
25812
+ }
25813
+ if (!Number.isFinite(b)) continue;
25814
+ const m = Math.max(a, b);
25815
+ sum += m > 0 ? (b - a) / m : 0;
25816
+ }
25817
+ return sum / chosen.length;
25818
+ }
25443
25819
  // src/bench/measures/stability.ts
25444
25820
  /**
25445
25821
  * This function makes a seeded small change of a graph: one node moves by `boardUnit`, one edge goes and one new edge comes.
@@ -25570,6 +25946,108 @@ function roomOrderFaults(before, after, snap = .5) {
25570
25946
  }
25571
25947
  return Math.round(faults * (n * (n - 1)) / 2 / Math.max(1, seen));
25572
25948
  }
25949
+ /**
25950
+ * This function gives the displacement of the boxes (M3 of MEAS0, Bridgeman 2000): `mean_v dist(box_open(v), box_packed(v)) / u`.
25951
+ * The distance is between the centers of the two boxes of a node, and `u` is the unit of the packed board.
25952
+ * The value has no unit, and lower is better. A board where no box moves gives 0. The cost is linear.
25953
+ */
25954
+ function boxDisplacement(before, after, unit) {
25955
+ const next = new Map(after.map((b) => [b.id, b.box]));
25956
+ let sum = 0;
25957
+ let n = 0;
25958
+ for (const b of before) {
25959
+ const a = next.get(b.id);
25960
+ if (!a) continue;
25961
+ sum += dist(center(b.box), center(a));
25962
+ n++;
25963
+ }
25964
+ return n === 0 ? 0 : sum / n / (unit > 0 ? unit : 1);
25965
+ }
25966
+ /** The number of nearest boxes of each box that make the neighbour pairs of `distanceDistortion`. */
25967
+ const DISTORTION_NEIGHBOURS = 4;
25968
+ /**
25969
+ * This function gives the distance distortion of the boxes (M5 of MEAS0): the Kruskal stress of the centers.
25970
+ * `σ = sqrt(Σ (α d'_ij - d_ij)^2 / Σ d_ij^2)`, with `d` the distance on the packed board, `d'` the distance on the open board, and `α = Σ d d' / Σ d'^2` the best scale.
25971
+ * The pairs are each box and its 4 nearest boxes on the packed board. The scale is 0 to 1, and lower is better.
25972
+ * A board that only changes its scale gives 0. The cost is `n log n` for an even density.
25973
+ */
25974
+ function distanceDistortion(before, after) {
25975
+ const next = new Map(after.map((b) => [b.id, center(b.box)]));
25976
+ const list = before.flatMap((b) => next.has(b.id) ? [{
25977
+ p: center(b.box),
25978
+ q: next.get(b.id)
25979
+ }] : []);
25980
+ const pairs = neighbourPairs(list.map((x) => x.p), 4);
25981
+ let dd = 0;
25982
+ let qq = 0;
25983
+ let pp = 0;
25984
+ const ds = [];
25985
+ for (const [i, j] of pairs) {
25986
+ const d = dist(list[i].p, list[j].p);
25987
+ const e = dist(list[i].q, list[j].q);
25988
+ ds.push([d, e]);
25989
+ dd += d * e;
25990
+ qq += e * e;
25991
+ pp += d * d;
25992
+ }
25993
+ if (pp === 0) return 0;
25994
+ const alpha = qq > 0 ? dd / qq : 0;
25995
+ const res = ds.reduce((s, [d, e]) => s + (alpha * e - d) ** 2, 0);
25996
+ return Math.sqrt(res / pp);
25997
+ }
25998
+ /** This function gives each pair of a point and one of its `k` nearest points one time, as `[i, j]` with `i < j`. A grid of buckets finds the points. */
25999
+ function neighbourPairs(points, k) {
26000
+ const n = points.length;
26001
+ if (n < 2) return [];
26002
+ let x0 = Number.POSITIVE_INFINITY;
26003
+ let y0 = Number.POSITIVE_INFINITY;
26004
+ let x1 = Number.NEGATIVE_INFINITY;
26005
+ let y1 = Number.NEGATIVE_INFINITY;
26006
+ for (const p of points) {
26007
+ x0 = Math.min(x0, p.x);
26008
+ y0 = Math.min(y0, p.y);
26009
+ x1 = Math.max(x1, p.x);
26010
+ y1 = Math.max(y1, p.y);
26011
+ }
26012
+ const cell = Math.max(1e-9, Math.sqrt(((x1 - x0) * (y1 - y0) || 1) / n) * 2);
26013
+ const key = (cx, cy) => `${cx},${cy}`;
26014
+ const buckets = /* @__PURE__ */ new Map();
26015
+ points.forEach((p, i) => {
26016
+ const kk = key(Math.floor((p.x - x0) / cell), Math.floor((p.y - y0) / cell));
26017
+ const list = buckets.get(kk);
26018
+ if (list) list.push(i);
26019
+ else buckets.set(kk, [i]);
26020
+ });
26021
+ const span = Math.ceil(Math.max(x1 - x0, y1 - y0) / cell) + 1;
26022
+ const seen = /* @__PURE__ */ new Set();
26023
+ const out = [];
26024
+ points.forEach((p, i) => {
26025
+ const cx = Math.floor((p.x - x0) / cell);
26026
+ const cy = Math.floor((p.y - y0) / cell);
26027
+ let near = [];
26028
+ for (let ring = 0; ring <= span; ring++) {
26029
+ for (let gx = cx - ring; gx <= cx + ring; gx++) for (let gy = cy - ring; gy <= cy + ring; gy++) {
26030
+ if (Math.max(Math.abs(gx - cx), Math.abs(gy - cy)) !== ring) continue;
26031
+ for (const j of buckets.get(key(gx, gy)) ?? []) if (j !== i) near.push({
26032
+ j,
26033
+ d: dist(p, points[j])
26034
+ });
26035
+ }
26036
+ if (near.length >= Math.min(k, n - 1)) {
26037
+ near.sort((a, b) => a.d - b.d || a.j - b.j);
26038
+ if (near[Math.min(k, n - 1) - 1].d <= ring * cell) break;
26039
+ }
26040
+ }
26041
+ near = near.sort((a, b) => a.d - b.d || a.j - b.j).slice(0, k);
26042
+ for (const { j } of near) {
26043
+ const pk = i < j ? `${i},${j}` : `${j},${i}`;
26044
+ if (seen.has(pk)) continue;
26045
+ seen.add(pk);
26046
+ out.push(i < j ? [i, j] : [j, i]);
26047
+ }
26048
+ });
26049
+ return out;
26050
+ }
25573
26051
  // src/bench/measures/pareto.ts
25574
26052
  /** This function gives the value of a measure as a cost: lower is better. A value that is not a number is the worst cost. */
25575
26053
  function cost(values, o) {
@@ -26117,6 +26595,128 @@ function laneArea(graph, open) {
26117
26595
  }
26118
26596
  return (x1 - x0) * (y1 - y0) / packed;
26119
26597
  }
26598
+ // src/bench/measures/symmetry.ts
26599
+ const EPS$1 = 1e-9;
26600
+ /** This function gives the corners of a polyline: the points without repeats and without inner points on a straight line. */
26601
+ function cornersOf(points) {
26602
+ const q = [];
26603
+ for (const v of points) if (q.length === 0 || dist(q[q.length - 1], v) > EPS$1) q.push(v);
26604
+ const out = [];
26605
+ for (let i = 0; i < q.length; i++) {
26606
+ const v = q[i];
26607
+ if (i > 0 && i + 1 < q.length) {
26608
+ const a = out[out.length - 1];
26609
+ const c = q[i + 1];
26610
+ const cr = (v.x - a.x) * (c.y - v.y) - (v.y - a.y) * (c.x - v.x);
26611
+ const dt = (v.x - a.x) * (c.x - v.x) + (v.y - a.y) * (c.y - v.y);
26612
+ if (Math.abs(cr) <= EPS$1 * Math.max(1, dist(a, v) * dist(v, c)) && dt > 0) continue;
26613
+ }
26614
+ out.push(v);
26615
+ }
26616
+ return out;
26617
+ }
26618
+ /** This function tells if two lists of corners are the same, point by point, within `tol`. */
26619
+ const sameCorners = (a, b, tol) => a.length === b.length && a.every((p, i) => dist(p, b[i]) <= tol);
26620
+ /**
26621
+ * This function gives the symmetry of the drawing (H3 of MEAS0). The value is the share of the routes that the best map of the board sends onto a route.
26622
+ * The maps are the two mirrors on the axes and the half turn. Each one goes through the center of the boxes.
26623
+ *
26624
+ * A route of the edge `(s, t)` maps when two things are true. The map sends the centers of `s` and `t` to the centers of the nodes `s'` and `t'`, within `u / 4`.
26625
+ * A route of an edge between `s'` and `t'` has the same corners as the mapped route, within `u / 4`. `u` is the unit of the board.
26626
+ *
26627
+ * The scale is 0 to 1, and higher is better. A router keeps the symmetry that the boxes have, or loses it. Routes without points give `undefined`.
26628
+ * The cost is linear in the points of the routes.
26629
+ */
26630
+ function routeSymmetry(graph, routes) {
26631
+ const drawn = routes.filter((r) => r.points.length >= 2);
26632
+ if (drawn.length === 0 || graph.nodes.length === 0) return void 0;
26633
+ const tol = boardUnit(graph) / 4;
26634
+ let x0 = Number.POSITIVE_INFINITY;
26635
+ let y0 = Number.POSITIVE_INFINITY;
26636
+ let x1 = Number.NEGATIVE_INFINITY;
26637
+ let y1 = Number.NEGATIVE_INFINITY;
26638
+ for (const n of graph.nodes) {
26639
+ x0 = Math.min(x0, n.box.x);
26640
+ y0 = Math.min(y0, n.box.y);
26641
+ x1 = Math.max(x1, n.box.x + n.box.w);
26642
+ y1 = Math.max(y1, n.box.y + n.box.h);
26643
+ }
26644
+ const cx = (x0 + x1) / 2;
26645
+ const cy = (y0 + y1) / 2;
26646
+ const maps = [
26647
+ (p) => ({
26648
+ x: 2 * cx - p.x,
26649
+ y: p.y
26650
+ }),
26651
+ (p) => ({
26652
+ x: p.x,
26653
+ y: 2 * cy - p.y
26654
+ }),
26655
+ (p) => ({
26656
+ x: 2 * cx - p.x,
26657
+ y: 2 * cy - p.y
26658
+ })
26659
+ ];
26660
+ const cellOf = (p) => [Math.floor(p.x / tol), Math.floor(p.y / tol)];
26661
+ const cells = /* @__PURE__ */ new Map();
26662
+ const at = /* @__PURE__ */ new Map();
26663
+ for (const n of graph.nodes) {
26664
+ const c = center(n.box);
26665
+ at.set(n.id, c);
26666
+ const [i, j] = cellOf(c);
26667
+ const k = `${i},${j}`;
26668
+ const list = cells.get(k);
26669
+ if (list) list.push({
26670
+ id: n.id,
26671
+ at: c
26672
+ });
26673
+ else cells.set(k, [{
26674
+ id: n.id,
26675
+ at: c
26676
+ }]);
26677
+ }
26678
+ const nodeAt = (p) => {
26679
+ const [i, j] = cellOf(p);
26680
+ let best;
26681
+ for (let a = i - 1; a <= i + 1; a++) for (let b = j - 1; b <= j + 1; b++) for (const n of cells.get(`${a},${b}`) ?? []) {
26682
+ const d = dist(n.at, p);
26683
+ if (d <= tol && (best === void 0 || d < best.d || d === best.d && n.id < best.id)) best = {
26684
+ id: n.id,
26685
+ d
26686
+ };
26687
+ }
26688
+ return best?.id;
26689
+ };
26690
+ const ends = new Map(graph.edges.map((e) => [e.id, [e.source.node, e.target.node]]));
26691
+ const byPair = /* @__PURE__ */ new Map();
26692
+ const corners = drawn.map((r) => cornersOf(r.points));
26693
+ drawn.forEach((r, i) => {
26694
+ const e = ends.get(r.edge);
26695
+ if (!e) return;
26696
+ const k = `${e[0]}\u0000${e[1]}`;
26697
+ const list = byPair.get(k);
26698
+ if (list) list.push(corners[i]);
26699
+ else byPair.set(k, [corners[i]]);
26700
+ });
26701
+ let best = 0;
26702
+ for (const m of maps) {
26703
+ let hits = 0;
26704
+ drawn.forEach((r, i) => {
26705
+ const e = ends.get(r.edge);
26706
+ if (!e) return;
26707
+ const a = at.get(e[0]);
26708
+ const b = at.get(e[1]);
26709
+ const s = a && nodeAt(m(a));
26710
+ const t = b && nodeAt(m(b));
26711
+ if (s === void 0 || t === void 0) return;
26712
+ const mapped = corners[i].map(m);
26713
+ const back = [...mapped].reverse();
26714
+ if ((byPair.get(`${s}\u0000${t}`) ?? []).some((c) => sameCorners(mapped, c, tol)) || (byPair.get(`${t}\u0000${s}`) ?? []).some((c) => sameCorners(back, c, tol))) hits++;
26715
+ });
26716
+ best = Math.max(best, hits / drawn.length);
26717
+ }
26718
+ return best;
26719
+ }
26120
26720
  // src/bench/suite.ts
26121
26721
  const now$1 = () => globalThis.performance?.now() ?? Date.now();
26122
26722
  /** The names of the proposed hard rules of MEAS0. The suite counts them. It does not apply them as rules yet. */
@@ -26287,7 +26887,7 @@ function routeMeasures(graph, result, row) {
26287
26887
  ...levelMeasures(graph, result)
26288
26888
  };
26289
26889
  }
26290
- /** This function draws the routes of a result with each curve strategy and measures the curves (C1, C2, C3, C6 and the curve rules). */
26890
+ /** This function draws the routes of a result with each curve strategy and measures the curves (C1, C2, C3, C4, C6, C8 and the curve rules). */
26291
26891
  function curveRuns(graph, result, curves, options) {
26292
26892
  const byId = nodeIndex(graph);
26293
26893
  const edges = new Map(graph.edges.map((e) => [e.id, e]));
@@ -26332,6 +26932,8 @@ function curveRuns(graph, result, curves, options) {
26332
26932
  const g1 = [];
26333
26933
  const g2 = [];
26334
26934
  const energy = [];
26935
+ const variation = [];
26936
+ const deviation = [];
26335
26937
  const rules = {
26336
26938
  "curve-clear": 0,
26337
26939
  "curve-ends": 0,
@@ -26345,6 +26947,8 @@ function curveRuns(graph, result, curves, options) {
26345
26947
  g1.push(g1Defect(segments));
26346
26948
  g2.push(g2Defect(segments, u));
26347
26949
  energy.push(bendingEnergy(s, .25));
26950
+ variation.push(curvatureVariation(s, .25));
26951
+ deviation.push(curveDeviation(s, r.points, u / 4) / u);
26348
26952
  length += polylineLength(s);
26349
26953
  const normals = options.normals.has(curve.name);
26350
26954
  const f = curveFaults({
@@ -26371,6 +26975,10 @@ function curveRuns(graph, result, curves, options) {
26371
26975
  "C2.max": g2.reduce((m, x) => Math.max(m, x), 0),
26372
26976
  "C3.median": quantile(energy, .5) || 0,
26373
26977
  "C3.q95": quantile(energy, .95) || 0,
26978
+ "C4.median": quantile(variation, .5) || 0,
26979
+ "C4.q95": quantile(variation, .95) || 0,
26980
+ "C8.median": quantile(deviation, .5) || 0,
26981
+ "C8.max": deviation.reduce((m, x) => Math.max(m, x), 0),
26374
26982
  "C6.length": ink > 0 ? length / ink : 1,
26375
26983
  ...rules
26376
26984
  };
@@ -27261,7 +27869,17 @@ function stepRouterStrategy(strategy) {
27261
27869
  level: strategy,
27262
27870
  levelOptions: o
27263
27871
  }).route(graph));
27264
- return own(strategy.maxNodes, (graph, o) => strategy.run(graph, o).result);
27872
+ return own(strategy.maxNodes, (graph, o) => {
27873
+ const out = strategy.run(graph, o);
27874
+ WHOLE_BOARDS.set(out.result, out.graph);
27875
+ return out.result;
27876
+ });
27877
+ }
27878
+ /** The board of the output of each whole strategy that `stepRouterStrategy` runs, by its result. The screen reads it for the measures of the moved boxes. */
27879
+ const WHOLE_BOARDS = /* @__PURE__ */ new WeakMap();
27880
+ /** This function gives the board of the output of a whole strategy that `stepRouterStrategy` ran, or `undefined` for another result. A room strategy can move the boxes of this board. */
27881
+ function wholeBoard(result) {
27882
+ return WHOLE_BOARDS.get(result);
27265
27883
  }
27266
27884
  /** This function gives a router for each strategy of one step of `RoutedStep` in a registry, in the order of the registry (`stepRouterStrategy`). */
27267
27885
  function stepRouters(registry, step) {
@@ -27454,6 +28072,7 @@ function graphInput(graph, head) {
27454
28072
  h: num(n.box.h),
27455
28073
  x: num(n.box.x),
27456
28074
  y: num(n.box.y),
28075
+ ...n.parent !== void 0 ? { parent: n.parent } : {},
27457
28076
  ...n.ports ? { ports: n.ports.map((p) => portToFile(p, n)) } : {},
27458
28077
  ...n.data ? { data: n.data } : {}
27459
28078
  }));
@@ -27519,7 +28138,7 @@ function regionBounds(region) {
27519
28138
  };
27520
28139
  }
27521
28140
  /**
27522
- * This function reads the graph of the router from an input file. The router reads the positions, the ports, the edges and the data.
28141
+ * This function reads the graph of the router from an input file. The router reads the positions, the ports, the parents, the edges and the data.
27523
28142
  * Each obstacle becomes a node without edges, with the bounding box of its region, after the boxes.
27524
28143
  * The router ignores the other fields of the packer: `container`, `gap`, `locked`, `rank`, the `shape` of a box and the `move` of an obstacle.
27525
28144
  * A box without `x` and `y` is an error.
@@ -27536,7 +28155,8 @@ function inputGraph(input) {
27536
28155
  h: b.h
27537
28156
  },
27538
28157
  ...b.ports ? { ports: b.ports.map(portToModel) } : {},
27539
- ...b.data ? { data: b.data } : {}
28158
+ ...b.data ? { data: b.data } : {},
28159
+ ...b.parent !== void 0 ? { parent: b.parent } : {}
27540
28160
  };
27541
28161
  });
27542
28162
  for (const o of input.obstacles ?? []) nodes.push({
@@ -28032,6 +28652,42 @@ const MEASURES = Object.freeze([
28032
28652
  "lower",
28033
28653
  true
28034
28654
  ],
28655
+ [
28656
+ "M2.order",
28657
+ "M2",
28658
+ "route.room.order",
28659
+ "count",
28660
+ "lower",
28661
+ false,
28662
+ "The number of pairs of boxes whose order or alignment on an axis changes when the boxes move. 0 is correct."
28663
+ ],
28664
+ [
28665
+ "M3.displacement",
28666
+ "M3",
28667
+ "route.room.displacement",
28668
+ "no unit",
28669
+ "lower",
28670
+ false,
28671
+ "The mean distance that the center of a box moves, divided by the unit of the board."
28672
+ ],
28673
+ [
28674
+ "M5.stress",
28675
+ "M5",
28676
+ "route.room.distortion",
28677
+ "0 to 1",
28678
+ "lower",
28679
+ false,
28680
+ "The stress of the distances from each box to its 4 nearest boxes, after the best change of scale. 0 is a board that only changes its scale."
28681
+ ],
28682
+ [
28683
+ "H3.share",
28684
+ "H3",
28685
+ "route.symmetry",
28686
+ "0 to 1",
28687
+ "higher",
28688
+ true,
28689
+ "The share of the routes that a mirror or a half turn of the board sends onto a route, for the best of the three maps."
28690
+ ],
28035
28691
  [
28036
28692
  "W3.crossings",
28037
28693
  "W3",
@@ -28224,6 +28880,33 @@ const MEASURES = Object.freeze([
28224
28880
  "higher",
28225
28881
  true
28226
28882
  ],
28883
+ [
28884
+ "Z1.countsSum",
28885
+ "Z1",
28886
+ "route.level.counts-sum",
28887
+ "count",
28888
+ "lower",
28889
+ false,
28890
+ "The faults of the hard rule `counts-sum`: a level whose items miss a node, or whose links miss an edge. 0 is correct."
28891
+ ],
28892
+ [
28893
+ "Z5.max",
28894
+ "Z5",
28895
+ "route.level.ratio.max",
28896
+ "ratio",
28897
+ "lower",
28898
+ true,
28899
+ "The largest ratio of the item counts of two levels in sequence. A large ratio is a sudden jump of the zoom."
28900
+ ],
28901
+ [
28902
+ "Z5.spread",
28903
+ "Z5",
28904
+ "route.level.ratio.spread",
28905
+ "no unit",
28906
+ "lower",
28907
+ true,
28908
+ "The standard deviation of the log of the ratios of the item counts of the levels. 0 is a zoom with steps of equal size."
28909
+ ],
28227
28910
  [
28228
28911
  "G1.modularity",
28229
28912
  "G1",
@@ -28264,6 +28947,33 @@ const MEASURES = Object.freeze([
28264
28947
  "higher",
28265
28948
  true
28266
28949
  ],
28950
+ [
28951
+ "G4.fill",
28952
+ "G4",
28953
+ "route.group.fill",
28954
+ "0 to 1",
28955
+ "higher",
28956
+ true,
28957
+ "The mean share of the box of a group of the far view that the boxes of its nodes fill. A full group box is 1."
28958
+ ],
28959
+ [
28960
+ "G5.overlaps",
28961
+ "G5",
28962
+ "route.group.overlaps",
28963
+ "count",
28964
+ "lower",
28965
+ true,
28966
+ "The number of pairs of items of one level whose boxes overlap. 0 is correct."
28967
+ ],
28968
+ [
28969
+ "G7.silhouette",
28970
+ "G7",
28971
+ "route.group.silhouette",
28972
+ "-1 to 1",
28973
+ "higher",
28974
+ true,
28975
+ "The mean silhouette of the nodes of the far view: how much nearer a node is to its group than to the nearest other group."
28976
+ ],
28267
28977
  [
28268
28978
  "C1.max",
28269
28979
  "C1",
@@ -28312,6 +29022,42 @@ const MEASURES = Object.freeze([
28312
29022
  "lower",
28313
29023
  false
28314
29024
  ],
29025
+ [
29026
+ "C4.median",
29027
+ "C4",
29028
+ "route.curve.c4.median",
29029
+ "no unit",
29030
+ "lower",
29031
+ false,
29032
+ "The median curvature variation of the curves, without a unit. A circle or a straight line is 0. A sudden change of the curvature is large."
29033
+ ],
29034
+ [
29035
+ "C4.q95",
29036
+ "C4",
29037
+ "route.curve.c4.q95",
29038
+ "no unit",
29039
+ "lower",
29040
+ false,
29041
+ "The 0.95 quantile of the curvature variation of the curves. It shows the worst curves."
29042
+ ],
29043
+ [
29044
+ "C8.median",
29045
+ "C8",
29046
+ "route.curve.c8.median",
29047
+ "no unit",
29048
+ "lower",
29049
+ false,
29050
+ "The median Fréchet distance of a curve from the polyline of its route, divided by the unit of the board."
29051
+ ],
29052
+ [
29053
+ "C8.max",
29054
+ "C8",
29055
+ "route.curve.c8.max",
29056
+ "no unit",
29057
+ "lower",
29058
+ false,
29059
+ "The largest Fréchet distance of a curve from the polyline of its route, divided by the unit of the board."
29060
+ ],
28315
29061
  [
28316
29062
  "C5.peak",
28317
29063
  "C5",
@@ -28360,7 +29106,7 @@ const MEASURES = Object.freeze([
28360
29106
  "lower",
28361
29107
  false
28362
29108
  ]
28363
- ].map(([key, id, name, unit, better, placementDependent]) => {
29109
+ ].map(([key, id, name, unit, better, placementDependent, definition]) => {
28364
29110
  const rule = MIN_EFFECT_BY_UNIT.get(unit);
28365
29111
  if (!rule) throw new Error(`MEASURES: the unit "${unit}" of ${key} has no rule in MIN_EFFECTS`);
28366
29112
  const info = {
@@ -28369,7 +29115,8 @@ const MEASURES = Object.freeze([
28369
29115
  name,
28370
29116
  unit,
28371
29117
  better,
28372
- placementDependent
29118
+ placementDependent,
29119
+ ...definition === void 0 ? {} : { definition }
28373
29120
  };
28374
29121
  return Object.freeze(rule.minEffect === void 0 ? info : {
28375
29122
  ...info,
@@ -30502,11 +31249,29 @@ function screenRegistry(step, strategies, baseline) {
30502
31249
  }
30503
31250
  /** This function tells if a step of the screen gives routes. The step `router` gives routes, and each step of `RoutedStep` gives routes as a router. */
30504
31251
  const givesRoutes = (step) => step === "router" || isRoutedStep(step);
31252
+ /** This function gives the measures of the shape of the groups and of the levels: G4, G5, G7 and Z5. A value without its data is not in the result. */
31253
+ function groupShapeValues(graph, levels) {
31254
+ const values = {};
31255
+ if (levels.length === 0) return values;
31256
+ values["G5.overlaps"] = itemOverlaps(levels);
31257
+ const far = farLevel(levels);
31258
+ const fill = far && groupFill(graph, far);
31259
+ if (fill !== void 0) values["G4.fill"] = fill;
31260
+ const sil = far && silhouette(graph, far);
31261
+ if (sil !== void 0) values["G7.silhouette"] = sil;
31262
+ const ratio = levelRatio(levels);
31263
+ if (ratio) {
31264
+ values["Z5.max"] = ratio.max;
31265
+ values["Z5.spread"] = ratio.spread;
31266
+ }
31267
+ return values;
31268
+ }
30505
31269
  /**
30506
31270
  * This function gives the values of a row of `compareStep`, with the measures of `measureQuality` for a router.
30507
31271
  * A router also gets the bundled share (B2), the merge angle (B6) and the bundle stretch (B3) against the routes `base` of `corners`.
30508
31272
  * It also gets the measures of MEAS3A: R12, R14, B5, B7, B8, B9, M1 and M4. The lane measures open each shared segment at `MEASURE_LANE_WIDTH`.
30509
- * A result with zoom levels also gets the link agreement (Z9), for a router and for a grouping rule.
31273
+ * A result with zoom levels also gets the link agreement (Z9) and `groupShapeValues`, for a router and for a grouping rule.
31274
+ * A router also gets the symmetry of its routes (H3). A whole strategy also gets M2, M3 and M5 of the board of its output.
30510
31275
  */
30511
31276
  function rowValues(step, graph, row, base) {
30512
31277
  const values = { ...row.values };
@@ -30540,10 +31305,20 @@ function rowValues(step, graph, row, base) {
30540
31305
  }
30541
31306
  values["M1.area"] = laneArea(graph, open);
30542
31307
  values["M4.laneThrough"] = lanesThrough(graph, open);
31308
+ const h3 = routeSymmetry(graph, routes);
31309
+ if (h3 !== void 0) values["H3.share"] = h3;
30543
31310
  }
30544
31311
  if ((givesRoutes(step) || step === "group") && row.result?.levels) {
30545
- const la = linkAgreement(graph, row.result.levels, row.result.routes, boardUnit(graph));
31312
+ const levels = row.result.levels;
31313
+ const la = linkAgreement(graph, levels, row.result.routes, boardUnit(graph));
30546
31314
  if (la !== void 0) values["Z9.mean"] = la;
31315
+ Object.assign(values, groupShapeValues(graph, levels));
31316
+ }
31317
+ if (step === "whole" && row.result) {
31318
+ const board = wholeBoard(row.result) ?? graph;
31319
+ values["M2.order"] = roomOrderFaults(graph.nodes, board.nodes);
31320
+ values["M3.displacement"] = boxDisplacement(graph.nodes, board.nodes, boardUnit(graph));
31321
+ values["M5.stress"] = distanceDistortion(graph.nodes, board.nodes);
30547
31322
  }
30548
31323
  return values;
30549
31324
  }
@@ -30730,6 +31505,7 @@ function screen(step, strategies = [], options = {}) {
30730
31505
  const { hard, deterministic } = rowRules(step, graph, row, again?.rows[i]);
30731
31506
  const values = rowValues(step, graph, row, cornersBase);
30732
31507
  if (hard["link-ends"] !== void 0) values["Z2.linkEnds"] = hard["link-ends"];
31508
+ if (hard["counts-sum"] !== void 0) values["Z1.countsSum"] = hard["counts-sum"];
30733
31509
  if (optima && withOptima.has(input.id) && row.result) Object.assign(values, gapValues(input, graph, row.result, cornersBase(), gapBase(), optima));
30734
31510
  if (shuffles > 0 && row.result) {
30735
31511
  const router = asRouter(strategy, own[row.strategy]);
@@ -31962,6 +32738,6 @@ const sharedStretchStrategy = defineStrategy({
31962
32738
  name: "shared-stretch"
31963
32739
  }).route(graph)
31964
32740
  });
31965
- 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, 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, 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, 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, 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, curveFaults, curveJoints, curveOf, curveRuns, curveSpace, curveStrategies, curvesHash, cutGroups, cutShare, declaresFamily, defineStrategy, denseGroupsBoard, densityPlan, densityRidges, depthLevels, detourSpread, diffusionPlan, dimensionAt, dimensionUnit, dist, 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, 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, frechet, frechetBundles, frechetPlan, freeLines, g1Defect, g2Defect, gapBaselines, gapCap, gapClosed, gapLaneCounts, gapLanes, gapNames, gapPlan, gapValues, gateStrategy, getStrategy, goesOutAndBack, graphInput, greedyBicliques, greedyCrossbars, groupEdges, 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, 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, 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, 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, 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, skeletonGroups, skeletonPath, slackSettings, smoothCurve, snapPlan, softDistance, softKeyClusters, softKeyPlan, spanningTree, spectralClusters, spectralLabels, spectralPlan, spinePart, spiralJoin, spiralPlan, spiralTree, splineCurve, splitPath, splitPoint, 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, wholeStrategies, withBends, withRepair, withStableOrder, wspdGroups, wspdPlan, zipLegs, zipperPlan };
32741
+ 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, 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, 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, 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 };
31966
32742
 
31967
32743
  //# sourceMappingURL=index.js.map