@mark1russell7/highways 0.9.3 → 0.9.5

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 (39) hide show
  1. package/README.md +85 -5
  2. package/build/index.d.ts +954 -5
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +10689 -5294
  5. package/build/index.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/bench/screen/exercise.ts +4 -1
  8. package/src/bench/screen/set.ts +58 -1
  9. package/src/families/cases.ts +17 -9
  10. package/src/families/coverage.ts +502 -0
  11. package/src/families/index.ts +4 -1
  12. package/src/highways/balance/index.ts +93 -0
  13. package/src/highways/bronchial/index.ts +144 -0
  14. package/src/highways/index.ts +5 -0
  15. package/src/highways/knee/index.ts +154 -0
  16. package/src/highways/knee/root.ts +19 -0
  17. package/src/highways/manifolds/index.ts +157 -0
  18. package/src/highways/slots/index.ts +178 -0
  19. package/src/index.ts +8 -0
  20. package/src/levels/detail/index.ts +140 -0
  21. package/src/levels/venation/index.ts +191 -0
  22. package/src/plugs/leaders/index.ts +42 -0
  23. package/src/plugs/spread/index.ts +39 -5
  24. package/src/routers/adam/index.ts +294 -0
  25. package/src/routers/annealing/index.ts +260 -0
  26. package/src/routers/canals/index.ts +191 -0
  27. package/src/routers/frames.ts +80 -16
  28. package/src/routers/gutter.ts +545 -0
  29. package/src/routers/index.ts +8 -2
  30. package/src/routers/reroute/index.ts +164 -0
  31. package/src/routers/shared.ts +43 -2
  32. package/src/routers/tight/index.ts +122 -0
  33. package/src/strategies/builtins.ts +158 -2
  34. package/src/strategies/schemas.ts +143 -0
  35. package/src/whole/curriculum/index.ts +243 -0
  36. package/src/whole/fireflies/index.ts +177 -0
  37. package/src/whole/portfolio/index.ts +108 -0
  38. package/src/whole/preferences/index.ts +247 -0
  39. package/src/whole/tracing/index.ts +144 -0
@@ -0,0 +1,164 @@
1
+ import { dist, type Point } from "../../geom.ts";
2
+ import type { Graph, GroupRule, Result, Router } from "../../model.ts";
3
+ import { defineStrategy, type Strategy } from "../../strategies/model.ts";
4
+ import type { OptionSchema } from "../../strategies/options.ts";
5
+ import { sharedRouter, type SharedSearch, type Way } from "../shared.ts";
6
+ import { byCollection } from "../trunks.ts";
7
+
8
+ /** The sine of 1 degree, as a literal. A literal gives the same bits on each platform, so a decision does not depend on `Math.sin`. */
9
+ const SIN_1 = 0.01745240643728351;
10
+
11
+ /** The options of the router `reroute` (RULE-044: a corner graph with the prices of the rules, and rounds of rip-up and reroute). */
12
+ export interface RerouteOptions {
13
+ /** The price of a link in a narrow channel, from 0 to 2. The default is 1. */
14
+ readonly alpha: number;
15
+ /** The price of each edge of another key on a link, from 0 to 2. The default is 0.5. */
16
+ readonly beta: number;
17
+ /** The discount of a link that an edge of the same key uses, from 0 to 2. The default is 0.5. */
18
+ readonly gamma: number;
19
+ /** The price of a turn, in units of `clearance`, from 0 to 2. The default is 0.25. */
20
+ readonly tau: number;
21
+ /** The number of rounds of rip-up and reroute, from 1 to 5. The default is 2. */
22
+ readonly rounds: number;
23
+ /** The share of the edges that each round rips up: the edges with the largest price over their direct length, from 0.05 to 0.3. The default is 0.15. */
24
+ readonly q: number;
25
+ /** A channel narrower than this width gets the price `alpha`, from 4 to 64. The default is 16. */
26
+ readonly clearance: number;
27
+ }
28
+
29
+ /** The default options of the router `reroute`. */
30
+ export const REROUTE_DEFAULTS: Readonly<RerouteOptions> = Object.freeze({ alpha: 1, beta: 0.5, gamma: 0.5, tau: 0.25, rounds: 2, q: 0.15, clearance: 16 });
31
+
32
+ /** The declarations of the options of the router `reroute` (RULE-044). */
33
+ export const REROUTE_SCHEMA: OptionSchema<RerouteOptions> = {
34
+ alpha: { type: "number", min: 0, max: 2, step: 0.05, marks: [0, 1, 2], default: 1, describe: "The price of a link in a narrow channel." },
35
+ beta: { type: "number", min: 0, max: 2, step: 0.05, marks: [0, 0.5, 1], default: 0.5, describe: "The price of each edge of another key on a link." },
36
+ gamma: { type: "number", min: 0, max: 2, step: 0.05, marks: [0, 0.5, 1], default: 0.5, describe: "The discount of a link that an edge of the same key uses." },
37
+ tau: { type: "number", min: 0, max: 2, step: 0.05, marks: [0, 0.25, 1], default: 0.25, describe: "The price of a turn, in units of the clearance." },
38
+ rounds: { type: "integer", min: 1, max: 5, step: 1, marks: [1, 2, 3, 5], default: 2, describe: "The number of rounds of rip-up and reroute." },
39
+ q: { type: "number", min: 0.05, max: 0.3, step: 0.05, marks: [0.05, 0.15, 0.3], default: 0.15, describe: "The share of the edges that each round rips up: the edges with the largest price over their direct length." },
40
+ clearance: { type: "number", min: 4, max: 64, step: 1, marks: [8, 16, 32], default: 16, describe: "A channel narrower than this width gets the price alpha." },
41
+ };
42
+
43
+ /** This function gives the number of turns of a way: the points where the direction changes by more than 1 degree. */
44
+ export function turnsOf(seq: readonly Point[]): number {
45
+ let turns = 0;
46
+ for (let i = 1; i + 1 < seq.length; i++) {
47
+ const a = seq[i - 1]!;
48
+ const b = seq[i]!;
49
+ const c = seq[i + 1]!;
50
+ const ux = b.x - a.x;
51
+ const uy = b.y - a.y;
52
+ const vx = c.x - b.x;
53
+ const vy = c.y - b.y;
54
+ const lu = Math.sqrt(ux * ux + uy * uy);
55
+ const lv = Math.sqrt(vx * vx + vy * vy);
56
+ if (lu <= 1e-9 || lv <= 1e-9) continue;
57
+ if (Math.abs(ux * vy - uy * vx) > SIN_1 * lu * lv || ux * vx + uy * vy < 0) turns++;
58
+ }
59
+ return turns;
60
+ }
61
+
62
+ /** The use of one link of the corner graph: the number of edges of each key on it. */
63
+ type LinkUse = Map<string, number>;
64
+
65
+ /**
66
+ * This function rips up and reroutes the ways of `shared` (RULE-044, after the rip-up and reroute of PathFinder, McMurchie 1995).
67
+ * The price factor of a link is `max(0.05, 1 + alpha × narrow + beta × others - gamma × same)`.
68
+ * `narrow` is `max(0, 1 - width / clearance)`. `others` counts the edges of the other keys on the link.
69
+ * `same` is 1 when an edge of the same key uses the link.
70
+ * The price of a way is the sum of the price factor times the length of each link, plus `tau × clearance` for each turn.
71
+ * Each round takes the share `q` of the ways with the largest price over their direct length. Each one gets a new search with the prices of the links.
72
+ * The new way stays only when its price is lower. The search has no state for the turns, so only the comparison reads `tau`.
73
+ */
74
+ export function rerouteWays(ways: readonly Way[], search: SharedSearch, options: Partial<RerouteOptions> = {}): void {
75
+ const o = { ...REROUTE_DEFAULTS, ...options };
76
+ const n = search.corners;
77
+ const use = new Map<number, LinkUse>();
78
+ const linksOf = (w: Way): number[] => {
79
+ const out: number[] = [];
80
+ for (let i = 1; i < w.ids.length; i++) {
81
+ const a = w.ids[i - 1]!;
82
+ const b = w.ids[i]!;
83
+ if (a < n && b < n) out.push(search.link(a, b));
84
+ }
85
+ return out;
86
+ };
87
+ const change = (w: Way, by: number): void => {
88
+ for (const k of linksOf(w)) {
89
+ let m = use.get(k);
90
+ if (!m) use.set(k, (m = new Map()));
91
+ const next = (m.get(w.key) ?? 0) + by;
92
+ if (next > 0) m.set(w.key, next);
93
+ else m.delete(w.key);
94
+ }
95
+ };
96
+ const found = ways.filter((w) => w.found && w.ids.length >= 2);
97
+ for (const w of found) change(w, 1);
98
+ const factor = (i: number, j: number, key: string): number => {
99
+ const m = use.get(search.link(i, j));
100
+ let others = 0;
101
+ let same = 0;
102
+ for (const [k, c] of m ?? []) {
103
+ if (k === key) same = 1;
104
+ else others += c;
105
+ }
106
+ const narrow = o.alpha > 0 ? Math.max(0, 1 - search.width(i, j) / o.clearance) : 0;
107
+ return Math.max(0.05, 1 + o.alpha * narrow + o.beta * others - o.gamma * same);
108
+ };
109
+ const priceOf = (seq: readonly Point[], ids: readonly number[], key: string): number => {
110
+ let p = 0;
111
+ for (let i = 1; i < seq.length; i++) {
112
+ const a = ids[i - 1]!;
113
+ const b = ids[i]!;
114
+ const d = dist(seq[i - 1]!, seq[i]!);
115
+ p += a < n && b < n ? d * factor(a, b, key) : d;
116
+ }
117
+ return p + o.tau * o.clearance * turnsOf(seq);
118
+ };
119
+ const floor = Math.max(0.05, 1 - o.gamma);
120
+ const count = (share: number): number => Math.max(1, Math.round(share * found.length));
121
+ for (let round = 0; round < Math.max(1, Math.floor(o.rounds)); round++) {
122
+ const ranked = found
123
+ .map((w) => ({ w, score: priceOf(w.seq, w.ids, w.key) / Math.max(1e-9, dist(w.ps.point, w.pt.point)) }))
124
+ .sort((a, b) => b.score - a.score || (a.w.edge.id < b.w.edge.id ? -1 : a.w.edge.id > b.w.edge.id ? 1 : 0));
125
+ let moved = 0;
126
+ for (const { w } of ranked.slice(0, count(Math.min(0.3, Math.max(0.05, o.q))))) {
127
+ change(w, -1);
128
+ const before = priceOf(w.seq, w.ids, w.key);
129
+ const next = search.find(w, (i, j, d) => d * factor(i, j, w.key), floor);
130
+ if (next && priceOf(next.seq, next.ids, w.key) < before - 1e-9) {
131
+ w.seq = next.seq;
132
+ w.ids = next.ids;
133
+ moved++;
134
+ }
135
+ change(w, 1);
136
+ }
137
+ if (moved === 0) break;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * This function makes the router `reroute` (RULE-044). It is the router `shared`, then the rounds of rip-up and reroute of `rerouteWays`.
143
+ * The runs and the bundles come from the ways after the rounds.
144
+ */
145
+ export function rerouteRouter(options: Partial<RerouteOptions> & { readonly name: string; readonly rule?: GroupRule; readonly maxNodes?: number }): Router {
146
+ const o = { ...REROUTE_DEFAULTS, ...options };
147
+ return sharedRouter({ name: o.name, ...(o.rule === undefined ? {} : { rule: o.rule }), ...(o.maxNodes === undefined ? {} : { maxNodes: o.maxNodes }) }, undefined, {
148
+ refine: (ways, search) => rerouteWays(ways, search, o),
149
+ });
150
+ }
151
+
152
+ /**
153
+ * The router strategy `reroute` (RULE-044), with the rule `byCollection` of `shared`. It is not a built-in router, so the benchmark and the hashes of `ROUTERS` do not change.
154
+ * A screen module or a consumer adds it to a registry.
155
+ */
156
+ export const rerouteStrategy: Strategy<"router", RerouteOptions> = defineStrategy({
157
+ step: "router",
158
+ name: "reroute",
159
+ describe: "Rule prices and rip-up (RULE-044). The ways of `shared` with the largest prices of narrow channels, other keys and turns get a new search.",
160
+ defaults: { ...REROUTE_DEFAULTS },
161
+ schema: REROUTE_SCHEMA,
162
+ maxNodes: 10_000,
163
+ run: (graph: Graph, o: Readonly<RerouteOptions>): Result => rerouteRouter({ ...o, name: "reroute", rule: byCollection }).route(graph),
164
+ });
@@ -92,6 +92,35 @@ export interface SharedNegotiation {
92
92
  readonly laneWidth: number;
93
93
  }
94
94
 
95
+ /**
96
+ * The search of the corner graph of one route of `shared`, for the hooks of `SharedHooks`.
97
+ * An index below `corners` is a corner of the graph. Each end of a way gets an index from `corners`, one for each place.
98
+ */
99
+ export interface SharedSearch {
100
+ /** The graph of the route, without the inner edges of a frame. */
101
+ readonly graph: Graph;
102
+ /** The number of corners of the corner graph. */
103
+ readonly corners: number;
104
+ /** This function gives the key of the link between the indices `i` and `j`, for each order of the two. */
105
+ link(i: number, j: number): number;
106
+ /**
107
+ * This function gives a shortest way of `w` through the corner graph, with its points and their indices, or `undefined`.
108
+ * The function `cost` can change the cost of the link from corner `i` to corner `j`, of length `d`. It must give at least `floor` × `d`.
109
+ */
110
+ find(w: Way, cost?: (i: number, j: number, d: number) => number, floor?: number): { seq: Point[]; ids: number[] } | undefined;
111
+ /** This function gives the free space across the link from corner `i` to corner `j` (`channelWidth`). The router keeps each width that it finds. */
112
+ width(i: number, j: number): number;
113
+ }
114
+
115
+ /**
116
+ * The hooks of a router that `shared` runs (WILD-023, RULE-021). The router `shared` itself has no hooks.
117
+ * `order` gives the order of the ways before the first pass. `refine` changes the ways after the passes, before the runs.
118
+ */
119
+ export interface SharedHooks {
120
+ readonly order?: (ways: readonly Way[], search: SharedSearch) => Way[];
121
+ readonly refine?: (ways: readonly Way[], search: SharedSearch) => void;
122
+ }
123
+
95
124
  /** A way of one edge through the corner graph. */
96
125
  export interface Way {
97
126
  readonly edge: Edge;
@@ -129,7 +158,7 @@ function turn(from: Point, base: Point, to: Point): number {
129
158
  * Thus the edges of a key come together on common ways. Each stretch that several edges share has the same points in each route.
130
159
  * The result gives each stretch as a run, with the order of its lanes.
131
160
  */
132
- export function sharedRouter(options: SharedOptions, negotiation?: SharedNegotiation): Router {
161
+ export function sharedRouter(options: SharedOptions, negotiation?: SharedNegotiation, hooks?: SharedHooks): Router {
133
162
  const discount = Math.min(1, Math.max(0.05, options.discount ?? 0.5));
134
163
  const flow = options.cost === "flow";
135
164
  const alpha = Math.min(1, Math.max(0.1, options.alpha ?? 0.6));
@@ -177,7 +206,18 @@ export function sharedRouter(options: SharedOptions, negotiation?: SharedNegotia
177
206
 
178
207
  // The edges of one key go one after the other, by default the longest first, so that the short edges join the long ways.
179
208
  const discounted = new Map<string, Map<number, string[]>>();
180
- const order = orderWays(all, options.order ?? "span", graph);
209
+ const search: SharedSearch = {
210
+ graph,
211
+ corners: corners.points.length,
212
+ link,
213
+ find: (w, cost, floor = 1) => {
214
+ const way = corners.path(ways.out(w.ps), ways.out(w.pt), cost, floor);
215
+ return way && { seq: way, ids: way.map((p, i) => (i === 0 || i === way.length - 1 ? endId(p) : index.get(p)!)) };
216
+ },
217
+ width: (i, j) => widthOf(i, j),
218
+ };
219
+ const sorted = orderWays(all, options.order ?? "span", graph);
220
+ const order = hooks?.order ? hooks.order(sorted, search) : sorted;
181
221
  // The largest number of edges on one link of each key. The cost share of that number is the floor of the search.
182
222
  const top = new Map<string, number>();
183
223
  // The width of the channel of each link, for the option `capacity`. Each link gets its width at its first use.
@@ -308,6 +348,7 @@ export function sharedRouter(options: SharedOptions, negotiation?: SharedNegotia
308
348
  }
309
349
  }
310
350
 
351
+ hooks?.refine?.(order, search);
311
352
  // With `merge: snell`, each route that joins or leaves a run moves its merge point along the run to the Snell angle.
312
353
  if (options.merge === "snell") {
313
354
  const pad = ways.margin / 2;
@@ -0,0 +1,122 @@
1
+ import { byText } from "../../compare.ts";
2
+ import { dist, polylineLength } from "../../geom.ts";
3
+ import type { Graph, GroupRule, Result, Router } from "../../model.ts";
4
+ import { defineStrategy, type Strategy } from "../../strategies/model.ts";
5
+ import type { OptionSchema } from "../../strategies/options.ts";
6
+ import { sharedRouter, type SharedSearch, type Way } from "../shared.ts";
7
+ import { byCollection } from "../trunks.ts";
8
+
9
+ /** The edges that go first: the edges with few good ways (`tight`), or the edges with many good ways (`loose`). */
10
+ export type TightFirst = "tight" | "loose";
11
+
12
+ /** The values of `TightFirst`. */
13
+ export const TIGHT_FIRSTS: readonly TightFirst[] = Object.freeze(["tight", "loose"]);
14
+
15
+ /** The order of two edges with the same slack: by the edge id, or the longer edge first. */
16
+ export type TightTies = "id" | "length";
17
+
18
+ /** The values of `TightTies`. */
19
+ export const TIGHT_TIES: readonly TightTies[] = Object.freeze(["id", "length"]);
20
+
21
+ /** The options of the router `tight` (WILD-023: tight edges first, after list scheduling by slack). */
22
+ export interface TightOptions {
23
+ /** The cost factor of the links of the first way in the search of the second way, from 2 to 10. The default is 3. */
24
+ readonly penalty: number;
25
+ /**
26
+ * The edges that go first. With `tight`, the default, an edge whose second way costs much more than its first way goes first.
27
+ * With `loose`, the order turns around: the formula of the record, with the rising slack.
28
+ */
29
+ readonly first: TightFirst;
30
+ /** The order of two edges with the same slack. The default is `length`: the longer edge first, as in `shared`. */
31
+ readonly ties: TightTies;
32
+ }
33
+
34
+ /** The default options of the router `tight`. */
35
+ export const TIGHT_DEFAULTS: Readonly<TightOptions> = Object.freeze({ penalty: 3, first: "tight", ties: "length" });
36
+
37
+ /** The declarations of the options of the router `tight` (WILD-023). */
38
+ export const TIGHT_SCHEMA: OptionSchema<TightOptions> = {
39
+ penalty: { type: "number", min: 2, max: 10, step: 0.5, marks: [2, 3, 5], default: 3, describe: "The cost factor of the links of the first way in the search of the second way." },
40
+ first: { type: "enum", values: ["tight", "loose"], default: "tight", describe: "The edges that go first: the edges with few good ways, or the edges with many good ways." },
41
+ ties: { type: "enum", values: ["id", "length"], default: "length", describe: "The order of two edges with the same slack: by the edge id, or the longer edge first." },
42
+ };
43
+
44
+ /**
45
+ * This function gives the slack of each way (WILD-023): `L2 / L1 - 1`. `L1` is the length of the shortest way.
46
+ * `L2` is the cost of the shortest way when each link of the first way costs `penalty` times its length.
47
+ * A way with no second way has the slack `penalty - 1`. A way with a second way of near-equal length has a slack near 0.
48
+ */
49
+ export function waySlacks(ways: readonly Way[], search: SharedSearch, penalty: number): Map<Way, number> {
50
+ const out = new Map<Way, number>();
51
+ const n = search.corners;
52
+ for (const w of ways) {
53
+ const first = search.find(w);
54
+ if (!first) {
55
+ out.set(w, 0);
56
+ continue;
57
+ }
58
+ const l1 = polylineLength(first.seq);
59
+ const own = new Set<number>();
60
+ for (let i = 1; i < first.ids.length; i++) {
61
+ const a = first.ids[i - 1]!;
62
+ const b = first.ids[i]!;
63
+ if (a < n && b < n) own.add(search.link(a, b));
64
+ }
65
+ const cost = (i: number, j: number, d: number): number => (own.has(search.link(i, j)) ? d * penalty : d);
66
+ const second = search.find(w, cost, 1);
67
+ let l2 = l1;
68
+ if (second) {
69
+ l2 = 0;
70
+ for (let i = 1; i < second.ids.length; i++) {
71
+ const a = second.ids[i - 1]!;
72
+ const b = second.ids[i]!;
73
+ const d = dist(second.seq[i - 1]!, second.seq[i]!);
74
+ l2 += a < n && b < n ? cost(a, b, d) : d;
75
+ }
76
+ }
77
+ out.set(w, l1 > 1e-9 ? l2 / l1 - 1 : 0);
78
+ }
79
+ return out;
80
+ }
81
+
82
+ /**
83
+ * This function makes the router `tight` (WILD-023). It is the router `shared` with another order of the edges of each key.
84
+ * With `first: tight`, the edges with the largest slack value go first: their second way costs much more, so they have few good ways.
85
+ * The edges with many ways of near-equal cost go last. The keys keep the order of `shared`.
86
+ */
87
+ export function tightRouter(options: Partial<TightOptions> & { readonly name: string; readonly rule?: GroupRule; readonly maxNodes?: number }): Router {
88
+ const o = { ...TIGHT_DEFAULTS, ...options };
89
+ const penalty = Math.min(10, Math.max(1, o.penalty));
90
+ const sign = o.first === "loose" ? 1 : -1;
91
+ return sharedRouter(
92
+ { name: o.name, ...(o.rule === undefined ? {} : { rule: o.rule }), ...(o.maxNodes === undefined ? {} : { maxNodes: o.maxNodes }) },
93
+ undefined,
94
+ {
95
+ order: (ways, search) => {
96
+ const slack = waySlacks(ways, search, penalty);
97
+ const span = (w: Way): number => dist(w.ps.point, w.pt.point);
98
+ return [...ways].sort(
99
+ (p, q) =>
100
+ byText(p.key, q.key) ||
101
+ sign * (slack.get(p)! - slack.get(q)!) ||
102
+ (o.ties === "length" ? span(q) - span(p) : 0) ||
103
+ byText(p.edge.id, q.edge.id),
104
+ );
105
+ },
106
+ },
107
+ );
108
+ }
109
+
110
+ /**
111
+ * The router strategy `tight` (WILD-023), with the rule `byCollection` of `shared`. It is not a built-in router, so the benchmark and the hashes of `ROUTERS` do not change.
112
+ * A screen module or a consumer adds it to a registry.
113
+ */
114
+ export const tightStrategy: Strategy<"router", TightOptions> = defineStrategy({
115
+ step: "router",
116
+ name: "tight",
117
+ describe: "Tight edges first (WILD-023). The router `shared` routes first the edges whose second way costs much more than their first way.",
118
+ defaults: { ...TIGHT_DEFAULTS },
119
+ schema: TIGHT_SCHEMA,
120
+ maxNodes: 10_000,
121
+ run: (graph: Graph, o: Readonly<TightOptions>): Result => tightRouter({ ...o, name: "tight", rule: byCollection }).route(graph),
122
+ });
@@ -46,6 +46,10 @@ import { P_HUB_DEFAULTS, pHubPlan, type PHubOptions } from "../highways/p-hub/in
46
46
  import { SCALE_SPACE_DEFAULTS, scaleSpacePlan, type ScaleSpaceOptions } from "../highways/scale-space/index.ts";
47
47
  import { GAP_DEFAULTS, gapPlan, type GapOptions } from "../highways/gaps/index.ts";
48
48
  import { INTERCHANGE_DEFAULTS, interchangePlan, type InterchangeOptions } from "../highways/interchanges/index.ts";
49
+ import { KNEE_DEFAULTS, kneePlan, type KneeOptions } from "../highways/knee/index.ts";
50
+ import { BALANCE_DEFAULTS, balancePlan, type BalanceOptions } from "../highways/balance/index.ts";
51
+ import { MANIFOLD_DEFAULTS, manifoldPlan, type ManifoldOptions } from "../highways/manifolds/index.ts";
52
+ import { BRONCHIAL_DEFAULTS, bronchialPlan, type BronchialOptions } from "../highways/bronchial/index.ts";
49
53
  import { SEARCH_DEFAULTS, searchPlan, type SearchOptions } from "../highways/search/index.ts";
50
54
  import { STEINER_DEFAULTS, steinerPlan, type SteinerOptions } from "../highways/steiner/index.ts";
51
55
  import { STREET_DEFAULTS, streetPlan, type StreetOptions } from "../highways/streets/index.ts";
@@ -72,11 +76,14 @@ import { WEISZFELD_DEFAULTS, weiszfeldPlan, type WeiszfeldOptions } from "../hig
72
76
  import { WSPD_DEFAULTS, wspdPlan, type WspdOptions } from "../highways/wspd/index.ts";
73
77
  import { ZIPPER_DEFAULTS, zipperPlan, type ZipperOptions } from "../highways/zipper/index.ts";
74
78
  import { depthLevels, type DepthLevelOptions } from "../levels/depths/index.ts";
79
+ import { VENATION_DEFAULTS, venationLevels, type VenationOptions } from "../levels/venation/index.ts";
80
+ import { DETAIL_DEFAULTS, detailLevels, type DetailOptions } from "../levels/detail/index.ts";
75
81
  import type { Graph, Result, Router } from "../model.ts";
76
82
  import { facingEnds } from "../plugs/facing/index.ts";
77
83
  import { justifyEnds, type JustifyOptions } from "../plugs/justify.ts";
78
84
  import { sideEnds, type SidePlugOptions } from "../plugs/side.ts";
79
85
  import { SPREAD_DEFAULTS, spreadEnds, type SpreadOptions } from "../plugs/spread/index.ts";
86
+ import { LEADER_DEFAULTS, leaderEnds, type LeaderOptions } from "../plugs/leaders/index.ts";
80
87
  import { roomDemand, type RoomMethod, type RoomOptions } from "../room/index.ts";
81
88
  import { levelRoom, type LevelRoomOptions } from "../room/levels/index.ts";
82
89
  import { cornersRouter, type CornersOptions } from "../routers/corners.ts";
@@ -87,11 +94,16 @@ import { straightRouter } from "../routers/straight.ts";
87
94
  import { byCollection, trunksRouter, type TrunksOptions } from "../routers/trunks.ts";
88
95
  import { buildSections, SECTION_DEFAULTS, type SectionOptions } from "../sections.ts";
89
96
  import { runPipeline, type PipelineOptions } from "../whole/pipeline/index.ts";
97
+ import { BOARD_PORTFOLIO_DEFAULTS, boardPortfolio, type BoardPortfolioOptions, type PortfolioSetting } from "../whole/portfolio/index.ts";
98
+ import { CURRICULUM_DEFAULTS, curriculum, type CurriculumOptions } from "../whole/curriculum/index.ts";
99
+ import { SLOT_DEFAULTS, slotPlan, type SlotOptions } from "../highways/slots/index.ts";
100
+ import { PREFERENCE_DEFAULTS, preferences, type PreferenceOptions } from "../whole/preferences/index.ts";
101
+ import { FIREFLY_DEFAULTS, fireflies, type FireflyOptions } from "../whole/fireflies/index.ts";
102
+ import { TRACING_DEFAULTS, tracing, type TracingOptions } from "../whole/tracing/index.ts";
90
103
  import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
91
104
  import type { OptionSchema } from "./options.ts";
92
105
  import { AREA_SCHEMA, CONFLUENT_SCHEMA, CROSSBAR_SCHEMA, FRECHET_SCHEMA, MLSEB_SCHEMA, POWER_SCHEMA, SBEB_SCHEMA } from "./schemas.ts";
93
- import { DEPTHS_SCHEMA, JUSTIFY_SCHEMA, PIPELINE_SCHEMA, SIDE_PLUG_SCHEMA, SPREAD_SCHEMA } from "./schemas.ts";
94
- 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";
106
+ import { DEPTHS_SCHEMA, JUSTIFY_SCHEMA, PIPELINE_SCHEMA, SIDE_PLUG_SCHEMA, SPREAD_SCHEMA, 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, KNEE_SCHEMA, BRONCHIAL_SCHEMA, BOARD_PORTFOLIO_SCHEMA, VENATION_SCHEMA, BALANCE_SCHEMA, MANIFOLD_SCHEMA, LEADER_SCHEMA, DETAIL_SCHEMA, CURRICULUM_SCHEMA, SLOT_SCHEMA, PREFERENCE_SCHEMA, FIREFLY_SCHEMA, TRACING_SCHEMA } from "./schemas.ts";
95
107
 
96
108
  /** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
97
109
  export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
@@ -475,6 +487,11 @@ export const highwayStrategies: {
475
487
  readonly streets: Strategy<"highway", StreetOptions>;
476
488
  readonly search: Strategy<"highway", SearchOptions>;
477
489
  readonly interchanges: Strategy<"highway", InterchangeOptions>;
490
+ readonly knee: Strategy<"highway", KneeOptions>;
491
+ readonly bronchial: Strategy<"highway", BronchialOptions>;
492
+ readonly balance: Strategy<"highway", BalanceOptions>;
493
+ readonly manifolds: Strategy<"highway", ManifoldOptions>;
494
+ readonly slots: Strategy<"highway", SlotOptions>;
478
495
  readonly "corridors+breakeven": Strategy<"highway", CorridorOptions & BreakEvenOptions>;
479
496
  readonly "corridors+stretch": Strategy<"highway", CorridorOptions & StretchOptions>;
480
497
  readonly "corridors+ambiguity": Strategy<"highway", CorridorOptions & AmbiguityGuardOptions>;
@@ -950,6 +967,51 @@ export const highwayStrategies: {
950
967
  maxNodes: 10_000,
951
968
  run: (input: HighwayInput, o: Readonly<InterchangeOptions>) => interchangePlan(input, o),
952
969
  }),
970
+ knee: defineStrategy({
971
+ step: "highway",
972
+ name: "knee",
973
+ describe: "The knee of the plant front (NAT-018). A sweep of the balancing factor of NAT-017 gives trees, and the tree at the knee of ink against path length wins.",
974
+ defaults: { ...KNEE_DEFAULTS } as KneeOptions,
975
+ schema: KNEE_SCHEMA,
976
+ maxNodes: 10_000,
977
+ run: (input: HighwayInput, o: Readonly<KneeOptions>) => kneePlan(input, o),
978
+ }),
979
+ bronchial: defineStrategy({
980
+ step: "highway",
981
+ name: "bronchial",
982
+ describe: "Bronchial splits that fill the space (NAT-019). The tree grows toward the center of its targets, then splits the targets in two halves, again and again.",
983
+ defaults: { ...BRONCHIAL_DEFAULTS } as BronchialOptions,
984
+ schema: BRONCHIAL_SCHEMA,
985
+ maxNodes: 10_000,
986
+ run: (input: HighwayInput, o: Readonly<BronchialOptions>) => bronchialPlan(input, o),
987
+ }),
988
+ balance: defineStrategy({
989
+ step: "highway",
990
+ name: "balance",
991
+ describe: "Stretch balance in a bundle (EDA-017). A price for each edge grows while its stretch is above the target, and the price moves its exit forward.",
992
+ defaults: { ...BALANCE_DEFAULTS } as BalanceOptions,
993
+ schema: BALANCE_SCHEMA,
994
+ maxNodes: 10_000,
995
+ run: (input: HighwayInput, o: Readonly<BalanceOptions>) => balancePlan(input, o),
996
+ }),
997
+ manifolds: defineStrategy({
998
+ step: "highway",
999
+ name: "manifolds",
1000
+ describe: "Manifolds for a fan to a row (WILD-036). A straight header in the gap before the row, with taps at right angles and one feed from the node.",
1001
+ defaults: { ...MANIFOLD_DEFAULTS } as ManifoldOptions,
1002
+ schema: MANIFOLD_SCHEMA,
1003
+ maxNodes: 10_000,
1004
+ run: (input: HighwayInput, o: Readonly<ManifoldOptions>) => manifoldPlan(input, o),
1005
+ }),
1006
+ slots: defineStrategy({
1007
+ step: "highway",
1008
+ name: "slots",
1009
+ describe: "A pull toward the lane slots of the trunks (RULE-047). An edge near a trunk joins it on a new lane when the pull is larger than its detour.",
1010
+ defaults: { ...SLOT_DEFAULTS } as SlotOptions,
1011
+ schema: SLOT_SCHEMA,
1012
+ maxNodes: 10_000,
1013
+ run: (input: HighwayInput, o: Readonly<SlotOptions>) => slotPlan(input, o),
1014
+ }),
953
1015
  "corridors+breakeven": gateStrategy(corridors, breakEvenGate),
954
1016
  "corridors+stretch": gateStrategy(corridors, stretchGate),
955
1017
  "corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
@@ -963,6 +1025,7 @@ export const plugStrategies: {
963
1025
  readonly side: Strategy<"plugs", SidePlugOptions>;
964
1026
  readonly justify: Strategy<"plugs", JustifyOptions>;
965
1027
  readonly spread: Strategy<"plugs", SpreadOptions>;
1028
+ readonly leaders: Strategy<"plugs", LeaderOptions>;
966
1029
  } = Object.freeze({
967
1030
  facing: defineStrategy({
968
1031
  step: "plugs",
@@ -996,11 +1059,21 @@ export const plugStrategies: {
996
1059
  schema: SPREAD_SCHEMA,
997
1060
  run: (input, o) => spreadEnds(input, o),
998
1061
  }),
1062
+ leaders: defineStrategy<"plugs", LeaderOptions>({
1063
+ step: "plugs",
1064
+ name: "leaders",
1065
+ describe: "Boundary leaders as plug stubs (EDA-041). The plugs of each wall take the order of po-leaders with no crossing, and keep a least gap.",
1066
+ defaults: { ...LEADER_DEFAULTS },
1067
+ schema: LEADER_SCHEMA,
1068
+ run: (input, o) => leaderEnds(input, o),
1069
+ }),
999
1070
  });
1000
1071
 
1001
1072
  /** The built-in level rules: the strategies of the step `levels`. The router `sections` makes its zoom levels with its option `level`. */
1002
1073
  export const levelStrategies: {
1003
1074
  readonly depths: Strategy<"levels", DepthLevelOptions>;
1075
+ readonly venation: Strategy<"levels", VenationOptions>;
1076
+ readonly detail: Strategy<"levels", DetailOptions>;
1004
1077
  } = Object.freeze({
1005
1078
  depths: defineStrategy<"levels", DepthLevelOptions>({
1006
1079
  step: "levels",
@@ -1010,14 +1083,57 @@ export const levelStrategies: {
1010
1083
  schema: DEPTHS_SCHEMA,
1011
1084
  run: (input, o) => depthLevels(input, o),
1012
1085
  }),
1086
+ venation: defineStrategy<"levels", VenationOptions>({
1087
+ step: "levels",
1088
+ name: "venation",
1089
+ describe: "A closed venation backbone (NAT-016). The links of each level with groups take their ways along the relative neighbourhood graph of the items.",
1090
+ defaults: { ...VENATION_DEFAULTS },
1091
+ schema: VENATION_SCHEMA,
1092
+ run: (input, o) => venationLevels(input, o),
1093
+ }),
1094
+ detail: defineStrategy<"levels", DetailOptions>({
1095
+ step: "levels",
1096
+ name: "detail",
1097
+ describe: "The level of detail of the bundles (WILD-016 with EDA-038). Legs of level links closer than a few pixels at the scale of their level become one stroke.",
1098
+ defaults: { ...DETAIL_DEFAULTS },
1099
+ schema: DETAIL_SCHEMA,
1100
+ run: (input, o) => detailLevels(input, o),
1101
+ }),
1013
1102
  });
1014
1103
 
1104
+ /**
1105
+ * The fixed list of settings of the whole strategy `board-portfolio` (WILD-045), in order. The option `settings` takes the first ones.
1106
+ * The list holds routers and highway strategies with their defaults. It comes from the catalog of the strategies, not from a fit to the screen.
1107
+ */
1108
+ export const BOARD_PORTFOLIO_SETTINGS: readonly PortfolioSetting[] = Object.freeze(
1109
+ [
1110
+ ...(["shared", "corners", "trunks", "sections", "sections-tree", "corners-smooth"] as const).map((name) => ({ name, pipeline: { router: routerStrategies[name] as Strategy<"router"> } })),
1111
+ ...(["corridors", "shared-ways", "road-classes", "steiner", "knee", "bronchial"] as const).map((name) => ({
1112
+ name: `highway:${name}`,
1113
+ pipeline: { router: routerStrategies.corners as Strategy<"router">, highway: highwayStrategies[name] as Strategy<"highway"> },
1114
+ })),
1115
+ ].map((s) => Object.freeze(s)),
1116
+ );
1117
+
1118
+ /**
1119
+ * The pool of routes of the whole strategy `curriculum` (RULE-048), in order: the routers of the base of the screen. The first one gives the first answer.
1120
+ * The option `settings` takes the first ones.
1121
+ */
1122
+ export const CURRICULUM_POOL: readonly PortfolioSetting[] = Object.freeze(
1123
+ (["corners", "shared", "trunks"] as const).map((name) => Object.freeze({ name, pipeline: { router: routerStrategies[name] as Strategy<"router"> } })),
1124
+ );
1125
+
1015
1126
  /**
1016
1127
  * 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.
1017
1128
  * Without options, it routes the graph with the router `corners`.
1018
1129
  */
1019
1130
  export const wholeStrategies: {
1020
1131
  readonly pipeline: Strategy<"whole", PipelineOptions>;
1132
+ readonly "board-portfolio": Strategy<"whole", BoardPortfolioOptions>;
1133
+ readonly curriculum: Strategy<"whole", CurriculumOptions>;
1134
+ readonly preferences: Strategy<"whole", PreferenceOptions>;
1135
+ readonly fireflies: Strategy<"whole", FireflyOptions>;
1136
+ readonly tracing: Strategy<"whole", TracingOptions>;
1021
1137
  } = Object.freeze({
1022
1138
  pipeline: defineStrategy<"whole", PipelineOptions>({
1023
1139
  step: "whole",
@@ -1027,6 +1143,46 @@ export const wholeStrategies: {
1027
1143
  schema: PIPELINE_SCHEMA,
1028
1144
  run: (graph, o) => runPipeline(graph, o),
1029
1145
  }),
1146
+ "board-portfolio": defineStrategy<"whole", BoardPortfolioOptions>({
1147
+ step: "whole",
1148
+ name: "board-portfolio",
1149
+ describe: "A portfolio for each board (WILD-045). The pipeline tries a fixed list of settings, and the least weighted rank of crossings, detour and ink wins.",
1150
+ defaults: { ...BOARD_PORTFOLIO_DEFAULTS },
1151
+ schema: BOARD_PORTFOLIO_SCHEMA,
1152
+ run: (graph, o) => boardPortfolio(graph, BOARD_PORTFOLIO_SETTINGS, o).output,
1153
+ }),
1154
+ curriculum: defineStrategy<"whole", CurriculumOptions>({
1155
+ step: "whole",
1156
+ name: "curriculum",
1157
+ describe: "A curriculum of rules (RULE-048). Each stage adds one rule, and each edge takes the route of a pool that improves it with no loss on the earlier rules.",
1158
+ defaults: { ...CURRICULUM_DEFAULTS },
1159
+ schema: CURRICULUM_SCHEMA,
1160
+ run: (graph, o) => curriculum(graph, CURRICULUM_POOL, o),
1161
+ }),
1162
+ preferences: defineStrategy<"whole", PreferenceOptions>({
1163
+ step: "whole",
1164
+ name: "preferences",
1165
+ describe: "Rule weights from preferences (RULE-038). A fit to made-up pairs gives the weights, and each edge takes the route of a pool with the least weighted cost.",
1166
+ defaults: { ...PREFERENCE_DEFAULTS },
1167
+ schema: PREFERENCE_SCHEMA,
1168
+ run: (graph, o) => preferences(graph, CURRICULUM_POOL, o),
1169
+ }),
1170
+ fireflies: defineStrategy<"whole", FireflyOptions>({
1171
+ step: "whole",
1172
+ name: "fireflies",
1173
+ describe: "Firefly and bee searches (NAT-047). A seeded swarm searches the discount and the margin of `shared` on the board, within a budget of settings.",
1174
+ defaults: { ...FIREFLY_DEFAULTS },
1175
+ schema: FIREFLY_SCHEMA,
1176
+ run: (graph, o) => fireflies(graph, routerStrategies.shared as Strategy<"router">, o).output,
1177
+ }),
1178
+ tracing: defineStrategy<"whole", TracingOptions>({
1179
+ step: "whole",
1180
+ name: "tracing",
1181
+ describe: "The least tracing load (WILD-057). The pipeline tries a fixed list of settings, and the least load of crossings, merges and bends on one edge wins.",
1182
+ defaults: { ...TRACING_DEFAULTS },
1183
+ schema: TRACING_SCHEMA,
1184
+ run: (graph, o) => tracing(graph, BOARD_PORTFOLIO_SETTINGS, o).output,
1185
+ }),
1030
1186
  });
1031
1187
 
1032
1188
  /** The built-in strategies of each step, in order. A consumer adds its own: `createRegistry([...BUILT_IN_STRATEGIES, mine])`. */