@mark1russell7/highways 0.8.1 → 0.9.0
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 +74 -8
- package/build/index.d.ts +705 -183
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4079 -2639
- package/build/index.js.map +1 -1
- package/package.json +3 -2
- package/src/bench/compare.ts +174 -5
- package/src/bench/room.ts +1 -1
- package/src/bench/run.ts +38 -3
- package/src/bench/screen/generators.ts +11 -41
- package/src/bench/screen/screen.ts +16 -10
- package/src/bench/screen/set.ts +156 -49
- package/src/bench/suite.ts +110 -12
- package/src/cases.ts +64 -18
- package/src/families/cases.ts +108 -0
- package/src/families/fixtures.ts +101 -0
- package/src/families/index.ts +36 -0
- package/src/families/model.ts +55 -0
- package/src/fixtures.ts +8 -2
- package/src/highways/soft-keys/index.ts +8 -4
- package/src/index.ts +8 -0
- package/src/lane-order.ts +37 -0
- package/src/lane-orders/stretch/index.ts +486 -0
- package/src/lane-orders/stretch/router.ts +22 -0
- package/src/levels/depths/index.ts +57 -0
- package/src/plugs/facing/index.ts +22 -0
- package/src/portfolio/router.ts +44 -0
- package/src/routers/corners.ts +27 -3
- package/src/routers/sections.ts +22 -40
- package/src/routers/shared.ts +26 -7
- package/src/routers/trunks.ts +21 -3
- package/src/segments.ts +4 -25
- package/src/strategies/builtins.ts +54 -2
- package/src/strategies/model.ts +55 -10
- package/src/strategies/options.ts +36 -4
- package/src/strategies/schemas.ts +68 -5
- package/src/whole/pipeline/index.ts +66 -0
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"directory": "packages/core"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://mark1russell7.github.io/highways/",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.9.0",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "./build/index.js",
|
|
15
15
|
"types": "./build/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@mark1russell7/layout-contract": "^0.2.
|
|
32
|
+
"@mark1russell7/layout-contract": "^0.2.14"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@mark1russell7/cue": "github:mark1russell7/cue",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"test": "vitest run",
|
|
44
44
|
"bench": "node scripts/bench.ts",
|
|
45
45
|
"ideas": "node scripts/ideas.ts",
|
|
46
|
+
"queue": "node scripts/queue.ts",
|
|
46
47
|
"bench:join": "node scripts/bench-join.ts",
|
|
47
48
|
"profile": "node scripts/profile.ts",
|
|
48
49
|
"levels": "node scripts/levels.ts",
|
package/src/bench/compare.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type { CurveSegment } from "../curves/model.ts";
|
|
2
|
-
import { polylineLength } from "../geom.ts";
|
|
3
|
-
import
|
|
2
|
+
import { polylineLength, type Box } from "../geom.ts";
|
|
3
|
+
import { highwayRouterStrategy } from "../highways/router.ts";
|
|
4
|
+
import type { Graph, LevelLink, Node, Result } from "../model.ts";
|
|
5
|
+
import { openGaps, roomLinks } from "../room/index.ts";
|
|
6
|
+
import { cornersRouter } from "../routers/corners.ts";
|
|
7
|
+
import { sectionsRouter } from "../routers/sections.ts";
|
|
4
8
|
import type { Section } from "../sections.ts";
|
|
5
9
|
import { runKey, segmentsOf } from "../segments.ts";
|
|
6
10
|
import { STRATEGIES } from "../strategies/builtins.ts";
|
|
7
|
-
import { asRouter, getStrategy, listStrategies, type LaneBoard, type Registry, type Step } from "../strategies/model.ts";
|
|
11
|
+
import { asRouter, createRegistry, defineStrategy, getStrategy, listStrategies, STEPS, type AnyStrategy, type LaneBoard, type Registry, type Step, type Strategy } from "../strategies/model.ts";
|
|
8
12
|
import { peakCurvature } from "./curves.ts";
|
|
9
13
|
import { measureLevelFaults, sumFaults } from "./links.ts";
|
|
10
14
|
import { paretoFront, type Objective } from "./measures/pareto.ts";
|
|
15
|
+
import { roomOrderFaults } from "./measures/room.ts";
|
|
11
16
|
import { boardUnit } from "./measures/routes.ts";
|
|
12
17
|
import { changeGraph, routeStability } from "./measures/stability.ts";
|
|
18
|
+
import { farLinks, measureBoard } from "./room.ts";
|
|
13
19
|
import { benchRun } from "./run.ts";
|
|
14
20
|
import { curveRuns, fixtureGroupOptions, FRONT_OBJECTIVES, groupRun, routeMeasures } from "./suite.ts";
|
|
15
21
|
|
|
@@ -42,11 +48,24 @@ function laneBoard(result: Result, lanes: { readonly width: number; readonly max
|
|
|
42
48
|
return { graph, open, width: lanes.width, ...(lanes.maxWidth === undefined ? {} : { maxWidth: lanes.maxWidth }) };
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The steps whose strategies give no routes alone. `compareAnyStep` and the screen compare each strategy of these steps as a router (`stepRouterStrategy`).
|
|
53
|
+
* The step `highway` gives a plan, and the step `plugs` gives the plugs.
|
|
54
|
+
* The step `levels` gives the zoom levels, and the step `whole` gives a board with its routes.
|
|
55
|
+
*/
|
|
56
|
+
export type RoutedStep = "plugs" | "highway" | "levels" | "whole";
|
|
57
|
+
|
|
58
|
+
/** The steps of `RoutedStep`, in the order of the pipeline. */
|
|
59
|
+
export const ROUTED_STEPS: readonly RoutedStep[] = ["plugs", "highway", "levels", "whole"];
|
|
60
|
+
|
|
61
|
+
/** This function tells if a step is a step of `RoutedStep`. */
|
|
62
|
+
export const isRoutedStep = (step: Step): step is RoutedStep => (ROUTED_STEPS as readonly Step[]).includes(step);
|
|
63
|
+
|
|
45
64
|
/**
|
|
46
65
|
* The steps that `compareStep` compares. The step `room` takes a packed board, not a graph, so it is not in the list.
|
|
47
|
-
* The
|
|
66
|
+
* The steps of `RoutedStep` give no routes alone, so they are not in the list. `compareAnyStep` compares them as routers.
|
|
48
67
|
*/
|
|
49
|
-
export type CompareStep = Exclude<Step, "room" |
|
|
68
|
+
export type CompareStep = Exclude<Step, "room" | RoutedStep>;
|
|
50
69
|
|
|
51
70
|
/** The steps that `compareStep` compares, in the order of the pipeline. */
|
|
52
71
|
export const COMPARE_STEPS: readonly CompareStep[] = ["group", "router", "curve"];
|
|
@@ -178,3 +197,153 @@ export function compareStep(step: CompareStep, graph: Graph, options: CompareOpt
|
|
|
178
197
|
excluded: rows.filter((r) => !fair.includes(r)).map((r) => r.strategy),
|
|
179
198
|
};
|
|
180
199
|
}
|
|
200
|
+
|
|
201
|
+
// The core runs in any host, so it reads the clock without the types of Node or the browser.
|
|
202
|
+
const now = (): number => (globalThis as { performance?: { now(): number } }).performance?.now() ?? Date.now();
|
|
203
|
+
|
|
204
|
+
/** The steps that `compareAnyStep` compares: each step of `STEPS`, in the order of the pipeline. */
|
|
205
|
+
export const COMPARE_ALL_STEPS: readonly Step[] = STEPS;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* This function gives a router for each highway strategy of a registry, in the order of the registry.
|
|
209
|
+
* Each router makes the plan of its strategy and routes the plan with `follow` (`highwayRouterStrategy`).
|
|
210
|
+
* The screen and `compareAnyStep` compare the highway strategies as these routers.
|
|
211
|
+
*/
|
|
212
|
+
export function highwayRouters(registry: Registry): Strategy<"router">[] {
|
|
213
|
+
return listStrategies(registry, "highway").map((s) => highwayRouterStrategy(s) as Strategy<"router">);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* This function gives a router strategy that runs a strategy of a step of `RoutedStep`. The router has the name, the options and the schema of the strategy.
|
|
218
|
+
* - A highway strategy makes its plan, and `follow` routes the plan (`highwayRouterStrategy`).
|
|
219
|
+
* - A plug rule gives the plugs of the router `corners` (its option `plug`).
|
|
220
|
+
* - A level rule gives the zoom levels of the router `sections` (its option `level`).
|
|
221
|
+
* - A whole strategy gives the routes of its output. A room strategy in its options can change the board, but the measures read the input graph.
|
|
222
|
+
*/
|
|
223
|
+
export function stepRouterStrategy(strategy: Strategy<RoutedStep>): Strategy<"router"> {
|
|
224
|
+
if (strategy.step === "highway") return highwayRouterStrategy(strategy as Strategy<"highway">) as Strategy<"router">;
|
|
225
|
+
const { name, describe, defaults } = strategy;
|
|
226
|
+
const own = (maxNodes: number | undefined, route: (graph: Graph, options: object) => Result): Strategy<"router"> =>
|
|
227
|
+
defineStrategy<"router", object>({
|
|
228
|
+
step: "router",
|
|
229
|
+
name,
|
|
230
|
+
describe,
|
|
231
|
+
defaults,
|
|
232
|
+
...(strategy.schema === undefined ? {} : { schema: strategy.schema }),
|
|
233
|
+
...(maxNodes === undefined ? {} : { maxNodes }),
|
|
234
|
+
run: route,
|
|
235
|
+
});
|
|
236
|
+
if (strategy.step === "plugs") return own(strategy.maxNodes ?? 10_000, (graph, o) => cornersRouter({ name, plug: strategy as Strategy<"plugs">, plugOptions: o }).route(graph));
|
|
237
|
+
if (strategy.step === "levels") return own(strategy.maxNodes ?? 100_000, (graph, o) => sectionsRouter({ name, level: strategy as Strategy<"levels">, levelOptions: o }).route(graph));
|
|
238
|
+
return own(strategy.maxNodes, (graph, o) => (strategy as Strategy<"whole">).run(graph, o).result);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** This function gives a router for each strategy of one step of `RoutedStep` in a registry, in the order of the registry (`stepRouterStrategy`). */
|
|
242
|
+
export function stepRouters(registry: Registry, step: RoutedStep): Strategy<"router">[] {
|
|
243
|
+
return (listStrategies(registry, step) as readonly Strategy<RoutedStep>[]).map((s) => stepRouterStrategy(s));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** One strategy of any step on one graph: a row of `compareStep`, or a row of the steps `highway` and `room`. */
|
|
247
|
+
export interface AnyCompareRow extends Omit<CompareRow, "step"> {
|
|
248
|
+
readonly step: Step;
|
|
249
|
+
/** The boxes of the board that the room strategy opens, in the order of the nodes, for the step `room`. */
|
|
250
|
+
readonly boxes?: readonly Box[];
|
|
251
|
+
/** The links of the far view on the board that the room strategy opens, for the step `room`. */
|
|
252
|
+
readonly links?: readonly LevelLink[];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The comparison of the strategies of any step on one graph. */
|
|
256
|
+
export interface AnyStepComparison extends Omit<StepComparison, "step" | "rows"> {
|
|
257
|
+
readonly step: Step;
|
|
258
|
+
/** One row for each strategy of the step, in the order of the registry. */
|
|
259
|
+
readonly rows: readonly AnyCompareRow[];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** The options of `compareAnyStep`. */
|
|
263
|
+
export interface AnyCompareOptions extends CompareOptions {
|
|
264
|
+
/** This function gets each row when its strategy is done, with the number of rows that are done and the number of all rows. A host can show the progress. */
|
|
265
|
+
readonly onRow?: (row: AnyCompareRow, done: number, total: number) => void;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** This function gives the area of the bounding box of the nodes. */
|
|
269
|
+
function boundsArea(nodes: readonly Node[]): number {
|
|
270
|
+
let [x0, y0, x1, y1] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
271
|
+
for (const n of nodes) {
|
|
272
|
+
x0 = Math.min(x0, n.box.x);
|
|
273
|
+
y0 = Math.min(y0, n.box.y);
|
|
274
|
+
x1 = Math.max(x1, n.box.x + n.box.w);
|
|
275
|
+
y1 = Math.max(y1, n.box.y + n.box.h);
|
|
276
|
+
}
|
|
277
|
+
return nodes.length ? (x1 - x0) * (y1 - y0) : 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* This function gives the measures of a board that a room strategy opened.
|
|
282
|
+
* It uses the keys and the lane spacing 4 of the step `room` of `runSuite`.
|
|
283
|
+
* The measures are the area against the board before (`M1.area`), the lane routes through a box (`M4.laneThrough`), the faults of the far view and `room-order`.
|
|
284
|
+
*/
|
|
285
|
+
export function roomValues(graph: Graph, open: readonly Node[]): Record<string, number> {
|
|
286
|
+
const area0 = boundsArea(graph.nodes);
|
|
287
|
+
const m = measureBoard({ nodes: open, edges: graph.edges }, 4);
|
|
288
|
+
return {
|
|
289
|
+
"M1.area": area0 > 0 ? boundsArea(open) / area0 : 1,
|
|
290
|
+
"M4.laneThrough": m.laneThrough,
|
|
291
|
+
crossings: m.crossings,
|
|
292
|
+
through: m.faults.through,
|
|
293
|
+
"R6.worstDetour": m.faults.worstDetour,
|
|
294
|
+
"room-order": roomOrderFaults(graph.nodes, open),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** This function runs one room strategy on the boxes of a graph, opens the gaps of the board and measures the open board. */
|
|
299
|
+
function roomRow(strategy: Strategy<"room">, graph: Graph, options: object | undefined): AnyCompareRow {
|
|
300
|
+
const t0 = now();
|
|
301
|
+
const gaps = strategy.run({ items: graph.nodes, links: roomLinks(graph) }, options);
|
|
302
|
+
const open = openGaps(graph.nodes, gaps);
|
|
303
|
+
const ms = now() - t0;
|
|
304
|
+
const values = { ...roomValues(graph, open), ms };
|
|
305
|
+
return { step: "room", strategy: strategy.name, violations: 0, ms, values, boxes: open.map((n) => n.box), links: farLinks({ nodes: open, edges: graph.edges }) };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** This function runs one strategy of one step on one graph, as `compareAnyStep` does. */
|
|
309
|
+
function compareOne(step: Step, strategy: AnyStrategy, graph: Graph, registry: Registry, options: CompareOptions): AnyCompareRow {
|
|
310
|
+
if (step === "room") return roomRow(strategy as Strategy<"room">, graph, options.options?.[strategy.name]);
|
|
311
|
+
if (isRoutedStep(step)) {
|
|
312
|
+
const one = createRegistry([stepRouterStrategy(strategy as Strategy<RoutedStep>) as AnyStrategy]);
|
|
313
|
+
return { ...compareStep("router", graph, { ...options, registry: one }).rows[0]!, step };
|
|
314
|
+
}
|
|
315
|
+
const curveRouter = step === "curve" ? [getStrategy(registry, "router", options.curveRouter ?? "corners") as AnyStrategy] : [];
|
|
316
|
+
return compareStep(step, graph, { ...options, registry: createRegistry([...curveRouter, strategy]) }).rows[0]!;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* This function runs each strategy of any step of `STEPS` on one graph and measures it. Then it gives the Pareto front of `FRONT_OBJECTIVES`.
|
|
321
|
+
* The steps `group`, `router` and `curve` give the rows of `compareStep`.
|
|
322
|
+
* The step `highway` makes the plan of each strategy and routes it with `follow`, as the screen does. It measures the routes as the step `router`.
|
|
323
|
+
* The steps `plugs`, `levels` and `whole` also run each strategy as a router (`stepRouterStrategy`), and measure the routes as the step `router`.
|
|
324
|
+
* The step `room` opens the gaps of the board with each strategy (`openGaps`). It measures the open board as the step `room` of `runSuite`.
|
|
325
|
+
* The step `room` expects a packed board, for example `packedGridBoard`. On a board with wide gaps, a gap opens only when its links need more room.
|
|
326
|
+
*
|
|
327
|
+
* The function runs one strategy at a time and gives each row to `onRow`. The same graph and the same options give the same measures. Only the times change.
|
|
328
|
+
*/
|
|
329
|
+
export function compareAnyStep(step: Step, graph: Graph, options: AnyCompareOptions = {}): AnyStepComparison {
|
|
330
|
+
const registry = options.registry ?? STRATEGIES;
|
|
331
|
+
const { onRow, ...rest } = options;
|
|
332
|
+
const strategies = listStrategies(registry, step) as readonly AnyStrategy[];
|
|
333
|
+
const rows: AnyCompareRow[] = [];
|
|
334
|
+
for (const strategy of strategies) {
|
|
335
|
+
const over = graph.nodes.length > (strategy.maxNodes ?? Infinity);
|
|
336
|
+
const row: AnyCompareRow = over ? { step, strategy: strategy.name, violations: 0, ms: 0, values: {}, skipped: `over ${strategy.maxNodes} nodes` } : compareOne(step, strategy, graph, registry, rest);
|
|
337
|
+
rows.push(row);
|
|
338
|
+
onRow?.(row, rows.length, strategies.length);
|
|
339
|
+
}
|
|
340
|
+
const objectives = FRONT_OBJECTIVES[step];
|
|
341
|
+
const fair = rows.filter((r) => r.violations === 0 && r.skipped === undefined);
|
|
342
|
+
return {
|
|
343
|
+
step,
|
|
344
|
+
rows,
|
|
345
|
+
objectives,
|
|
346
|
+
front: paretoFront(fair, (r) => r.values, objectives).map((r) => r.strategy),
|
|
347
|
+
excluded: rows.filter((r) => !fair.includes(r)).map((r) => r.strategy),
|
|
348
|
+
};
|
|
349
|
+
}
|
package/src/bench/room.ts
CHANGED
|
@@ -85,7 +85,7 @@ export function measureBoard(graph: Graph, spacing: number, order?: LaneOrder):
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/** This function gives the links of the far view of a board: `levelLinks` with each node as one item. */
|
|
88
|
-
function farLinks(graph: Graph): LevelLink[] {
|
|
88
|
+
export function farLinks(graph: Graph): LevelLink[] {
|
|
89
89
|
const items = graph.nodes.map((n) => ({ id: n.id, box: n.box, nodes: [n.id] }));
|
|
90
90
|
return levelLinks(
|
|
91
91
|
items,
|
package/src/bench/run.ts
CHANGED
|
@@ -27,6 +27,23 @@ export interface BenchRow {
|
|
|
27
27
|
readonly levelFaults: readonly LevelFaults[];
|
|
28
28
|
/** The routes of this cell took longer than the cell limit. The cell did not start more routes. A row within the limit has no such field. */
|
|
29
29
|
readonly overLimit?: OverLimit;
|
|
30
|
+
/** The time of the paired baseline arm (BENCH-GREEN). Only a cell with a baseline router has it. */
|
|
31
|
+
readonly paired?: Paired;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The paired baseline arm of a cell. The baseline router routes the same graph in turn with the router, on the same CPU.
|
|
36
|
+
* The ratio does not change with the speed of the runner, so the ratios of two runs compare better than their times.
|
|
37
|
+
*/
|
|
38
|
+
export interface Paired {
|
|
39
|
+
/** The name of the baseline router. */
|
|
40
|
+
readonly base: string;
|
|
41
|
+
/** The median time of one route of the router in the paired routes, in milliseconds. */
|
|
42
|
+
readonly ms: number;
|
|
43
|
+
/** The median time of one route of the baseline router, in milliseconds. */
|
|
44
|
+
readonly baseMs: number;
|
|
45
|
+
/** The ratio `ms / baseMs`. A ratio below 1 tells that the router is faster than the baseline. */
|
|
46
|
+
readonly ratio: number;
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
/** The record of a cell over its time limit. */
|
|
@@ -97,32 +114,49 @@ export interface BenchRun {
|
|
|
97
114
|
readonly result: Result;
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
/**
|
|
101
|
-
|
|
117
|
+
/**
|
|
118
|
+
* This function does the work of `benchOne` and also gives the result, so more measures can read it without a second route.
|
|
119
|
+
* With a baseline router `pair`, the baseline routes the graph one time for each route of the router, in turn: first before, then after.
|
|
120
|
+
* The row then gets `paired`. The baseline routes do not count for the cell limit, and their results do not change the row.
|
|
121
|
+
*/
|
|
122
|
+
export function benchRun(router: Router, fixture: string, graph: Graph, repeat = 3, cellLimit: number = Infinity, pair?: Router): BenchRun {
|
|
102
123
|
const times: number[] = [];
|
|
124
|
+
const baseTimes: number[] = [];
|
|
125
|
+
const timeBase = (): void => {
|
|
126
|
+
if (!pair) return;
|
|
127
|
+
const t0 = now();
|
|
128
|
+
pair.route(graph);
|
|
129
|
+
baseTimes.push(now() - t0);
|
|
130
|
+
};
|
|
103
131
|
let first: Result | undefined;
|
|
104
132
|
let deterministic = true;
|
|
105
133
|
let spent = 0;
|
|
106
134
|
for (let i = 0; i < Math.max(2, repeat); i++) {
|
|
135
|
+
// The order of the two arms changes at each route, so a warm cache helps each arm equally.
|
|
136
|
+
if (i % 2 === 0) timeBase();
|
|
107
137
|
const t0 = now();
|
|
108
138
|
const r = router.route(graph);
|
|
109
139
|
const t = now() - t0;
|
|
110
140
|
times.push(t);
|
|
111
141
|
spent += t;
|
|
142
|
+
if (i % 2 === 1) timeBase();
|
|
112
143
|
if (!first) first = r;
|
|
113
144
|
else if (i === 1 && !sameData(r, first)) deterministic = false;
|
|
114
145
|
if (spent > cellLimit) break;
|
|
115
146
|
}
|
|
116
147
|
const runs = times.length;
|
|
117
148
|
times.sort((a, b) => a - b);
|
|
149
|
+
baseTimes.sort((a, b) => a - b);
|
|
118
150
|
const result = first!;
|
|
151
|
+
const ms = times[Math.floor(times.length / 2)]!;
|
|
152
|
+
const baseMs = baseTimes[Math.floor(baseTimes.length / 2)];
|
|
119
153
|
const all = checkProperties(graph, result, 1_000_000);
|
|
120
154
|
const row: BenchRow = {
|
|
121
155
|
router: router.name,
|
|
122
156
|
fixture,
|
|
123
157
|
nodes: graph.nodes.length,
|
|
124
158
|
edges: graph.edges.length,
|
|
125
|
-
ms
|
|
159
|
+
ms,
|
|
126
160
|
deterministic,
|
|
127
161
|
violations: all.length,
|
|
128
162
|
sample: all.slice(0, 5),
|
|
@@ -130,6 +164,7 @@ export function benchRun(router: Router, fixture: string, graph: Graph, repeat =
|
|
|
130
164
|
faults: measureRouteFaults(graph, result),
|
|
131
165
|
levelFaults: measureLevelFaults(result),
|
|
132
166
|
...(spent > cellLimit ? { overLimit: { limit: cellLimit, runs } } : {}),
|
|
167
|
+
...(pair && baseMs !== undefined ? { paired: { base: pair.name, ms, baseMs, ratio: baseMs > 0 ? ms / baseMs : 0 } } : {}),
|
|
133
168
|
};
|
|
134
169
|
return { row, result };
|
|
135
170
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { createFamilyRegistry, RECT_SCATTERED } from "@mark1russell7/layout-contract";
|
|
2
|
+
import type { LayoutInput } from "./forms.ts";
|
|
3
3
|
|
|
4
|
-
/** This function gives a whole number from `lo` to `hi`, both included, from a generator of random numbers. */
|
|
5
|
-
export
|
|
6
|
-
return lo + Math.floor(r() * (hi - lo + 1));
|
|
7
|
-
}
|
|
4
|
+
/** This function gives a whole number from `lo` to `hi`, both included, from a generator of random numbers. It is the shared function of `@mark1russell7/layout-contract`. */
|
|
5
|
+
export { randomInt } from "@mark1russell7/layout-contract";
|
|
8
6
|
|
|
9
7
|
/** The parameters of the generator `rect-scattered`. */
|
|
10
8
|
export interface RectScatteredParams {
|
|
@@ -12,43 +10,15 @@ export interface RectScatteredParams {
|
|
|
12
10
|
readonly count: number;
|
|
13
11
|
}
|
|
14
12
|
|
|
13
|
+
/** The registry of the shared family `rect-scattered` (CORPUS-GEN). highways has no copy of its generator. */
|
|
14
|
+
const SHARED = createFamilyRegistry([RECT_SCATTERED]);
|
|
15
|
+
|
|
15
16
|
/**
|
|
16
17
|
* This function makes the input of the shared generator `rect-scattered`, version 1: a fixed rectangle container, scattered obstacles and boxes without positions.
|
|
17
|
-
* The repository `packing` makes the same file, byte for byte.
|
|
18
|
-
*
|
|
18
|
+
* It uses the shared family `RECT_SCATTERED` of `@mark1russell7/layout-contract` (CORPUS-GEN). The repository `packing` makes the same file, byte for byte.
|
|
19
|
+
*
|
|
20
|
+
* @deprecated Use `RECT_SCATTERED` on a registry of `createFamilyRegistry`. This function stays for the callers of today.
|
|
19
21
|
*/
|
|
20
22
|
export function rectScattered(seed: number, params: RectScatteredParams, set = "xcheck-v1"): LayoutInput {
|
|
21
|
-
|
|
22
|
-
const w = randomInt(r, 600, 1000);
|
|
23
|
-
const h = randomInt(r, 400, 800);
|
|
24
|
-
r = mulberry32(stepSeed(seed, "obstacles"));
|
|
25
|
-
const n = randomInt(r, 3, 8);
|
|
26
|
-
const obstacles: LayoutObstacle[] = [];
|
|
27
|
-
for (let i = 0; i < n; i++) {
|
|
28
|
-
const ow = randomInt(r, 40, 160);
|
|
29
|
-
const oh = randomInt(r, 40, 160);
|
|
30
|
-
const x = randomInt(r, 0, w - ow);
|
|
31
|
-
const y = randomInt(r, 0, h - oh);
|
|
32
|
-
obstacles.push({ id: `o${i}`, region: { kind: "rect", x, y, w: ow, h: oh } });
|
|
33
|
-
}
|
|
34
|
-
r = mulberry32(stepSeed(seed, "boxes"));
|
|
35
|
-
const boxes = Array.from({ length: params.count }, (_, i) => {
|
|
36
|
-
const bw = randomInt(r, 20, 120);
|
|
37
|
-
const bh = randomInt(r, 20, 80);
|
|
38
|
-
return { id: `b${i}`, w: bw, h: bh };
|
|
39
|
-
});
|
|
40
|
-
const body = {
|
|
41
|
-
schema: "layout-input/1" as const,
|
|
42
|
-
id: `${set}/rect-scattered/s${seed}`,
|
|
43
|
-
set,
|
|
44
|
-
family: "rect-scattered",
|
|
45
|
-
seed,
|
|
46
|
-
source: "made-up" as const,
|
|
47
|
-
generator: { name: "rect-scattered", version: 1, params: { count: params.count }, seed },
|
|
48
|
-
container: { w, h, fixed: "size" as const },
|
|
49
|
-
obstacles,
|
|
50
|
-
boxes,
|
|
51
|
-
edges: [],
|
|
52
|
-
};
|
|
53
|
-
return { ...body, hash: documentHash(body) };
|
|
23
|
+
return SHARED.generate("rect-scattered", seed, { count: params.count }, { set }) as unknown as LayoutInput;
|
|
54
24
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { highwayRouterStrategy } from "../../highways/router.ts";
|
|
2
1
|
import type { Graph, Result, Route } from "../../model.ts";
|
|
3
2
|
import { stableOrderStrategy } from "../../order/index.ts";
|
|
4
3
|
import { repairStrategy } from "../../repair/index.ts";
|
|
5
4
|
import { STRATEGIES } from "../../strategies/builtins.ts";
|
|
6
5
|
import { asRouter, createRegistry, getStrategy, listStrategies, register, STEPS, type AnyStrategy, type Registry, type Strategy } from "../../strategies/model.ts";
|
|
7
|
-
import { compareStep, type CompareRow, type CompareStep, type StepComparison } from "../compare.ts";
|
|
6
|
+
import { compareStep, highwayRouters, isRoutedStep, stepRouters, type CompareRow, type CompareStep, type RoutedStep, type StepComparison } from "../compare.ts";
|
|
8
7
|
import { bundledShare, bundleStretch, mergeAngle } from "../measures/bundles.ts";
|
|
9
8
|
import { highwayEdits } from "../measures/highways.ts";
|
|
10
9
|
import { laneArea, laneCrossings, laneFidelity, lanesThrough, occupiedArea, openLanes, orthogonality, routeSeparation, runProfile } from "../measures/lanes.ts";
|
|
@@ -27,13 +26,14 @@ import { measureByName, measureName, namedObjectives } from "./measures.ts";
|
|
|
27
26
|
import { SCREEN_V1, screenInputs, type ScreenSet } from "./set.ts";
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
|
-
* The steps of the screen: the steps of `compareStep` and the
|
|
29
|
+
* The steps of the screen: the steps of `compareStep` and the steps of `RoutedStep`.
|
|
31
30
|
* The screen of the step `highway` routes the plan of each strategy with `follow`, and measures the routes as the routes of a router.
|
|
31
|
+
* The screen of the steps `plugs`, `levels` and `whole` runs each strategy as a router (`stepRouterStrategy`). It measures the routes as the routes of a router.
|
|
32
32
|
*/
|
|
33
|
-
export type ScreenStep = CompareStep |
|
|
33
|
+
export type ScreenStep = CompareStep | RoutedStep;
|
|
34
34
|
|
|
35
35
|
/** The steps of the screen, in the order of the pipeline. */
|
|
36
|
-
export const SCREEN_STEPS: readonly ScreenStep[] = ["group", "router", "highway", "curve"];
|
|
36
|
+
export const SCREEN_STEPS: readonly ScreenStep[] = ["group", "plugs", "router", "highway", "levels", "curve", "whole"];
|
|
37
37
|
|
|
38
38
|
/** The routers that the screen of the step `highway` also runs by default. They are the baselines of the note on highways. */
|
|
39
39
|
export const HIGHWAY_BASE_ROUTERS: readonly string[] = ["corners", "shared", "trunks"];
|
|
@@ -54,6 +54,9 @@ const ROUTE_RULES: readonly string[] = ["complete", "ends-at-plugs", "fixed-plug
|
|
|
54
54
|
export const SCREEN_RULES: Readonly<Record<ScreenStep, readonly string[]>> = {
|
|
55
55
|
router: ROUTE_RULES,
|
|
56
56
|
highway: ROUTE_RULES,
|
|
57
|
+
plugs: ROUTE_RULES,
|
|
58
|
+
levels: ROUTE_RULES,
|
|
59
|
+
whole: ROUTE_RULES,
|
|
57
60
|
group: ["sections", "counts-sum", "link-ends", "same-twice"],
|
|
58
61
|
curve: ["same-twice"],
|
|
59
62
|
};
|
|
@@ -221,8 +224,8 @@ function screenRegistry(step: ScreenStep, strategies: readonly (Strategy<ScreenS
|
|
|
221
224
|
return { registry: createRegistry([...others, ...(own as readonly AnyStrategy[])]), candidates };
|
|
222
225
|
}
|
|
223
226
|
|
|
224
|
-
/** This function tells if a step of the screen gives routes. The step `router` gives routes, and
|
|
225
|
-
const givesRoutes = (step: ScreenStep): step is "router" |
|
|
227
|
+
/** 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. */
|
|
228
|
+
const givesRoutes = (step: ScreenStep): step is "router" | RoutedStep => step === "router" || isRoutedStep(step);
|
|
226
229
|
|
|
227
230
|
/**
|
|
228
231
|
* This function gives the values of a row of `compareStep`, with the measures of `measureQuality` for a router.
|
|
@@ -347,14 +350,17 @@ export function screen(step: ScreenStep, strategies: readonly (Strategy<ScreenSt
|
|
|
347
350
|
const candidates = screened.candidates;
|
|
348
351
|
// The step `highway` runs as the step `router`. Each highway strategy with `follow` is a router, after the base routers.
|
|
349
352
|
const base = step === "highway" ? (options.baseRouters ?? HIGHWAY_BASE_ROUTERS).map((name) => getStrategy(STRATEGIES, "router", name)) : [];
|
|
353
|
+
// The steps `plugs`, `levels` and `whole` run each strategy as one router, with no base routers.
|
|
350
354
|
const unwrapped =
|
|
351
355
|
step === "highway"
|
|
352
|
-
? createRegistry([...base, ...
|
|
353
|
-
:
|
|
356
|
+
? createRegistry([...base, ...highwayRouters(screened.registry)] as readonly AnyStrategy[])
|
|
357
|
+
: isRoutedStep(step)
|
|
358
|
+
? createRegistry(stepRouters(screened.registry, step) as readonly AnyStrategy[])
|
|
359
|
+
: screened.registry;
|
|
354
360
|
const wrapped = givesRoutes(step) ? wrapRouters(unwrapped, options.wrap ?? []) : { registry: unwrapped, added: [] };
|
|
355
361
|
const registry = wrapped.registry;
|
|
356
362
|
for (const name of wrapped.added) candidates.add(name);
|
|
357
|
-
const compareAs: CompareStep = step
|
|
363
|
+
const compareAs: CompareStep = isRoutedStep(step) ? "router" : step;
|
|
358
364
|
const baseNames = new Set(base.map((s) => s.name));
|
|
359
365
|
const stepOf = (name: string): ScreenStep => (baseNames.has(unwrappedName(name)) ? "router" : step);
|
|
360
366
|
// A wrapped router gets the options of its router, unless the call gives its own.
|