@mark1russell7/highways 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +112 -8
  2. package/build/index.d.ts +989 -187
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +9761 -6738
  5. package/build/index.js.map +1 -1
  6. package/package.json +3 -2
  7. package/src/bench/compare.ts +174 -5
  8. package/src/bench/room.ts +1 -1
  9. package/src/bench/run.ts +38 -3
  10. package/src/bench/screen/exercise.ts +5 -0
  11. package/src/bench/screen/forms.ts +5 -1
  12. package/src/bench/screen/generators.ts +11 -41
  13. package/src/bench/screen/screen.ts +16 -10
  14. package/src/bench/screen/set.ts +156 -49
  15. package/src/bench/suite.ts +110 -12
  16. package/src/cases.ts +64 -18
  17. package/src/families/cases.ts +108 -0
  18. package/src/families/fixtures.ts +101 -0
  19. package/src/families/index.ts +36 -0
  20. package/src/families/model.ts +55 -0
  21. package/src/fixtures.ts +8 -2
  22. package/src/highways/fixtures.test-util.ts +13 -0
  23. package/src/highways/follow.ts +3 -2
  24. package/src/highways/free-lines.ts +141 -0
  25. package/src/highways/gaps/index.ts +85 -0
  26. package/src/highways/index.ts +6 -0
  27. package/src/highways/interchanges/index.ts +231 -0
  28. package/src/highways/search/index.ts +170 -0
  29. package/src/highways/snap.ts +4 -2
  30. package/src/highways/soft-keys/index.ts +8 -4
  31. package/src/highways/steiner/index.ts +215 -0
  32. package/src/highways/streets/index.ts +78 -0
  33. package/src/index.ts +8 -0
  34. package/src/lane-order.ts +37 -0
  35. package/src/lane-orders/stretch/index.ts +486 -0
  36. package/src/lane-orders/stretch/router.ts +22 -0
  37. package/src/levels/depths/index.ts +57 -0
  38. package/src/model.ts +60 -4
  39. package/src/plugs/facing/index.ts +22 -0
  40. package/src/portfolio/router.ts +44 -0
  41. package/src/routers/attraction/index.ts +61 -0
  42. package/src/routers/corners.ts +31 -7
  43. package/src/routers/index.ts +2 -1
  44. package/src/routers/plugs.ts +3 -2
  45. package/src/routers/sections.ts +25 -42
  46. package/src/routers/shared.ts +96 -14
  47. package/src/routers/straight.ts +3 -2
  48. package/src/routers/trunks.ts +24 -5
  49. package/src/segments.ts +4 -25
  50. package/src/strategies/builtins.ts +109 -2
  51. package/src/strategies/model.ts +55 -10
  52. package/src/strategies/options.ts +36 -4
  53. package/src/strategies/schemas.ts +124 -5
  54. package/src/whole/pipeline/index.ts +66 -0
@@ -1,13 +1,15 @@
1
1
  import { center, dist, type Point } from "../geom.ts";
2
- import { nodeIndex, type Edge, type Graph, type GroupRule, type Result, type Route, type Router, type SharedRun } from "../model.ts";
2
+ import { nodeIndex, resolvePorts, type Edge, type Graph, type GroupRule, type Result, type Route, type Router, type SharedRun } from "../model.ts";
3
3
  import { channelWidth } from "../channels.ts";
4
4
  import { orderRun } from "../lane-order.ts";
5
+ import { stretchLaneOrders, type LaneStretchOrder } from "../lane-orders/stretch/index.ts";
5
6
  import { byText } from "../compare.ts";
6
7
  import { placePlug, type Plug } from "../plugs.ts";
7
8
  import { buildSections, type Section } from "../sections.ts";
8
9
  import { orderLanes, type RunOrder } from "../segments.ts";
9
10
  import { WayFinder, type WayOptions } from "../ways.ts";
10
11
  import { snellMerges } from "./snell.ts";
12
+ import { groupRuleOf, type GroupRuleName } from "./trunks.ts";
11
13
 
12
14
  /** The cost of a link that edges of one key share: a fixed discount, or the flow cost `len * f^alpha` (EDA-029). */
13
15
  export type ShareCost = "fixed" | "flow";
@@ -22,12 +24,22 @@ export type ShareMerge = "corner" | "snell";
22
24
  export interface SharedOptions extends WayOptions {
23
25
  /** The name in the benchmark. */
24
26
  readonly name: string;
25
- /** The edges with one key share their ways. Without a rule, all edges share. An edge without a key shares with no edge. */
26
- readonly rule?: GroupRule;
27
+ /**
28
+ * The edges with one key share their ways. The option takes a rule or the name of a built-in rule (`GROUP_RULE_NAMES`).
29
+ * Without a rule, or with `all`, all edges share. An edge without a key shares with no edge.
30
+ */
31
+ readonly rule?: GroupRule | GroupRuleName;
27
32
  /** The cost of a link that an edge with the same key uses already, as a share of its length. The default is 0.5. */
28
33
  readonly discount?: number;
29
34
  /** The lane order of the runs. With `bundle`, the default, two edges keep one order on each run of their common stretch. With `run`, each run gets its order alone. */
30
35
  readonly laneOrder?: RunOrder;
36
+ /**
37
+ * The lane order of each stretch of runs (LANE-STRETCH). With `none`, the default, each run gets its order alone.
38
+ * With `consensus`, `periphery` or `sifting`, the runs that follow each other on the routes share one order. Only the lane order `bundle` reads it.
39
+ */
40
+ readonly stretchOrder?: LaneStretchOrder;
41
+ /** The number of passes of the stretch order `sifting`, from 1 to 4. The default is 2. */
42
+ readonly siftPasses?: number;
31
43
  /**
32
44
  * The cost of a shared link. With `fixed`, the default, a link that the key uses already costs `discount` times its length.
33
45
  * With `flow`, one more edge on a link with `f` edges costs `len * ((f + 1)^alpha - f^alpha)` (EDA-029).
@@ -65,6 +77,21 @@ export interface SharedOptions extends WayOptions {
65
77
  readonly maxNodes?: number;
66
78
  }
67
79
 
80
+ /**
81
+ * The negotiation of the router `attraction` (HWY-044, PathFinder turned around). Each pass routes each edge again from no ways.
82
+ * A link costs `len (1 - attraction h) (1 + weight max(0, use - cap))` more than in `shared`, with `h` the history of the link.
83
+ */
84
+ export interface SharedNegotiation {
85
+ /** The number of passes, 1 or more. */
86
+ readonly passes: number;
87
+ /** The pull of the history of a link, from 0 to 1. */
88
+ readonly attraction: number;
89
+ /** The cost weight of each edge over the capacity of a link, 0 or more. */
90
+ readonly capacityWeight: number;
91
+ /** The width of one lane. The capacity of a link is its free width divided by this width, and 1 or more. */
92
+ readonly laneWidth: number;
93
+ }
94
+
68
95
  /** A way of one edge through the corner graph. */
69
96
  export interface Way {
70
97
  readonly edge: Edge;
@@ -102,7 +129,7 @@ function turn(from: Point, base: Point, to: Point): number {
102
129
  * Thus the edges of a key come together on common ways. Each stretch that several edges share has the same points in each route.
103
130
  * The result gives each stretch as a run, with the order of its lanes.
104
131
  */
105
- export function sharedRouter(options: SharedOptions): Router {
132
+ export function sharedRouter(options: SharedOptions, negotiation?: SharedNegotiation): Router {
106
133
  const discount = Math.min(1, Math.max(0.05, options.discount ?? 0.5));
107
134
  const flow = options.cost === "flow";
108
135
  const alpha = Math.min(1, Math.max(0.1, options.alpha ?? 0.6));
@@ -113,13 +140,16 @@ export function sharedRouter(options: SharedOptions): Router {
113
140
  const reuseExponent = Math.max(0, options.reuseExponent ?? 1);
114
141
  const capacity = Math.max(0, options.capacity ?? 0);
115
142
  const laneWidth = Math.max(0, options.laneWidth ?? 4);
143
+ // The name `all` is the same as no rule.
144
+ const rule = options.rule === undefined || options.rule === "all" ? undefined : groupRuleOf(options.rule);
116
145
  const keyOf = (e: Edge): string => {
117
- if (!options.rule) return "*";
118
- return options.rule(e) ?? `\u0000${e.id}`;
146
+ if (!rule) return "*";
147
+ return rule(e) ?? `\u0000${e.id}`;
119
148
  };
120
149
  const router: Router = {
121
150
  name: options.name,
122
- route(graph: Graph): Result {
151
+ route(input: Graph): Result {
152
+ const graph = resolvePorts(input);
123
153
  const byId = nodeIndex(graph);
124
154
  const ways = new WayFinder(graph.nodes, options);
125
155
  const corners = ways.corners;
@@ -158,8 +188,11 @@ export function sharedRouter(options: SharedOptions): Router {
158
188
  if (wd === undefined) widths.set(k, (wd = channelWidth(ways.obstacles, corners.points[i]!, corners.points[j]!)));
159
189
  return wd;
160
190
  };
161
- /** This function routes one way. `before` holds the edges of the key on each link in the pass before, for the option `reuse`. */
162
- const route = (w: Way, before?: ReadonlyMap<number, number>): void => {
191
+ /**
192
+ * This function routes one way. `before` holds the edges of the key on each link in the pass before, for the option `reuse`.
193
+ * `history` holds the history of each link of the key, for the negotiation of `attraction`.
194
+ */
195
+ const route = (w: Way, before?: ReadonlyMap<number, number>, history?: ReadonlyMap<number, number>): void => {
163
196
  let used = discounted.get(w.key);
164
197
  if (!used) discounted.set(w.key, (used = new Map()));
165
198
  const mine = used;
@@ -178,6 +211,20 @@ export function sharedRouter(options: SharedOptions): Router {
178
211
  cost = (i, j, d) => plain(i, j, d) / (1 + reuse * (before.get(link(i, j)) ?? 0)) ** reuseExponent;
179
212
  floor /= (1 + reuse * most) ** reuseExponent;
180
213
  }
214
+ if (negotiation !== undefined && history !== undefined) {
215
+ const plain = cost;
216
+ const a = negotiation.attraction;
217
+ const b = negotiation.capacityWeight;
218
+ let most = 0;
219
+ for (const h of history.values()) most = Math.max(most, h);
220
+ cost = (i, j, d) => {
221
+ const k = link(i, j);
222
+ const use = (mine.get(k)?.length ?? 0) + 1;
223
+ const cap = Math.max(1, Math.floor(widthOf(i, j) / negotiation.laneWidth));
224
+ return plain(i, j, d) * (1 - a * (history.get(k) ?? 0)) * (1 + b * Math.max(0, use - cap));
225
+ };
226
+ floor *= 1 - a * most;
227
+ }
181
228
  const way = corners.path(ways.out(w.ps), ways.out(w.pt), cost, floor);
182
229
  if (!way) return;
183
230
  w.found = true;
@@ -193,9 +240,39 @@ export function sharedRouter(options: SharedOptions): Router {
193
240
  if (flow) top.set(w.key, Math.max(top.get(w.key) ?? 0, mine.get(link(a, b))!.length));
194
241
  }
195
242
  };
196
- for (const w of order) route(w);
243
+ /**
244
+ * This function gives the passes of the negotiation (HWY-044). After each pass, the history of each link grows by `use / m`,
245
+ * with `m` the edges of the key, up to `0.9 / attraction`. Each later pass routes each edge again from no ways.
246
+ */
247
+ const negotiate = (list: readonly Way[]): void => {
248
+ const nego = negotiation!;
249
+ const histories = new Map<string, Map<number, number>>();
250
+ const size = new Map<string, number>();
251
+ for (const w of list) size.set(w.key, (size.get(w.key) ?? 0) + 1);
252
+ const most = nego.attraction > 0 ? 0.9 / nego.attraction : Infinity;
253
+ for (let pass = 0; pass < Math.max(1, Math.floor(nego.passes)); pass++) {
254
+ if (pass > 0) {
255
+ for (const [key, links] of discounted) {
256
+ let h = histories.get(key);
257
+ if (!h) histories.set(key, (h = new Map()));
258
+ const m = size.get(key) ?? 1;
259
+ for (const [k, users] of links) h.set(k, Math.min(most, (h.get(k) ?? 0) + users.length / m));
260
+ }
261
+ discounted.clear();
262
+ top.clear();
263
+ for (const w of list) {
264
+ w.found = false;
265
+ w.seq = [];
266
+ w.ids = [];
267
+ }
268
+ }
269
+ for (const w of list) route(w, undefined, histories.get(w.key) ?? new Map());
270
+ }
271
+ };
272
+ if (negotiation !== undefined) negotiate(order);
273
+ else for (const w of order) route(w);
197
274
  // Each later pass takes each edge off its links and finds its way again, with the ways of the other edges fixed.
198
- for (let pass = 1; pass < passes; pass++) {
275
+ for (let pass = 1; negotiation === undefined && pass < passes; pass++) {
199
276
  if (reuse > 0) {
200
277
  // Winding Roads (HWY-005): each edge finds its way again from no ways, and the busy links of the pass before pull it.
201
278
  const before = new Map<string, Map<number, number>>();
@@ -311,10 +388,15 @@ export function sharedRouter(options: SharedOptions): Router {
311
388
  for (const w of all) routes.push({ edge: w.edge.id, points: points.get(w.edge.id) ?? [w.ps.point, w.pt.point] });
312
389
  const shared: SharedRun[] = [];
313
390
  const routeOf = new Map(routes.map((r) => [r.edge, r]));
314
- for (const run of runs.values()) {
315
- const edges = options.laneOrder === "run" ? laneOrderOfRun(run) : orderLanes(run.points, run.ways.map((w) => routeOf.get(w.edge.id)!), "bundle");
391
+ const list = [...runs.values()];
392
+ const routesOf = (run: Run): Route[] => run.ways.map((w) => routeOf.get(w.edge.id)!);
393
+ // With a stretch order, the runs that follow each other on the routes share one lane order (LANE-STRETCH).
394
+ const method = options.laneOrder === "run" ? "none" : (options.stretchOrder ?? "none");
395
+ const stretch = method === "none" ? undefined : stretchLaneOrders(list.map((run) => ({ points: run.points, routes: routesOf(run) })), { method, passes: options.siftPasses ?? 2 });
396
+ list.forEach((run, i) => {
397
+ const edges = options.laneOrder === "run" ? laneOrderOfRun(run) : (stretch?.[i] ?? orderLanes(run.points, routesOf(run), "bundle"));
316
398
  shared.push({ id: `r${shared.length}`, key: run.key, points: run.points, edges });
317
- }
399
+ });
318
400
  return { routes, runs: shared };
319
401
  },
320
402
  };
@@ -1,5 +1,5 @@
1
1
  import { center, type Box, type Point } from "../geom.ts";
2
- import { fixedPortPoint, nodeIndex, type Graph, type Result, type Route, type Router } from "../model.ts";
2
+ import { fixedPortPoint, nodeIndex, resolvePorts, type Graph, type Result, type Route, type Router } from "../model.ts";
3
3
 
4
4
  /** This function gives the point where the ray from the center of the box to `toward` leaves the box. */
5
5
  export function boxExit(box: Box, toward: Point): Point {
@@ -19,7 +19,8 @@ export function boxExit(box: Box, toward: Point): Point {
19
19
  */
20
20
  export const straightRouter: Router = {
21
21
  name: "straight",
22
- route(graph: Graph): Result {
22
+ route(input: Graph): Result {
23
+ const graph = resolvePorts(input);
23
24
  const byId = nodeIndex(graph);
24
25
  const routes: Route[] = [];
25
26
  for (const e of graph.edges) {
@@ -1,5 +1,5 @@
1
1
  import { center, dist, type Point } from "../geom.ts";
2
- import { nodeIndex, type Bundle, type Edge, type Graph, type GroupRule, type Node, type Result, type Route, type Router, type SharedRun } from "../model.ts";
2
+ import { nodeIndex, resolvePorts, type Bundle, type Edge, type Graph, type GroupRule, type Node, type Result, type Route, type Router, type SharedRun } from "../model.ts";
3
3
  import { placePlug, type Plug } from "../plugs.ts";
4
4
  import { orderLanes, type RunOrder } from "../segments.ts";
5
5
  import { WayFinder, type WayOptions } from "../ways.ts";
@@ -9,8 +9,8 @@ import { balancedFan, fanPath, gilbertPoint } from "./fan.ts";
9
9
  export interface TrunksOptions extends WayOptions {
10
10
  /** The name in the benchmark. */
11
11
  readonly name: string;
12
- /** The grouping rule. The edges from one plug with one key share a trunk. */
13
- readonly rule: GroupRule;
12
+ /** The grouping rule, or the name of a built-in rule (`GROUP_RULE_NAMES`). The edges from one plug with one key share a trunk. */
13
+ readonly rule: GroupRule | GroupRuleName;
14
14
  /** The split point is at this share of the way to the center of the targets. The default is 0.5. */
15
15
  readonly split?: number;
16
16
  /** A group with fewer edges stays unbundled. The default is 2. */
@@ -55,6 +55,23 @@ export const byCollection: GroupRule = (e) => {
55
55
  return typeof c === "string" ? c : undefined;
56
56
  };
57
57
 
58
+ /** The names of the built-in grouping rules of the edges. With `collection`, the rule is `byCollection`. With `all`, each edge has one key. */
59
+ export type GroupRuleName = "collection" | "all";
60
+
61
+ /** The names of the built-in grouping rules, in the order of a control. */
62
+ export const GROUP_RULE_NAMES: readonly GroupRuleName[] = ["collection", "all"];
63
+
64
+ /** The rule `all`: each edge has the same key. */
65
+ const allEdges: GroupRule = () => "*";
66
+
67
+ /** This function gives the rule of a name of `GROUP_RULE_NAMES`. It gives a rule of the caller as it is. It throws a `RangeError` for an unknown name. */
68
+ export function groupRuleOf(rule: GroupRule | GroupRuleName): GroupRule {
69
+ if (typeof rule === "function") return rule;
70
+ if (rule === "collection") return byCollection;
71
+ if (rule === "all") return allEdges;
72
+ throw new RangeError(`There is no grouping rule "${String(rule)}". The names are ${GROUP_RULE_NAMES.join(" and ")}.`);
73
+ }
74
+
58
75
  /**
59
76
  * This function groups the edges by their source plug and their key. Each group keeps the order of the edges.
60
77
  * A group with fewer than `minSize` edges does not appear.
@@ -189,9 +206,11 @@ export function sharedWays(routes: readonly Route[], from: number): { points: Po
189
206
  * The edges outside the groups go as in the corner router.
190
207
  */
191
208
  export function trunksRouter(options: TrunksOptions): Router {
209
+ const rule = groupRuleOf(options.rule);
192
210
  const router: Router = {
193
211
  name: options.name,
194
- route(graph: Graph): Result {
212
+ route(input: Graph): Result {
213
+ const graph = resolvePorts(input);
195
214
  const byId = nodeIndex(graph);
196
215
  const ways = new WayFinder(graph.nodes, options);
197
216
  const points = new Map<string, readonly Point[]>();
@@ -200,7 +219,7 @@ export function trunksRouter(options: TrunksOptions): Router {
200
219
  const part = (options.leave ?? "part") === "part";
201
220
  // A branch leaves the trunk early only on a line that keeps out of the boxes as far as the corner graph does.
202
221
  const clear = (a: Point, b: Point): boolean => ways.obstacles.clear(a, b, ways.margin / 2);
203
- for (const [id, group] of groupEdges(graph.edges, options.rule, options.minSize ?? 2)) {
222
+ for (const [id, group] of groupEdges(graph.edges, rule, options.minSize ?? 2)) {
204
223
  const first = group[0]!;
205
224
  const s = byId.get(first.source.node);
206
225
  const targets = group.map((e) => byId.get(e.target.node));
package/src/segments.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { dist, type Point } from "./geom.ts";
2
- import { orderPlaced, type Placed } from "./lane-order.ts";
2
+ import { findRun, orderPlaced, placeRoutes } from "./lane-order.ts";
3
3
  import type { Result, Route } from "./model.ts";
4
4
 
5
5
  /** A piece of drawn way. A shared segment carries several edges, in lane order. A private segment carries one edge. */
@@ -34,16 +34,6 @@ export interface SegmentGraph {
34
34
 
35
35
  const SAME = 1e-6;
36
36
 
37
- /** This function gives the index where `p` holds the points of `part` in a row, from `from` on, or -1. */
38
- function indexOf(p: readonly Point[], part: readonly Point[], from: number): number {
39
- for (let i = from; i + part.length <= p.length; i++) {
40
- let ok = true;
41
- for (let j = 0; ok && j < part.length; j++) ok = dist(p[i + j]!, part[j]!) <= SAME;
42
- if (ok) return i;
43
- }
44
- return -1;
45
- }
46
-
47
37
  /**
48
38
  * This function breaks each route into segments. The runs and the trunks of the bundles become shared segments.
49
39
  * The other parts of each route become private segments. The segments of a path, in order, give the points of its route again.
@@ -68,10 +58,10 @@ export function segmentsOf(result: Result): SegmentGraph {
68
58
  // The place of each shared segment in the route, in the order of the route.
69
59
  const found: { at: number; end: number; ref: SegmentRef }[] = [];
70
60
  for (const s of onEdge.get(route.edge) ?? []) {
71
- let at = indexOf(p, s.points, 0);
61
+ let at = findRun(p, s.points, 0);
72
62
  let reverse = false;
73
63
  if (at < 0) {
74
- at = indexOf(p, [...s.points].reverse(), 0);
64
+ at = findRun(p, [...s.points].reverse(), 0);
75
65
  reverse = true;
76
66
  }
77
67
  if (at >= 0) found.push({ at, end: at + s.points.length - 1, ref: { segment: s.id, reverse, lane: s.lane } });
@@ -221,18 +211,7 @@ export type RunOrder = "bundle" | "run";
221
211
  * On a run with a cycle, the order depends on the order of `routes`. The item LANE-STRETCH is to remove the cycles.
222
212
  */
223
213
  export function orderLanes(run: readonly Point[], routes: readonly Route[], order: RunOrder = "run"): string[] {
224
- const placed: Placed[] = [];
225
- const lost: string[] = [];
226
- for (const r of routes) {
227
- let at = indexOf(r.points, run, 0);
228
- if (at >= 0) placed.push({ edge: r.edge, p: r.points, s: at, e: at + run.length - 1 });
229
- else {
230
- const p = [...r.points].reverse();
231
- at = indexOf(p, run, 0);
232
- if (at >= 0) placed.push({ edge: r.edge, p, s: at, e: at + run.length - 1 });
233
- else lost.push(r.edge);
234
- }
235
- }
214
+ const { placed, lost } = placeRoutes(run, routes);
236
215
  const lanes = orderPlaced(placed, order);
237
216
  return [...lanes.map((x) => x.edge), ...lost.sort()];
238
217
  }
@@ -44,6 +44,11 @@ import { TERRITORY_DEFAULTS, territoryPlan, type TerritoryOptions } from "../hig
44
44
  import { CONSENSUS_DEFAULTS, consensusPlan, type ConsensusOptions } from "../highways/consensus/index.ts";
45
45
  import { P_HUB_DEFAULTS, pHubPlan, type PHubOptions } from "../highways/p-hub/index.ts";
46
46
  import { SCALE_SPACE_DEFAULTS, scaleSpacePlan, type ScaleSpaceOptions } from "../highways/scale-space/index.ts";
47
+ import { GAP_DEFAULTS, gapPlan, type GapOptions } from "../highways/gaps/index.ts";
48
+ import { INTERCHANGE_DEFAULTS, interchangePlan, type InterchangeOptions } from "../highways/interchanges/index.ts";
49
+ import { SEARCH_DEFAULTS, searchPlan, type SearchOptions } from "../highways/search/index.ts";
50
+ import { STEINER_DEFAULTS, steinerPlan, type SteinerOptions } from "../highways/steiner/index.ts";
51
+ import { STREET_DEFAULTS, streetPlan, type StreetOptions } from "../highways/streets/index.ts";
47
52
  import { RIDGE_DEFAULTS, ridgePlan, type RidgeOptions } from "../highways/ridges/index.ts";
48
53
  import { SOFT_KEY_DEFAULTS, softKeyPlan, type SoftKeyOptions } from "../highways/soft-keys/index.ts";
49
54
  import { FLOW_FIELD_DEFAULTS, flowFieldPlan, type FlowFieldOptions } from "../highways/flow-field/index.ts";
@@ -66,7 +71,9 @@ import { TRACLUS_DEFAULTS, traclusPlan, type TraclusOptions } from "../highways/
66
71
  import { WEISZFELD_DEFAULTS, weiszfeldPlan, type WeiszfeldOptions } from "../highways/weiszfeld/index.ts";
67
72
  import { WSPD_DEFAULTS, wspdPlan, type WspdOptions } from "../highways/wspd/index.ts";
68
73
  import { ZIPPER_DEFAULTS, zipperPlan, type ZipperOptions } from "../highways/zipper/index.ts";
74
+ import { depthLevels, type DepthLevelOptions } from "../levels/depths/index.ts";
69
75
  import type { Graph, Result, Router } from "../model.ts";
76
+ import { facingEnds } from "../plugs/facing/index.ts";
70
77
  import { roomDemand, type RoomMethod, type RoomOptions } from "../room/index.ts";
71
78
  import { levelRoom, type LevelRoomOptions } from "../room/levels/index.ts";
72
79
  import { cornersRouter, type CornersOptions } from "../routers/corners.ts";
@@ -76,10 +83,12 @@ import { sharedRouter, type SharedOptions } from "../routers/shared.ts";
76
83
  import { straightRouter } from "../routers/straight.ts";
77
84
  import { byCollection, trunksRouter, type TrunksOptions } from "../routers/trunks.ts";
78
85
  import { buildSections, SECTION_DEFAULTS, type SectionOptions } from "../sections.ts";
86
+ import { runPipeline, type PipelineOptions } from "../whole/pipeline/index.ts";
79
87
  import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
80
88
  import type { OptionSchema } from "./options.ts";
81
89
  import { AREA_SCHEMA, CONFLUENT_SCHEMA, CROSSBAR_SCHEMA, FRECHET_SCHEMA, MLSEB_SCHEMA, POWER_SCHEMA, SBEB_SCHEMA } from "./schemas.ts";
82
- import { CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CONSENSUS_SCHEMA, CUBU_SCHEMA, CUT_SCHEMA, DIFFUSION_SCHEMA, DIVIDED_SCHEMA, EDGE_PATH_SCHEMA, FDEB_SCHEMA, FLOW_CLUSTER_SCHEMA, FLOW_FIELD_SCHEMA, GROWTH_SCHEMA, HDBSCAN_SCHEMA, HIERARCHY_SCHEMA, HIGHWAY_SCHEMA, HOUGH_SCHEMA, HUB_ARC_SCHEMA, KD_SCHEMA, KDEEB_SCHEMA, KEY_SCHEMA, LANES_SCHEMA, LEVEL_HIGHWAY_SCHEMA, LEVEL_ROOM_SCHEMA, LINK_COMMUNITY_SCHEMA, MEET_SCHEMA, MESH_SCHEMA, MINGLE_SCHEMA, NESTED_SCHEMA, OD_CELL_SCHEMA, P_HUB_SCHEMA, QUICKBUNDLES_SCHEMA, RADIUS_SCHEMA, RESISTANCE_SCHEMA, RIDGE_SCHEMA, ROAD_CLASS_SCHEMA, ROOM_SCHEMA, SCALE_SPACE_SCHEMA, SECTION_SCHEMA, SELECT_SCHEMA, SECTIONS_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, SOFT_KEY_SCHEMA, SPECTRAL_SCHEMA, SPIRAL_SCHEMA, STUB_SCHEMA, TENSION_SCHEMA, TERRITORY_SCHEMA, TRACLUS_SCHEMA, TRUNKS_SCHEMA, WEISZFELD_SCHEMA, WSPD_SCHEMA, ZIPPER_SCHEMA } from "./schemas.ts";
90
+ import { DEPTHS_SCHEMA, PIPELINE_SCHEMA } from "./schemas.ts";
91
+ import { CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CONSENSUS_SCHEMA, CUBU_SCHEMA, CUT_SCHEMA, DIFFUSION_SCHEMA, DIVIDED_SCHEMA, EDGE_PATH_SCHEMA, FDEB_SCHEMA, FLOW_CLUSTER_SCHEMA, FLOW_FIELD_SCHEMA, GROWTH_SCHEMA, HDBSCAN_SCHEMA, HIERARCHY_SCHEMA, HIGHWAY_SCHEMA, HOUGH_SCHEMA, HUB_ARC_SCHEMA, KD_SCHEMA, KDEEB_SCHEMA, KEY_SCHEMA, LANES_SCHEMA, LEVEL_HIGHWAY_SCHEMA, LEVEL_ROOM_SCHEMA, LINK_COMMUNITY_SCHEMA, MEET_SCHEMA, MESH_SCHEMA, MINGLE_SCHEMA, NESTED_SCHEMA, OD_CELL_SCHEMA, P_HUB_SCHEMA, QUICKBUNDLES_SCHEMA, RADIUS_SCHEMA, RESISTANCE_SCHEMA, RIDGE_SCHEMA, ROAD_CLASS_SCHEMA, ROOM_SCHEMA, SCALE_SPACE_SCHEMA, SECTION_SCHEMA, SELECT_SCHEMA, SECTIONS_SCHEMA, SHARED_ALL_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, SOFT_KEY_SCHEMA, SPECTRAL_SCHEMA, SPIRAL_SCHEMA, STUB_SCHEMA, TENSION_SCHEMA, TERRITORY_SCHEMA, TRACLUS_SCHEMA, TRUNKS_SCHEMA, WEISZFELD_SCHEMA, WSPD_SCHEMA, ZIPPER_SCHEMA, STEINER_SCHEMA, GAP_SCHEMA, STREET_SCHEMA, SEARCH_SCHEMA, INTERCHANGE_SCHEMA } from "./schemas.ts";
83
92
 
84
93
  /** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
85
94
  export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
@@ -150,7 +159,7 @@ export const routerStrategies: {
150
159
  sharedRouter,
151
160
  SHARED_SCHEMA,
152
161
  ),
153
- "shared-all": routerStrategy<RouterPreset<SharedOptions>>("shared-all", "The same as `shared`, but all edges share their ways.", 10_000, {}, sharedRouter, SHARED_SCHEMA),
162
+ "shared-all": routerStrategy<RouterPreset<SharedOptions>>("shared-all", "The same as `shared`, but all edges share their ways.", 10_000, {}, sharedRouter, SHARED_ALL_SCHEMA),
154
163
  sections: routerStrategy<RouterPreset<SectionsOptions>>(
155
164
  "sections",
156
165
  "A tree of sections, with a hub in each section. A long edge goes through the hubs, and a short edge goes directly.",
@@ -458,6 +467,11 @@ export const highwayStrategies: {
458
467
  readonly consensus: Strategy<"highway", ConsensusOptions>;
459
468
  readonly "p-hub": Strategy<"highway", PHubOptions>;
460
469
  readonly "scale-space": Strategy<"highway", ScaleSpaceOptions>;
470
+ readonly steiner: Strategy<"highway", SteinerOptions>;
471
+ readonly gaps: Strategy<"highway", GapOptions>;
472
+ readonly streets: Strategy<"highway", StreetOptions>;
473
+ readonly search: Strategy<"highway", SearchOptions>;
474
+ readonly interchanges: Strategy<"highway", InterchangeOptions>;
461
475
  readonly "corridors+breakeven": Strategy<"highway", CorridorOptions & BreakEvenOptions>;
462
476
  readonly "corridors+stretch": Strategy<"highway", CorridorOptions & StretchOptions>;
463
477
  readonly "corridors+ambiguity": Strategy<"highway", CorridorOptions & AmbiguityGuardOptions>;
@@ -888,6 +902,51 @@ export const highwayStrategies: {
888
902
  maxNodes: 10_000,
889
903
  run: (input: HighwayInput, o: Readonly<ScaleSpaceOptions>) => scaleSpacePlan(input, o),
890
904
  }),
905
+ steiner: defineStrategy({
906
+ step: "highway",
907
+ name: "steiner",
908
+ describe: "Obstacle-avoiding rectilinear Steiner trees (HWY-036 with EDA-015). Each group of edges gets one tree, and the stretches that edges share become highways.",
909
+ defaults: { ...STEINER_DEFAULTS } as SteinerOptions,
910
+ schema: STEINER_SCHEMA,
911
+ maxNodes: 10_000,
912
+ run: (input: HighwayInput, o: Readonly<SteinerOptions>) => steinerPlan(input, o),
913
+ }),
914
+ gaps: defineStrategy({
915
+ step: "highway",
916
+ name: "gaps",
917
+ describe: "Gap highways (HWY-057). The free middle lines of the gaps are streets, and a street that enough edges choose becomes a highway.",
918
+ defaults: { ...GAP_DEFAULTS } as GapOptions,
919
+ schema: GAP_SCHEMA,
920
+ maxNodes: 10_000,
921
+ run: (input: HighwayInput, o: Readonly<GapOptions>) => gapPlan(input, o),
922
+ }),
923
+ streets: defineStrategy({
924
+ step: "highway",
925
+ name: "streets",
926
+ describe: "Orthogonal streets by a greedy set cover (HWY-058). Each step takes the street that its new edges ride the most per unit of its length.",
927
+ defaults: { ...STREET_DEFAULTS } as StreetOptions,
928
+ schema: STREET_SCHEMA,
929
+ maxNodes: 10_000,
930
+ run: (input: HighwayInput, o: Readonly<StreetOptions>) => streetPlan(input, o),
931
+ }),
932
+ search: defineStrategy({
933
+ step: "highway",
934
+ name: "search",
935
+ describe: "A local search over the set of highways (HWY-059). The annealing adds, drops and moves highways, edges and points on a score of the bundle measures.",
936
+ defaults: { ...SEARCH_DEFAULTS } as SearchOptions,
937
+ schema: SEARCH_SCHEMA,
938
+ maxNodes: 10_000,
939
+ run: (input: HighwayInput, o: Readonly<SearchOptions>) => searchPlan(input, o),
940
+ }),
941
+ interchanges: defineStrategy({
942
+ step: "highway",
943
+ name: "interchanges",
944
+ describe: "A network of highways with interchanges (HWY-060). Each edge rides up to a number of highways, with a cost for each change.",
945
+ defaults: { ...INTERCHANGE_DEFAULTS } as InterchangeOptions,
946
+ schema: INTERCHANGE_SCHEMA,
947
+ maxNodes: 10_000,
948
+ run: (input: HighwayInput, o: Readonly<InterchangeOptions>) => interchangePlan(input, o),
949
+ }),
891
950
  "corridors+breakeven": gateStrategy(corridors, breakEvenGate),
892
951
  "corridors+stretch": gateStrategy(corridors, stretchGate),
893
952
  "corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
@@ -895,6 +954,51 @@ export const highwayStrategies: {
895
954
  "corridors+ids": gateStrategy(corridors, stableIdGate),
896
955
  });
897
956
 
957
+ /** The built-in plug rules: the strategies of the step `plugs`. The router `corners` routes between their plugs with its option `plug`. */
958
+ export const plugStrategies: {
959
+ readonly facing: Strategy<"plugs", object>;
960
+ } = Object.freeze({
961
+ facing: defineStrategy({
962
+ step: "plugs",
963
+ name: "facing",
964
+ describe: "Each free plug faces the other end of its edge, on the side of the larger component of the direction. It is the default of the router `corners`.",
965
+ defaults: {},
966
+ schema: {},
967
+ run: (input) => facingEnds(input),
968
+ }),
969
+ });
970
+
971
+ /** The built-in level rules: the strategies of the step `levels`. The router `sections` makes its zoom levels with its option `level`. */
972
+ export const levelStrategies: {
973
+ readonly depths: Strategy<"levels", DepthLevelOptions>;
974
+ } = Object.freeze({
975
+ depths: defineStrategy<"levels", DepthLevelOptions>({
976
+ step: "levels",
977
+ name: "depths",
978
+ 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`.",
979
+ defaults: {},
980
+ schema: DEPTHS_SCHEMA,
981
+ run: (input, o) => depthLevels(input, o),
982
+ }),
983
+ });
984
+
985
+ /**
986
+ * 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.
987
+ * Without options, it routes the graph with the router `corners`.
988
+ */
989
+ export const wholeStrategies: {
990
+ readonly pipeline: Strategy<"whole", PipelineOptions>;
991
+ } = Object.freeze({
992
+ pipeline: defineStrategy<"whole", PipelineOptions>({
993
+ step: "whole",
994
+ name: "pipeline",
995
+ 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.",
996
+ defaults: { router: routerStrategies.corners as Strategy<"router"> },
997
+ schema: PIPELINE_SCHEMA,
998
+ run: (graph, o) => runPipeline(graph, o),
999
+ }),
1000
+ });
1001
+
898
1002
  /** The built-in strategies of each step, in order. A consumer adds its own: `createRegistry([...BUILT_IN_STRATEGIES, mine])`. */
899
1003
  export const BUILT_IN_STRATEGIES: readonly AnyStrategy[] = Object.freeze([
900
1004
  ...Object.values(groupStrategies),
@@ -902,6 +1006,9 @@ export const BUILT_IN_STRATEGIES: readonly AnyStrategy[] = Object.freeze([
902
1006
  ...Object.values(highwayStrategies),
903
1007
  ...Object.values(curveStrategies),
904
1008
  ...Object.values(roomStrategies),
1009
+ ...Object.values(plugStrategies),
1010
+ ...Object.values(levelStrategies),
1011
+ ...Object.values(wholeStrategies),
905
1012
  ]);
906
1013
 
907
1014
  /** The registry of the built-in strategies. The benchmark and the site take their lists from it. */
@@ -2,10 +2,12 @@ import type { CurveSegment } from "../curves/model.ts";
2
2
  import type { CurveSpace } from "../curves/space.ts";
3
3
  import type { Point } from "../geom.ts";
4
4
  import type { HighwayInput, HighwayPlan } from "../highways/model.ts";
5
- import type { Graph, Result, Router } from "../model.ts";
5
+ import type { Graph, Level, Result, Route, Router } from "../model.ts";
6
+ import type { Plug } from "../plugs.ts";
6
7
  import type { Boxed, Gap, RoomLink } from "../room/index.ts";
7
8
  import type { Section } from "../sections.ts";
8
9
  import type { SegmentGraph } from "../segments.ts";
10
+ import type { WayFinder } from "../ways.ts";
9
11
  import type { OptionSchema } from "./options.ts";
10
12
 
11
13
  /** The input of a curve strategy: one polyline and the context of its route. */
@@ -48,21 +50,57 @@ export interface RoomInput {
48
50
  readonly sections?: Section;
49
51
  }
50
52
 
53
+ /** The input of a plug strategy: the graph, and optionally the way finder of the router that reads the plugs. */
54
+ export interface PlugInput {
55
+ readonly graph: Graph;
56
+ /** The ways around the nodes of the graph, for a strategy that reads the free space. The default strategy `facing` does not read it. */
57
+ readonly ways?: WayFinder;
58
+ }
59
+
60
+ /** The plugs of the two ends of one edge: the output of a plug strategy for that edge. */
61
+ export interface PlugPair {
62
+ readonly edge: string;
63
+ readonly source: Plug;
64
+ readonly target: Plug;
65
+ }
66
+
67
+ /** The input of a level strategy: the graph, the tree of sections of its grouping rule and the routes of the router. */
68
+ export interface LevelInput {
69
+ readonly graph: Graph;
70
+ /** The tree of sections. The indices of its nodes are the indices of `graph.nodes`. */
71
+ readonly root: Section;
72
+ /** The routes of the router. The default strategy `depths` gives the links of the last level the points of these routes. */
73
+ readonly routes: readonly Route[];
74
+ }
75
+
76
+ /** The output of a strategy of the step `whole`: the board that the routes go on, and the routes. */
77
+ export interface WholeOutput {
78
+ /** The board: the graph, or the graph with the gaps that a room strategy opened. It has the edges of the input. */
79
+ readonly graph: Graph;
80
+ readonly result: Result;
81
+ }
82
+
51
83
  /**
52
84
  * The steps that have strategies, with the fixed input and output type of each step.
53
85
  *
54
86
  * - `group`: a graph in, the tree of sections out. The router `sections` makes its hubs and its zoom levels from this tree.
87
+ * - `plugs`: a graph in, the plugs of the two ends of each edge out. The router `corners` routes between these plugs.
55
88
  * - `router`: a graph in, the routes (and the `levels`, `bundles` and `runs`) out.
56
89
  * - `highway`: a graph and optionally its first routes in, a highway plan out. `follow` routes the edges along the plan.
90
+ * - `levels`: a graph, its tree of sections and its routes in, the zoom levels out. The router `sections` gives them in its result.
57
91
  * - `curve`: a polyline in, the segments of a curve out.
58
92
  * - `room`: the boxes and links of a board in, the gaps of the board with their need out.
93
+ * - `whole`: a graph in, the board and its routes out. A strategy of this step runs the other steps.
59
94
  */
60
95
  export interface StepTypes {
61
96
  readonly group: { readonly input: Graph; readonly output: Section };
97
+ readonly plugs: { readonly input: PlugInput; readonly output: PlugPair[] };
62
98
  readonly router: { readonly input: Graph; readonly output: Result };
63
99
  readonly highway: { readonly input: HighwayInput; readonly output: HighwayPlan };
100
+ readonly levels: { readonly input: LevelInput; readonly output: Level[] };
64
101
  readonly curve: { readonly input: CurveInput; readonly output: CurveSegment[] };
65
102
  readonly room: { readonly input: RoomInput; readonly output: Gap[] };
103
+ readonly whole: { readonly input: Graph; readonly output: WholeOutput };
66
104
  }
67
105
 
68
106
  /** The name of a step that has strategies. */
@@ -74,8 +112,12 @@ export type StepInput<S extends Step> = StepTypes[S]["input"];
74
112
  /** The output type of a step. */
75
113
  export type StepOutput<S extends Step> = StepTypes[S]["output"];
76
114
 
77
- /** The steps that have strategies, in the order of the pipeline. */
78
- export const STEPS: readonly Step[] = ["group", "router", "highway", "curve", "room"];
115
+ /**
116
+ * The steps that have strategies, in the order of the pipeline.
117
+ * The plugs come before the router, because the router searches its ways between them. The levels come after the routes, because the last level reads them.
118
+ * The step `whole` runs the other steps, so it comes last.
119
+ */
120
+ export const STEPS: readonly Step[] = ["group", "plugs", "router", "highway", "levels", "curve", "room", "whole"];
79
121
 
80
122
  /**
81
123
  * A strategy: one way to do one step, with a name and a description.
@@ -147,16 +189,16 @@ export function defineStrategy<S extends Step, O extends object>(definition: Str
147
189
  /**
148
190
  * A registry: the strategies of each step, in order. A registry is data. No import changes it.
149
191
  * A consumer makes its own registry with `createRegistry`, and adds a strategy with `register`.
150
- * The lists `group` and `highway` are optional in the type, so a registry from version 0.1.5 or 0.3.0 stays a registry. `createRegistry` always gives them.
192
+ * The lists `group`, `highway`, `plugs`, `levels` and `whole` are optional in the type, so a registry of an older version stays a registry. `createRegistry` always gives them.
151
193
  */
152
- export type Registry = { readonly [S in Exclude<Step, "group" | "highway">]: readonly Strategy<S>[] } & {
153
- readonly group?: readonly Strategy<"group">[];
154
- readonly highway?: readonly Strategy<"highway">[];
155
- };
194
+ export type Registry = { readonly [S in Exclude<Step, OptionalStep>]: readonly Strategy<S>[] } & { readonly [S in OptionalStep]?: readonly Strategy<S>[] };
195
+
196
+ /** The steps whose lists are optional in a `Registry`: the steps that came after version 0.1.5. */
197
+ type OptionalStep = "group" | "highway" | "plugs" | "levels" | "whole";
156
198
 
157
199
  /** This function makes a registry from a list of strategies. It keeps their order. Two strategies with one step and one name are an error. */
158
200
  export function createRegistry(strategies: readonly AnyStrategy[]): Registry {
159
- const out: { [S in Step]: Strategy<S>[] } = { group: [], router: [], highway: [], curve: [], room: [] };
201
+ const out: { [S in Step]: Strategy<S>[] } = { group: [], plugs: [], router: [], highway: [], levels: [], curve: [], room: [], whole: [] };
160
202
  for (const s of strategies) {
161
203
  const list = out[s.step] as AnyStrategy[] | undefined;
162
204
  if (!list) throw new Error(`There is no step "${String(s.step)}". The steps are ${STEPS.join(", ")}.`);
@@ -165,16 +207,19 @@ export function createRegistry(strategies: readonly AnyStrategy[]): Registry {
165
207
  }
166
208
  return Object.freeze({
167
209
  group: Object.freeze(out.group),
210
+ plugs: Object.freeze(out.plugs),
168
211
  router: Object.freeze(out.router),
169
212
  highway: Object.freeze(out.highway),
213
+ levels: Object.freeze(out.levels),
170
214
  curve: Object.freeze(out.curve),
171
215
  room: Object.freeze(out.room),
216
+ whole: Object.freeze(out.whole),
172
217
  });
173
218
  }
174
219
 
175
220
  /** This function gives a new registry with the strategies of `registry` and then `strategies`. It does not change `registry`. */
176
221
  export function register(registry: Registry, ...strategies: readonly AnyStrategy[]): Registry {
177
- return createRegistry([...(registry.group ?? []), ...registry.router, ...(registry.highway ?? []), ...registry.curve, ...registry.room, ...strategies]);
222
+ return createRegistry([...STEPS.flatMap((step) => listStrategies(registry, step) as readonly AnyStrategy[]), ...strategies]);
178
223
  }
179
224
 
180
225
  /** This function gives the strategies of one step of a registry, in order. */