@mark1russell7/highways 0.2.0 → 0.4.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 +265 -9
- package/build/index.d.ts +2168 -101
- package/build/index.d.ts.map +1 -1
- package/build/index.js +11624 -3422
- package/build/index.js.map +1 -1
- package/package.json +4 -1
- package/src/bench/compare.ts +180 -0
- package/src/bench/curves.ts +514 -2
- package/src/bench/links.ts +28 -6
- package/src/bench/measures/bundles.ts +119 -0
- package/src/bench/measures/curves.ts +211 -0
- package/src/bench/measures/groups.ts +122 -0
- package/src/bench/measures/index.ts +9 -0
- package/src/bench/measures/levels.ts +103 -0
- package/src/bench/measures/pareto.ts +37 -0
- package/src/bench/measures/room.ts +43 -0
- package/src/bench/measures/routes.ts +226 -0
- package/src/bench/measures/stability.ts +104 -0
- package/src/bench/measures/stats.ts +114 -0
- package/src/bench/properties.ts +18 -2
- package/src/bench/room.ts +209 -2
- package/src/bench/run.ts +86 -11
- package/src/bench/screen/forms.ts +372 -0
- package/src/bench/screen/generators.ts +54 -0
- package/src/bench/screen/hash.ts +129 -0
- package/src/bench/screen/index.ts +6 -0
- package/src/bench/screen/measures.ts +114 -0
- package/src/bench/screen/screen.ts +390 -0
- package/src/bench/screen/set.ts +136 -0
- package/src/bench/suite.ts +839 -0
- package/src/cases.ts +41 -5
- package/src/curves/clothoid/index.ts +150 -0
- package/src/curves/corner.ts +116 -0
- package/src/curves/fit.ts +79 -0
- package/src/curves/highway/index.ts +636 -0
- package/src/curves/index.ts +16 -1
- package/src/curves/model.ts +4 -2
- package/src/curves/smooth/index.ts +50 -0
- package/src/curves/space.ts +76 -0
- package/src/groups/cut/index.ts +292 -0
- package/src/groups/index.ts +2 -0
- package/src/groups/kd/index.ts +207 -0
- package/src/highways/corridors/index.ts +305 -0
- package/src/highways/follow.ts +155 -0
- package/src/highways/index.ts +7 -0
- package/src/highways/keys/index.ts +15 -0
- package/src/highways/model.ts +111 -0
- package/src/highways/result.ts +34 -0
- package/src/highways/router.ts +40 -0
- package/src/highways/shared-ways/index.ts +15 -0
- package/src/index.ts +6 -0
- package/src/levels.ts +163 -5
- package/src/room/bends.ts +203 -0
- package/src/room/index.ts +28 -0
- package/src/room/levels/index.ts +155 -0
- package/src/routers/sections.ts +15 -40
- package/src/routers/shared.ts +7 -2
- package/src/routers/trunks.ts +131 -6
- package/src/sections.ts +47 -1
- package/src/segments.ts +81 -31
- package/src/strategies/builtins.ts +206 -9
- package/src/strategies/index.ts +1 -0
- package/src/strategies/model.ts +55 -7
- package/src/strategies/options.ts +296 -0
- package/src/strategies/schemas.ts +234 -0
- package/src/visibility.ts +190 -1
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.4.0",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "./build/index.js",
|
|
15
15
|
"types": "./build/index.d.ts",
|
|
@@ -38,8 +38,11 @@
|
|
|
38
38
|
"build": "tsdown",
|
|
39
39
|
"test": "vitest run",
|
|
40
40
|
"bench": "node scripts/bench.ts",
|
|
41
|
+
"bench:join": "node scripts/bench-join.ts",
|
|
41
42
|
"profile": "node scripts/profile.ts",
|
|
42
43
|
"levels": "node scripts/levels.ts",
|
|
44
|
+
"screen": "node scripts/screen.ts",
|
|
45
|
+
"screen:inputs": "node scripts/screen-inputs.ts",
|
|
43
46
|
"test:watch": "vitest",
|
|
44
47
|
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json"
|
|
45
48
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { CurveSegment } from "../curves/model.ts";
|
|
2
|
+
import { polylineLength } from "../geom.ts";
|
|
3
|
+
import type { Graph, Result } from "../model.ts";
|
|
4
|
+
import type { Section } from "../sections.ts";
|
|
5
|
+
import { runKey, segmentsOf } from "../segments.ts";
|
|
6
|
+
import { STRATEGIES } from "../strategies/builtins.ts";
|
|
7
|
+
import { asRouter, getStrategy, listStrategies, type LaneBoard, type Registry, type Step } from "../strategies/model.ts";
|
|
8
|
+
import { peakCurvature } from "./curves.ts";
|
|
9
|
+
import { measureLevelFaults, sumFaults } from "./links.ts";
|
|
10
|
+
import { paretoFront, type Objective } from "./measures/pareto.ts";
|
|
11
|
+
import { boardUnit } from "./measures/routes.ts";
|
|
12
|
+
import { changeGraph, routeStability } from "./measures/stability.ts";
|
|
13
|
+
import { benchRun } from "./run.ts";
|
|
14
|
+
import { curveRuns, fixtureGroupOptions, FRONT_OBJECTIVES, groupRun, routeMeasures } from "./suite.ts";
|
|
15
|
+
|
|
16
|
+
/** This function gives the median of a list that can hold `Infinity`. An empty list gives 0. */
|
|
17
|
+
function medianOf(values: readonly number[]): number {
|
|
18
|
+
if (values.length === 0) return 0;
|
|
19
|
+
const s = [...values].sort((a, b) => a - b);
|
|
20
|
+
const mid = s.length >> 1;
|
|
21
|
+
if (s.length % 2 === 1) return s[mid]!;
|
|
22
|
+
const [a, b] = [s[mid - 1]!, s[mid]!];
|
|
23
|
+
return a === b ? a : (a + b) / 2;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* This function gives the measures of the level links of a result: the links through a box (`Z3.through`) and the length of all links (`Z3.length`).
|
|
28
|
+
* A result without a level with groups gives no measure.
|
|
29
|
+
*/
|
|
30
|
+
function levelLinkMeasures(result: Result): Record<string, number> {
|
|
31
|
+
const levels = (result.levels ?? []).filter((l) => l.items.some((i) => i.nodes.length > 1));
|
|
32
|
+
if (levels.length === 0) return {};
|
|
33
|
+
const length = levels.reduce((sum, l) => sum + l.links.reduce((s, k) => s + polylineLength(k.points), 0), 0);
|
|
34
|
+
return { "Z3.through": sumFaults(measureLevelFaults(result)).through, "Z3.length": length };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** This function gives the lanes of a result with each shared segment open. A result without shared segments gives `undefined`. */
|
|
38
|
+
function laneBoard(result: Result, lanes: { readonly width: number; readonly maxWidth?: number }): LaneBoard | undefined {
|
|
39
|
+
const graph = segmentsOf(result);
|
|
40
|
+
const open = new Set(graph.segments.filter((s) => s.count > 1).map((s) => runKey(s.edges)));
|
|
41
|
+
if (open.size === 0) return undefined;
|
|
42
|
+
return { graph, open, width: lanes.width, ...(lanes.maxWidth === undefined ? {} : { maxWidth: lanes.maxWidth }) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The steps that `compareStep` compares. The step `room` takes a packed board, not a graph, so it is not in the list.
|
|
47
|
+
* The step `highway` gives a plan, not routes. The screen routes each plan with `follow` and compares it as a router.
|
|
48
|
+
*/
|
|
49
|
+
export type CompareStep = Exclude<Step, "room" | "highway">;
|
|
50
|
+
|
|
51
|
+
/** The steps that `compareStep` compares, in the order of the pipeline. */
|
|
52
|
+
export const COMPARE_STEPS: readonly CompareStep[] = ["group", "router", "curve"];
|
|
53
|
+
|
|
54
|
+
/** The options of `compareStep`. */
|
|
55
|
+
export interface CompareOptions {
|
|
56
|
+
/** The strategies of each step. The default is the registry of the built-in strategies. */
|
|
57
|
+
readonly registry?: Registry;
|
|
58
|
+
/** The options of each strategy of the step, by its name. A strategy without an entry gets its defaults. */
|
|
59
|
+
readonly options?: Readonly<Record<string, object>>;
|
|
60
|
+
/** The router whose routes the step `curve` draws. The default is `corners`, as in `runSuite`. */
|
|
61
|
+
readonly curveRouter?: string;
|
|
62
|
+
/**
|
|
63
|
+
* With this value, the step `curve` opens each shared segment of the routes of `curveRouter`, and each curve draws the lanes of `laneRoutes`.
|
|
64
|
+
* So the curve `highway` bends the lanes of a bundle as one. Without it, each curve draws the routes. A router without shared segments gives the routes in both cases.
|
|
65
|
+
*/
|
|
66
|
+
readonly lanes?: { readonly width: number; readonly maxWidth?: number };
|
|
67
|
+
/** The seed of the small change of the graph for the stability measures (Z10 and G9). The default is 1. */
|
|
68
|
+
readonly seed?: number;
|
|
69
|
+
/** The route stability (Z10) routes a changed graph again, so the function measures it only up to this number of nodes. The default is 1000. */
|
|
70
|
+
readonly stabilityMax?: number;
|
|
71
|
+
/** The name of the graph in the rows. The default is `board`. */
|
|
72
|
+
readonly name?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** One strategy of one step on one graph: its measures and its output. */
|
|
76
|
+
export interface CompareRow {
|
|
77
|
+
readonly step: CompareStep;
|
|
78
|
+
readonly strategy: string;
|
|
79
|
+
/** The violations of the hard rules, as `runSuite` counts them. A strategy with a violation does not enter the front. */
|
|
80
|
+
readonly violations: number;
|
|
81
|
+
/** The time of the step, in milliseconds. For the step `router`, it is the median of two routes. */
|
|
82
|
+
readonly ms: number;
|
|
83
|
+
/**
|
|
84
|
+
* The measures, by the keys of `runSuite`, and `ms`. The step `router` adds `ink`, and the step `curve` adds `C5.peak`.
|
|
85
|
+
* A router with zoom levels also gets `Z3.through` and `Z3.length`, the measures of its level links.
|
|
86
|
+
*/
|
|
87
|
+
readonly values: Readonly<Record<string, number>>;
|
|
88
|
+
/** The reason when the strategy did not run, for example a graph over its `maxNodes`. */
|
|
89
|
+
readonly skipped?: string;
|
|
90
|
+
/** The result of the router, for the steps `router` and `group`. The step `group` routes with `sections` on the tree of the rule. */
|
|
91
|
+
readonly result?: Result;
|
|
92
|
+
/** The tree of the grouping rule, for the step `group`. */
|
|
93
|
+
readonly tree?: Section;
|
|
94
|
+
/** The segments of each curve, for the step `curve`, in the order of `edges`. */
|
|
95
|
+
readonly curves?: readonly CurveSegment[][];
|
|
96
|
+
/** The edge of each curve of `curves`. */
|
|
97
|
+
readonly edges?: readonly string[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The comparison of the strategies of one step on one graph. */
|
|
101
|
+
export interface StepComparison {
|
|
102
|
+
readonly step: CompareStep;
|
|
103
|
+
/** One row for each strategy of the step, in the order of the registry. */
|
|
104
|
+
readonly rows: readonly CompareRow[];
|
|
105
|
+
/** The axes of the Pareto front: `FRONT_OBJECTIVES` of the step. */
|
|
106
|
+
readonly objectives: readonly Objective[];
|
|
107
|
+
/** The strategies on the Pareto front, in the order of the registry. */
|
|
108
|
+
readonly front: readonly string[];
|
|
109
|
+
/** The strategies with a violation of a hard rule, or that did not run. They do not enter the front. */
|
|
110
|
+
readonly excluded: readonly string[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* This function runs each strategy of one step on one graph and measures it with the measures of `runSuite`. Then it gives the Pareto front.
|
|
115
|
+
* The step `router` routes the graph with each router. The step `curve` draws the routes of `curveRouter` with each curve, or their lanes with the option `lanes`.
|
|
116
|
+
* The step `group` routes the graph with `sections` on the tree of each grouping rule.
|
|
117
|
+
*
|
|
118
|
+
* The same graph and the same options give the same measures. Only the times change.
|
|
119
|
+
*/
|
|
120
|
+
export function compareStep(step: CompareStep, graph: Graph, options: CompareOptions = {}): StepComparison {
|
|
121
|
+
const registry = options.registry ?? STRATEGIES;
|
|
122
|
+
const own = options.options ?? {};
|
|
123
|
+
const name = options.name ?? "board";
|
|
124
|
+
const seed = options.seed ?? 1;
|
|
125
|
+
const stabilityMax = options.stabilityMax ?? 1000;
|
|
126
|
+
const at = { fixture: name, seed: 0 };
|
|
127
|
+
let rows: CompareRow[];
|
|
128
|
+
if (step === "router") {
|
|
129
|
+
const change = graph.nodes.length <= stabilityMax ? changeGraph(graph, seed) : undefined;
|
|
130
|
+
rows = listStrategies(registry, "router").map((strategy): CompareRow => {
|
|
131
|
+
if (graph.nodes.length > (strategy.maxNodes ?? Infinity)) return { step, strategy: strategy.name, violations: 0, ms: 0, values: {}, skipped: `over ${strategy.maxNodes} nodes` };
|
|
132
|
+
const router = asRouter(strategy, own[strategy.name]);
|
|
133
|
+
const { row, result } = benchRun(router, name, graph, 2);
|
|
134
|
+
const values: Record<string, number> = { ...routeMeasures(graph, result, row), ...levelLinkMeasures(result), ink: row.quality.ink, ms: row.ms };
|
|
135
|
+
if (change) {
|
|
136
|
+
const s = routeStability(graph, result, router.route(change.graph), { seed });
|
|
137
|
+
values["Z10.mean"] = s.mean;
|
|
138
|
+
values["Z10.max"] = s.max;
|
|
139
|
+
values["Z10.moved"] = s.moved;
|
|
140
|
+
}
|
|
141
|
+
return { step, strategy: strategy.name, violations: row.violations + (row.deterministic ? 0 : 1), ms: row.ms, values, result };
|
|
142
|
+
});
|
|
143
|
+
} else if (step === "curve") {
|
|
144
|
+
const router = asRouter(getStrategy(registry, "router", options.curveRouter ?? "corners"));
|
|
145
|
+
const result = router.route(graph);
|
|
146
|
+
const u = boardUnit(graph);
|
|
147
|
+
const lanes = options.lanes && laneBoard(result, options.lanes);
|
|
148
|
+
const runs = curveRuns(graph, result, listStrategies(registry, "curve"), {
|
|
149
|
+
...at,
|
|
150
|
+
smooth: new Set(["rounded", "arc", "spline", "smooth", "clothoid", "highway"]),
|
|
151
|
+
normals: new Set(["spline"]),
|
|
152
|
+
curveOptions: own,
|
|
153
|
+
...(lanes ? { lanes } : {}),
|
|
154
|
+
});
|
|
155
|
+
rows = runs.map(({ row, drawn, edges }): CompareRow => {
|
|
156
|
+
const peaks = drawn.map((d) => peakCurvature(d) * u);
|
|
157
|
+
const values = { ...row.values, "C5.peak": medianOf(peaks), ms: row.ms };
|
|
158
|
+
return { step, strategy: row.strategy, violations: row.violations, ms: row.ms, values, curves: drawn, edges };
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
const change = changeGraph(graph, seed);
|
|
162
|
+
rows = listStrategies(registry, "group").map((strategy): CompareRow => {
|
|
163
|
+
const opts = { ...fixtureGroupOptions(strategy.name, graph), ...own[strategy.name] };
|
|
164
|
+
const changed = { ...fixtureGroupOptions(strategy.name, change.graph), ...own[strategy.name] };
|
|
165
|
+
const run = groupRun(graph, strategy, opts, { graph: change.graph, options: changed }, at);
|
|
166
|
+
const ms = run.row.treeMs ?? run.row.ms;
|
|
167
|
+
const values = { ...run.row.values, ms };
|
|
168
|
+
return { step, strategy: strategy.name, violations: run.row.violations, ms, values, tree: run.tree, ...(run.result ? { result: run.result } : {}) };
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const objectives = FRONT_OBJECTIVES[step];
|
|
172
|
+
const fair = rows.filter((r) => r.violations === 0 && r.skipped === undefined);
|
|
173
|
+
return {
|
|
174
|
+
step,
|
|
175
|
+
rows,
|
|
176
|
+
objectives,
|
|
177
|
+
front: paretoFront(fair, (r) => r.values, objectives).map((r) => r.strategy),
|
|
178
|
+
excluded: rows.filter((r) => !fair.includes(r)).map((r) => r.strategy),
|
|
179
|
+
};
|
|
180
|
+
}
|