@mark1russell7/layout-contract 0.2.15 → 0.2.17
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 +12 -2
- package/build/index.d.ts +87 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +223 -27
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/families.ts +36 -6
- package/src/geometry.ts +28 -0
- package/src/measures.ts +6 -1
- package/src/rules.ts +3 -2
- package/src/shapes.ts +120 -8
- package/src/strategy-space.ts +106 -11
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,63 @@ 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`. It does not read `bound`. */
|
|
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 level of a measure or a rule on a shape kind. It joins the lists `shapes`, `safe` and `bound.shapes`.
|
|
210
|
+
* A kind of `shapesOf` is `exact`, a kind of `safe` is `safe`, a kind of `bound.shapes` is `bound`, and another kind is `none`.
|
|
211
|
+
*/
|
|
212
|
+
function shapeLevel(use, kind) {
|
|
213
|
+
const level = ruleLevel(use, kind);
|
|
214
|
+
if (level !== "none") return level;
|
|
215
|
+
return use.bound?.shapes.includes(kind) === true ? "bound" : "none";
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* This function gives the problems of a list of measures and rules. An empty list tells that the list is good.
|
|
219
|
+
* It refuses an unknown kind, a kind two times in one list, and a kind in more than one of `shapesOf`, `safe` and `bound.shapes`.
|
|
220
|
+
* It also refuses a `bound` without a list of kinds, and a `side` that is not `upper` or `lower`.
|
|
221
|
+
*/
|
|
222
|
+
function shapeSupportProblems(uses) {
|
|
223
|
+
const out = [];
|
|
224
|
+
for (const use of uses) {
|
|
225
|
+
const bound = use.bound;
|
|
226
|
+
const isBound = typeof bound === "object" && bound !== null && !Array.isArray(bound);
|
|
227
|
+
if (bound !== void 0 && !isBound) out.push(`${use.id}: the bound value is not an object with shapes and a side`);
|
|
228
|
+
const boundShapes = isBound ? bound.shapes : void 0;
|
|
229
|
+
for (const [name, list] of [
|
|
230
|
+
["shapes", use.shapes],
|
|
231
|
+
["safe", use.safe],
|
|
232
|
+
["bound", boundShapes]
|
|
233
|
+
]) {
|
|
234
|
+
if (list === void 0 && (name !== "bound" || !isBound)) continue;
|
|
235
|
+
if (!Array.isArray(list)) {
|
|
236
|
+
out.push(`${use.id}: the ${name === "bound" ? "bound shapes" : name} value is not a list of shape kinds`);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
list.forEach((k, i) => {
|
|
240
|
+
if (!isShapeKind(k)) out.push(`${use.id}: the ${name} kind ${String(k)} is not a declared kind`);
|
|
241
|
+
else if (list.indexOf(k) !== i) out.push(`${use.id}: the ${name} list has the kind ${k} two times`);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const exact = use.shapes === void 0 ? DEFAULT_SHAPES : Array.isArray(use.shapes) ? use.shapes : [];
|
|
245
|
+
if (Array.isArray(use.safe)) {
|
|
246
|
+
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`);
|
|
247
|
+
}
|
|
248
|
+
if (!isBound) continue;
|
|
249
|
+
const side = bound.side;
|
|
250
|
+
if (side !== "upper" && side !== "lower") out.push(`${use.id}: the bound side ${String(side)} is not upper or lower`);
|
|
251
|
+
if (!Array.isArray(boundShapes)) continue;
|
|
252
|
+
const safe = Array.isArray(use.safe) ? use.safe : [];
|
|
253
|
+
for (const k of new Set(boundShapes)) {
|
|
254
|
+
if (exact.includes(k)) out.push(`${use.id}: the kind ${String(k)} is in shapes and in bound`);
|
|
255
|
+
if (safe.includes(k)) out.push(`${use.id}: the kind ${String(k)} is in safe and in bound`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
178
260
|
const REGION_KIND = {
|
|
179
261
|
rect: "rect",
|
|
180
262
|
polygon: "polygon",
|
|
@@ -193,6 +275,18 @@ function shapeKindOf(box) {
|
|
|
193
275
|
if (box.shapeKind !== void 0) return box.shapeKind;
|
|
194
276
|
return box.shape === void 0 ? "rect" : REGION_KIND[box.shape.kind];
|
|
195
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* This function gives the exact area of a box, or `undefined` when the contract knows only its bounding box.
|
|
280
|
+
* A box with `shape` gives `regionArea` of the shape. A `rect` box without `shape` gives `w` by `h`.
|
|
281
|
+
* 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`.
|
|
282
|
+
* A box of another kind without `shape` is a bad input, and gives `undefined`.
|
|
283
|
+
*/
|
|
284
|
+
function boxArea(box) {
|
|
285
|
+
const kind = shapeKindOf(box);
|
|
286
|
+
if (kind === "free") return void 0;
|
|
287
|
+
if (box.shape !== void 0) return regionArea(box.shape);
|
|
288
|
+
return kind === "rect" ? box.w * box.h : void 0;
|
|
289
|
+
}
|
|
196
290
|
/** 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
291
|
function shapeKindsOf(input) {
|
|
198
292
|
const seen = new Set(input.boxes.map(shapeKindOf));
|
|
@@ -219,19 +313,23 @@ function shapesOf(use) {
|
|
|
219
313
|
/**
|
|
220
314
|
* 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
315
|
* Each break has the claim `shape`, the id of the input, and the first 5 ids of the boxes of the kind.
|
|
316
|
+
* By default, a kind of `safe` or of `bound.shapes` is a break, because the result is not exact.
|
|
317
|
+
* With the level `safe`, a kind of `safe` is not a break. With the level `bound`, a kind of `safe` and a kind of `bound.shapes` are not a break.
|
|
222
318
|
*/
|
|
223
|
-
function checkShapes(input, uses) {
|
|
319
|
+
function checkShapes(input, uses, options = {}) {
|
|
224
320
|
const kinds = shapeKindsOf(input);
|
|
225
321
|
const out = [];
|
|
226
322
|
for (const use of uses) {
|
|
227
323
|
const shapes = shapesOf(use);
|
|
228
324
|
for (const kind of kinds) {
|
|
229
|
-
|
|
325
|
+
const level = shapeLevel(use, kind);
|
|
326
|
+
if (level === "exact" || level === "safe" && (options.level === "safe" || options.level === "bound") || level === "bound" && options.level === "bound") continue;
|
|
230
327
|
const ids = input.boxes.filter((b) => shapeKindOf(b) === kind).map((b) => b.id);
|
|
231
328
|
const shown = ids.slice(0, 5).join(", ") + (ids.length > 5 ? `, and ${ids.length - 5} more` : "");
|
|
329
|
+
const safe = level === "safe" ? `. The count of ${use.id} on ${kind} is only safe` : level === "bound" ? `. The value of ${use.id} is only ${use.bound.side === "upper" ? "an upper" : "a lower"} bound for ${kind}` : "";
|
|
232
330
|
out.push({
|
|
233
331
|
claim: "shape",
|
|
234
|
-
detail: `${use.id} supports only the shape kinds ${shapes.join(", ") || "(none)"}, but the input ${input.id} has ${kind} boxes: ${shown}`,
|
|
332
|
+
detail: `${use.id} supports only the shape kinds ${shapes.join(", ") || "(none)"}, but the input ${input.id} has ${kind} boxes: ${shown}${safe}`,
|
|
235
333
|
inputs: [input.id]
|
|
236
334
|
});
|
|
237
335
|
}
|
|
@@ -585,16 +683,27 @@ function countPortsOffBorder(boxes, eps = RECORD_EPS) {
|
|
|
585
683
|
/**
|
|
586
684
|
* The hard rules of a pack, with the shape kinds that each rule supports.
|
|
587
685
|
* `no-overlap` and `in-container` test the bounding box of a shape. That count is exact only for a `rect`.
|
|
686
|
+
* 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.
|
|
588
687
|
* `locked-kept` and `complete` read only the positions and the ids, so they support each kind.
|
|
589
688
|
*/
|
|
590
689
|
const PACK_HARD_RULES = Object.freeze([
|
|
591
690
|
{
|
|
592
691
|
id: "no-overlap",
|
|
593
|
-
shapes: ["rect"]
|
|
692
|
+
shapes: ["rect"],
|
|
693
|
+
safe: [
|
|
694
|
+
"polygon",
|
|
695
|
+
"circle",
|
|
696
|
+
"free"
|
|
697
|
+
]
|
|
594
698
|
},
|
|
595
699
|
{
|
|
596
700
|
id: "in-container",
|
|
597
|
-
shapes: ["rect"]
|
|
701
|
+
shapes: ["rect"],
|
|
702
|
+
safe: [
|
|
703
|
+
"polygon",
|
|
704
|
+
"circle",
|
|
705
|
+
"free"
|
|
706
|
+
]
|
|
598
707
|
},
|
|
599
708
|
{
|
|
600
709
|
id: "locked-kept",
|
|
@@ -2085,6 +2194,8 @@ function optionProblems(stage, o) {
|
|
|
2085
2194
|
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`]);
|
|
2086
2195
|
const pool = o.members.pool;
|
|
2087
2196
|
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`]);
|
|
2197
|
+
const view = o.members.view;
|
|
2198
|
+
if (view !== void 0 && view !== "bounds") out.push(["view", `${at} has the member view ${show(view)}, not "bounds"`]);
|
|
2088
2199
|
}
|
|
2089
2200
|
return out;
|
|
2090
2201
|
}
|
|
@@ -2103,6 +2214,15 @@ function supportsProblems(supports, at) {
|
|
|
2103
2214
|
}
|
|
2104
2215
|
/** This function tells if an option supports each shape kind of the list. */
|
|
2105
2216
|
const supportsShapes = (o, shapes) => shapes === void 0 || o.supports?.shape === void 0 || shapes.every((k) => o.supports.shape.includes(k));
|
|
2217
|
+
/** The shape kinds that the members of an option with `view: "bounds"` see: a bounding box is a `rect`. */
|
|
2218
|
+
const BOUNDS_VIEW = Object.freeze(["rect"]);
|
|
2219
|
+
/**
|
|
2220
|
+
* This function gives the shape kinds of the members of a meta option. With `view: "bounds"`, a list with a kind becomes `BOUNDS_VIEW`.
|
|
2221
|
+
* Without a list, or with an empty list, the value does not change.
|
|
2222
|
+
*/
|
|
2223
|
+
const memberShapes = (rule, shapes) => rule.view === "bounds" && shapes !== void 0 && shapes.length > 0 ? BOUNDS_VIEW : shapes;
|
|
2224
|
+
/** This function tells if two lists have the same shape kinds. */
|
|
2225
|
+
const sameKinds = (a, b) => a.every((k) => b.includes(k)) && b.every((k) => a.includes(k));
|
|
2106
2226
|
/** This function gives the problems of the params of one stage choice. */
|
|
2107
2227
|
function choiceParamProblems(o, params, at) {
|
|
2108
2228
|
if (params !== void 0 && !isObject(params)) return [["config", `the params of ${at} are not an object`]];
|
|
@@ -2176,7 +2296,7 @@ function configProblems(space, config, member, shapes) {
|
|
|
2176
2296
|
out.push(["depth", `a member has the meta option ${s.name}.${o.id}. The depth of a meta config is 1`]);
|
|
2177
2297
|
continue;
|
|
2178
2298
|
}
|
|
2179
|
-
out.push(...memberProblems(partOf(space, s.name).space, o.members, c["members"], at, shapes));
|
|
2299
|
+
out.push(...memberProblems(partOf(space, s.name).space, o.members, c["members"], at, memberShapes(o.members, shapes)));
|
|
2180
2300
|
}
|
|
2181
2301
|
for (const c of space.constraints ?? []) if (!holds(c, chosen)) out.push(["constraint", `the config breaks the constraint ${describe(c)}`]);
|
|
2182
2302
|
return out;
|
|
@@ -2239,6 +2359,7 @@ const toBreaks = (problems, inputs = []) => problems.map(([claim, detail]) => ({
|
|
|
2239
2359
|
*
|
|
2240
2360
|
* A member list that is not an entry of the `pool` of its option gives the claim `pool`.
|
|
2241
2361
|
* 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.
|
|
2362
|
+
* A member of an option with `view: "bounds"` gets the check with the kind `rect`.
|
|
2242
2363
|
*/
|
|
2243
2364
|
function checkConfig(space, config, options = {}) {
|
|
2244
2365
|
return toBreaks(configProblems(space, config, false, options.shapes));
|
|
@@ -2499,7 +2620,9 @@ function combos(space, levels, meta, limit) {
|
|
|
2499
2620
|
for (const s of stages) for (const o of s.options) {
|
|
2500
2621
|
if (o.members === void 0) continue;
|
|
2501
2622
|
const choices = new Map(stages.map((x) => [x.name, x === s ? o : null]));
|
|
2502
|
-
const
|
|
2623
|
+
const own = memberSpaces.get(o);
|
|
2624
|
+
const from = own === void 0 ? base : baseCount(own, levels, limit);
|
|
2625
|
+
const lists = o.members.pool === void 0 ? memberLists(from, o.members, limit) : BigInt(o.members.pool.length);
|
|
2503
2626
|
if (keeps(space, choices)) out.push({
|
|
2504
2627
|
choices,
|
|
2505
2628
|
base: false,
|
|
@@ -2524,6 +2647,8 @@ function spaceSize(space, options) {
|
|
|
2524
2647
|
* This function gives the space with only the options that support each shape kind of `shapes`. Without `shapes`, it gives the same space.
|
|
2525
2648
|
* A stage can lose all of its options. Then the space has no config with that stage.
|
|
2526
2649
|
* A pool keeps only the entries whose members fit the smaller space. A meta option without such an entry goes out.
|
|
2650
|
+
*
|
|
2651
|
+
* The members of an option with `view: "bounds"` come from the space for the kinds of `memberShapes`. `memberSpaces` keeps that space.
|
|
2527
2652
|
*/
|
|
2528
2653
|
function restrict(space, shapes) {
|
|
2529
2654
|
if (shapes === void 0) return space;
|
|
@@ -2536,16 +2661,23 @@ function restrict(space, shapes) {
|
|
|
2536
2661
|
...space.parts === void 0 ? {} : { parts: space.parts.map((p) => restrict(p, shapes)) }
|
|
2537
2662
|
};
|
|
2538
2663
|
const withPool = (s, o) => {
|
|
2539
|
-
if (o.members
|
|
2540
|
-
const
|
|
2664
|
+
if (o.members === void 0) return [o];
|
|
2665
|
+
const inner = memberShapes(o.members, shapes);
|
|
2666
|
+
const own = sameKinds(inner, shapes) ? void 0 : restrict(partOf(space, s.name).space, inner);
|
|
2667
|
+
const mark = (option) => {
|
|
2668
|
+
if (own !== void 0) memberSpaces.set(option, own);
|
|
2669
|
+
return [option];
|
|
2670
|
+
};
|
|
2671
|
+
if (o.members.pool === void 0) return own === void 0 ? [o] : mark({ ...o });
|
|
2672
|
+
const part = own ?? partOf(kept, s.name).space;
|
|
2541
2673
|
const pool = o.members.pool.filter((entry) => fits(part, entry));
|
|
2542
|
-
return pool.length === 0 ? [] :
|
|
2674
|
+
return pool.length === 0 ? [] : mark({
|
|
2543
2675
|
...o,
|
|
2544
2676
|
members: {
|
|
2545
2677
|
...o.members,
|
|
2546
2678
|
pool
|
|
2547
2679
|
}
|
|
2548
|
-
}
|
|
2680
|
+
});
|
|
2549
2681
|
};
|
|
2550
2682
|
return {
|
|
2551
2683
|
...kept,
|
|
@@ -2555,6 +2687,31 @@ function restrict(space, shapes) {
|
|
|
2555
2687
|
}))
|
|
2556
2688
|
};
|
|
2557
2689
|
}
|
|
2690
|
+
/** 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. */
|
|
2691
|
+
const memberSpaces = /* @__PURE__ */ new WeakMap();
|
|
2692
|
+
/**
|
|
2693
|
+
* This function tells if an option of a restricted space has a member list. A base option and a meta option with a `pool` have one.
|
|
2694
|
+
* A different meta option has one when its members have a base config. `restrict` keeps only the pool entries that fit.
|
|
2695
|
+
*/
|
|
2696
|
+
const hasMembers = (space, stage, o) => o.members === void 0 || o.members.pool !== void 0 || combos(memberSpaces.get(o) ?? partOf(space, stage).space, 1, false, 0n).length > 0;
|
|
2697
|
+
/** This function tells if a restricted space has a config. A base config counts. With `meta`, a meta config counts when each of its meta options has a member list. */
|
|
2698
|
+
const hasConfig = (space, meta) => combos(space, 1, meta, 0n).some((c) => c.base || [...c.choices].every(([stage, o]) => o === null || hasMembers(space, stage, o)));
|
|
2699
|
+
/** This function gives the error of a sample of a space that has no config for the shape kinds `shapes`. `kept` is the restricted space. */
|
|
2700
|
+
function noSample(space, kept, shapes, meta) {
|
|
2701
|
+
const list = (names, one, more) => names.length === 1 ? `${one} ${names[0]}` : `${more} ${names.join(", ")}`;
|
|
2702
|
+
const empty = kept.stages.filter((s) => !s.optional && !s.options.some((o) => meta || o.members === void 0)).map((s) => s.name);
|
|
2703
|
+
const removed = !meta ? [] : space.stages.flatMap((s) => s.options.filter((o) => o.members !== void 0 && o.members.view !== "bounds" && supportsShapes(o, shapes)).filter((o) => {
|
|
2704
|
+
const own = kept.stages.find((x) => x.name === s.name)?.options.find((x) => x.id === o.id);
|
|
2705
|
+
return own === void 0 || !hasMembers(kept, s.name, own);
|
|
2706
|
+
}).map((o) => `${s.name}.${o.id}`));
|
|
2707
|
+
let text = `The space ${space.engine} has no config for a sample with the shape kinds ${shapes.join(", ")}.`;
|
|
2708
|
+
if (empty.length > 0) text += ` ${list(empty, "The stage", "The stages")} ${empty.length === 1 ? "has" : "have"} no option for these kinds.`;
|
|
2709
|
+
if (removed.length > 0) text += ` ${list(removed, "The meta option", "The meta options")} ${removed.length === 1 ? "has" : "have"} no member config for these kinds. Set members.view to "bounds" when the members see only the bounding boxes.`;
|
|
2710
|
+
if (empty.length === 0 && removed.length === 0) text += " The constraints remove each config.";
|
|
2711
|
+
return new RangeError(text);
|
|
2712
|
+
}
|
|
2713
|
+
/** This function gives the count of the base configs of a space that is known to be good. */
|
|
2714
|
+
const baseCount = (space, levels, limit) => combos(space, levels, false, limit).reduce((sum, c) => sum + c.weight, 0n);
|
|
2558
2715
|
/** This function throws a `RangeError` for a list of shape kinds with a kind that is not declared. */
|
|
2559
2716
|
function assertShapes(shapes) {
|
|
2560
2717
|
if (shapes === void 0) return;
|
|
@@ -2631,16 +2788,27 @@ function sampleValue(p, u) {
|
|
|
2631
2788
|
}
|
|
2632
2789
|
/** This function gives a seed of 32 bits from a seed and a step name. */
|
|
2633
2790
|
const subSeed = (seed, step) => Math.floor(stepRandom(seed, step)() * 4294967296);
|
|
2634
|
-
/**
|
|
2635
|
-
function
|
|
2791
|
+
/**
|
|
2792
|
+
* This function gives a sample of a space: Latin hypercube strata over the options, the params and the member counts.
|
|
2793
|
+
* `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.
|
|
2794
|
+
*/
|
|
2795
|
+
function sampleConfigs(space, n, seed, meta, maxTries, canon = space) {
|
|
2636
2796
|
const stages = space.stages;
|
|
2637
2797
|
const prefix = prefixes(space);
|
|
2798
|
+
const bounded = new Set(stages.filter((s) => meta && s.options.some((o) => memberSpaces.has(o))).map((s) => prefix.get(s.name)));
|
|
2638
2799
|
const lists = stages.map((s) => {
|
|
2639
2800
|
const list = s.options.filter((o) => meta || o.members === void 0);
|
|
2640
2801
|
if (s.optional) list.push(null);
|
|
2802
|
+
if (list.length === 0 && bounded.has(prefix.get(s.name))) list.push(null);
|
|
2641
2803
|
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.`);
|
|
2642
2804
|
return list;
|
|
2643
2805
|
});
|
|
2806
|
+
const usable = /* @__PURE__ */ new Map();
|
|
2807
|
+
const withMembers = (s, o) => {
|
|
2808
|
+
if (o === null || o === void 0 || o.members === void 0) return true;
|
|
2809
|
+
if (!usable.has(o)) usable.set(o, hasMembers(space, s.name, o));
|
|
2810
|
+
return usable.get(o);
|
|
2811
|
+
};
|
|
2644
2812
|
const points = [];
|
|
2645
2813
|
let tries = 0;
|
|
2646
2814
|
for (let round = 0; points.length < n; round++) {
|
|
@@ -2656,7 +2824,7 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
|
|
|
2656
2824
|
const m = metaPart.get(prefix.get(s.name));
|
|
2657
2825
|
if (m !== void 0 && m !== s.name) choices.set(s.name, null);
|
|
2658
2826
|
}
|
|
2659
|
-
if (keeps(space, choices)) points.push(choices);
|
|
2827
|
+
if (stages.every((s) => s.optional === true || choices.get(s.name) !== null || metaPart.has(prefix.get(s.name))) && keeps(space, choices) && stages.every((s) => withMembers(s, choices.get(s.name)))) points.push(choices);
|
|
2660
2828
|
}
|
|
2661
2829
|
}
|
|
2662
2830
|
const values = points.map(() => stages.map(() => ({})));
|
|
@@ -2681,7 +2849,7 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
|
|
|
2681
2849
|
});
|
|
2682
2850
|
continue;
|
|
2683
2851
|
}
|
|
2684
|
-
const part = partOf(space, s.name).space;
|
|
2852
|
+
const part = memberSpaces.get(o) ?? partOf(space, s.name).space;
|
|
2685
2853
|
const poolSize = Math.max(n, 4 * o.members.max, 8);
|
|
2686
2854
|
const seen = /* @__PURE__ */ new Set();
|
|
2687
2855
|
const pool = sampleConfigs(part, poolSize, subSeed(seed, `pool:${s.name}:${o.id}`), false, Math.max(1e3, 100 * poolSize)).filter((c) => {
|
|
@@ -2711,7 +2879,7 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
|
|
|
2711
2879
|
members: members[i][k]
|
|
2712
2880
|
};
|
|
2713
2881
|
});
|
|
2714
|
-
return canonicalConfig(
|
|
2882
|
+
return canonicalConfig(canon, {
|
|
2715
2883
|
space: space.engine,
|
|
2716
2884
|
stages: out
|
|
2717
2885
|
});
|
|
@@ -2725,6 +2893,8 @@ function sampleConfigs(space, n, seed, meta, maxTries) {
|
|
|
2725
2893
|
* - A sample draws again when a point breaks a constraint. After `maxTries` draws, it throws an `Error`. Two sampled configs can be equal.
|
|
2726
2894
|
* - With `shapes`, only the options that support each kind of the list count. Without `shapes`, the result does not change.
|
|
2727
2895
|
* - A meta option with a `pool` gives only the entries of its pool. With `shapes`, only the entries whose members support each kind count.
|
|
2896
|
+
* - A sample draws again when a point chooses a meta option without a member list. `spaceSize` gives such an option 0.
|
|
2897
|
+
* - With `shapes`, a sample throws a `RangeError` when the space has no config for the kinds. The error names the stages without an option and the meta options without members.
|
|
2728
2898
|
*/
|
|
2729
2899
|
function enumerateSpace(space, options) {
|
|
2730
2900
|
assertSpace(space);
|
|
@@ -2739,7 +2909,13 @@ function enumerateSpace(space, options) {
|
|
|
2739
2909
|
const meta = options.meta ?? "skip";
|
|
2740
2910
|
if (meta !== "skip" && meta !== "sample") throw new RangeError(`The option meta is ${String(meta)}, not skip or sample.`);
|
|
2741
2911
|
if (options.n === 0) return [];
|
|
2742
|
-
|
|
2912
|
+
const kept = restrict(space, options.shapes);
|
|
2913
|
+
try {
|
|
2914
|
+
return sampleConfigs(kept, options.n, options.seed, meta === "sample", options.maxTries ?? Math.max(1e3, 100 * options.n), space);
|
|
2915
|
+
} catch (error) {
|
|
2916
|
+
if (options.shapes !== void 0 && !hasConfig(kept, meta === "sample")) throw noSample(space, kept, options.shapes, meta === "sample");
|
|
2917
|
+
throw error;
|
|
2918
|
+
}
|
|
2743
2919
|
}
|
|
2744
2920
|
/**
|
|
2745
2921
|
* 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.
|
|
@@ -3193,7 +3369,7 @@ const CORPUS_DIMENSIONS = Object.freeze({
|
|
|
3193
3369
|
});
|
|
3194
3370
|
/**
|
|
3195
3371
|
* This function gives a dimension of `CORPUS_DIMENSIONS`, with the fields of `patch` over it. It throws a `RangeError` for an unknown name.
|
|
3196
|
-
* With `supported` or `
|
|
3372
|
+
* With `supported`, `invalidates` or `precision` in the patch, it also throws a `RangeError` for a bad dimension, as `ownerDimension` does.
|
|
3197
3373
|
*/
|
|
3198
3374
|
function sharedDimension(name, patch = {}) {
|
|
3199
3375
|
const base = Object.hasOwn(CORPUS_DIMENSIONS, name) ? CORPUS_DIMENSIONS[name] : void 0;
|
|
@@ -3203,12 +3379,12 @@ function sharedDimension(name, patch = {}) {
|
|
|
3203
3379
|
...patch,
|
|
3204
3380
|
name
|
|
3205
3381
|
});
|
|
3206
|
-
if (patch.supported !== void 0 || patch.invalidates !== void 0) throwProblems(`sharedDimension: the dimension "${name}"`, ownProblems(dim));
|
|
3382
|
+
if (patch.supported !== void 0 || patch.invalidates !== void 0 || patch.precision !== void 0) throwProblems(`sharedDimension: the dimension "${name}"`, ownProblems(dim));
|
|
3207
3383
|
return dim;
|
|
3208
3384
|
}
|
|
3209
3385
|
/**
|
|
3210
3386
|
* This function gives a dimension that only its owner declares, for example `cut` of a family of `packing`.
|
|
3211
|
-
* It throws a `RangeError` for a name of `CORPUS_DIMENSIONS`, the reserved name `kind`, or a bad default, level, `supported` or `
|
|
3387
|
+
* It throws a `RangeError` for a name of `CORPUS_DIMENSIONS`, the reserved name `kind`, or a bad default, level, `supported`, `invalidates` or `precision`.
|
|
3212
3388
|
* The registry examines the `when` of the dimension, because it needs the earlier dimensions.
|
|
3213
3389
|
*/
|
|
3214
3390
|
function ownerDimension(name, spec) {
|
|
@@ -3252,7 +3428,7 @@ function supportedText(dim) {
|
|
|
3252
3428
|
if (isSupportedList(s)) return s.map((v) => JSON.stringify(v)).join(", ");
|
|
3253
3429
|
return `the values from ${s.min ?? dim.min} to ${s.max ?? dim.max}`;
|
|
3254
3430
|
}
|
|
3255
|
-
/** This function gives the problems of one dimension that need no other dimension: the default, the levels, `supported` and `
|
|
3431
|
+
/** This function gives the problems of one dimension that need no other dimension: the default, the levels, `supported`, `invalidates` and `precision`. */
|
|
3256
3432
|
function ownProblems(d) {
|
|
3257
3433
|
const out = [];
|
|
3258
3434
|
const bad = paramProblem(d, d.default);
|
|
@@ -3263,6 +3439,7 @@ function ownProblems(d) {
|
|
|
3263
3439
|
}
|
|
3264
3440
|
out.push(...supportProblems(d, bad === void 0));
|
|
3265
3441
|
out.push(...invalidatesProblems(d));
|
|
3442
|
+
out.push(...precisionProblems(d));
|
|
3266
3443
|
return out;
|
|
3267
3444
|
}
|
|
3268
3445
|
/** This function gives the problems of the `supported` values of a dimension. `goodDefault` tells that the default is a good value. */
|
|
@@ -3304,6 +3481,22 @@ function invalidatesProblems(d) {
|
|
|
3304
3481
|
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`);
|
|
3305
3482
|
return out;
|
|
3306
3483
|
}
|
|
3484
|
+
/** This function gives the problems of the `precision` of a dimension. A bad value and a dimension that is not numeric are problems. */
|
|
3485
|
+
function precisionProblems(d) {
|
|
3486
|
+
const p = d.precision;
|
|
3487
|
+
if (p === void 0) return [];
|
|
3488
|
+
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`];
|
|
3489
|
+
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}`];
|
|
3490
|
+
return [];
|
|
3491
|
+
}
|
|
3492
|
+
/** This function rounds a number to `places` decimal places: `Math.round(v * 10 ** places) / 10 ** places`. */
|
|
3493
|
+
const roundTo = (v, places) => Math.round(v * 10 ** places) / 10 ** places;
|
|
3494
|
+
/** This function tells if a value is the default of a dimension. With `precision`, it compares the two numbers after it rounds them. */
|
|
3495
|
+
function isDefault(d, value) {
|
|
3496
|
+
const def = paramValue(d, d.default);
|
|
3497
|
+
if (d.precision === void 0 || typeof value !== "number" || typeof def !== "number") return value === def;
|
|
3498
|
+
return roundTo(value, d.precision) === roundTo(def, d.precision);
|
|
3499
|
+
}
|
|
3307
3500
|
/** This function tells if a log scale of a range starts at 0. Then the scale is the log of `1 + x - min`. */
|
|
3308
3501
|
const fromZero = (d) => d.scale === "log" && (d.min ?? 0) <= 0;
|
|
3309
3502
|
/**
|
|
@@ -3459,12 +3652,15 @@ function sweepLevels(dimensions, options, where, by) {
|
|
|
3459
3652
|
return lv;
|
|
3460
3653
|
});
|
|
3461
3654
|
}
|
|
3462
|
-
/**
|
|
3655
|
+
/**
|
|
3656
|
+
* This function gives the output fields that the values make wrong: the `invalidates` of each dimension with a value other than its default, sorted.
|
|
3657
|
+
* A dimension with `precision` compares the rounded value with the rounded default.
|
|
3658
|
+
*/
|
|
3463
3659
|
function invalidFields(dimensions, full) {
|
|
3464
3660
|
const out = /* @__PURE__ */ new Set();
|
|
3465
3661
|
for (const d of dimensions) {
|
|
3466
3662
|
if (d.invalidates === void 0 || d.invalidates.length === 0) continue;
|
|
3467
|
-
if (full[d.name]
|
|
3663
|
+
if (!isDefault(d, full[d.name])) for (const f of d.invalidates) out.add(f);
|
|
3468
3664
|
}
|
|
3469
3665
|
return [...out].sort(byCode);
|
|
3470
3666
|
}
|
|
@@ -3795,6 +3991,6 @@ const RECT_SCATTERED = Object.freeze({
|
|
|
3795
3991
|
};
|
|
3796
3992
|
}
|
|
3797
3993
|
});
|
|
3798
|
-
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, 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, 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 };
|
|
3994
|
+
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, shapeLevel, shapeSupportProblems, shapesOf, sharedDimension, spaceShapeKinds, spaceSize, splitConfig, splitFileName, splitFolds, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide, verdict };
|
|
3799
3995
|
|
|
3800
3996
|
//# sourceMappingURL=index.js.map
|