@mark1russell7/layout-contract 0.2.9 → 0.2.10
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 +3 -1
- package/build/index.d.ts +176 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +723 -5
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/strategy-space.ts +854 -0
package/build/index.js
CHANGED
|
@@ -1404,7 +1404,7 @@ function declaredCounts(inputs, declares, ids = []) {
|
|
|
1404
1404
|
return out;
|
|
1405
1405
|
}
|
|
1406
1406
|
/** This function tells if a value is a plain object. */
|
|
1407
|
-
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1407
|
+
const isObject$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1408
1408
|
/**
|
|
1409
1409
|
* This function reads the field `claims.declared` of a manifest. A manifest without it gives `undefined`.
|
|
1410
1410
|
* It throws an `Error` for a claim without a whole `minCount`, a list `thin` of ids and a whole count for each id.
|
|
@@ -1412,14 +1412,14 @@ const isObject = (value) => value !== null && typeof value === "object" && !Arra
|
|
|
1412
1412
|
function declaredClaimOf(manifest) {
|
|
1413
1413
|
const claims = manifest["claims"];
|
|
1414
1414
|
if (claims === void 0) return void 0;
|
|
1415
|
-
if (!isObject(claims)) throw new Error("The field claims of a corpus file is an object.");
|
|
1415
|
+
if (!isObject$1(claims)) throw new Error("The field claims of a corpus file is an object.");
|
|
1416
1416
|
const declared = claims["declared"];
|
|
1417
1417
|
if (declared === void 0) return void 0;
|
|
1418
|
-
if (!isObject(declared)) throw new Error("The field claims.declared of a corpus file is an object.");
|
|
1418
|
+
if (!isObject$1(declared)) throw new Error("The field claims.declared of a corpus file is an object.");
|
|
1419
1419
|
const { minCount, thin, counts } = declared;
|
|
1420
1420
|
if (!Number.isInteger(minCount) || minCount < 0) throw new Error("The field claims.declared.minCount is a whole number of 0 or more.");
|
|
1421
1421
|
if (!Array.isArray(thin) || !thin.every((id) => typeof id === "string")) throw new Error("The field claims.declared.thin is a list of property ids.");
|
|
1422
|
-
if (!isObject(counts) || !Object.values(counts).every((n) => Number.isInteger(n) && n >= 0)) throw new Error("The field claims.declared.counts gives a whole count for each property id.");
|
|
1422
|
+
if (!isObject$1(counts) || !Object.values(counts).every((n) => Number.isInteger(n) && n >= 0)) throw new Error("The field claims.declared.counts gives a whole count for each property id.");
|
|
1423
1423
|
return {
|
|
1424
1424
|
minCount,
|
|
1425
1425
|
thin: [...thin],
|
|
@@ -1540,6 +1540,724 @@ function leaveOneSeedOut(seeds) {
|
|
|
1540
1540
|
heldOut: [s]
|
|
1541
1541
|
}));
|
|
1542
1542
|
}
|
|
1543
|
-
|
|
1543
|
+
// src/strategy-space.ts
|
|
1544
|
+
/** The schema of a strategy space. */
|
|
1545
|
+
const STRATEGY_SPACE_SCHEMA = "strategy-space/1";
|
|
1546
|
+
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1547
|
+
const show = (value) => typeof value === "string" ? `"${value}"` : String(value);
|
|
1548
|
+
const isNumeric = (p) => p.kind === "int" || p.kind === "real";
|
|
1549
|
+
const zero = (v) => v === 0 ? 0 : v;
|
|
1550
|
+
/** This function gives the stable form of a real number: 12 significant digits, with `-0` as `0`. */
|
|
1551
|
+
const realForm = (v, digits = 12) => zero(Number(v.toPrecision(digits)));
|
|
1552
|
+
/** This function gives the stable form of a value of a param. */
|
|
1553
|
+
function normalize(p, v) {
|
|
1554
|
+
if (typeof v !== "number") return v;
|
|
1555
|
+
if (p.kind !== "real") return zero(v);
|
|
1556
|
+
return Math.min(p.max ?? Infinity, Math.max(p.min ?? -Infinity, realForm(v)));
|
|
1557
|
+
}
|
|
1558
|
+
/** This function tells if a value is on the step of a param. */
|
|
1559
|
+
function onStep(p, v) {
|
|
1560
|
+
if (p.step === void 0 || p.min === void 0) return true;
|
|
1561
|
+
const q = (v - p.min) / p.step;
|
|
1562
|
+
return Math.abs(q - Math.round(q)) <= 1e-9 * Math.max(1, Math.abs(q));
|
|
1563
|
+
}
|
|
1564
|
+
/** This function gives the problem of a value of a param, or `undefined` for a value in the range. */
|
|
1565
|
+
function valueProblem(p, v) {
|
|
1566
|
+
if (p.kind === "bool") return typeof v === "boolean" ? void 0 : `${show(v)} is not true or false`;
|
|
1567
|
+
if (p.kind === "enum") return (p.values ?? []).some((x) => x === v) ? void 0 : `${show(v)} is not one of ${(p.values ?? []).map(show).join(", ")}`;
|
|
1568
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return `${show(v)} is not a finite number`;
|
|
1569
|
+
if (p.kind === "int" && !Number.isInteger(v)) return `${v} is not a whole number`;
|
|
1570
|
+
if (p.min !== void 0 && v < p.min) return `${v} is less than the min ${p.min}`;
|
|
1571
|
+
if (p.max !== void 0 && v > p.max) return `${v} is more than the max ${p.max}`;
|
|
1572
|
+
if (!onStep(p, v)) return `${v} is not on the step ${p.step} from ${p.min}`;
|
|
1573
|
+
}
|
|
1574
|
+
/** This function tells if a param is active for the values of the earlier params. */
|
|
1575
|
+
function isActive(p, values) {
|
|
1576
|
+
if (p.when === void 0) return true;
|
|
1577
|
+
if (!Object.hasOwn(values, p.when.param)) return false;
|
|
1578
|
+
const v = values[p.when.param];
|
|
1579
|
+
return p.when.in.some((x) => x === v);
|
|
1580
|
+
}
|
|
1581
|
+
/** This function gives the part of a space that has a stage, and the prefix of the names of its stages. */
|
|
1582
|
+
function partOf(space, stage) {
|
|
1583
|
+
for (const part of space.parts ?? []) {
|
|
1584
|
+
const head = `${part.engine}.`;
|
|
1585
|
+
if (stage.startsWith(head)) {
|
|
1586
|
+
const inner = partOf(part, stage.slice(head.length));
|
|
1587
|
+
return {
|
|
1588
|
+
space: inner.space,
|
|
1589
|
+
prefix: head + inner.prefix
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return {
|
|
1594
|
+
space,
|
|
1595
|
+
prefix: ""
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
/** This function gives the prefix of the innermost part of each stage. */
|
|
1599
|
+
const prefixes = (space) => new Map(space.stages.map((s) => [s.name, partOf(space, s.name).prefix]));
|
|
1600
|
+
const refMatches = (ref, choices) => (choices.get(ref.stage) ?? null) === ref.option;
|
|
1601
|
+
/** This function tells if the option choices keep a constraint. */
|
|
1602
|
+
function holds(c, choices) {
|
|
1603
|
+
if (!refMatches(c.if, choices)) return true;
|
|
1604
|
+
return "not" in c.then ? !refMatches(c.then.not, choices) : refMatches(c.then, choices);
|
|
1605
|
+
}
|
|
1606
|
+
const describe = (c) => {
|
|
1607
|
+
const ref = (r) => `${r.stage}=${r.option ?? "null"}`;
|
|
1608
|
+
return "not" in c.then ? `if ${ref(c.if)} then not ${ref(c.then.not)}` : `if ${ref(c.if)} then ${ref(c.then)}`;
|
|
1609
|
+
};
|
|
1610
|
+
/** This function gives the problems of the range and the default of one param. */
|
|
1611
|
+
function paramProblems(p, at) {
|
|
1612
|
+
const out = [];
|
|
1613
|
+
if (isNumeric(p)) {
|
|
1614
|
+
const { min, max, step } = p;
|
|
1615
|
+
if (typeof min !== "number" || typeof max !== "number" || !Number.isFinite(min) || !Number.isFinite(max)) {
|
|
1616
|
+
out.push(["range", `${at} has no finite min and max`]);
|
|
1617
|
+
return out;
|
|
1618
|
+
}
|
|
1619
|
+
if (min > max) out.push(["range", `${at} has an empty range: the min ${min} is more than the max ${max}`]);
|
|
1620
|
+
if (p.kind === "int" && (!Number.isInteger(min) || !Number.isInteger(max))) out.push(["range", `${at} is an int with a min or a max that is not whole`]);
|
|
1621
|
+
if (step !== void 0 && (!Number.isFinite(step) || step <= 0 || p.kind === "int" && !Number.isInteger(step))) out.push(["range", `${at} has the step ${step}, which is not a ${p.kind === "int" ? "whole " : ""}number above 0`]);
|
|
1622
|
+
if (p.scale === "log" && min <= 0) out.push(["scale", `${at} has the scale log with the min ${min}, which is not above 0`]);
|
|
1623
|
+
} else {
|
|
1624
|
+
if (p.scale !== void 0) out.push(["scale", `${at} is a ${p.kind} param with a scale`]);
|
|
1625
|
+
if (p.kind === "enum") {
|
|
1626
|
+
if (!Array.isArray(p.values) || p.values.length === 0) out.push(["values", `${at} is an enum without values`]);
|
|
1627
|
+
else {
|
|
1628
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1629
|
+
for (const v of p.values) {
|
|
1630
|
+
if (seen.has(v)) out.push(["duplicate", `${at} has the value ${show(v)} two times`]);
|
|
1631
|
+
seen.add(v);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
if (out.length === 0) {
|
|
1637
|
+
const problem = valueProblem(p, p.default);
|
|
1638
|
+
if (problem !== void 0) out.push(["default", `the default of ${at} is not in its range: ${problem}`]);
|
|
1639
|
+
}
|
|
1640
|
+
return out;
|
|
1641
|
+
}
|
|
1642
|
+
/** This function gives the problems of one stage option. */
|
|
1643
|
+
function optionProblems(stage, o) {
|
|
1644
|
+
const out = [];
|
|
1645
|
+
const at = `the option ${stage.name}.${o.id}`;
|
|
1646
|
+
if (!Array.isArray(o.params)) return [["config", `${at} has no list of params`]];
|
|
1647
|
+
const names = o.params.map((p) => p.name);
|
|
1648
|
+
o.params.forEach((p, i) => {
|
|
1649
|
+
const pat = `the param ${stage.name}.${o.id}.${p.name}`;
|
|
1650
|
+
if (names.indexOf(p.name) !== i) out.push(["duplicate", `${at} has the param ${p.name} two times`]);
|
|
1651
|
+
out.push(...paramProblems(p, pat));
|
|
1652
|
+
if (p.when === void 0) return;
|
|
1653
|
+
const j = names.indexOf(p.when.param);
|
|
1654
|
+
if (j < 0) out.push(["unknown", `${pat} has a when with the unknown param ${p.when.param}`]);
|
|
1655
|
+
else if (j >= i) out.push(["when", `${pat} has a when with the param ${p.when.param}, which does not come before it`]);
|
|
1656
|
+
else if (!Array.isArray(p.when.in) || p.when.in.length === 0) out.push(["when", `${pat} has a when with no values`]);
|
|
1657
|
+
else for (const v of p.when.in) {
|
|
1658
|
+
const problem = valueProblem(o.params[j], v);
|
|
1659
|
+
if (problem !== void 0) out.push(["when", `${pat} has a when value that is not a value of ${p.when.param}: ${problem}`]);
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
if (o.members !== void 0) {
|
|
1663
|
+
const { min, max, ordered } = o.members;
|
|
1664
|
+
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`]);
|
|
1665
|
+
}
|
|
1666
|
+
return out;
|
|
1667
|
+
}
|
|
1668
|
+
/** This function gives the problems of the params of one stage choice. */
|
|
1669
|
+
function choiceParamProblems(o, params, at) {
|
|
1670
|
+
if (params !== void 0 && !isObject(params)) return [["config", `the params of ${at} are not an object`]];
|
|
1671
|
+
const given = params ?? {};
|
|
1672
|
+
const out = [];
|
|
1673
|
+
for (const key of Object.keys(given)) if (!o.params.some((p) => p.name === key)) out.push(["unknown", `${at} has the unknown param ${key}`]);
|
|
1674
|
+
const values = {};
|
|
1675
|
+
for (const p of o.params) {
|
|
1676
|
+
const has = Object.hasOwn(given, p.name);
|
|
1677
|
+
if (!isActive(p, values)) {
|
|
1678
|
+
if (has) out.push(["inactive", `${at} sets the param ${p.name}, which is not active`]);
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
const v = has ? given[p.name] : p.default;
|
|
1682
|
+
const problem = valueProblem(p, v);
|
|
1683
|
+
if (problem !== void 0) out.push(["param", `the param ${p.name} of ${at} is not in its range: ${problem}`]);
|
|
1684
|
+
else values[p.name] = normalize(p, v);
|
|
1685
|
+
}
|
|
1686
|
+
return out;
|
|
1687
|
+
}
|
|
1688
|
+
/** This function gives the problems of a config of a space. A member of a meta option has `member: true`. */
|
|
1689
|
+
function configProblems(space, config, member) {
|
|
1690
|
+
if (!isObject(config) || !isObject(config["stages"])) return [["config", "the config is not an object with stages"]];
|
|
1691
|
+
const out = [];
|
|
1692
|
+
const stages = config["stages"];
|
|
1693
|
+
if (config["space"] !== space.engine) out.push(["space", `the space of the config is ${show(config["space"])}, not ${show(space.engine)}`]);
|
|
1694
|
+
for (const key of Object.keys(stages)) if (!space.stages.some((s) => s.name === key)) out.push(["unknown", `the config has the unknown stage ${key}`]);
|
|
1695
|
+
const optionOf = (s) => {
|
|
1696
|
+
const c = stages[s.name];
|
|
1697
|
+
return isObject(c) ? s.options.find((o) => o.id === c["option"]) : void 0;
|
|
1698
|
+
};
|
|
1699
|
+
const prefix = prefixes(space);
|
|
1700
|
+
const metaStage = /* @__PURE__ */ new Map();
|
|
1701
|
+
for (const s of space.stages) {
|
|
1702
|
+
if (optionOf(s)?.members === void 0) continue;
|
|
1703
|
+
const p = prefix.get(s.name);
|
|
1704
|
+
if (metaStage.has(p)) out.push(["meta", `the stages ${metaStage.get(p)} and ${s.name} both have a meta option`]);
|
|
1705
|
+
else metaStage.set(p, s.name);
|
|
1706
|
+
}
|
|
1707
|
+
const chosen = /* @__PURE__ */ new Map();
|
|
1708
|
+
for (const s of space.stages) {
|
|
1709
|
+
const c = Object.hasOwn(stages, s.name) ? stages[s.name] : void 0;
|
|
1710
|
+
const meta = metaStage.get(prefix.get(s.name));
|
|
1711
|
+
chosen.set(s.name, null);
|
|
1712
|
+
if (c === void 0 || c === null) {
|
|
1713
|
+
if (!s.optional && meta === void 0) out.push(["missing", `the config has no option for the stage ${s.name}`]);
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
if (!isObject(c) || typeof c["option"] !== "string") {
|
|
1717
|
+
out.push(["config", `the choice of the stage ${s.name} has no option`]);
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1720
|
+
const o = s.options.find((x) => x.id === c["option"]);
|
|
1721
|
+
if (o === void 0) {
|
|
1722
|
+
out.push(["unknown", `the stage ${s.name} has no option ${c["option"]}`]);
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
chosen.set(s.name, o.id);
|
|
1726
|
+
const at = `the option ${s.name}.${o.id}`;
|
|
1727
|
+
if (meta !== void 0 && meta !== s.name) out.push(["meta", `the stage ${s.name} is set, but the meta option of ${meta} replaces the other stages of its part`]);
|
|
1728
|
+
out.push(...choiceParamProblems(o, c["params"], at));
|
|
1729
|
+
if (o.members === void 0) {
|
|
1730
|
+
if (c["members"] !== void 0) out.push(["members", `${at} is not a meta option, but the config gives members`]);
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
if (member) {
|
|
1734
|
+
out.push(["depth", `a member has the meta option ${s.name}.${o.id}. The depth of a meta config is 1`]);
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
out.push(...memberProblems(partOf(space, s.name).space, o.members, c["members"], at));
|
|
1738
|
+
}
|
|
1739
|
+
for (const c of space.constraints ?? []) if (!holds(c, chosen)) out.push(["constraint", `the config breaks the constraint ${describe(c)}`]);
|
|
1740
|
+
return out;
|
|
1741
|
+
}
|
|
1742
|
+
/** This function gives the problems of the members of a meta option. `part` is the space of the members. */
|
|
1743
|
+
function memberProblems(part, rule, members, at) {
|
|
1744
|
+
if (!Array.isArray(members)) return [["members", `${at} is a meta option without a list of members`]];
|
|
1745
|
+
const out = [];
|
|
1746
|
+
if (members.length < rule.min || members.length > rule.max) out.push(["members", `${at} has ${members.length} members, not ${rule.min} to ${rule.max}`]);
|
|
1747
|
+
const ids = /* @__PURE__ */ new Map();
|
|
1748
|
+
members.forEach((m, k) => {
|
|
1749
|
+
const inner = configProblems(part, m, true);
|
|
1750
|
+
for (const [claim, detail] of inner) out.push([claim, `the member ${k} of ${at}: ${detail}`]);
|
|
1751
|
+
if (inner.length > 0) return;
|
|
1752
|
+
const id = idOf(canonicalOf(part, m, true));
|
|
1753
|
+
if (ids.has(id)) out.push(["duplicate", `the members ${ids.get(id)} and ${k} of ${at} are the same config ${id}`]);
|
|
1754
|
+
else ids.set(id, k);
|
|
1755
|
+
});
|
|
1756
|
+
return out;
|
|
1757
|
+
}
|
|
1758
|
+
const toBreaks = (problems, inputs = []) => problems.map(([claim, detail]) => ({
|
|
1759
|
+
claim,
|
|
1760
|
+
detail,
|
|
1761
|
+
inputs
|
|
1762
|
+
}));
|
|
1763
|
+
/**
|
|
1764
|
+
* This function checks one config of a space. It gives one break for each problem. An empty list tells that the config is good.
|
|
1765
|
+
* The `claim` of a break is the kind of the problem, for example `missing`, `unknown`, `inactive`, `param`, `constraint`, `members` or `depth`.
|
|
1766
|
+
*/
|
|
1767
|
+
function checkConfig(space, config) {
|
|
1768
|
+
return toBreaks(configProblems(space, config, false));
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* This function checks a space. It gives one break for each problem. An empty list tells that the space is good.
|
|
1772
|
+
* It finds duplicate names, empty ranges, defaults out of range, enums without values, bad log scales, unknown names and bad named configs.
|
|
1773
|
+
* The `inputs` of a break of a named config have the name of the config.
|
|
1774
|
+
*/
|
|
1775
|
+
function checkSpace(space) {
|
|
1776
|
+
if (!isObject(space) || !Array.isArray(space.stages)) return toBreaks([["schema", "the space is not an object with stages"]]);
|
|
1777
|
+
const out = [];
|
|
1778
|
+
if (space.schema !== "strategy-space/1") out.push(["schema", `the schema is ${show(space.schema)}, not ${STRATEGY_SPACE_SCHEMA}`]);
|
|
1779
|
+
if (typeof space.engine !== "string" || space.engine === "") out.push(["schema", "the space has no engine name"]);
|
|
1780
|
+
if (typeof space.version !== "string") out.push(["schema", "the space has no version"]);
|
|
1781
|
+
const names = space.stages.map((s) => s.name);
|
|
1782
|
+
space.stages.forEach((s, i) => {
|
|
1783
|
+
if (names.indexOf(s.name) !== i) out.push(["duplicate", `the space has the stage ${s.name} two times`]);
|
|
1784
|
+
const options = Array.isArray(s.options) ? s.options : [];
|
|
1785
|
+
if (options.length === 0) {
|
|
1786
|
+
out.push(["options", `the stage ${s.name} has no options`]);
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
const ids = options.map((o) => o.id);
|
|
1790
|
+
options.forEach((o, j) => {
|
|
1791
|
+
if (ids.indexOf(o.id) !== j) out.push(["duplicate", `the stage ${s.name} has the option ${o.id} two times`]);
|
|
1792
|
+
out.push(...optionProblems(s, o));
|
|
1793
|
+
});
|
|
1794
|
+
if (!s.optional && options.every((o) => o.members !== void 0)) out.push(["meta", `the stage ${s.name} is not optional and has only meta options, so no base config exists`]);
|
|
1795
|
+
});
|
|
1796
|
+
const known = (ref, at) => {
|
|
1797
|
+
const s = space.stages.find((x) => x.name === ref?.stage);
|
|
1798
|
+
if (s === void 0) out.push(["unknown", `${at} has the unknown stage ${ref?.stage}`]);
|
|
1799
|
+
else if (ref.option === null ? !s.optional : !s.options.some((o) => o.id === ref.option)) out.push(["unknown", `${at} has the unknown option ${s.name}=${ref.option ?? "null"}`]);
|
|
1800
|
+
};
|
|
1801
|
+
(space.constraints ?? []).forEach((c, k) => {
|
|
1802
|
+
known(c.if, `the constraint ${k}`);
|
|
1803
|
+
known("not" in c.then ? c.then.not : c.then, `the constraint ${k}`);
|
|
1804
|
+
});
|
|
1805
|
+
for (const part of space.parts ?? []) for (const b of checkSpace(part)) out.push([b.claim, `the part ${part.engine}: ${b.detail}`]);
|
|
1806
|
+
if (space.parts !== void 0) {
|
|
1807
|
+
for (const s of space.stages) if (partOf(space, s.name).prefix === "") out.push(["parts", `the stage ${s.name} is not a stage of a part`]);
|
|
1808
|
+
}
|
|
1809
|
+
const breaks = toBreaks(out);
|
|
1810
|
+
if (breaks.length > 0) return breaks;
|
|
1811
|
+
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]));
|
|
1812
|
+
return breaks;
|
|
1813
|
+
}
|
|
1814
|
+
/** This function gives the canonical form of a config that is known to be good. */
|
|
1815
|
+
function canonicalOf(space, config, member) {
|
|
1816
|
+
const stages = {};
|
|
1817
|
+
for (const s of space.stages) {
|
|
1818
|
+
const c = Object.hasOwn(config.stages, s.name) ? config.stages[s.name] : void 0;
|
|
1819
|
+
if (c === void 0 || c === null) {
|
|
1820
|
+
stages[s.name] = null;
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
const o = s.options.find((x) => x.id === c.option);
|
|
1824
|
+
const given = c.params ?? {};
|
|
1825
|
+
const params = {};
|
|
1826
|
+
for (const p of o.params) if (isActive(p, params)) params[p.name] = normalize(p, Object.hasOwn(given, p.name) ? given[p.name] : p.default);
|
|
1827
|
+
if (o.members === void 0 || member) {
|
|
1828
|
+
stages[s.name] = {
|
|
1829
|
+
option: o.id,
|
|
1830
|
+
params
|
|
1831
|
+
};
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
const part = partOf(space, s.name).space;
|
|
1835
|
+
const members = (c.members ?? []).map((m) => canonicalOf(part, m, true));
|
|
1836
|
+
if (!o.members.ordered) members.sort((a, b) => idOf(a) < idOf(b) ? -1 : idOf(a) > idOf(b) ? 1 : 0);
|
|
1837
|
+
stages[s.name] = {
|
|
1838
|
+
option: o.id,
|
|
1839
|
+
params,
|
|
1840
|
+
members
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
return {
|
|
1844
|
+
space: space.engine,
|
|
1845
|
+
stages
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
const idOf = (canonical) => sha256Hex(canonicalJson(canonical)).slice(0, 16);
|
|
1849
|
+
/**
|
|
1850
|
+
* This function gives the canonical form of a config: each stage of the space, the active params only, the defaults, and stable numbers.
|
|
1851
|
+
* An unused optional stage is `null`. The members of an unordered meta option are sorted by their `configId`.
|
|
1852
|
+
* It throws an `Error` for a config with a break of `checkConfig`.
|
|
1853
|
+
*/
|
|
1854
|
+
function canonicalConfig(space, config) {
|
|
1855
|
+
const problems = configProblems(space, config, false);
|
|
1856
|
+
if (problems.length > 0) throw new Error(`The config of the space ${space.engine} is not good: ${problems.map((p) => p[1]).join("; ")}.`);
|
|
1857
|
+
return canonicalOf(space, config, false);
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* This function gives the id of a config: the first 16 hex characters of the SHA-256 of `canonicalJson` of `canonicalConfig`.
|
|
1861
|
+
* Two spellings of one config get one id. The version of the space is not part of the id.
|
|
1862
|
+
*/
|
|
1863
|
+
function configId(space, config) {
|
|
1864
|
+
return idOf(canonicalConfig(space, config));
|
|
1865
|
+
}
|
|
1866
|
+
/** This function throws an `Error` for a space with a break of `checkSpace`. */
|
|
1867
|
+
function assertSpace(space) {
|
|
1868
|
+
const breaks = checkSpace(space);
|
|
1869
|
+
if (breaks.length > 0) throw new Error(`The space ${String(space?.engine)} is not good: ${breaks.map((b) => b.detail).join("; ")}.`);
|
|
1870
|
+
}
|
|
1871
|
+
/** This function throws a `RangeError` when a value is not a whole number of `min` or more. */
|
|
1872
|
+
function assertWhole(value, name, min) {
|
|
1873
|
+
if (!Number.isInteger(value) || value < min) throw new RangeError(`The option ${name} is ${value}, not a whole number of ${min} or more.`);
|
|
1874
|
+
}
|
|
1875
|
+
/** This function gives the last point of the step grid of a numeric param. */
|
|
1876
|
+
function lastPoint(p) {
|
|
1877
|
+
const min = p.min;
|
|
1878
|
+
const step = p.step ?? (p.kind === "int" ? 1 : void 0);
|
|
1879
|
+
if (step === void 0) return p.max;
|
|
1880
|
+
return min + Math.floor((p.max - min) / step + 1e-9) * step;
|
|
1881
|
+
}
|
|
1882
|
+
/** This function moves a value of a numeric param onto its step and into its range. */
|
|
1883
|
+
function snap(p, x, digits) {
|
|
1884
|
+
const min = p.min;
|
|
1885
|
+
const step = p.step ?? (p.kind === "int" ? 1 : void 0);
|
|
1886
|
+
let v = step === void 0 ? x : min + Math.round((x - min) / step) * step;
|
|
1887
|
+
v = Math.min(lastPoint(p), Math.max(min, v));
|
|
1888
|
+
return p.kind === "int" ? zero(Math.round(v)) : Math.min(p.max, Math.max(min, realForm(v, digits)));
|
|
1889
|
+
}
|
|
1890
|
+
/** This function gives the point at the fraction `t` of the range of a numeric param, on its scale. */
|
|
1891
|
+
function at01(p, t) {
|
|
1892
|
+
const min = p.min;
|
|
1893
|
+
const max = p.max;
|
|
1894
|
+
if (p.scale === "log") return Math.exp(Math.log(min) + t * (Math.log(max) - Math.log(min)));
|
|
1895
|
+
return min + t * (max - min);
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* This function gives the grid values of one param, in increasing order. An `enum` gives its values, and a `bool` gives `false` and `true`.
|
|
1899
|
+
* With 1 level, a numeric param gives its default. With k levels, it gives k points from `min` to `max` on its scale and step, without repeats.
|
|
1900
|
+
*/
|
|
1901
|
+
function paramLevels(param, levels) {
|
|
1902
|
+
assertWhole(levels, "levels", 1);
|
|
1903
|
+
if (param.kind === "bool") return [false, true];
|
|
1904
|
+
if (param.kind === "enum") return [...param.values ?? []];
|
|
1905
|
+
if (levels === 1 || param.min === param.max) return [normalize(param, param.default)];
|
|
1906
|
+
const out = [];
|
|
1907
|
+
const steps = param.kind === "int" || param.step !== void 0 ? Math.round((lastPoint(param) - param.min) / (param.step ?? 1)) + 1 : Infinity;
|
|
1908
|
+
if (steps <= levels && param.scale !== "log") for (let j = 0; j < steps; j++) out.push(snap(param, param.min + j * (param.step ?? 1), 12));
|
|
1909
|
+
else for (let j = 0; j < levels; j++) out.push(snap(param, at01(param, j / (levels - 1)), 12));
|
|
1910
|
+
return [...new Set(out)];
|
|
1911
|
+
}
|
|
1912
|
+
/** This function tells if a later param of the list reads a param in its `when`. */
|
|
1913
|
+
const readLater = (params, i) => params.slice(i + 1).some((q) => q.when?.param === params[i].name);
|
|
1914
|
+
/** This function gives the count of the grid assignments of the params of an option, with only the active params. */
|
|
1915
|
+
function countParams(params, levels, i = 0, values = {}) {
|
|
1916
|
+
if (i === params.length) return 1n;
|
|
1917
|
+
const p = params[i];
|
|
1918
|
+
if (!isActive(p, values)) return countParams(params, levels, i + 1, values);
|
|
1919
|
+
const lv = paramLevels(p, levels);
|
|
1920
|
+
if (!readLater(params, i)) return BigInt(lv.length) * countParams(params, levels, i + 1, values);
|
|
1921
|
+
let sum = 0n;
|
|
1922
|
+
for (const v of lv) sum += countParams(params, levels, i + 1, {
|
|
1923
|
+
...values,
|
|
1924
|
+
[p.name]: v
|
|
1925
|
+
});
|
|
1926
|
+
return sum;
|
|
1927
|
+
}
|
|
1928
|
+
/** This function gives the grid assignments of the params of an option, in a stable order. */
|
|
1929
|
+
function* gridParams(params, levels, i = 0, values = {}) {
|
|
1930
|
+
if (i === params.length) {
|
|
1931
|
+
yield { ...values };
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
const p = params[i];
|
|
1935
|
+
if (!isActive(p, values)) {
|
|
1936
|
+
yield* gridParams(params, levels, i + 1, values);
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
for (const v of paramLevels(p, levels)) yield* gridParams(params, levels, i + 1, {
|
|
1940
|
+
...values,
|
|
1941
|
+
[p.name]: v
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
const optionIds = (choices) => new Map([...choices].map(([k, o]) => [k, o?.id ?? null]));
|
|
1945
|
+
const keeps = (space, choices) => (space.constraints ?? []).every((c) => holds(c, optionIds(choices)));
|
|
1946
|
+
/** This function gives the count of the member lists of a meta option from `base` base configs, or `null` above `limit`. */
|
|
1947
|
+
function memberLists(base, rule, limit) {
|
|
1948
|
+
let sum = 0n;
|
|
1949
|
+
for (let m = rule.min; m <= rule.max; m++) {
|
|
1950
|
+
let n = 1n;
|
|
1951
|
+
for (let k = 0; k < m; k++) {
|
|
1952
|
+
n = n * (base - BigInt(k)) / (rule.ordered ? 1n : BigInt(k + 1));
|
|
1953
|
+
if (n <= 0n) break;
|
|
1954
|
+
}
|
|
1955
|
+
if (n <= 0n) break;
|
|
1956
|
+
sum += n;
|
|
1957
|
+
if (sum > limit) return null;
|
|
1958
|
+
}
|
|
1959
|
+
return sum;
|
|
1960
|
+
}
|
|
1961
|
+
/** This function multiplies two counts. A `null` count or a product above `limit` gives `null`. */
|
|
1962
|
+
const times = (a, b, limit) => a === null || b === null ? null : a * b > limit ? null : a * b;
|
|
1963
|
+
/**
|
|
1964
|
+
* This function gives the combinations of options of a space and their grid counts.
|
|
1965
|
+
* A product space joins the combinations of its parts. Each combination keeps the constraints.
|
|
1966
|
+
*/
|
|
1967
|
+
function combos(space, levels, meta, limit) {
|
|
1968
|
+
if (space.parts !== void 0 && space.parts.length > 0) {
|
|
1969
|
+
let acc = [{
|
|
1970
|
+
choices: /* @__PURE__ */ new Map(),
|
|
1971
|
+
base: true,
|
|
1972
|
+
weight: 1n
|
|
1973
|
+
}];
|
|
1974
|
+
for (const part of space.parts) {
|
|
1975
|
+
const head = `${part.engine}.`;
|
|
1976
|
+
const own = combos(part, levels, meta, limit);
|
|
1977
|
+
const next = [];
|
|
1978
|
+
for (const a of acc) for (const b of own) {
|
|
1979
|
+
const choices = new Map(a.choices);
|
|
1980
|
+
for (const [k, o] of b.choices) choices.set(head + k, o);
|
|
1981
|
+
const base = a.base && b.base;
|
|
1982
|
+
next.push({
|
|
1983
|
+
choices,
|
|
1984
|
+
base,
|
|
1985
|
+
weight: base ? a.weight * b.weight : times(a.weight, b.weight, limit)
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
acc = next;
|
|
1989
|
+
}
|
|
1990
|
+
return acc.filter((c) => keeps(space, c.choices));
|
|
1991
|
+
}
|
|
1992
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1993
|
+
const countOf = (o) => {
|
|
1994
|
+
if (!counts.has(o)) counts.set(o, countParams(o.params, levels));
|
|
1995
|
+
return counts.get(o);
|
|
1996
|
+
};
|
|
1997
|
+
const out = [];
|
|
1998
|
+
const stages = space.stages;
|
|
1999
|
+
const walk = (i, choices, weight) => {
|
|
2000
|
+
if (i === stages.length) {
|
|
2001
|
+
if (keeps(space, choices)) out.push({
|
|
2002
|
+
choices: new Map(choices),
|
|
2003
|
+
base: true,
|
|
2004
|
+
weight
|
|
2005
|
+
});
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const s = stages[i];
|
|
2009
|
+
const list = s.options.filter((o) => o.members === void 0);
|
|
2010
|
+
if (s.optional) list.push(null);
|
|
2011
|
+
for (const o of list) {
|
|
2012
|
+
choices.set(s.name, o);
|
|
2013
|
+
walk(i + 1, choices, o === null ? weight : weight * countOf(o));
|
|
2014
|
+
}
|
|
2015
|
+
choices.delete(s.name);
|
|
2016
|
+
};
|
|
2017
|
+
walk(0, /* @__PURE__ */ new Map(), 1n);
|
|
2018
|
+
if (!meta) return out;
|
|
2019
|
+
const base = out.reduce((sum, c) => sum + c.weight, 0n);
|
|
2020
|
+
for (const s of stages) for (const o of s.options) {
|
|
2021
|
+
if (o.members === void 0) continue;
|
|
2022
|
+
const choices = new Map(stages.map((x) => [x.name, x === s ? o : null]));
|
|
2023
|
+
if (keeps(space, choices)) out.push({
|
|
2024
|
+
choices,
|
|
2025
|
+
base: false,
|
|
2026
|
+
weight: times(countOf(o), memberLists(base, o.members, limit), limit)
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
return out;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* This function gives the count of the grid points of a space, with `levels` levels for each numeric param.
|
|
2033
|
+
* It counts only the active params and the combinations that keep the constraints. It gives the meta configs apart.
|
|
2034
|
+
*/
|
|
2035
|
+
function spaceSize(space, options) {
|
|
2036
|
+
assertSpace(space);
|
|
2037
|
+
assertWhole(options.levels, "levels", 1);
|
|
2038
|
+
const limit = options.maxMeta ?? 10n ** 18n;
|
|
2039
|
+
let base = 0n;
|
|
2040
|
+
let meta = 0n;
|
|
2041
|
+
for (const c of combos(space, options.levels, true, limit)) if (c.base) base += c.weight;
|
|
2042
|
+
else meta = meta === null || c.weight === null || meta + c.weight > limit ? null : meta + c.weight;
|
|
2043
|
+
return {
|
|
2044
|
+
base,
|
|
2045
|
+
meta
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
/** This function gives the cartesian product of lists, with the last list as the fastest. */
|
|
2049
|
+
function* product(lists, i = 0, acc = []) {
|
|
2050
|
+
if (i === lists.length) {
|
|
2051
|
+
yield [...acc];
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
for (const x of lists[i]) {
|
|
2055
|
+
acc.push(x);
|
|
2056
|
+
yield* product(lists, i + 1, acc);
|
|
2057
|
+
acc.pop();
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
/** This function gives the configs of the grid of a space, without meta options. */
|
|
2061
|
+
function gridConfigs(space, levels, limit) {
|
|
2062
|
+
const size = spaceSize(space, {
|
|
2063
|
+
levels,
|
|
2064
|
+
maxMeta: 0n
|
|
2065
|
+
}).base;
|
|
2066
|
+
if (size > BigInt(limit)) throw new RangeError(`The grid of the space ${space.engine} has ${size} configs, more than the limit ${limit}.`);
|
|
2067
|
+
const out = [];
|
|
2068
|
+
for (const c of combos(space, levels, false, 0n)) {
|
|
2069
|
+
const names = [...c.choices.keys()];
|
|
2070
|
+
const lists = names.map((k) => {
|
|
2071
|
+
const o = c.choices.get(k);
|
|
2072
|
+
return o === null ? [null] : [...gridParams(o.params, levels)].map((params) => ({
|
|
2073
|
+
option: o.id,
|
|
2074
|
+
params
|
|
2075
|
+
}));
|
|
2076
|
+
});
|
|
2077
|
+
for (const row of product(lists)) out.push(canonicalOf(space, {
|
|
2078
|
+
space: space.engine,
|
|
2079
|
+
stages: Object.fromEntries(names.map((k, i) => [k, row[i]]))
|
|
2080
|
+
}, false));
|
|
2081
|
+
}
|
|
2082
|
+
return out;
|
|
2083
|
+
}
|
|
2084
|
+
/** This function gives a random order of the numbers from 0 to `n - 1`. */
|
|
2085
|
+
function permutation(n, r) {
|
|
2086
|
+
const out = Array.from({ length: n }, (_, i) => i);
|
|
2087
|
+
for (let i = n - 1; i > 0; i--) {
|
|
2088
|
+
const j = Math.floor(r() * (i + 1));
|
|
2089
|
+
[out[i], out[j]] = [out[j], out[i]];
|
|
2090
|
+
}
|
|
2091
|
+
return out;
|
|
2092
|
+
}
|
|
2093
|
+
/** This function gives the value of a param at the fraction `u` of its range. A sampled real number has 6 significant digits. */
|
|
2094
|
+
function sampleValue(p, u) {
|
|
2095
|
+
if (p.kind === "bool") return u >= .5;
|
|
2096
|
+
if (p.kind === "enum") {
|
|
2097
|
+
const values = p.values;
|
|
2098
|
+
return values[Math.min(values.length - 1, Math.floor(u * values.length))];
|
|
2099
|
+
}
|
|
2100
|
+
if (p.min === p.max) return normalize(p, p.min);
|
|
2101
|
+
if (p.scale !== "log" && (p.kind === "int" || p.step !== void 0)) {
|
|
2102
|
+
const steps = Math.round((lastPoint(p) - p.min) / (p.step ?? 1)) + 1;
|
|
2103
|
+
return snap(p, p.min + Math.min(steps - 1, Math.floor(u * steps)) * (p.step ?? 1), 6);
|
|
2104
|
+
}
|
|
2105
|
+
return snap(p, at01(p, u), 6);
|
|
2106
|
+
}
|
|
2107
|
+
/** This function gives a seed of 32 bits from a seed and a step name. */
|
|
2108
|
+
const subSeed = (seed, step) => Math.floor(stepRandom(seed, step)() * 4294967296);
|
|
2109
|
+
/** This function gives a sample of a space: Latin hypercube strata over the options, the params and the member counts. */
|
|
2110
|
+
function sampleConfigs(space, n, seed, meta, maxTries) {
|
|
2111
|
+
const stages = space.stages;
|
|
2112
|
+
const prefix = prefixes(space);
|
|
2113
|
+
const lists = stages.map((s) => {
|
|
2114
|
+
const list = s.options.filter((o) => meta || o.members === void 0);
|
|
2115
|
+
if (s.optional) list.push(null);
|
|
2116
|
+
return list;
|
|
2117
|
+
});
|
|
2118
|
+
const points = [];
|
|
2119
|
+
let tries = 0;
|
|
2120
|
+
for (let round = 0; points.length < n; round++) {
|
|
2121
|
+
const r = stepRandom(seed, `options:${round}`);
|
|
2122
|
+
const columns = lists.map((list) => permutation(n, r).map((p) => list[Math.floor(p * list.length / n)]));
|
|
2123
|
+
for (let i = 0; i < n && points.length < n; i++) {
|
|
2124
|
+
if (tries >= maxTries) throw new Error(`The constraints of the space ${space.engine} remove too many points: ${points.length} of ${n} configs after ${tries} tries.`);
|
|
2125
|
+
tries++;
|
|
2126
|
+
const choices = new Map(stages.map((s, k) => [s.name, columns[k][i]]));
|
|
2127
|
+
const metaPart = /* @__PURE__ */ new Map();
|
|
2128
|
+
for (const s of stages) if (choices.get(s.name)?.members !== void 0 && !metaPart.has(prefix.get(s.name))) metaPart.set(prefix.get(s.name), s.name);
|
|
2129
|
+
for (const s of stages) {
|
|
2130
|
+
const m = metaPart.get(prefix.get(s.name));
|
|
2131
|
+
if (m !== void 0 && m !== s.name) choices.set(s.name, null);
|
|
2132
|
+
}
|
|
2133
|
+
if (keeps(space, choices)) points.push(choices);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
const values = points.map(() => stages.map(() => ({})));
|
|
2137
|
+
const members = points.map(() => stages.map(() => []));
|
|
2138
|
+
stages.forEach((s, k) => {
|
|
2139
|
+
for (const o of s.options) {
|
|
2140
|
+
const users = points.flatMap((c, i) => c.get(s.name) === o ? [i] : []);
|
|
2141
|
+
if (users.length === 0) continue;
|
|
2142
|
+
for (const p of o.params) {
|
|
2143
|
+
const active = users.filter((i) => isActive(p, values[i][k]));
|
|
2144
|
+
const r = stepRandom(seed, `param:${s.name}:${o.id}:${p.name}`);
|
|
2145
|
+
permutation(active.length, r).forEach((stratum, j) => {
|
|
2146
|
+
values[active[j]][k][p.name] = sampleValue(p, (stratum + r()) / active.length);
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
if (o.members === void 0) continue;
|
|
2150
|
+
const part = partOf(space, s.name).space;
|
|
2151
|
+
const poolSize = Math.max(n, 4 * o.members.max, 8);
|
|
2152
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2153
|
+
const pool = sampleConfigs(part, poolSize, subSeed(seed, `pool:${s.name}:${o.id}`), false, Math.max(1e3, 100 * poolSize)).filter((c) => {
|
|
2154
|
+
const id = idOf(c);
|
|
2155
|
+
return seen.has(id) ? false : (seen.add(id), true);
|
|
2156
|
+
});
|
|
2157
|
+
const r = stepRandom(seed, `members:${s.name}:${o.id}`);
|
|
2158
|
+
const { min, max } = o.members;
|
|
2159
|
+
permutation(users.length, r).forEach((stratum, j) => {
|
|
2160
|
+
const count = min + Math.min(max - min, Math.floor((stratum + r()) / users.length * (max - min + 1)));
|
|
2161
|
+
if (pool.length < count) throw new Error(`The space ${part.engine} has only ${pool.length} distinct base samples, fewer than the ${count} members of ${s.name}.${o.id}.`);
|
|
2162
|
+
const picked = permutation(pool.length, r).slice(0, count);
|
|
2163
|
+
members[users[j]][k] = picked.map((x) => pool[x]);
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
});
|
|
2167
|
+
return points.map((choices, i) => {
|
|
2168
|
+
const out = {};
|
|
2169
|
+
stages.forEach((s, k) => {
|
|
2170
|
+
const o = choices.get(s.name);
|
|
2171
|
+
out[s.name] = o === null ? null : o.members === void 0 ? {
|
|
2172
|
+
option: o.id,
|
|
2173
|
+
params: values[i][k]
|
|
2174
|
+
} : {
|
|
2175
|
+
option: o.id,
|
|
2176
|
+
params: values[i][k],
|
|
2177
|
+
members: members[i][k]
|
|
2178
|
+
};
|
|
2179
|
+
});
|
|
2180
|
+
return canonicalConfig(space, {
|
|
2181
|
+
space: space.engine,
|
|
2182
|
+
stages: out
|
|
2183
|
+
});
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
/**
|
|
2187
|
+
* This function gives configs of a space, in their canonical form. Each config keeps the constraints. The result is deterministic.
|
|
2188
|
+
*
|
|
2189
|
+
* - The mode `grid` gives each grid point without a meta option, in a stable order. Stages, options and values go in the order of the space.
|
|
2190
|
+
* - 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.
|
|
2191
|
+
* - A sample draws again when a point breaks a constraint. After `maxTries` draws, it throws an `Error`. Two sampled configs can be equal.
|
|
2192
|
+
*/
|
|
2193
|
+
function enumerateSpace(space, options) {
|
|
2194
|
+
assertSpace(space);
|
|
2195
|
+
if (options.mode === "grid") {
|
|
2196
|
+
assertWhole(options.levels, "levels", 1);
|
|
2197
|
+
return gridConfigs(space, options.levels, options.limit ?? 1e5);
|
|
2198
|
+
}
|
|
2199
|
+
if (options.mode !== "sample") throw new RangeError(`The mode ${String(options.mode)} is not grid or sample.`);
|
|
2200
|
+
assertWhole(options.n, "n", 0);
|
|
2201
|
+
if (!Number.isInteger(options.seed)) throw new RangeError(`The seed ${options.seed} is not a whole number.`);
|
|
2202
|
+
const meta = options.meta ?? "skip";
|
|
2203
|
+
if (meta !== "skip" && meta !== "sample") throw new RangeError(`The option meta is ${String(meta)}, not skip or sample.`);
|
|
2204
|
+
if (options.n === 0) return [];
|
|
2205
|
+
return sampleConfigs(space, options.n, options.seed, meta === "sample", options.maxTries ?? Math.max(1e3, 100 * options.n));
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* This function joins configs of the parts of a product space into one config of the space `space`.
|
|
2209
|
+
* The name of each stage gets the prefix `<space of the part>.`. The members of a meta option do not change.
|
|
2210
|
+
*/
|
|
2211
|
+
function joinConfigs(space, parts) {
|
|
2212
|
+
const stages = {};
|
|
2213
|
+
for (const part of parts) for (const [k, c] of Object.entries(part.stages)) stages[`${part.space}.${k}`] = c;
|
|
2214
|
+
return {
|
|
2215
|
+
space,
|
|
2216
|
+
stages
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
/** This function gives the config of the part `engine` of a joint config. It throws a `RangeError` when no stage has the prefix `<engine>.`. */
|
|
2220
|
+
function splitConfig(config, engine) {
|
|
2221
|
+
const head = `${engine}.`;
|
|
2222
|
+
const entries = Object.entries(config.stages).filter(([k]) => k.startsWith(head));
|
|
2223
|
+
if (entries.length === 0) throw new RangeError(`The config of the space ${config.space} has no stage of the part ${engine}.`);
|
|
2224
|
+
return {
|
|
2225
|
+
space: engine,
|
|
2226
|
+
stages: Object.fromEntries(entries.map(([k, c]) => [k.slice(head.length), c]))
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* This function joins two spaces, for example a packer and a router, into the space `name`. The stages of `a` and `b` get the prefixes `<a.engine>.` and `<b.engine>.`.
|
|
2231
|
+
* The constraints get the same prefixes. The named configs are the pairs `<name of a>+<name of b>`. It throws a `RangeError` when the two engines have the same name.
|
|
2232
|
+
*/
|
|
2233
|
+
function productSpace(a, b, name) {
|
|
2234
|
+
if (a.engine === b.engine) throw new RangeError(`The two spaces have the same engine ${a.engine}.`);
|
|
2235
|
+
if (name === "") throw new RangeError("The product space has no name.");
|
|
2236
|
+
const stagesOf = (s) => s.stages.map((x) => ({
|
|
2237
|
+
...x,
|
|
2238
|
+
name: `${s.engine}.${x.name}`
|
|
2239
|
+
}));
|
|
2240
|
+
const ref = (s, r) => ({
|
|
2241
|
+
stage: `${s.engine}.${r.stage}`,
|
|
2242
|
+
option: r.option
|
|
2243
|
+
});
|
|
2244
|
+
const constraintsOf = (s) => (s.constraints ?? []).map((c) => ({
|
|
2245
|
+
if: ref(s, c.if),
|
|
2246
|
+
then: "not" in c.then ? { not: ref(s, c.then.not) } : ref(s, c.then)
|
|
2247
|
+
}));
|
|
2248
|
+
const named = {};
|
|
2249
|
+
for (const [ka, ca] of Object.entries(a.named ?? {})) for (const [kb, cb] of Object.entries(b.named ?? {})) named[`${ka}+${kb}`] = joinConfigs(name, [ca, cb]);
|
|
2250
|
+
const constraints = [...constraintsOf(a), ...constraintsOf(b)];
|
|
2251
|
+
return {
|
|
2252
|
+
schema: STRATEGY_SPACE_SCHEMA,
|
|
2253
|
+
engine: name,
|
|
2254
|
+
version: `${a.engine}@${a.version}+${b.engine}@${b.version}`,
|
|
2255
|
+
stages: [...stagesOf(a), ...stagesOf(b)],
|
|
2256
|
+
...constraints.length > 0 ? { constraints } : {},
|
|
2257
|
+
...Object.keys(named).length > 0 ? { named } : {},
|
|
2258
|
+
parts: [a, b]
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
export { DEFAULT_TOLERANCE, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, JOINT_MEASURES, JOINT_MOVED_MIN, RECORD_EPS, RECORD_SCALE, SIDES, SPLIT_SCHEMA, STRATEGY_SPACE_SCHEMA, applyPack, boundsOf, canonicalConfig, canonicalJson, checkConfig, checkDeclared, checkInput, checkSpace, checkSplit, compareMeasure, configId, corpusHashOf, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, declaredClaimOf, declaredCounts, documentHash, edgeStep, enumerateSpace, exercised, fnv1a32, grow, joinConfigs, leaveOneSeedOut, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, packHard, paramLevels, pickMoves, placedRect, portfolio, productSpace, randomInt, rectInRegion, regionHitsRect, selectBest, sha256Hex, spaceSize, splitConfig, splitFileName, splitFolds, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide };
|
|
1544
2262
|
|
|
1545
2263
|
//# sourceMappingURL=index.js.map
|