@mark1russell7/highways 0.9.4 → 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.
- package/README.md +38 -7
- package/build/index.d.ts +549 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4965 -2175
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bench/screen/exercise.ts +2 -1
- package/src/highways/balance/index.ts +93 -0
- package/src/highways/index.ts +3 -0
- package/src/highways/manifolds/index.ts +157 -0
- package/src/highways/slots/index.ts +178 -0
- package/src/index.ts +6 -0
- package/src/levels/detail/index.ts +140 -0
- package/src/plugs/leaders/index.ts +42 -0
- package/src/plugs/spread/index.ts +39 -5
- package/src/routers/adam/index.ts +294 -0
- package/src/routers/canals/index.ts +191 -0
- package/src/routers/index.ts +3 -0
- package/src/routers/reroute/index.ts +164 -0
- package/src/routers/shared.ts +3 -0
- package/src/strategies/builtins.ts +102 -1
- package/src/strategies/schemas.ts +89 -0
- package/src/whole/curriculum/index.ts +243 -0
- package/src/whole/fireflies/index.ts +177 -0
- package/src/whole/preferences/index.ts +247 -0
- package/src/whole/tracing/index.ts +144 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { checkProperties } from "../../bench/properties.ts";
|
|
2
|
+
import { segmentsCross, type Point } from "../../geom.ts";
|
|
3
|
+
import type { Graph, Result } from "../../model.ts";
|
|
4
|
+
import { turnsOf } from "../../routers/reroute/index.ts";
|
|
5
|
+
import type { WholeOutput } from "../../strategies/model.ts";
|
|
6
|
+
import { runPipeline } from "../pipeline/index.ts";
|
|
7
|
+
import type { PortfolioSetting } from "../portfolio/index.ts";
|
|
8
|
+
|
|
9
|
+
/** The value of the loads of the edges of a board that the pick reads: the median, the quantile 0.95 or the mean. */
|
|
10
|
+
export type TracingQuantile = "q50" | "q95" | "mean";
|
|
11
|
+
|
|
12
|
+
/** The values of `TracingQuantile`. */
|
|
13
|
+
export const TRACING_QUANTILES: readonly TracingQuantile[] = Object.freeze(["q50", "q95", "mean"]);
|
|
14
|
+
|
|
15
|
+
/** The weights of the places to choose on the way of one edge (WILD-057). */
|
|
16
|
+
export interface TracingWeights {
|
|
17
|
+
/** The weight of a crossing, from 0 to 4. The default is 1. */
|
|
18
|
+
readonly crossing: number;
|
|
19
|
+
/** The weight of a merge or a split where the edge is not an outer lane, from 0 to 4. The default is 2. */
|
|
20
|
+
readonly merge: number;
|
|
21
|
+
/** The weight of a bend, from 0 to 4. The default is 0.5. */
|
|
22
|
+
readonly bend: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The default weights of the tracing load, as the record gives them. */
|
|
26
|
+
export const TRACING_WEIGHTS: Readonly<TracingWeights> = Object.freeze({ crossing: 1, merge: 2, bend: 0.5 });
|
|
27
|
+
|
|
28
|
+
/** The options of the whole strategy `tracing` (WILD-057). */
|
|
29
|
+
export interface TracingOptions extends TracingWeights {
|
|
30
|
+
/** The number of settings that the strategy tries, from the start of its list, from 1 to 12. The default is 4. */
|
|
31
|
+
readonly settings: number;
|
|
32
|
+
/** The value of the loads that the pick reads. The default is `q95`. */
|
|
33
|
+
readonly quantile: TracingQuantile;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The default options of the strategy `tracing`. */
|
|
37
|
+
export const TRACING_DEFAULTS: Readonly<TracingOptions> = Object.freeze({ ...TRACING_WEIGHTS, settings: 4, quantile: "q95" });
|
|
38
|
+
|
|
39
|
+
/** The bounds of a polyline: the least x and y, and the largest x and y. */
|
|
40
|
+
function boundsOf(p: readonly Point[]): [number, number, number, number] {
|
|
41
|
+
let x0 = Infinity;
|
|
42
|
+
let y0 = Infinity;
|
|
43
|
+
let x1 = -Infinity;
|
|
44
|
+
let y1 = -Infinity;
|
|
45
|
+
for (const q of p) {
|
|
46
|
+
x0 = Math.min(x0, q.x);
|
|
47
|
+
y0 = Math.min(y0, q.y);
|
|
48
|
+
x1 = Math.max(x1, q.x);
|
|
49
|
+
y1 = Math.max(y1, q.y);
|
|
50
|
+
}
|
|
51
|
+
return [x0, y0, x1, y1];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* This function gives the tracing load of each edge of a result (WILD-057, after the tracing tasks of Ware, Purchase, Colpoys and McGill 2002).
|
|
56
|
+
* The load is `T(e) = crossing × crossings + merge × (merges and splits where e is not an outer lane) + bend × bends`.
|
|
57
|
+
* A shared run where the edge is not an outer lane gives one merge and one split. Such a bundle gives one split.
|
|
58
|
+
* A bend is a turn of more than 1 degree. The measure is W4 of MEAS3.
|
|
59
|
+
*/
|
|
60
|
+
export function tracingLoads(result: Result, weights: Partial<TracingWeights> = {}): Map<string, number> {
|
|
61
|
+
const w = { ...TRACING_WEIGHTS, ...weights };
|
|
62
|
+
const routes = result.routes;
|
|
63
|
+
const bounds = routes.map((r) => boundsOf(r.points));
|
|
64
|
+
const crossings = new Array<number>(routes.length).fill(0);
|
|
65
|
+
for (let i = 0; i < routes.length; i++) {
|
|
66
|
+
const a = routes[i]!.points;
|
|
67
|
+
const [ax0, ay0, ax1, ay1] = bounds[i]!;
|
|
68
|
+
for (let j = i + 1; j < routes.length; j++) {
|
|
69
|
+
const [bx0, by0, bx1, by1] = bounds[j]!;
|
|
70
|
+
if (bx0 > ax1 || bx1 < ax0 || by0 > ay1 || by1 < ay0) continue;
|
|
71
|
+
const b = routes[j]!.points;
|
|
72
|
+
let n = 0;
|
|
73
|
+
for (let p = 1; p < a.length; p++) for (let q = 1; q < b.length; q++) if (segmentsCross(a[p - 1]!, a[p]!, b[q - 1]!, b[q]!)) n++;
|
|
74
|
+
crossings[i]! += n;
|
|
75
|
+
crossings[j]! += n;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const choices = new Map<string, number>();
|
|
79
|
+
const inner = (edges: readonly string[], add: number): void => {
|
|
80
|
+
if (edges.length < 3) return;
|
|
81
|
+
for (const e of edges.slice(1, -1)) choices.set(e, (choices.get(e) ?? 0) + add);
|
|
82
|
+
};
|
|
83
|
+
for (const run of result.runs ?? []) inner(run.edges, 2);
|
|
84
|
+
for (const b of result.bundles ?? []) inner(b.edges, 1);
|
|
85
|
+
const out = new Map<string, number>();
|
|
86
|
+
routes.forEach((r, i) => out.set(r.edge, w.crossing * crossings[i]! + w.merge * (choices.get(r.edge) ?? 0) + w.bend * turnsOf(r.points)));
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** This function gives a value of a list of loads: the median, the quantile 0.95 by the nearest rank, or the mean. An empty list gives 0. */
|
|
91
|
+
export function tracingValue(loads: Iterable<number>, quantile: TracingQuantile): number {
|
|
92
|
+
const list = [...loads].sort((a, b) => a - b);
|
|
93
|
+
if (list.length === 0) return 0;
|
|
94
|
+
if (quantile === "mean") return list.reduce((s, v) => s + v, 0) / list.length;
|
|
95
|
+
const q = quantile === "q50" ? 0.5 : 0.95;
|
|
96
|
+
return list[Math.min(list.length - 1, Math.max(0, Math.ceil(q * list.length) - 1))]!;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The tracing loads of one setting on the board: the value that the pick reads, and the median for a tie. */
|
|
100
|
+
export interface TracingEntry {
|
|
101
|
+
readonly name: string;
|
|
102
|
+
readonly value: number;
|
|
103
|
+
readonly median: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The result of `tracing`: the output, the name of the pick and the loads of each setting with no break of a hard rule. */
|
|
107
|
+
export interface TracingRun {
|
|
108
|
+
readonly output: WholeOutput;
|
|
109
|
+
readonly pick: string;
|
|
110
|
+
readonly entries: readonly TracingEntry[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* This function runs the whole strategy `tracing` (WILD-057). It runs the first `settings` settings of `list` on the board.
|
|
115
|
+
* A setting with a break of a hard rule, or above its node limit, does not count. The setting with the least tracing load wins.
|
|
116
|
+
* A tie goes to the least median load, then to the earlier setting. It reads only the board and the outputs of its own settings.
|
|
117
|
+
*/
|
|
118
|
+
export function tracing(graph: Graph, list: readonly PortfolioSetting[], options: Partial<TracingOptions> = {}): TracingRun {
|
|
119
|
+
const o = { ...TRACING_DEFAULTS, ...options };
|
|
120
|
+
const count = Math.max(1, Math.min(list.length, Math.floor(o.settings)));
|
|
121
|
+
const entries: TracingEntry[] = [];
|
|
122
|
+
const outputs: WholeOutput[] = [];
|
|
123
|
+
let fallback: { name: string; output: WholeOutput } | undefined;
|
|
124
|
+
for (const s of list.slice(0, count)) {
|
|
125
|
+
const limit = (s.pipeline.highway ?? s.pipeline.router).maxNodes;
|
|
126
|
+
if (limit !== undefined && graph.nodes.length > limit) continue;
|
|
127
|
+
const output = runPipeline(graph, s.pipeline);
|
|
128
|
+
fallback ??= { name: s.name, output };
|
|
129
|
+
if (checkProperties(output.graph, output.result, 1).length > 0) continue;
|
|
130
|
+
const loads = [...tracingLoads(output.result, o).values()];
|
|
131
|
+
entries.push({ name: s.name, value: tracingValue(loads, o.quantile), median: tracingValue(loads, "q50") });
|
|
132
|
+
outputs.push(output);
|
|
133
|
+
}
|
|
134
|
+
if (entries.length === 0) {
|
|
135
|
+
const first = fallback ?? { name: list[0]!.name, output: runPipeline(graph, list[0]!.pipeline) };
|
|
136
|
+
return { output: first.output, pick: first.name, entries };
|
|
137
|
+
}
|
|
138
|
+
let best = 0;
|
|
139
|
+
entries.forEach((e, i) => {
|
|
140
|
+
const b = entries[best]!;
|
|
141
|
+
if (e.value < b.value - 1e-9 || (Math.abs(e.value - b.value) <= 1e-9 && e.median < b.median - 1e-9)) best = i;
|
|
142
|
+
});
|
|
143
|
+
return { output: outputs[best]!, pick: entries[best]!.name, entries };
|
|
144
|
+
}
|