@mark1russell7/highways 0.8.1 → 0.9.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 +74 -8
- package/build/index.d.ts +705 -183
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4079 -2639
- package/build/index.js.map +1 -1
- package/package.json +3 -2
- package/src/bench/compare.ts +174 -5
- package/src/bench/room.ts +1 -1
- package/src/bench/run.ts +38 -3
- package/src/bench/screen/generators.ts +11 -41
- package/src/bench/screen/screen.ts +16 -10
- package/src/bench/screen/set.ts +156 -49
- package/src/bench/suite.ts +110 -12
- package/src/cases.ts +64 -18
- package/src/families/cases.ts +108 -0
- package/src/families/fixtures.ts +101 -0
- package/src/families/index.ts +36 -0
- package/src/families/model.ts +55 -0
- package/src/fixtures.ts +8 -2
- package/src/highways/soft-keys/index.ts +8 -4
- package/src/index.ts +8 -0
- package/src/lane-order.ts +37 -0
- package/src/lane-orders/stretch/index.ts +486 -0
- package/src/lane-orders/stretch/router.ts +22 -0
- package/src/levels/depths/index.ts +57 -0
- package/src/plugs/facing/index.ts +22 -0
- package/src/portfolio/router.ts +44 -0
- package/src/routers/corners.ts +27 -3
- package/src/routers/sections.ts +22 -40
- package/src/routers/shared.ts +26 -7
- package/src/routers/trunks.ts +21 -3
- package/src/segments.ts +4 -25
- package/src/strategies/builtins.ts +54 -2
- package/src/strategies/model.ts +55 -10
- package/src/strategies/options.ts +36 -4
- package/src/strategies/schemas.ts +68 -5
- package/src/whole/pipeline/index.ts +66 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// The 3 case families of the screen sets: `hub-chain`, `many-groups` and `packed-grid` (CORPUS-GEN).
|
|
2
|
+
import type { CorpusDimension, DimensionValues, GenerateContext } from "@mark1russell7/layout-contract";
|
|
3
|
+
import { hubChainBoard, manyGroupsBoard, packedGridBoard } from "../cases.ts";
|
|
4
|
+
import type { Graph } from "../model.ts";
|
|
5
|
+
import { changedDimensions, numberDimension, numberOf, type HighwaysBoard, type HighwaysFamily } from "./model.ts";
|
|
6
|
+
|
|
7
|
+
/** This function gives a whole-number dimension with a linear scale. */
|
|
8
|
+
const count = (name: string, min: number, max: number, value: number, meaning: string): CorpusDimension =>
|
|
9
|
+
numberDimension({ name, kind: "int", min, max, default: value, meaning });
|
|
10
|
+
|
|
11
|
+
/** This function gives the numbers of the full dimension values, by name. */
|
|
12
|
+
function numbers(dimensions: readonly CorpusDimension[], dims: DimensionValues): Record<string, number> {
|
|
13
|
+
return Object.fromEntries(dimensions.map((d) => [d.name, numberOf(dims, d.name)]));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The fields of a case family of `caseFamily`. */
|
|
17
|
+
interface CaseFields {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly meaning: string;
|
|
20
|
+
readonly dimensions: readonly CorpusDimension[];
|
|
21
|
+
/** The name of the generator record, for example `case.hub-chain`. */
|
|
22
|
+
readonly generator: string;
|
|
23
|
+
/** This function gives the parameters of the generator record from the full values. The default gives the values that differ from their defaults. */
|
|
24
|
+
readonly params?: (dims: DimensionValues) => Readonly<Record<string, unknown>>;
|
|
25
|
+
/** This function makes the board. It gets the values by name, the input seed and the source of random numbers of the registry. */
|
|
26
|
+
readonly make: (values: Readonly<Record<string, number>>, seed: number, random: () => number) => Graph;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** This function gives a case family. Its stream is mulberry32 of the input seed, and it uses only the stream, `Math.floor` and `Math.ceil`. */
|
|
30
|
+
function caseFamily(fields: CaseFields): HighwaysFamily {
|
|
31
|
+
const { name, meaning, dimensions } = fields;
|
|
32
|
+
const params = fields.params ?? ((dims: DimensionValues) => changedDimensions(dimensions, dims));
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
name,
|
|
35
|
+
version: 1,
|
|
36
|
+
owner: "highways",
|
|
37
|
+
meaning,
|
|
38
|
+
dimensions,
|
|
39
|
+
seeding: "mulberry32 of the input seed. The fixed part of the board takes no number. Then one stream gives the random links.",
|
|
40
|
+
crossEngine: "exact",
|
|
41
|
+
generate(seed: number, dims: DimensionValues, context: GenerateContext): HighwaysBoard {
|
|
42
|
+
return { graph: fields.make(numbers(dimensions, dims), seed, context.random), generator: { name: fields.generator, version: 1, params: params(dims), seed } };
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The dimensions of `hub-chain`. */
|
|
48
|
+
const HUB_CHAIN_DIMENSIONS: readonly CorpusDimension[] = Object.freeze([
|
|
49
|
+
count("rows", 1, 8, 3, "The number of rows of cards."),
|
|
50
|
+
count("cols", 4, 64, 32, "The number of cards in each row."),
|
|
51
|
+
count("long", 0, 200, 40, "The number of tries for a long link between two rows."),
|
|
52
|
+
count("gap", 0, 96, 48, "The free space between two cards, in units."),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/** The family `hub-chain`: rows of cards in a chain, and long links between rows. At its defaults, it is `hubChainBoard`. */
|
|
56
|
+
export const HUB_CHAIN: HighwaysFamily = caseFamily({
|
|
57
|
+
name: "hub-chain",
|
|
58
|
+
meaning: "Rows of cards in a chain, and long links that climb a chain of hubs away from their targets.",
|
|
59
|
+
dimensions: HUB_CHAIN_DIMENSIONS,
|
|
60
|
+
generator: "case.hub-chain",
|
|
61
|
+
make: (v, seed, random) => hubChainBoard({ seed, rows: v["rows"]!, cols: v["cols"]!, long: v["long"]!, gap: v["gap"]! }, random),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/** The dimensions of `many-groups`. */
|
|
65
|
+
const MANY_GROUPS_DIMENSIONS: readonly CorpusDimension[] = Object.freeze([
|
|
66
|
+
count("groupCols", 1, 16, 8, "The number of columns of groups."),
|
|
67
|
+
count("groupRows", 1, 8, 4, "The number of rows of groups."),
|
|
68
|
+
count("perGroup", 1, 16, 4, "The number of cards in each group."),
|
|
69
|
+
count("crossLinksPerGroup", 0, 8, 3, "The number of random links between groups, for each group."),
|
|
70
|
+
count("groupGap", 0, 120, 60, "The free space between two groups, in units."),
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
/** The family `many-groups`: groups of cards in a grid, with links between groups. At its defaults, it is `manyGroupsBoard`. */
|
|
74
|
+
export const MANY_GROUPS: HighwaysFamily = caseFamily({
|
|
75
|
+
name: "many-groups",
|
|
76
|
+
meaning: "A grid of groups of cards, with random links between groups that go around the board.",
|
|
77
|
+
dimensions: MANY_GROUPS_DIMENSIONS,
|
|
78
|
+
generator: "case.many-groups",
|
|
79
|
+
make: (v, seed, random) =>
|
|
80
|
+
manyGroupsBoard(
|
|
81
|
+
{ seed, groupCols: v["groupCols"]!, groupRows: v["groupRows"]!, perGroup: v["perGroup"]!, crossLinksPerGroup: v["crossLinksPerGroup"]!, groupGap: v["groupGap"]! },
|
|
82
|
+
random,
|
|
83
|
+
),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/** The dimensions of `packed-grid`. */
|
|
87
|
+
const PACKED_GRID_DIMENSIONS: readonly CorpusDimension[] = Object.freeze([
|
|
88
|
+
count("cols", 2, 32, 12, "The number of columns of boxes."),
|
|
89
|
+
count("rows", 2, 32, 8, "The number of rows of boxes."),
|
|
90
|
+
count("long", 0, 256, 48, "The number of long links between boxes far apart."),
|
|
91
|
+
count("reach", 1, 4, 2, "The largest step, in rows and in columns, of the link of a box to a box near it."),
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The family `packed-grid`: a grid of boxes that touch. At its defaults, it is `packedGridBoard` with 12 columns, 8 rows and 48 long links.
|
|
96
|
+
* Its generator record always has `cols`, `rows` and `long`, as the screen sets of today. It has `reach` only away from its default.
|
|
97
|
+
*/
|
|
98
|
+
export const PACKED_GRID: HighwaysFamily = caseFamily({
|
|
99
|
+
name: "packed-grid",
|
|
100
|
+
meaning: "A packed grid of boxes that touch, with no channel for the routes.",
|
|
101
|
+
dimensions: PACKED_GRID_DIMENSIONS,
|
|
102
|
+
generator: "case.packed-grid",
|
|
103
|
+
params: (dims) => {
|
|
104
|
+
const reach = numberOf(dims, "reach");
|
|
105
|
+
return { cols: numberOf(dims, "cols"), rows: numberOf(dims, "rows"), long: numberOf(dims, "long"), ...(reach === 2 ? {} : { reach }) };
|
|
106
|
+
},
|
|
107
|
+
make: (v, seed, random) => packedGridBoard({ seed, cols: v["cols"]!, rows: v["rows"]!, long: v["long"]!, reach: v["reach"]! }, random),
|
|
108
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// The 3 fixture families of the screen sets: `tiny-uniform`, `small-clustered` and `dense-groups` (CORPUS-GEN).
|
|
2
|
+
import type { CorpusDimension, DimensionValues, GenerateContext } from "@mark1russell7/layout-contract";
|
|
3
|
+
import { fixtureRngSeed, makeFixture, type FixtureKind, type FixtureSpec } from "../fixtures.ts";
|
|
4
|
+
import { enumDimension, numberDimension, numberOf, type HighwaysBoard, type HighwaysFamily } from "./model.ts";
|
|
5
|
+
|
|
6
|
+
/** The values of the dimension `layout`: each `FixtureKind`. */
|
|
7
|
+
export const FIXTURE_LAYOUTS: readonly FixtureKind[] = Object.freeze(["uniform", "clustered", "grid", "hubs", "collections"]);
|
|
8
|
+
|
|
9
|
+
/** The share of the edges near the source when a spec has no `locality`: the default of `makeFixture`. */
|
|
10
|
+
export const FIXTURE_LOCALITY = 0.65;
|
|
11
|
+
|
|
12
|
+
/** The defaults of the dimensions of one fixture family: the spec of its board in the benchmark. */
|
|
13
|
+
interface FixtureDefaults {
|
|
14
|
+
readonly layout: FixtureKind;
|
|
15
|
+
readonly nodes: number;
|
|
16
|
+
readonly edgesPerNode: number;
|
|
17
|
+
readonly fixedPorts: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* This function gives the 5 dimensions of a fixture family. The name `kind` is reserved, so the dimension of the `FixtureKind` is `layout`.
|
|
22
|
+
* No dimension has a `when`, because each kind reads `locality` and `fixedPorts`.
|
|
23
|
+
*/
|
|
24
|
+
function fixtureDimensions(d: FixtureDefaults): readonly CorpusDimension[] {
|
|
25
|
+
return Object.freeze([
|
|
26
|
+
enumDimension("layout", FIXTURE_LAYOUTS, d.layout, "The way that the board puts its nodes and picks its edges: the `kind` of the `FixtureSpec`."),
|
|
27
|
+
numberDimension({ name: "nodes", kind: "int", min: 2, max: 400, default: d.nodes, scale: "log", meaning: "The number of nodes of the board." }),
|
|
28
|
+
numberDimension({ name: "edgesPerNode", kind: "real", min: 0, max: 6, default: d.edgesPerNode, meaning: "The number of edges, divided by the number of nodes." }),
|
|
29
|
+
numberDimension({ name: "locality", kind: "real", min: 0, max: 1, default: FIXTURE_LOCALITY, meaning: "The share of the edges that join a node near the source." }),
|
|
30
|
+
numberDimension({ name: "fixedPorts", kind: "real", min: 0, max: 1, default: d.fixedPorts, meaning: "The share of the nodes with one fixed plug for their outgoing edges." }),
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** This function gives the spec of a fixture board from the full dimension values. A field at the default of `makeFixture` stays out of the spec. */
|
|
35
|
+
export function fixtureSpecOf(name: string, seed: number, dims: DimensionValues): FixtureSpec {
|
|
36
|
+
const layout = dims["layout"];
|
|
37
|
+
if (typeof layout !== "string" || !FIXTURE_LAYOUTS.includes(layout as FixtureKind)) throw new RangeError(`The dimension "layout" is ${String(layout)}, not a fixture kind.`);
|
|
38
|
+
const locality = numberOf(dims, "locality");
|
|
39
|
+
const fixedPorts = numberOf(dims, "fixedPorts");
|
|
40
|
+
return {
|
|
41
|
+
name,
|
|
42
|
+
kind: layout as FixtureKind,
|
|
43
|
+
nodes: numberOf(dims, "nodes"),
|
|
44
|
+
edgesPerNode: numberOf(dims, "edgesPerNode"),
|
|
45
|
+
seed,
|
|
46
|
+
...(locality === FIXTURE_LOCALITY ? {} : { locality }),
|
|
47
|
+
...(fixedPorts === 0 ? {} : { fixedPorts }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* This function gives the parameters of the generator record of a fixture board: the spec without its name and its seed.
|
|
53
|
+
* Thus the record of each screen board stays the record of today, for example `{ kind, nodes, edgesPerNode, fixedPorts }` of `small-clustered`.
|
|
54
|
+
*/
|
|
55
|
+
export function fixtureParams(spec: FixtureSpec): Readonly<Record<string, unknown>> {
|
|
56
|
+
const { name: _name, seed: _seed, ...params } = spec;
|
|
57
|
+
return params;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** This function gives one fixture family with the defaults of its board in the benchmark. */
|
|
61
|
+
function fixtureFamily(name: string, meaning: string, defaults: FixtureDefaults): HighwaysFamily {
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
name,
|
|
64
|
+
version: 1,
|
|
65
|
+
owner: "highways",
|
|
66
|
+
meaning,
|
|
67
|
+
dimensions: fixtureDimensions(defaults),
|
|
68
|
+
seeding: "mulberry32 of (seed × 2654435761 + nodes) >>> 0. One stream gives the boxes, the plugs and then the edges, in a fixed order.",
|
|
69
|
+
// The kinds `clustered` and `collections` use `Math.log` and `Math.cos`, and the kind `hubs` uses `**`. Each fixture family has these kinds in `layout`.
|
|
70
|
+
crossEngine: "libm-dependent",
|
|
71
|
+
rngSeed: (seed: number, dims: DimensionValues) => fixtureRngSeed(seed, numberOf(dims, "nodes")),
|
|
72
|
+
generate(seed: number, dims: DimensionValues, context: GenerateContext): HighwaysBoard {
|
|
73
|
+
const spec = fixtureSpecOf(name, seed, dims);
|
|
74
|
+
return { graph: makeFixture(spec, context.random), generator: { name: "fixture", version: 1, params: fixtureParams(spec), seed } };
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The family `tiny-uniform`: 10 nodes at random places. It is the fixture `tiny-uniform` of `FIXTURES` at its defaults. */
|
|
80
|
+
export const TINY_UNIFORM: HighwaysFamily = fixtureFamily("tiny-uniform", "A tiny board of nodes at random places: the cost of a strategy at its start.", {
|
|
81
|
+
layout: "uniform",
|
|
82
|
+
nodes: 10,
|
|
83
|
+
edgesPerNode: 1.5,
|
|
84
|
+
fixedPorts: 0,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/** The family `small-clustered`: 100 nodes in clusters, with a fixed plug on 20% of the nodes. It is the fixture `small-clustered` at its defaults. */
|
|
88
|
+
export const SMALL_CLUSTERED: HighwaysFamily = fixtureFamily("small-clustered", "Nodes in clusters, and fixed plugs on a share of the nodes.", {
|
|
89
|
+
layout: "clustered",
|
|
90
|
+
nodes: 100,
|
|
91
|
+
edgesPerNode: 2,
|
|
92
|
+
fixedPorts: 0.2,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/** The family `dense-groups`: 150 nodes in tight clusters. It is `denseGroupsBoard` at its defaults. */
|
|
96
|
+
export const DENSE_GROUPS: HighwaysFamily = fixtureFamily("dense-groups", "Dense clusters whose groups overlap at the far view.", {
|
|
97
|
+
layout: "clustered",
|
|
98
|
+
nodes: 150,
|
|
99
|
+
edgesPerNode: 2,
|
|
100
|
+
fixedPorts: 0,
|
|
101
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The corpus families of highways on the shared registry of `@mark1russell7/layout-contract` (CORPUS-GEN, L45).
|
|
2
|
+
import { createFamilyRegistry, type FamilyRegistry } from "@mark1russell7/layout-contract";
|
|
3
|
+
import { HUB_CHAIN, MANY_GROUPS, PACKED_GRID } from "./cases.ts";
|
|
4
|
+
import { DENSE_GROUPS, SMALL_CLUSTERED, TINY_UNIFORM } from "./fixtures.ts";
|
|
5
|
+
import type { HighwaysBoard, HighwaysFamily } from "./model.ts";
|
|
6
|
+
|
|
7
|
+
export * from "./model.ts";
|
|
8
|
+
export { DENSE_GROUPS, FIXTURE_LAYOUTS, FIXTURE_LOCALITY, fixtureParams, fixtureSpecOf, SMALL_CLUSTERED, TINY_UNIFORM } from "./fixtures.ts";
|
|
9
|
+
export { HUB_CHAIN, MANY_GROUPS, PACKED_GRID } from "./cases.ts";
|
|
10
|
+
// The parts of the shared registry that a host of the families uses. The site imports only `@mark1russell7/highways`.
|
|
11
|
+
export {
|
|
12
|
+
createFamilyRegistry,
|
|
13
|
+
DEFAULT_RNG,
|
|
14
|
+
dimensionAt,
|
|
15
|
+
dimensionUnit,
|
|
16
|
+
familyKind,
|
|
17
|
+
levelsOf,
|
|
18
|
+
paramProblem,
|
|
19
|
+
type CorpusDimension,
|
|
20
|
+
type CorpusFamily,
|
|
21
|
+
type DimensionValues,
|
|
22
|
+
type FamilyRegistry,
|
|
23
|
+
type GenerateContext,
|
|
24
|
+
type ParamValue,
|
|
25
|
+
} from "@mark1russell7/layout-contract";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The corpus families of highways, in the order of the screen sets. Each family at its defaults makes the board of its screen family.
|
|
29
|
+
* An import registers nothing. A host makes a registry with `createFamilyRegistry(HIGHWAYS_FAMILIES)`, or with the lists of other engines too.
|
|
30
|
+
*/
|
|
31
|
+
export const HIGHWAYS_FAMILIES: readonly HighwaysFamily[] = Object.freeze([TINY_UNIFORM, SMALL_CLUSTERED, DENSE_GROUPS, HUB_CHAIN, MANY_GROUPS, PACKED_GRID]);
|
|
32
|
+
|
|
33
|
+
/** This function gives a new registry of `HIGHWAYS_FAMILIES`. */
|
|
34
|
+
export function highwaysRegistry(): FamilyRegistry<HighwaysBoard> {
|
|
35
|
+
return createFamilyRegistry(HIGHWAYS_FAMILIES);
|
|
36
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The form of the corpus families of highways (CORPUS-GEN) on the shared registry of `@mark1russell7/layout-contract` (L45).
|
|
2
|
+
import type { CorpusDimension, CorpusFamily, DimensionValues, ParamValue } from "@mark1russell7/layout-contract";
|
|
3
|
+
import type { LayoutGenerator } from "../bench/screen/forms.ts";
|
|
4
|
+
import type { Graph } from "../model.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The output of a family of highways: the graph of the board and its generator record.
|
|
8
|
+
* A screen set puts the board into an input file with `graphInput`. The set gives the id, the member seed and the cell.
|
|
9
|
+
*/
|
|
10
|
+
export interface HighwaysBoard {
|
|
11
|
+
readonly graph: Graph;
|
|
12
|
+
/** The generator of the board: its name, its version, its parameters and its input seed. */
|
|
13
|
+
readonly generator: LayoutGenerator;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A corpus family of highways: a family of the shared registry whose output is a `HighwaysBoard`. */
|
|
17
|
+
export type HighwaysFamily = CorpusFamily<HighwaysBoard>;
|
|
18
|
+
|
|
19
|
+
/** The engine of each dimension of the families of highways. */
|
|
20
|
+
export const HIGHWAYS_ENGINE = "highways";
|
|
21
|
+
|
|
22
|
+
/** The fields of a numeric dimension of `numberDimension`. */
|
|
23
|
+
interface NumberFields {
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly kind: "int" | "real";
|
|
26
|
+
readonly min: number;
|
|
27
|
+
readonly max: number;
|
|
28
|
+
readonly default: number;
|
|
29
|
+
readonly scale?: "linear" | "log";
|
|
30
|
+
readonly meaning: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** This function gives an `int` or a `real` dimension that only highways reads. */
|
|
34
|
+
export function numberDimension(fields: NumberFields): CorpusDimension {
|
|
35
|
+
return Object.freeze({ ...fields, scale: fields.scale ?? "linear", engines: Object.freeze([HIGHWAYS_ENGINE]) });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** This function gives an `enum` dimension that only highways reads. */
|
|
39
|
+
export function enumDimension(name: string, values: readonly string[], value: string, meaning: string): CorpusDimension {
|
|
40
|
+
return Object.freeze({ name, kind: "enum", values: Object.freeze([...values]), default: value, meaning, engines: Object.freeze([HIGHWAYS_ENGINE]) });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** This function gives the values of the dimensions that differ from their defaults, in the order of the dimensions. */
|
|
44
|
+
export function changedDimensions(dimensions: readonly CorpusDimension[], dims: DimensionValues): Record<string, ParamValue> {
|
|
45
|
+
const out: Record<string, ParamValue> = {};
|
|
46
|
+
for (const d of dimensions) if (!Object.is(dims[d.name], d.default)) out[d.name] = dims[d.name]!;
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** This function gives the value of a numeric dimension. It throws a `RangeError` when the value is not a number. */
|
|
51
|
+
export function numberOf(dims: DimensionValues, name: string): number {
|
|
52
|
+
const v = dims[name];
|
|
53
|
+
if (typeof v !== "number") throw new RangeError(`The dimension "${name}" is ${String(v)}, not a number.`);
|
|
54
|
+
return v;
|
|
55
|
+
}
|
package/src/fixtures.ts
CHANGED
|
@@ -52,12 +52,18 @@ const SIDES: readonly Side[] = ["north", "east", "south", "west"];
|
|
|
52
52
|
const GAP = 12;
|
|
53
53
|
const SPACING = 90;
|
|
54
54
|
|
|
55
|
+
/** This function gives the seed of the random numbers of a fixture: `(seed × 2654435761 + nodes) >>> 0`. */
|
|
56
|
+
export function fixtureRngSeed(seed: number, nodes: number): number {
|
|
57
|
+
return (seed * 2654435761 + nodes) >>> 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
/**
|
|
56
61
|
* This function makes the graph of a fixture. The same spec always gives the same graph.
|
|
57
62
|
* Node boxes do not overlap, and a gap of 12 units stays between them.
|
|
63
|
+
* The default source of random numbers is `rng(fixtureRngSeed(spec.seed, spec.nodes))`. A corpus family gives the same stream from its registry.
|
|
58
64
|
*/
|
|
59
|
-
export function makeFixture(spec: FixtureSpec): Graph {
|
|
60
|
-
const R =
|
|
65
|
+
export function makeFixture(spec: FixtureSpec, random: () => number = rng(fixtureRngSeed(spec.seed, spec.nodes))): Graph {
|
|
66
|
+
const R = random;
|
|
61
67
|
const gauss = (): number => Math.sqrt(-2 * Math.log(R() + 1e-12)) * Math.cos(2 * Math.PI * R());
|
|
62
68
|
const side = Math.sqrt(spec.nodes) * SPACING;
|
|
63
69
|
const grid = new Grid<Box>(SPACING);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GroupRule } from "../../model.ts";
|
|
2
|
-
import { byCollection } from "../../routers/trunks.ts";
|
|
2
|
+
import { byCollection, groupRuleOf, type GroupRuleName } from "../../routers/trunks.ts";
|
|
3
3
|
import { chainClusters, flowDistance, type Linkage } from "../flow-clusters/index.ts";
|
|
4
4
|
import { groupedPlan, type EdgeGroup, type GroupSpine } from "../grouped.ts";
|
|
5
5
|
import type { HighwayInput, HighwayPlan } from "../model.ts";
|
|
@@ -7,8 +7,11 @@ import { edgePlugs } from "../stretches.ts";
|
|
|
7
7
|
|
|
8
8
|
/** The options of the highway strategy `soft-keys` (HWY-019 of the note on highways). */
|
|
9
9
|
export interface SoftKeyOptions {
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The rule that gives the key of each edge, or the name of a built-in rule (`GROUP_RULE_NAMES`). The default is `byCollection`: the string `data.collection`.
|
|
12
|
+
* With `all`, each edge has one key, so the keys add no distance.
|
|
13
|
+
*/
|
|
14
|
+
readonly rule: GroupRule | GroupRuleName;
|
|
12
15
|
/** The distance that two different keys add. `Infinity` keeps the edges of two keys apart, as the hard keys do. The default is 0.25. */
|
|
13
16
|
readonly lambda: number;
|
|
14
17
|
/** Two clusters join while their distance is this value or less. The default is 0.5. */
|
|
@@ -54,11 +57,12 @@ export function softDistance(geom: number, keyA: string | undefined, keyB: strin
|
|
|
54
57
|
* Each edge of a cluster gets the key of the cluster. It gets `flip` when it goes opposite to the first edge of the cluster.
|
|
55
58
|
*/
|
|
56
59
|
export function softKeyClusters(input: HighwayInput, o: Readonly<SoftKeyOptions>): Map<string, EdgeGroup> {
|
|
60
|
+
const rule = groupRuleOf(o.rule);
|
|
57
61
|
const ends = edgePlugs(input.graph).map(({ edge, ps, pt }) => ({
|
|
58
62
|
edge: edge.id,
|
|
59
63
|
s: ps.point,
|
|
60
64
|
t: pt.point,
|
|
61
|
-
key:
|
|
65
|
+
key: rule(edge),
|
|
62
66
|
values: o.attributes.map((a) => {
|
|
63
67
|
const v = edge.data?.[a];
|
|
64
68
|
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,9 @@ export * from "./routers/index.ts";
|
|
|
17
17
|
export * from "./room/index.ts";
|
|
18
18
|
export * from "./room/lanes.ts";
|
|
19
19
|
export * from "./room/levels/index.ts";
|
|
20
|
+
export * from "./plugs/facing/index.ts";
|
|
21
|
+
export * from "./levels/depths/index.ts";
|
|
22
|
+
export * from "./whole/pipeline/index.ts";
|
|
20
23
|
export * from "./bench/properties.ts";
|
|
21
24
|
export * from "./bench/quality.ts";
|
|
22
25
|
export * from "./bench/links.ts";
|
|
@@ -29,9 +32,14 @@ export * from "./bench/suite.ts";
|
|
|
29
32
|
export * from "./strategies/index.ts";
|
|
30
33
|
export * from "./bench/compare.ts";
|
|
31
34
|
export * from "./bench/screen/index.ts";
|
|
35
|
+
// The corpus families of highways on the shared registry of the contract (CORPUS-GEN).
|
|
36
|
+
export * from "./families/index.ts";
|
|
32
37
|
// The stretch of the exact model, for a benchmark that compares routers with the measures of the portfolio (PORTFOLIO-SPEED).
|
|
33
38
|
export { exactStretch, exactStretches } from "./bench/exact/index.ts";
|
|
34
39
|
export * from "./highways/index.ts";
|
|
35
40
|
export * from "./order/index.ts";
|
|
36
41
|
export * from "./repair/index.ts";
|
|
37
42
|
export * from "./portfolio/index.ts";
|
|
43
|
+
// One lane order for each stretch of runs (LANE-STRETCH), and its opt-in router `shared-stretch`.
|
|
44
|
+
export * from "./lane-orders/stretch/index.ts";
|
|
45
|
+
export { sharedStretchStrategy } from "./lane-orders/stretch/router.ts";
|
package/src/lane-order.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// The package index does not export this module.
|
|
3
3
|
import { byText } from "./compare.ts";
|
|
4
4
|
import { dist, type Point } from "./geom.ts";
|
|
5
|
+
import type { Route } from "./model.ts";
|
|
5
6
|
|
|
6
7
|
/** Two points nearer than this distance are the same point. The value is the same as in `segments.ts`. */
|
|
7
8
|
const SAME = 1e-6;
|
|
@@ -14,6 +15,42 @@ export interface Placed {
|
|
|
14
15
|
readonly e: number;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/** A placed route with the way that it goes through the run. */
|
|
19
|
+
export interface PlacedRoute extends Placed {
|
|
20
|
+
/** This value is true when the route goes from the last point of the run to its first point. Then `p` holds the points of the route in reverse. */
|
|
21
|
+
readonly reversed: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** This function gives the index where `p` holds the points of `part` in a row, from `from` on, or -1. */
|
|
25
|
+
export function findRun(p: readonly Point[], part: readonly Point[], from: number): number {
|
|
26
|
+
for (let i = from; i + part.length <= p.length; i++) {
|
|
27
|
+
let ok = true;
|
|
28
|
+
for (let j = 0; ok && j < part.length; j++) ok = dist(p[i + j]!, part[j]!) <= SAME;
|
|
29
|
+
if (ok) return i;
|
|
30
|
+
}
|
|
31
|
+
return -1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* This function places the routes on the run `run`. A route holds the points of the run in a row, in either direction.
|
|
36
|
+
* The list `lost` holds the edges of the routes that do not hold the run, in the order of `routes`.
|
|
37
|
+
*/
|
|
38
|
+
export function placeRoutes(run: readonly Point[], routes: readonly Route[]): { readonly placed: PlacedRoute[]; readonly lost: string[] } {
|
|
39
|
+
const placed: PlacedRoute[] = [];
|
|
40
|
+
const lost: string[] = [];
|
|
41
|
+
for (const r of routes) {
|
|
42
|
+
let at = findRun(r.points, run, 0);
|
|
43
|
+
if (at >= 0) placed.push({ edge: r.edge, p: r.points, s: at, e: at + run.length - 1, reversed: false });
|
|
44
|
+
else {
|
|
45
|
+
const p = [...r.points].reverse();
|
|
46
|
+
at = findRun(p, run, 0);
|
|
47
|
+
if (at >= 0) placed.push({ edge: r.edge, p, s: at, e: at + run.length - 1, reversed: true });
|
|
48
|
+
else lost.push(r.edge);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { placed, lost };
|
|
52
|
+
}
|
|
53
|
+
|
|
17
54
|
/** The place where two routes part past one end of their common stretch, and the side of the first route there. */
|
|
18
55
|
interface Parting {
|
|
19
56
|
/** The last common point. */
|