@mark1russell7/packing 0.2.6 → 0.2.8

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/packing",
4
- "version": "0.2.6",
4
+ "version": "0.2.8",
5
5
  "description": "Rectangle packing: shelf, guillotine and skyline strategies, push cascades and measures of a layout.",
6
6
  "keywords": [
7
7
  "rectangle-packing",
package/src/index.ts CHANGED
@@ -79,8 +79,29 @@ export {
79
79
  type PushStrategyOptions,
80
80
  } from "./adapters.ts";
81
81
  export { fromRects, toRects } from "./convert.ts";
82
- export { packWith, strategies, strategy } from "./registry.ts";
82
+ export { composites, packWith, strategies, strategy } from "./registry.ts";
83
83
  export { hardBreaks, packPortfolio, PORTFOLIO_LEVELS, PORTFOLIO_STAGE, type PackPortfolioOptions, type PortfolioEntry } from "./portfolio.ts";
84
+ export {
85
+ cellKey,
86
+ PORTFOLIO_FRAME_MS,
87
+ portfolioCell,
88
+ portfolioDefaultMembers,
89
+ portfolioMembers,
90
+ portfolioSetOf,
91
+ spreadBand,
92
+ spreadOf,
93
+ SPREAD_THRESHOLDS,
94
+ THIN_INPUTS,
95
+ type PortfolioCell,
96
+ type PortfolioCellSets,
97
+ type PortfolioSetSource,
98
+ type PortfolioSets,
99
+ type PortfolioSize,
100
+ type PortfolioTrainScore,
101
+ type SpreadBand,
102
+ } from "./portfolio-cell.ts";
103
+ export { PORTFOLIO_SETS } from "./portfolio-sets.ts";
104
+ export { portfolioStrategy, type PortfolioStrategyOptions } from "./portfolio-strategy.ts";
84
105
  export type { Portfolio, PortfolioPick, PortfolioTrace, MemberTrace, SelectLevel } from "@mark1russell7/layout-contract";
85
106
  export { checkHard, type ContainerTest, type PackHard } from "./check.ts";
86
107
  export {
@@ -0,0 +1,165 @@
1
+ // The per-cell portfolio of roadmap item k20, part 2 (`docs/PACKER.md`, "The per-cell portfolio"). The cell of an input
2
+ // comes from the input itself. The table `PORTFOLIO_SETS` gives the members of each cell.
3
+
4
+ import type { PackInput } from "./contract.ts";
5
+ import { countCell, type CountScale } from "./layout-input.ts";
6
+ import { PORTFOLIO_SETS } from "./portfolio-sets.ts";
7
+ import type { PortfolioEntry } from "./portfolio.ts";
8
+ import { cellOf, type DimensionValue } from "./strategy.ts";
9
+
10
+ /** The band of the size spread of the items of an input. */
11
+ export type SpreadBand = "identical" | "uniform" | "heavyTail";
12
+
13
+ /** The cell of an input for the per-cell portfolio: the container shape, the band of the item count and the band of the size spread. */
14
+ export interface PortfolioCell {
15
+ shape: DimensionValue<"containerShape">;
16
+ /** The band of the item count, as `countCell` gives it: `1`, `10`, `100`, `1k` or `10k`. */
17
+ count: CountScale;
18
+ spread: SpreadBand;
19
+ }
20
+
21
+ /**
22
+ * The thresholds of the spread bands. The statistic is the standard deviation of the natural log of the linear size
23
+ * `sqrt(w * h)` of each item.
24
+ *
25
+ * - Below `identical`, the band is `identical`.
26
+ * - Below `uniform`, the band is `uniform`.
27
+ * - At `uniform` or above, the band is `heavyTail`.
28
+ *
29
+ * On the seeds 1 to 7 of the generators of `pack-screen-v1`, the uniform sizes give 0.258 at most. The heavy-tail sizes give 0.278 at least.
30
+ */
31
+ export const SPREAD_THRESHOLDS: Readonly<{ identical: number; uniform: number }> = Object.freeze({ identical: 0.02, uniform: 0.27 });
32
+
33
+ /** A cell with fewer inputs than this count in the training records is thin. It uses the set of its container shape. */
34
+ export const THIN_INPUTS = 12;
35
+
36
+ /**
37
+ * The frame budget of the default member count, in milliseconds. The default count of a cell is the largest prefix of its
38
+ * set of 3 that fits this budget. The prefix fits when its median summed member time on the CI records is this value or less.
39
+ * The count is 1 or more at all times.
40
+ * The default output thus does not depend on the clock: a time limit is only the opt-in option `budgetMs`.
41
+ */
42
+ export const PORTFOLIO_FRAME_MS = 50;
43
+
44
+ /** This function gives the key of a cell: `<shape>/<count>/<spread>`, for example `rect/100/uniform`. */
45
+ export function cellKey(cell: PortfolioCell): string {
46
+ return `${cell.shape}/${cell.count}/${cell.spread}`;
47
+ }
48
+
49
+ /**
50
+ * This function gives the spread statistic of an input: the standard deviation of the natural log of `sqrt(w * h)` of each item.
51
+ * An item with no area does not count. Fewer than 2 items with an area give 0.
52
+ */
53
+ export function spreadOf(input: PackInput): number {
54
+ const logs: number[] = [];
55
+ for (const item of input.items) {
56
+ const area = item.w * item.h;
57
+ if (area > 0 && Number.isFinite(area)) logs.push(Math.log(area) / 2);
58
+ }
59
+ if (logs.length < 2) return 0;
60
+ const mean = logs.reduce((a, b) => a + b, 0) / logs.length;
61
+ return Math.sqrt(logs.reduce((a, b) => a + (b - mean) ** 2, 0) / logs.length);
62
+ }
63
+
64
+ /** This function gives the band of a spread statistic by `SPREAD_THRESHOLDS`. */
65
+ export function spreadBand(spread: number): SpreadBand {
66
+ if (spread < SPREAD_THRESHOLDS.identical) return "identical";
67
+ return spread < SPREAD_THRESHOLDS.uniform ? "uniform" : "heavyTail";
68
+ }
69
+
70
+ /**
71
+ * This function gives the cell of an input for the per-cell portfolio. The function reads only the input, not a corpus id.
72
+ *
73
+ * - `shape` is the `containerShape` of `cellOf`.
74
+ * - `count` is `countCell` of the count of the items.
75
+ * - `spread` is `spreadBand` of `spreadOf`.
76
+ */
77
+ export function portfolioCell(input: PackInput): PortfolioCell {
78
+ return { shape: cellOf(input).containerShape, count: countCell(input.items.length), spread: spreadBand(spreadOf(input)) };
79
+ }
80
+
81
+ /** The sizes of the sets of the table. */
82
+ export type PortfolioSize = 1 | 2 | 3;
83
+
84
+ /** The score of a set on its training records. The regrets are means against the oracle of the pool. */
85
+ export interface PortfolioTrainScore {
86
+ inputs: number;
87
+ /** The mean of the hard breaks of the winner of the set less those of the oracle */
88
+ hardRegret: number;
89
+ /** The mean of `pack.overflow.excessArea` of the winner of the set less that of the oracle */
90
+ excessAreaRegret: number;
91
+ /** The mean of `pack.fill` of the oracle less that of the winner of the set */
92
+ fillRegret: number;
93
+ /** The inputs where the winner of the set is in the last kept list of the oracle */
94
+ wins: number;
95
+ winShare: number;
96
+ /** The mean of the summed times of the members on an input, in milliseconds */
97
+ msMean: number;
98
+ }
99
+
100
+ /** The sets of one cell of the table, by size, with their training scores. */
101
+ export interface PortfolioCellSets {
102
+ /** The inputs of the cell in the training records */
103
+ inputs: number;
104
+ /** The value `true` when `inputs` is less than `THIN_INPUTS`. A thin cell has no sets. It uses the sets of its shape. */
105
+ thin: boolean;
106
+ /** The ids of the members of each set, in the order of the pick. A thin cell has none. */
107
+ sets?: Readonly<Record<`${PortfolioSize}`, readonly string[]>>;
108
+ /** The training score of each set */
109
+ train?: Readonly<Record<`${PortfolioSize}`, PortfolioTrainScore>>;
110
+ /** The median over the inputs of the summed member times of each prefix of the set of 3, in milliseconds. A missing record counts 0. */
111
+ prefixMedianMs?: Readonly<Record<`${PortfolioSize}`, number>>;
112
+ /** The default member count: the largest prefix of the set of 3 with `prefixMedianMs` of `PORTFOLIO_FRAME_MS` or less, and 1 at least */
113
+ defaultMembers?: PortfolioSize;
114
+ }
115
+
116
+ /** The table of the per-cell portfolio. `bench/choose-sets.ts` generates it from the records of one run. */
117
+ export interface PortfolioSets {
118
+ /** The run of the records: the corpus, the commit of the code and the id of the run of `bench.yml`. It also has the frame budget of the default counts. */
119
+ source: Readonly<{ corpus: string; run: string; commit: string; inputs: number; frameMs: number }>;
120
+ /** The member of each id: a strategy of the registry with some options */
121
+ entries: Readonly<Record<string, PortfolioEntry>>;
122
+ /** The sets of all the inputs. They are the fixed default of an input of an unknown shape. */
123
+ all: PortfolioCellSets;
124
+ /** The sets of each container shape. They are the fallback of a thin cell and of an unknown cell. */
125
+ shapes: Readonly<Record<string, PortfolioCellSets>>;
126
+ /** The sets of each cell, by `cellKey` */
127
+ cells: Readonly<Record<string, PortfolioCellSets>>;
128
+ }
129
+
130
+ /** The source of the members of an input in the table. */
131
+ export type PortfolioSetSource = "cell" | "shape" | "default";
132
+
133
+ /** This function gives the sets of an input from a table: the sets of its cell, or of its shape, or of all the inputs. */
134
+ export function portfolioSetOf(input: PackInput, table: PortfolioSets = PORTFOLIO_SETS): { key: string; source: PortfolioSetSource; sets: PortfolioCellSets } {
135
+ const cell = portfolioCell(input);
136
+ const key = cellKey(cell);
137
+ const own = table.cells[key];
138
+ if (own?.sets !== undefined) return { key, source: "cell", sets: own };
139
+ const shape = table.shapes[cell.shape];
140
+ if (shape?.sets !== undefined) return { key, source: "shape", sets: shape };
141
+ return { key, source: "default", sets: table.all };
142
+ }
143
+
144
+ /**
145
+ * This function gives the `k` members of an input for `packPortfolio`. The members are the set of the cell of the input.
146
+ * A thin cell or an unknown cell takes the set of its shape, or the set of all the inputs. The function throws a `RangeError` for a `k` other than 1, 2 or 3.
147
+ */
148
+ export function portfolioMembers(input: PackInput, k: number, table: PortfolioSets = PORTFOLIO_SETS): PortfolioEntry[] {
149
+ if (k !== 1 && k !== 2 && k !== 3) throw new RangeError(`The size of a portfolio set is 1, 2 or 3, not ${k}.`);
150
+ const ids = portfolioSetOf(input, table).sets.sets![`${k}`];
151
+ return ids.map((id) => {
152
+ const e = table.entries[id];
153
+ if (e === undefined) throw new RangeError(`The table of the portfolio sets has no entry "${id}".`);
154
+ return { ...e };
155
+ });
156
+ }
157
+
158
+ /**
159
+ * This function gives the default member count of an input: `defaultMembers` of the sets of its cell, its shape or all the inputs.
160
+ * With a `k`, the count is not more than `k`. A table without the count gives `k`, or 3 without a `k`.
161
+ */
162
+ export function portfolioDefaultMembers(input: PackInput, k?: number, table: PortfolioSets = PORTFOLIO_SETS): number {
163
+ const count = portfolioSetOf(input, table).sets.defaultMembers ?? 3;
164
+ return k === undefined ? count : Math.min(count, k);
165
+ }