@mark1russell7/packing 0.1.7 → 0.2.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/src/generators.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // container, then the obstacles, then the boxes. Each step has its own random numbers: `mulberry32` of
3
3
  // `fnv1a32("<seed>:<step>")`. Thus a packing input and a routing input with the same seed get the same obstacles.
4
4
 
5
- import type { Container, Item, Obstacle, Region } from "./contract.ts";
5
+ import type { Item, LayoutContainer, Obstacle, Region } from "./contract.ts";
6
6
  import {
7
7
  countCell,
8
8
  fnv1a32,
@@ -10,7 +10,7 @@ import {
10
10
  round3,
11
11
  type CorpusCell,
12
12
  type GeneratorParams,
13
- type LayoutInput,
13
+ type GeneratedInput,
14
14
  type ObstacleLayout,
15
15
  type SizeDistribution,
16
16
  } from "./layout-input.ts";
@@ -36,11 +36,11 @@ export interface CorpusGenerator<P extends GeneratorParams = GeneratorParams> {
36
36
  /** This method gives the cell of each input of the parameters. The cell does not depend on the seed. */
37
37
  cell(params?: Partial<P>): CorpusCell;
38
38
  /** This method gives the input file of a seed. The `id` is `<set>/<family>/s<seed>`. */
39
- generate(seed: number, params?: Partial<P>, set?: string): LayoutInput;
39
+ generate(seed: number, params?: Partial<P>, set?: string): GeneratedInput;
40
40
  }
41
41
 
42
42
  /** The parts of a file that a generator makes. */
43
- type Parts = Pick<LayoutInput, "container" | "boxes"> & { obstacles: Obstacle[] };
43
+ type Parts = Pick<GeneratedInput, "container" | "boxes"> & { obstacles: Obstacle[] };
44
44
 
45
45
  /** The data of a generator, before `defineGenerator` adds the common steps. */
46
46
  interface GeneratorSpec<P extends GeneratorParams> {
@@ -153,12 +153,12 @@ function defineGenerator<P extends GeneratorParams>(spec: GeneratorSpec<P>): Cor
153
153
  values: spec.values,
154
154
  withDefaults,
155
155
  cell: (params?: Partial<P>): CorpusCell => spec.cell(withDefaults(params)),
156
- generate(seed: number, params?: Partial<P>, set: string = spec.set): LayoutInput {
156
+ generate(seed: number, params?: Partial<P>, set: string = spec.set): GeneratedInput {
157
157
  if (!Number.isInteger(seed) || seed < 0) throw new RangeError(`The seed is a whole number from 0. It is ${seed}.`);
158
158
  const full = withDefaults(params);
159
159
  const family = spec.family(full);
160
160
  const { container, obstacles, boxes } = spec.build(seed, full);
161
- const file: Omit<LayoutInput, "hash"> = roundNumbers({
161
+ const file: Omit<GeneratedInput, "hash"> = roundNumbers({
162
162
  schema: "layout-input/1" as const,
163
163
  id: `${set}/${family}/s${seed}`,
164
164
  set,
@@ -195,7 +195,7 @@ export const rectScattered: CorpusGenerator<RectScatteredParams> = defineGenerat
195
195
  let r = stepRandom(seed, "container");
196
196
  const w = int(r, 600, 1000);
197
197
  const h = int(r, 400, 800);
198
- const container: Container = { w, h, fixed: "size" };
198
+ const container: LayoutContainer = { w, h, fixed: "size" };
199
199
 
200
200
  r = stepRandom(seed, "obstacles");
201
201
  const n = int(r, 3, 8);
@@ -232,8 +232,8 @@ const OBSTACLE_LAYOUTS = ["none", "center", "scattered", "wall", "corridors", "t
232
232
  const SIZE_DISTRIBUTIONS = ["identical", "nearIdentical", "uniform", "heavyTail", "bimodal", "extremeAspect"] as const;
233
233
 
234
234
  /** This function gives the container of a size value for the nominal rectangle `w` by `h`, with an optional shape. */
235
- function containerOf(size: DimensionValue<"containerSize">, w: number, h: number, shape?: Region): Container {
236
- const containers: Record<DimensionValue<"containerSize">, Container> = {
235
+ function containerOf(size: DimensionValue<"containerSize">, w: number, h: number, shape?: Region): LayoutContainer {
236
+ const containers: Record<DimensionValue<"containerSize">, LayoutContainer> = {
237
237
  fixed: { w, h, fixed: "size" },
238
238
  freeHeight: { w, fixed: "width" },
239
239
  freeWidth: { h, fixed: "height" },
@@ -241,8 +241,7 @@ function containerOf(size: DimensionValue<"containerSize">, w: number, h: number
241
241
  freeArea: { fixed: "none", aspectWeight: 0 },
242
242
  };
243
243
  const container = containers[size];
244
- if (shape !== undefined) container.shape = shape;
245
- return container;
244
+ return shape === undefined ? container : { ...container, shape };
246
245
  }
247
246
 
248
247
  /** This function gives the 4 corners of a rectangle, clockwise from the top left. */
@@ -625,7 +624,7 @@ export const edgeCases: CorpusGenerator<EdgeCaseParams> = defineGenerator<EdgeCa
625
624
  w = int(r, 600, 1000);
626
625
  h = int(r, 400, 800);
627
626
  }
628
- const container: Container = { w, h, fixed: "size" };
627
+ const container: LayoutContainer = { w, h, fixed: "size" };
629
628
 
630
629
  r = stepRandom(seed, "obstacles");
631
630
  const obstacles: Obstacle[] = [];
package/src/index.ts CHANGED
@@ -29,13 +29,14 @@ export {
29
29
  DEFAULT_TOLERANCE,
30
30
  EPS,
31
31
  RECORD_EPS,
32
+ checkInput,
32
33
  marginOf,
33
34
  turnItem,
34
35
  turnPoint,
35
36
  turnPort,
36
37
  turnRegion,
37
- type Container,
38
38
  type Item,
39
+ type LayoutContainer,
39
40
  type Margin,
40
41
  type Obstacle,
41
42
  type PackInput,
@@ -43,6 +44,8 @@ export {
43
44
  type Placement,
44
45
  type Port,
45
46
  type Region,
47
+ type Side,
48
+ type Sides,
46
49
  type StageTrace,
47
50
  type Tolerance,
48
51
  } from "./contract.ts";
@@ -65,7 +68,7 @@ export {
65
68
  export { logSweep, push, rows, shelf, skyline, type LayoutStrategyOptions, type PushStrategyOptions } from "./adapters.ts";
66
69
  export { fromRects, toRects } from "./convert.ts";
67
70
  export { packWith, strategies, strategy } from "./registry.ts";
68
- export { checkHard, type HardBreaks } from "./check.ts";
71
+ export { checkHard, type PackHard } from "./check.ts";
69
72
  export {
70
73
  canonicalJson,
71
74
  countCell,
@@ -75,9 +78,10 @@ export {
75
78
  toPackInput,
76
79
  type CorpusCell,
77
80
  type CountScale,
78
- type Edge,
81
+ type GeneratedInput,
79
82
  type GeneratorParams,
80
83
  type GeneratorStamp,
84
+ type LayoutEdge,
81
85
  type LayoutInput,
82
86
  type ObstacleLayout,
83
87
  type SizeDistribution,
@@ -1,52 +1,39 @@
1
1
  // The input file `layout-input/1` of `docs/FORMAT.md`: its types, its numbers, its canonical form and its hash.
2
2
 
3
- import type { Container, Item, Obstacle, PackInput } from "./contract.ts";
3
+ import type { LayoutContainer, LayoutEdge, LayoutGenerator, LayoutInput } from "@mark1russell7/layout-contract";
4
+ import type { PackInput } from "./contract.ts";
4
5
  import { sha256Hex, utf8 } from "./sha256.ts";
5
6
  import type { Cell } from "./strategy.ts";
6
7
 
7
- /** An edge between two boxes. The router reads it, and the packer ignores it. */
8
- export interface Edge {
9
- id: string;
10
- source: string;
11
- target: string;
12
- sourcePort?: string;
13
- targetPort?: string;
14
- /** The free data of the edge. The packer does not read it. */
15
- data?: Readonly<Record<string, unknown>>;
16
- }
8
+ export type { LayoutEdge, LayoutInput } from "@mark1russell7/layout-contract";
17
9
 
18
10
  /** The parameters of a generator: a name and a string or a number for each parameter. */
19
11
  export type GeneratorParams = Record<string, string | number>;
20
12
 
21
- /** The stamp of a generated input: the generator, its version, the full parameters and the seed. */
22
- export interface GeneratorStamp {
23
- name: string;
24
- version: number;
25
- params: GeneratorParams;
26
- seed: number;
13
+ /**
14
+ * The stamp of a generated input: the generator, its version, the full parameters and the seed.
15
+ * It is `LayoutGenerator` of the contract with a string or a number for each parameter.
16
+ */
17
+ export interface GeneratorStamp extends LayoutGenerator {
18
+ readonly params: Readonly<GeneratorParams>;
27
19
  }
28
20
 
29
- /** One input file `layout-input/1`. `docs/FORMAT.md` gives each field. */
30
- export interface LayoutInput {
31
- schema: "layout-input/1";
32
- id: string;
33
- set: string;
34
- family: string;
35
- seed: number;
21
+ /**
22
+ * A generated input file `layout-input/1`. It is a `LayoutInput` with each field that a generator writes.
23
+ * `docs/FORMAT.md` gives each field.
24
+ */
25
+ export interface GeneratedInput extends LayoutInput {
26
+ readonly set: string;
27
+ readonly family: string;
28
+ readonly seed: number;
36
29
  /** The origin: `made-up`, `published:<set>` or `known-best:<construction>`. */
37
- source: string;
30
+ readonly source: string;
38
31
  /** The generator of a generated input. A hand-made or published input has `null`. */
39
- generator: GeneratorStamp | null;
40
- container: Container;
41
- obstacles?: Obstacle[];
42
- gap?: number;
43
- boxes: Item[];
44
- edges: Edge[];
45
- base?: string;
46
- change?: string;
47
- step?: number;
32
+ readonly generator: GeneratorStamp | null;
33
+ readonly container: LayoutContainer;
34
+ readonly edges: readonly LayoutEdge[];
48
35
  /** The first 16 hex characters of the SHA-256 of the canonical form of the other fields. */
49
- hash: string;
36
+ readonly hash: string;
50
37
  }
51
38
 
52
39
  /** The layout of the obstacles (`docs/research/TAXONOMY.md`, dimension 3). */
@@ -98,9 +85,8 @@ export function canonicalJson(value: unknown): string {
98
85
  }
99
86
 
100
87
  /** This function gives the hash of an input file: the first 16 hex characters of the SHA-256 of its canonical form without `hash`. */
101
- export function hashInput(file: Omit<LayoutInput, "hash"> | LayoutInput): string {
102
- const rest: Partial<LayoutInput> = { ...file };
103
- delete rest.hash;
88
+ export function hashInput(file: LayoutInput): string {
89
+ const { hash: _hash, ...rest } = file;
104
90
  return sha256Hex(canonicalJson(rest)).slice(0, 16);
105
91
  }
106
92
 
@@ -113,10 +99,15 @@ export function countCell(count: number): CountScale {
113
99
  return "10k";
114
100
  }
115
101
 
116
- /** This function gives the packing input of a file. The boxes become the items, and the edges go. */
102
+ /**
103
+ * This function gives the packing input of a file. The boxes become the items, and the edges go.
104
+ * A file without a container gets `{ fixed: "none" }`, so the packer picks the size. It is `toPackInput` of the contract.
105
+ */
117
106
  export function toPackInput(file: Pick<LayoutInput, "container" | "boxes" | "obstacles" | "gap">): PackInput {
118
- const input: PackInput = { container: file.container, items: file.boxes };
119
- if (file.obstacles !== undefined) input.obstacles = file.obstacles;
120
- if (file.gap !== undefined) input.gap = file.gap;
121
- return input;
107
+ return {
108
+ container: file.container ?? { fixed: "none" },
109
+ items: file.boxes,
110
+ ...(file.obstacles === undefined ? {} : { obstacles: file.obstacles }),
111
+ ...(file.gap === undefined ? {} : { gap: file.gap }),
112
+ };
122
113
  }
package/src/measures.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  // The algebra of measures (roadmap item k8). `docs/MEASURES.md` defines each measure. This file computes them.
2
2
 
3
+ import type { MeasureSpec as ContractMeasureSpec, Direction } from "@mark1russell7/layout-contract";
3
4
  import { RECORD_EPS, type PackInput, type PackOutput } from "./contract.ts";
4
5
  import { aspectError } from "./free.ts";
5
6
  import { round3 } from "./layout-input.ts";
6
7
 
7
- /** The direction of a measure: `lower` when a lower value is better, `higher` when a higher value is better. */
8
- export type Direction = "lower" | "higher";
8
+ /** The direction of a measure comes from the contract: `lower` when a lower value is better, `higher` when a higher value is better. */
9
+ export type { Direction } from "@mark1russell7/layout-contract";
9
10
 
10
11
  /** The family of a measure. Each family has a section in `docs/MEASURES.md`. */
11
12
  export type MeasureFamily = "density" | "aspect" | "order" | "raggedness" | "overflow" | "stability" | "determinism" | "speed";
@@ -13,21 +14,17 @@ export type MeasureFamily = "density" | "aspect" | "order" | "raggedness" | "ove
13
14
  /** The id of a measure of the packer. The ids are those of `docs/FORMAT.md`, and `highways` uses the same ids. */
14
15
  export type MeasureId = "pack.area" | "pack.fill" | "pack.whitespace" | "pack.aspect" | "pack.aspectError" | "pack.order.inversions" | "pack.order.breaks" | "pack.raggedness" | "pack.overflow" | "pack.stability.moved" | "pack.stability.mean" | "pack.stability.q95" | "pack.deterministic" | "pack.search.widths" | "pack.search.ms";
15
16
 
16
- /** The facts of one measure: its family, its unit, its direction and its range. */
17
- export interface MeasureSpec {
18
- id: MeasureId;
19
- family: MeasureFamily;
20
- unit: string;
21
- direction: Direction;
22
- /** The lowest value. */
23
- min: number;
24
- /** The highest value. The value `Infinity` tells that the measure has no upper limit. */
25
- max: number;
17
+ /**
18
+ * The facts of one measure: its family, its unit, its direction and its range.
19
+ * It is `MeasureSpec` of the contract for a packer measure id, with a family.
20
+ */
21
+ export interface MeasureSpec extends ContractMeasureSpec<MeasureId> {
22
+ readonly family: MeasureFamily;
26
23
  /**
27
24
  * The floor is the best value of a guard measure. The floor rule of `docs/PROMOTION.md` of `highways` reads it. Each
28
25
  * guard of the packer has the floor 0. A measure without a floor has no floor rule.
29
26
  */
30
- floor?: number;
27
+ readonly floor?: number;
31
28
  /**
32
29
  * The tolerance is the absolute tolerance at the floor. A candidate within the tolerance ties with a default at the floor.
33
30
  *
@@ -36,7 +33,7 @@ export interface MeasureSpec {
36
33
  * - `pack.stability.mean` has 0.005 container widths, and `pack.stability.q95` has 0.01. The default often moves no item.
37
34
  * - `pack.stability.moved` has 0.02: 2% of the items that stay. On a small change, the default often moves no item.
38
35
  */
39
- tol?: number;
36
+ readonly tol?: number;
40
37
  }
41
38
 
42
39
  /** The measures of one layout, by id. `measure` gives them. The values are not rounded. */
@@ -175,7 +172,7 @@ export function readsBefore(a: Pick<Box, "x" | "y" | "h">, b: Pick<Box, "x" | "y
175
172
  /** This function sums one measure over the stages of a trace. A trace without the measure gives 0. */
176
173
  function traceSum(output: PackOutput, id: string): number {
177
174
  let sum = 0;
178
- for (const stage of output.trace) sum += stage.measures[id] ?? 0;
175
+ for (const stage of output.trace ?? []) sum += stage.measures?.[id] ?? 0;
179
176
  return sum;
180
177
  }
181
178
 
package/src/strategy.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Container, PackInput, PackOutput, Region } from "./contract.ts";
1
+ import type { LayoutContainer, PackInput, PackOutput, Region } from "./contract.ts";
2
2
 
3
3
  /**
4
4
  * For each dimension of the taxonomy, the values that a strategy supports. `docs/research/TAXONOMY.md` gives the values.
@@ -108,7 +108,7 @@ export function createContext(seed = 0, more: Omit<PackContext, "seed" | "rng">
108
108
  }
109
109
 
110
110
  /** This function tells if a polygon ring is convex. A ring with fewer than 3 points is convex. */
111
- function isConvex(points: readonly [number, number][]): boolean {
111
+ function isConvex(points: readonly (readonly [number, number])[]): boolean {
112
112
  let sign = 0;
113
113
  const n = points.length;
114
114
  for (let i = 0; i < n; i++) {
@@ -163,7 +163,7 @@ export function cellOf(input: PackInput): Cell {
163
163
  const { container, items } = input;
164
164
  const obstacles = input.obstacles ?? [];
165
165
 
166
- const sizes: Record<Container["fixed"], DimensionValue<"containerSize">> = {
166
+ const sizes: Record<LayoutContainer["fixed"], DimensionValue<"containerSize">> = {
167
167
  size: "fixed",
168
168
  width: "freeHeight",
169
169
  height: "freeWidth",