@mark1russell7/highways 0.9.2 → 0.9.4
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 +84 -1
- package/build/index.d.ts +466 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +8119 -5058
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bench/screen/exercise.ts +2 -0
- package/src/bench/screen/set.ts +58 -1
- package/src/families/cases.ts +17 -9
- package/src/families/coverage.ts +502 -0
- package/src/families/index.ts +4 -1
- package/src/highways/bronchial/index.ts +144 -0
- package/src/highways/index.ts +2 -0
- package/src/highways/knee/index.ts +154 -0
- package/src/highways/knee/root.ts +19 -0
- package/src/index.ts +4 -0
- package/src/levels/venation/index.ts +191 -0
- package/src/plugs/fix.ts +112 -0
- package/src/plugs/justify.ts +66 -0
- package/src/plugs/row-cards.ts +46 -0
- package/src/plugs/side.ts +23 -2
- package/src/plugs/spread/index.ts +217 -0
- package/src/routers/annealing/index.ts +260 -0
- package/src/routers/corners.ts +3 -39
- package/src/routers/frames.ts +80 -16
- package/src/routers/gutter.ts +545 -0
- package/src/routers/index.ts +5 -2
- package/src/routers/shared.ts +40 -2
- package/src/routers/tight/index.ts +122 -0
- package/src/strategies/builtins.ts +87 -2
- package/src/strategies/schemas.ts +76 -0
- package/src/whole/portfolio/index.ts +108 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { byText } from "../../compare.ts";
|
|
2
|
+
import { dist, polylineLength } from "../../geom.ts";
|
|
3
|
+
import type { Graph, GroupRule, Result, Router } from "../../model.ts";
|
|
4
|
+
import { defineStrategy, type Strategy } from "../../strategies/model.ts";
|
|
5
|
+
import type { OptionSchema } from "../../strategies/options.ts";
|
|
6
|
+
import { sharedRouter, type SharedSearch, type Way } from "../shared.ts";
|
|
7
|
+
import { byCollection } from "../trunks.ts";
|
|
8
|
+
|
|
9
|
+
/** The edges that go first: the edges with few good ways (`tight`), or the edges with many good ways (`loose`). */
|
|
10
|
+
export type TightFirst = "tight" | "loose";
|
|
11
|
+
|
|
12
|
+
/** The values of `TightFirst`. */
|
|
13
|
+
export const TIGHT_FIRSTS: readonly TightFirst[] = Object.freeze(["tight", "loose"]);
|
|
14
|
+
|
|
15
|
+
/** The order of two edges with the same slack: by the edge id, or the longer edge first. */
|
|
16
|
+
export type TightTies = "id" | "length";
|
|
17
|
+
|
|
18
|
+
/** The values of `TightTies`. */
|
|
19
|
+
export const TIGHT_TIES: readonly TightTies[] = Object.freeze(["id", "length"]);
|
|
20
|
+
|
|
21
|
+
/** The options of the router `tight` (WILD-023: tight edges first, after list scheduling by slack). */
|
|
22
|
+
export interface TightOptions {
|
|
23
|
+
/** The cost factor of the links of the first way in the search of the second way, from 2 to 10. The default is 3. */
|
|
24
|
+
readonly penalty: number;
|
|
25
|
+
/**
|
|
26
|
+
* The edges that go first. With `tight`, the default, an edge whose second way costs much more than its first way goes first.
|
|
27
|
+
* With `loose`, the order turns around: the formula of the record, with the rising slack.
|
|
28
|
+
*/
|
|
29
|
+
readonly first: TightFirst;
|
|
30
|
+
/** The order of two edges with the same slack. The default is `length`: the longer edge first, as in `shared`. */
|
|
31
|
+
readonly ties: TightTies;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The default options of the router `tight`. */
|
|
35
|
+
export const TIGHT_DEFAULTS: Readonly<TightOptions> = Object.freeze({ penalty: 3, first: "tight", ties: "length" });
|
|
36
|
+
|
|
37
|
+
/** The declarations of the options of the router `tight` (WILD-023). */
|
|
38
|
+
export const TIGHT_SCHEMA: OptionSchema<TightOptions> = {
|
|
39
|
+
penalty: { type: "number", min: 2, max: 10, step: 0.5, marks: [2, 3, 5], default: 3, describe: "The cost factor of the links of the first way in the search of the second way." },
|
|
40
|
+
first: { type: "enum", values: ["tight", "loose"], default: "tight", describe: "The edges that go first: the edges with few good ways, or the edges with many good ways." },
|
|
41
|
+
ties: { type: "enum", values: ["id", "length"], default: "length", describe: "The order of two edges with the same slack: by the edge id, or the longer edge first." },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* This function gives the slack of each way (WILD-023): `L2 / L1 - 1`. `L1` is the length of the shortest way.
|
|
46
|
+
* `L2` is the cost of the shortest way when each link of the first way costs `penalty` times its length.
|
|
47
|
+
* A way with no second way has the slack `penalty - 1`. A way with a second way of near-equal length has a slack near 0.
|
|
48
|
+
*/
|
|
49
|
+
export function waySlacks(ways: readonly Way[], search: SharedSearch, penalty: number): Map<Way, number> {
|
|
50
|
+
const out = new Map<Way, number>();
|
|
51
|
+
const n = search.corners;
|
|
52
|
+
for (const w of ways) {
|
|
53
|
+
const first = search.find(w);
|
|
54
|
+
if (!first) {
|
|
55
|
+
out.set(w, 0);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const l1 = polylineLength(first.seq);
|
|
59
|
+
const own = new Set<number>();
|
|
60
|
+
for (let i = 1; i < first.ids.length; i++) {
|
|
61
|
+
const a = first.ids[i - 1]!;
|
|
62
|
+
const b = first.ids[i]!;
|
|
63
|
+
if (a < n && b < n) own.add(search.link(a, b));
|
|
64
|
+
}
|
|
65
|
+
const cost = (i: number, j: number, d: number): number => (own.has(search.link(i, j)) ? d * penalty : d);
|
|
66
|
+
const second = search.find(w, cost, 1);
|
|
67
|
+
let l2 = l1;
|
|
68
|
+
if (second) {
|
|
69
|
+
l2 = 0;
|
|
70
|
+
for (let i = 1; i < second.ids.length; i++) {
|
|
71
|
+
const a = second.ids[i - 1]!;
|
|
72
|
+
const b = second.ids[i]!;
|
|
73
|
+
const d = dist(second.seq[i - 1]!, second.seq[i]!);
|
|
74
|
+
l2 += a < n && b < n ? cost(a, b, d) : d;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
out.set(w, l1 > 1e-9 ? l2 / l1 - 1 : 0);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* This function makes the router `tight` (WILD-023). It is the router `shared` with another order of the edges of each key.
|
|
84
|
+
* With `first: tight`, the edges with the largest slack value go first: their second way costs much more, so they have few good ways.
|
|
85
|
+
* The edges with many ways of near-equal cost go last. The keys keep the order of `shared`.
|
|
86
|
+
*/
|
|
87
|
+
export function tightRouter(options: Partial<TightOptions> & { readonly name: string; readonly rule?: GroupRule; readonly maxNodes?: number }): Router {
|
|
88
|
+
const o = { ...TIGHT_DEFAULTS, ...options };
|
|
89
|
+
const penalty = Math.min(10, Math.max(1, o.penalty));
|
|
90
|
+
const sign = o.first === "loose" ? 1 : -1;
|
|
91
|
+
return sharedRouter(
|
|
92
|
+
{ name: o.name, ...(o.rule === undefined ? {} : { rule: o.rule }), ...(o.maxNodes === undefined ? {} : { maxNodes: o.maxNodes }) },
|
|
93
|
+
undefined,
|
|
94
|
+
{
|
|
95
|
+
order: (ways, search) => {
|
|
96
|
+
const slack = waySlacks(ways, search, penalty);
|
|
97
|
+
const span = (w: Way): number => dist(w.ps.point, w.pt.point);
|
|
98
|
+
return [...ways].sort(
|
|
99
|
+
(p, q) =>
|
|
100
|
+
byText(p.key, q.key) ||
|
|
101
|
+
sign * (slack.get(p)! - slack.get(q)!) ||
|
|
102
|
+
(o.ties === "length" ? span(q) - span(p) : 0) ||
|
|
103
|
+
byText(p.edge.id, q.edge.id),
|
|
104
|
+
);
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The router strategy `tight` (WILD-023), with the rule `byCollection` of `shared`. It is not a built-in router, so the benchmark and the hashes of `ROUTERS` do not change.
|
|
112
|
+
* A screen module or a consumer adds it to a registry.
|
|
113
|
+
*/
|
|
114
|
+
export const tightStrategy: Strategy<"router", TightOptions> = defineStrategy({
|
|
115
|
+
step: "router",
|
|
116
|
+
name: "tight",
|
|
117
|
+
describe: "Tight edges first (WILD-023). The router `shared` routes first the edges whose second way costs much more than their first way.",
|
|
118
|
+
defaults: { ...TIGHT_DEFAULTS },
|
|
119
|
+
schema: TIGHT_SCHEMA,
|
|
120
|
+
maxNodes: 10_000,
|
|
121
|
+
run: (graph: Graph, o: Readonly<TightOptions>): Result => tightRouter({ ...o, name: "tight", rule: byCollection }).route(graph),
|
|
122
|
+
});
|
|
@@ -46,6 +46,8 @@ import { P_HUB_DEFAULTS, pHubPlan, type PHubOptions } from "../highways/p-hub/in
|
|
|
46
46
|
import { SCALE_SPACE_DEFAULTS, scaleSpacePlan, type ScaleSpaceOptions } from "../highways/scale-space/index.ts";
|
|
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
|
+
import { KNEE_DEFAULTS, kneePlan, type KneeOptions } from "../highways/knee/index.ts";
|
|
50
|
+
import { BRONCHIAL_DEFAULTS, bronchialPlan, type BronchialOptions } from "../highways/bronchial/index.ts";
|
|
49
51
|
import { SEARCH_DEFAULTS, searchPlan, type SearchOptions } from "../highways/search/index.ts";
|
|
50
52
|
import { STEINER_DEFAULTS, steinerPlan, type SteinerOptions } from "../highways/steiner/index.ts";
|
|
51
53
|
import { STREET_DEFAULTS, streetPlan, type StreetOptions } from "../highways/streets/index.ts";
|
|
@@ -72,8 +74,12 @@ import { WEISZFELD_DEFAULTS, weiszfeldPlan, type WeiszfeldOptions } from "../hig
|
|
|
72
74
|
import { WSPD_DEFAULTS, wspdPlan, type WspdOptions } from "../highways/wspd/index.ts";
|
|
73
75
|
import { ZIPPER_DEFAULTS, zipperPlan, type ZipperOptions } from "../highways/zipper/index.ts";
|
|
74
76
|
import { depthLevels, type DepthLevelOptions } from "../levels/depths/index.ts";
|
|
77
|
+
import { VENATION_DEFAULTS, venationLevels, type VenationOptions } from "../levels/venation/index.ts";
|
|
75
78
|
import type { Graph, Result, Router } from "../model.ts";
|
|
76
79
|
import { facingEnds } from "../plugs/facing/index.ts";
|
|
80
|
+
import { justifyEnds, type JustifyOptions } from "../plugs/justify.ts";
|
|
81
|
+
import { sideEnds, type SidePlugOptions } from "../plugs/side.ts";
|
|
82
|
+
import { SPREAD_DEFAULTS, spreadEnds, type SpreadOptions } from "../plugs/spread/index.ts";
|
|
77
83
|
import { roomDemand, type RoomMethod, type RoomOptions } from "../room/index.ts";
|
|
78
84
|
import { levelRoom, type LevelRoomOptions } from "../room/levels/index.ts";
|
|
79
85
|
import { cornersRouter, type CornersOptions } from "../routers/corners.ts";
|
|
@@ -84,11 +90,11 @@ import { straightRouter } from "../routers/straight.ts";
|
|
|
84
90
|
import { byCollection, trunksRouter, type TrunksOptions } from "../routers/trunks.ts";
|
|
85
91
|
import { buildSections, SECTION_DEFAULTS, type SectionOptions } from "../sections.ts";
|
|
86
92
|
import { runPipeline, type PipelineOptions } from "../whole/pipeline/index.ts";
|
|
93
|
+
import { BOARD_PORTFOLIO_DEFAULTS, boardPortfolio, type BoardPortfolioOptions, type PortfolioSetting } from "../whole/portfolio/index.ts";
|
|
87
94
|
import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
|
|
88
95
|
import type { OptionSchema } from "./options.ts";
|
|
89
96
|
import { AREA_SCHEMA, CONFLUENT_SCHEMA, CROSSBAR_SCHEMA, FRECHET_SCHEMA, MLSEB_SCHEMA, POWER_SCHEMA, SBEB_SCHEMA } from "./schemas.ts";
|
|
90
|
-
import { DEPTHS_SCHEMA, PIPELINE_SCHEMA } from "./schemas.ts";
|
|
91
|
-
import { 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 } 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";
|
|
92
98
|
|
|
93
99
|
/** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
|
|
94
100
|
export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
|
|
@@ -472,6 +478,8 @@ export const highwayStrategies: {
|
|
|
472
478
|
readonly streets: Strategy<"highway", StreetOptions>;
|
|
473
479
|
readonly search: Strategy<"highway", SearchOptions>;
|
|
474
480
|
readonly interchanges: Strategy<"highway", InterchangeOptions>;
|
|
481
|
+
readonly knee: Strategy<"highway", KneeOptions>;
|
|
482
|
+
readonly bronchial: Strategy<"highway", BronchialOptions>;
|
|
475
483
|
readonly "corridors+breakeven": Strategy<"highway", CorridorOptions & BreakEvenOptions>;
|
|
476
484
|
readonly "corridors+stretch": Strategy<"highway", CorridorOptions & StretchOptions>;
|
|
477
485
|
readonly "corridors+ambiguity": Strategy<"highway", CorridorOptions & AmbiguityGuardOptions>;
|
|
@@ -947,6 +955,24 @@ export const highwayStrategies: {
|
|
|
947
955
|
maxNodes: 10_000,
|
|
948
956
|
run: (input: HighwayInput, o: Readonly<InterchangeOptions>) => interchangePlan(input, o),
|
|
949
957
|
}),
|
|
958
|
+
knee: defineStrategy({
|
|
959
|
+
step: "highway",
|
|
960
|
+
name: "knee",
|
|
961
|
+
describe: "The knee of the plant front (NAT-018). A sweep of the balancing factor of NAT-017 gives trees, and the tree at the knee of ink against path length wins.",
|
|
962
|
+
defaults: { ...KNEE_DEFAULTS } as KneeOptions,
|
|
963
|
+
schema: KNEE_SCHEMA,
|
|
964
|
+
maxNodes: 10_000,
|
|
965
|
+
run: (input: HighwayInput, o: Readonly<KneeOptions>) => kneePlan(input, o),
|
|
966
|
+
}),
|
|
967
|
+
bronchial: defineStrategy({
|
|
968
|
+
step: "highway",
|
|
969
|
+
name: "bronchial",
|
|
970
|
+
describe: "Bronchial splits that fill the space (NAT-019). The tree grows toward the center of its targets, then splits the targets in two halves, again and again.",
|
|
971
|
+
defaults: { ...BRONCHIAL_DEFAULTS } as BronchialOptions,
|
|
972
|
+
schema: BRONCHIAL_SCHEMA,
|
|
973
|
+
maxNodes: 10_000,
|
|
974
|
+
run: (input: HighwayInput, o: Readonly<BronchialOptions>) => bronchialPlan(input, o),
|
|
975
|
+
}),
|
|
950
976
|
"corridors+breakeven": gateStrategy(corridors, breakEvenGate),
|
|
951
977
|
"corridors+stretch": gateStrategy(corridors, stretchGate),
|
|
952
978
|
"corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
|
|
@@ -957,6 +983,9 @@ export const highwayStrategies: {
|
|
|
957
983
|
/** The built-in plug rules: the strategies of the step `plugs`. The router `corners` routes between their plugs with its option `plug`. */
|
|
958
984
|
export const plugStrategies: {
|
|
959
985
|
readonly facing: Strategy<"plugs", object>;
|
|
986
|
+
readonly side: Strategy<"plugs", SidePlugOptions>;
|
|
987
|
+
readonly justify: Strategy<"plugs", JustifyOptions>;
|
|
988
|
+
readonly spread: Strategy<"plugs", SpreadOptions>;
|
|
960
989
|
} = Object.freeze({
|
|
961
990
|
facing: defineStrategy({
|
|
962
991
|
step: "plugs",
|
|
@@ -966,11 +995,36 @@ export const plugStrategies: {
|
|
|
966
995
|
schema: {},
|
|
967
996
|
run: (input) => facingEnds(input),
|
|
968
997
|
}),
|
|
998
|
+
side: defineStrategy<"plugs", SidePlugOptions>({
|
|
999
|
+
step: "plugs",
|
|
1000
|
+
name: "side",
|
|
1001
|
+
describe: "WILD-001. The search of a route starts from the whole free side, and the plug goes where the way leaves the side. It is `corners` with `plugs: side`.",
|
|
1002
|
+
defaults: { bend: 1, inset: 6, most: 16 },
|
|
1003
|
+
schema: SIDE_PLUG_SCHEMA,
|
|
1004
|
+
run: (input, o) => sideEnds(input, o),
|
|
1005
|
+
}),
|
|
1006
|
+
justify: defineStrategy<"plugs", JustifyOptions>({
|
|
1007
|
+
step: "plugs",
|
|
1008
|
+
name: "justify",
|
|
1009
|
+
describe: "WILD-002. The plugs of `facing`, then the free plugs of each side keep their order and a minimum gap. They move the least distance.",
|
|
1010
|
+
defaults: { plugGap: 8 },
|
|
1011
|
+
schema: JUSTIFY_SCHEMA,
|
|
1012
|
+
run: (input, o) => justifyEnds(input, o),
|
|
1013
|
+
}),
|
|
1014
|
+
spread: defineStrategy<"plugs", SpreadOptions>({
|
|
1015
|
+
step: "plugs",
|
|
1016
|
+
name: "spread",
|
|
1017
|
+
describe: "PLUG-SPREAD. The free plugs of each wall share its room with a minimum gap, in the order of their far ends. On a short wall the gap shrinks, and no plug drops.",
|
|
1018
|
+
defaults: { ...SPREAD_DEFAULTS },
|
|
1019
|
+
schema: SPREAD_SCHEMA,
|
|
1020
|
+
run: (input, o) => spreadEnds(input, o),
|
|
1021
|
+
}),
|
|
969
1022
|
});
|
|
970
1023
|
|
|
971
1024
|
/** The built-in level rules: the strategies of the step `levels`. The router `sections` makes its zoom levels with its option `level`. */
|
|
972
1025
|
export const levelStrategies: {
|
|
973
1026
|
readonly depths: Strategy<"levels", DepthLevelOptions>;
|
|
1027
|
+
readonly venation: Strategy<"levels", VenationOptions>;
|
|
974
1028
|
} = Object.freeze({
|
|
975
1029
|
depths: defineStrategy<"levels", DepthLevelOptions>({
|
|
976
1030
|
step: "levels",
|
|
@@ -980,14 +1034,37 @@ export const levelStrategies: {
|
|
|
980
1034
|
schema: DEPTHS_SCHEMA,
|
|
981
1035
|
run: (input, o) => depthLevels(input, o),
|
|
982
1036
|
}),
|
|
1037
|
+
venation: defineStrategy<"levels", VenationOptions>({
|
|
1038
|
+
step: "levels",
|
|
1039
|
+
name: "venation",
|
|
1040
|
+
describe: "A closed venation backbone (NAT-016). The links of each level with groups take their ways along the relative neighbourhood graph of the items.",
|
|
1041
|
+
defaults: { ...VENATION_DEFAULTS },
|
|
1042
|
+
schema: VENATION_SCHEMA,
|
|
1043
|
+
run: (input, o) => venationLevels(input, o),
|
|
1044
|
+
}),
|
|
983
1045
|
});
|
|
984
1046
|
|
|
1047
|
+
/**
|
|
1048
|
+
* The fixed list of settings of the whole strategy `board-portfolio` (WILD-045), in order. The option `settings` takes the first ones.
|
|
1049
|
+
* The list holds routers and highway strategies with their defaults. It comes from the catalog of the strategies, not from a fit to the screen.
|
|
1050
|
+
*/
|
|
1051
|
+
export const BOARD_PORTFOLIO_SETTINGS: readonly PortfolioSetting[] = Object.freeze(
|
|
1052
|
+
[
|
|
1053
|
+
...(["shared", "corners", "trunks", "sections", "sections-tree", "corners-smooth"] as const).map((name) => ({ name, pipeline: { router: routerStrategies[name] as Strategy<"router"> } })),
|
|
1054
|
+
...(["corridors", "shared-ways", "road-classes", "steiner", "knee", "bronchial"] as const).map((name) => ({
|
|
1055
|
+
name: `highway:${name}`,
|
|
1056
|
+
pipeline: { router: routerStrategies.corners as Strategy<"router">, highway: highwayStrategies[name] as Strategy<"highway"> },
|
|
1057
|
+
})),
|
|
1058
|
+
].map((s) => Object.freeze(s)),
|
|
1059
|
+
);
|
|
1060
|
+
|
|
985
1061
|
/**
|
|
986
1062
|
* 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.
|
|
987
1063
|
* Without options, it routes the graph with the router `corners`.
|
|
988
1064
|
*/
|
|
989
1065
|
export const wholeStrategies: {
|
|
990
1066
|
readonly pipeline: Strategy<"whole", PipelineOptions>;
|
|
1067
|
+
readonly "board-portfolio": Strategy<"whole", BoardPortfolioOptions>;
|
|
991
1068
|
} = Object.freeze({
|
|
992
1069
|
pipeline: defineStrategy<"whole", PipelineOptions>({
|
|
993
1070
|
step: "whole",
|
|
@@ -997,6 +1074,14 @@ export const wholeStrategies: {
|
|
|
997
1074
|
schema: PIPELINE_SCHEMA,
|
|
998
1075
|
run: (graph, o) => runPipeline(graph, o),
|
|
999
1076
|
}),
|
|
1077
|
+
"board-portfolio": defineStrategy<"whole", BoardPortfolioOptions>({
|
|
1078
|
+
step: "whole",
|
|
1079
|
+
name: "board-portfolio",
|
|
1080
|
+
describe: "A portfolio for each board (WILD-045). The pipeline tries a fixed list of settings, and the least weighted rank of crossings, detour and ink wins.",
|
|
1081
|
+
defaults: { ...BOARD_PORTFOLIO_DEFAULTS },
|
|
1082
|
+
schema: BOARD_PORTFOLIO_SCHEMA,
|
|
1083
|
+
run: (graph, o) => boardPortfolio(graph, BOARD_PORTFOLIO_SETTINGS, o).output,
|
|
1084
|
+
}),
|
|
1000
1085
|
});
|
|
1001
1086
|
|
|
1002
1087
|
/** The built-in strategies of each step, in order. A consumer adds its own: `createRegistry([...BUILT_IN_STRATEGIES, mine])`. */
|
|
@@ -6,6 +6,9 @@ import type { MlsebOptions } from "../highways/mlseb/index.ts";
|
|
|
6
6
|
import type { PowerOptions } from "../highways/power/index.ts";
|
|
7
7
|
import type { SbebOptions } from "../highways/sbeb/index.ts";
|
|
8
8
|
import type { CutOptions } from "../groups/cut/index.ts";
|
|
9
|
+
import type { JustifyOptions } from "../plugs/justify.ts";
|
|
10
|
+
import type { SidePlugOptions } from "../plugs/side.ts";
|
|
11
|
+
import type { SpreadOptions } from "../plugs/spread/index.ts";
|
|
9
12
|
import type { KdOptions } from "../groups/kd/index.ts";
|
|
10
13
|
import type { KeyOptions } from "../groups/keys/index.ts";
|
|
11
14
|
import type { HighwayBase } from "../highways/base.ts";
|
|
@@ -41,6 +44,8 @@ import type { RoadClassOptions } from "../highways/road-classes/index.ts";
|
|
|
41
44
|
import type { ScaleSpaceOptions } from "../highways/scale-space/index.ts";
|
|
42
45
|
import type { GapOptions } from "../highways/gaps/index.ts";
|
|
43
46
|
import type { InterchangeOptions } from "../highways/interchanges/index.ts";
|
|
47
|
+
import type { KneeOptions } from "../highways/knee/index.ts";
|
|
48
|
+
import type { BronchialOptions } from "../highways/bronchial/index.ts";
|
|
44
49
|
import type { SearchOptions } from "../highways/search/index.ts";
|
|
45
50
|
import type { SteinerOptions } from "../highways/steiner/index.ts";
|
|
46
51
|
import type { StreetOptions } from "../highways/streets/index.ts";
|
|
@@ -56,6 +61,8 @@ import type { WspdOptions } from "../highways/wspd/index.ts";
|
|
|
56
61
|
import type { ZipperOptions } from "../highways/zipper/index.ts";
|
|
57
62
|
import type { LaneStretchOptions } from "../lane-orders/stretch/index.ts";
|
|
58
63
|
import type { DepthLevelOptions } from "../levels/depths/index.ts";
|
|
64
|
+
import type { VenationOptions } from "../levels/venation/index.ts";
|
|
65
|
+
import type { BoardPortfolioOptions } from "../whole/portfolio/index.ts";
|
|
59
66
|
import type { CornersOptions } from "../routers/corners.ts";
|
|
60
67
|
import type { SectionsOptions } from "../routers/sections.ts";
|
|
61
68
|
import type { SharedOptions } from "../routers/shared.ts";
|
|
@@ -138,6 +145,20 @@ export const CORNERS_SCHEMA: OptionSchema<RouterPreset<CornersOptions>> = {
|
|
|
138
145
|
step: 1,
|
|
139
146
|
describe: "The distance from the walls of a frame to the bounds of its inner routes. Without a value, the inset is the margin.",
|
|
140
147
|
},
|
|
148
|
+
inner: {
|
|
149
|
+
type: "enum",
|
|
150
|
+
values: ["corners", "gutter"],
|
|
151
|
+
default: "corners",
|
|
152
|
+
describe: "The mode of the inner routes. With `gutter`, each inner route is orthogonal in the gaps between the nodes of the frame, with a lane in each shared gap.",
|
|
153
|
+
},
|
|
154
|
+
bendCost: {
|
|
155
|
+
type: "number",
|
|
156
|
+
min: 0,
|
|
157
|
+
max: 100,
|
|
158
|
+
step: 1,
|
|
159
|
+
default: 16,
|
|
160
|
+
describe: "The cost of one bend of an inner route of the mode `gutter`, in units of length. A larger cost gives fewer bends.",
|
|
161
|
+
},
|
|
141
162
|
};
|
|
142
163
|
|
|
143
164
|
/** The declarations of the options of the router `trunks`. */
|
|
@@ -928,6 +949,25 @@ export const DEPTHS_SCHEMA: OptionSchema<DepthLevelOptions> = {
|
|
|
928
949
|
levels: SECTIONS_SCHEMA.levels,
|
|
929
950
|
};
|
|
930
951
|
|
|
952
|
+
/** The declarations of the options of the plug strategy `side` (WILD-001). The defaults are the plugs of `corners` with `plugs: "side"`. */
|
|
953
|
+
export const SIDE_PLUG_SCHEMA: OptionSchema<SidePlugOptions> = {
|
|
954
|
+
bend: { type: "number", min: 0, max: 4, step: 0.25, default: 1, describe: "The cost of a slant first leg: its length grows by this value times its angle to the normal." },
|
|
955
|
+
inset: { type: "number", min: 0, max: 12, step: 1, default: 6, describe: "A plug stays this far from the corners of its side." },
|
|
956
|
+
most: { type: "integer", min: 1, max: 64, step: 1, default: 16, describe: "The largest number of places that the search tries on each side." },
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
/** The declarations of the options of the plug strategy `justify` (WILD-002). */
|
|
960
|
+
export const JUSTIFY_SCHEMA: OptionSchema<JustifyOptions> = {
|
|
961
|
+
plugGap: { type: "number", min: 0, max: 24, step: 1, default: 8, describe: "The free plugs of a side keep their order and at least this gap. 0 keeps each plug where it faces." },
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
/** The declarations of the options of the plug strategy `spread` (PLUG-SPREAD). */
|
|
965
|
+
export const SPREAD_SCHEMA: OptionSchema<SpreadOptions> = {
|
|
966
|
+
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." },
|
|
967
|
+
inset: { type: "number", min: 0, max: 12, step: 1, default: 6, describe: "A plug stays this far from the corners of its wall." },
|
|
968
|
+
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
|
+
};
|
|
970
|
+
|
|
931
971
|
/** The declarations of the options of the whole strategy `pipeline`. */
|
|
932
972
|
export const PIPELINE_SCHEMA: OptionSchema<PipelineOptions> = {
|
|
933
973
|
router: { type: "strategy", step: "router", describe: "The router. A highway plan takes its place." },
|
|
@@ -994,3 +1034,39 @@ export const INTERCHANGE_SCHEMA: OptionSchema<InterchangeOptions> = {
|
|
|
994
1034
|
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." },
|
|
995
1035
|
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." },
|
|
996
1036
|
};
|
|
1037
|
+
|
|
1038
|
+
/** The declarations of the options of the highway strategy `knee` (NAT-018). */
|
|
1039
|
+
export const KNEE_SCHEMA: OptionSchema<KneeOptions> = {
|
|
1040
|
+
sweep: { type: "integer", min: 5, max: 21, step: 1, marks: [5, 11, 21], default: 11, describe: "The number of points of the sweep of the balancing factor." },
|
|
1041
|
+
rule: { type: "enum", values: ["chord", "weights"], default: "chord", describe: "The rule that picks one tree: the knee of the chord, or the least weighted sum." },
|
|
1042
|
+
ink: { type: "number", min: 0, max: 1, step: 0.05, marks: [0.25, 0.5, 0.75], default: 0.5, describe: "The weight of the ink in the rule `weights`. The rest of the weight goes to the path length." },
|
|
1043
|
+
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." },
|
|
1044
|
+
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." },
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
/** The declarations of the options of the highway strategy `bronchial` (NAT-019). */
|
|
1048
|
+
export const BRONCHIAL_SCHEMA: OptionSchema<BronchialOptions> = {
|
|
1049
|
+
split: { type: "enum", values: ["count", "weight", "gap"], default: "count", describe: "The rule that splits the targets of a tip in two: by count, by weight, or at the widest gap." },
|
|
1050
|
+
share: { type: "number", min: 0.3, max: 0.7, step: 0.05, marks: [0.3, 0.5, 0.7], default: 0.5, describe: "The share of the distance to the center of the targets that a branch grows." },
|
|
1051
|
+
smallest: { type: "integer", min: 1, max: 3, step: 1, default: 1, describe: "A set with this number of targets or fewer stops the splits." },
|
|
1052
|
+
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." },
|
|
1053
|
+
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." },
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
/** The declarations of the options of the level strategy `venation` (NAT-016): the options of `depths`, and the options of the backbone. */
|
|
1057
|
+
export const VENATION_SCHEMA: OptionSchema<VenationOptions> = {
|
|
1058
|
+
...DEPTHS_SCHEMA,
|
|
1059
|
+
backbone: { type: "enum", values: ["rng", "gabriel"], default: "rng", describe: "The graph of the backbone: the relative neighbourhood graph, or the Gabriel graph." },
|
|
1060
|
+
discount: { type: "number", min: 0, max: 0.8, step: 0.05, marks: [0, 0.4, 0.8], default: 0.4, describe: "The share of the cost of a backbone link that a later level link saves when an earlier link uses it." },
|
|
1061
|
+
detour: { type: "number", min: 1, max: 3, step: 0.05, marks: [1.25, 1.5, 2], default: 1.5, describe: "A level link takes its way on the backbone only when that way is this many times its direct link or less." },
|
|
1062
|
+
maxItems: { type: "integer", min: 10, max: 2000, step: 10, marks: [100, 400, 1000], default: 400, describe: "A level with more items than this number keeps the links of `depths`." },
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
/** The declarations of the options of the whole strategy `board-portfolio` (WILD-045). */
|
|
1066
|
+
export const BOARD_PORTFOLIO_SCHEMA: OptionSchema<BoardPortfolioOptions> = {
|
|
1067
|
+
settings: { type: "integer", min: 3, max: 12, step: 1, marks: [3, 4, 8, 12], default: 4, describe: "The number of settings that the portfolio tries, from the start of its list." },
|
|
1068
|
+
crossings: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2], default: 1, describe: "The weight of the crossings (R1) in the score." },
|
|
1069
|
+
detour: { type: "number", min: 0, max: 4, step: 0.25, marks: [0, 1, 2], default: 1, describe: "The weight of the median detour (R5) in the score." },
|
|
1070
|
+
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
|
+
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
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { inkSaved } from "../../bench/measures/bundles.ts";
|
|
2
|
+
import { detourSpread } from "../../bench/measures/routes.ts";
|
|
3
|
+
import { checkProperties } from "../../bench/properties.ts";
|
|
4
|
+
import { measureQuality } from "../../bench/quality.ts";
|
|
5
|
+
import type { Graph, Result } from "../../model.ts";
|
|
6
|
+
import type { WholeOutput } from "../../strategies/model.ts";
|
|
7
|
+
import { runPipeline, type PipelineOptions } from "../pipeline/index.ts";
|
|
8
|
+
|
|
9
|
+
/** The clock in milliseconds. */
|
|
10
|
+
const clock = (): number => (globalThis as { performance?: { now(): number } }).performance?.now() ?? Date.now();
|
|
11
|
+
|
|
12
|
+
/** One setting of the portfolio: a name and the options of the pipeline. */
|
|
13
|
+
export interface PortfolioSetting {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly pipeline: PipelineOptions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The options of the whole strategy `board-portfolio` (WILD-045). */
|
|
19
|
+
export interface BoardPortfolioOptions {
|
|
20
|
+
/** The number of settings that the portfolio tries, from the start of its list, from 3 to 12. The default is 4. */
|
|
21
|
+
readonly settings: number;
|
|
22
|
+
/** The weight of the crossings (R1) in the score, from 0 to 4. The default is 1. */
|
|
23
|
+
readonly crossings: number;
|
|
24
|
+
/** The weight of the median detour (R5) in the score, from 0 to 4. The default is 1. */
|
|
25
|
+
readonly detour: number;
|
|
26
|
+
/** The weight of the ink saved (B1) in the score, from 0 to 4. The default is 1. */
|
|
27
|
+
readonly ink: number;
|
|
28
|
+
/**
|
|
29
|
+
* The time budget in seconds, from 0 to 10. After the budget, no new setting starts. The first setting always runs.
|
|
30
|
+
* The value 0, the default, sets no budget, so the output does not depend on the speed of the machine.
|
|
31
|
+
*/
|
|
32
|
+
readonly budget: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The default options of the strategy `board-portfolio`. */
|
|
36
|
+
export const BOARD_PORTFOLIO_DEFAULTS: Readonly<BoardPortfolioOptions> = Object.freeze({ settings: 4, crossings: 1, detour: 1, ink: 1, budget: 0 });
|
|
37
|
+
|
|
38
|
+
/** The measures of one setting on the board, and its output. */
|
|
39
|
+
export interface PortfolioEntry {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly output: WholeOutput;
|
|
42
|
+
readonly crossings: number;
|
|
43
|
+
readonly detour: number;
|
|
44
|
+
readonly ink: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The result of `boardPortfolio`: the output and the name of the pick. It also holds the measures of each setting with no break of a hard rule. */
|
|
48
|
+
export interface BoardPortfolioRun {
|
|
49
|
+
readonly output: WholeOutput;
|
|
50
|
+
readonly pick: string;
|
|
51
|
+
readonly entries: readonly PortfolioEntry[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** This function gives the rank of each value from 0 (the best) to 1 (the worst). Equal values get the mean of their ranks. One value gets 0. */
|
|
55
|
+
export function rankShares(values: readonly number[], lowerIsBetter: boolean): number[] {
|
|
56
|
+
const n = values.length;
|
|
57
|
+
if (n < 2) return values.map(() => 0);
|
|
58
|
+
const order = values.map((_, i) => i).sort((a, b) => (lowerIsBetter ? values[a]! - values[b]! : values[b]! - values[a]!));
|
|
59
|
+
const rank = new Array<number>(n).fill(0);
|
|
60
|
+
for (let i = 0; i < n; ) {
|
|
61
|
+
let j = i;
|
|
62
|
+
while (j + 1 < n && Math.abs(values[order[j + 1]!]! - values[order[i]!]!) <= 1e-9) j++;
|
|
63
|
+
for (let k = i; k <= j; k++) rank[order[k]!] = (i + j) / 2 / (n - 1);
|
|
64
|
+
i = j + 1;
|
|
65
|
+
}
|
|
66
|
+
return rank;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* This function runs the portfolio for one board (WILD-045, after Rice, 1976). It reads only the board and the outputs of its own settings.
|
|
71
|
+
* A setting whose router or highway strategy has a node limit below the size of the board does not run.
|
|
72
|
+
* A setting with a break of a hard rule does not count. The score of a setting is `Σ w_i × the rank share of measure_i` over the settings.
|
|
73
|
+
* The measures are the crossings (R1), the median detour (R5) and the ink saved (B1). The least score wins, and a tie goes to the earlier setting.
|
|
74
|
+
*/
|
|
75
|
+
export function boardPortfolio(graph: Graph, list: readonly PortfolioSetting[], o: Readonly<BoardPortfolioOptions>, now: () => number = clock): BoardPortfolioRun {
|
|
76
|
+
const count = Math.max(1, Math.min(list.length, Math.floor(o.settings)));
|
|
77
|
+
const t0 = now();
|
|
78
|
+
const entries: PortfolioEntry[] = [];
|
|
79
|
+
let fallback: { name: string; output: WholeOutput } | undefined;
|
|
80
|
+
for (const s of list.slice(0, count)) {
|
|
81
|
+
if (o.budget > 0 && fallback !== undefined && now() - t0 > o.budget * 1000) break;
|
|
82
|
+
const limit = (s.pipeline.highway ?? s.pipeline.router).maxNodes;
|
|
83
|
+
if (limit !== undefined && graph.nodes.length > limit) continue;
|
|
84
|
+
const output = runPipeline(graph, s.pipeline);
|
|
85
|
+
fallback ??= { name: s.name, output };
|
|
86
|
+
if (checkProperties(output.graph, output.result, 1).length > 0) continue;
|
|
87
|
+
entries.push({ name: s.name, output, ...measuresOf(output.graph, output.result) });
|
|
88
|
+
}
|
|
89
|
+
if (entries.length === 0) {
|
|
90
|
+
const first = fallback ?? { name: list[0]!.name, output: runPipeline(graph, list[0]!.pipeline) };
|
|
91
|
+
return { output: first.output, pick: first.name, entries };
|
|
92
|
+
}
|
|
93
|
+
const r1 = rankShares(entries.map((e) => e.crossings), true);
|
|
94
|
+
const r5 = rankShares(entries.map((e) => e.detour), true);
|
|
95
|
+
const b1 = rankShares(entries.map((e) => e.ink), false);
|
|
96
|
+
let best = 0;
|
|
97
|
+
let score = Infinity;
|
|
98
|
+
entries.forEach((_, i) => {
|
|
99
|
+
const s = o.crossings * r1[i]! + o.detour * r5[i]! + o.ink * b1[i]!;
|
|
100
|
+
if (s < score - 1e-9) [best, score] = [i, s];
|
|
101
|
+
});
|
|
102
|
+
return { output: entries[best]!.output, pick: entries[best]!.name, entries };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** This function gives the three measures of the score of one output: the crossings, the median detour and the ink saved. */
|
|
106
|
+
function measuresOf(graph: Graph, result: Result): { crossings: number; detour: number; ink: number } {
|
|
107
|
+
return { crossings: measureQuality(graph, result).crossings, detour: detourSpread(result.routes).median, ink: inkSaved(result.routes) };
|
|
108
|
+
}
|