@mark1russell7/highways 0.5.0 → 0.5.1

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.
Files changed (56) hide show
  1. package/README.md +43 -2
  2. package/build/index.d.ts +1472 -63
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +10007 -3406
  5. package/build/index.js.map +1 -1
  6. package/package.json +11 -2
  7. package/src/bench/exact/index.ts +72 -11
  8. package/src/bench/exact/optima-exact-v1-bounds.json +14409 -0
  9. package/src/bench/exact/optima-exact-v1.json +4809 -0
  10. package/src/bench/exact/solve.ts +129 -0
  11. package/src/bench/screen/activation.ts +3 -0
  12. package/src/bench/screen/bounds.ts +140 -0
  13. package/src/bench/screen/compare.ts +26 -0
  14. package/src/bench/screen/exact-set.ts +100 -0
  15. package/src/bench/screen/exercise.ts +290 -0
  16. package/src/bench/screen/forms.ts +14 -0
  17. package/src/bench/screen/gap.ts +316 -0
  18. package/src/bench/screen/hash.ts +7 -76
  19. package/src/bench/screen/index.ts +6 -0
  20. package/src/bench/screen/measures.ts +71 -1
  21. package/src/bench/screen/portfolio-pick.ts +20 -0
  22. package/src/bench/screen/screen.ts +50 -2
  23. package/src/bench/screen/set.ts +3 -2
  24. package/src/highways/areas/index.ts +160 -0
  25. package/src/highways/confluent/index.ts +154 -0
  26. package/src/highways/confluent/k23.test-util.ts +8 -0
  27. package/src/highways/consensus/index.ts +129 -0
  28. package/src/highways/crossbars/index.ts +184 -0
  29. package/src/highways/cubu/index.ts +26 -0
  30. package/src/highways/divided/index.ts +111 -0
  31. package/src/highways/fdeb/index.ts +193 -0
  32. package/src/highways/frechet/index.ts +232 -0
  33. package/src/highways/hierarchy/index.ts +140 -0
  34. package/src/highways/index.ts +64 -0
  35. package/src/highways/kdeeb/index.ts +355 -0
  36. package/src/highways/link-communities/index.ts +189 -0
  37. package/src/highways/mesh/index.ts +209 -0
  38. package/src/highways/mlseb/index.ts +204 -0
  39. package/src/highways/p-hub/index.ts +248 -0
  40. package/src/highways/power/index.ts +117 -0
  41. package/src/highways/resistance/index.ts +159 -0
  42. package/src/highways/sbeb/index.ts +303 -0
  43. package/src/highways/scale-space/index.ts +135 -0
  44. package/src/highways/spectral/index.ts +391 -0
  45. package/src/highways/territories/index.ts +150 -0
  46. package/src/highways/trace.ts +153 -0
  47. package/src/index.ts +1 -0
  48. package/src/portfolio/bands.ts +33 -0
  49. package/src/portfolio/fit.ts +29 -0
  50. package/src/portfolio/index.ts +6 -0
  51. package/src/portfolio/key.ts +98 -0
  52. package/src/portfolio/members.ts +171 -0
  53. package/src/portfolio/router.ts +133 -0
  54. package/src/portfolio/sets.ts +28 -0
  55. package/src/strategies/builtins.ts +222 -1
  56. package/src/strategies/schemas.ts +280 -1
@@ -0,0 +1,129 @@
1
+ import { follow } from "../../highways/follow.ts";
2
+ import type { HighwayPlan } from "../../highways/model.ts";
3
+ import type { Graph, Route } from "../../model.ts";
4
+ import { STRATEGIES } from "../../strategies/builtins.ts";
5
+ import { asRouter, getStrategy } from "../../strategies/model.ts";
6
+ import { inkSaved } from "../measures/bundles.ts";
7
+ import { maxStretch } from "../screen/gap.ts";
8
+ import { exactModel, type ExactRule } from "./index.ts";
9
+
10
+ /**
11
+ * The part of a persistent model of the package `highs` that `solveExact` uses.
12
+ * The library does not import `highs`. The caller gives the loaded runtime, so `highs` stays a devDependency.
13
+ */
14
+ export interface HighsModelLike {
15
+ readonly options: { set(values: Readonly<Record<string, string | number | boolean>>): unknown };
16
+ readonly info: { get(name: string): number | bigint };
17
+ run(): unknown;
18
+ getModelStatus(): number;
19
+ getObjectiveValue(): number;
20
+ getSolution(): { readonly colValue: ArrayLike<number> };
21
+ getDimensions(): { readonly numCols: number };
22
+ getColName(index: number): string;
23
+ dispose(): void;
24
+ }
25
+
26
+ /** The part of the loaded runtime of the package `highs` that `solveExact` uses. */
27
+ export interface HighsLike {
28
+ createModel(source: { readonly format: "lp"; readonly data: string }): HighsModelLike;
29
+ readonly constants: { readonly modelStatus: { readonly optimal: number; readonly timeLimit: number } };
30
+ }
31
+
32
+ /** The result of one solve of the exact model. */
33
+ export interface ExactSolve {
34
+ /** The model status of HiGHS, for example `optimal` or `time-limit`. */
35
+ readonly status: string;
36
+ /** This value is `true` when HiGHS proved the optimum, with a relative MIP gap of 0. */
37
+ readonly proven: boolean;
38
+ /** The objective of the best solution, or `null` without a solution. */
39
+ readonly objective: number | null;
40
+ /** The dual bound of HiGHS at the end of the solve, or `null` when HiGHS gives none. */
41
+ readonly bound: number | null;
42
+ /** The relative MIP gap of HiGHS at the end of the solve: `|objective - bound| / |objective|`. It is `null` without a solution. */
43
+ readonly mipGap: number | null;
44
+ /** The time of the solve, in seconds, without the time to make the model. */
45
+ readonly seconds: number;
46
+ /** The size of the model: the edges with a way in the stretch limit, the binary variables and the constraints. */
47
+ readonly edges: number;
48
+ readonly binaries: number;
49
+ readonly rows: number;
50
+ /** The plan of the best solution. It is empty without a solution. */
51
+ readonly plan: HighwayPlan;
52
+ /** The ink saved (B1) of the model itself: `1 - (the ink of the open links) / (the length of the ways)`. It is `null` without a solution. */
53
+ readonly modelB1: number | null;
54
+ }
55
+
56
+ /** This function gives a finite number of the info of HiGHS, or `null`. */
57
+ function infoNumber(model: HighsModelLike, name: string): number | null {
58
+ try {
59
+ const v = Number(model.info.get(name));
60
+ return Number.isFinite(v) ? v : null;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * This function makes the exact model of a graph and solves it with HiGHS.
68
+ * The relative MIP gap is 0, and the time limit is `seconds`.
69
+ * It gives the status, the proof, the bound, the gap, the time and the plan of the best solution.
70
+ */
71
+ export function solveExact(highs: HighsLike, graph: Graph, o: { readonly stretch: number; readonly rule: ExactRule; readonly seconds: number }): ExactSolve {
72
+ const model = exactModel(graph, { stretch: o.stretch, rule: o.rule });
73
+ const size = { edges: model.edges, binaries: model.columns, rows: model.rows };
74
+ const m = highs.createModel({ format: "lp", data: model.lp });
75
+ try {
76
+ m.options.set({ time_limit: o.seconds, output_flag: false, mip_rel_gap: 0 });
77
+ const start = Date.now();
78
+ m.run();
79
+ const seconds = (Date.now() - start) / 1000;
80
+ const code = m.getModelStatus();
81
+ const statuses = highs.constants.modelStatus;
82
+ const status = code === statuses.optimal ? "optimal" : code === statuses.timeLimit ? "time-limit" : `status-${code}`;
83
+ // The value 2 of `primal_solution_status` is a feasible solution.
84
+ const found = infoNumber(m, "primal_solution_status") === 2;
85
+ if (!found) return { status, proven: false, objective: null, bound: infoNumber(m, "mip_dual_bound"), mipGap: null, seconds, ...size, plan: { highways: [] }, modelB1: null };
86
+ const values = m.getSolution().colValue;
87
+ const byName = new Map<string, number>();
88
+ const cols = m.getDimensions().numCols;
89
+ for (let i = 0; i < cols; i++) byName.set(m.getColName(i), values[i]!);
90
+ const value = (name: string): number => byName.get(name) ?? 0;
91
+ const plan = model.decode(value);
92
+ // The ink is the sum of the lengths of the open links (`y`), and the length of the ways is the sum of the lengths of the arcs (`x`).
93
+ let ink = 0;
94
+ let ways = 0;
95
+ for (const [name, len] of model.lengths) {
96
+ if (value(name) <= 0.5) continue;
97
+ if (name.startsWith("y")) ink += len;
98
+ else ways += len;
99
+ }
100
+ const mipGap = infoNumber(m, "mip_gap");
101
+ return {
102
+ status,
103
+ proven: code === statuses.optimal,
104
+ objective: m.getObjectiveValue(),
105
+ bound: infoNumber(m, "mip_dual_bound"),
106
+ mipGap: code === statuses.optimal ? (mipGap ?? 0) : mipGap,
107
+ seconds,
108
+ ...size,
109
+ plan,
110
+ modelB1: ways > 0 ? Math.max(0, 1 - ink / ways) : 0,
111
+ };
112
+ } finally {
113
+ m.dispose();
114
+ }
115
+ }
116
+
117
+ /** The routes of a plan after `follow`, with the ink saved (B1) and the largest bundle stretch (B3) against the routes of `corners`. */
118
+ export interface PlanMeasures {
119
+ readonly routes: readonly Route[];
120
+ readonly b1: number;
121
+ readonly b3max: number;
122
+ }
123
+
124
+ /** This function routes a plan with `follow`, and gives B1 and the largest B3 of the routes. The base of B3 is the router `corners`. */
125
+ export function planMeasures(graph: Graph, plan: HighwayPlan): PlanMeasures {
126
+ const routes = follow(graph, plan).routes;
127
+ const corners = asRouter(getStrategy(STRATEGIES, "router", "corners")).route(graph).routes;
128
+ return { routes, b1: inkSaved(routes), b3max: maxStretch(routes, corners) };
129
+ }
@@ -0,0 +1,3 @@
1
+ // The activation test of a screen (ACTIVATION): did the idea of a candidate run on the input?
2
+ // `outputHash`, `outputGroups` and `exercised` come from `@mark1russell7/layout-contract` (PORTFOLIO-FIT). `docs/PROMOTION.md` gives the rules.
3
+ export { exercised, outputGroups, outputHash, type ExerciseEvidence, type HashOptions, type OutputGroup } from "@mark1russell7/layout-contract";
@@ -0,0 +1,140 @@
1
+ import { quantile } from "../measures/stats.ts";
2
+ import { GAP_BASELINES, type ExactOptimum, type GapRule } from "./gap.ts";
3
+
4
+ /**
5
+ * The interval of the optimum B1 of one solve (EXACT-BOUNDS). The low end is the B1 of the best solution of HiGHS.
6
+ * The high end comes from the dual bound. For a proven optimum, the two ends are equal.
7
+ */
8
+ export interface OptimumInterval {
9
+ readonly lo: number;
10
+ readonly hi: number;
11
+ readonly proven: boolean;
12
+ }
13
+
14
+ /**
15
+ * This function gives the interval of the optimum B1 of a solve, or `undefined` without a solution.
16
+ * The high end is `1 - (1 - b1) × bound / objective`. It supposes that the ink of the optimum is at least `bound / objective` times the ink of the best solution,
17
+ * with routes of the same length. The objective of the model is the ink plus a small cost of the length, so the ratio is a ratio of the ink.
18
+ * Without a positive bound, the high end is 1.
19
+ */
20
+ export function optimumInterval(o: ExactOptimum): OptimumInterval | undefined {
21
+ if (o.b1 === null || o.objective === null) return undefined;
22
+ if (o.proven) return { lo: o.b1, hi: o.b1, proven: true };
23
+ const ratio = o.bound !== null && o.bound > 0 && o.objective > 0 ? Math.min(1, o.bound / o.objective) : 0;
24
+ return { lo: o.b1, hi: Math.min(1, Math.max(o.b1, 1 - (1 - o.b1) * ratio)), proven: false };
25
+ }
26
+
27
+ /** The interval of the gap closed of one run against an interval of the optimum. */
28
+ export interface GapInterval {
29
+ readonly lo: number;
30
+ readonly hi: number;
31
+ readonly mid: number;
32
+ readonly width: number;
33
+ }
34
+
35
+ /**
36
+ * This function gives the interval of the gap closed `(b1 - base) / (optimum - base)` over the interval of the optimum.
37
+ * For a run above the baseline, the high end of the optimum gives the low end of the gap closed. The low end of the optimum gives the high end.
38
+ * It gives `undefined` when the low end of the optimum is at the baseline or below it, because then the gap closed has no limit.
39
+ */
40
+ export function gapInterval(b1: number, base: number, optimum: { readonly lo: number; readonly hi: number }): GapInterval | undefined {
41
+ const dLo = optimum.lo - base;
42
+ const dHi = optimum.hi - base;
43
+ if (!(dLo > 1e-9)) return undefined;
44
+ const a = (b1 - base) / dHi;
45
+ const b = (b1 - base) / dLo;
46
+ const lo = Math.min(a, b);
47
+ const hi = Math.max(a, b);
48
+ return { lo, hi, mid: (lo + hi) / 2, width: hi - lo };
49
+ }
50
+
51
+ /** The values of one run that the bounds read: B1, the largest stretch of the model and the flag of mixed collections. */
52
+ export interface BoundsRun {
53
+ readonly b1: number;
54
+ readonly stretch: number;
55
+ readonly mixed: boolean;
56
+ }
57
+
58
+ /** The gap closed of one strategy at one size, one rule and one cap, over the inputs. */
59
+ export interface BoundsRow {
60
+ readonly strategy: string;
61
+ /** The number of inputs, and the number of inputs where the run has a value. */
62
+ readonly inputs: number;
63
+ readonly values: number;
64
+ /** The medians over the inputs with a value: the midpoint, the low end, the high end and the width of the interval. */
65
+ readonly mid: number;
66
+ readonly lo: number;
67
+ readonly hi: number;
68
+ readonly width: number;
69
+ }
70
+
71
+ /** The gap closed of each strategy at one size, one rule and one cap, sorted by the median of the midpoint. */
72
+ export interface BoundsCell {
73
+ readonly nodes: number;
74
+ readonly rule: GapRule;
75
+ readonly cap: number;
76
+ /** The counts of the solves: the inputs, the proven optima, the solves with a bound only, and the solves without a solution. */
77
+ readonly inputs: number;
78
+ readonly proven: number;
79
+ readonly bounded: number;
80
+ readonly none: number;
81
+ /** The median width of the interval of the optimum B1, over the solves with a solution. */
82
+ readonly optimumWidth: number;
83
+ readonly rows: readonly BoundsRow[];
84
+ }
85
+
86
+ /**
87
+ * This function gives the gap closed of each strategy for each size, rule and cap of the optima.
88
+ * A run has a value when it obeys the rule and when its stretch is at or below the cap. The solve must also have a solution.
89
+ * The rule `collection` refuses a run with a shared segment that holds edges of two collections.
90
+ * The baseline of each rule is the run of `GAP_BASELINES` on the same input, with the prefix `hw:` for a highway strategy.
91
+ * The rows sort by the median of the midpoint, from high to low. A strategy with a value on fewer than half of the inputs comes after the others.
92
+ */
93
+ export function boundsCells(runs: ReadonlyMap<string, ReadonlyMap<string, BoundsRun>>, optima: readonly ExactOptimum[]): BoundsCell[] {
94
+ const keys = [...new Set(optima.map((o) => `${o.nodes}|${o.rule}|${o.cap}`))];
95
+ const cells: BoundsCell[] = [];
96
+ const baseName = (rule: GapRule): string => (rule === "all" ? `hw:${GAP_BASELINES.all}` : GAP_BASELINES.collection);
97
+ for (const key of keys) {
98
+ const [n, rule, c] = key.split("|") as [string, GapRule, string];
99
+ const nodes = Number(n);
100
+ const cap = Number(c);
101
+ const mine = optima.filter((o) => o.nodes === nodes && o.rule === rule && o.cap === cap && runs.has(o.input));
102
+ const intervals = new Map(mine.flatMap((o) => (optimumInterval(o) ? [[o.input, optimumInterval(o)!] as const] : [])));
103
+ const names = [...new Set(mine.flatMap((o) => [...runs.get(o.input)!.keys()]))].sort();
104
+ const rows: BoundsRow[] = names.map((strategy) => {
105
+ const vs: GapInterval[] = [];
106
+ for (const o of mine) {
107
+ const row = runs.get(o.input)!;
108
+ const r = row.get(strategy);
109
+ const base = row.get(baseName(rule));
110
+ const opt = intervals.get(o.input);
111
+ if (r === undefined || base === undefined || opt === undefined) continue;
112
+ if ((rule === "collection" && r.mixed) || r.stretch > cap + 1e-9) continue;
113
+ const g = gapInterval(r.b1, base.b1, opt);
114
+ if (g !== undefined) vs.push(g);
115
+ }
116
+ const med = (f: (g: GapInterval) => number): number => quantile(vs.map(f), 0.5);
117
+ return { strategy, inputs: mine.length, values: vs.length, mid: med((g) => g.mid), lo: med((g) => g.lo), hi: med((g) => g.hi), width: med((g) => g.width) };
118
+ });
119
+ const few = (r: BoundsRow): number => (2 * r.values < r.inputs || r.values === 0 ? 1 : 0);
120
+ rows.sort((a, b) => few(a) - few(b) || (Number.isNaN(b.mid) ? -Infinity : b.mid) - (Number.isNaN(a.mid) ? -Infinity : a.mid) || (a.strategy < b.strategy ? -1 : 1));
121
+ const widths = [...intervals.values()].map((i) => i.hi - i.lo);
122
+ cells.push({
123
+ nodes,
124
+ rule,
125
+ cap,
126
+ inputs: mine.length,
127
+ proven: mine.filter((o) => o.proven).length,
128
+ bounded: mine.filter((o) => !o.proven && intervals.has(o.input)).length,
129
+ none: mine.filter((o) => !intervals.has(o.input)).length,
130
+ optimumWidth: quantile(widths, 0.5),
131
+ rows,
132
+ });
133
+ }
134
+ return cells.sort((a, b) => a.nodes - b.nodes || (a.rule < b.rule ? -1 : a.rule > b.rule ? 1 : 0) || a.cap - b.cap);
135
+ }
136
+
137
+ /** This function gives the strategies that rank in a cell: the first `k` rows with a value on at least half of the inputs. */
138
+ export function topOf(cell: BoundsCell, k = 5): BoundsRow[] {
139
+ return cell.rows.filter((r) => r.values > 0 && 2 * r.values >= r.inputs).slice(0, k);
140
+ }
@@ -0,0 +1,26 @@
1
+ // The compare of one measure (WIN-RES). `compareMeasure` and `minEffectOf` come from `@mark1russell7/layout-contract` (PORTFOLIO-FIT).
2
+ // `docs/PROMOTION.md` gives the rule. The test `compare.test.ts` checks that the functions agree with the note.
3
+ import type { MeasureSpec, Verdict } from "@mark1russell7/layout-contract";
4
+
5
+ export { compareMeasure, minEffectOf } from "@mark1russell7/layout-contract";
6
+
7
+ /**
8
+ * The smallest change of a measure that counts as better or worse (WIN-RES).
9
+ * A number is an absolute change. `{ rel }` is a share of the larger of the two absolute values.
10
+ * The threshold is not less than `RECORD_EPS` at any time.
11
+ */
12
+ export type MinEffect = NonNullable<MeasureSpec["minEffect"]>;
13
+
14
+ /** The result of a compare of a candidate with a baseline on one measure. A tie is not a win. It is the `Verdict` of the contract. */
15
+ export type MeasureCompare = Verdict;
16
+
17
+ /** The part of a measure that a compare reads: the better side and the minimum effect. */
18
+ export type CompareSpec = Pick<MeasureSpec, "direction" | "minEffect">;
19
+
20
+ /**
21
+ * This function applies the wave rule to the compares of one board, for example of `B1` and of the `B3` q95.
22
+ * The board is a win only when no compare is `worse` and one or more compares are `better`. Thus a board of ties is not a win.
23
+ */
24
+ export function boardWins(compares: readonly MeasureCompare[]): boolean {
25
+ return !compares.includes("worse") && compares.includes("better");
26
+ }
@@ -0,0 +1,100 @@
1
+ import { makeFixture, type FixtureKind } from "../../fixtures.ts";
2
+ import type { Graph } from "../../model.ts";
3
+ import type { LayoutCell, LayoutGenerator } from "./forms.ts";
4
+ import type { ScreenFamily, ScreenSet } from "./set.ts";
5
+
6
+ /** The board classes of the small boards with a known optimum (MEAS2). */
7
+ export type ExactClass = "uniform" | "clustered" | "hubs" | "fanout";
8
+
9
+ /** The board classes of `EXACT_V1`, in the order of the set. */
10
+ export const EXACT_CLASSES: readonly ExactClass[] = ["uniform", "clustered", "hubs", "fanout"];
11
+
12
+ /** The edges of each node of a small board with a known optimum. */
13
+ export const EXACT_EDGES_PER_NODE = 1.5;
14
+
15
+ /** The fixture kind of each class. The class `fanout` is the kind `collections`: each cluster is one collection, with fan-outs. */
16
+ const KINDS: Readonly<Record<ExactClass, FixtureKind>> = { uniform: "uniform", clustered: "clustered", hubs: "hubs", fanout: "collections" };
17
+
18
+ /** The cell of each class in the taxonomy of the corpus. */
19
+ const CELLS: Readonly<Record<ExactClass, LayoutCell>> = {
20
+ uniform: { edgeShape: "random" },
21
+ clustered: { edgeShape: "clustered" },
22
+ hubs: { edgeShape: "hubChain" },
23
+ fanout: { edgeShape: "bundled" },
24
+ };
25
+
26
+ /** This function gives the seed of the fixture of the member `k` of a class: `100 × (the index of the class + 1) + k`. */
27
+ export function exactSeed(cls: ExactClass, k: number): number {
28
+ return 100 * (EXACT_CLASSES.indexOf(cls) + 1) + k;
29
+ }
30
+
31
+ /** This function gives the generator of a small board: the generator `fixture` with the kind of the class, `nodes` and the seed of `exactSeed`. */
32
+ export function exactGenerator(cls: ExactClass, nodes: number, k: number): LayoutGenerator {
33
+ return { name: "fixture", version: 1, params: { kind: KINDS[cls], nodes, edgesPerNode: EXACT_EDGES_PER_NODE }, seed: exactSeed(cls, k) };
34
+ }
35
+
36
+ /** This function makes the small board of a class with `nodes` nodes and the member `k`. The same values always give the same graph. */
37
+ export function exactBoard(cls: ExactClass, nodes: number, k: number): Graph {
38
+ return makeFixture({ name: cls, kind: KINDS[cls], nodes, edgesPerNode: EXACT_EDGES_PER_NODE, seed: exactSeed(cls, k) });
39
+ }
40
+
41
+ /**
42
+ * This function gives a family of small boards of one class. The member `k` has `sizes[k mod sizes.length]` nodes.
43
+ * The count of the cell is `10`, because each board has fewer than 100 nodes.
44
+ */
45
+ export function exactFamily(cls: ExactClass, sizes: readonly number[], seeds: number): ScreenFamily {
46
+ const nodes = (k: number): number => sizes[k % sizes.length]!;
47
+ return {
48
+ name: cls,
49
+ shows: `Small boards of the class ${cls} with ${sizes.join(", ")} nodes and a known optimum.`,
50
+ seeds,
51
+ cell: { containerSize: "freeAspect", containerShape: "rect", obstacles: "none", mode: "offline", groups: "none", count: "10", edgeDensity: "medium", ports: "free", ...CELLS[cls] },
52
+ make: (k) => exactBoard(cls, nodes(k), k),
53
+ generator: (k) => exactGenerator(cls, nodes(k), k),
54
+ };
55
+ }
56
+
57
+ /**
58
+ * The node counts of the boards of `EXACT_V1`. The member `k` of each class has `EXACT_V1_SIZES[k mod length]` nodes.
59
+ * The size ladder of MEAS2 (CI run 40613531631) proved each optimum at 10 nodes within 300 s, for each class, both rules and the cap 1.5.
60
+ * At 14 nodes, 4 of 24 solves stopped at the time limit.
61
+ */
62
+ export const EXACT_V1_SIZES: readonly number[] = [10];
63
+
64
+ /** The number of seeds of each class of `EXACT_V1`. It is `cellMin` of the promotion rule. */
65
+ export const EXACT_V1_SEEDS = 10;
66
+
67
+ /**
68
+ * The screen set `exact-v1` (MEAS2): small boards of made-up data with a known optimum of the exact model.
69
+ * It has 4 classes of 10 seeds. The file `optima-exact-v1.json` of `bench/exact` keeps the optimum of each input, each rule and each cap.
70
+ * This set does not change. The tests compare it with the files in `corpus/exact-v1`.
71
+ */
72
+ export const EXACT_V1: ScreenSet = {
73
+ id: "exact-v1",
74
+ families: EXACT_CLASSES.map((cls) => exactFamily(cls, EXACT_V1_SIZES, EXACT_V1_SEEDS)),
75
+ };
76
+
77
+ /** The node counts of the boards of `EXACT_V1_BOUNDS`. HiGHS proves few optima at these sizes within 300 s, so the set keeps bounds (EXACT-BOUNDS). */
78
+ export const EXACT_V1_BOUNDS_SIZES: readonly number[] = [18, 24, 40];
79
+
80
+ /** This function gives the name of the family of a class at a size in `EXACT_V1_BOUNDS`: `<class>-n<nodes>`. */
81
+ export function boundsFamilyName(cls: ExactClass, nodes: number): string {
82
+ return `${cls}-n${nodes}`;
83
+ }
84
+
85
+ /**
86
+ * The screen set `exact-v1-bounds` (EXACT-BOUNDS): the boards of the classes of `exact-v1` at 18, 24 and 40 nodes, with 10 seeds each.
87
+ * Each family is one class at one size. The member `k` uses the seed `exactSeed(class, k)`, as in `exact-v1`.
88
+ * The file `optima-exact-v1-bounds.json` of `bench/exact` keeps the best bound and the best solution of each input, each rule and each cap.
89
+ * This set does not change. The tests compare it with the files in `corpus/exact-v1-bounds`.
90
+ */
91
+ export const EXACT_V1_BOUNDS: ScreenSet = {
92
+ id: "exact-v1-bounds",
93
+ families: EXACT_V1_BOUNDS_SIZES.flatMap((n) =>
94
+ EXACT_CLASSES.map((cls) => ({
95
+ ...exactFamily(cls, [n], EXACT_V1_SEEDS),
96
+ name: boundsFamilyName(cls, n),
97
+ shows: `Boards of the class ${cls} with ${n} nodes, with a bound of the optimum.`,
98
+ })),
99
+ ),
100
+ };