@mark1russell7/highways 0.9.4 → 0.9.6

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.
@@ -108,6 +108,8 @@ export interface SharedSearch {
108
108
  * The function `cost` can change the cost of the link from corner `i` to corner `j`, of length `d`. It must give at least `floor` × `d`.
109
109
  */
110
110
  find(w: Way, cost?: (i: number, j: number, d: number) => number, floor?: number): { seq: Point[]; ids: number[] } | undefined;
111
+ /** This function gives the free space across the link from corner `i` to corner `j` (`channelWidth`). The router keeps each width that it finds. */
112
+ width(i: number, j: number): number;
111
113
  }
112
114
 
113
115
  /**
@@ -212,6 +214,7 @@ export function sharedRouter(options: SharedOptions, negotiation?: SharedNegotia
212
214
  const way = corners.path(ways.out(w.ps), ways.out(w.pt), cost, floor);
213
215
  return way && { seq: way, ids: way.map((p, i) => (i === 0 || i === way.length - 1 ? endId(p) : index.get(p)!)) };
214
216
  },
217
+ width: (i, j) => widthOf(i, j),
215
218
  };
216
219
  const sorted = orderWays(all, options.order ?? "span", graph);
217
220
  const order = hooks?.order ? hooks.order(sorted, search) : sorted;
@@ -47,6 +47,8 @@ import { SCALE_SPACE_DEFAULTS, scaleSpacePlan, type ScaleSpaceOptions } from "..
47
47
  import { GAP_DEFAULTS, gapPlan, type GapOptions } from "../highways/gaps/index.ts";
48
48
  import { INTERCHANGE_DEFAULTS, interchangePlan, type InterchangeOptions } from "../highways/interchanges/index.ts";
49
49
  import { KNEE_DEFAULTS, kneePlan, type KneeOptions } from "../highways/knee/index.ts";
50
+ import { BALANCE_DEFAULTS, balancePlan, type BalanceOptions } from "../highways/balance/index.ts";
51
+ import { MANIFOLD_DEFAULTS, manifoldPlan, type ManifoldOptions } from "../highways/manifolds/index.ts";
50
52
  import { BRONCHIAL_DEFAULTS, bronchialPlan, type BronchialOptions } from "../highways/bronchial/index.ts";
51
53
  import { SEARCH_DEFAULTS, searchPlan, type SearchOptions } from "../highways/search/index.ts";
52
54
  import { STEINER_DEFAULTS, steinerPlan, type SteinerOptions } from "../highways/steiner/index.ts";
@@ -75,11 +77,14 @@ import { WSPD_DEFAULTS, wspdPlan, type WspdOptions } from "../highways/wspd/inde
75
77
  import { ZIPPER_DEFAULTS, zipperPlan, type ZipperOptions } from "../highways/zipper/index.ts";
76
78
  import { depthLevels, type DepthLevelOptions } from "../levels/depths/index.ts";
77
79
  import { VENATION_DEFAULTS, venationLevels, type VenationOptions } from "../levels/venation/index.ts";
80
+ import { DETAIL_DEFAULTS, detailLevels, type DetailOptions } from "../levels/detail/index.ts";
78
81
  import type { Graph, Result, Router } from "../model.ts";
79
82
  import { facingEnds } from "../plugs/facing/index.ts";
80
83
  import { justifyEnds, type JustifyOptions } from "../plugs/justify.ts";
81
84
  import { sideEnds, type SidePlugOptions } from "../plugs/side.ts";
82
85
  import { SPREAD_DEFAULTS, spreadEnds, type SpreadOptions } from "../plugs/spread/index.ts";
86
+ import { ASSIGN_DEFAULTS, assignEnds, type AssignOptions } from "../plugs/assign/index.ts";
87
+ import { LEADER_DEFAULTS, leaderEnds, type LeaderOptions } from "../plugs/leaders/index.ts";
83
88
  import { roomDemand, type RoomMethod, type RoomOptions } from "../room/index.ts";
84
89
  import { levelRoom, type LevelRoomOptions } from "../room/levels/index.ts";
85
90
  import { cornersRouter, type CornersOptions } from "../routers/corners.ts";
@@ -91,10 +96,15 @@ import { byCollection, trunksRouter, type TrunksOptions } from "../routers/trunk
91
96
  import { buildSections, SECTION_DEFAULTS, type SectionOptions } from "../sections.ts";
92
97
  import { runPipeline, type PipelineOptions } from "../whole/pipeline/index.ts";
93
98
  import { BOARD_PORTFOLIO_DEFAULTS, boardPortfolio, type BoardPortfolioOptions, type PortfolioSetting } from "../whole/portfolio/index.ts";
99
+ import { CURRICULUM_DEFAULTS, curriculum, type CurriculumOptions } from "../whole/curriculum/index.ts";
100
+ import { SLOT_DEFAULTS, slotPlan, type SlotOptions } from "../highways/slots/index.ts";
101
+ import { PREFERENCE_DEFAULTS, preferences, type PreferenceOptions } from "../whole/preferences/index.ts";
102
+ import { FIREFLY_DEFAULTS, fireflies, type FireflyOptions } from "../whole/fireflies/index.ts";
103
+ import { TRACING_DEFAULTS, tracing, type TracingOptions } from "../whole/tracing/index.ts";
94
104
  import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
95
105
  import type { OptionSchema } from "./options.ts";
96
106
  import { AREA_SCHEMA, CONFLUENT_SCHEMA, CROSSBAR_SCHEMA, FRECHET_SCHEMA, MLSEB_SCHEMA, POWER_SCHEMA, SBEB_SCHEMA } from "./schemas.ts";
97
- import { DEPTHS_SCHEMA, JUSTIFY_SCHEMA, PIPELINE_SCHEMA, SIDE_PLUG_SCHEMA, SPREAD_SCHEMA, CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CONSENSUS_SCHEMA, CUBU_SCHEMA, CUT_SCHEMA, DIFFUSION_SCHEMA, DIVIDED_SCHEMA, EDGE_PATH_SCHEMA, FDEB_SCHEMA, FLOW_CLUSTER_SCHEMA, FLOW_FIELD_SCHEMA, GROWTH_SCHEMA, HDBSCAN_SCHEMA, HIERARCHY_SCHEMA, HIGHWAY_SCHEMA, HOUGH_SCHEMA, HUB_ARC_SCHEMA, KD_SCHEMA, KDEEB_SCHEMA, KEY_SCHEMA, LANES_SCHEMA, LEVEL_HIGHWAY_SCHEMA, LEVEL_ROOM_SCHEMA, LINK_COMMUNITY_SCHEMA, MEET_SCHEMA, MESH_SCHEMA, MINGLE_SCHEMA, NESTED_SCHEMA, OD_CELL_SCHEMA, P_HUB_SCHEMA, QUICKBUNDLES_SCHEMA, RADIUS_SCHEMA, RESISTANCE_SCHEMA, RIDGE_SCHEMA, ROAD_CLASS_SCHEMA, ROOM_SCHEMA, SCALE_SPACE_SCHEMA, SECTION_SCHEMA, SELECT_SCHEMA, SECTIONS_SCHEMA, SHARED_ALL_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, SOFT_KEY_SCHEMA, SPECTRAL_SCHEMA, SPIRAL_SCHEMA, STUB_SCHEMA, TENSION_SCHEMA, TERRITORY_SCHEMA, TRACLUS_SCHEMA, TRUNKS_SCHEMA, WEISZFELD_SCHEMA, WSPD_SCHEMA, ZIPPER_SCHEMA, STEINER_SCHEMA, GAP_SCHEMA, STREET_SCHEMA, SEARCH_SCHEMA, INTERCHANGE_SCHEMA, KNEE_SCHEMA, BRONCHIAL_SCHEMA, BOARD_PORTFOLIO_SCHEMA, VENATION_SCHEMA } from "./schemas.ts";
107
+ import { ASSIGN_SCHEMA, DEPTHS_SCHEMA, JUSTIFY_SCHEMA, PIPELINE_SCHEMA, SIDE_PLUG_SCHEMA, SPREAD_SCHEMA, CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CONSENSUS_SCHEMA, CUBU_SCHEMA, CUT_SCHEMA, DIFFUSION_SCHEMA, DIVIDED_SCHEMA, EDGE_PATH_SCHEMA, FDEB_SCHEMA, FLOW_CLUSTER_SCHEMA, FLOW_FIELD_SCHEMA, GROWTH_SCHEMA, HDBSCAN_SCHEMA, HIERARCHY_SCHEMA, HIGHWAY_SCHEMA, HOUGH_SCHEMA, HUB_ARC_SCHEMA, KD_SCHEMA, KDEEB_SCHEMA, KEY_SCHEMA, LANES_SCHEMA, LEVEL_HIGHWAY_SCHEMA, LEVEL_ROOM_SCHEMA, LINK_COMMUNITY_SCHEMA, MEET_SCHEMA, MESH_SCHEMA, MINGLE_SCHEMA, NESTED_SCHEMA, OD_CELL_SCHEMA, P_HUB_SCHEMA, QUICKBUNDLES_SCHEMA, RADIUS_SCHEMA, RESISTANCE_SCHEMA, RIDGE_SCHEMA, ROAD_CLASS_SCHEMA, ROOM_SCHEMA, SCALE_SPACE_SCHEMA, SECTION_SCHEMA, SELECT_SCHEMA, SECTIONS_SCHEMA, SHARED_ALL_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, SOFT_KEY_SCHEMA, SPECTRAL_SCHEMA, SPIRAL_SCHEMA, STUB_SCHEMA, TENSION_SCHEMA, TERRITORY_SCHEMA, TRACLUS_SCHEMA, TRUNKS_SCHEMA, WEISZFELD_SCHEMA, WSPD_SCHEMA, ZIPPER_SCHEMA, STEINER_SCHEMA, GAP_SCHEMA, STREET_SCHEMA, SEARCH_SCHEMA, INTERCHANGE_SCHEMA, KNEE_SCHEMA, BRONCHIAL_SCHEMA, BOARD_PORTFOLIO_SCHEMA, VENATION_SCHEMA, BALANCE_SCHEMA, MANIFOLD_SCHEMA, LEADER_SCHEMA, DETAIL_SCHEMA, CURRICULUM_SCHEMA, SLOT_SCHEMA, PREFERENCE_SCHEMA, FIREFLY_SCHEMA, TRACING_SCHEMA } from "./schemas.ts";
98
108
 
99
109
  /** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
100
110
  export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
@@ -480,6 +490,9 @@ export const highwayStrategies: {
480
490
  readonly interchanges: Strategy<"highway", InterchangeOptions>;
481
491
  readonly knee: Strategy<"highway", KneeOptions>;
482
492
  readonly bronchial: Strategy<"highway", BronchialOptions>;
493
+ readonly balance: Strategy<"highway", BalanceOptions>;
494
+ readonly manifolds: Strategy<"highway", ManifoldOptions>;
495
+ readonly slots: Strategy<"highway", SlotOptions>;
483
496
  readonly "corridors+breakeven": Strategy<"highway", CorridorOptions & BreakEvenOptions>;
484
497
  readonly "corridors+stretch": Strategy<"highway", CorridorOptions & StretchOptions>;
485
498
  readonly "corridors+ambiguity": Strategy<"highway", CorridorOptions & AmbiguityGuardOptions>;
@@ -973,6 +986,33 @@ export const highwayStrategies: {
973
986
  maxNodes: 10_000,
974
987
  run: (input: HighwayInput, o: Readonly<BronchialOptions>) => bronchialPlan(input, o),
975
988
  }),
989
+ balance: defineStrategy({
990
+ step: "highway",
991
+ name: "balance",
992
+ describe: "Stretch balance in a bundle (EDA-017). A price for each edge grows while its stretch is above the target, and the price moves its exit forward.",
993
+ defaults: { ...BALANCE_DEFAULTS } as BalanceOptions,
994
+ schema: BALANCE_SCHEMA,
995
+ maxNodes: 10_000,
996
+ run: (input: HighwayInput, o: Readonly<BalanceOptions>) => balancePlan(input, o),
997
+ }),
998
+ manifolds: defineStrategy({
999
+ step: "highway",
1000
+ name: "manifolds",
1001
+ describe: "Manifolds for a fan to a row (WILD-036). A straight header in the gap before the row, with taps at right angles and one feed from the node.",
1002
+ defaults: { ...MANIFOLD_DEFAULTS } as ManifoldOptions,
1003
+ schema: MANIFOLD_SCHEMA,
1004
+ maxNodes: 10_000,
1005
+ run: (input: HighwayInput, o: Readonly<ManifoldOptions>) => manifoldPlan(input, o),
1006
+ }),
1007
+ slots: defineStrategy({
1008
+ step: "highway",
1009
+ name: "slots",
1010
+ describe: "A pull toward the lane slots of the trunks (RULE-047). An edge near a trunk joins it on a new lane when the pull is larger than its detour.",
1011
+ defaults: { ...SLOT_DEFAULTS } as SlotOptions,
1012
+ schema: SLOT_SCHEMA,
1013
+ maxNodes: 10_000,
1014
+ run: (input: HighwayInput, o: Readonly<SlotOptions>) => slotPlan(input, o),
1015
+ }),
976
1016
  "corridors+breakeven": gateStrategy(corridors, breakEvenGate),
977
1017
  "corridors+stretch": gateStrategy(corridors, stretchGate),
978
1018
  "corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
@@ -986,6 +1026,8 @@ export const plugStrategies: {
986
1026
  readonly side: Strategy<"plugs", SidePlugOptions>;
987
1027
  readonly justify: Strategy<"plugs", JustifyOptions>;
988
1028
  readonly spread: Strategy<"plugs", SpreadOptions>;
1029
+ readonly leaders: Strategy<"plugs", LeaderOptions>;
1030
+ readonly assign: Strategy<"plugs", AssignOptions>;
989
1031
  } = Object.freeze({
990
1032
  facing: defineStrategy({
991
1033
  step: "plugs",
@@ -1019,12 +1061,29 @@ export const plugStrategies: {
1019
1061
  schema: SPREAD_SCHEMA,
1020
1062
  run: (input, o) => spreadEnds(input, o),
1021
1063
  }),
1064
+ leaders: defineStrategy<"plugs", LeaderOptions>({
1065
+ step: "plugs",
1066
+ name: "leaders",
1067
+ describe: "Boundary leaders as plug stubs (EDA-041). The plugs of each wall take the order of po-leaders with no crossing, and keep a least gap.",
1068
+ defaults: { ...LEADER_DEFAULTS },
1069
+ schema: LEADER_SCHEMA,
1070
+ run: (input, o) => leaderEnds(input, o),
1071
+ }),
1072
+ assign: defineStrategy<"plugs", AssignOptions>({
1073
+ step: "plugs",
1074
+ name: "assign",
1075
+ describe: "PLUG-ASSIGN. The plugs of each wall go into the ranges of their ports with a minimum gap. The most ends are in their range, then the order of the far ends.",
1076
+ defaults: { ...ASSIGN_DEFAULTS },
1077
+ schema: ASSIGN_SCHEMA,
1078
+ run: (input, o) => assignEnds(input, o),
1079
+ }),
1022
1080
  });
1023
1081
 
1024
1082
  /** The built-in level rules: the strategies of the step `levels`. The router `sections` makes its zoom levels with its option `level`. */
1025
1083
  export const levelStrategies: {
1026
1084
  readonly depths: Strategy<"levels", DepthLevelOptions>;
1027
1085
  readonly venation: Strategy<"levels", VenationOptions>;
1086
+ readonly detail: Strategy<"levels", DetailOptions>;
1028
1087
  } = Object.freeze({
1029
1088
  depths: defineStrategy<"levels", DepthLevelOptions>({
1030
1089
  step: "levels",
@@ -1042,6 +1101,14 @@ export const levelStrategies: {
1042
1101
  schema: VENATION_SCHEMA,
1043
1102
  run: (input, o) => venationLevels(input, o),
1044
1103
  }),
1104
+ detail: defineStrategy<"levels", DetailOptions>({
1105
+ step: "levels",
1106
+ name: "detail",
1107
+ describe: "The level of detail of the bundles (WILD-016 with EDA-038). Legs of level links closer than a few pixels at the scale of their level become one stroke.",
1108
+ defaults: { ...DETAIL_DEFAULTS },
1109
+ schema: DETAIL_SCHEMA,
1110
+ run: (input, o) => detailLevels(input, o),
1111
+ }),
1045
1112
  });
1046
1113
 
1047
1114
  /**
@@ -1058,6 +1125,14 @@ export const BOARD_PORTFOLIO_SETTINGS: readonly PortfolioSetting[] = Object.free
1058
1125
  ].map((s) => Object.freeze(s)),
1059
1126
  );
1060
1127
 
1128
+ /**
1129
+ * The pool of routes of the whole strategy `curriculum` (RULE-048), in order: the routers of the base of the screen. The first one gives the first answer.
1130
+ * The option `settings` takes the first ones.
1131
+ */
1132
+ export const CURRICULUM_POOL: readonly PortfolioSetting[] = Object.freeze(
1133
+ (["corners", "shared", "trunks"] as const).map((name) => Object.freeze({ name, pipeline: { router: routerStrategies[name] as Strategy<"router"> } })),
1134
+ );
1135
+
1061
1136
  /**
1062
1137
  * The built-in whole strategies: the strategies of the step `whole`. The strategy `pipeline` runs one strategy of each step, as the demo of the site does.
1063
1138
  * Without options, it routes the graph with the router `corners`.
@@ -1065,6 +1140,10 @@ export const BOARD_PORTFOLIO_SETTINGS: readonly PortfolioSetting[] = Object.free
1065
1140
  export const wholeStrategies: {
1066
1141
  readonly pipeline: Strategy<"whole", PipelineOptions>;
1067
1142
  readonly "board-portfolio": Strategy<"whole", BoardPortfolioOptions>;
1143
+ readonly curriculum: Strategy<"whole", CurriculumOptions>;
1144
+ readonly preferences: Strategy<"whole", PreferenceOptions>;
1145
+ readonly fireflies: Strategy<"whole", FireflyOptions>;
1146
+ readonly tracing: Strategy<"whole", TracingOptions>;
1068
1147
  } = Object.freeze({
1069
1148
  pipeline: defineStrategy<"whole", PipelineOptions>({
1070
1149
  step: "whole",
@@ -1082,6 +1161,38 @@ export const wholeStrategies: {
1082
1161
  schema: BOARD_PORTFOLIO_SCHEMA,
1083
1162
  run: (graph, o) => boardPortfolio(graph, BOARD_PORTFOLIO_SETTINGS, o).output,
1084
1163
  }),
1164
+ curriculum: defineStrategy<"whole", CurriculumOptions>({
1165
+ step: "whole",
1166
+ name: "curriculum",
1167
+ describe: "A curriculum of rules (RULE-048). Each stage adds one rule, and each edge takes the route of a pool that improves it with no loss on the earlier rules.",
1168
+ defaults: { ...CURRICULUM_DEFAULTS },
1169
+ schema: CURRICULUM_SCHEMA,
1170
+ run: (graph, o) => curriculum(graph, CURRICULUM_POOL, o),
1171
+ }),
1172
+ preferences: defineStrategy<"whole", PreferenceOptions>({
1173
+ step: "whole",
1174
+ name: "preferences",
1175
+ describe: "Rule weights from preferences (RULE-038). A fit to made-up pairs gives the weights, and each edge takes the route of a pool with the least weighted cost.",
1176
+ defaults: { ...PREFERENCE_DEFAULTS },
1177
+ schema: PREFERENCE_SCHEMA,
1178
+ run: (graph, o) => preferences(graph, CURRICULUM_POOL, o),
1179
+ }),
1180
+ fireflies: defineStrategy<"whole", FireflyOptions>({
1181
+ step: "whole",
1182
+ name: "fireflies",
1183
+ describe: "Firefly and bee searches (NAT-047). A seeded swarm searches the discount and the margin of `shared` on the board, within a budget of settings.",
1184
+ defaults: { ...FIREFLY_DEFAULTS },
1185
+ schema: FIREFLY_SCHEMA,
1186
+ run: (graph, o) => fireflies(graph, routerStrategies.shared as Strategy<"router">, o).output,
1187
+ }),
1188
+ tracing: defineStrategy<"whole", TracingOptions>({
1189
+ step: "whole",
1190
+ name: "tracing",
1191
+ describe: "The least tracing load (WILD-057). The pipeline tries a fixed list of settings, and the least load of crossings, merges and bends on one edge wins.",
1192
+ defaults: { ...TRACING_DEFAULTS },
1193
+ schema: TRACING_SCHEMA,
1194
+ run: (graph, o) => tracing(graph, BOARD_PORTFOLIO_SETTINGS, o).output,
1195
+ }),
1085
1196
  });
1086
1197
 
1087
1198
  /** The built-in strategies of each step, in order. A consumer adds its own: `createRegistry([...BUILT_IN_STRATEGIES, mine])`. */
@@ -9,6 +9,16 @@ import type { CutOptions } from "../groups/cut/index.ts";
9
9
  import type { JustifyOptions } from "../plugs/justify.ts";
10
10
  import type { SidePlugOptions } from "../plugs/side.ts";
11
11
  import type { SpreadOptions } from "../plugs/spread/index.ts";
12
+ import type { AssignOptions } from "../plugs/assign/index.ts";
13
+ import type { LeaderOptions } from "../plugs/leaders/index.ts";
14
+ import type { BalanceOptions } from "../highways/balance/index.ts";
15
+ import type { ManifoldOptions } from "../highways/manifolds/index.ts";
16
+ import type { DetailOptions } from "../levels/detail/index.ts";
17
+ import type { CurriculumOptions } from "../whole/curriculum/index.ts";
18
+ import type { SlotOptions } from "../highways/slots/index.ts";
19
+ import type { PreferenceOptions } from "../whole/preferences/index.ts";
20
+ import type { FireflyOptions } from "../whole/fireflies/index.ts";
21
+ import type { TracingOptions } from "../whole/tracing/index.ts";
12
22
  import type { KdOptions } from "../groups/kd/index.ts";
13
23
  import type { KeyOptions } from "../groups/keys/index.ts";
14
24
  import type { HighwayBase } from "../highways/base.ts";
@@ -968,6 +978,12 @@ export const SPREAD_SCHEMA: OptionSchema<SpreadOptions> = {
968
978
  rounds: { type: "integer", min: 0, max: 8, step: 1, default: 2, describe: "The passes that sort each wall again by the new places of the far ends." },
969
979
  };
970
980
 
981
+ /** The declarations of the options of the plug strategy `assign` (PLUG-ASSIGN). */
982
+ export const ASSIGN_SCHEMA: OptionSchema<AssignOptions> = {
983
+ plugGap: { type: "number", min: 0, max: 24, step: 1, default: 8, describe: "The least gap between two plugs of one wall. On a short wall, the gap shrinks evenly." },
984
+ inset: { type: "number", min: 0, max: 12, step: 1, default: 6, describe: "A plug stays this far from the corners of its wall." },
985
+ };
986
+
971
987
  /** The declarations of the options of the whole strategy `pipeline`. */
972
988
  export const PIPELINE_SCHEMA: OptionSchema<PipelineOptions> = {
973
989
  router: { type: "strategy", step: "router", describe: "The router. A highway plan takes its place." },
@@ -1070,3 +1086,83 @@ export const BOARD_PORTFOLIO_SCHEMA: OptionSchema<BoardPortfolioOptions> = {
1070
1086
  ink: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2], default: 1, describe: "The weight of the ink saved (B1) in the score." },
1071
1087
  budget: { type: "number", min: 0, max: 10, step: 0.1, marks: [0, 1, 10], default: 0, describe: "The time budget in seconds. The value 0 sets no budget." },
1072
1088
  };
1089
+
1090
+ /** The declarations of the options of the highway strategy `balance` (EDA-017). */
1091
+ export const BALANCE_SCHEMA: OptionSchema<BalanceOptions> = {
1092
+ tau: { type: "number", min: 1.1, max: 2, step: 0.05, marks: [1.1, 1.2, 1.5, 2], default: 1.2, describe: "The target of the stretch of an edge: its route on the plan over its route alone." },
1093
+ iterations: { type: "integer", min: 5, max: 30, step: 1, marks: [5, 10, 30], default: 5, describe: "The number of updates of the prices." },
1094
+ end: { type: "enum", values: ["exit", "entry", "both"], default: "exit", describe: "The end of a ride that the price moves: the exit, the entry, or both by half." },
1095
+ from: { ...HIGHWAY_BASE, default: "shared-ways" },
1096
+ };
1097
+
1098
+ /** The declarations of the options of the highway strategy `manifolds` (WILD-036). */
1099
+ export const MANIFOLD_SCHEMA: OptionSchema<ManifoldOptions> = {
1100
+ kMin: { type: "integer", min: 3, max: 8, step: 1, marks: [3, 4, 6, 8], default: 3, describe: "A manifold has this number of taps or more." },
1101
+ band: { type: "number", min: 0.5, max: 2, step: 0.25, marks: [0.5, 1, 2], default: 1, describe: "The nodes of one row have centers in a band of this many median box heights." },
1102
+ side: { type: "enum", values: ["near", "far"], default: "near", describe: "The place of the header in the gap before the row: near the row, or near the feed node." },
1103
+ detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "A tap stays on the manifold only when its way is this many times the direct line or less." },
1104
+ from: { ...HIGHWAY_BASE, default: "shared-ways" },
1105
+ };
1106
+
1107
+ /** The declarations of the options of the highway strategy `slots` (RULE-047). */
1108
+ export const SLOT_SCHEMA: OptionSchema<SlotOptions> = {
1109
+ pull: { type: "number", min: 0.25, max: 8, step: 0.25, marks: [0.5, 1, 2, 4, 8], default: 2, describe: "The weight of the pull toward the slot of a new lane." },
1110
+ reach: { type: "number", min: 8, max: 64, step: 4, marks: [8, 16, 24, 48, 64], default: 24, describe: "A route point farther than this distance from a trunk gets no pull." },
1111
+ laneSpacing: { type: "number", min: 2, max: 8, step: 1, marks: [2, 4, 8], default: 4, describe: "The distance between two lanes of a trunk." },
1112
+ maxStretch: { type: "number", min: 1.05, max: 2, step: 0.05, marks: [1.1, 1.25, 1.5, 2], default: 1.25, describe: "The largest new length of a route over its first length after it joins a trunk." },
1113
+ keys: { type: "enum", values: ["same", "any"], default: "same", describe: "The edges that can join a trunk: only the edges with the key of the trunk, or each edge." },
1114
+ from: { ...HIGHWAY_BASE, default: "shared-ways" },
1115
+ };
1116
+
1117
+ /** The declarations of the options of the plug strategy `leaders` (EDA-041). */
1118
+ export const LEADER_SCHEMA: OptionSchema<LeaderOptions> = {
1119
+ leader: { type: "enum", values: ["s", "po", "opo"], default: "po", describe: "The type of leader: the order of the angles, the order along the wall, or even places." },
1120
+ slotSpacing: { type: "number", min: 2, max: 8, step: 1, marks: [2, 4, 8], default: 8, describe: "The least gap between two plugs of one wall." },
1121
+ inset: SPREAD_SCHEMA.inset,
1122
+ rounds: { type: "integer", min: 0, max: 4, step: 1, default: 2, describe: "The passes that sort each wall again by the new places of the far ends." },
1123
+ };
1124
+
1125
+ /** The declarations of the options of the level strategy `detail` (WILD-016 with EDA-038): the options of `depths`, and the limits of the collapse. */
1126
+ export const DETAIL_SCHEMA: OptionSchema<DetailOptions> = {
1127
+ ...DEPTHS_SCHEMA,
1128
+ pxMin: { type: "number", min: 0.5, max: 4, step: 0.5, marks: [1, 2, 3, 4], default: 3, describe: "Two parallel legs closer than this number of pixels on the screen become one stroke." },
1129
+ minCount: { type: "integer", min: 2, max: 5, step: 1, default: 2, describe: "A stroke holds this number of edges or more." },
1130
+ w0: { type: "number", min: 1, max: 4, step: 0.5, default: 1.5, describe: "The width of a stroke of one edge, in pixels." },
1131
+ widthScale: { type: "enum", values: ["linear", "sqrt", "log"], default: "log", describe: "The growth of the width of a stroke with its count." },
1132
+ };
1133
+
1134
+ /** The declarations of the options of the whole strategy `curriculum` (RULE-048). */
1135
+ export const CURRICULUM_SCHEMA: OptionSchema<CurriculumOptions> = {
1136
+ order: { type: "enum", values: ["R1-R12-R2", "R12-R1-R2", "R1-R2-R12"], default: "R1-R12-R2", describe: "The order in which the stages add the rules." },
1137
+ passes: { type: "integer", min: 1, max: 3, step: 1, default: 1, describe: "The number of passes over the edges in each stage." },
1138
+ settings: { type: "integer", min: 2, max: 3, step: 1, default: 3, describe: "The number of settings in the pool of routes, from the start of its list." },
1139
+ detour: { type: "number", min: 1.5, max: 4, step: 0.25, marks: [1.5, 2, 3], default: 2, describe: "A new route has a detour of at most this value, or of its old route." },
1140
+ };
1141
+
1142
+ /** The declarations of the options of the whole strategy `preferences` (RULE-038). */
1143
+ export const PREFERENCE_SCHEMA: OptionSchema<PreferenceOptions> = {
1144
+ l2: { type: "number", min: 0, max: 1, step: 0.01, marks: [0, 0.01, 0.1, 1], default: 0.01, describe: "The weight of the L2 term of the fit of the weights to the made-up pairs." },
1145
+ passes: { type: "integer", min: 1, max: 3, step: 1, default: 1, describe: "The number of passes over the edges." },
1146
+ settings: { type: "integer", min: 2, max: 3, step: 1, default: 3, describe: "The number of settings in the pool of routes, from the start of its list." },
1147
+ detour: { type: "number", min: 1.5, max: 4, step: 0.25, marks: [1.5, 2, 3], default: 2, describe: "A new route has a detour of at most this value, or of its old route." },
1148
+ };
1149
+
1150
+ /** The declarations of the options of the whole strategy `fireflies` (NAT-047). */
1151
+ export const FIREFLY_SCHEMA: OptionSchema<FireflyOptions> = {
1152
+ search: { type: "enum", values: ["firefly", "bees"], default: "firefly", describe: "The search: the firefly algorithm, or the artificial bee colony." },
1153
+ budget: { type: "integer", min: 4, max: 40, step: 1, marks: [4, 12, 20, 40], default: 12, describe: "The largest number of different settings that the search routes." },
1154
+ colony: { type: "integer", min: 4, max: 20, step: 1, marks: [4, 6, 10, 20], default: 6, describe: "The number of fireflies or food sources." },
1155
+ gamma: { type: "number", min: 0.01, max: 10, step: 0.01, scale: "log", marks: [0.01, 0.1, 1, 10], default: 1, describe: "The absorption of the light of a firefly." },
1156
+ alpha: { type: "number", min: 0.01, max: 0.5, step: 0.01, marks: [0.01, 0.1, 0.2, 0.5], default: 0.2, describe: "The size of the random step of a firefly, as a share of the range." },
1157
+ limit: { type: "integer", min: 10, max: 100, step: 10, marks: [10, 50, 100], default: 10, describe: "A scout bee leaves a food source after this number of tries with no gain." },
1158
+ seed: { type: "integer", min: 0, max: 1_000_000, step: 1, marks: [1, 2, 3], default: 1, describe: "The seed of the search. The same seed gives the same routes." },
1159
+ };
1160
+
1161
+ /** The declarations of the options of the whole strategy `tracing` (WILD-057). */
1162
+ export const TRACING_SCHEMA: OptionSchema<TracingOptions> = {
1163
+ crossing: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2, 4], default: 1, describe: "The weight of a crossing in the tracing load." },
1164
+ merge: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2, 4], default: 2, describe: "The weight of a merge or a split where the edge is not an outer lane." },
1165
+ bend: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 0.5, 1, 4], default: 0.5, describe: "The weight of a bend in the tracing load." },
1166
+ settings: { type: "integer", min: 1, max: 12, step: 1, marks: [1, 4, 8, 12], default: 4, describe: "The number of settings that the strategy tries, from the start of its list." },
1167
+ quantile: { type: "enum", values: ["q50", "q95", "mean"], default: "q95", describe: "The value of the loads of the edges that the pick reads." },
1168
+ };
@@ -0,0 +1,243 @@
1
+ import { checkProperties } from "../../bench/properties.ts";
2
+ import { dist, polylineLength, segmentsCross, type Point } from "../../geom.ts";
3
+ import type { Graph, Route } from "../../model.ts";
4
+ import type { WholeOutput } from "../../strategies/model.ts";
5
+ import { runPipeline } from "../pipeline/index.ts";
6
+ import type { PortfolioSetting } from "../portfolio/index.ts";
7
+
8
+ /** One rule of the curriculum: the crossings (R1), the lane separation (R12) or the crossing angle (R2). */
9
+ export type CurriculumRule = "R1" | "R12" | "R2";
10
+
11
+ /** The order in which the curriculum adds its rules. */
12
+ export type CurriculumOrder = "R1-R12-R2" | "R12-R1-R2" | "R1-R2-R12";
13
+
14
+ /** The values of `CurriculumOrder`. */
15
+ export const CURRICULUM_ORDERS: readonly CurriculumOrder[] = Object.freeze(["R1-R12-R2", "R12-R1-R2", "R1-R2-R12"]);
16
+
17
+ /** The options of the whole strategy `curriculum` (RULE-048). */
18
+ export interface CurriculumOptions {
19
+ /** The order in which the stages add the rules. The default is `R1-R12-R2`. */
20
+ readonly order: CurriculumOrder;
21
+ /** The number of passes over the edges in each stage, from 1 to 3. The default is 1. */
22
+ readonly passes: number;
23
+ /** The number of settings in the pool of routes, from the start of its list, from 2 to 3. The default is 3. */
24
+ readonly settings: number;
25
+ /** A new route has a detour of at most this value, or of its old route, from 1.5 to 4. The default is 2. */
26
+ readonly detour: number;
27
+ }
28
+
29
+ /** The default options of the strategy `curriculum`. */
30
+ export const CURRICULUM_DEFAULTS: Readonly<CurriculumOptions> = Object.freeze({ order: "R1-R12-R2", passes: 1, settings: 3, detour: 2 });
31
+
32
+ /** Two legs closer than this distance count for the lane separation (R12), as in the screen. */
33
+ const NEAR = 4;
34
+
35
+ /** A route with its bounding box, for the quick test of two routes. */
36
+ interface Shape {
37
+ readonly route: Route;
38
+ readonly x0: number;
39
+ readonly y0: number;
40
+ readonly x1: number;
41
+ readonly y1: number;
42
+ }
43
+
44
+ /** This function gives the shape of a route. */
45
+ function shapeOf(route: Route): Shape {
46
+ let x0 = Infinity;
47
+ let y0 = Infinity;
48
+ let x1 = -Infinity;
49
+ let y1 = -Infinity;
50
+ for (const p of route.points) {
51
+ x0 = Math.min(x0, p.x);
52
+ y0 = Math.min(y0, p.y);
53
+ x1 = Math.max(x1, p.x);
54
+ y1 = Math.max(y1, p.y);
55
+ }
56
+ return { route, x0, y0, x1, y1 };
57
+ }
58
+
59
+ /** The sine of 5 degrees, as a literal. A literal gives the same bits on each platform, so a decision does not depend on `Math.sin`. */
60
+ const SIN_5 = 0.08715574274765817;
61
+
62
+ /** The constants of the arc sine of fdlibm (`e_asin.c`). */
63
+ const PIO2_HI = 1.5707963267948966;
64
+ const PIO2_LO = 6.123233995736766e-17;
65
+ const PIO4_HI = 0.7853981633974483;
66
+ const PS = [1.66666666666666657415e-1, -3.25565818622400915405e-1, 2.01212532134862925881e-1, -4.00555345006794114027e-2, 7.91534994289814532176e-4, 3.47933107596021167570e-5] as const;
67
+ const QS = [-2.40339491173441421878, 2.02094576023350569471, -6.88283971605453293030e-1, 7.70381505559019352791e-2] as const;
68
+ const WORDS = new DataView(new ArrayBuffer(8));
69
+
70
+ /** This function gives the rational part `p / q` of the arc sine of fdlibm at `t`. */
71
+ function asinRatio(t: number): [number, number] {
72
+ const p = t * (PS[0] + t * (PS[1] + t * (PS[2] + t * (PS[3] + t * (PS[4] + t * PS[5])))));
73
+ const q = 1 + t * (QS[0] + t * (QS[1] + t * (QS[2] + t * QS[3])));
74
+ return [p, q];
75
+ }
76
+
77
+ /**
78
+ * This function gives the arc sine of `x` in radians, from -1 to 1. It is the algorithm of fdlibm, with only `+`, `*`, `/` and `Math.sqrt`.
79
+ * These operations are exact to the last bit on each platform, so the result does not depend on the engine.
80
+ */
81
+ export function arcsin(x: number): number {
82
+ const ax = Math.abs(x);
83
+ if (Number.isNaN(x) || ax > 1) return Number.NaN;
84
+ if (ax === 1) return x * PIO2_HI + x * PIO2_LO;
85
+ if (ax < 0.5) {
86
+ if (ax < 2 ** -27) return x;
87
+ const [p, q] = asinRatio(x * x);
88
+ return x + x * (p / q);
89
+ }
90
+ const t = (1 - ax) * 0.5;
91
+ const [p, q] = asinRatio(t);
92
+ const s = Math.sqrt(t);
93
+ let r: number;
94
+ WORDS.setFloat64(0, ax);
95
+ if (WORDS.getUint32(0) >= 0x3fef3333) {
96
+ r = PIO2_HI - (2 * (s + s * (p / q)) - PIO2_LO);
97
+ } else {
98
+ WORDS.setFloat64(0, s);
99
+ WORDS.setUint32(4, 0);
100
+ const w = WORDS.getFloat64(0);
101
+ const c = (t - w * w) / (s + w);
102
+ const pp = 2 * s * (p / q) - (PIO2_LO - 2 * c);
103
+ const qq = PIO4_HI - 2 * w;
104
+ r = PIO4_HI - (pp - qq);
105
+ }
106
+ return x > 0 ? r : -r;
107
+ }
108
+
109
+ /** This function gives the distance between the segments `ab` and `cd`, when they do not cross. */
110
+ function segmentGap(a: Point, b: Point, c: Point, d: Point): number {
111
+ const at = (p: Point, u: Point, v: Point): number => {
112
+ const dx = v.x - u.x;
113
+ const dy = v.y - u.y;
114
+ const l2 = dx * dx + dy * dy;
115
+ const t = l2 > 0 ? Math.max(0, Math.min(1, ((p.x - u.x) * dx + (p.y - u.y) * dy) / l2)) : 0;
116
+ const ex = p.x - u.x - t * dx;
117
+ const ey = p.y - u.y - t * dy;
118
+ return Math.sqrt(ex * ex + ey * ey);
119
+ };
120
+ return Math.min(at(a, c, d), at(b, c, d), at(c, a, b), at(d, a, b));
121
+ }
122
+
123
+ /**
124
+ * This function gives the costs of the rules between two routes. R1 is the number of crossings.
125
+ * R2 is the sum of `1 - θ / 90` over the crossings. θ is the crossing angle in degrees.
126
+ * R12 is the overlap of the legs of `a` and `b` that are within 5 degrees of parallel. The two legs are nearer than 4 units.
127
+ */
128
+ export function pairCosts(a: readonly Point[], b: readonly Point[]): Record<CurriculumRule, number> {
129
+ let r1 = 0;
130
+ let r2 = 0;
131
+ let r12 = 0;
132
+ for (let i = 1; i < a.length; i++) {
133
+ const p = a[i - 1]!;
134
+ const q = a[i]!;
135
+ const ux = q.x - p.x;
136
+ const uy = q.y - p.y;
137
+ const lu = Math.sqrt(ux * ux + uy * uy);
138
+ if (lu <= 1e-9) continue;
139
+ for (let j = 1; j < b.length; j++) {
140
+ const r = b[j - 1]!;
141
+ const s = b[j]!;
142
+ const vx = s.x - r.x;
143
+ const vy = s.y - r.y;
144
+ const lv = Math.sqrt(vx * vx + vy * vy);
145
+ if (lv <= 1e-9) continue;
146
+ if (segmentsCross(p, q, r, s)) {
147
+ r1++;
148
+ const angle = (arcsin(Math.min(1, Math.abs(ux * vy - uy * vx) / (lu * lv))) * 180) / Math.PI;
149
+ r2 += 1 - angle / 90;
150
+ continue;
151
+ }
152
+ if (Math.abs(ux * vy - uy * vx) > SIN_5 * lu * lv) continue;
153
+ const t0 = ((r.x - p.x) * ux + (r.y - p.y) * uy) / lu;
154
+ const t1 = ((s.x - p.x) * ux + (s.y - p.y) * uy) / lu;
155
+ const overlap = Math.min(lu, Math.max(t0, t1)) - Math.max(0, Math.min(t0, t1));
156
+ if (overlap > 1e-9 && segmentGap(p, q, r, s) < NEAR) r12 += overlap;
157
+ }
158
+ }
159
+ return { R1: r1, R2: r2, R12: r12 };
160
+ }
161
+
162
+ /** This function gives the detour of a route: its length over the distance between its ends. */
163
+ function detourOf(points: readonly Point[]): number {
164
+ const span = dist(points[0]!, points[points.length - 1]!);
165
+ return span > 1e-9 ? polylineLength(points) / span : 1;
166
+ }
167
+
168
+ /**
169
+ * This function runs the whole strategy `curriculum` (RULE-048, after curriculum learning, Bengio 2009). The record has no energy solver yet (RULE-019).
170
+ * Thus a pool of routes of fixed settings takes its place. Each edge can take its route from any setting of the pool.
171
+ * The first answer is the first setting. Each stage adds one rule, in the order `order`. A pass of a stage visits the edges, the worst first.
172
+ * An edge takes the route of the pool with the least cost of the new rule. That cost is lower, and the earlier rules do not get worse.
173
+ * A route with a break of a hard rule is not in the pool. The output has the routes alone, with no runs, bundles or levels.
174
+ */
175
+ export function curriculum(graph: Graph, list: readonly PortfolioSetting[], options: Partial<CurriculumOptions> = {}): WholeOutput {
176
+ const o = { ...CURRICULUM_DEFAULTS, ...options };
177
+ const count = Math.max(1, Math.min(list.length, Math.floor(o.settings)));
178
+ const pool = new Map<string, Route[]>();
179
+ let first: WholeOutput | undefined;
180
+ for (const s of list.slice(0, count)) {
181
+ const limit = (s.pipeline.highway ?? s.pipeline.router).maxNodes;
182
+ if (limit !== undefined && graph.nodes.length > limit) continue;
183
+ const output = runPipeline(graph, s.pipeline);
184
+ first ??= output;
185
+ const faulty = new Set(checkProperties(output.graph, output.result, 1_000_000).map((v) => v.edge));
186
+ for (const r of output.result.routes) {
187
+ if (faulty.has(r.edge) || r.points.length < 2) continue;
188
+ const list2 = pool.get(r.edge);
189
+ if (list2) list2.push(r);
190
+ else pool.set(r.edge, [r]);
191
+ }
192
+ }
193
+ if (!first) return runPipeline(graph, list[0]!.pipeline);
194
+ const rules = o.order.split("-") as CurriculumRule[];
195
+ const current = new Map<string, Shape>(first.result.routes.map((r) => [r.edge, shapeOf(r)]));
196
+ // A faulty first route takes the first route of the pool, so no stage starts from a break of a hard rule.
197
+ for (const [edge, shape] of current) {
198
+ const own = pool.get(edge);
199
+ if (own && !own.includes(shape.route)) current.set(edge, shapeOf(own[0]!));
200
+ }
201
+ const costOf = (edge: string, s: Shape): Record<CurriculumRule, number> => {
202
+ const sum = { R1: 0, R2: 0, R12: 0 };
203
+ for (const [other, t] of current) {
204
+ if (other === edge || t.x0 > s.x1 + NEAR || t.x1 < s.x0 - NEAR || t.y0 > s.y1 + NEAR || t.y1 < s.y0 - NEAR) continue;
205
+ const c = pairCosts(s.route.points, t.route.points);
206
+ sum.R1 += c.R1;
207
+ sum.R2 += c.R2;
208
+ sum.R12 += c.R12;
209
+ }
210
+ return sum;
211
+ };
212
+ const edges = [...current.keys()];
213
+ for (let stage = 0; stage < rules.length; stage++) {
214
+ const rule = rules[stage]!;
215
+ const earlier = rules.slice(0, stage);
216
+ for (let pass = 0; pass < Math.max(1, Math.floor(o.passes)); pass++) {
217
+ const costs = new Map(edges.map((e) => [e, costOf(e, current.get(e)!)]));
218
+ const order = [...edges].sort((a, b) => costs.get(b)![rule] - costs.get(a)![rule] || (a < b ? -1 : a > b ? 1 : 0));
219
+ let moved = 0;
220
+ for (const edge of order) {
221
+ const now = current.get(edge)!;
222
+ const base = costOf(edge, now);
223
+ if (base[rule] <= 1e-9) continue;
224
+ const cap = Math.max(o.detour, detourOf(now.route.points));
225
+ let best: { shape: Shape; cost: number } | undefined;
226
+ for (const r of pool.get(edge) ?? []) {
227
+ if (r === now.route || detourOf(r.points) > cap + 1e-9) continue;
228
+ const shape = shapeOf(r);
229
+ const c = costOf(edge, shape);
230
+ if (c[rule] >= base[rule] - 1e-9 || earlier.some((k) => c[k] > base[k] + 1e-9)) continue;
231
+ if (!best || c[rule] < best.cost - 1e-9) best = { shape, cost: c[rule] };
232
+ }
233
+ if (best) {
234
+ current.set(edge, best.shape);
235
+ moved++;
236
+ }
237
+ }
238
+ if (moved === 0) break;
239
+ }
240
+ }
241
+ const routes = first.result.routes.map((r) => current.get(r.edge)?.route ?? r);
242
+ return { graph: first.graph, result: { routes } };
243
+ }