@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.
Files changed (54) hide show
  1. package/README.md +112 -8
  2. package/build/index.d.ts +989 -187
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +9761 -6738
  5. package/build/index.js.map +1 -1
  6. package/package.json +3 -2
  7. package/src/bench/compare.ts +174 -5
  8. package/src/bench/room.ts +1 -1
  9. package/src/bench/run.ts +38 -3
  10. package/src/bench/screen/exercise.ts +5 -0
  11. package/src/bench/screen/forms.ts +5 -1
  12. package/src/bench/screen/generators.ts +11 -41
  13. package/src/bench/screen/screen.ts +16 -10
  14. package/src/bench/screen/set.ts +156 -49
  15. package/src/bench/suite.ts +110 -12
  16. package/src/cases.ts +64 -18
  17. package/src/families/cases.ts +108 -0
  18. package/src/families/fixtures.ts +101 -0
  19. package/src/families/index.ts +36 -0
  20. package/src/families/model.ts +55 -0
  21. package/src/fixtures.ts +8 -2
  22. package/src/highways/fixtures.test-util.ts +13 -0
  23. package/src/highways/follow.ts +3 -2
  24. package/src/highways/free-lines.ts +141 -0
  25. package/src/highways/gaps/index.ts +85 -0
  26. package/src/highways/index.ts +6 -0
  27. package/src/highways/interchanges/index.ts +231 -0
  28. package/src/highways/search/index.ts +170 -0
  29. package/src/highways/snap.ts +4 -2
  30. package/src/highways/soft-keys/index.ts +8 -4
  31. package/src/highways/steiner/index.ts +215 -0
  32. package/src/highways/streets/index.ts +78 -0
  33. package/src/index.ts +8 -0
  34. package/src/lane-order.ts +37 -0
  35. package/src/lane-orders/stretch/index.ts +486 -0
  36. package/src/lane-orders/stretch/router.ts +22 -0
  37. package/src/levels/depths/index.ts +57 -0
  38. package/src/model.ts +60 -4
  39. package/src/plugs/facing/index.ts +22 -0
  40. package/src/portfolio/router.ts +44 -0
  41. package/src/routers/attraction/index.ts +61 -0
  42. package/src/routers/corners.ts +31 -7
  43. package/src/routers/index.ts +2 -1
  44. package/src/routers/plugs.ts +3 -2
  45. package/src/routers/sections.ts +25 -42
  46. package/src/routers/shared.ts +96 -14
  47. package/src/routers/straight.ts +3 -2
  48. package/src/routers/trunks.ts +24 -5
  49. package/src/segments.ts +4 -25
  50. package/src/strategies/builtins.ts +109 -2
  51. package/src/strategies/model.ts +55 -10
  52. package/src/strategies/options.ts +36 -4
  53. package/src/strategies/schemas.ts +124 -5
  54. 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
- /** The declaration that fits the type of an option value: numbers, booleans, string unions or other values. */
65
- export type OptionDeclaration<V> = [V] extends [number] ? NumberOption : [V] extends [boolean] ? BooleanOption : [V] extends [string] ? EnumOption<V> : ValueOption<V>;
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 { TrunksOptions } from "../routers/trunks.ts";
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: { type: "function", describe: "The grouping rule. The edges from one plug with one key share a trunk." },
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 routers `shared` and `shared-all`. */
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: { type: "function", describe: "The edges with one key share their ways. Without a rule, all edges share." },
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: { type: "function", describe: "The rule that gives the key of each edge. Two different keys add the distance `lambda`." },
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
+ }