@mark1russell7/highways 0.5.1 → 0.7.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 +8 -2
- package/build/index.d.ts +30 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +263 -62
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bench/exact/index.ts +159 -13
- package/src/bench/screen/portfolio-pick.ts +2 -1
- package/src/index.ts +2 -0
- package/src/portfolio/bands.ts +6 -1
- package/src/portfolio/fit.ts +27 -15
- package/src/portfolio/router.ts +19 -3
- package/src/portfolio/sets.ts +16 -4
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"directory": "packages/core"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://mark1russell7.github.io/highways/",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.7.0",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "./build/index.js",
|
|
15
15
|
"types": "./build/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@mark1russell7/layout-contract": "^0.2.
|
|
32
|
+
"@mark1russell7/layout-contract": "^0.2.8"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@mark1russell7/cue": "github:mark1russell7/cue",
|
package/src/bench/exact/index.ts
CHANGED
|
@@ -219,33 +219,179 @@ export function exactModel(graph: Graph, o: Readonly<ExactOptions>): ExactModel
|
|
|
219
219
|
return { lp, columns: binaries.length, rows: rows.length, edges: arcsOf.length, lengths, decode };
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/** A binary heap of node indexes by a key, smallest first. A node can be in the heap more than one time. The search skips an old entry. */
|
|
223
|
+
class NodeHeap {
|
|
224
|
+
#key: Float64Array = new Float64Array(64);
|
|
225
|
+
#node: Int32Array = new Int32Array(64);
|
|
226
|
+
size = 0;
|
|
227
|
+
|
|
228
|
+
push(key: number, node: number): void {
|
|
229
|
+
if (this.size === this.#key.length) {
|
|
230
|
+
const k = new Float64Array(2 * this.size);
|
|
231
|
+
k.set(this.#key);
|
|
232
|
+
this.#key = k;
|
|
233
|
+
const n = new Int32Array(2 * this.size);
|
|
234
|
+
n.set(this.#node);
|
|
235
|
+
this.#node = n;
|
|
236
|
+
}
|
|
237
|
+
const keys = this.#key;
|
|
238
|
+
const nodes = this.#node;
|
|
239
|
+
let i = this.size++;
|
|
240
|
+
while (i > 0) {
|
|
241
|
+
const up = (i - 1) >> 1;
|
|
242
|
+
if (keys[up]! <= key) break;
|
|
243
|
+
keys[i] = keys[up]!;
|
|
244
|
+
nodes[i] = nodes[up]!;
|
|
245
|
+
i = up;
|
|
246
|
+
}
|
|
247
|
+
keys[i] = key;
|
|
248
|
+
nodes[i] = node;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** This method removes the node with the smallest key, and gives it. */
|
|
252
|
+
pop(): number {
|
|
253
|
+
const keys = this.#key;
|
|
254
|
+
const nodes = this.#node;
|
|
255
|
+
const top = nodes[0]!;
|
|
256
|
+
const n = --this.size;
|
|
257
|
+
const key = keys[n]!;
|
|
258
|
+
const node = nodes[n]!;
|
|
259
|
+
let i = 0;
|
|
260
|
+
for (;;) {
|
|
261
|
+
let c = 2 * i + 1;
|
|
262
|
+
if (c >= n) break;
|
|
263
|
+
if (c + 1 < n && keys[c + 1]! < keys[c]!) c++;
|
|
264
|
+
if (keys[c]! >= key) break;
|
|
265
|
+
keys[i] = keys[c]!;
|
|
266
|
+
nodes[i] = nodes[c]!;
|
|
267
|
+
i = c;
|
|
268
|
+
}
|
|
269
|
+
keys[i] = key;
|
|
270
|
+
nodes[i] = node;
|
|
271
|
+
return top;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
222
275
|
/**
|
|
223
|
-
* This function gives the
|
|
224
|
-
* The
|
|
225
|
-
*
|
|
226
|
-
*
|
|
276
|
+
* This function gives the shortest distance from `start` to each node of `targets` (A*). The search stops when it has each target.
|
|
277
|
+
* The guide is the straight distance to the nearest target. Each link is a straight line, so the guide is not above the true distance at any time.
|
|
278
|
+
* Thus the distances are the distances of Dijkstra. Only the order of the search changes.
|
|
279
|
+
* A target that the search cannot reach gets `Infinity`.
|
|
227
280
|
*/
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
281
|
+
function distancesTo(points: readonly Point[], adj: readonly (readonly { to: number; len: number }[])[], start: number, targets: readonly number[]): Map<number, number> {
|
|
282
|
+
const d = new Float64Array(adj.length).fill(Infinity);
|
|
283
|
+
const done = new Uint8Array(adj.length);
|
|
284
|
+
const goal = new Set(targets);
|
|
285
|
+
const ends = targets.map((t) => points[t]!);
|
|
286
|
+
const guide = (v: number): number => {
|
|
287
|
+
let h = Infinity;
|
|
288
|
+
for (const t of ends) h = Math.min(h, dist(points[v]!, t));
|
|
289
|
+
return h;
|
|
234
290
|
};
|
|
291
|
+
const heap = new NodeHeap();
|
|
292
|
+
d[start] = 0;
|
|
293
|
+
heap.push(guide(start), start);
|
|
294
|
+
let left = goal.size;
|
|
295
|
+
while (heap.size > 0 && left > 0) {
|
|
296
|
+
const u = heap.pop();
|
|
297
|
+
if (done[u]) continue;
|
|
298
|
+
done[u] = 1;
|
|
299
|
+
if (goal.has(u)) left--;
|
|
300
|
+
for (const { to, len } of adj[u]!) {
|
|
301
|
+
const nd = d[u]! + len;
|
|
302
|
+
if (nd < d[to]!) {
|
|
303
|
+
d[to] = nd;
|
|
304
|
+
// A rounding error can make the guide a little too large. Then a node opens again, so its distance stays the least.
|
|
305
|
+
done[to] = 0;
|
|
306
|
+
heap.push(nd + guide(to), to);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return new Map(targets.map((t) => [t, d[t]!]));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* This function gives the shortest distance in the network between the two end points of each edge, by the pair `a,b` of the end points.
|
|
315
|
+
* The distance does not depend on the direction, so one search serves each edge between the same two points.
|
|
316
|
+
* An end point of many edges starts one search to all its other end points. The function starts the searches from the end points with the most edges first.
|
|
317
|
+
*/
|
|
318
|
+
function pairDistances(network: ExactNetwork): Map<string, number> {
|
|
319
|
+
const pairKey = (a: number, b: number): string => (a < b ? `${a},${b}` : `${b},${a}`);
|
|
320
|
+
const partners = new Map<number, Set<number>>();
|
|
321
|
+
const open = new Set<string>();
|
|
322
|
+
for (const e of network.ends) {
|
|
323
|
+
if (e.a === e.b) continue;
|
|
324
|
+
const k = pairKey(e.a, e.b);
|
|
325
|
+
if (open.has(k)) continue;
|
|
326
|
+
open.add(k);
|
|
327
|
+
for (const [u, v] of [
|
|
328
|
+
[e.a, e.b],
|
|
329
|
+
[e.b, e.a],
|
|
330
|
+
] as const) {
|
|
331
|
+
if (!partners.has(u)) partners.set(u, new Set());
|
|
332
|
+
partners.get(u)!.add(v);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const out = new Map<string, number>();
|
|
336
|
+
const order = [...partners.keys()].sort((u, v) => partners.get(v)!.size - partners.get(u)!.size || u - v);
|
|
337
|
+
for (const u of order) {
|
|
338
|
+
const targets = [...partners.get(u)!].filter((v) => open.has(pairKey(u, v)));
|
|
339
|
+
if (targets.length === 0) continue;
|
|
340
|
+
for (const [v, len] of distancesTo(network.points, network.adj, u, targets)) {
|
|
341
|
+
const k = pairKey(u, v);
|
|
342
|
+
out.set(k, len);
|
|
343
|
+
open.delete(k);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** This function gives the function of the stretch of each route of the graph, from the network and the shortest distance of each edge. */
|
|
350
|
+
function stretchesOf(network: ExactNetwork, best: (e: ExactNetwork["ends"][number]) => number): (routes: readonly Route[]) => Map<string, number> {
|
|
235
351
|
return (routes) => {
|
|
236
352
|
const byEdge = new Map(routes.map((r) => [r.edge, r]));
|
|
237
353
|
const out = new Map<string, number>();
|
|
238
|
-
for (const e of ends) {
|
|
354
|
+
for (const e of network.ends) {
|
|
239
355
|
const r = byEdge.get(e.edge.id);
|
|
240
356
|
if (!r || r.points.length < 2 || e.a === e.b) continue;
|
|
241
|
-
const b =
|
|
357
|
+
const b = best(e);
|
|
242
358
|
if (!Number.isFinite(b) || b <= 1) continue;
|
|
243
|
-
out.set(e.edge.id, (polylineLength(r.points) - 2 * margin) / b);
|
|
359
|
+
out.set(e.edge.id, (polylineLength(r.points) - 2 * network.margin) / b);
|
|
244
360
|
}
|
|
245
361
|
return out;
|
|
246
362
|
};
|
|
247
363
|
}
|
|
248
364
|
|
|
365
|
+
/**
|
|
366
|
+
* This function gives the stretch of each route as the exact model counts it, by the edge id.
|
|
367
|
+
* The stretch of a route is its length without its two plug ends, divided by the shortest way in the network of the model.
|
|
368
|
+
* The function builds the network one time. The result function then measures each list of routes of the graph, so a portfolio pays for the network once.
|
|
369
|
+
* The first call finds the shortest ways with one A* search for each end point with edges (PORTFOLIO-SPEED). The later calls use them again.
|
|
370
|
+
* An edge without a route or without a way in the network has no value.
|
|
371
|
+
*/
|
|
372
|
+
export function exactStretches(graph: Graph, o: Readonly<WayOptions> = {}): (routes: readonly Route[]) => Map<string, number> {
|
|
373
|
+
const network = exactNetwork(graph, o);
|
|
374
|
+
let best: Map<string, number> | undefined;
|
|
375
|
+
return stretchesOf(network, (e) => {
|
|
376
|
+
best ??= pairDistances(network);
|
|
377
|
+
return best.get(e.a < e.b ? `${e.a},${e.b}` : `${e.b},${e.a}`) ?? Infinity;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* This function gives the same values as `exactStretches` with the search of the first version. That search is one full Dijkstra with a scan, from the first end point of each edge.
|
|
383
|
+
* It is slow on large boards. The tests and the check `portfolio:time --check-stretch` compare the two functions.
|
|
384
|
+
*/
|
|
385
|
+
export function exactStretchesReference(graph: Graph, o: Readonly<WayOptions> = {}): (routes: readonly Route[]) => Map<string, number> {
|
|
386
|
+
const network = exactNetwork(graph, o);
|
|
387
|
+
const best = new Map<number, Float64Array>();
|
|
388
|
+
const from = (a: number): Float64Array => {
|
|
389
|
+
if (!best.has(a)) best.set(a, distances(network.adj, a));
|
|
390
|
+
return best.get(a)!;
|
|
391
|
+
};
|
|
392
|
+
return stretchesOf(network, (e) => from(e.a)[e.b]!);
|
|
393
|
+
}
|
|
394
|
+
|
|
249
395
|
/**
|
|
250
396
|
* This function gives the largest stretch of routes as the exact model counts it (`exactStretches`).
|
|
251
397
|
* The cap of the model is a limit on this value.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The portfolio combinator (PORTFOLIO-API): `portfolio`, `selectBest` and `pickMoves` come from `@mark1russell7/layout-contract` 0.2.6 (PORTFOLIO-FIT).
|
|
2
|
-
// The
|
|
2
|
+
// The option `hashes` of `PortfolioOptions` comes from the contract 0.2.8 (HASH-LAZY).
|
|
3
|
+
// The rules of the pick, the time limit, the hashes and the trace are in `docs/PORTFOLIO.md` of the contract.
|
|
3
4
|
export {
|
|
4
5
|
pickMoves,
|
|
5
6
|
portfolio,
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,8 @@ export * from "./bench/suite.ts";
|
|
|
29
29
|
export * from "./strategies/index.ts";
|
|
30
30
|
export * from "./bench/compare.ts";
|
|
31
31
|
export * from "./bench/screen/index.ts";
|
|
32
|
+
// The stretch of the exact model, for a benchmark that compares routers with the measures of the portfolio (PORTFOLIO-SPEED).
|
|
33
|
+
export { exactStretch, exactStretches } from "./bench/exact/index.ts";
|
|
32
34
|
export * from "./highways/index.ts";
|
|
33
35
|
export * from "./order/index.ts";
|
|
34
36
|
export * from "./repair/index.ts";
|
package/src/portfolio/bands.ts
CHANGED
|
@@ -20,11 +20,16 @@ export interface PortfolioBand {
|
|
|
20
20
|
readonly maxNodes: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* The size bands: up to 12 nodes, 13 to 24 nodes, 25 to 40 nodes, 41 to 64 nodes and 65 to 128 nodes. An input above 128 nodes uses the band `n65-128`.
|
|
25
|
+
* The two large bands come from PORTFOLIO-SPEED. Their boards have no optima, so their sets come from the B1 under the guard.
|
|
26
|
+
*/
|
|
24
27
|
export const PORTFOLIO_BANDS: readonly PortfolioBand[] = [
|
|
25
28
|
{ id: "n1-12", maxNodes: 12 },
|
|
26
29
|
{ id: "n13-24", maxNodes: 24 },
|
|
27
30
|
{ id: "n25-40", maxNodes: 40 },
|
|
31
|
+
{ id: "n41-64", maxNodes: 64 },
|
|
32
|
+
{ id: "n65-128", maxNodes: 128 },
|
|
28
33
|
];
|
|
29
34
|
|
|
30
35
|
/** This function gives the band of a node count: the first band whose `maxNodes` is the count or more, else the last band. */
|
package/src/portfolio/fit.ts
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
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 times come from the CI run
|
|
2
|
+
// The times come from the CI run 40681241949 (the workflow `Portfolio`, `portfolio:time --members`).
|
|
3
3
|
import type { PortfolioCap, PortfolioRule } from "./bands.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The fit of each rule, cap and size band, by the key `<rule>|<cap>|<band>`: the members of the default of the portfolio router, in their order.
|
|
7
|
-
* Each member has a node limit: it runs only on an input with that many nodes or fewer. The limits keep the model time at or below
|
|
7
|
+
* Each member has a node limit: it runs only on an input with that many nodes or fewer. The limits keep the model time at or below 45 ms.
|
|
8
8
|
* The base `shared` is not in a fit: the portfolio always runs it first.
|
|
9
9
|
*/
|
|
10
10
|
export const PORTFOLIO_FIT: Readonly<Record<`${PortfolioRule}|${PortfolioCap}|${string}`, readonly (readonly [string, number])[]>> = {
|
|
11
11
|
"collection|1.1|n1-12": [["corners-side", 12], ["w2c:select+stretch", 12], ["trunks-gilbert-a05", 12], ["w3a:spiral-a15", 12], ["trunks-tree-bf08", 12], ["hw:zipper", 12], ["w2c:level-links+stretch-b1.1", 12], ["hw:stubs", 12]],
|
|
12
|
-
"collection|1.1|n13-24": [["w3e:crossbars+stretch", 24], ["corners-side", 24], ["shared-p2", 24], ["w2c:zipper+stretch-b1.1", 18], ["
|
|
13
|
-
"collection|1.1|n25-40": [["w2a:shared-ways+breakeven",
|
|
12
|
+
"collection|1.1|n13-24": [["w3e:crossbars+stretch", 24], ["trunks-tree-bf08", 24], ["corners-side", 24], ["shared-p2", 24], ["w2c:zipper+stretch-b1.1", 18], ["hw:weiszfeld", 18], ["trunks-gilbert-a07", 18]],
|
|
13
|
+
"collection|1.1|n25-40": [["w2a:shared-ways+breakeven", 40], ["w2b:weiszfeld-a1", 40]],
|
|
14
|
+
"collection|1.1|n41-64": [],
|
|
15
|
+
"collection|1.1|n65-128": [],
|
|
14
16
|
"collection|1.25|n1-12": [["corners-side", 12], ["trunks-tree-bf06", 12], ["w3f:p-hub-a0.2", 12], ["hw:stubs", 12], ["hw:p-hub", 12], ["hw:spiral", 12], ["w2c:level-links+stretch", 12], ["w3f:spectral-c0.3", 12]],
|
|
15
|
-
"collection|1.25|n13-24": [["w2a:shared-ways+stretch", 24], ["shared-p3", 24], ["trunks-gilbert-a07", 24], ["w3e:crossbars+stretch", 24], ["trunks-tree-bf06",
|
|
16
|
-
"collection|1.25|n25-40": [["w2a:shared-ways+stretch",
|
|
17
|
+
"collection|1.25|n13-24": [["w2a:shared-ways+stretch", 24], ["shared-p3", 24], ["trunks-gilbert-a07", 24], ["w3e:crossbars+stretch", 24], ["trunks-tree-bf06", 24], ["w3c:hdbscan+stretch", 18], ["shared-wind-p2-r1-e2", 18], ["hw:stubs", 18]],
|
|
18
|
+
"collection|1.25|n25-40": [["w2a:shared-ways+stretch", 40], ["shared-p3", 40], ["shared-order-shortest", 40], ["w2b:weiszfeld-a1", 32]],
|
|
19
|
+
"collection|1.25|n41-64": [],
|
|
20
|
+
"collection|1.25|n65-128": [],
|
|
17
21
|
"collection|1.5|n1-12": [["trunks-gilbert-a03", 12], ["w3e:areas-keys", 12], ["trunks-tree-bf02", 12], ["w3f:p-hub-a0.2", 12], ["w2b:zipper-a1", 12], ["w3a:spiral-a35", 12], ["w3f:spectral-c0.3", 12], ["w2c:level-links+stretch", 12]],
|
|
18
|
-
"collection|1.5|n13-24": [["shared-wind-p2-r0.5", 24], ["trunks-gilbert-a03", 24], ["w3e:crossbars+stretch", 24], ["shared-wind-p2-r1-e2", 24], ["trunks-tree-bf06", 24], ["shared-flow-a06-p3",
|
|
19
|
-
"collection|1.5|n25-40": [["shared-p3", 40], ["hw:lanes",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"all|1.1|
|
|
23
|
-
"all|1.
|
|
24
|
-
"all|1.
|
|
22
|
+
"collection|1.5|n13-24": [["shared-wind-p2-r0.5", 24], ["trunks-gilbert-a03", 24], ["w3e:crossbars+stretch", 24], ["shared-wind-p2-r1-e2", 24], ["trunks-tree-bf06", 24], ["shared-flow-a06-p3", 24], ["shared-wind-p3-r1", 24], ["trunks-gilbert-a07", 24]],
|
|
23
|
+
"collection|1.5|n25-40": [["shared-p3", 40], ["hw:lanes", 40], ["shared-wind-p2-r1-e2", 40], ["hw:crossbars", 32]],
|
|
24
|
+
"collection|1.5|n41-64": [["shared-flow-a06-p3", 64]],
|
|
25
|
+
"collection|1.5|n65-128": [],
|
|
26
|
+
"all|1.1|n1-12": [["w2b:zipper-p2", 12], ["hw:cubu", 12], ["w3d:kdeeb+stretch", 12], ["w3c:hough-wide", 12], ["w3d:kdeeb-routes", 12], ["hw:stubs", 12], ["hw:spiral", 12], ["w3b:growth-s0.7", 10]],
|
|
27
|
+
"all|1.1|n13-24": [["w3e:frechet-e0.5", 24], ["w3a:diffusion-wide", 24], ["w2c:zipper+stretch-b1.1", 24], ["w3d:cubu+stretch", 18], ["w3a:spiral-a15", 18]],
|
|
28
|
+
"all|1.1|n25-40": [["w2c:zipper+stretch-b1.1", 40], ["w3a:spiral-a15", 32]],
|
|
29
|
+
"all|1.1|n41-64": [["hw:diffusion", 64]],
|
|
30
|
+
"all|1.1|n65-128": [],
|
|
31
|
+
"all|1.25|n1-12": [["w3d:kdeeb-routes", 12], ["w3e:mlseb+stretch", 12], ["hw:growth", 12], ["hw:level-links", 12], ["w3b:growth-input", 10], ["w3e:confluent+stretch", 10], ["w3d:kdeeb-nomask", 10]],
|
|
32
|
+
"all|1.25|n13-24": [["w3d:kdeeb-routes", 24], ["w3f:p-hub-a0.2", 24], ["w3d:kdeeb-nomask", 18], ["w3a:spiral-n3", 18]],
|
|
25
33
|
"all|1.25|n25-40": [["w3d:kdeeb-routes", 32]],
|
|
26
|
-
"all|1.
|
|
34
|
+
"all|1.25|n41-64": [],
|
|
35
|
+
"all|1.25|n65-128": [],
|
|
36
|
+
"all|1.5|n1-12": [["w3b:growth-s0.3", 12], ["w3e:mlseb-a0", 12], ["w3e:mlseb-routes", 10], ["w3c:edge-paths-k1.5", 10], ["w3f:territories-r2", 10]],
|
|
27
37
|
"all|1.5|n13-24": [["hw:growth", 24], ["w3b:growth-input", 24]],
|
|
28
|
-
"all|1.5|n25-40": [["hw:growth", 40], ["w3b:growth-s0.7",
|
|
38
|
+
"all|1.5|n25-40": [["hw:growth", 40], ["w3b:growth-s0.7", 40], ["w3b:growth-s0.3", 32]],
|
|
39
|
+
"all|1.5|n41-64": [],
|
|
40
|
+
"all|1.5|n65-128": [],
|
|
29
41
|
};
|
package/src/portfolio/router.ts
CHANGED
|
@@ -30,8 +30,20 @@ export interface PortfolioRouterOptions {
|
|
|
30
30
|
readonly maxMembers?: number;
|
|
31
31
|
/** The clock in milliseconds. The default is `performance.now`. */
|
|
32
32
|
readonly now?: () => number;
|
|
33
|
+
/**
|
|
34
|
+
* The members that get an output hash in the trace (HASH-LAZY). The pick does not use the hashes, so the winner and the output do not change.
|
|
35
|
+
* - With `pick` (the default), only the base and the winner get a hash, and only when the winner is not the base.
|
|
36
|
+
* - With `all`, each member that ran gets a hash, as before HASH-LAZY. Use it when you need the `outputHash` of each member.
|
|
37
|
+
*/
|
|
38
|
+
readonly hashes?: PortfolioHashes;
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
/** The values of the option `hashes` of the portfolio router. */
|
|
42
|
+
export type PortfolioHashes = "all" | "pick";
|
|
43
|
+
|
|
44
|
+
/** The values of the option `hashes`, in order. The default is `pick`. */
|
|
45
|
+
export const PORTFOLIO_HASHES: readonly PortfolioHashes[] = ["pick", "all"];
|
|
46
|
+
|
|
35
47
|
/** The base member of each portfolio: the router `shared` with the rule `byCollection`. It is the fallback output above the cap. */
|
|
36
48
|
export const PORTFOLIO_BASE_MEMBER = "shared";
|
|
37
49
|
|
|
@@ -49,14 +61,16 @@ export interface PortfolioRoute {
|
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
/** 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 } {
|
|
64
|
+
function checked(o: PortfolioRouterOptions): { rule: PortfolioRule; cap: PortfolioCap; k: 3 | 5 | 8 | undefined; hashes: PortfolioHashes } {
|
|
53
65
|
const rule = o.rule ?? "collection";
|
|
54
66
|
const cap = o.cap ?? 1.5;
|
|
55
67
|
const k = o.k;
|
|
68
|
+
const hashes = o.hashes ?? "pick";
|
|
56
69
|
if (!PORTFOLIO_RULES.includes(rule)) throw new RangeError(`The portfolio router has the rule "${String(rule)}". It must be ${PORTFOLIO_RULES.join(" or ")}.`);
|
|
57
70
|
if (!PORTFOLIO_CAPS.includes(cap)) throw new RangeError(`The portfolio router has the cap ${String(cap)}. It must be ${PORTFOLIO_CAPS.join(", ")}.`);
|
|
58
71
|
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
|
-
|
|
72
|
+
if (!PORTFOLIO_HASHES.includes(hashes)) throw new RangeError(`The portfolio router has the hashes "${String(hashes)}". It must be ${PORTFOLIO_HASHES.join(" or ")}.`);
|
|
73
|
+
return { rule, cap, k, hashes };
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
/** 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`. */
|
|
@@ -83,11 +97,12 @@ export function portfolioMembers(nodes: number, options: PortfolioRouterOptions
|
|
|
83
97
|
* 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
98
|
* A tie within the `minEffect` of B1 goes to the first member (`selectBest`). A member that throws does not win.
|
|
85
99
|
* The result carries the trace of the pick and the move count `move.pick`.
|
|
100
|
+
* With the option `hashes` "pick" (the default), only the base and the winner get an `outputHash` in the trace. They get it only when the winner is not the base.
|
|
86
101
|
*/
|
|
87
102
|
export function routePortfolio(graph: Graph, options: PortfolioRouterOptions = {}): PortfolioRoute {
|
|
88
103
|
// 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
104
|
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);
|
|
105
|
+
const { rule, cap, hashes } = checked(options);
|
|
91
106
|
// The key compares each member with the output of the base, so the run of the base keeps its output.
|
|
92
107
|
let base: Result | undefined;
|
|
93
108
|
const members: Member<Graph, Result>[] = names.map((name) => {
|
|
@@ -104,6 +119,7 @@ export function routePortfolio(graph: Graph, options: PortfolioRouterOptions = {
|
|
|
104
119
|
};
|
|
105
120
|
});
|
|
106
121
|
const p = portfolio(portfolioName(options), members, portfolioKey(graph, rule, cap, () => base), {
|
|
122
|
+
hashes,
|
|
107
123
|
...(options.budgetMs === undefined ? {} : { budgetMs: options.budgetMs }),
|
|
108
124
|
...(options.maxMembers === undefined ? {} : { maxMembers: options.maxMembers }),
|
|
109
125
|
...(options.now === undefined ? {} : { now: options.now }),
|
package/src/portfolio/sets.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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.
|
|
2
|
+
// The records come from the CI runs 40631516113, 40644555345, 40644557536, 40644560237, 40644562652, 40644565132, 40644567956, 40644570455, 40644572910, 40644575649, 40644578541, 40644580965, 40644583603, 40665016754.
|
|
3
3
|
import type { PortfolioCap, PortfolioRule } from "./bands.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -8,21 +8,33 @@ import type { PortfolioCap, PortfolioRule } from "./bands.ts";
|
|
|
8
8
|
*/
|
|
9
9
|
export const PORTFOLIO_SETS: Readonly<Record<`${PortfolioRule}|${PortfolioCap}|${string}`, readonly string[]>> = {
|
|
10
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", "corners-side", "shared-p2", "w2c:zipper+stretch-b1.1", "
|
|
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
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": [],
|
|
13
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"],
|
|
14
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"],
|
|
15
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"],
|
|
16
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"],
|
|
17
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"],
|
|
18
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"],
|
|
19
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"],
|
|
20
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"],
|
|
21
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"],
|
|
22
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"],
|
|
23
|
-
"all|1.25|n13-24": ["w3d:kdeeb-routes", "
|
|
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"],
|
|
24
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"],
|
|
25
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"],
|
|
26
|
-
"all|1.5|n13-24": ["hw:growth", "w3b:growth-input", "hw:mlseb", "
|
|
36
|
+
"all|1.5|n13-24": ["hw:growth", "w3b:growth-input", "hw:mlseb", "w3b:growth-s0.3"],
|
|
27
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"],
|
|
28
40
|
};
|