@mark1russell7/layout-contract 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/build/index.d.ts +139 -11
- package/build/index.d.ts.map +1 -1
- package/build/index.js +395 -50
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/families.ts +264 -26
- package/src/geometry.ts +28 -0
- package/src/input.ts +34 -2
- package/src/rules.ts +18 -3
- package/src/shapes.ts +76 -8
- package/src/strategy-space.ts +56 -11
- package/src/turn.ts +8 -2
package/build/index.js
CHANGED
|
@@ -73,6 +73,28 @@ function boundsOf(region) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* This function gives the exact area of a region. A `rect` gives `w` by `h`, a circle gives `PI * r * r`, and an ellipse gives `PI * rx * ry`.
|
|
78
|
+
* A polygon gives the absolute value of the shoelace formula, less the area of each hole. A ring with fewer than 3 points has the area 0.
|
|
79
|
+
*/
|
|
80
|
+
function regionArea(region) {
|
|
81
|
+
switch (region.kind) {
|
|
82
|
+
case "rect": return region.w * region.h;
|
|
83
|
+
case "circle": return Math.PI * region.r * region.r;
|
|
84
|
+
case "ellipse": return Math.PI * region.rx * region.ry;
|
|
85
|
+
case "polygon": return (region.holes ?? []).reduce((area, hole) => area - ringArea(hole), ringArea(region.points));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** This function gives the area of one ring of a polygon: the absolute value of the shoelace formula. */
|
|
89
|
+
function ringArea(ring) {
|
|
90
|
+
let twice = 0;
|
|
91
|
+
for (let i = 0; i < ring.length; i++) {
|
|
92
|
+
const [ax, ay] = ring[i];
|
|
93
|
+
const [bx, by] = ring[(i + 1) % ring.length];
|
|
94
|
+
twice += ax * by - bx * ay;
|
|
95
|
+
}
|
|
96
|
+
return Math.abs(twice) / 2;
|
|
97
|
+
}
|
|
76
98
|
/** This function gives the region moved by `dx` and `dy`. */
|
|
77
99
|
function translate(region, dx, dy) {
|
|
78
100
|
switch (region.kind) {
|
|
@@ -111,7 +133,10 @@ function overlapOf(a, b) {
|
|
|
111
133
|
};
|
|
112
134
|
}
|
|
113
135
|
// src/shapes.ts
|
|
114
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* The declared shape kinds, in their order. Only `rect` has all of its geometry.
|
|
138
|
+
* `polygon` and `circle` have their bounding box and their exact area. `free` has its bounding box only.
|
|
139
|
+
*/
|
|
115
140
|
const SHAPE_KINDS = Object.freeze([
|
|
116
141
|
{
|
|
117
142
|
id: "rect",
|
|
@@ -130,7 +155,7 @@ const SHAPE_KINDS = Object.freeze([
|
|
|
130
155
|
regions: ["polygon"],
|
|
131
156
|
geometry: {
|
|
132
157
|
bounds: true,
|
|
133
|
-
area:
|
|
158
|
+
area: true,
|
|
134
159
|
contains: false,
|
|
135
160
|
overlap: false
|
|
136
161
|
}
|
|
@@ -145,7 +170,7 @@ const SHAPE_KINDS = Object.freeze([
|
|
|
145
170
|
regions: ["circle"],
|
|
146
171
|
geometry: {
|
|
147
172
|
bounds: true,
|
|
148
|
-
area:
|
|
173
|
+
area: true,
|
|
149
174
|
contains: false,
|
|
150
175
|
overlap: false
|
|
151
176
|
}
|
|
@@ -175,6 +200,35 @@ const SHAPE_KINDS = Object.freeze([
|
|
|
175
200
|
const SHAPE_KIND_IDS = Object.freeze(SHAPE_KINDS.map((k) => k.id));
|
|
176
201
|
/** The shape kinds of a measure or a rule without `shapes`. The contract measured only rectangles before L46. */
|
|
177
202
|
const DEFAULT_SHAPES = Object.freeze(["rect"]);
|
|
203
|
+
/** This function gives the level of a rule on a shape kind. A kind of `shapesOf` is `exact`, a kind of `safe` is `safe`, and another kind is `none`. */
|
|
204
|
+
function ruleLevel(rule, kind) {
|
|
205
|
+
if (shapesOf(rule).includes(kind)) return "exact";
|
|
206
|
+
return rule.safe?.includes(kind) === true ? "safe" : "none";
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* This function gives the problems of a list of measures and rules. An empty list tells that the list is good.
|
|
210
|
+
* It refuses an unknown kind, a kind two times in one list, and a kind in `safe` that is also in `shapesOf`.
|
|
211
|
+
*/
|
|
212
|
+
function shapeSupportProblems(uses) {
|
|
213
|
+
const out = [];
|
|
214
|
+
for (const use of uses) {
|
|
215
|
+
for (const [name, list] of [["shapes", use.shapes], ["safe", use.safe]]) {
|
|
216
|
+
if (list === void 0) continue;
|
|
217
|
+
if (!Array.isArray(list)) {
|
|
218
|
+
out.push(`${use.id}: the ${name} value is not a list of shape kinds`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
list.forEach((k, i) => {
|
|
222
|
+
if (!isShapeKind(k)) out.push(`${use.id}: the ${name} kind ${String(k)} is not a declared kind`);
|
|
223
|
+
else if (list.indexOf(k) !== i) out.push(`${use.id}: the ${name} list has the kind ${k} two times`);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (!Array.isArray(use.safe)) continue;
|
|
227
|
+
const exact = use.shapes === void 0 ? DEFAULT_SHAPES : Array.isArray(use.shapes) ? use.shapes : [];
|
|
228
|
+
for (const k of new Set(use.safe)) if (exact.includes(k)) out.push(`${use.id}: the kind ${String(k)} is in shapes and in safe`);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
178
232
|
const REGION_KIND = {
|
|
179
233
|
rect: "rect",
|
|
180
234
|
polygon: "polygon",
|
|
@@ -193,6 +247,18 @@ function shapeKindOf(box) {
|
|
|
193
247
|
if (box.shapeKind !== void 0) return box.shapeKind;
|
|
194
248
|
return box.shape === void 0 ? "rect" : REGION_KIND[box.shape.kind];
|
|
195
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* This function gives the exact area of a box, or `undefined` when the contract knows only its bounding box.
|
|
252
|
+
* A box with `shape` gives `regionArea` of the shape. A `rect` box without `shape` gives `w` by `h`.
|
|
253
|
+
* A box of the kind `free` gives `undefined`, also with a shape. Thus a box with an ellipse shape gives `undefined`, because `shapeKindOf` makes an ellipse `free`.
|
|
254
|
+
* A box of another kind without `shape` is a bad input, and gives `undefined`.
|
|
255
|
+
*/
|
|
256
|
+
function boxArea(box) {
|
|
257
|
+
const kind = shapeKindOf(box);
|
|
258
|
+
if (kind === "free") return void 0;
|
|
259
|
+
if (box.shape !== void 0) return regionArea(box.shape);
|
|
260
|
+
return kind === "rect" ? box.w * box.h : void 0;
|
|
261
|
+
}
|
|
196
262
|
/** This function gives the shape kinds of the boxes of an input, in the order of `SHAPE_KINDS`. An input without boxes gives an empty list. */
|
|
197
263
|
function shapeKindsOf(input) {
|
|
198
264
|
const seen = new Set(input.boxes.map(shapeKindOf));
|
|
@@ -219,19 +285,22 @@ function shapesOf(use) {
|
|
|
219
285
|
/**
|
|
220
286
|
* This function checks that each measure and each rule of a run supports each shape kind of the input. An empty list tells that the run is good.
|
|
221
287
|
* Each break has the claim `shape`, the id of the input, and the first 5 ids of the boxes of the kind.
|
|
288
|
+
* By default, a kind of `safe` is a break, because the count of the rule is not exact. With the level `safe`, it is not a break.
|
|
222
289
|
*/
|
|
223
|
-
function checkShapes(input, uses) {
|
|
290
|
+
function checkShapes(input, uses, options = {}) {
|
|
224
291
|
const kinds = shapeKindsOf(input);
|
|
225
292
|
const out = [];
|
|
226
293
|
for (const use of uses) {
|
|
227
294
|
const shapes = shapesOf(use);
|
|
228
295
|
for (const kind of kinds) {
|
|
229
|
-
|
|
296
|
+
const level = ruleLevel(use, kind);
|
|
297
|
+
if (level === "exact" || level === "safe" && options.level === "safe") continue;
|
|
230
298
|
const ids = input.boxes.filter((b) => shapeKindOf(b) === kind).map((b) => b.id);
|
|
231
299
|
const shown = ids.slice(0, 5).join(", ") + (ids.length > 5 ? `, and ${ids.length - 5} more` : "");
|
|
300
|
+
const safe = level === "safe" ? `. The count of ${use.id} on ${kind} is only safe` : "";
|
|
232
301
|
out.push({
|
|
233
302
|
claim: "shape",
|
|
234
|
-
detail: `${use.id} supports only the shape kinds ${shapes.join(", ") || "(none)"}, but the input ${input.id} has ${kind} boxes: ${shown}`,
|
|
303
|
+
detail: `${use.id} supports only the shape kinds ${shapes.join(", ") || "(none)"}, but the input ${input.id} has ${kind} boxes: ${shown}${safe}`,
|
|
235
304
|
inputs: [input.id]
|
|
236
305
|
});
|
|
237
306
|
}
|
|
@@ -260,6 +329,36 @@ const DEFAULT_TOLERANCE = Object.freeze({
|
|
|
260
329
|
through: .5
|
|
261
330
|
});
|
|
262
331
|
// src/input.ts
|
|
332
|
+
/**
|
|
333
|
+
* This function gives the point of a port on the border of its box, relative to the top-left corner of the box.
|
|
334
|
+
* A port with `x` and `y` gives that point. A port with `side` and `along` gives the point at that fraction of the side.
|
|
335
|
+
* Another port gives `undefined`, because the router picks its point.
|
|
336
|
+
*/
|
|
337
|
+
function portPoint(box, port) {
|
|
338
|
+
if (port.x !== void 0 && port.y !== void 0) return {
|
|
339
|
+
x: port.x,
|
|
340
|
+
y: port.y
|
|
341
|
+
};
|
|
342
|
+
if (port.side === void 0 || port.along === void 0) return void 0;
|
|
343
|
+
switch (port.side) {
|
|
344
|
+
case "top": return {
|
|
345
|
+
x: port.along * box.w,
|
|
346
|
+
y: 0
|
|
347
|
+
};
|
|
348
|
+
case "bottom": return {
|
|
349
|
+
x: port.along * box.w,
|
|
350
|
+
y: box.h
|
|
351
|
+
};
|
|
352
|
+
case "left": return {
|
|
353
|
+
x: 0,
|
|
354
|
+
y: port.along * box.h
|
|
355
|
+
};
|
|
356
|
+
case "right": return {
|
|
357
|
+
x: box.w,
|
|
358
|
+
y: port.along * box.h
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
263
362
|
/** This function gives the packer input of a file. An input without a container gets `{ fixed: "none" }`, so the packer picks the size. */
|
|
264
363
|
function toPackInput(input) {
|
|
265
364
|
return {
|
|
@@ -271,7 +370,7 @@ function toPackInput(input) {
|
|
|
271
370
|
};
|
|
272
371
|
}
|
|
273
372
|
/** 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;
|
|
373
|
+
const byCode$1 = (x, y) => x < y ? -1 : x > y ? 1 : 0;
|
|
275
374
|
/**
|
|
276
375
|
* This function gives the canonical form of an input. `documentHash` uses it for each `layout-input/1` document.
|
|
277
376
|
* Each pair of `pairGaps` gets `a` before `b`, and the list is sorted by `a` and then by `b`.
|
|
@@ -279,11 +378,11 @@ const byCode = (x, y) => x < y ? -1 : x > y ? 1 : 0;
|
|
|
279
378
|
*/
|
|
280
379
|
function canonicalInput(input) {
|
|
281
380
|
if (!Array.isArray(input.pairGaps)) return input;
|
|
282
|
-
const pairGaps = input.pairGaps.map((p) => byCode(p.a, p.b) > 0 ? {
|
|
381
|
+
const pairGaps = input.pairGaps.map((p) => byCode$1(p.a, p.b) > 0 ? {
|
|
283
382
|
...p,
|
|
284
383
|
a: p.b,
|
|
285
384
|
b: p.a
|
|
286
|
-
} : p).sort((x, y) => byCode(x.a, y.a) || byCode(x.b, y.b));
|
|
385
|
+
} : p).sort((x, y) => byCode$1(x.a, y.a) || byCode$1(x.b, y.b));
|
|
287
386
|
return {
|
|
288
387
|
...input,
|
|
289
388
|
pairGaps
|
|
@@ -298,14 +397,20 @@ function turnSide(side) {
|
|
|
298
397
|
function turnPoint(x, y, h) {
|
|
299
398
|
return [h - y, x];
|
|
300
399
|
}
|
|
301
|
-
/**
|
|
400
|
+
/**
|
|
401
|
+
* This function gives a port of a box of height `h` after the turn. Each value of the port turns, also a port with only `x` or only `y`.
|
|
402
|
+
* The start of a side is its top end or its left end. Thus `left` goes to `top` and `right` goes to `bottom` with `along` changed to `1 - along`.
|
|
403
|
+
* `top` goes to `right` and `bottom` goes to `left` with the same `along`.
|
|
404
|
+
*/
|
|
302
405
|
function turnPort(port, h) {
|
|
303
|
-
const { side, x, y, ...rest } = port;
|
|
406
|
+
const { side, x, y, along, ...rest } = port;
|
|
407
|
+
const flips = side === "left" || side === "right";
|
|
304
408
|
return {
|
|
305
409
|
...rest,
|
|
306
410
|
...side === void 0 ? {} : { side: turnSide(side) },
|
|
307
411
|
...y === void 0 ? {} : { x: h - y },
|
|
308
|
-
...x === void 0 ? {} : { y: x }
|
|
412
|
+
...x === void 0 ? {} : { y: x },
|
|
413
|
+
...along === void 0 ? {} : { along: flips ? 1 - along : along }
|
|
309
414
|
};
|
|
310
415
|
}
|
|
311
416
|
/** This function gives a margin after the turn: the value of each side moves one side clockwise. */
|
|
@@ -549,16 +654,27 @@ function countPortsOffBorder(boxes, eps = RECORD_EPS) {
|
|
|
549
654
|
/**
|
|
550
655
|
* The hard rules of a pack, with the shape kinds that each rule supports.
|
|
551
656
|
* `no-overlap` and `in-container` test the bounding box of a shape. That count is exact only for a `rect`.
|
|
657
|
+
* For the other kinds, the count is safe: a shape is inside its bounding box, so the count is an upper bound. A count of 0 tells that the rule has no break.
|
|
552
658
|
* `locked-kept` and `complete` read only the positions and the ids, so they support each kind.
|
|
553
659
|
*/
|
|
554
660
|
const PACK_HARD_RULES = Object.freeze([
|
|
555
661
|
{
|
|
556
662
|
id: "no-overlap",
|
|
557
|
-
shapes: ["rect"]
|
|
663
|
+
shapes: ["rect"],
|
|
664
|
+
safe: [
|
|
665
|
+
"polygon",
|
|
666
|
+
"circle",
|
|
667
|
+
"free"
|
|
668
|
+
]
|
|
558
669
|
},
|
|
559
670
|
{
|
|
560
671
|
id: "in-container",
|
|
561
|
-
shapes: ["rect"]
|
|
672
|
+
shapes: ["rect"],
|
|
673
|
+
safe: [
|
|
674
|
+
"polygon",
|
|
675
|
+
"circle",
|
|
676
|
+
"free"
|
|
677
|
+
]
|
|
562
678
|
},
|
|
563
679
|
{
|
|
564
680
|
id: "locked-kept",
|
|
@@ -764,6 +880,7 @@ function packHard(input, output, tolerance = DEFAULT_TOLERANCE.overlap, options
|
|
|
764
880
|
* This function gives the problems of an input file. An empty list tells that the file is good.
|
|
765
881
|
* A problem is a bad input, not a break of a hard rule. Thus an engine refuses the input.
|
|
766
882
|
* In `pairGaps`, an unknown box, a pair of one box, a repeated pair in either order, a bad `gap` and bad `lanes` are problems.
|
|
883
|
+
* A port with `along` and no `side`, with `x` or `y`, or with a value outside [0, 1] is a problem.
|
|
767
884
|
*/
|
|
768
885
|
function checkInput(input) {
|
|
769
886
|
const problems = [];
|
|
@@ -775,12 +892,23 @@ function checkInput(input) {
|
|
|
775
892
|
if (box.locked === true && (box.x === void 0 || box.y === void 0)) problems.push(`box ${box.id}: a locked box has no position`);
|
|
776
893
|
const shape = shapeKindProblem(box);
|
|
777
894
|
if (shape !== void 0) problems.push(shape);
|
|
895
|
+
for (const port of box.ports ?? []) problems.push(...alongProblems(box.id, port));
|
|
778
896
|
}
|
|
779
897
|
if (countPortsOffBorder(input.boxes) > 0) problems.push("a fixed port is not on the border of its box");
|
|
780
898
|
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
899
|
problems.push(...pairGapProblems(input.pairGaps, ids));
|
|
782
900
|
return problems;
|
|
783
901
|
}
|
|
902
|
+
/** This function gives the problems of the `along` of a port: no side, a value outside [0, 1], or `x` or `y` with it. A port without `along` has no problem. */
|
|
903
|
+
function alongProblems(boxId, port) {
|
|
904
|
+
if (port.along === void 0) return [];
|
|
905
|
+
const at = `box ${boxId} port ${port.id}`;
|
|
906
|
+
const problems = [];
|
|
907
|
+
if (port.side === void 0) problems.push(`${at}: a port with along has no side`);
|
|
908
|
+
if (typeof port.along !== "number" || !Number.isFinite(port.along) || port.along < 0 || port.along > 1) problems.push(`${at}: the along value ${String(port.along)} is not a finite number from 0 to 1`);
|
|
909
|
+
if (port.x !== void 0 || port.y !== void 0) problems.push(`${at}: a port with along also has x or y`);
|
|
910
|
+
return problems;
|
|
911
|
+
}
|
|
784
912
|
/** This function gives the problems of the `pairGaps` of an input. `ids` has the ids of the boxes. */
|
|
785
913
|
function pairGapProblems(pairGaps, ids) {
|
|
786
914
|
if (pairGaps === void 0) return [];
|
|
@@ -2037,6 +2165,8 @@ function optionProblems(stage, o) {
|
|
|
2037
2165
|
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
2166
|
const pool = o.members.pool;
|
|
2039
2167
|
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`]);
|
|
2168
|
+
const view = o.members.view;
|
|
2169
|
+
if (view !== void 0 && view !== "bounds") out.push(["view", `${at} has the member view ${show(view)}, not "bounds"`]);
|
|
2040
2170
|
}
|
|
2041
2171
|
return out;
|
|
2042
2172
|
}
|
|
@@ -2055,6 +2185,15 @@ function supportsProblems(supports, at) {
|
|
|
2055
2185
|
}
|
|
2056
2186
|
/** This function tells if an option supports each shape kind of the list. */
|
|
2057
2187
|
const supportsShapes = (o, shapes) => shapes === void 0 || o.supports?.shape === void 0 || shapes.every((k) => o.supports.shape.includes(k));
|
|
2188
|
+
/** The shape kinds that the members of an option with `view: "bounds"` see: a bounding box is a `rect`. */
|
|
2189
|
+
const BOUNDS_VIEW = Object.freeze(["rect"]);
|
|
2190
|
+
/**
|
|
2191
|
+
* This function gives the shape kinds of the members of a meta option. With `view: "bounds"`, a list with a kind becomes `BOUNDS_VIEW`.
|
|
2192
|
+
* Without a list, or with an empty list, the value does not change.
|
|
2193
|
+
*/
|
|
2194
|
+
const memberShapes = (rule, shapes) => rule.view === "bounds" && shapes !== void 0 && shapes.length > 0 ? BOUNDS_VIEW : shapes;
|
|
2195
|
+
/** This function tells if two lists have the same shape kinds. */
|
|
2196
|
+
const sameKinds = (a, b) => a.every((k) => b.includes(k)) && b.every((k) => a.includes(k));
|
|
2058
2197
|
/** This function gives the problems of the params of one stage choice. */
|
|
2059
2198
|
function choiceParamProblems(o, params, at) {
|
|
2060
2199
|
if (params !== void 0 && !isObject(params)) return [["config", `the params of ${at} are not an object`]];
|
|
@@ -2128,7 +2267,7 @@ function configProblems(space, config, member, shapes) {
|
|
|
2128
2267
|
out.push(["depth", `a member has the meta option ${s.name}.${o.id}. The depth of a meta config is 1`]);
|
|
2129
2268
|
continue;
|
|
2130
2269
|
}
|
|
2131
|
-
out.push(...memberProblems(partOf(space, s.name).space, o.members, c["members"], at, shapes));
|
|
2270
|
+
out.push(...memberProblems(partOf(space, s.name).space, o.members, c["members"], at, memberShapes(o.members, shapes)));
|
|
2132
2271
|
}
|
|
2133
2272
|
for (const c of space.constraints ?? []) if (!holds(c, chosen)) out.push(["constraint", `the config breaks the constraint ${describe(c)}`]);
|
|
2134
2273
|
return out;
|
|
@@ -2191,6 +2330,7 @@ const toBreaks = (problems, inputs = []) => problems.map(([claim, detail]) => ({
|
|
|
2191
2330
|
*
|
|
2192
2331
|
* A member list that is not an entry of the `pool` of its option gives the claim `pool`.
|
|
2193
2332
|
* 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.
|
|
2333
|
+
* A member of an option with `view: "bounds"` gets the check with the kind `rect`.
|
|
2194
2334
|
*/
|
|
2195
2335
|
function checkConfig(space, config, options = {}) {
|
|
2196
2336
|
return toBreaks(configProblems(space, config, false, options.shapes));
|
|
@@ -2451,7 +2591,9 @@ function combos(space, levels, meta, limit) {
|
|
|
2451
2591
|
for (const s of stages) for (const o of s.options) {
|
|
2452
2592
|
if (o.members === void 0) continue;
|
|
2453
2593
|
const choices = new Map(stages.map((x) => [x.name, x === s ? o : null]));
|
|
2454
|
-
const
|
|
2594
|
+
const own = memberSpaces.get(o);
|
|
2595
|
+
const from = own === void 0 ? base : baseCount(own, levels, limit);
|
|
2596
|
+
const lists = o.members.pool === void 0 ? memberLists(from, o.members, limit) : BigInt(o.members.pool.length);
|
|
2455
2597
|
if (keeps(space, choices)) out.push({
|
|
2456
2598
|
choices,
|
|
2457
2599
|
base: false,
|
|
@@ -2476,6 +2618,8 @@ function spaceSize(space, options) {
|
|
|
2476
2618
|
* This function gives the space with only the options that support each shape kind of `shapes`. Without `shapes`, it gives the same space.
|
|
2477
2619
|
* A stage can lose all of its options. Then the space has no config with that stage.
|
|
2478
2620
|
* A pool keeps only the entries whose members fit the smaller space. A meta option without such an entry goes out.
|
|
2621
|
+
*
|
|
2622
|
+
* The members of an option with `view: "bounds"` come from the space for the kinds of `memberShapes`. `memberSpaces` keeps that space.
|
|
2479
2623
|
*/
|
|
2480
2624
|
function restrict(space, shapes) {
|
|
2481
2625
|
if (shapes === void 0) return space;
|
|
@@ -2488,16 +2632,23 @@ function restrict(space, shapes) {
|
|
|
2488
2632
|
...space.parts === void 0 ? {} : { parts: space.parts.map((p) => restrict(p, shapes)) }
|
|
2489
2633
|
};
|
|
2490
2634
|
const withPool = (s, o) => {
|
|
2491
|
-
if (o.members
|
|
2492
|
-
const
|
|
2635
|
+
if (o.members === void 0) return [o];
|
|
2636
|
+
const inner = memberShapes(o.members, shapes);
|
|
2637
|
+
const own = sameKinds(inner, shapes) ? void 0 : restrict(partOf(space, s.name).space, inner);
|
|
2638
|
+
const mark = (option) => {
|
|
2639
|
+
if (own !== void 0) memberSpaces.set(option, own);
|
|
2640
|
+
return [option];
|
|
2641
|
+
};
|
|
2642
|
+
if (o.members.pool === void 0) return own === void 0 ? [o] : mark({ ...o });
|
|
2643
|
+
const part = own ?? partOf(kept, s.name).space;
|
|
2493
2644
|
const pool = o.members.pool.filter((entry) => fits(part, entry));
|
|
2494
|
-
return pool.length === 0 ? [] :
|
|
2645
|
+
return pool.length === 0 ? [] : mark({
|
|
2495
2646
|
...o,
|
|
2496
2647
|
members: {
|
|
2497
2648
|
...o.members,
|
|
2498
2649
|
pool
|
|
2499
2650
|
}
|
|
2500
|
-
}
|
|
2651
|
+
});
|
|
2501
2652
|
};
|
|
2502
2653
|
return {
|
|
2503
2654
|
...kept,
|
|
@@ -2507,6 +2658,10 @@ function restrict(space, shapes) {
|
|
|
2507
2658
|
}))
|
|
2508
2659
|
};
|
|
2509
2660
|
}
|
|
2661
|
+
/** The space of the members of a meta option of a restricted space, when it is not the part of the option. Only `view: "bounds"` makes such a space. */
|
|
2662
|
+
const memberSpaces = /* @__PURE__ */ new WeakMap();
|
|
2663
|
+
/** This function gives the count of the base configs of a space that is known to be good. */
|
|
2664
|
+
const baseCount = (space, levels, limit) => combos(space, levels, false, limit).reduce((sum, c) => sum + c.weight, 0n);
|
|
2510
2665
|
/** This function throws a `RangeError` for a list of shape kinds with a kind that is not declared. */
|
|
2511
2666
|
function assertShapes(shapes) {
|
|
2512
2667
|
if (shapes === void 0) return;
|
|
@@ -2583,13 +2738,18 @@ function sampleValue(p, u) {
|
|
|
2583
2738
|
}
|
|
2584
2739
|
/** This function gives a seed of 32 bits from a seed and a step name. */
|
|
2585
2740
|
const subSeed = (seed, step) => Math.floor(stepRandom(seed, step)() * 4294967296);
|
|
2586
|
-
/**
|
|
2587
|
-
function
|
|
2741
|
+
/**
|
|
2742
|
+
* This function gives a sample of a space: Latin hypercube strata over the options, the params and the member counts.
|
|
2743
|
+
* `canon` is the space of `canonicalConfig`: the space before a shape filter. A member of an option with `view: "bounds"` can have an option that the filter removed.
|
|
2744
|
+
*/
|
|
2745
|
+
function sampleConfigs(space, n, seed, meta, maxTries, canon = space) {
|
|
2588
2746
|
const stages = space.stages;
|
|
2589
2747
|
const prefix = prefixes(space);
|
|
2748
|
+
const bounded = new Set(stages.filter((s) => meta && s.options.some((o) => memberSpaces.has(o))).map((s) => prefix.get(s.name)));
|
|
2590
2749
|
const lists = stages.map((s) => {
|
|
2591
2750
|
const list = s.options.filter((o) => meta || o.members === void 0);
|
|
2592
2751
|
if (s.optional) list.push(null);
|
|
2752
|
+
if (list.length === 0 && bounded.has(prefix.get(s.name))) list.push(null);
|
|
2593
2753
|
if (list.length === 0) throw new Error(`The stage ${s.name} of the space ${space.engine} has no option for a sample. A shape filter can remove all of its options.`);
|
|
2594
2754
|
return list;
|
|
2595
2755
|
});
|
|
@@ -2608,7 +2768,7 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
|
|
|
2608
2768
|
const m = metaPart.get(prefix.get(s.name));
|
|
2609
2769
|
if (m !== void 0 && m !== s.name) choices.set(s.name, null);
|
|
2610
2770
|
}
|
|
2611
|
-
if (keeps(space, choices)) points.push(choices);
|
|
2771
|
+
if (stages.every((s) => s.optional === true || choices.get(s.name) !== null || metaPart.has(prefix.get(s.name))) && keeps(space, choices)) points.push(choices);
|
|
2612
2772
|
}
|
|
2613
2773
|
}
|
|
2614
2774
|
const values = points.map(() => stages.map(() => ({})));
|
|
@@ -2633,7 +2793,7 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
|
|
|
2633
2793
|
});
|
|
2634
2794
|
continue;
|
|
2635
2795
|
}
|
|
2636
|
-
const part = partOf(space, s.name).space;
|
|
2796
|
+
const part = memberSpaces.get(o) ?? partOf(space, s.name).space;
|
|
2637
2797
|
const poolSize = Math.max(n, 4 * o.members.max, 8);
|
|
2638
2798
|
const seen = /* @__PURE__ */ new Set();
|
|
2639
2799
|
const pool = sampleConfigs(part, poolSize, subSeed(seed, `pool:${s.name}:${o.id}`), false, Math.max(1e3, 100 * poolSize)).filter((c) => {
|
|
@@ -2663,7 +2823,7 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
|
|
|
2663
2823
|
members: members[i][k]
|
|
2664
2824
|
};
|
|
2665
2825
|
});
|
|
2666
|
-
return canonicalConfig(
|
|
2826
|
+
return canonicalConfig(canon, {
|
|
2667
2827
|
space: space.engine,
|
|
2668
2828
|
stages: out
|
|
2669
2829
|
});
|
|
@@ -2691,7 +2851,7 @@ function enumerateSpace(space, options) {
|
|
|
2691
2851
|
const meta = options.meta ?? "skip";
|
|
2692
2852
|
if (meta !== "skip" && meta !== "sample") throw new RangeError(`The option meta is ${String(meta)}, not skip or sample.`);
|
|
2693
2853
|
if (options.n === 0) return [];
|
|
2694
|
-
return sampleConfigs(restrict(space, options.shapes), options.n, options.seed, meta === "sample", options.maxTries ?? Math.max(1e3, 100 * options.n));
|
|
2854
|
+
return sampleConfigs(restrict(space, options.shapes), options.n, options.seed, meta === "sample", options.maxTries ?? Math.max(1e3, 100 * options.n), space);
|
|
2695
2855
|
}
|
|
2696
2856
|
/**
|
|
2697
2857
|
* This function gives the shape kinds that a space accepts, in the order of `SHAPE_KINDS`. The value comes from the `supports` of the options.
|
|
@@ -2993,6 +3153,8 @@ function verdict(records, options) {
|
|
|
2993
3153
|
};
|
|
2994
3154
|
}
|
|
2995
3155
|
// src/families.ts
|
|
3156
|
+
/** The name of the output field that lists the fields that `generate` of a registry removed. */
|
|
3157
|
+
const INVALIDATED_FIELD = "invalidated";
|
|
2996
3158
|
/** The random numbers of the default factory: `mulberry32` of `random.ts`. Its stream is pinned in `families.test.ts`. */
|
|
2997
3159
|
const DEFAULT_RNG = mulberry32;
|
|
2998
3160
|
/** 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. */
|
|
@@ -3141,15 +3303,135 @@ const CORPUS_DIMENSIONS = Object.freeze({
|
|
|
3141
3303
|
value: 0
|
|
3142
3304
|
}, "The free space between two items, in units.", [P, J])
|
|
3143
3305
|
});
|
|
3144
|
-
/**
|
|
3306
|
+
/**
|
|
3307
|
+
* This function gives a dimension of `CORPUS_DIMENSIONS`, with the fields of `patch` over it. It throws a `RangeError` for an unknown name.
|
|
3308
|
+
* With `supported`, `invalidates` or `precision` in the patch, it also throws a `RangeError` for a bad dimension, as `ownerDimension` does.
|
|
3309
|
+
*/
|
|
3145
3310
|
function sharedDimension(name, patch = {}) {
|
|
3146
3311
|
const base = Object.hasOwn(CORPUS_DIMENSIONS, name) ? CORPUS_DIMENSIONS[name] : void 0;
|
|
3147
3312
|
if (base === void 0) throw new RangeError(`The shared dimension table has no dimension "${name}".`);
|
|
3148
|
-
|
|
3313
|
+
const dim = Object.freeze({
|
|
3149
3314
|
...base,
|
|
3150
3315
|
...patch,
|
|
3151
3316
|
name
|
|
3152
3317
|
});
|
|
3318
|
+
if (patch.supported !== void 0 || patch.invalidates !== void 0 || patch.precision !== void 0) throwProblems(`sharedDimension: the dimension "${name}"`, ownProblems(dim));
|
|
3319
|
+
return dim;
|
|
3320
|
+
}
|
|
3321
|
+
/**
|
|
3322
|
+
* This function gives a dimension that only its owner declares, for example `cut` of a family of `packing`.
|
|
3323
|
+
* It throws a `RangeError` for a name of `CORPUS_DIMENSIONS`, the reserved name `kind`, or a bad default, level, `supported`, `invalidates` or `precision`.
|
|
3324
|
+
* The registry examines the `when` of the dimension, because it needs the earlier dimensions.
|
|
3325
|
+
*/
|
|
3326
|
+
function ownerDimension(name, spec) {
|
|
3327
|
+
const where = `ownerDimension: the dimension "${name}"`;
|
|
3328
|
+
if (typeof name !== "string" || name === "") throw new RangeError("ownerDimension: the name of a dimension is a text that is not empty.");
|
|
3329
|
+
if (name === "kind") throw new RangeError(`${where} has a reserved name: the cell of \`cellOf\` has the \`familyKind\` of the family in \`kind\`.`);
|
|
3330
|
+
if (Object.hasOwn(CORPUS_DIMENSIONS, name)) throw new RangeError(`${where} is in the shared dimension table. Use sharedDimension("${name}") for it.`);
|
|
3331
|
+
const dim = Object.freeze({
|
|
3332
|
+
...spec,
|
|
3333
|
+
name
|
|
3334
|
+
});
|
|
3335
|
+
throwProblems(where, ownProblems(dim));
|
|
3336
|
+
return dim;
|
|
3337
|
+
}
|
|
3338
|
+
/** This function throws a `RangeError` with each problem of a list. An empty list throws nothing. */
|
|
3339
|
+
function throwProblems(where, problems) {
|
|
3340
|
+
if (problems.length > 0) throw new RangeError(`${where} is not good: ${problems.join("; ")}.`);
|
|
3341
|
+
}
|
|
3342
|
+
/** This function tells if a value of the stable form of a dimension is in its `supported` values. Without `supported`, each value is in them. */
|
|
3343
|
+
function inSupported(dim, value) {
|
|
3344
|
+
const s = dim.supported;
|
|
3345
|
+
if (s === void 0) return true;
|
|
3346
|
+
if (isSupportedList(s)) return s.some((v) => paramValue(dim, v) === value);
|
|
3347
|
+
if (typeof value !== "number") return false;
|
|
3348
|
+
return (s.min === void 0 || value >= s.min) && (s.max === void 0 || value <= s.max);
|
|
3349
|
+
}
|
|
3350
|
+
/** This function tells if `SupportedValues` has the list form. */
|
|
3351
|
+
const isSupportedList = (s) => Array.isArray(s);
|
|
3352
|
+
/**
|
|
3353
|
+
* This function tells if a family accepts a value of a dimension. A site uses it to show the values that a family does not support.
|
|
3354
|
+
* It gives `false` for a bad value, and for a value outside `supported`. Without `supported`, it gives `true` for each good value.
|
|
3355
|
+
*/
|
|
3356
|
+
function dimensionSupports(dim, value) {
|
|
3357
|
+
if (paramProblem(dim, value) !== void 0) return false;
|
|
3358
|
+
return inSupported(dim, paramValue(dim, value));
|
|
3359
|
+
}
|
|
3360
|
+
/** This function gives the text of the supported values of a dimension, for a message. */
|
|
3361
|
+
function supportedText(dim) {
|
|
3362
|
+
const s = dim.supported;
|
|
3363
|
+
if (s === void 0) return "each value";
|
|
3364
|
+
if (isSupportedList(s)) return s.map((v) => JSON.stringify(v)).join(", ");
|
|
3365
|
+
return `the values from ${s.min ?? dim.min} to ${s.max ?? dim.max}`;
|
|
3366
|
+
}
|
|
3367
|
+
/** This function gives the problems of one dimension that need no other dimension: the default, the levels, `supported`, `invalidates` and `precision`. */
|
|
3368
|
+
function ownProblems(d) {
|
|
3369
|
+
const out = [];
|
|
3370
|
+
const bad = paramProblem(d, d.default);
|
|
3371
|
+
if (bad !== void 0) out.push(`the default of "${d.name}" is bad: ${bad}`);
|
|
3372
|
+
for (const v of d.levels ?? []) {
|
|
3373
|
+
const p = paramProblem(d, v);
|
|
3374
|
+
if (p !== void 0) out.push(`a level of "${d.name}" is bad: ${p}`);
|
|
3375
|
+
}
|
|
3376
|
+
out.push(...supportProblems(d, bad === void 0));
|
|
3377
|
+
out.push(...invalidatesProblems(d));
|
|
3378
|
+
out.push(...precisionProblems(d));
|
|
3379
|
+
return out;
|
|
3380
|
+
}
|
|
3381
|
+
/** This function gives the problems of the `supported` values of a dimension. `goodDefault` tells that the default is a good value. */
|
|
3382
|
+
function supportProblems(d, goodDefault) {
|
|
3383
|
+
const s = d.supported;
|
|
3384
|
+
if (s === void 0) return [];
|
|
3385
|
+
const out = [];
|
|
3386
|
+
const numeric = d.kind === "int" || d.kind === "real";
|
|
3387
|
+
if (isSupportedList(s)) {
|
|
3388
|
+
if (numeric) return [`the supported values of "${d.name}" are a range with min and max, because the dimension is numeric`];
|
|
3389
|
+
if (s.length === 0) return [`the supported values of "${d.name}" are an empty list`];
|
|
3390
|
+
for (const v of s) {
|
|
3391
|
+
const p = paramProblem(d, v);
|
|
3392
|
+
if (p !== void 0) out.push(`a supported value of "${d.name}" is bad: ${p}`);
|
|
3393
|
+
}
|
|
3394
|
+
if (new Set(s).size !== s.length) out.push(`the supported values of "${d.name}" have a value two times`);
|
|
3395
|
+
} else {
|
|
3396
|
+
if (s === null || typeof s !== "object") return [`the supported values of "${d.name}" are a list or a range`];
|
|
3397
|
+
if (!numeric) return [`the supported values of "${d.name}" are a list, because the dimension is not numeric`];
|
|
3398
|
+
for (const end of [s.min, s.max]) {
|
|
3399
|
+
if (end === void 0) continue;
|
|
3400
|
+
const p = paramProblem(d, end);
|
|
3401
|
+
if (p !== void 0) out.push(`an end of the supported range of "${d.name}" is bad: ${p}`);
|
|
3402
|
+
}
|
|
3403
|
+
if (s.min !== void 0 && s.max !== void 0 && s.min > s.max) out.push(`the supported range of "${d.name}" has a min above its max`);
|
|
3404
|
+
}
|
|
3405
|
+
if (out.length > 0) return out;
|
|
3406
|
+
if (goodDefault && !inSupported(d, paramValue(d, d.default))) out.push(`the default of "${d.name}" is not a supported value`);
|
|
3407
|
+
for (const v of d.levels ?? []) if (paramProblem(d, v) === void 0 && !inSupported(d, paramValue(d, v))) out.push(`the level ${JSON.stringify(v)} of "${d.name}" is not a supported value`);
|
|
3408
|
+
return out;
|
|
3409
|
+
}
|
|
3410
|
+
/** This function gives the problems of the `invalidates` fields of a dimension. */
|
|
3411
|
+
function invalidatesProblems(d) {
|
|
3412
|
+
const list = d.invalidates;
|
|
3413
|
+
if (list === void 0) return [];
|
|
3414
|
+
if (!Array.isArray(list) || !list.every((f) => typeof f === "string" && f !== "")) return [`the invalidates of "${d.name}" is not a list of field names`];
|
|
3415
|
+
const out = [];
|
|
3416
|
+
if (new Set(list).size !== list.length) out.push(`the invalidates of "${d.name}" has a field two times`);
|
|
3417
|
+
for (const f of ["hash", INVALIDATED_FIELD]) if (list.includes(f)) out.push(`the invalidates of "${d.name}" has the field "${f}", which the registry writes`);
|
|
3418
|
+
return out;
|
|
3419
|
+
}
|
|
3420
|
+
/** This function gives the problems of the `precision` of a dimension. A bad value and a dimension that is not numeric are problems. */
|
|
3421
|
+
function precisionProblems(d) {
|
|
3422
|
+
const p = d.precision;
|
|
3423
|
+
if (p === void 0) return [];
|
|
3424
|
+
if (typeof p !== "number" || !Number.isInteger(p) || p < 0) return [`the precision of "${d.name}" is ${JSON.stringify(p)}, not a whole number of 0 or more`];
|
|
3425
|
+
if (d.kind !== "int" && d.kind !== "real") return [`the precision of "${d.name}" is for a numeric dimension, but the dimension is a ${d.kind}`];
|
|
3426
|
+
return [];
|
|
3427
|
+
}
|
|
3428
|
+
/** This function rounds a number to `places` decimal places: `Math.round(v * 10 ** places) / 10 ** places`. */
|
|
3429
|
+
const roundTo = (v, places) => Math.round(v * 10 ** places) / 10 ** places;
|
|
3430
|
+
/** This function tells if a value is the default of a dimension. With `precision`, it compares the two numbers after it rounds them. */
|
|
3431
|
+
function isDefault(d, value) {
|
|
3432
|
+
const def = paramValue(d, d.default);
|
|
3433
|
+
if (d.precision === void 0 || typeof value !== "number" || typeof def !== "number") return value === def;
|
|
3434
|
+
return roundTo(value, d.precision) === roundTo(def, d.precision);
|
|
3153
3435
|
}
|
|
3154
3436
|
/** This function tells if a log scale of a range starts at 0. Then the scale is the log of `1 + x - min`. */
|
|
3155
3437
|
const fromZero = (d) => d.scale === "log" && (d.min ?? 0) <= 0;
|
|
@@ -3178,30 +3460,45 @@ function dimensionUnit(dim, value) {
|
|
|
3178
3460
|
if (fromZero(dim)) return Math.log1p(v - min) / Math.log1p(max - min);
|
|
3179
3461
|
return (Math.log(v) - Math.log(min)) / (Math.log(max) - Math.log(min));
|
|
3180
3462
|
}
|
|
3463
|
+
/** This function gives the range of the supported values of a numeric dimension: its own `min` and `max` in place of the ends of `supported`. */
|
|
3464
|
+
function supportedSpan(dim) {
|
|
3465
|
+
const s = dim.supported;
|
|
3466
|
+
const range = s === void 0 || isSupportedList(s) ? {} : s;
|
|
3467
|
+
return {
|
|
3468
|
+
min: range.min ?? dim.min,
|
|
3469
|
+
max: range.max ?? dim.max
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3181
3472
|
/**
|
|
3182
3473
|
* 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
3474
|
* With `steps`, a numeric dimension gives `steps` points from `min` to `max` on its scale. Else it gives its default.
|
|
3475
|
+
* With `supported`, an `enum` and a `bool` give only the supported values, and the points of a numeric dimension stay in the supported range.
|
|
3184
3476
|
*/
|
|
3185
3477
|
function levelsOf(dim, steps) {
|
|
3186
3478
|
if (dim.levels !== void 0) return [...dim.levels];
|
|
3187
|
-
if (dim.kind === "enum" || dim.kind === "bool"
|
|
3479
|
+
if (dim.kind === "enum" || dim.kind === "bool") return paramLevels(dim, 1).filter((v) => inSupported(dim, paramValue(dim, v)));
|
|
3480
|
+
if (steps === void 0) return paramLevels(dim, 1);
|
|
3481
|
+
if (dim.supported !== void 0) {
|
|
3482
|
+
const { supported: _supported, ...rest } = dim;
|
|
3483
|
+
const span = supportedSpan(dim);
|
|
3484
|
+
return levelsOf({
|
|
3485
|
+
...rest,
|
|
3486
|
+
...span.min === void 0 ? {} : { min: span.min },
|
|
3487
|
+
...span.max === void 0 ? {} : { max: span.max }
|
|
3488
|
+
}, steps);
|
|
3489
|
+
}
|
|
3188
3490
|
if (!fromZero(dim)) return paramLevels(dim, steps);
|
|
3189
3491
|
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
3492
|
if (steps === 1) return [paramValue(dim, dim.default)];
|
|
3191
3493
|
return [...new Set(Array.from({ length: steps }, (_, j) => dimensionAt(dim, j / (steps - 1))))];
|
|
3192
3494
|
}
|
|
3193
|
-
/** This function gives the problems of the dimensions of a family: a repeated name, a bad default,
|
|
3495
|
+
/** This function gives the problems of the dimensions of a family: a repeated name, a bad default, level, `supported`, `invalidates` or `when`. */
|
|
3194
3496
|
function dimensionProblems(dims) {
|
|
3195
3497
|
const out = [];
|
|
3196
3498
|
const names = dims.map((d) => d.name);
|
|
3197
3499
|
dims.forEach((d, i) => {
|
|
3198
3500
|
if (names.indexOf(d.name) !== i) out.push(`the dimension "${d.name}" is in the list two times`);
|
|
3199
|
-
|
|
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
|
-
}
|
|
3501
|
+
out.push(...ownProblems(d));
|
|
3205
3502
|
if (d.when === void 0) return;
|
|
3206
3503
|
const j = names.indexOf(d.when.param);
|
|
3207
3504
|
if (j < 0 || j >= i) out.push(`the when of "${d.name}" names "${d.when.param}", which is not an earlier dimension`);
|
|
@@ -3213,9 +3510,11 @@ function dimensionProblems(dims) {
|
|
|
3213
3510
|
/**
|
|
3214
3511
|
* 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
3512
|
* 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.
|
|
3513
|
+
*
|
|
3216
3514
|
* The function throws a `RangeError` for an unknown dimension or a bad value, also for a bad value of an inactive dimension.
|
|
3515
|
+
* It also throws a `RangeError` for a value outside `supported`, with the text "not supported by" and `by`.
|
|
3217
3516
|
*/
|
|
3218
|
-
function resolveDimensions(dimensions, dims = {}, where = "The family") {
|
|
3517
|
+
function resolveDimensions(dimensions, dims = {}, where = "The family", by = "the family") {
|
|
3219
3518
|
const known = new Set(dimensions.map((d) => d.name));
|
|
3220
3519
|
for (const key of Object.keys(dims)) if (!known.has(key)) throw new RangeError(`${where} has no dimension "${key}".`);
|
|
3221
3520
|
const active = {};
|
|
@@ -3225,6 +3524,7 @@ function resolveDimensions(dimensions, dims = {}, where = "The family") {
|
|
|
3225
3524
|
if (given !== void 0) {
|
|
3226
3525
|
const bad = paramProblem(d, given);
|
|
3227
3526
|
if (bad !== void 0) throw new RangeError(`${where}: the dimension "${d.name}" is bad: ${bad}.`);
|
|
3527
|
+
if (!inSupported(d, paramValue(d, given))) throw new RangeError(`${where}: the value ${JSON.stringify(given)} of the dimension "${d.name}" is not supported by ${by}. The supported values are ${supportedText(d)}.`);
|
|
3228
3528
|
}
|
|
3229
3529
|
const on = paramActive(d, active);
|
|
3230
3530
|
const value = paramValue(d, on && given !== void 0 ? given : d.default);
|
|
@@ -3274,7 +3574,7 @@ function countOf(dimensions, levels, i = 0, values = {}) {
|
|
|
3274
3574
|
return sum;
|
|
3275
3575
|
}
|
|
3276
3576
|
/** 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) {
|
|
3577
|
+
function sweepLevels(dimensions, options, where, by) {
|
|
3278
3578
|
const names = new Set(dimensions.map((d) => d.name));
|
|
3279
3579
|
for (const name of [...options.sweep ?? [], ...Object.keys(options.levels ?? {})]) if (!names.has(name)) throw new RangeError(`${where} has no dimension "${name}".`);
|
|
3280
3580
|
return dimensions.map((d) => {
|
|
@@ -3283,10 +3583,49 @@ function sweepLevels(dimensions, options, where) {
|
|
|
3283
3583
|
for (const v of lv) {
|
|
3284
3584
|
const bad = paramProblem(d, v);
|
|
3285
3585
|
if (bad !== void 0) throw new RangeError(`${where}: a level of "${d.name}" is bad: ${bad}.`);
|
|
3586
|
+
if (!inSupported(d, paramValue(d, v))) throw new RangeError(`${where}: the level ${JSON.stringify(v)} of "${d.name}" is not supported by ${by}.`);
|
|
3286
3587
|
}
|
|
3287
3588
|
return lv;
|
|
3288
3589
|
});
|
|
3289
3590
|
}
|
|
3591
|
+
/**
|
|
3592
|
+
* This function gives the output fields that the values make wrong: the `invalidates` of each dimension with a value other than its default, sorted.
|
|
3593
|
+
* A dimension with `precision` compares the rounded value with the rounded default.
|
|
3594
|
+
*/
|
|
3595
|
+
function invalidFields(dimensions, full) {
|
|
3596
|
+
const out = /* @__PURE__ */ new Set();
|
|
3597
|
+
for (const d of dimensions) {
|
|
3598
|
+
if (d.invalidates === void 0 || d.invalidates.length === 0) continue;
|
|
3599
|
+
if (!isDefault(d, full[d.name])) for (const f of d.invalidates) out.add(f);
|
|
3600
|
+
}
|
|
3601
|
+
return [...out].sort(byCode);
|
|
3602
|
+
}
|
|
3603
|
+
/** This function compares two strings by their UTF-16 code units. */
|
|
3604
|
+
const byCode = (x, y) => x < y ? -1 : x > y ? 1 : 0;
|
|
3605
|
+
/**
|
|
3606
|
+
* This function removes the fields of `fields` from an output, and adds their names to its field `invalidated`, sorted.
|
|
3607
|
+
* Only a field of the output counts. The `hash` of text gets the value of `hash` again. An output with none of the fields is the same object.
|
|
3608
|
+
*/
|
|
3609
|
+
function dropFields(output, fields, hash, where) {
|
|
3610
|
+
if (fields.length === 0) return output;
|
|
3611
|
+
if (output === null || typeof output !== "object" || Array.isArray(output)) throw new RangeError(`${where} invalidates the fields ${fields.join(", ")}, but its output is not an object.`);
|
|
3612
|
+
const o = output;
|
|
3613
|
+
const dropped = fields.filter((f) => Object.hasOwn(o, f));
|
|
3614
|
+
if (dropped.length === 0) return output;
|
|
3615
|
+
const before = o[INVALIDATED_FIELD];
|
|
3616
|
+
const prior = Array.isArray(before) ? before.filter((f) => typeof f === "string") : [];
|
|
3617
|
+
const body = {};
|
|
3618
|
+
for (const key of Object.keys(o)) if (key !== "hash" && key !== "invalidated" && !dropped.includes(key)) body[key] = o[key];
|
|
3619
|
+
body[INVALIDATED_FIELD] = [.../* @__PURE__ */ new Set([...prior, ...dropped])].sort(byCode);
|
|
3620
|
+
if (typeof o["hash"] !== "string") return Object.hasOwn(o, "hash") ? {
|
|
3621
|
+
...body,
|
|
3622
|
+
hash: o["hash"]
|
|
3623
|
+
} : body;
|
|
3624
|
+
return {
|
|
3625
|
+
...body,
|
|
3626
|
+
hash: hash(body)
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3290
3629
|
/** 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
3630
|
function activation(dimensions, dim) {
|
|
3292
3631
|
const out = {};
|
|
@@ -3322,6 +3661,8 @@ function createFamilyRegistry(families = []) {
|
|
|
3322
3661
|
const { kind: _kind, ...rest } = dims;
|
|
3323
3662
|
return rest;
|
|
3324
3663
|
};
|
|
3664
|
+
/** This function gives the full dimension values of an input of `f`. A message names the family as `familyKind` gives it. */
|
|
3665
|
+
const resolveIn = (f, dims) => resolveDimensions(f.dimensions, dims, label(f), familyKind(f.name, f.version));
|
|
3325
3666
|
const streamOf = (f, seed, full) => {
|
|
3326
3667
|
const rng = f.rng ?? DEFAULT_RNG;
|
|
3327
3668
|
return {
|
|
@@ -3348,46 +3689,49 @@ function createFamilyRegistry(families = []) {
|
|
|
3348
3689
|
families: () => [...list],
|
|
3349
3690
|
resolve(name, dims, version) {
|
|
3350
3691
|
const f = find(name, version);
|
|
3351
|
-
return
|
|
3692
|
+
return resolveIn(f, ownDims(f, dims));
|
|
3352
3693
|
},
|
|
3353
3694
|
generate(name, seed, dims, options = {}) {
|
|
3354
3695
|
checkSeed(seed);
|
|
3355
3696
|
const f = find(name, options.version);
|
|
3356
|
-
const full =
|
|
3697
|
+
const full = resolveIn(f, ownDims(f, dims));
|
|
3357
3698
|
const context = {
|
|
3358
3699
|
...streamOf(f, seed, full),
|
|
3359
3700
|
...options.set === void 0 ? {} : { set: options.set }
|
|
3360
3701
|
};
|
|
3361
|
-
|
|
3702
|
+
const output = f.generate(seed, full, context);
|
|
3703
|
+
const hash = f.hash ?? documentHash;
|
|
3704
|
+
return dropFields(output, invalidFields(f.dimensions, full), hash, label(f));
|
|
3362
3705
|
},
|
|
3363
3706
|
cellOf(name, dims, version) {
|
|
3364
3707
|
const f = find(name, version);
|
|
3365
3708
|
return {
|
|
3366
3709
|
kind: familyKind(f.name, f.version),
|
|
3367
|
-
...activeValues(f.dimensions,
|
|
3710
|
+
...activeValues(f.dimensions, resolveIn(f, ownDims(f, dims)))
|
|
3368
3711
|
};
|
|
3369
3712
|
},
|
|
3370
3713
|
*cells(name, options = {}) {
|
|
3371
3714
|
const f = find(name, options.version);
|
|
3372
|
-
for (const values of sweepOf(f.dimensions, sweepLevels(f.dimensions, options, label(f)))) yield
|
|
3715
|
+
for (const values of sweepOf(f.dimensions, sweepLevels(f.dimensions, options, label(f), familyKind(f.name, f.version)))) yield resolveIn(f, values);
|
|
3373
3716
|
},
|
|
3374
3717
|
cellCount(name, options = {}) {
|
|
3375
3718
|
const f = find(name, options.version);
|
|
3376
|
-
return countOf(f.dimensions, sweepLevels(f.dimensions, options, label(f)));
|
|
3719
|
+
return countOf(f.dimensions, sweepLevels(f.dimensions, options, label(f), familyKind(f.name, f.version)));
|
|
3377
3720
|
},
|
|
3378
3721
|
boundaryCells(name, version) {
|
|
3379
3722
|
const f = find(name, version);
|
|
3380
|
-
const base =
|
|
3723
|
+
const base = resolveIn(f, {});
|
|
3381
3724
|
const out = [base];
|
|
3382
3725
|
const seen = /* @__PURE__ */ new Set([JSON.stringify(base)]);
|
|
3383
3726
|
for (const d of f.dimensions) {
|
|
3384
3727
|
if (d.kind !== "int" && d.kind !== "real") continue;
|
|
3385
|
-
|
|
3728
|
+
const span = supportedSpan(d);
|
|
3729
|
+
for (const end of [span.min, span.max]) {
|
|
3386
3730
|
if (end === void 0) continue;
|
|
3387
|
-
const cell =
|
|
3731
|
+
const cell = resolveIn(f, {
|
|
3388
3732
|
...activation(f.dimensions, d),
|
|
3389
3733
|
[d.name]: end
|
|
3390
|
-
}
|
|
3734
|
+
});
|
|
3391
3735
|
const key = JSON.stringify(cell);
|
|
3392
3736
|
if (!seen.has(key)) {
|
|
3393
3737
|
seen.add(key);
|
|
@@ -3401,7 +3745,7 @@ function createFamilyRegistry(families = []) {
|
|
|
3401
3745
|
const f = find(name, version);
|
|
3402
3746
|
return (seed, dims) => {
|
|
3403
3747
|
checkSeed(seed);
|
|
3404
|
-
return streamOf(f, seed,
|
|
3748
|
+
return streamOf(f, seed, resolveIn(f, ownDims(f, dims))).random;
|
|
3405
3749
|
};
|
|
3406
3750
|
},
|
|
3407
3751
|
familyBy() {
|
|
@@ -3440,6 +3784,7 @@ function composeFamily(base, steps, options = {}) {
|
|
|
3440
3784
|
...base.rng === void 0 ? {} : { rng: base.rng },
|
|
3441
3785
|
...base.rngSeed === void 0 ? {} : { rngSeed: (seed, full) => base.rngSeed(seed, pick(base.dimensions, full)) },
|
|
3442
3786
|
...base.familyBy === void 0 ? {} : { familyBy: base.familyBy },
|
|
3787
|
+
...options.hash === void 0 ? {} : { hash: options.hash },
|
|
3443
3788
|
generate(seed, full, context) {
|
|
3444
3789
|
let input = base.generate(seed, pick(base.dimensions, full), context);
|
|
3445
3790
|
const parts = [];
|
|
@@ -3582,6 +3927,6 @@ const RECT_SCATTERED = Object.freeze({
|
|
|
3582
3927
|
};
|
|
3583
3928
|
}
|
|
3584
3929
|
});
|
|
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 };
|
|
3930
|
+
export { CORPUS_DIMENSIONS, DEFAULT_FAMILY_BY, DEFAULT_RNG, DEFAULT_SHAPES, DEFAULT_TOLERANCE, EDGES_STEP, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, FAMILY_ANY_KIND, INVALIDATED_FIELD, 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, boxArea, canonicalConfig, canonicalInput, canonicalJson, checkConfig, checkDeclared, checkInput, checkShapes, checkSpace, checkSplit, compareMeasure, composeFamily, configId, corpusHashOf, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, createFamilyRegistry, declaredClaimOf, declaredCounts, dimensionAt, dimensionSupports, dimensionUnit, documentHash, edgeStep, enumerateSpace, exercised, familyKind, familyOf, fnv1a32, grow, isShapeKind, itemShapeOf, joinConfigs, leaveOneSeedOut, levelsOf, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, ownerDimension, packHard, paramActive, paramLevels, paramProblem, paramValue, pickMoves, placedRect, portPoint, portfolio, productSpace, randomInt, rectInRegion, regionArea, regionHitsRect, resolveDimensions, ruleLevel, selectBest, sha256Hex, shapeKindOf, shapeKindProblem, shapeKindsOf, shapeSupportProblems, shapesOf, sharedDimension, spaceShapeKinds, spaceSize, splitConfig, splitFileName, splitFolds, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide, verdict };
|
|
3586
3931
|
|
|
3587
3932
|
//# sourceMappingURL=index.js.map
|