@mark1russell7/highways 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -2
- package/build/index.d.ts +1489 -63
- package/build/index.d.ts.map +1 -1
- package/build/index.js +10139 -3356
- package/build/index.js.map +1 -1
- package/package.json +11 -2
- package/src/bench/exact/index.ts +218 -11
- package/src/bench/exact/optima-exact-v1-bounds.json +14409 -0
- package/src/bench/exact/optima-exact-v1.json +4809 -0
- package/src/bench/exact/solve.ts +129 -0
- package/src/bench/screen/activation.ts +3 -0
- package/src/bench/screen/bounds.ts +140 -0
- package/src/bench/screen/compare.ts +26 -0
- package/src/bench/screen/exact-set.ts +100 -0
- package/src/bench/screen/exercise.ts +290 -0
- package/src/bench/screen/forms.ts +14 -0
- package/src/bench/screen/gap.ts +316 -0
- package/src/bench/screen/hash.ts +7 -76
- package/src/bench/screen/index.ts +6 -0
- package/src/bench/screen/measures.ts +71 -1
- package/src/bench/screen/portfolio-pick.ts +20 -0
- package/src/bench/screen/screen.ts +50 -2
- package/src/bench/screen/set.ts +3 -2
- package/src/highways/areas/index.ts +160 -0
- package/src/highways/confluent/index.ts +154 -0
- package/src/highways/confluent/k23.test-util.ts +8 -0
- package/src/highways/consensus/index.ts +129 -0
- package/src/highways/crossbars/index.ts +184 -0
- package/src/highways/cubu/index.ts +26 -0
- package/src/highways/divided/index.ts +111 -0
- package/src/highways/fdeb/index.ts +193 -0
- package/src/highways/frechet/index.ts +232 -0
- package/src/highways/hierarchy/index.ts +140 -0
- package/src/highways/index.ts +64 -0
- package/src/highways/kdeeb/index.ts +355 -0
- package/src/highways/link-communities/index.ts +189 -0
- package/src/highways/mesh/index.ts +209 -0
- package/src/highways/mlseb/index.ts +204 -0
- package/src/highways/p-hub/index.ts +248 -0
- package/src/highways/power/index.ts +117 -0
- package/src/highways/resistance/index.ts +159 -0
- package/src/highways/sbeb/index.ts +303 -0
- package/src/highways/scale-space/index.ts +135 -0
- package/src/highways/spectral/index.ts +391 -0
- package/src/highways/territories/index.ts +150 -0
- package/src/highways/trace.ts +153 -0
- package/src/index.ts +3 -0
- package/src/portfolio/bands.ts +38 -0
- package/src/portfolio/fit.ts +41 -0
- package/src/portfolio/index.ts +6 -0
- package/src/portfolio/key.ts +98 -0
- package/src/portfolio/members.ts +171 -0
- package/src/portfolio/router.ts +133 -0
- package/src/portfolio/sets.ts +40 -0
- package/src/strategies/builtins.ts +222 -1
- package/src/strategies/schemas.ts +280 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// The portfolio router (PORTFOLIO-API, PORTFOLIO-FIT): it runs the base `shared` and a small greedy set of members on an input, and gives the output with the most
|
|
2
|
+
// ink saved inside the rule and the stretch cap of the caller. The combinator is `portfolio` of `@mark1russell7/layout-contract`.
|
|
3
|
+
// The router is opt-in: it is not in the registry `STRATEGIES`, so no default output changes.
|
|
4
|
+
import { pickMoves, portfolio, type Member, type PortfolioTrace } from "../bench/screen/portfolio-pick.ts";
|
|
5
|
+
import type { Graph, Result, Router } from "../model.ts";
|
|
6
|
+
import { asRouter, defineStrategy, type Strategy } from "../strategies/model.ts";
|
|
7
|
+
import { PORTFOLIO_CAPS, PORTFOLIO_KS_API, PORTFOLIO_RULES, portfolioBand, type PortfolioCap, type PortfolioRule } from "./bands.ts";
|
|
8
|
+
import { PORTFOLIO_FIT } from "./fit.ts";
|
|
9
|
+
import { portfolioKey } from "./key.ts";
|
|
10
|
+
import { portfolioMember } from "./members.ts";
|
|
11
|
+
import { PORTFOLIO_SETS } from "./sets.ts";
|
|
12
|
+
|
|
13
|
+
/** The options of the portfolio router. */
|
|
14
|
+
export interface PortfolioRouterOptions {
|
|
15
|
+
/** The rule of the caller. Under `collection` (the default), a shared segment must hold edges of one collection only. Under `all`, collections can mix. */
|
|
16
|
+
readonly rule?: PortfolioRule;
|
|
17
|
+
/** The stretch cap of the caller: 1.1, 1.25 or 1.5 (the default). An output with an edge above the cap does not win. The base is the fallback output. */
|
|
18
|
+
readonly cap?: PortfolioCap;
|
|
19
|
+
/**
|
|
20
|
+
* The count of members after the base: 3, 5 or 8. Without `k` (the default), the router runs the fit of `fit.ts`.
|
|
21
|
+
* The fit gives a member count for each rule, cap and band, and a node limit for each member. Thus the default is deterministic.
|
|
22
|
+
*/
|
|
23
|
+
readonly k?: 3 | 5 | 8;
|
|
24
|
+
/**
|
|
25
|
+
* A time limit of the pick in milliseconds, as a ceiling that the caller sets. The base always runs. The combinator cuts the members that start after the limit.
|
|
26
|
+
* A cut by the budget makes the output depend on the speed of the machine. Then the trace has `deterministic: false`.
|
|
27
|
+
*/
|
|
28
|
+
readonly budgetMs?: number;
|
|
29
|
+
/** The largest count of members that run, with the base. */
|
|
30
|
+
readonly maxMembers?: number;
|
|
31
|
+
/** The clock in milliseconds. The default is `performance.now`. */
|
|
32
|
+
readonly now?: () => number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The base member of each portfolio: the router `shared` with the rule `byCollection`. It is the fallback output above the cap. */
|
|
36
|
+
export const PORTFOLIO_BASE_MEMBER = "shared";
|
|
37
|
+
|
|
38
|
+
/** The result of the portfolio router: the output of the winner, the winner, the trace of the pick, the move counts and the band. */
|
|
39
|
+
export interface PortfolioRoute {
|
|
40
|
+
readonly result: Result;
|
|
41
|
+
readonly winner: string;
|
|
42
|
+
readonly trace: PortfolioTrace;
|
|
43
|
+
/** The move count of the pick (ACTIVATION): `{ "move.pick": 1 }` when the winner is not the base and has another output. */
|
|
44
|
+
readonly moves: Readonly<Record<string, number>>;
|
|
45
|
+
/** The size band of the input, from its node count. */
|
|
46
|
+
readonly band: string;
|
|
47
|
+
/** The names of the members, in their order. */
|
|
48
|
+
readonly members: readonly string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** This function checks the options of the portfolio router, and gives them with their defaults. It throws a `RangeError` for a bad value. */
|
|
52
|
+
function checked(o: PortfolioRouterOptions): { rule: PortfolioRule; cap: PortfolioCap; k: 3 | 5 | 8 | undefined } {
|
|
53
|
+
const rule = o.rule ?? "collection";
|
|
54
|
+
const cap = o.cap ?? 1.5;
|
|
55
|
+
const k = o.k;
|
|
56
|
+
if (!PORTFOLIO_RULES.includes(rule)) throw new RangeError(`The portfolio router has the rule "${String(rule)}". It must be ${PORTFOLIO_RULES.join(" or ")}.`);
|
|
57
|
+
if (!PORTFOLIO_CAPS.includes(cap)) throw new RangeError(`The portfolio router has the cap ${String(cap)}. It must be ${PORTFOLIO_CAPS.join(", ")}.`);
|
|
58
|
+
if (k !== undefined && !PORTFOLIO_KS_API.includes(k)) throw new RangeError(`The portfolio router has the k ${String(k)}. It must be ${PORTFOLIO_KS_API.join(", ")}.`);
|
|
59
|
+
return { rule, cap, k };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** This function gives the name of the portfolio router with the options `options`: `portfolio-<rule>-<cap>-k<k>`, or `portfolio-<rule>-<cap>-fit` without `k`. */
|
|
63
|
+
export function portfolioName(options: PortfolioRouterOptions = {}): string {
|
|
64
|
+
const { rule, cap, k } = checked(options);
|
|
65
|
+
return `portfolio-${rule}-${cap}-${k === undefined ? "fit" : `k${k}`}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* This function gives the names of the members for an input with `nodes` nodes. The base comes first.
|
|
70
|
+
* - With `k`, the first `k` names of the set of the band follow.
|
|
71
|
+
* - Without `k`, the members of the fit of the band follow, each only when `nodes` is at or below its node limit.
|
|
72
|
+
*/
|
|
73
|
+
export function portfolioMembers(nodes: number, options: PortfolioRouterOptions = {}): string[] {
|
|
74
|
+
const { rule, cap, k } = checked(options);
|
|
75
|
+
const key: `${PortfolioRule}|${PortfolioCap}|${string}` = `${rule}|${cap}|${portfolioBand(nodes).id}`;
|
|
76
|
+
if (k === undefined) return [PORTFOLIO_BASE_MEMBER, ...(PORTFOLIO_FIT[key] ?? []).filter(([s, limit]) => s !== PORTFOLIO_BASE_MEMBER && nodes <= limit).map(([s]) => s)];
|
|
77
|
+
const set = PORTFOLIO_SETS[key] ?? [];
|
|
78
|
+
return [PORTFOLIO_BASE_MEMBER, ...set.filter((s) => s !== PORTFOLIO_BASE_MEMBER).slice(0, k)];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* This function routes the graph `graph` with the portfolio of its size band, rule and cap. The base runs first, then the members in their order.
|
|
83
|
+
* The key is `portfolioKey`: a member wins only when it passes the guard against the base. Of those members, the winner has the most ink saved.
|
|
84
|
+
* A tie within the `minEffect` of B1 goes to the first member (`selectBest`). A member that throws does not win.
|
|
85
|
+
* The result carries the trace of the pick and the move count `move.pick`.
|
|
86
|
+
*/
|
|
87
|
+
export function routePortfolio(graph: Graph, options: PortfolioRouterOptions = {}): PortfolioRoute {
|
|
88
|
+
// A member with a node limit below the size of the graph does not run. The base `shared` has no such limit below the limit of the router.
|
|
89
|
+
const names = portfolioMembers(graph.nodes.length, options).filter((name) => name === PORTFOLIO_BASE_MEMBER || (portfolioMember(name)?.maxNodes ?? Infinity) >= graph.nodes.length);
|
|
90
|
+
const { rule, cap } = checked(options);
|
|
91
|
+
// The key compares each member with the output of the base, so the run of the base keeps its output.
|
|
92
|
+
let base: Result | undefined;
|
|
93
|
+
const members: Member<Graph, Result>[] = names.map((name) => {
|
|
94
|
+
const s = portfolioMember(name);
|
|
95
|
+
if (s === undefined) throw new Error(`The portfolio router has no member "${name}".`);
|
|
96
|
+
const router = asRouter(s);
|
|
97
|
+
if (name !== PORTFOLIO_BASE_MEMBER) return { id: name, run: (g: Graph) => router.route(g) };
|
|
98
|
+
return {
|
|
99
|
+
id: name,
|
|
100
|
+
run: (g: Graph) => {
|
|
101
|
+
base = router.route(g);
|
|
102
|
+
return base;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
const p = portfolio(portfolioName(options), members, portfolioKey(graph, rule, cap, () => base), {
|
|
107
|
+
...(options.budgetMs === undefined ? {} : { budgetMs: options.budgetMs }),
|
|
108
|
+
...(options.maxMembers === undefined ? {} : { maxMembers: options.maxMembers }),
|
|
109
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
110
|
+
});
|
|
111
|
+
const pick = p.pick(graph);
|
|
112
|
+
const moves = Object.fromEntries(Object.entries(pickMoves(pick.trace)).map(([k, v]) => [`move.${k}`, v]));
|
|
113
|
+
return { result: pick.output, winner: pick.winner, trace: pick.trace, moves, band: portfolioBand(graph.nodes.length).id, members: names };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** This function gives a router that routes each graph with `routePortfolio` and the options `options`. Its name is `portfolioName(options)`. */
|
|
117
|
+
export function portfolioRouter(options: PortfolioRouterOptions = {}): Router {
|
|
118
|
+
checked(options);
|
|
119
|
+
return { name: portfolioName(options), maxNodes: 10_000, route: (graph) => routePortfolio(graph, options).result };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The portfolio router as a router strategy. It is not in the registry `STRATEGIES`, so the benchmark and the site do not race it by default.
|
|
124
|
+
* A caller adds it to a registry of its own, or calls `routePortfolio` for the trace.
|
|
125
|
+
*/
|
|
126
|
+
export const portfolioStrategy: Strategy<"router", PortfolioRouterOptions> = defineStrategy<"router", PortfolioRouterOptions>({
|
|
127
|
+
step: "router",
|
|
128
|
+
name: "portfolio",
|
|
129
|
+
describe: "The portfolio router (PORTFOLIO-API). It runs `shared` and the fit of the size band, and gives the output with the most ink saved inside the rule and the cap.",
|
|
130
|
+
defaults: { rule: "collection", cap: 1.5 },
|
|
131
|
+
maxNodes: 10_000,
|
|
132
|
+
run: (graph, options) => routePortfolio(graph, options).result,
|
|
133
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// This file is generated by `scripts/portfolio-sets.ts` (`pnpm portfolio:sets`). Do not edit it. The test `sets.test.ts` checks that it is current.
|
|
2
|
+
// The records come from the CI runs 40631516113, 40644555345, 40644557536, 40644560237, 40644562652, 40644565132, 40644567956, 40644570455, 40644572910, 40644575649, 40644578541, 40644580965, 40644583603, 40665016754.
|
|
3
|
+
import type { PortfolioCap, PortfolioRule } from "./bands.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The greedy set of each rule, cap and size band, by the key `<rule>|<cap>|<band>`. The set of k strategies is the first k names.
|
|
7
|
+
* Each set comes from the train seeds (0 to 4) of its band. The base `shared` is not in a set: the portfolio always runs it first.
|
|
8
|
+
*/
|
|
9
|
+
export const PORTFOLIO_SETS: Readonly<Record<`${PortfolioRule}|${PortfolioCap}|${string}`, readonly string[]>> = {
|
|
10
|
+
"collection|1.1|n1-12": ["corners-side", "w2c:select+stretch", "trunks-gilbert-a05", "w3a:spiral-a15", "trunks-tree-bf08", "hw:zipper", "w2c:level-links+stretch-b1.1", "hw:stubs"],
|
|
11
|
+
"collection|1.1|n13-24": ["w3e:crossbars+stretch", "trunks-tree-bf08", "corners-side", "shared-p2", "w2c:zipper+stretch-b1.1", "hw:weiszfeld", "trunks-gilbert-a07", "hw:stubs"],
|
|
12
|
+
"collection|1.1|n25-40": ["w2a:shared-ways+breakeven", "w2b:weiszfeld-a1", "w2c:level-links+stretch-b1.1"],
|
|
13
|
+
"collection|1.1|n41-64": [],
|
|
14
|
+
"collection|1.1|n65-128": [],
|
|
15
|
+
"collection|1.25|n1-12": ["corners-side", "trunks-tree-bf06", "w3f:p-hub-a0.2", "hw:stubs", "hw:p-hub", "hw:spiral", "w2c:level-links+stretch", "w3f:spectral-c0.3"],
|
|
16
|
+
"collection|1.25|n13-24": ["w2a:shared-ways+stretch", "shared-p3", "trunks-gilbert-a07", "w3e:crossbars+stretch", "trunks-tree-bf06", "w3c:hdbscan+stretch", "shared-wind-p2-r1-e2", "hw:stubs"],
|
|
17
|
+
"collection|1.25|n25-40": ["w2a:shared-ways+stretch", "shared-p3", "shared-order-shortest", "w2b:weiszfeld-a1", "w3e:crossbars+stretch", "trunks-tree-bf08", "w2a:shared-ways+ambiguity", "w2b:weiszfeld-a0"],
|
|
18
|
+
"collection|1.25|n41-64": ["hw:lanes", "w3e:crossbars+stretch", "w2a:shared-ways+stretch", "shared-flow-a06-p3"],
|
|
19
|
+
"collection|1.25|n65-128": ["hw:lanes"],
|
|
20
|
+
"collection|1.5|n1-12": ["trunks-gilbert-a03", "w3e:areas-keys", "trunks-tree-bf02", "w3f:p-hub-a0.2", "w2b:zipper-a1", "w3a:spiral-a35", "w3f:spectral-c0.3", "w2c:level-links+stretch"],
|
|
21
|
+
"collection|1.5|n13-24": ["shared-wind-p2-r0.5", "trunks-gilbert-a03", "w3e:crossbars+stretch", "shared-wind-p2-r1-e2", "trunks-tree-bf06", "shared-flow-a06-p3", "shared-wind-p3-r1", "trunks-gilbert-a07"],
|
|
22
|
+
"collection|1.5|n25-40": ["shared-p3", "hw:lanes", "shared-wind-p2-r1-e2", "hw:crossbars", "shared-flow-a06-p3", "trunks-tree-bf06", "shared-wind-p2-r1", "shared-wind-p2-r2"],
|
|
23
|
+
"collection|1.5|n41-64": ["shared-flow-a06-p3", "hw:lanes", "shared-p2", "trunks-tree-bf06"],
|
|
24
|
+
"collection|1.5|n65-128": ["shared-p2", "shared-p3", "shared-flow-a06-p3", "hw:lanes"],
|
|
25
|
+
"all|1.1|n1-12": ["w2b:zipper-p2", "hw:cubu", "w3d:kdeeb+stretch", "w3c:hough-wide", "w3d:kdeeb-routes", "hw:stubs", "hw:spiral", "w3b:growth-s0.7"],
|
|
26
|
+
"all|1.1|n13-24": ["w3e:frechet-e0.5", "w3a:diffusion-wide", "w2c:zipper+stretch-b1.1", "w3d:cubu+stretch", "w3a:spiral-a15", "w3d:kdeeb-routes", "hw:diffusion", "hw:mesh"],
|
|
27
|
+
"all|1.1|n25-40": ["w2c:zipper+stretch-b1.1", "w3a:spiral-a15", "w3d:mesh+stretch", "w3a:diffusion+stretch", "w3a:diffusion-wide", "w2c:road-classes-d0.3", "w3d:kdeeb-routes"],
|
|
28
|
+
"all|1.1|n41-64": ["hw:diffusion", "w3a:spiral-a15", "w2c:zipper+stretch-b1.1", "w3e:frechet-e0.5"],
|
|
29
|
+
"all|1.1|n65-128": ["hw:diffusion"],
|
|
30
|
+
"all|1.25|n1-12": ["w3d:kdeeb-routes", "w3e:mlseb+stretch", "hw:growth", "hw:level-links", "w3b:growth-input", "w3e:confluent+stretch", "w3d:kdeeb-nomask", "w3a:diffusion-g2"],
|
|
31
|
+
"all|1.25|n13-24": ["w3d:kdeeb-routes", "w3f:p-hub-a0.2", "w3d:kdeeb-nomask", "w3a:spiral-n3", "w3b:growth-input", "w3d:cubu+stretch", "w3a:spiral-a35", "trunks-tree-bf06"],
|
|
32
|
+
"all|1.25|n25-40": ["w3d:kdeeb-routes", "hw:spiral", "hw:shared-ways-all", "w3a:diffusion-g2", "w3e:frechet+stretch", "hw:cubu"],
|
|
33
|
+
"all|1.25|n41-64": ["w3e:frechet+stretch", "hw:cubu", "w3a:diffusion-wide", "w2a:shared-ways+stretch", "w3a:spiral-a35"],
|
|
34
|
+
"all|1.25|n65-128": ["hw:diffusion", "w3a:diffusion-wide", "w3a:diffusion-l3"],
|
|
35
|
+
"all|1.5|n1-12": ["w3b:growth-s0.3", "w3e:mlseb-a0", "w3e:mlseb-routes", "w3c:edge-paths-k1.5", "w3f:territories-r2", "hw:mlseb", "hw:growth", "w3e:mlseb-parabola"],
|
|
36
|
+
"all|1.5|n13-24": ["hw:growth", "w3b:growth-input", "hw:mlseb", "w3b:growth-s0.3"],
|
|
37
|
+
"all|1.5|n25-40": ["hw:growth", "w3b:growth-s0.7", "w3b:growth-s0.3", "w3b:growth-t1.2", "w3b:growth-input"],
|
|
38
|
+
"all|1.5|n41-64": ["hw:growth", "w3b:growth-s0.3", "w3a:diffusion-g2", "w3b:growth-s0.7", "w3a:diffusion-l3", "w3b:growth-input"],
|
|
39
|
+
"all|1.5|n65-128": ["hw:growth", "w3b:growth-s0.3", "hw:shared-ways-all", "w3a:diffusion-g2"],
|
|
40
|
+
};
|
|
@@ -24,6 +24,26 @@ import { EDGE_PATH_DEFAULTS, edgePathPlan, type EdgePathOptions } from "../highw
|
|
|
24
24
|
import { HDBSCAN_DEFAULTS, hdbscanPlan, type HdbscanOptions } from "../highways/hdbscan/index.ts";
|
|
25
25
|
import { HOUGH_DEFAULTS, houghPlan, type HoughOptions } from "../highways/hough/index.ts";
|
|
26
26
|
import { HUB_ARC_DEFAULTS, hubArcPlan, type HubArcOptions } from "../highways/hub-arcs/index.ts";
|
|
27
|
+
import { DIVIDED_DEFAULTS, dividedPlan, type DividedOptions } from "../highways/divided/index.ts";
|
|
28
|
+
import { HIERARCHY_DEFAULTS, hierarchyPlan, type HierarchyOptions } from "../highways/hierarchy/index.ts";
|
|
29
|
+
import { FDEB_DEFAULTS, fdebPlan, type FdebOptions } from "../highways/fdeb/index.ts";
|
|
30
|
+
import { MESH_DEFAULTS, meshPlan, type MeshOptions } from "../highways/mesh/index.ts";
|
|
31
|
+
import { KDEEB_DEFAULTS, kdeebPlan, type KdeebOptions } from "../highways/kdeeb/index.ts";
|
|
32
|
+
import { CUBU_DEFAULTS, cubuPlan, type CubuOptions } from "../highways/cubu/index.ts";
|
|
33
|
+
import { MLSEB_DEFAULTS, mlsebPlan, type MlsebOptions } from "../highways/mlseb/index.ts";
|
|
34
|
+
import { SBEB_DEFAULTS, sbebPlan, type SbebOptions } from "../highways/sbeb/index.ts";
|
|
35
|
+
import { AREA_DEFAULTS, areaPlan, type AreaOptions } from "../highways/areas/index.ts";
|
|
36
|
+
import { CONFLUENT_DEFAULTS, confluentPlan, type ConfluentOptions } from "../highways/confluent/index.ts";
|
|
37
|
+
import { POWER_DEFAULTS, powerPlan, type PowerOptions } from "../highways/power/index.ts";
|
|
38
|
+
import { CROSSBAR_DEFAULTS, crossbarPlan, type CrossbarOptions } from "../highways/crossbars/index.ts";
|
|
39
|
+
import { FRECHET_DEFAULTS, frechetPlan, type FrechetOptions } from "../highways/frechet/index.ts";
|
|
40
|
+
import { SPECTRAL_DEFAULTS, spectralPlan, type SpectralOptions } from "../highways/spectral/index.ts";
|
|
41
|
+
import { RESISTANCE_DEFAULTS, resistancePlan, type ResistanceOptions } from "../highways/resistance/index.ts";
|
|
42
|
+
import { LINK_COMMUNITY_DEFAULTS, linkCommunityPlan, type LinkCommunityOptions } from "../highways/link-communities/index.ts";
|
|
43
|
+
import { TERRITORY_DEFAULTS, territoryPlan, type TerritoryOptions } from "../highways/territories/index.ts";
|
|
44
|
+
import { CONSENSUS_DEFAULTS, consensusPlan, type ConsensusOptions } from "../highways/consensus/index.ts";
|
|
45
|
+
import { P_HUB_DEFAULTS, pHubPlan, type PHubOptions } from "../highways/p-hub/index.ts";
|
|
46
|
+
import { SCALE_SPACE_DEFAULTS, scaleSpacePlan, type ScaleSpaceOptions } from "../highways/scale-space/index.ts";
|
|
27
47
|
import { RIDGE_DEFAULTS, ridgePlan, type RidgeOptions } from "../highways/ridges/index.ts";
|
|
28
48
|
import { SOFT_KEY_DEFAULTS, softKeyPlan, type SoftKeyOptions } from "../highways/soft-keys/index.ts";
|
|
29
49
|
import { FLOW_FIELD_DEFAULTS, flowFieldPlan, type FlowFieldOptions } from "../highways/flow-field/index.ts";
|
|
@@ -58,7 +78,8 @@ import { byCollection, trunksRouter, type TrunksOptions } from "../routers/trunk
|
|
|
58
78
|
import { buildSections, SECTION_DEFAULTS, type SectionOptions } from "../sections.ts";
|
|
59
79
|
import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
|
|
60
80
|
import type { OptionSchema } from "./options.ts";
|
|
61
|
-
import {
|
|
81
|
+
import { AREA_SCHEMA, CONFLUENT_SCHEMA, CROSSBAR_SCHEMA, FRECHET_SCHEMA, MLSEB_SCHEMA, POWER_SCHEMA, SBEB_SCHEMA } from "./schemas.ts";
|
|
82
|
+
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_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 } from "./schemas.ts";
|
|
62
83
|
|
|
63
84
|
/** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
|
|
64
85
|
export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
|
|
@@ -417,6 +438,26 @@ export const highwayStrategies: {
|
|
|
417
438
|
readonly ridges: Strategy<"highway", RidgeOptions>;
|
|
418
439
|
readonly hdbscan: Strategy<"highway", HdbscanOptions>;
|
|
419
440
|
readonly "hub-arcs": Strategy<"highway", HubArcOptions>;
|
|
441
|
+
readonly divided: Strategy<"highway", DividedOptions>;
|
|
442
|
+
readonly hierarchy: Strategy<"highway", HierarchyOptions>;
|
|
443
|
+
readonly fdeb: Strategy<"highway", FdebOptions>;
|
|
444
|
+
readonly mesh: Strategy<"highway", MeshOptions>;
|
|
445
|
+
readonly kdeeb: Strategy<"highway", KdeebOptions>;
|
|
446
|
+
readonly cubu: Strategy<"highway", CubuOptions>;
|
|
447
|
+
readonly mlseb: Strategy<"highway", MlsebOptions>;
|
|
448
|
+
readonly sbeb: Strategy<"highway", SbebOptions>;
|
|
449
|
+
readonly areas: Strategy<"highway", AreaOptions>;
|
|
450
|
+
readonly confluent: Strategy<"highway", ConfluentOptions>;
|
|
451
|
+
readonly power: Strategy<"highway", PowerOptions>;
|
|
452
|
+
readonly crossbars: Strategy<"highway", CrossbarOptions>;
|
|
453
|
+
readonly frechet: Strategy<"highway", FrechetOptions>;
|
|
454
|
+
readonly spectral: Strategy<"highway", SpectralOptions>;
|
|
455
|
+
readonly resistance: Strategy<"highway", ResistanceOptions>;
|
|
456
|
+
readonly "link-communities": Strategy<"highway", LinkCommunityOptions>;
|
|
457
|
+
readonly territories: Strategy<"highway", TerritoryOptions>;
|
|
458
|
+
readonly consensus: Strategy<"highway", ConsensusOptions>;
|
|
459
|
+
readonly "p-hub": Strategy<"highway", PHubOptions>;
|
|
460
|
+
readonly "scale-space": Strategy<"highway", ScaleSpaceOptions>;
|
|
420
461
|
readonly "corridors+breakeven": Strategy<"highway", CorridorOptions & BreakEvenOptions>;
|
|
421
462
|
readonly "corridors+stretch": Strategy<"highway", CorridorOptions & StretchOptions>;
|
|
422
463
|
readonly "corridors+ambiguity": Strategy<"highway", CorridorOptions & AmbiguityGuardOptions>;
|
|
@@ -667,6 +708,186 @@ export const highwayStrategies: {
|
|
|
667
708
|
maxNodes: 10_000,
|
|
668
709
|
run: (input: HighwayInput, o: Readonly<HubArcOptions>) => hubArcPlan(input, o),
|
|
669
710
|
}),
|
|
711
|
+
divided: defineStrategy({
|
|
712
|
+
step: "highway",
|
|
713
|
+
name: "divided",
|
|
714
|
+
describe: "Divided edge bundling (HWY-011). Each highway of two-way traffic splits into two carriageways at an offset, one for each direction.",
|
|
715
|
+
defaults: { ...DIVIDED_DEFAULTS } as DividedOptions,
|
|
716
|
+
schema: DIVIDED_SCHEMA,
|
|
717
|
+
maxNodes: 10_000,
|
|
718
|
+
run: (input: HighwayInput, o: Readonly<DividedOptions>) => dividedPlan(input, o),
|
|
719
|
+
}),
|
|
720
|
+
hierarchy: defineStrategy({
|
|
721
|
+
step: "highway",
|
|
722
|
+
name: "hierarchy",
|
|
723
|
+
describe: "Hierarchical edge bundling (HWY-001). Each edge goes near the hubs of the sections between its two leaves, and the shared stretches become highways.",
|
|
724
|
+
defaults: { ...HIERARCHY_DEFAULTS } as HierarchyOptions,
|
|
725
|
+
schema: HIERARCHY_SCHEMA,
|
|
726
|
+
maxNodes: 10_000,
|
|
727
|
+
run: (input: HighwayInput, o: Readonly<HierarchyOptions>) => hierarchyPlan(input, o),
|
|
728
|
+
}),
|
|
729
|
+
fdeb: defineStrategy({
|
|
730
|
+
step: "highway",
|
|
731
|
+
name: "fdeb",
|
|
732
|
+
describe: "Force-directed edge bundling with a field of the boxes (HWY-002 with RULE-002). The bundled points snap back to legal ways.",
|
|
733
|
+
defaults: { ...FDEB_DEFAULTS } as FdebOptions,
|
|
734
|
+
schema: FDEB_SCHEMA,
|
|
735
|
+
maxNodes: 10_000,
|
|
736
|
+
run: (input: HighwayInput, o: Readonly<FdebOptions>) => fdebPlan(input, o),
|
|
737
|
+
}),
|
|
738
|
+
mesh: defineStrategy({
|
|
739
|
+
step: "highway",
|
|
740
|
+
name: "mesh",
|
|
741
|
+
describe: "A control mesh (HWY-004). The crossings of the edges with the mesh form clusters, and the edges of a cluster share its point.",
|
|
742
|
+
defaults: { ...MESH_DEFAULTS } as MeshOptions,
|
|
743
|
+
schema: MESH_SCHEMA,
|
|
744
|
+
maxNodes: 10_000,
|
|
745
|
+
run: (input: HighwayInput, o: Readonly<MeshOptions>) => meshPlan(input, o),
|
|
746
|
+
}),
|
|
747
|
+
kdeeb: defineStrategy({
|
|
748
|
+
step: "highway",
|
|
749
|
+
name: "kdeeb",
|
|
750
|
+
describe: "Density bundling with a mask of the free space (RULE-028 with HWY-006). The edges move up the density, and the ridges become highways.",
|
|
751
|
+
defaults: { ...KDEEB_DEFAULTS } as KdeebOptions,
|
|
752
|
+
schema: KDEEB_SCHEMA,
|
|
753
|
+
maxNodes: 10_000,
|
|
754
|
+
run: (input: HighwayInput, o: Readonly<KdeebOptions>) => kdeebPlan(input, o),
|
|
755
|
+
}),
|
|
756
|
+
cubu: defineStrategy({
|
|
757
|
+
step: "highway",
|
|
758
|
+
name: "cubu",
|
|
759
|
+
describe: "Fast density bundling (HWY-007). The steps of `kdeeb` with a separable blur or a Fourier transform of the counts of the cells.",
|
|
760
|
+
defaults: { ...CUBU_DEFAULTS } as CubuOptions,
|
|
761
|
+
schema: CUBU_SCHEMA,
|
|
762
|
+
maxNodes: 10_000,
|
|
763
|
+
run: (input: HighwayInput, o: Readonly<CubuOptions>) => cubuPlan(input, o),
|
|
764
|
+
}),
|
|
765
|
+
mlseb: defineStrategy({
|
|
766
|
+
step: "highway",
|
|
767
|
+
name: "mlseb",
|
|
768
|
+
describe: "Moving least squares bundling (HWY-008). Each point of an edge moves to a local fit of the near edges, and the bundled points snap back to legal ways.",
|
|
769
|
+
defaults: { ...MLSEB_DEFAULTS } as MlsebOptions,
|
|
770
|
+
schema: MLSEB_SCHEMA,
|
|
771
|
+
maxNodes: 10_000,
|
|
772
|
+
run: (input: HighwayInput, o: Readonly<MlsebOptions>) => mlsebPlan(input, o),
|
|
773
|
+
}),
|
|
774
|
+
sbeb: defineStrategy({
|
|
775
|
+
step: "highway",
|
|
776
|
+
name: "sbeb",
|
|
777
|
+
describe: "Skeleton-based edge bundling (HWY-009). The edges of a group move toward the skeleton of their shape, and the bundled points snap back to legal ways.",
|
|
778
|
+
defaults: { ...SBEB_DEFAULTS } as SbebOptions,
|
|
779
|
+
schema: SBEB_SCHEMA,
|
|
780
|
+
maxNodes: 10_000,
|
|
781
|
+
run: (input: HighwayInput, o: Readonly<SbebOptions>) => sbebPlan(input, o),
|
|
782
|
+
}),
|
|
783
|
+
areas: defineStrategy({
|
|
784
|
+
step: "highway",
|
|
785
|
+
name: "areas",
|
|
786
|
+
describe: "Image-based edge bundles (HWY-010). Each group of edges is one shaded area around the longest path of the skeleton of its shape.",
|
|
787
|
+
defaults: { ...AREA_DEFAULTS } as AreaOptions,
|
|
788
|
+
schema: AREA_SCHEMA,
|
|
789
|
+
maxNodes: 10_000,
|
|
790
|
+
run: (input: HighwayInput, o: Readonly<AreaOptions>) => areaPlan(input, o),
|
|
791
|
+
}),
|
|
792
|
+
confluent: defineStrategy({
|
|
793
|
+
step: "highway",
|
|
794
|
+
name: "confluent",
|
|
795
|
+
describe: "Confluent drawings (HWY-013). Each biclique of a greedy search is one track, with a merge junction and a split junction.",
|
|
796
|
+
defaults: { ...CONFLUENT_DEFAULTS } as ConfluentOptions,
|
|
797
|
+
schema: CONFLUENT_SCHEMA,
|
|
798
|
+
maxNodes: 10_000,
|
|
799
|
+
run: (input: HighwayInput, o: Readonly<ConfluentOptions>) => confluentPlan(input, o),
|
|
800
|
+
}),
|
|
801
|
+
power: defineStrategy({
|
|
802
|
+
step: "highway",
|
|
803
|
+
name: "power",
|
|
804
|
+
describe: "Edge compression with modules and power graphs (HWY-014). The edges between two modules are one power edge, and a large power edge is a highway.",
|
|
805
|
+
defaults: { ...POWER_DEFAULTS } as PowerOptions,
|
|
806
|
+
schema: POWER_SCHEMA,
|
|
807
|
+
maxNodes: 10_000,
|
|
808
|
+
run: (input: HighwayInput, o: Readonly<PowerOptions>) => powerPlan(input, o),
|
|
809
|
+
}),
|
|
810
|
+
crossbars: defineStrategy({
|
|
811
|
+
step: "highway",
|
|
812
|
+
name: "crossbars",
|
|
813
|
+
describe: "Bundled crossings (HWY-022). Two sets of edges that cross near one place cross as two bundles, at one place.",
|
|
814
|
+
defaults: { ...CROSSBAR_DEFAULTS } as CrossbarOptions,
|
|
815
|
+
schema: CROSSBAR_SCHEMA,
|
|
816
|
+
maxNodes: 10_000,
|
|
817
|
+
run: (input: HighwayInput, o: Readonly<CrossbarOptions>) => crossbarPlan(input, o),
|
|
818
|
+
}),
|
|
819
|
+
frechet: defineStrategy({
|
|
820
|
+
step: "highway",
|
|
821
|
+
name: "frechet",
|
|
822
|
+
describe: "Subtrajectory clusters by the discrete Fréchet distance (HWY-027). The parts of the first routes that stay near one reference part become highways.",
|
|
823
|
+
defaults: { ...FRECHET_DEFAULTS } as FrechetOptions,
|
|
824
|
+
schema: FRECHET_SCHEMA,
|
|
825
|
+
maxNodes: 10_000,
|
|
826
|
+
run: (input: HighwayInput, o: Readonly<FrechetOptions>) => frechetPlan(input, o),
|
|
827
|
+
}),
|
|
828
|
+
spectral: defineStrategy({
|
|
829
|
+
step: "highway",
|
|
830
|
+
name: "spectral",
|
|
831
|
+
describe: "Spectral clustering of the compatibility graph of the edges (HWY-029). The largest eigengap gives the number of clusters, and each cluster gets highways.",
|
|
832
|
+
defaults: { ...SPECTRAL_DEFAULTS } as SpectralOptions,
|
|
833
|
+
schema: SPECTRAL_SCHEMA,
|
|
834
|
+
maxNodes: 10_000,
|
|
835
|
+
run: (input: HighwayInput, o: Readonly<SpectralOptions>) => spectralPlan(input, o),
|
|
836
|
+
}),
|
|
837
|
+
resistance: defineStrategy({
|
|
838
|
+
step: "highway",
|
|
839
|
+
name: "resistance",
|
|
840
|
+
describe: "Spectral bundling with the effective resistance (HWY-021). A pair of two bridges of the graph gets a lower weight, then the clusters of `spectral` apply.",
|
|
841
|
+
defaults: { ...RESISTANCE_DEFAULTS } as ResistanceOptions,
|
|
842
|
+
schema: RESISTANCE_SCHEMA,
|
|
843
|
+
maxNodes: 10_000,
|
|
844
|
+
run: (input: HighwayInput, o: Readonly<ResistanceOptions>) => resistancePlan(input, o),
|
|
845
|
+
}),
|
|
846
|
+
"link-communities": defineStrategy({
|
|
847
|
+
step: "highway",
|
|
848
|
+
name: "link-communities",
|
|
849
|
+
describe: "Link communities on the line graph (HWY-030). The edges with a common node and similar neighbours form communities, and each community gets highways.",
|
|
850
|
+
defaults: { ...LINK_COMMUNITY_DEFAULTS } as LinkCommunityOptions,
|
|
851
|
+
schema: LINK_COMMUNITY_SCHEMA,
|
|
852
|
+
maxNodes: 10_000,
|
|
853
|
+
run: (input: HighwayInput, o: Readonly<LinkCommunityOptions>) => linkCommunityPlan(input, o),
|
|
854
|
+
}),
|
|
855
|
+
territories: defineStrategy({
|
|
856
|
+
step: "highway",
|
|
857
|
+
name: "territories",
|
|
858
|
+
describe: "Territory aggregation of flows (HWY-033). The plugs and bends form cells, and the strong flows between two neighbour cells become highways.",
|
|
859
|
+
defaults: { ...TERRITORY_DEFAULTS } as TerritoryOptions,
|
|
860
|
+
schema: TERRITORY_SCHEMA,
|
|
861
|
+
maxNodes: 10_000,
|
|
862
|
+
run: (input: HighwayInput, o: Readonly<TerritoryOptions>) => territoryPlan(input, o),
|
|
863
|
+
}),
|
|
864
|
+
consensus: defineStrategy({
|
|
865
|
+
step: "highway",
|
|
866
|
+
name: "consensus",
|
|
867
|
+
describe: "A consensus of clusterings (HWY-035). The finders vote on the pairs of edges, and the pairs that most finders agree on form the highways.",
|
|
868
|
+
defaults: { ...CONSENSUS_DEFAULTS } as ConsensusOptions,
|
|
869
|
+
schema: CONSENSUS_SCHEMA,
|
|
870
|
+
maxNodes: 10_000,
|
|
871
|
+
run: (input: HighwayInput, o: Readonly<ConsensusOptions>) => consensusPlan(input, o),
|
|
872
|
+
}),
|
|
873
|
+
"p-hub": defineStrategy({
|
|
874
|
+
step: "highway",
|
|
875
|
+
name: "p-hub",
|
|
876
|
+
describe: "Hub location with the p-hub median (HWY-041). The p-median of the plugs gives the hubs, and the links between two hubs become highways.",
|
|
877
|
+
defaults: { ...P_HUB_DEFAULTS } as PHubOptions,
|
|
878
|
+
schema: P_HUB_SCHEMA,
|
|
879
|
+
maxNodes: 10_000,
|
|
880
|
+
run: (input: HighwayInput, o: Readonly<PHubOptions>) => pHubPlan(input, o),
|
|
881
|
+
}),
|
|
882
|
+
"scale-space": defineStrategy({
|
|
883
|
+
step: "highway",
|
|
884
|
+
name: "scale-space",
|
|
885
|
+
describe: "A scale space with persistence (HWY-053). The clusters of edges that live over a long range of scales get highways at the scale of the plan.",
|
|
886
|
+
defaults: { ...SCALE_SPACE_DEFAULTS } as ScaleSpaceOptions,
|
|
887
|
+
schema: SCALE_SPACE_SCHEMA,
|
|
888
|
+
maxNodes: 10_000,
|
|
889
|
+
run: (input: HighwayInput, o: Readonly<ScaleSpaceOptions>) => scaleSpacePlan(input, o),
|
|
890
|
+
}),
|
|
670
891
|
"corridors+breakeven": gateStrategy(corridors, breakEvenGate),
|
|
671
892
|
"corridors+stretch": gateStrategy(corridors, stretchGate),
|
|
672
893
|
"corridors+ambiguity": gateStrategy(corridors, ambiguityGate),
|