@mark1russell7/layout-contract 0.2.13 → 0.2.14

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/build/index.js CHANGED
@@ -266,7 +266,27 @@ function toPackInput(input) {
266
266
  container: input.container ?? { fixed: "none" },
267
267
  items: input.boxes,
268
268
  ...input.obstacles === void 0 ? {} : { obstacles: input.obstacles },
269
- ...input.gap === void 0 ? {} : { gap: input.gap }
269
+ ...input.gap === void 0 ? {} : { gap: input.gap },
270
+ ...input.pairGaps === void 0 ? {} : { pairGaps: input.pairGaps }
271
+ };
272
+ }
273
+ /** This function compares two strings by their UTF-16 code units, as `Array.prototype.sort` does without a compare function. */
274
+ const byCode = (x, y) => x < y ? -1 : x > y ? 1 : 0;
275
+ /**
276
+ * This function gives the canonical form of an input. `documentHash` uses it for each `layout-input/1` document.
277
+ * Each pair of `pairGaps` gets `a` before `b`, and the list is sorted by `a` and then by `b`.
278
+ * An input without `pairGaps` is the same object, so its hash does not change.
279
+ */
280
+ function canonicalInput(input) {
281
+ if (!Array.isArray(input.pairGaps)) return input;
282
+ const pairGaps = input.pairGaps.map((p) => byCode(p.a, p.b) > 0 ? {
283
+ ...p,
284
+ a: p.b,
285
+ b: p.a
286
+ } : p).sort((x, y) => byCode(x.a, y.a) || byCode(x.b, y.b));
287
+ return {
288
+ ...input,
289
+ pairGaps
270
290
  };
271
291
  }
272
292
  // src/turn.ts
@@ -423,20 +443,53 @@ function placedRect(box) {
423
443
  /**
424
444
  * This function counts the pairs of placed boxes whose insides, with their margins, overlap by more than `tolerance` on each axis.
425
445
  * A box with a shape counts as its bounding box. That area is larger, thus the count is safe.
446
+ *
447
+ * A pair of `pairGaps`, in either order, uses its `gap` in place of the two margins. One box grows by the gap, and the other box does not grow.
448
+ * Thus the pair breaks the rule only when the boxes are nearer than the gap on each axis. The other pairs do not change.
426
449
  */
427
- function countOverlaps(boxes, tolerance = DEFAULT_TOLERANCE.overlap) {
450
+ function countOverlaps(boxes, tolerance = DEFAULT_TOLERANCE.overlap, pairGaps) {
451
+ if (pairGaps === void 0 || pairGaps.length === 0) return marginOverlaps(boxes, tolerance, void 0);
452
+ const gaps = /* @__PURE__ */ new Map();
453
+ for (const p of pairGaps) {
454
+ const key = pairKey(p.a, p.b);
455
+ if (p.a !== p.b && !gaps.has(key)) gaps.set(key, p.gap);
456
+ }
457
+ const raw = /* @__PURE__ */ new Map();
458
+ for (const box of boxes) {
459
+ const r = placedRect(box);
460
+ if (r !== void 0 && !raw.has(box.id)) raw.set(box.id, r);
461
+ }
462
+ let count = marginOverlaps(boxes, tolerance, gaps);
463
+ for (const [key, gap] of gaps) {
464
+ const [a, b] = JSON.parse(key);
465
+ const ra = raw.get(a);
466
+ const rb = raw.get(b);
467
+ if (ra === void 0 || rb === void 0) continue;
468
+ const o = overlapOf(grow(ra, gap), rb);
469
+ if (o.x > tolerance && o.y > tolerance) count++;
470
+ }
471
+ return count;
472
+ }
473
+ /** This function gives the key of a pair of box ids, with the same key for each order. */
474
+ const pairKey = (a, b) => JSON.stringify(a < b ? [a, b] : [b, a]);
475
+ /** This function counts the overlaps of the boxes with their margins. It skips the pairs of `skip`. Without `skip`, it is the rule of 0.2.13. */
476
+ function marginOverlaps(boxes, tolerance, skip) {
428
477
  const rects = [];
429
478
  for (const box of boxes) {
430
479
  const r = placedRect(box);
431
- if (r !== void 0) rects.push(grow(r, box.margin));
480
+ if (r !== void 0) rects.push({
481
+ id: box.id,
482
+ r: grow(r, box.margin)
483
+ });
432
484
  }
433
- rects.sort((a, b) => a.x - b.x);
485
+ rects.sort((a, b) => a.r.x - b.r.x);
434
486
  let count = 0;
435
487
  for (let i = 0; i < rects.length; i++) {
436
- const a = rects[i];
488
+ const a = rects[i].r;
437
489
  for (let j = i + 1; j < rects.length; j++) {
438
- const b = rects[j];
490
+ const b = rects[j].r;
439
491
  if (b.x >= a.x + a.w - tolerance) break;
492
+ if (skip !== void 0 && skip.has(pairKey(rects[i].id, rects[j].id))) continue;
440
493
  const o = overlapOf(a, b);
441
494
  if (o.x > tolerance && o.y > tolerance) count++;
442
495
  }
@@ -671,7 +724,8 @@ function countObstacleHits(boxes, obstacles, tolerance = DEFAULT_TOLERANCE.overl
671
724
  /**
672
725
  * This function gives the count of each hard rule of the output of a packer.
673
726
  * It applies the output with `applyPack`. Only the placed boxes and the locked boxes count for the overlaps and the container.
674
- * `no-overlap` counts the pairs of boxes and the pairs of a box and an obstacle.
727
+ * `no-overlap` counts the pairs of boxes and the pairs of a box and an obstacle. A pair of `pairGaps` uses its gap in place of the margins.
728
+ *
675
729
  * A locked box without a placement, or with a move of more than `RECORD_EPS`, breaks `locked-kept`.
676
730
  * A locked box without a position in the input is a bad input, not a break. `checkInput` finds it.
677
731
  * Another box without a placement that is not in `overflow` breaks `complete`.
@@ -697,7 +751,7 @@ function packHard(input, output, tolerance = DEFAULT_TOLERANCE.overlap, options
697
751
  else if (!out.has(box.id)) incomplete++;
698
752
  }
699
753
  return {
700
- "no-overlap": countOverlaps(kept, tolerance) + countObstacleHits(kept, after.obstacles, tolerance),
754
+ "no-overlap": countOverlaps(kept, tolerance, input.pairGaps) + countObstacleHits(kept, after.obstacles, tolerance),
701
755
  "in-container": countOutside({
702
756
  ...after,
703
757
  boxes: kept
@@ -709,6 +763,7 @@ function packHard(input, output, tolerance = DEFAULT_TOLERANCE.overlap, options
709
763
  /**
710
764
  * This function gives the problems of an input file. An empty list tells that the file is good.
711
765
  * A problem is a bad input, not a break of a hard rule. Thus an engine refuses the input.
766
+ * In `pairGaps`, an unknown box, a pair of one box, a repeated pair in either order, a bad `gap` and bad `lanes` are problems.
712
767
  */
713
768
  function checkInput(input) {
714
769
  const problems = [];
@@ -723,6 +778,30 @@ function checkInput(input) {
723
778
  }
724
779
  if (countPortsOffBorder(input.boxes) > 0) problems.push("a fixed port is not on the border of its box");
725
780
  for (const edge of input.edges ?? []) if (!ids.has(edge.source) || !ids.has(edge.target)) problems.push(`edge ${edge.id}: a box of the edge is not in the input`);
781
+ problems.push(...pairGapProblems(input.pairGaps, ids));
782
+ return problems;
783
+ }
784
+ /** This function gives the problems of the `pairGaps` of an input. `ids` has the ids of the boxes. */
785
+ function pairGapProblems(pairGaps, ids) {
786
+ if (pairGaps === void 0) return [];
787
+ if (!Array.isArray(pairGaps)) return ["pairGaps: the value is not a list"];
788
+ const problems = [];
789
+ const seen = /* @__PURE__ */ new Map();
790
+ pairGaps.forEach((p, i) => {
791
+ const at = `pair gap ${i} (${String(p?.a)}, ${String(p?.b)})`;
792
+ if (typeof p !== "object" || p === null || typeof p.a !== "string" || typeof p.b !== "string") {
793
+ problems.push(`pair gap ${i}: the entry has no string ids a and b`);
794
+ return;
795
+ }
796
+ for (const id of [p.a, p.b]) if (!ids.has(id)) problems.push(`${at}: the box ${id} is not in the input`);
797
+ if (p.a === p.b) problems.push(`${at}: the two ids are the same box`);
798
+ if (typeof p.gap !== "number" || !Number.isFinite(p.gap) || p.gap < 0) problems.push(`${at}: the gap ${String(p.gap)} is not a finite number of 0 or more`);
799
+ if (p.lanes !== void 0 && (typeof p.lanes !== "number" || !Number.isInteger(p.lanes) || p.lanes < 0)) problems.push(`${at}: the lanes value ${String(p.lanes)} is not a whole number of 0 or more`);
800
+ const key = JSON.stringify(p.a < p.b ? [p.a, p.b] : [p.b, p.a]);
801
+ const first = seen.get(key);
802
+ if (first !== void 0) problems.push(`${at}: the pair is also pair gap ${first}`);
803
+ else seen.set(key, i);
804
+ });
726
805
  return problems;
727
806
  }
728
807
  // src/measures.ts
@@ -1287,10 +1366,11 @@ function outputHash(value, options = {}) {
1287
1366
  /**
1288
1367
  * This function gives the hash of a document. It uses the rule of `outputHash`, but the form does not include the field `hash` of the document.
1289
1368
  * With the default grid, it is the input hash of `docs/FORMAT.md` of `packing`.
1369
+ * A document with the schema `layout-input/1` goes through `canonicalInput` first. Thus the order of its `pairGaps` does not change the hash.
1290
1370
  */
1291
1371
  function documentHash(doc, options = {}) {
1292
1372
  const { hash: _hash, ...rest } = doc;
1293
- return outputHash(rest, options);
1373
+ return outputHash(rest.schema === "layout-input/1" ? canonicalInput(rest) : rest, options);
1294
1374
  }
1295
1375
  /**
1296
1376
  * This function gives the output groups of the candidates. The key of `hashes` is the id of a candidate.
@@ -1858,6 +1938,18 @@ function isActive(p, values) {
1858
1938
  const v = values[p.when.param];
1859
1939
  return p.when.in.some((x) => x === v);
1860
1940
  }
1941
+ /** This function gives the problem of a value of a param, or `undefined` for a value in its range. The corpus dimensions of `families.ts` use it. */
1942
+ function paramProblem(param, value) {
1943
+ return valueProblem(param, value);
1944
+ }
1945
+ /** This function tells if a param is active for the values of the earlier params. A param without `when` is always active. */
1946
+ function paramActive(param, values) {
1947
+ return isActive(param, values);
1948
+ }
1949
+ /** This function gives the stable form of a value of a param: a real number gets 12 significant digits, and `-0` becomes `0`. */
1950
+ function paramValue(param, value) {
1951
+ return normalize(param, value);
1952
+ }
1861
1953
  /** This function gives the part of a space that has a stage, and the prefix of the names of its stages. */
1862
1954
  function partOf(space, stage) {
1863
1955
  for (const part of space.parts ?? []) {
@@ -1943,6 +2035,8 @@ function optionProblems(stage, o) {
1943
2035
  if (o.members !== void 0) {
1944
2036
  const { min, max, ordered } = o.members;
1945
2037
  if (!Number.isInteger(min) || !Number.isInteger(max) || min < 1 || min > max || typeof ordered !== "boolean") out.push(["members", `${at} has a member rule without whole counts 1 <= min <= max and a boolean ordered`]);
2038
+ const pool = o.members.pool;
2039
+ if (pool !== void 0 && (!Array.isArray(pool) || pool.length === 0 || !pool.every((entry) => Array.isArray(entry)))) out.push(["pool", `${at} has a pool that is not a list of one or more member lists`]);
1946
2040
  }
1947
2041
  return out;
1948
2042
  }
@@ -2053,8 +2147,39 @@ function memberProblems(part, rule, members, at, shapes) {
2053
2147
  if (ids.has(id)) out.push(["duplicate", `the members ${ids.get(id)} and ${k} of ${at} are the same config ${id}`]);
2054
2148
  else ids.set(id, k);
2055
2149
  });
2150
+ if (rule.pool !== void 0 && out.length === 0) {
2151
+ const key = memberKey(part, rule, members);
2152
+ if (!rule.pool.some((entry) => memberKey(part, rule, entry) === key)) out.push(["pool", `${at} has a member list that is not in its pool of ${rule.pool.length} ${rule.pool.length === 1 ? "list" : "lists"}`]);
2153
+ }
2056
2154
  return out;
2057
2155
  }
2156
+ /** This function gives the key of a good member list: the `configId` of each member, sorted when the rule is not ordered. */
2157
+ function memberKey(part, rule, members) {
2158
+ const ids = members.map((m) => idOf(canonicalOf(part, m, true)));
2159
+ if (!rule.ordered) ids.sort();
2160
+ return ids.join(",");
2161
+ }
2162
+ /** This function gives the problems of the pool of a meta option of a good space. `part` is the space of the members. */
2163
+ function poolProblems(part, rule, at) {
2164
+ const out = [];
2165
+ const keys = /* @__PURE__ */ new Map();
2166
+ const plain = {
2167
+ min: rule.min,
2168
+ max: rule.max,
2169
+ ordered: rule.ordered
2170
+ };
2171
+ (rule.pool ?? []).forEach((entry, j) => {
2172
+ const inner = memberProblems(part, plain, entry, `the pool entry ${j} of ${at}`);
2173
+ out.push(...inner);
2174
+ if (inner.length > 0) return;
2175
+ const key = memberKey(part, rule, entry);
2176
+ if (keys.has(key)) out.push(["duplicate", `the pool entries ${keys.get(key)} and ${j} of ${at} have the same members`]);
2177
+ else keys.set(key, j);
2178
+ });
2179
+ return out;
2180
+ }
2181
+ /** This function tells if a member list fits a space: each member is a good config of the space. A shape filter removes options from the space. */
2182
+ const fits = (part, entry) => entry.every((m) => configProblems(part, m, true).length === 0);
2058
2183
  const toBreaks = (problems, inputs = []) => problems.map(([claim, detail]) => ({
2059
2184
  claim,
2060
2185
  detail,
@@ -2063,6 +2188,8 @@ const toBreaks = (problems, inputs = []) => problems.map(([claim, detail]) => ({
2063
2188
  /**
2064
2189
  * This function checks one config of a space. It gives one break for each problem. An empty list tells that the config is good.
2065
2190
  * The `claim` of a break is the kind of the problem, for example `missing`, `unknown`, `inactive`, `param`, `constraint`, `members` or `depth`.
2191
+ *
2192
+ * A member list that is not an entry of the `pool` of its option gives the claim `pool`.
2066
2193
  * With `shapes`, an option that does not support a kind of the list gives the claim `shape`. A member of a meta option gets the same check.
2067
2194
  */
2068
2195
  function checkConfig(space, config, options = {}) {
@@ -2071,6 +2198,7 @@ function checkConfig(space, config, options = {}) {
2071
2198
  /**
2072
2199
  * This function checks a space. It gives one break for each problem. An empty list tells that the space is good.
2073
2200
  * It finds duplicate names, empty ranges, defaults out of range, enums without values, bad log scales, unknown names and bad named configs.
2201
+ * It also checks each entry of a `pool`: the count of its members, each member and a repeated entry.
2074
2202
  * The `inputs` of a break of a named config have the name of the config.
2075
2203
  */
2076
2204
  function checkSpace(space) {
@@ -2109,6 +2237,8 @@ function checkSpace(space) {
2109
2237
  }
2110
2238
  const breaks = toBreaks(out);
2111
2239
  if (breaks.length > 0) return breaks;
2240
+ for (const s of space.stages) for (const o of s.options) if (o.members?.pool !== void 0) breaks.push(...toBreaks(poolProblems(partOf(space, s.name).space, o.members, `the option ${s.name}.${o.id}`)));
2241
+ if (breaks.length > 0) return breaks;
2112
2242
  for (const [name, config] of Object.entries(space.named ?? {})) breaks.push(...toBreaks(configProblems(space, config, false).map(([c, d]) => [c, `the named config ${name}: ${d}`]), [name]));
2113
2243
  return breaks;
2114
2244
  }
@@ -2244,7 +2374,7 @@ function* gridParams(params, levels, i = 0, values = {}) {
2244
2374
  }
2245
2375
  const optionIds = (choices) => new Map([...choices].map(([k, o]) => [k, o?.id ?? null]));
2246
2376
  const keeps = (space, choices) => (space.constraints ?? []).every((c) => holds(c, optionIds(choices)));
2247
- /** This function gives the count of the member lists of a meta option from `base` base configs, or `null` above `limit`. */
2377
+ /** This function gives the count of the member lists of a meta option without a pool from `base` base configs, or `null` above `limit`. */
2248
2378
  function memberLists(base, rule, limit) {
2249
2379
  let sum = 0n;
2250
2380
  for (let m = rule.min; m <= rule.max; m++) {
@@ -2321,10 +2451,11 @@ function combos(space, levels, meta, limit) {
2321
2451
  for (const s of stages) for (const o of s.options) {
2322
2452
  if (o.members === void 0) continue;
2323
2453
  const choices = new Map(stages.map((x) => [x.name, x === s ? o : null]));
2454
+ const lists = o.members.pool === void 0 ? memberLists(base, o.members, limit) : BigInt(o.members.pool.length);
2324
2455
  if (keeps(space, choices)) out.push({
2325
2456
  choices,
2326
2457
  base: false,
2327
- weight: times(countOf(o), memberLists(base, o.members, limit), limit)
2458
+ weight: times(countOf(o), lists, limit)
2328
2459
  });
2329
2460
  }
2330
2461
  return out;
@@ -2332,6 +2463,7 @@ function combos(space, levels, meta, limit) {
2332
2463
  /**
2333
2464
  * This function gives the count of the grid points of a space, with `levels` levels for each numeric param.
2334
2465
  * It counts only the active params and the combinations that keep the constraints. It gives the meta configs apart.
2466
+ * A meta option with a `pool` counts each entry of the pool one time.
2335
2467
  * With `shapes`, it counts only the options that support each kind of the list.
2336
2468
  */
2337
2469
  function spaceSize(space, options) {
@@ -2343,10 +2475,11 @@ function spaceSize(space, options) {
2343
2475
  /**
2344
2476
  * This function gives the space with only the options that support each shape kind of `shapes`. Without `shapes`, it gives the same space.
2345
2477
  * A stage can lose all of its options. Then the space has no config with that stage.
2478
+ * A pool keeps only the entries whose members fit the smaller space. A meta option without such an entry goes out.
2346
2479
  */
2347
2480
  function restrict(space, shapes) {
2348
2481
  if (shapes === void 0) return space;
2349
- return {
2482
+ const kept = {
2350
2483
  ...space,
2351
2484
  stages: space.stages.map((s) => ({
2352
2485
  ...s,
@@ -2354,6 +2487,25 @@ function restrict(space, shapes) {
2354
2487
  })),
2355
2488
  ...space.parts === void 0 ? {} : { parts: space.parts.map((p) => restrict(p, shapes)) }
2356
2489
  };
2490
+ const withPool = (s, o) => {
2491
+ if (o.members?.pool === void 0) return [o];
2492
+ const part = partOf(kept, s.name).space;
2493
+ const pool = o.members.pool.filter((entry) => fits(part, entry));
2494
+ return pool.length === 0 ? [] : [{
2495
+ ...o,
2496
+ members: {
2497
+ ...o.members,
2498
+ pool
2499
+ }
2500
+ }];
2501
+ };
2502
+ return {
2503
+ ...kept,
2504
+ stages: kept.stages.map((s) => ({
2505
+ ...s,
2506
+ options: s.options.flatMap((o) => withPool(s, o))
2507
+ }))
2508
+ };
2357
2509
  }
2358
2510
  /** This function throws a `RangeError` for a list of shape kinds with a kind that is not declared. */
2359
2511
  function assertShapes(shapes) {
@@ -2473,6 +2625,14 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
2473
2625
  });
2474
2626
  }
2475
2627
  if (o.members === void 0) continue;
2628
+ const fixed = o.members.pool;
2629
+ if (fixed !== void 0) {
2630
+ const r = stepRandom(seed, `pool-entry:${s.name}:${o.id}`);
2631
+ permutation(users.length, r).forEach((stratum, j) => {
2632
+ members[users[j]][k] = [...fixed[Math.min(fixed.length - 1, Math.floor((stratum + r()) / users.length * fixed.length))]];
2633
+ });
2634
+ continue;
2635
+ }
2476
2636
  const part = partOf(space, s.name).space;
2477
2637
  const poolSize = Math.max(n, 4 * o.members.max, 8);
2478
2638
  const seen = /* @__PURE__ */ new Set();
@@ -2516,6 +2676,7 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
2516
2676
  * - The mode `sample` gives `n` configs of a seeded Latin hypercube. The options of a stage get equal strata, and each active param gets its own strata.
2517
2677
  * - A sample draws again when a point breaks a constraint. After `maxTries` draws, it throws an `Error`. Two sampled configs can be equal.
2518
2678
  * - With `shapes`, only the options that support each kind of the list count. Without `shapes`, the result does not change.
2679
+ * - A meta option with a `pool` gives only the entries of its pool. With `shapes`, only the entries whose members support each kind count.
2519
2680
  */
2520
2681
  function enumerateSpace(space, options) {
2521
2682
  assertSpace(space);
@@ -2831,6 +2992,596 @@ function verdict(records, options) {
2831
2992
  dropped
2832
2993
  };
2833
2994
  }
2834
- export { DEFAULT_SHAPES, DEFAULT_TOLERANCE, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, FAMILY_ANY_KIND, JOINT_MEASURES, JOINT_MOVED_MIN, PACK_HARD_RULES, RECORD_EPS, RECORD_SCALE, SHAPE_KINDS, SHAPE_KIND_IDS, SIDES, SPLIT_SCHEMA, STRATEGY_SPACE_SCHEMA, VERDICT_DRAWS, VERDICT_EXACT_CAP, VERDICT_EXACT_MAX, VERDICT_RUNS, VERDICT_SEED, applyPack, boundsOf, canonicalConfig, canonicalJson, checkConfig, checkDeclared, checkInput, checkShapes, checkSpace, checkSplit, compareMeasure, configId, corpusHashOf, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, declaredClaimOf, declaredCounts, documentHash, edgeStep, enumerateSpace, exercised, familyOf, fnv1a32, grow, isShapeKind, itemShapeOf, joinConfigs, leaveOneSeedOut, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, packHard, paramLevels, pickMoves, placedRect, portfolio, productSpace, randomInt, rectInRegion, regionHitsRect, selectBest, sha256Hex, shapeKindOf, shapeKindProblem, shapeKindsOf, shapesOf, spaceShapeKinds, spaceSize, splitConfig, splitFileName, splitFolds, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide, verdict };
2995
+ // src/families.ts
2996
+ /** The random numbers of the default factory: `mulberry32` of `random.ts`. Its stream is pinned in `families.test.ts`. */
2997
+ const DEFAULT_RNG = mulberry32;
2998
+ /** This function gives the `kind` of the cells of a version of a family: `<name>@<version>`, for example `rect-grid@2`. Thus two versions do not share a family key. */
2999
+ function familyKind(name, version) {
3000
+ return `${name}@${version}`;
3001
+ }
3002
+ /** The default key of a family in a manifest: the kind of the cell alone. It is the map form of `FamilyBy` with the entry `"*"`. */
3003
+ const DEFAULT_FAMILY_BY = Object.freeze({ "*": Object.freeze([]) });
3004
+ /** This function gives an `enum` dimension. */
3005
+ const choice = (name, values, value, meaning, engines) => Object.freeze({
3006
+ name,
3007
+ kind: "enum",
3008
+ values: Object.freeze([...values]),
3009
+ default: value,
3010
+ meaning,
3011
+ engines: Object.freeze([...engines])
3012
+ });
3013
+ /** This function gives an `int` or a `real` dimension. */
3014
+ const range = (name, kind, limits, meaning, engines) => Object.freeze({
3015
+ name,
3016
+ kind,
3017
+ min: limits.min,
3018
+ max: limits.max,
3019
+ default: limits.value,
3020
+ scale: limits.scale ?? "linear",
3021
+ ...limits.levels === void 0 ? {} : { levels: Object.freeze([...limits.levels]) },
3022
+ meaning,
3023
+ engines: Object.freeze([...engines])
3024
+ });
3025
+ const P = "packing";
3026
+ const H = "highways";
3027
+ const J = "joint";
3028
+ /**
3029
+ * The shared dimension table. A family takes a dimension of it with `sharedDimension`, or declares its own dimension.
3030
+ * The coverage table of each dimension and each engine is in `docs/CORPUS.md`.
3031
+ */
3032
+ const CORPUS_DIMENSIONS = Object.freeze({
3033
+ count: range("count", "int", {
3034
+ min: 0,
3035
+ max: 1e5,
3036
+ value: 100,
3037
+ scale: "log",
3038
+ levels: [
3039
+ 1,
3040
+ 10,
3041
+ 100,
3042
+ 1e3,
3043
+ 1e4
3044
+ ]
3045
+ }, "The count of the items (n) of an input.", [
3046
+ P,
3047
+ H,
3048
+ J
3049
+ ]),
3050
+ sizes: choice("sizes", [
3051
+ "identical",
3052
+ "nearIdentical",
3053
+ "uniform",
3054
+ "heavyTail",
3055
+ "bimodal",
3056
+ "extremeAspect"
3057
+ ], "uniform", "The distribution of the sizes of the items.", [P, J]),
3058
+ aspect: range("aspect", "real", {
3059
+ min: .25,
3060
+ max: 4,
3061
+ value: 1,
3062
+ scale: "log"
3063
+ }, "The width over the height of a container with no fixed size.", [P]),
3064
+ density: range("density", "real", {
3065
+ min: .05,
3066
+ max: 1,
3067
+ value: .4,
3068
+ scale: "linear"
3069
+ }, "The area of the items over the area of the frame of the container.", [P]),
3070
+ obstacles: choice("obstacles", [
3071
+ "none",
3072
+ "center",
3073
+ "scattered",
3074
+ "wall",
3075
+ "corridors",
3076
+ "touching",
3077
+ "edge"
3078
+ ], "none", "The layout of the fixed obstacles.", [P, J]),
3079
+ containerSize: choice("containerSize", [
3080
+ "fixed",
3081
+ "freeHeight",
3082
+ "freeWidth",
3083
+ "freeAspect",
3084
+ "freeArea"
3085
+ ], "fixed", "The sides of the container that the engine can change.", [P, J]),
3086
+ containerShape: choice("containerShape", [
3087
+ "rect",
3088
+ "convex",
3089
+ "polygon",
3090
+ "circle",
3091
+ "ellipse",
3092
+ "holes"
3093
+ ], "rect", "The kind of the shape of the container.", [P, J]),
3094
+ shape: choice("shape", SHAPE_KIND_IDS, "rect", "The shape kind of each item: a `ShapeKind` of `shapes.ts`.", [P]),
3095
+ order: choice("order", ["free", "soft"], "free", "The reading order of the items: no order, or a rank on each item.", [P]),
3096
+ groups: choice("groups", [
3097
+ "none",
3098
+ "flat",
3099
+ "nested"
3100
+ ], "none", "The groups of the items: no groups, one level, or groups in groups.", [H, J]),
3101
+ edgeDensity: choice("edgeDensity", [
3102
+ "sparse",
3103
+ "medium",
3104
+ "dense"
3105
+ ], "medium", "The mean count of the edges of each item: 0.5, 1.5 or 4.", [H, J]),
3106
+ edgeShape: choice("edgeShape", [
3107
+ "random",
3108
+ "clustered",
3109
+ "hubChain",
3110
+ "hubs",
3111
+ "longRange",
3112
+ "tree",
3113
+ "bundled"
3114
+ ], "random", "The topology of the edges.", [H, J]),
3115
+ ports: choice("ports", [
3116
+ "free",
3117
+ "sides",
3118
+ "fixed"
3119
+ ], "free", "The ports of the edges: free, a side for each end, or fixed ports.", [H, J]),
3120
+ hubBy: Object.freeze({
3121
+ ...choice("hubBy", ["area", "random"], "area", "The order of the hubs of the shapes hubs and tree: the largest item first, or random.", [J]),
3122
+ when: Object.freeze({
3123
+ param: "edgeShape",
3124
+ in: Object.freeze(["hubs", "tree"])
3125
+ })
3126
+ }),
3127
+ spacing: choice("spacing", [
3128
+ "stress",
3129
+ "gap",
3130
+ "margin",
3131
+ "both"
3132
+ ], "both", "The split of the route clearance between the margins and the gap. `stress` has neither.", [J]),
3133
+ margin: range("margin", "real", {
3134
+ min: 0,
3135
+ max: 32,
3136
+ value: 0
3137
+ }, "The margin of each item, in units.", [P, J]),
3138
+ gap: range("gap", "real", {
3139
+ min: 0,
3140
+ max: 32,
3141
+ value: 0
3142
+ }, "The free space between two items, in units.", [P, J])
3143
+ });
3144
+ /** This function gives a dimension of `CORPUS_DIMENSIONS`, with the fields of `patch` over it. It throws a `RangeError` for an unknown name. */
3145
+ function sharedDimension(name, patch = {}) {
3146
+ const base = Object.hasOwn(CORPUS_DIMENSIONS, name) ? CORPUS_DIMENSIONS[name] : void 0;
3147
+ if (base === void 0) throw new RangeError(`The shared dimension table has no dimension "${name}".`);
3148
+ return Object.freeze({
3149
+ ...base,
3150
+ ...patch,
3151
+ name
3152
+ });
3153
+ }
3154
+ /** This function tells if a log scale of a range starts at 0. Then the scale is the log of `1 + x - min`. */
3155
+ const fromZero = (d) => d.scale === "log" && (d.min ?? 0) <= 0;
3156
+ /**
3157
+ * This function gives the value at the fraction `t` of the range of a numeric dimension, on its scale. A slider of a site uses it.
3158
+ * A log scale with a `min` of 0 or less is the log of `1 + x - min`. An `int` dimension gives a whole number.
3159
+ */
3160
+ function dimensionAt(dim, t) {
3161
+ const min = dim.min ?? 0;
3162
+ const max = dim.max ?? min;
3163
+ const u = Math.min(1, Math.max(0, t));
3164
+ let v;
3165
+ if (dim.scale !== "log") v = min + u * (max - min);
3166
+ else if (fromZero(dim)) v = min + Math.expm1(u * Math.log1p(max - min));
3167
+ else v = Math.exp(Math.log(min) + u * (Math.log(max) - Math.log(min)));
3168
+ v = Math.min(max, Math.max(min, v));
3169
+ return dim.kind === "int" ? Math.round(v) : paramValue(dim, v);
3170
+ }
3171
+ /** This function gives the fraction of the range of a numeric dimension at a value, on its scale. It is the inverse of `dimensionAt`. */
3172
+ function dimensionUnit(dim, value) {
3173
+ const min = dim.min ?? 0;
3174
+ const max = dim.max ?? min;
3175
+ if (max === min) return 0;
3176
+ const v = Math.min(max, Math.max(min, value));
3177
+ if (dim.scale !== "log") return (v - min) / (max - min);
3178
+ if (fromZero(dim)) return Math.log1p(v - min) / Math.log1p(max - min);
3179
+ return (Math.log(v) - Math.log(min)) / (Math.log(max) - Math.log(min));
3180
+ }
3181
+ /**
3182
+ * This function gives the values of a sweep of a dimension. `levels` of the dimension come first. Then an `enum` gives its values, and a `bool` gives `false` and `true`.
3183
+ * With `steps`, a numeric dimension gives `steps` points from `min` to `max` on its scale. Else it gives its default.
3184
+ */
3185
+ function levelsOf(dim, steps) {
3186
+ if (dim.levels !== void 0) return [...dim.levels];
3187
+ if (dim.kind === "enum" || dim.kind === "bool" || steps === void 0) return paramLevels(dim, 1);
3188
+ if (!fromZero(dim)) return paramLevels(dim, steps);
3189
+ if (!Number.isInteger(steps) || steps < 1) throw new RangeError(`The steps of a sweep are a whole number of 1 or more, not ${steps}.`);
3190
+ if (steps === 1) return [paramValue(dim, dim.default)];
3191
+ return [...new Set(Array.from({ length: steps }, (_, j) => dimensionAt(dim, j / (steps - 1))))];
3192
+ }
3193
+ /** This function gives the problems of the dimensions of a family: a repeated name, a bad default, a bad level or a bad `when`. */
3194
+ function dimensionProblems(dims) {
3195
+ const out = [];
3196
+ const names = dims.map((d) => d.name);
3197
+ dims.forEach((d, i) => {
3198
+ if (names.indexOf(d.name) !== i) out.push(`the dimension "${d.name}" is in the list two times`);
3199
+ const bad = paramProblem(d, d.default);
3200
+ if (bad !== void 0) out.push(`the default of "${d.name}" is bad: ${bad}`);
3201
+ for (const v of d.levels ?? []) {
3202
+ const p = paramProblem(d, v);
3203
+ if (p !== void 0) out.push(`a level of "${d.name}" is bad: ${p}`);
3204
+ }
3205
+ if (d.when === void 0) return;
3206
+ const j = names.indexOf(d.when.param);
3207
+ if (j < 0 || j >= i) out.push(`the when of "${d.name}" names "${d.when.param}", which is not an earlier dimension`);
3208
+ else if (d.when.in.length === 0) out.push(`the when of "${d.name}" has no values`);
3209
+ else for (const v of d.when.in) if (paramProblem(dims[j], v) !== void 0) out.push(`the when of "${d.name}" has a value that "${d.when.param}" does not have`);
3210
+ });
3211
+ return out;
3212
+ }
3213
+ /**
3214
+ * This function gives the full dimension values of an input. A missing dimension gets its default. An inactive dimension of `when` also gets its default.
3215
+ * Thus a default that the caller gives, a missing value and an inactive value give the same values. A real number gets its stable form.
3216
+ * The function throws a `RangeError` for an unknown dimension or a bad value, also for a bad value of an inactive dimension.
3217
+ */
3218
+ function resolveDimensions(dimensions, dims = {}, where = "The family") {
3219
+ const known = new Set(dimensions.map((d) => d.name));
3220
+ for (const key of Object.keys(dims)) if (!known.has(key)) throw new RangeError(`${where} has no dimension "${key}".`);
3221
+ const active = {};
3222
+ const out = {};
3223
+ for (const d of dimensions) {
3224
+ const given = Object.hasOwn(dims, d.name) ? dims[d.name] : void 0;
3225
+ if (given !== void 0) {
3226
+ const bad = paramProblem(d, given);
3227
+ if (bad !== void 0) throw new RangeError(`${where}: the dimension "${d.name}" is bad: ${bad}.`);
3228
+ }
3229
+ const on = paramActive(d, active);
3230
+ const value = paramValue(d, on && given !== void 0 ? given : d.default);
3231
+ if (on) active[d.name] = value;
3232
+ out[d.name] = value;
3233
+ }
3234
+ return out;
3235
+ }
3236
+ /** This function gives the values of the active dimensions only. */
3237
+ function activeValues(dimensions, full) {
3238
+ const active = {};
3239
+ for (const d of dimensions) if (paramActive(d, active)) active[d.name] = full[d.name];
3240
+ return active;
3241
+ }
3242
+ /** This function gives the active values of each cell of a sweep, in the order of the dimensions. The last dimension changes first. */
3243
+ function* sweepOf(dimensions, levels, i = 0, values = {}) {
3244
+ if (i === dimensions.length) {
3245
+ yield { ...values };
3246
+ return;
3247
+ }
3248
+ const d = dimensions[i];
3249
+ const lv = levels[i];
3250
+ if (lv === void 0 || !paramActive(d, values)) {
3251
+ yield* sweepOf(dimensions, levels, i + 1, paramActive(d, values) ? {
3252
+ ...values,
3253
+ [d.name]: paramValue(d, d.default)
3254
+ } : values);
3255
+ return;
3256
+ }
3257
+ for (const v of lv) yield* sweepOf(dimensions, levels, i + 1, {
3258
+ ...values,
3259
+ [d.name]: paramValue(d, v)
3260
+ });
3261
+ }
3262
+ /** This function counts the cells of a sweep with no list of the cells. A dimension that a later `when` reads splits the count. */
3263
+ function countOf(dimensions, levels, i = 0, values = {}) {
3264
+ if (i === dimensions.length) return 1;
3265
+ const d = dimensions[i];
3266
+ if (!paramActive(d, values)) return countOf(dimensions, levels, i + 1, values);
3267
+ const lv = levels[i] ?? [d.default];
3268
+ if (!dimensions.slice(i + 1).some((q) => q.when?.param === d.name)) return lv.length * countOf(dimensions, levels, i + 1, values);
3269
+ let sum = 0;
3270
+ for (const v of lv) sum += countOf(dimensions, levels, i + 1, {
3271
+ ...values,
3272
+ [d.name]: paramValue(d, v)
3273
+ });
3274
+ return sum;
3275
+ }
3276
+ /** This function gives the levels of each dimension of a sweep. A dimension outside the sweep has `undefined`, so it keeps its default. */
3277
+ function sweepLevels(dimensions, options, where) {
3278
+ const names = new Set(dimensions.map((d) => d.name));
3279
+ for (const name of [...options.sweep ?? [], ...Object.keys(options.levels ?? {})]) if (!names.has(name)) throw new RangeError(`${where} has no dimension "${name}".`);
3280
+ return dimensions.map((d) => {
3281
+ if (options.sweep !== void 0 && !options.sweep.includes(d.name)) return void 0;
3282
+ const lv = options.levels !== void 0 && Object.hasOwn(options.levels, d.name) ? options.levels[d.name] : levelsOf(d);
3283
+ for (const v of lv) {
3284
+ const bad = paramProblem(d, v);
3285
+ if (bad !== void 0) throw new RangeError(`${where}: a level of "${d.name}" is bad: ${bad}.`);
3286
+ }
3287
+ return lv;
3288
+ });
3289
+ }
3290
+ /** This function gives the values that make a dimension active: the first value of its `when`, and the values that make that dimension active. */
3291
+ function activation(dimensions, dim) {
3292
+ const out = {};
3293
+ let d = dim;
3294
+ while (d?.when !== void 0) {
3295
+ const param = d.when.param;
3296
+ out[param] = d.when.in[0];
3297
+ d = dimensions.find((q) => q.name === param);
3298
+ }
3299
+ return out;
3300
+ }
3301
+ /** This function throws a `RangeError` when a seed is not a whole number of 0 or more. */
3302
+ function checkSeed(seed) {
3303
+ if (!Number.isSafeInteger(seed) || seed < 0) throw new RangeError(`The seed is a whole number of 0 or more, not ${seed}.`);
3304
+ }
3305
+ /** This function gives a new registry with the families of the list. Each engine gives the list of its families. An import registers nothing. */
3306
+ function createFamilyRegistry(families = []) {
3307
+ const list = [];
3308
+ const label = (f) => `The family "${f.name}" version ${f.version}`;
3309
+ const find = (name, version) => {
3310
+ const named = list.filter((f) => f.name === name);
3311
+ if (named.length === 0) throw new RangeError(`The registry has no family "${name}".`);
3312
+ if (version === void 0) return named[0];
3313
+ const found = named.find((f) => f.version === version);
3314
+ if (found === void 0) throw new RangeError(`The family "${name}" has the versions ${named.map((f) => f.version).join(", ")}, not ${version}.`);
3315
+ return found;
3316
+ };
3317
+ /** This function removes the `kind` of a cell of `cellOf` when it is the `familyKind` of `f`. It throws a `RangeError` for another kind. */
3318
+ const ownDims = (f, dims) => {
3319
+ if (dims === void 0 || !Object.hasOwn(dims, "kind")) return dims;
3320
+ const kind = familyKind(f.name, f.version);
3321
+ if (dims["kind"] !== kind) throw new RangeError(`${label(f)} has the kind "${kind}", not the kind ${JSON.stringify(dims["kind"])} of the cell.`);
3322
+ const { kind: _kind, ...rest } = dims;
3323
+ return rest;
3324
+ };
3325
+ const streamOf = (f, seed, full) => {
3326
+ const rng = f.rng ?? DEFAULT_RNG;
3327
+ return {
3328
+ rng,
3329
+ random: rng(f.rngSeed === void 0 ? seed : f.rngSeed(seed, full))
3330
+ };
3331
+ };
3332
+ const registry = {
3333
+ register(family) {
3334
+ if (!Number.isInteger(family.version) || family.version < 1) throw new RangeError(`${label(family)} has a version that is not a whole number of 1 or more.`);
3335
+ if (list.some((f) => f.name === family.name && f.version === family.version)) throw new RangeError(`${label(family)} is in the registry already.`);
3336
+ const problems = dimensionProblems(family.dimensions);
3337
+ if (family.dimensions.map((d) => d.name).includes("kind")) problems.push("the dimension name \"kind\" is reserved: the cell of `cellOf` has the `familyKind` of the family in `kind`");
3338
+ for (const key of family.familyBy ?? []) {
3339
+ const d = family.dimensions.find((q) => q.name === key);
3340
+ if (d === void 0) problems.push(`the familyBy dimension "${key}" is not a dimension of the family`);
3341
+ else if (d.when !== void 0) problems.push(`the familyBy dimension "${key}" has a when, so a cell without it has no family key`);
3342
+ }
3343
+ if (problems.length > 0) throw new RangeError(`${label(family)} is not good: ${problems.join("; ")}.`);
3344
+ list.push(family);
3345
+ return registry;
3346
+ },
3347
+ family: find,
3348
+ families: () => [...list],
3349
+ resolve(name, dims, version) {
3350
+ const f = find(name, version);
3351
+ return resolveDimensions(f.dimensions, ownDims(f, dims), label(f));
3352
+ },
3353
+ generate(name, seed, dims, options = {}) {
3354
+ checkSeed(seed);
3355
+ const f = find(name, options.version);
3356
+ const full = resolveDimensions(f.dimensions, ownDims(f, dims), label(f));
3357
+ const context = {
3358
+ ...streamOf(f, seed, full),
3359
+ ...options.set === void 0 ? {} : { set: options.set }
3360
+ };
3361
+ return f.generate(seed, full, context);
3362
+ },
3363
+ cellOf(name, dims, version) {
3364
+ const f = find(name, version);
3365
+ return {
3366
+ kind: familyKind(f.name, f.version),
3367
+ ...activeValues(f.dimensions, resolveDimensions(f.dimensions, ownDims(f, dims), label(f)))
3368
+ };
3369
+ },
3370
+ *cells(name, options = {}) {
3371
+ const f = find(name, options.version);
3372
+ for (const values of sweepOf(f.dimensions, sweepLevels(f.dimensions, options, label(f)))) yield resolveDimensions(f.dimensions, values, label(f));
3373
+ },
3374
+ cellCount(name, options = {}) {
3375
+ const f = find(name, options.version);
3376
+ return countOf(f.dimensions, sweepLevels(f.dimensions, options, label(f)));
3377
+ },
3378
+ boundaryCells(name, version) {
3379
+ const f = find(name, version);
3380
+ const base = resolveDimensions(f.dimensions, {}, label(f));
3381
+ const out = [base];
3382
+ const seen = /* @__PURE__ */ new Set([JSON.stringify(base)]);
3383
+ for (const d of f.dimensions) {
3384
+ if (d.kind !== "int" && d.kind !== "real") continue;
3385
+ for (const end of [d.min, d.max]) {
3386
+ if (end === void 0) continue;
3387
+ const cell = resolveDimensions(f.dimensions, {
3388
+ ...activation(f.dimensions, d),
3389
+ [d.name]: end
3390
+ }, label(f));
3391
+ const key = JSON.stringify(cell);
3392
+ if (!seen.has(key)) {
3393
+ seen.add(key);
3394
+ out.push(cell);
3395
+ }
3396
+ }
3397
+ }
3398
+ return out;
3399
+ },
3400
+ rngOf(name, version) {
3401
+ const f = find(name, version);
3402
+ return (seed, dims) => {
3403
+ checkSeed(seed);
3404
+ return streamOf(f, seed, resolveDimensions(f.dimensions, ownDims(f, dims), label(f))).random;
3405
+ };
3406
+ },
3407
+ familyBy() {
3408
+ const out = {};
3409
+ for (const f of list) out[familyKind(f.name, f.version)] = [...f.familyBy ?? []];
3410
+ return out;
3411
+ }
3412
+ };
3413
+ for (const f of families) registry.register(f);
3414
+ return registry;
3415
+ }
3416
+ /** This function gives the values of a list of dimensions from the full values. */
3417
+ const pick = (dims, full) => Object.fromEntries(dims.map((d) => [d.name, full[d.name]]));
3418
+ /**
3419
+ * This function gives a family that runs the base family and then each step, with the same seed.
3420
+ * The family of the input gets the part of each step after a dot. The id becomes `<set>/<family>/s<seed>`, and the input gets the hash of `options.hash`.
3421
+ * The function throws a `RangeError` when a step has a dimension with the name of an earlier dimension.
3422
+ */
3423
+ function composeFamily(base, steps, options = {}) {
3424
+ const dimensions = [...base.dimensions, ...steps.flatMap((s) => s.dimensions)];
3425
+ const seen = /* @__PURE__ */ new Set();
3426
+ for (const d of dimensions) {
3427
+ if (seen.has(d.name)) throw new RangeError(`composeFamily: the dimension "${d.name}" is in the base family or in an earlier step already.`);
3428
+ seen.add(d.name);
3429
+ }
3430
+ const hash = options.hash ?? documentHash;
3431
+ const libm = base.crossEngine === "libm-dependent" || steps.some((s) => s.crossEngine === "libm-dependent");
3432
+ return Object.freeze({
3433
+ name: options.name ?? [base.name, ...steps.map((s) => s.name)].join("+"),
3434
+ version: base.version,
3435
+ owner: options.owner ?? base.owner,
3436
+ meaning: options.meaning ?? `${base.meaning} Then the steps ${steps.map((s) => s.name).join(", ")}.`,
3437
+ dimensions,
3438
+ seeding: [base.seeding, ...steps.map((s) => `${s.name}: ${s.seeding}`)].join(" "),
3439
+ crossEngine: libm ? "libm-dependent" : "exact",
3440
+ ...base.rng === void 0 ? {} : { rng: base.rng },
3441
+ ...base.rngSeed === void 0 ? {} : { rngSeed: (seed, full) => base.rngSeed(seed, pick(base.dimensions, full)) },
3442
+ ...base.familyBy === void 0 ? {} : { familyBy: base.familyBy },
3443
+ generate(seed, full, context) {
3444
+ let input = base.generate(seed, pick(base.dimensions, full), context);
3445
+ const parts = [];
3446
+ for (const step of steps) {
3447
+ const values = pick(step.dimensions, full);
3448
+ input = step.apply(input, seed, values);
3449
+ if (step.familyPart !== void 0) parts.push(step.familyPart(values));
3450
+ }
3451
+ const family = [input.family ?? base.name, ...parts].join(".");
3452
+ const set = input.set ?? context.set;
3453
+ const id = `${set === void 0 ? "" : `${set}/`}${family}/s${seed}`;
3454
+ const stepped = {
3455
+ ...input,
3456
+ id,
3457
+ family
3458
+ };
3459
+ return {
3460
+ ...stepped,
3461
+ hash: hash(stepped)
3462
+ };
3463
+ }
3464
+ });
3465
+ }
3466
+ /** The dimensions of `EDGES_STEP`: the parameters of `edgeStep`, from the shared table. */
3467
+ const EDGE_DIMENSIONS = Object.freeze([
3468
+ "edgeDensity",
3469
+ "edgeShape",
3470
+ "groups",
3471
+ "ports",
3472
+ "hubBy"
3473
+ ].map((n) => CORPUS_DIMENSIONS[n]));
3474
+ /** This function gives the parameters of `edgeStep` from the values of the dimensions. It throws a `RangeError` for a missing or a bad value. */
3475
+ function edgeParams(d) {
3476
+ const text = (name) => {
3477
+ const dim = EDGE_DIMENSIONS.find((q) => q.name === name);
3478
+ const value = Object.hasOwn(d, name) ? d[name] : void 0;
3479
+ const bad = value === void 0 ? "the value is missing" : paramProblem(dim, value);
3480
+ if (bad !== void 0 || typeof value !== "string") throw new RangeError(`EDGES_STEP: the dimension "${name}" is bad: ${bad ?? "it is not text"}.`);
3481
+ return value;
3482
+ };
3483
+ return {
3484
+ edgeDensity: text("edgeDensity"),
3485
+ edgeShape: text("edgeShape"),
3486
+ groups: text("groups"),
3487
+ ports: text("ports"),
3488
+ hubBy: text("hubBy")
3489
+ };
3490
+ }
3491
+ /**
3492
+ * The step `edges` of `edgeStep` (version 1), as a corpus step. Its family part is `e-<edgeDensity>.<edgeShape>.<groups>.<ports>`.
3493
+ * The value `random` of `hubBy` adds the part `.hub-random`.
3494
+ */
3495
+ const EDGES_STEP = Object.freeze({
3496
+ name: "edges",
3497
+ version: 1,
3498
+ owner: "layout-contract",
3499
+ meaning: "The edges, the groups and the ports of an input, from its topology alone.",
3500
+ dimensions: EDGE_DIMENSIONS,
3501
+ seeding: "stepRandom(seed, step) for the steps edges and ports.",
3502
+ crossEngine: "libm-dependent",
3503
+ familyPart: (d) => `e-${d["edgeDensity"]}.${d["edgeShape"]}.${d["groups"]}.${d["ports"]}${d["hubBy"] === "random" ? ".hub-random" : ""}`,
3504
+ apply: (input, seed, d) => edgeStep(input, edgeParams(d), seed)
3505
+ });
3506
+ /** The set of `RECT_SCATTERED` when the caller gives no set. */
3507
+ const XCHECK_SET = "xcheck-v1";
3508
+ /**
3509
+ * The shared family `rect-scattered`, version 1: a fixed rectangle container, rectangle obstacles at random places and boxes without positions.
3510
+ * It is the one copy of the generator of `docs/FORMAT.md` of `packing`. Its input is the input of `packing`, byte for byte.
3511
+ * The steps `container`, `obstacles` and `boxes` each have a stream of `stepRandom`. Each value is a whole number, so the family is `exact`.
3512
+ */
3513
+ const RECT_SCATTERED = Object.freeze({
3514
+ name: "rect-scattered",
3515
+ version: 1,
3516
+ owner: "layout-contract",
3517
+ meaning: "Rectangle obstacles at random places in a fixed rectangle container, and boxes of random sizes.",
3518
+ dimensions: Object.freeze([sharedDimension("count", { default: 40 })]),
3519
+ seeding: "stepRandom(seed, container) gives w and h. stepRandom(seed, obstacles) gives n, then ow, oh, x and y of each obstacle. stepRandom(seed, boxes) gives bw and bh of each box.",
3520
+ crossEngine: "exact",
3521
+ generate(seed, dims, context) {
3522
+ const count = dims["count"];
3523
+ if (typeof count !== "number" || !Number.isInteger(count) || count < 0) throw new RangeError(`rect-scattered: the count is a whole number of 0 or more, not ${String(count)}.`);
3524
+ const set = context.set ?? XCHECK_SET;
3525
+ let r = stepRandom(seed, "container");
3526
+ const w = randomInt(r, 600, 1e3);
3527
+ const h = randomInt(r, 400, 800);
3528
+ r = stepRandom(seed, "obstacles");
3529
+ const n = randomInt(r, 3, 8);
3530
+ const obstacles = [];
3531
+ for (let i = 0; i < n; i++) {
3532
+ const ow = randomInt(r, 40, 160);
3533
+ const oh = randomInt(r, 40, 160);
3534
+ const x = randomInt(r, 0, w - ow);
3535
+ const y = randomInt(r, 0, h - oh);
3536
+ obstacles.push({
3537
+ id: `o${i}`,
3538
+ region: {
3539
+ kind: "rect",
3540
+ x,
3541
+ y,
3542
+ w: ow,
3543
+ h: oh
3544
+ }
3545
+ });
3546
+ }
3547
+ r = stepRandom(seed, "boxes");
3548
+ const boxes = Array.from({ length: count }, (_, i) => {
3549
+ const bw = randomInt(r, 20, 120);
3550
+ const bh = randomInt(r, 20, 80);
3551
+ return {
3552
+ id: `b${i}`,
3553
+ w: bw,
3554
+ h: bh
3555
+ };
3556
+ });
3557
+ const body = {
3558
+ schema: "layout-input/1",
3559
+ id: `${set}/rect-scattered/s${seed}`,
3560
+ set,
3561
+ family: "rect-scattered",
3562
+ seed,
3563
+ source: "made-up",
3564
+ generator: {
3565
+ name: "rect-scattered",
3566
+ version: 1,
3567
+ params: { count },
3568
+ seed
3569
+ },
3570
+ container: {
3571
+ w,
3572
+ h,
3573
+ fixed: "size"
3574
+ },
3575
+ obstacles,
3576
+ boxes,
3577
+ edges: []
3578
+ };
3579
+ return {
3580
+ ...body,
3581
+ hash: documentHash(body)
3582
+ };
3583
+ }
3584
+ });
3585
+ export { CORPUS_DIMENSIONS, DEFAULT_FAMILY_BY, DEFAULT_RNG, DEFAULT_SHAPES, DEFAULT_TOLERANCE, EDGES_STEP, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, FAMILY_ANY_KIND, JOINT_MEASURES, JOINT_MOVED_MIN, PACK_HARD_RULES, RECORD_EPS, RECORD_SCALE, RECT_SCATTERED, SHAPE_KINDS, SHAPE_KIND_IDS, SIDES, SPLIT_SCHEMA, STRATEGY_SPACE_SCHEMA, VERDICT_DRAWS, VERDICT_EXACT_CAP, VERDICT_EXACT_MAX, VERDICT_RUNS, VERDICT_SEED, applyPack, boundsOf, canonicalConfig, canonicalInput, canonicalJson, checkConfig, checkDeclared, checkInput, checkShapes, checkSpace, checkSplit, compareMeasure, composeFamily, configId, corpusHashOf, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, createFamilyRegistry, declaredClaimOf, declaredCounts, dimensionAt, dimensionUnit, documentHash, edgeStep, enumerateSpace, exercised, familyKind, familyOf, fnv1a32, grow, isShapeKind, itemShapeOf, joinConfigs, leaveOneSeedOut, levelsOf, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, packHard, paramActive, paramLevels, paramProblem, paramValue, pickMoves, placedRect, portfolio, productSpace, randomInt, rectInRegion, regionHitsRect, resolveDimensions, selectBest, sha256Hex, shapeKindOf, shapeKindProblem, shapeKindsOf, shapesOf, sharedDimension, spaceShapeKinds, spaceSize, splitConfig, splitFileName, splitFolds, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide, verdict };
2835
3586
 
2836
3587
  //# sourceMappingURL=index.js.map