@mark1russell7/highways 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -8
- package/build/index.d.ts +989 -187
- package/build/index.d.ts.map +1 -1
- package/build/index.js +9761 -6738
- 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/exercise.ts +5 -0
- package/src/bench/screen/forms.ts +5 -1
- 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/fixtures.test-util.ts +13 -0
- package/src/highways/follow.ts +3 -2
- package/src/highways/free-lines.ts +141 -0
- package/src/highways/gaps/index.ts +85 -0
- package/src/highways/index.ts +6 -0
- package/src/highways/interchanges/index.ts +231 -0
- package/src/highways/search/index.ts +170 -0
- package/src/highways/snap.ts +4 -2
- package/src/highways/soft-keys/index.ts +8 -4
- package/src/highways/steiner/index.ts +215 -0
- package/src/highways/streets/index.ts +78 -0
- 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/model.ts +60 -4
- package/src/plugs/facing/index.ts +22 -0
- package/src/portfolio/router.ts +44 -0
- package/src/routers/attraction/index.ts +61 -0
- package/src/routers/corners.ts +31 -7
- package/src/routers/index.ts +2 -1
- package/src/routers/plugs.ts +3 -2
- package/src/routers/sections.ts +25 -42
- package/src/routers/shared.ts +96 -14
- package/src/routers/straight.ts +3 -2
- package/src/routers/trunks.ts +24 -5
- package/src/segments.ts +4 -25
- package/src/strategies/builtins.ts +109 -2
- package/src/strategies/model.ts +55 -10
- package/src/strategies/options.ts +36 -4
- package/src/strategies/schemas.ts +124 -5
- package/src/whole/pipeline/index.ts +66 -0
|
@@ -26,6 +26,11 @@ export interface NumberOption extends OptionBase {
|
|
|
26
26
|
readonly special?: readonly number[];
|
|
27
27
|
/** These values are the notable values of the option: the default and the values of the sweeps of the benchmark. */
|
|
28
28
|
readonly marks?: readonly number[];
|
|
29
|
+
/**
|
|
30
|
+
* These values are the only valid values of the option, in the order of a control. A control shows them as a list, and a sweep gives them.
|
|
31
|
+
* Without them, each value on the range is valid.
|
|
32
|
+
*/
|
|
33
|
+
readonly values?: readonly number[];
|
|
29
34
|
/** The strategy uses this value when the caller and `defaults` give no value. */
|
|
30
35
|
readonly default?: number;
|
|
31
36
|
}
|
|
@@ -42,6 +47,11 @@ export interface EnumOption<V extends string = string> extends OptionBase {
|
|
|
42
47
|
readonly type: "enum";
|
|
43
48
|
/** The values of the option, in the order of a control. */
|
|
44
49
|
readonly values: readonly V[];
|
|
50
|
+
/**
|
|
51
|
+
* With `function`, the option also takes a rule of the caller in place of a name. A control sets only the names.
|
|
52
|
+
* Each name is a known rule of the strategy.
|
|
53
|
+
*/
|
|
54
|
+
readonly custom?: "function";
|
|
45
55
|
/** The strategy uses this value when the caller and `defaults` give no value. */
|
|
46
56
|
readonly default?: V;
|
|
47
57
|
}
|
|
@@ -61,8 +71,19 @@ export interface ValueOption<V = unknown> extends OptionBase {
|
|
|
61
71
|
/** The declaration of one option of any type. */
|
|
62
72
|
export type AnyOption = NumberOption | BooleanOption | EnumOption | ValueOption;
|
|
63
73
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The declaration that fits the type of an option value: numbers, booleans, string unions or other values.
|
|
76
|
+
* A union of strings and other values takes an enum of the strings or a value declaration. An example is a rule or the name of a rule.
|
|
77
|
+
*/
|
|
78
|
+
export type OptionDeclaration<V> = [V] extends [number]
|
|
79
|
+
? NumberOption
|
|
80
|
+
: [V] extends [boolean]
|
|
81
|
+
? BooleanOption
|
|
82
|
+
: [V] extends [string]
|
|
83
|
+
? EnumOption<V>
|
|
84
|
+
: [Extract<V, string>] extends [never]
|
|
85
|
+
? ValueOption<V>
|
|
86
|
+
: EnumOption<Extract<V, string>> | ValueOption<V>;
|
|
66
87
|
|
|
67
88
|
/** The declarations of the options of a strategy. The schema has one declaration for each key of the options type, and no other key. */
|
|
68
89
|
export type OptionSchema<O> = { readonly [K in keyof O]-?: OptionDeclaration<NonNullable<O[K]>> };
|
|
@@ -139,6 +160,7 @@ function issueOf(key: string, value: unknown, d: AnyOption): OptionIssue | undef
|
|
|
139
160
|
case "integer": {
|
|
140
161
|
if (typeof value !== "number" || Number.isNaN(value)) return issue("type", `The option "${key}" takes a number.`);
|
|
141
162
|
if (isSpecial(d, value)) return undefined;
|
|
163
|
+
if (d.values !== undefined && !d.values.includes(value)) return issue("value", `The option "${key}" takes ${d.values.join(", ")}, not ${value}.`);
|
|
142
164
|
if (value < d.min || value > d.max) return issue("range", `The option "${key}" is ${value}, out of the range ${d.min} to ${d.max}.`);
|
|
143
165
|
if (d.type === "integer" && !Number.isInteger(value)) return issue("integer", `The option "${key}" takes a whole number, not ${value}.`);
|
|
144
166
|
return undefined;
|
|
@@ -146,6 +168,7 @@ function issueOf(key: string, value: unknown, d: AnyOption): OptionIssue | undef
|
|
|
146
168
|
case "boolean":
|
|
147
169
|
return typeof value === "boolean" ? undefined : issue("type", `The option "${key}" takes true or false.`);
|
|
148
170
|
case "enum":
|
|
171
|
+
if (d.custom === "function" && typeof value === "function") return undefined;
|
|
149
172
|
return d.values.includes(value as string) ? undefined : issue("value", `The option "${key}" takes ${d.values.join(", ")}, not ${String(value)}.`);
|
|
150
173
|
case "function":
|
|
151
174
|
return typeof value === "function" ? undefined : issue("type", `The option "${key}" takes a function.`);
|
|
@@ -176,7 +199,7 @@ export function checkOptions<S extends Step, O extends object>(strategy: Strateg
|
|
|
176
199
|
|
|
177
200
|
/**
|
|
178
201
|
* This function moves each number option into the range of its declaration, for a control. An integer option gets the nearest whole number.
|
|
179
|
-
* A special value stays. The function keeps each other value as it is. `checkOptions` reports the values that this function cannot correct.
|
|
202
|
+
* An option with a list of `values` gets the nearest value of the list. A special value stays. The function keeps each other value as it is. `checkOptions` reports the values that this function cannot correct.
|
|
180
203
|
*/
|
|
181
204
|
export function clampOptions<S extends Step, O extends object>(strategy: Strategy<S, O>, options: Partial<O>): Partial<O> {
|
|
182
205
|
const schema = schemaOf(strategy);
|
|
@@ -184,6 +207,10 @@ export function clampOptions<S extends Step, O extends object>(strategy: Strateg
|
|
|
184
207
|
for (const [key, value] of Object.entries(options)) {
|
|
185
208
|
const d = schema[key];
|
|
186
209
|
if (!d || (d.type !== "number" && d.type !== "integer") || typeof value !== "number" || Number.isNaN(value) || isSpecial(d, value)) continue;
|
|
210
|
+
if (d.values !== undefined && d.values.length > 0) {
|
|
211
|
+
out[key] = d.values.reduce((best, v) => (Math.abs(v - value) < Math.abs(best - value) ? v : best));
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
187
214
|
const v = Math.min(d.max, Math.max(d.min, value));
|
|
188
215
|
out[key] = d.type === "integer" ? Math.min(d.max, Math.max(d.min, Math.round(v))) : v;
|
|
189
216
|
}
|
|
@@ -211,7 +238,7 @@ function rangeValues(d: NumberOption, count: number): number[] {
|
|
|
211
238
|
|
|
212
239
|
/**
|
|
213
240
|
* This function gives the values of a sweep of one option declaration.
|
|
214
|
-
* A number option gives its `marks`, or `count` values on its range and its scale. A boolean gives `false` and `true`. An enum gives its values.
|
|
241
|
+
* A number option gives its `marks`, or its list of `values`, or `count` values on its range and its scale. A boolean gives `false` and `true`. An enum gives its values.
|
|
215
242
|
* An option of another type has no sweep, so the function throws an error.
|
|
216
243
|
*/
|
|
217
244
|
export function sweepValues(declaration: AnyOption, options: SweepOptions = {}): unknown[] {
|
|
@@ -219,6 +246,7 @@ export function sweepValues(declaration: AnyOption, options: SweepOptions = {}):
|
|
|
219
246
|
case "number":
|
|
220
247
|
case "integer": {
|
|
221
248
|
if (options.count === undefined && declaration.marks !== undefined) return [...declaration.marks];
|
|
249
|
+
if (declaration.values !== undefined) return [...declaration.values];
|
|
222
250
|
const values = rangeValues(declaration, options.count ?? SWEEP_COUNT);
|
|
223
251
|
return options.special ? [...values, ...(declaration.special ?? []).filter((s) => !values.includes(s))] : values;
|
|
224
252
|
}
|
|
@@ -268,6 +296,10 @@ function declarationIssues(key: string, d: AnyOption): string[] {
|
|
|
268
296
|
if (d.type === "integer" && (!Number.isInteger(d.min) || !Number.isInteger(d.max))) out.push(`The integer option "${key}" has a range that is not whole.`);
|
|
269
297
|
if (d.step !== undefined && !(d.step > 0)) out.push(`The option "${key}" has the step ${d.step}. A step is more than 0.`);
|
|
270
298
|
if (d.scale === "log" && !(d.min > 0)) out.push(`The option "${key}" has the scale log and the min ${d.min}. The scale log needs a min above 0.`);
|
|
299
|
+
if (d.values !== undefined && d.values.length === 0) out.push(`The number option "${key}" has an empty list of values.`);
|
|
300
|
+
for (const v of d.values ?? []) {
|
|
301
|
+
if (v < d.min || v > d.max || (d.type === "integer" && !Number.isInteger(v))) out.push(`The value ${v} of the option "${key}" is not on its range ${d.min} to ${d.max}.`);
|
|
302
|
+
}
|
|
271
303
|
for (const v of [...(d.marks ?? []), ...(d.default === undefined ? [] : [d.default])]) {
|
|
272
304
|
const issue = issueOf(key, v, d);
|
|
273
305
|
if (issue) out.push(issue.message);
|
|
@@ -39,6 +39,11 @@ import type { ResistanceOptions } from "../highways/resistance/index.ts";
|
|
|
39
39
|
import type { RidgeOptions } from "../highways/ridges/index.ts";
|
|
40
40
|
import type { RoadClassOptions } from "../highways/road-classes/index.ts";
|
|
41
41
|
import type { ScaleSpaceOptions } from "../highways/scale-space/index.ts";
|
|
42
|
+
import type { GapOptions } from "../highways/gaps/index.ts";
|
|
43
|
+
import type { InterchangeOptions } from "../highways/interchanges/index.ts";
|
|
44
|
+
import type { SearchOptions } from "../highways/search/index.ts";
|
|
45
|
+
import type { SteinerOptions } from "../highways/steiner/index.ts";
|
|
46
|
+
import type { StreetOptions } from "../highways/streets/index.ts";
|
|
42
47
|
import type { SelectOptions } from "../highways/select/index.ts";
|
|
43
48
|
import type { SoftKeyOptions } from "../highways/soft-keys/index.ts";
|
|
44
49
|
import type { SpectralOptions } from "../highways/spectral/index.ts";
|
|
@@ -49,12 +54,15 @@ import type { TraclusOptions } from "../highways/traclus/index.ts";
|
|
|
49
54
|
import type { WeiszfeldOptions } from "../highways/weiszfeld/index.ts";
|
|
50
55
|
import type { WspdOptions } from "../highways/wspd/index.ts";
|
|
51
56
|
import type { ZipperOptions } from "../highways/zipper/index.ts";
|
|
57
|
+
import type { LaneStretchOptions } from "../lane-orders/stretch/index.ts";
|
|
58
|
+
import type { DepthLevelOptions } from "../levels/depths/index.ts";
|
|
52
59
|
import type { CornersOptions } from "../routers/corners.ts";
|
|
53
60
|
import type { SectionsOptions } from "../routers/sections.ts";
|
|
54
61
|
import type { SharedOptions } from "../routers/shared.ts";
|
|
55
|
-
import type
|
|
62
|
+
import { GROUP_RULE_NAMES, type GroupRuleName, type TrunksOptions } from "../routers/trunks.ts";
|
|
56
63
|
import type { SectionOptions } from "../sections.ts";
|
|
57
64
|
import type { WayOptions } from "../ways.ts";
|
|
65
|
+
import type { PipelineOptions } from "../whole/pipeline/index.ts";
|
|
58
66
|
import type { RunOrder } from "../segments.ts";
|
|
59
67
|
import type { ClothoidStrategyOptions, HighwayStrategyOptions, LevelRoomPreset, RadiusOptions, RoomPreset, RouterPreset, SmoothStrategyOptions, TensionOptions } from "./builtins.ts";
|
|
60
68
|
import type { EnumOption, NumberOption, OptionSchema } from "./options.ts";
|
|
@@ -69,6 +77,24 @@ const WAY_SCHEMA: OptionSchema<WayOptions> = {
|
|
|
69
77
|
smooth: { type: "boolean", default: false, describe: "With `true`, a way in clear view uses the inflection rule, and the other ways get round corners." },
|
|
70
78
|
};
|
|
71
79
|
|
|
80
|
+
/** The declarations of the options of the stretch order (LANE-STRETCH): the method and the passes of `sifting`. A generator can list each value. */
|
|
81
|
+
export const LANE_STRETCH_SCHEMA: OptionSchema<LaneStretchOptions> = {
|
|
82
|
+
method: {
|
|
83
|
+
type: "enum",
|
|
84
|
+
values: ["none", "consensus", "periphery", "sifting"],
|
|
85
|
+
default: "sifting",
|
|
86
|
+
describe: "The order of each stretch: the order of each run alone, the order of the pair relation, the order of the largest run, or the order of the pair relation after sifting.",
|
|
87
|
+
},
|
|
88
|
+
passes: { type: "integer", min: 1, max: 4, step: 1, default: 2, describe: "The number of passes of the method `sifting`. In each pass, each edge moves to its best place." },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The declaration of the grouping rule of the edges: a name of `GROUP_RULE_NAMES`. A new name of the list needs no change here.
|
|
93
|
+
* The option also takes a rule of the caller (`custom`), so the old value form stays valid.
|
|
94
|
+
* The `defaults` of a strategy keep the rule `byCollection`, and the declaration gives its name `collection` as its `default`.
|
|
95
|
+
*/
|
|
96
|
+
const EDGE_RULE: EnumOption<GroupRuleName> = { type: "enum", values: GROUP_RULE_NAMES, custom: "function", describe: "The grouping rule." };
|
|
97
|
+
|
|
72
98
|
/** The declaration of the lane order of the shared ways of the routers `trunks` and `shared`. */
|
|
73
99
|
const LANE_ORDER: EnumOption<RunOrder> = {
|
|
74
100
|
type: "enum",
|
|
@@ -97,12 +123,14 @@ export const CORNERS_SCHEMA: OptionSchema<RouterPreset<CornersOptions>> = {
|
|
|
97
123
|
default: 0,
|
|
98
124
|
describe: "With a gap above 0, the free plugs of a side keep their order and at least this gap (WILD-002). 0 keeps each plug where it is.",
|
|
99
125
|
},
|
|
126
|
+
plug: { type: "strategy", step: "plugs", describe: "The plug rule: a strategy of the step `plugs`. Without it, the option `plugs` places the plugs." },
|
|
127
|
+
plugOptions: { type: "object", describe: "The options of the strategy `plug`." },
|
|
100
128
|
};
|
|
101
129
|
|
|
102
130
|
/** The declarations of the options of the router `trunks`. */
|
|
103
131
|
export const TRUNKS_SCHEMA: OptionSchema<RouterPreset<TrunksOptions>> = {
|
|
104
132
|
...WAY_SCHEMA,
|
|
105
|
-
rule: {
|
|
133
|
+
rule: { ...EDGE_RULE, default: "collection", describe: "The grouping rule. The edges from one plug with one key share a trunk. With `all`, each edge has one key." },
|
|
106
134
|
split: { type: "number", min: 0.1, max: 0.9, step: 0.05, default: 0.5, describe: "The split point is at this share of the way to the center of the targets." },
|
|
107
135
|
minSize: { type: "integer", min: 1, max: 8, step: 1, default: 2, describe: "A group with fewer edges stays unbundled." },
|
|
108
136
|
laneOrder: LANE_ORDER,
|
|
@@ -128,10 +156,10 @@ export const TRUNKS_SCHEMA: OptionSchema<RouterPreset<TrunksOptions>> = {
|
|
|
128
156
|
bf: { type: "number", min: 0, max: 1, step: 0.05, default: 0.4, describe: "The balancing factor of the fan `tree`. 0 gives the least ink, and a larger factor gives more direct routes." },
|
|
129
157
|
};
|
|
130
158
|
|
|
131
|
-
/** The declarations of the options of the
|
|
159
|
+
/** The declarations of the options of the router `shared` and of the highway strategy `shared-ways`. */
|
|
132
160
|
export const SHARED_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
|
|
133
161
|
...WAY_SCHEMA,
|
|
134
|
-
rule: {
|
|
162
|
+
rule: { ...EDGE_RULE, default: "collection", describe: "The edges with one key share their ways. With `collection`, the key is `data.collection`. With `all`, all edges share." },
|
|
135
163
|
discount: {
|
|
136
164
|
type: "number",
|
|
137
165
|
min: 0.05,
|
|
@@ -179,6 +207,18 @@ export const SHARED_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
|
|
|
179
207
|
default: "corner",
|
|
180
208
|
describe: "With `corner`, a route joins a run at a corner of the graph. With `snell`, it joins at the angle `acos(d)` (NAT-035).",
|
|
181
209
|
},
|
|
210
|
+
stretchOrder: {
|
|
211
|
+
...LANE_STRETCH_SCHEMA.method,
|
|
212
|
+
default: "none",
|
|
213
|
+
describe: "With `none`, each run gets its lane order alone. With the other values, the runs that follow each other share one order (LANE-STRETCH).",
|
|
214
|
+
},
|
|
215
|
+
siftPasses: LANE_STRETCH_SCHEMA.passes,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/** The declarations of the options of the router `shared-all`. It sets no rule, so all edges share. Thus its rule has no `default`. */
|
|
219
|
+
export const SHARED_ALL_SCHEMA: OptionSchema<RouterPreset<SharedOptions>> = {
|
|
220
|
+
...SHARED_SCHEMA,
|
|
221
|
+
rule: { ...EDGE_RULE, describe: "The edges with one key share their ways. Without a rule, or with `all`, all edges share. With `collection`, the key is `data.collection`." },
|
|
182
222
|
};
|
|
183
223
|
|
|
184
224
|
/** The declarations of the options of the highway strategy `corridors` (HWY-031). */
|
|
@@ -248,6 +288,8 @@ export const SECTIONS_SCHEMA: OptionSchema<RouterPreset<SectionsOptions>> = {
|
|
|
248
288
|
group: { type: "strategy", step: "group", describe: "The grouping rule: a strategy of the step `group`. Without it, the router uses the quadtree." },
|
|
249
289
|
groupOptions: { type: "object", describe: "The options of the strategy `group`." },
|
|
250
290
|
levels: { type: "list", describe: "The depths of the tree that get a level with groups. Without it, each depth gets a level." },
|
|
291
|
+
level: { type: "strategy", step: "levels", describe: "The level rule: a strategy of the step `levels`. Without it, the router uses the strategy `depths`." },
|
|
292
|
+
levelOptions: { type: "object", describe: "The options of the strategy `level`. They go over the level options of the router." },
|
|
251
293
|
laneOrder: RUN_LANE_ORDER,
|
|
252
294
|
};
|
|
253
295
|
|
|
@@ -553,7 +595,7 @@ export const EDGE_PATH_SCHEMA: OptionSchema<EdgePathOptions> = {
|
|
|
553
595
|
|
|
554
596
|
/** The declarations of the options of the highway strategy `soft-keys` (HWY-019). */
|
|
555
597
|
export const SOFT_KEY_SCHEMA: OptionSchema<SoftKeyOptions> = {
|
|
556
|
-
rule: {
|
|
598
|
+
rule: { ...EDGE_RULE, default: "collection", describe: "The rule that gives the key of each edge. Two different keys add the distance `lambda`. With `all`, the keys add no distance." },
|
|
557
599
|
lambda: { type: "number", min: 0, max: 2, step: 0.05, marks: [0, 0.25, 1], default: 0.25, describe: "The distance that two different keys add. A very large value keeps the keys apart." },
|
|
558
600
|
tau: { type: "number", min: 0.1, max: 1.5, step: 0.05, marks: [0.3, 0.5, 1], default: 0.5, describe: "Two clusters join while their distance is this value or less." },
|
|
559
601
|
attributes: { type: "list", default: [], describe: "The names of number fields of `data` that add a distance." },
|
|
@@ -862,3 +904,80 @@ export const SCALE_SPACE_SCHEMA: OptionSchema<ScaleSpaceOptions> = {
|
|
|
862
904
|
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A cluster with this number of edges or more gets highways." },
|
|
863
905
|
spine: GROUP_SPINE,
|
|
864
906
|
};
|
|
907
|
+
|
|
908
|
+
/** The declarations of the options of the level strategy `depths`: the limits of the level links, and the depths that get a level. */
|
|
909
|
+
export const DEPTHS_SCHEMA: OptionSchema<DepthLevelOptions> = {
|
|
910
|
+
margin: WAY_SCHEMA.margin,
|
|
911
|
+
k: WAY_SCHEMA.k,
|
|
912
|
+
tightMargin: SECTIONS_SCHEMA.tightMargin,
|
|
913
|
+
plugInset: SECTIONS_SCHEMA.plugInset,
|
|
914
|
+
throughCost: SECTIONS_SCHEMA.throughCost,
|
|
915
|
+
levels: SECTIONS_SCHEMA.levels,
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
/** The declarations of the options of the whole strategy `pipeline`. */
|
|
919
|
+
export const PIPELINE_SCHEMA: OptionSchema<PipelineOptions> = {
|
|
920
|
+
router: { type: "strategy", step: "router", describe: "The router. A highway plan takes its place." },
|
|
921
|
+
routerOptions: { type: "object", describe: "The options of the router." },
|
|
922
|
+
highway: { type: "strategy", step: "highway", describe: "The highway strategy. With it, the plan and `follow` give the routes. Without it, the router routes." },
|
|
923
|
+
highwayOptions: { type: "object", describe: "The options of the highway strategy." },
|
|
924
|
+
group: { type: "strategy", step: "group", describe: "The grouping rule of a router that reads one, for example `sections`." },
|
|
925
|
+
groupOptions: { type: "object", describe: "The options of the grouping rule." },
|
|
926
|
+
plugs: { type: "strategy", step: "plugs", describe: "The plug rule of a router that reads one, for example `corners`." },
|
|
927
|
+
plugOptions: { type: "object", describe: "The options of the plug rule." },
|
|
928
|
+
levels: { type: "strategy", step: "levels", describe: "The level rule of a router that reads one, for example `sections`." },
|
|
929
|
+
levelOptions: { type: "object", describe: "The options of the level rule." },
|
|
930
|
+
room: { type: "strategy", step: "room", describe: "The room strategy. With it, the gaps of the board open before the routes. Without it, the board stays." },
|
|
931
|
+
roomOptions: { type: "object", describe: "The options of the room strategy." },
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
/** The declarations of the options of the highway strategy `steiner` (HWY-036 with EDA-015). */
|
|
935
|
+
export const STEINER_SCHEMA: OptionSchema<SteinerOptions> = {
|
|
936
|
+
group: { type: "enum", values: ["source", "key"], default: "source", describe: "The groups of the trees: the edges from one source node, or the edges of one collection." },
|
|
937
|
+
method: { type: "enum", values: ["one-steiner", "spanning"], default: "one-steiner", describe: "The method of the tree: the iterated 1-Steiner method, or the spanning tree of the terminals." },
|
|
938
|
+
maxGroup: { type: "integer", min: 3, max: 32, step: 1, marks: [8, 12, 16], default: 12, describe: "A group with more terminals than this number gets the spanning tree alone." },
|
|
939
|
+
metric: { type: "enum", values: ["rectilinear", "octilinear"], default: "rectilinear", describe: "The metric of the tree: two directions or four directions." },
|
|
940
|
+
detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "An edge rides the tree only when its path in the tree is this many times its first route or less." },
|
|
941
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A stretch of a tree with fewer edges than this number is not a highway." },
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
/** The declarations of the options of the highway strategy `gaps` (HWY-057). */
|
|
945
|
+
export const GAP_SCHEMA: OptionSchema<GapOptions> = {
|
|
946
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, marks: [2, 3, 6], default: 3, describe: "A street with fewer edges than this number (the demand) is not a highway." },
|
|
947
|
+
discount: { type: "number", min: 0.1, max: 0.8, step: 0.05, marks: [0.3, 0.5, 0.7], default: 0.5, describe: "The cost of a length on a street against a length off the streets." },
|
|
948
|
+
detour: { type: "number", min: 1, max: 2, step: 0.05, marks: [1.2, 1.3, 1.5], default: 1.3, describe: "An edge rides a street only when its way along the street is this many times its first route or less." },
|
|
949
|
+
source: { type: "enum", values: ["room", "corners"], default: "room", describe: "The source of the streets: the gaps of the room step, or the lines through the corners." },
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
/** The declarations of the options of the highway strategy `streets` (HWY-058). */
|
|
953
|
+
export const STREET_SCHEMA: OptionSchema<StreetOptions> = {
|
|
954
|
+
detour: { type: "number", min: 1, max: 1.5, step: 0.05, marks: [1.1, 1.2, 1.3], default: 1.2, describe: "A street covers an edge when the way of the edge along it is this many times its first route or less." },
|
|
955
|
+
minGain: { type: "number", min: 1, max: 8, step: 0.25, marks: [1.5, 2, 4], default: 2, describe: "The cover stops when the best street gives less gain: the length that its edges ride over its length." },
|
|
956
|
+
maxStreets: { type: "integer", min: 4, max: 64, step: 4, marks: [16, 32], default: 32, describe: "The largest number of streets." },
|
|
957
|
+
minSize: { type: "integer", min: 2, max: 16, step: 1, default: 2, describe: "A street with fewer new edges than this number is not a highway." },
|
|
958
|
+
source: { type: "enum", values: ["room", "corners"], default: "corners", describe: "The source of the streets: the gaps of the room step, or the lines through the corners." },
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
/** The declarations of the options of the highway strategy `search` (HWY-059). */
|
|
962
|
+
export const SEARCH_SCHEMA: OptionSchema<SearchOptions> = {
|
|
963
|
+
from: { type: "enum", values: ["corridors", "quickbundles", "wspd", "road-classes", "flow-field", "od-cells", "shared-ways"], default: "corridors", describe: "The plan where the search starts." },
|
|
964
|
+
pool: { type: "list", default: ["quickbundles", "road-classes"], describe: "The generators of the highways that the move `add` takes." },
|
|
965
|
+
evaluations: { type: "integer", min: 0, max: 400, step: 4, marks: [0, 16, 64], default: 16, describe: "The number of plans that the search measures. The value 0 gives the start plan." },
|
|
966
|
+
temperature: { type: "number", min: 0, max: 0.2, step: 0.005, marks: [0, 0.02, 0.05], default: 0.02, describe: "The first temperature of the annealing, on the scale of the score." },
|
|
967
|
+
inkWeight: { type: "number", min: 0, max: 4, step: 0.25, marks: [1], default: 1, describe: "The weight of the ink that the routes save (B1)." },
|
|
968
|
+
stretchWeight: { type: "number", min: 0, max: 4, step: 0.25, marks: [0.5], default: 0.5, describe: "The weight of the 0.95 quantile of the stretch (B3)." },
|
|
969
|
+
ambiguityWeight: { type: "number", min: 0, max: 4, step: 0.25, marks: [0.5], default: 0.5, describe: "The weight of the ambiguity (B4)." },
|
|
970
|
+
angleWeight: { type: "number", min: 0, max: 4, step: 0.25, marks: [0.25], default: 0.25, describe: "The weight of the largest merge angle (B6), divided by 180 degrees." },
|
|
971
|
+
seed: { type: "integer", min: 0, max: 1000, step: 1, default: 1, describe: "The seed of the moves." },
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
/** The declarations of the options of the highway strategy `interchanges` (HWY-060). */
|
|
975
|
+
export const INTERCHANGE_SCHEMA: OptionSchema<InterchangeOptions> = {
|
|
976
|
+
from: { type: "enum", values: ["corridors", "shared-ways"], default: "corridors", describe: "The plan that gives the highways of the network." },
|
|
977
|
+
changeCost: { type: "number", min: 0, max: 8, step: 0.5, marks: [0, 2, 4], default: 2, describe: "The cost of a change between two highways, in units of the board." },
|
|
978
|
+
interchanges: { type: "integer", min: 1, max: 32, step: 1, marks: [4, 16], default: 16, describe: "The largest number of interchanges." },
|
|
979
|
+
perEdge: { type: "integer", min: 1, max: 3, step: 1, default: 2, describe: "The largest number of highways on the way of one edge." },
|
|
980
|
+
radius: { type: "number", min: 0.5, max: 8, step: 0.5, marks: [1, 2, 4], default: 2, describe: "Two highways get an interchange where they come this near, in units of the board." },
|
|
981
|
+
discount: { type: "number", min: 0.1, max: 1, step: 0.05, marks: [0.3, 0.5, 0.7], default: 0.5, describe: "The cost of a length on a highway against a length off the highways." },
|
|
982
|
+
detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "An edge rides the network only when its way is this many times its first route or less." },
|
|
983
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { highwayRouterStrategy } from "../../highways/router.ts";
|
|
2
|
+
import type { Graph } from "../../model.ts";
|
|
3
|
+
import { openGaps, roomLinks } from "../../room/index.ts";
|
|
4
|
+
import { asRouter, type Step, type Strategy, type WholeOutput } from "../../strategies/model.ts";
|
|
5
|
+
import { listOptions } from "../../strategies/options.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The options of the whole strategy `pipeline`: one strategy of each step, and the options of each one.
|
|
9
|
+
* A rule of the steps `group`, `plugs` and `levels` acts only on a router that reads a rule of that step (`strategyKey`).
|
|
10
|
+
*/
|
|
11
|
+
export interface PipelineOptions {
|
|
12
|
+
/** The router. A highway plan takes its place. */
|
|
13
|
+
readonly router: Strategy<"router">;
|
|
14
|
+
/** The options of the router. */
|
|
15
|
+
readonly routerOptions?: object;
|
|
16
|
+
/** The highway strategy. With it, the plan of the strategy and `follow` give the routes, not the router. */
|
|
17
|
+
readonly highway?: Strategy<"highway">;
|
|
18
|
+
/** The options of the highway strategy. */
|
|
19
|
+
readonly highwayOptions?: object;
|
|
20
|
+
/** The grouping rule of a router that reads one, for example `sections`. */
|
|
21
|
+
readonly group?: Strategy<"group">;
|
|
22
|
+
/** The options of the grouping rule. */
|
|
23
|
+
readonly groupOptions?: object;
|
|
24
|
+
/** The plug rule of a router that reads one, for example `corners`. */
|
|
25
|
+
readonly plugs?: Strategy<"plugs">;
|
|
26
|
+
/** The options of the plug rule. */
|
|
27
|
+
readonly plugOptions?: object;
|
|
28
|
+
/** The level rule of a router that reads one, for example `sections`. */
|
|
29
|
+
readonly levels?: Strategy<"levels">;
|
|
30
|
+
/** The options of the level rule. */
|
|
31
|
+
readonly levelOptions?: object;
|
|
32
|
+
/** The room strategy. With it, the pipeline opens the gaps of the board before the routes (`openGaps`). */
|
|
33
|
+
readonly room?: Strategy<"room">;
|
|
34
|
+
/** The options of the room strategy. */
|
|
35
|
+
readonly roomOptions?: object;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* This function gives the key of the option of a router that takes a strategy of one step.
|
|
40
|
+
* It gives `undefined` when the router has no such option.
|
|
41
|
+
* The schema of the router declares the option with the type `strategy` and the step. The options of that strategy have the key `<key>Options`.
|
|
42
|
+
*/
|
|
43
|
+
export function strategyKey(router: Strategy<"router">, step: Step): string | undefined {
|
|
44
|
+
return listOptions(router).find((o) => o.declaration.type === "strategy" && o.declaration.step === step)?.key;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* This function runs the whole strategy `pipeline` on one graph. The room strategy opens the gaps of the board first.
|
|
49
|
+
* A highway strategy makes its plan, and `follow` routes the plan. Otherwise the router routes, with each rule of the steps `group`, `plugs` and `levels` that it reads.
|
|
50
|
+
* The output has the board and the routes. The function does not examine the `maxNodes` of the router.
|
|
51
|
+
*/
|
|
52
|
+
export function runPipeline(graph: Graph, options: PipelineOptions): WholeOutput {
|
|
53
|
+
const board = options.room ? { nodes: openGaps(graph.nodes, options.room.run({ items: graph.nodes, links: roomLinks(graph) }, options.roomOptions)), edges: graph.edges } : graph;
|
|
54
|
+
if (options.highway) return { graph: board, result: asRouter(highwayRouterStrategy(options.highway) as Strategy<"router">, options.highwayOptions).route(board) };
|
|
55
|
+
let routerOptions = options.routerOptions;
|
|
56
|
+
const rules: readonly [Step, Strategy<Step> | undefined, object | undefined][] = [
|
|
57
|
+
["group", options.group, options.groupOptions],
|
|
58
|
+
["plugs", options.plugs, options.plugOptions],
|
|
59
|
+
["levels", options.levels, options.levelOptions],
|
|
60
|
+
];
|
|
61
|
+
for (const [step, rule, own] of rules) {
|
|
62
|
+
const key = rule && strategyKey(options.router, step);
|
|
63
|
+
if (key) routerOptions = { ...routerOptions, [key]: rule, [`${key}Options`]: own };
|
|
64
|
+
}
|
|
65
|
+
return { graph: board, result: asRouter(options.router, routerOptions).route(board) };
|
|
66
|
+
}
|