@mark1russell7/layout-contract 0.2.5 → 0.2.7

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 CHANGED
@@ -18,8 +18,10 @@ The package has no runtime dependencies.
18
18
  | The random numbers | `mulberry32`, `fnv1a32`, `stepRandom`, `randomInt` |
19
19
  | The hashes | `sha256Hex`, `canonicalJson`, `documentHash`, `outputHash`, `HashOptions` |
20
20
  | The promotion guards | `outputGroups`, `OutputGroup`, `exercised`, `ExerciseEvidence` |
21
+ | The portfolio | `portfolio`, `Portfolio`, `PortfolioKey`, `PortfolioLevel`, `PortfolioOptions`, `PortfolioPick`, `PortfolioTrace`, `Member`, `MemberTrace`, `MemberStatus`, `pickMoves` |
22
+ | The pick rule | `selectBest`, `SelectCandidate`, `SelectLevel`, `Selection`, `PickSpec` |
21
23
  | The turn | `turnBox`, `turnPort`, `turnMargin`, `turnRegion`, `turnSide`, `turnPoint` |
22
- | The tolerances | `EPS`, `RECORD_EPS`, `Tolerance`, `DEFAULT_TOLERANCE` |
24
+ | The tolerances | `EPS`, `RECORD_EPS`, `RECORD_SCALE`, `Tolerance`, `DEFAULT_TOLERANCE` |
23
25
  | The hard rules | `countOverlaps`, `countObstacleHits`, `regionHitsRect`, `rectInRegion`, `countOutside`, `ContainerTest`, `countPortsOffBorder`, `packHard`, `PackHard`, `placedRect`, `checkInput` |
24
26
 
25
27
  ## The rules
@@ -51,4 +53,4 @@ const placed = applyPack(input, output);
51
53
  const hard = packHard(input, output);
52
54
  ```
53
55
 
54
- The formats are in `docs/FORMAT.md` of `packing` and `docs/LAYOUT-RECORDS.md` of `highways`. The joint measures are in `docs/JOINT-MEASURES.md` of this repository, and the joint corpus is in `docs/JOINT-CORPUS.md`. The promotion guards are in `docs/PROMOTION.md`.
56
+ The formats are in `docs/FORMAT.md` of `packing` and `docs/LAYOUT-RECORDS.md` of `highways`. The joint measures are in `docs/JOINT-MEASURES.md` of this repository, and the joint corpus is in `docs/JOINT-CORPUS.md`. The promotion guards are in `docs/PROMOTION.md`. The portfolio and its pick rule are in `docs/PORTFOLIO.md`.
package/build/index.d.ts CHANGED
@@ -66,9 +66,14 @@ export declare function overlapOf(a: Rect, b: Rect): {
66
66
  };
67
67
  /** The epsilon for the float compares in an engine. */
68
68
  export declare const EPS = 1e-6;
69
+ /**
70
+ * The count of grid steps in one unit of a written file. A file rounds each number `v` to `Math.round(v * RECORD_SCALE) / RECORD_SCALE`.
71
+ * Use a multiplication by this value, not a division by `RECORD_EPS`. The two differ at some half steps.
72
+ */
73
+ export declare const RECORD_SCALE = 1e3;
69
74
  /**
70
75
  * The epsilon for the float compares on a written file. A file rounds each number to 3 decimals,
71
- * so a difference of two values can move by up to 0.001.
76
+ * so a difference of two values can move by up to 0.001. It is `1 / RECORD_SCALE`.
72
77
  */
73
78
  export declare const RECORD_EPS = .001;
74
79
  /**
@@ -437,7 +442,8 @@ export type Verdict = "better" | "worse" | "tie";
437
442
  export declare function minEffectOf(spec: Pick<MeasureSpec, "minEffect">, a?: number, b?: number): number;
438
443
  /**
439
444
  * This function compares the value `candidate` with the value `baseline` of one measure on one input.
440
- * A change of at most `minEffectOf(spec, candidate, baseline)` is a tie. The test is symmetric: a swap of the two values swaps better and worse.
445
+ * The compare uses whole grid steps of `RECORD_EPS`. Thus a live value and its rounded record give the same verdict.
446
+ * A change of at most the threshold of `minEffectOf` in grid steps is a tie. A swap of the two values swaps better and worse.
441
447
  * Two equal values are a tie, also two infinite values. A value `NaN` is an error.
442
448
  */
443
449
  export declare function compareMeasure(candidate: number, baseline: number, spec: Pick<MeasureSpec, "direction" | "minEffect">): Verdict;
@@ -556,4 +562,131 @@ export interface ExerciseEvidence {
556
562
  * The function throws a `RangeError` when only one of the two lists is given, or when their lengths are different.
557
563
  */
558
564
  export declare function exercised(evidence: ExerciseEvidence): boolean;
565
+ /** The part of a `MeasureSpec` that a pick uses: the direction and the `minEffect`. */
566
+ export type PickSpec = Pick<MeasureSpec, "direction" | "minEffect">;
567
+ /** A member of a portfolio: a function with an id. A portfolio is also a member. */
568
+ export interface Member<I, O> {
569
+ readonly id: string;
570
+ /** This function gives the output of the member for an input. */
571
+ run(input: I): O;
572
+ }
573
+ /** One level of the key of a portfolio: a measure of an output. */
574
+ export interface PortfolioLevel<I, O> {
575
+ /** The id of the level. The id `hard` is reserved. */
576
+ readonly id: string;
577
+ readonly spec: PickSpec;
578
+ /** This function gives the value of the measure for the output of a member. The value must be finite. */
579
+ value(input: I, output: O): number;
580
+ }
581
+ /** The key of a portfolio: the hard level, then the other levels in order. */
582
+ export interface PortfolioKey<I, O> {
583
+ /** This function gives the count of hard breaks. It is level 0, with the direction `lower` and the `minEffect` 0. */
584
+ hard(input: I, output: O): number;
585
+ /** The levels after the hard level, in the order of the pick. */
586
+ readonly levels: readonly PortfolioLevel<I, O>[];
587
+ /** This function gives the hash of an output. The default is `outputHash` with the default grid. */
588
+ hash?(output: O): string;
589
+ }
590
+ /** The options of a portfolio. */
591
+ export interface PortfolioOptions {
592
+ /** The time limit in milliseconds, 0 or more. The first member always starts. A member that starts always finishes. */
593
+ readonly budgetMs?: number;
594
+ /** The largest count of members that run, a whole number of 1 or more. The other members are cut. */
595
+ readonly maxMembers?: number;
596
+ /** The clock in milliseconds. The default is `performance.now`, or `Date.now` without `performance`. */
597
+ readonly now?: () => number;
598
+ }
599
+ /** The status of a member in a pick. */
600
+ export type MemberStatus = "ran" | "cut" | "error";
601
+ /** The trace of one member in a pick. */
602
+ export interface MemberTrace {
603
+ readonly id: string;
604
+ readonly status: MemberStatus;
605
+ /** The value of each level, with the key `hard` for the hard level. Only a member with the status `ran` has values. */
606
+ readonly values?: Readonly<Record<string, number>>;
607
+ /** The time of the run of the member, in milliseconds. */
608
+ readonly ms?: number;
609
+ /** The hash of the output of the member. */
610
+ readonly outputHash?: string;
611
+ /** The message of the error of a member with the status `error`. */
612
+ readonly error?: string;
613
+ }
614
+ /** The trace of one pick of a portfolio. */
615
+ export interface PortfolioTrace {
616
+ /** One entry for each member, in the order of the members. A cut member is also in the list. */
617
+ readonly members: readonly MemberTrace[];
618
+ readonly winner: string;
619
+ /** The index of the winner in the list of the members. */
620
+ readonly winnerIndex: number;
621
+ /** The value `true` tells that the winner is not member 0 and has a different output hash. */
622
+ readonly picked: boolean;
623
+ /** The time of the full pick, in milliseconds. */
624
+ readonly totalMs: number;
625
+ readonly budgetMs?: number;
626
+ readonly maxMembers?: number;
627
+ /** The count of the cut members. */
628
+ readonly cut: number;
629
+ /** The value `false` tells that the time limit cut one member or more. Then a run on a different computer can give a different pick. */
630
+ readonly deterministic: boolean;
631
+ /** The ids of the members that stay in after each level of `selectBest`. The first list is for the hard level. */
632
+ readonly kept: readonly (readonly string[])[];
633
+ }
634
+ /** The result of one pick of a portfolio: the output of the winner, its id and the trace. */
635
+ export interface PortfolioPick<O> {
636
+ readonly output: O;
637
+ readonly winner: string;
638
+ readonly trace: PortfolioTrace;
639
+ }
640
+ /** A portfolio: a member that runs its members and gives the output of the best member. */
641
+ export interface Portfolio<I, O> extends Member<I, O> {
642
+ /** This function starts the members on an input and gives the output of the winner with the trace. */
643
+ pick(input: I): PortfolioPick<O>;
644
+ }
645
+ /** A candidate of `selectBest`: an id and the value of each level. */
646
+ export interface SelectCandidate {
647
+ readonly id: string;
648
+ readonly values: Readonly<Record<string, number>>;
649
+ }
650
+ /** A level of `selectBest`: the id of a value and its spec. */
651
+ export interface SelectLevel {
652
+ readonly id: string;
653
+ readonly spec: PickSpec;
654
+ }
655
+ /** The result of `selectBest`. */
656
+ export interface Selection {
657
+ readonly winner: string;
658
+ /** The ids of the candidates that stay in after each level, in the given order. */
659
+ readonly kept: readonly (readonly string[])[];
660
+ }
661
+ /**
662
+ * This function picks the best candidate. For each level, it finds the best value of the candidates that stay in.
663
+ * It keeps each candidate that is not worse than that best value by `compareMeasure`.
664
+ * The winner is the first candidate, in the given order, that stays in after the last level.
665
+ * Without levels, the winner is the first candidate and `kept` is empty.
666
+ *
667
+ * The reason for the rule: a scan that compares each candidate with the current winner is not transitive, because of the `minEffect` ties.
668
+ * Such a scan can drift away from the best value. An example: "a" is lower with the `minEffect` 0.005, and "b" is higher.
669
+ * The candidates A (0, 1), B (0.004, 2) and C (0.008, 3) give the winner B.
670
+ * The scan picks C, but A is better than C on "a".
671
+ *
672
+ * The function throws a `RangeError` for an empty list of candidates, for a missing value, and for a value that is not finite.
673
+ */
674
+ export declare function selectBest(candidates: readonly SelectCandidate[], levels: readonly SelectLevel[]): Selection;
675
+ /** This function gives the move `pick` of a pick: 1 when `picked` is true, else 0. A record keeps it as the trace measure `move.pick`. */
676
+ export declare function pickMoves(trace: Pick<PortfolioTrace, "picked">): {
677
+ pick: number;
678
+ };
679
+ /**
680
+ * This function makes a portfolio. Its `pick` starts the members in their order. `selectBest` picks the winner of the members with the status `ran`.
681
+ * The first level is the hard level, then the levels of the key follow.
682
+ * `members` is a list, or a function of the input that gives the list.
683
+ *
684
+ * A member that throws, or that gives a value that is not finite, gets the status `error`. It does not win.
685
+ * When each member has an error, `pick` throws the error of the first member.
686
+ * `docs/PORTFOLIO.md` tells the rules of the time limit, the trace and `picked`.
687
+ *
688
+ * The function throws a `RangeError` for a level with the id `hard`, for two levels with the same id and for a bad option.
689
+ * It also throws a `RangeError` for an empty list of members, or for two members with the same id.
690
+ */
691
+ export declare function portfolio<I, O>(id: string, members: readonly Member<I, O>[] | ((input: I) => readonly Member<I, O>[]), key: PortfolioKey<I, O>, options?: PortfolioOptions): Portfolio<I, O>;
559
692
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/geometry.ts","../src/tolerance.ts","../src/input.ts","../src/output.ts","../src/record.ts","../src/rules.ts","../src/turn.ts","../src/measures.ts","../src/strategy.ts","../src/random.ts","../src/edges.ts","../src/hash.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/geometry.ts","../src/tolerance.ts","../src/input.ts","../src/output.ts","../src/record.ts","../src/rules.ts","../src/turn.ts","../src/measures.ts","../src/strategy.ts","../src/random.ts","../src/edges.ts","../src/hash.ts","../src/portfolio.ts"],"mappings":"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"}
package/build/index.js CHANGED
@@ -114,8 +114,13 @@ function overlapOf(a, b) {
114
114
  /** The epsilon for the float compares in an engine. */
115
115
  const EPS = 1e-6;
116
116
  /**
117
+ * The count of grid steps in one unit of a written file. A file rounds each number `v` to `Math.round(v * RECORD_SCALE) / RECORD_SCALE`.
118
+ * Use a multiplication by this value, not a division by `RECORD_EPS`. The two differ at some half steps.
119
+ */
120
+ const RECORD_SCALE = 1e3;
121
+ /**
117
122
  * The epsilon for the float compares on a written file. A file rounds each number to 3 decimals,
118
- * so a difference of two values can move by up to 0.001.
123
+ * so a difference of two values can move by up to 0.001. It is `1 / RECORD_SCALE`.
119
124
  */
120
125
  const RECORD_EPS = .001;
121
126
  /**
@@ -669,16 +674,29 @@ function minEffectOf(spec, a = 0, b = 0) {
669
674
  const t = typeof m === "number" ? m : m.rel * Math.max(Math.abs(a), Math.abs(b));
670
675
  return Math.max(RECORD_EPS, Number.isFinite(t) ? t : 0);
671
676
  }
677
+ /** The slack of the threshold in grid steps. It absorbs only the float noise of the threshold, because the change is a whole number. */
678
+ const STEP_NOISE = 1e-6;
672
679
  /**
673
680
  * This function compares the value `candidate` with the value `baseline` of one measure on one input.
674
- * A change of at most `minEffectOf(spec, candidate, baseline)` is a tie. The test is symmetric: a swap of the two values swaps better and worse.
681
+ * The compare uses whole grid steps of `RECORD_EPS`. Thus a live value and its rounded record give the same verdict.
682
+ * A change of at most the threshold of `minEffectOf` in grid steps is a tie. A swap of the two values swaps better and worse.
675
683
  * Two equal values are a tie, also two infinite values. A value `NaN` is an error.
676
684
  */
677
685
  function compareMeasure(candidate, baseline, spec) {
678
686
  if (Number.isNaN(candidate) || Number.isNaN(baseline)) throw new RangeError("compareMeasure: a value is NaN");
679
687
  if (candidate === baseline) return "tie";
680
- const delta = candidate - baseline;
681
- if (!(Math.abs(delta) > minEffectOf(spec, candidate, baseline))) return "tie";
688
+ const a = Math.round(candidate * RECORD_SCALE);
689
+ const b = Math.round(baseline * RECORD_SCALE);
690
+ let delta;
691
+ if (Number.isSafeInteger(a) && Number.isSafeInteger(b)) {
692
+ delta = a - b;
693
+ const m = spec.minEffect ?? 0;
694
+ const t = typeof m === "number" ? m * RECORD_SCALE : m.rel * Math.max(Math.abs(a), Math.abs(b));
695
+ if (Math.abs(delta) <= Math.max(1, Number.isFinite(t) ? t : 0) + STEP_NOISE) return "tie";
696
+ } else {
697
+ delta = candidate - baseline;
698
+ if (!(Math.abs(delta) > minEffectOf(spec, candidate, baseline))) return "tie";
699
+ }
682
700
  return (spec.direction === "lower" ? delta < 0 : delta > 0) ? "better" : "worse";
683
701
  }
684
702
  // src/utf8.ts
@@ -1139,6 +1157,225 @@ function exercised(evidence) {
1139
1157
  if (hashes && baseHashes) return hashes.some((h, i) => h !== baseHashes[i]);
1140
1158
  return false;
1141
1159
  }
1142
- export { DEFAULT_TOLERANCE, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, JOINT_MEASURES, JOINT_MOVED_MIN, RECORD_EPS, SIDES, applyPack, boundsOf, canonicalJson, checkInput, compareMeasure, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, documentHash, edgeStep, exercised, fnv1a32, grow, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, packHard, placedRect, randomInt, rectInRegion, regionHitsRect, sha256Hex, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide };
1160
+ // src/portfolio.ts
1161
+ /**
1162
+ * This function picks the best candidate. For each level, it finds the best value of the candidates that stay in.
1163
+ * It keeps each candidate that is not worse than that best value by `compareMeasure`.
1164
+ * The winner is the first candidate, in the given order, that stays in after the last level.
1165
+ * Without levels, the winner is the first candidate and `kept` is empty.
1166
+ *
1167
+ * The reason for the rule: a scan that compares each candidate with the current winner is not transitive, because of the `minEffect` ties.
1168
+ * Such a scan can drift away from the best value. An example: "a" is lower with the `minEffect` 0.005, and "b" is higher.
1169
+ * The candidates A (0, 1), B (0.004, 2) and C (0.008, 3) give the winner B.
1170
+ * The scan picks C, but A is better than C on "a".
1171
+ *
1172
+ * The function throws a `RangeError` for an empty list of candidates, for a missing value, and for a value that is not finite.
1173
+ */
1174
+ function selectBest(candidates, levels) {
1175
+ if (candidates.length === 0) throw new RangeError("selectBest: the list of candidates is empty.");
1176
+ for (const level of levels) for (const c of candidates) {
1177
+ const v = c.values[level.id];
1178
+ if (typeof v !== "number") throw new RangeError(`selectBest: the candidate "${c.id}" has no value for the level "${level.id}".`);
1179
+ if (!Number.isFinite(v)) throw new RangeError(`selectBest: the candidate "${c.id}" has the value ${v} for the level "${level.id}".`);
1180
+ }
1181
+ let alive = candidates;
1182
+ const kept = [];
1183
+ for (const level of levels) {
1184
+ const at = (c) => c.values[level.id];
1185
+ let best = at(alive[0]);
1186
+ for (const c of alive) best = level.spec.direction === "lower" ? Math.min(best, at(c)) : Math.max(best, at(c));
1187
+ alive = alive.filter((c) => compareMeasure(at(c), best, level.spec) !== "worse");
1188
+ kept.push(alive.map((c) => c.id));
1189
+ }
1190
+ return {
1191
+ winner: alive[0].id,
1192
+ kept
1193
+ };
1194
+ }
1195
+ /** This function gives the move `pick` of a pick: 1 when `picked` is true, else 0. A record keeps it as the trace measure `move.pick`. */
1196
+ function pickMoves(trace) {
1197
+ return { pick: trace.picked ? 1 : 0 };
1198
+ }
1199
+ /** This function gives the default clock: `performance.now`, or `Date.now` without `performance`. */
1200
+ function defaultClock() {
1201
+ const perf = globalThis.performance;
1202
+ return perf ? () => perf.now() : () => Date.now();
1203
+ }
1204
+ /** This function gives the message of an error value. */
1205
+ function messageOf(e) {
1206
+ return e instanceof Error ? e.message : String(e);
1207
+ }
1208
+ /** This function throws a `RangeError` for an empty list of members or for two members with the same id. */
1209
+ function checkMembers(id, members) {
1210
+ if (members.length === 0) throw new RangeError(`The portfolio "${id}" has no members.`);
1211
+ const seen = /* @__PURE__ */ new Set();
1212
+ for (const m of members) {
1213
+ if (seen.has(m.id)) throw new RangeError(`The portfolio "${id}" has two members with the id "${m.id}".`);
1214
+ seen.add(m.id);
1215
+ }
1216
+ }
1217
+ /**
1218
+ * This function makes a portfolio. Its `pick` starts the members in their order. `selectBest` picks the winner of the members with the status `ran`.
1219
+ * The first level is the hard level, then the levels of the key follow.
1220
+ * `members` is a list, or a function of the input that gives the list.
1221
+ *
1222
+ * A member that throws, or that gives a value that is not finite, gets the status `error`. It does not win.
1223
+ * When each member has an error, `pick` throws the error of the first member.
1224
+ * `docs/PORTFOLIO.md` tells the rules of the time limit, the trace and `picked`.
1225
+ *
1226
+ * The function throws a `RangeError` for a level with the id `hard`, for two levels with the same id and for a bad option.
1227
+ * It also throws a `RangeError` for an empty list of members, or for two members with the same id.
1228
+ */
1229
+ function portfolio(id, members, key, options = {}) {
1230
+ const levelIds = /* @__PURE__ */ new Set();
1231
+ for (const level of key.levels) {
1232
+ if (level.id === "hard") throw new RangeError(`The portfolio "${id}" has a level with the reserved id "hard".`);
1233
+ if (levelIds.has(level.id)) throw new RangeError(`The portfolio "${id}" has two levels with the id "${level.id}".`);
1234
+ levelIds.add(level.id);
1235
+ }
1236
+ const { budgetMs, maxMembers } = options;
1237
+ if (budgetMs !== void 0 && !(budgetMs >= 0)) throw new RangeError(`The portfolio "${id}" has the budgetMs ${budgetMs}. It must be 0 or more.`);
1238
+ if (maxMembers !== void 0 && !(Number.isInteger(maxMembers) && maxMembers >= 1)) throw new RangeError(`The portfolio "${id}" has the maxMembers ${maxMembers}. It must be a whole number of 1 or more.`);
1239
+ if (typeof members !== "function") checkMembers(id, members);
1240
+ const levels = [{
1241
+ id: "hard",
1242
+ spec: {
1243
+ direction: "lower",
1244
+ minEffect: 0
1245
+ }
1246
+ }, ...key.levels.map((l) => ({
1247
+ id: l.id,
1248
+ spec: l.spec
1249
+ }))];
1250
+ const now = options.now ?? defaultClock();
1251
+ const evaluate = (m, input, output) => {
1252
+ const values = {};
1253
+ const measure = (level, f) => {
1254
+ let v;
1255
+ try {
1256
+ v = f();
1257
+ } catch (e) {
1258
+ throw new RangeError(`The level "${level}" of the member "${m.id}" of the portfolio "${id}" throws: ${messageOf(e)}`, { cause: e });
1259
+ }
1260
+ if (typeof v !== "number" || !Number.isFinite(v)) throw new RangeError(`The member "${m.id}" of the portfolio "${id}" has the value ${v} for the level "${level}".`);
1261
+ values[level] = v;
1262
+ };
1263
+ try {
1264
+ measure("hard", () => key.hard(input, output));
1265
+ for (const level of key.levels) measure(level.id, () => level.value(input, output));
1266
+ let hash;
1267
+ try {
1268
+ hash = key.hash ? key.hash(output) : outputHash(output);
1269
+ } catch (e) {
1270
+ throw new RangeError(`The hash of the member "${m.id}" of the portfolio "${id}" throws: ${messageOf(e)}`, { cause: e });
1271
+ }
1272
+ return {
1273
+ ok: true,
1274
+ output,
1275
+ values,
1276
+ hash
1277
+ };
1278
+ } catch (e) {
1279
+ return {
1280
+ ok: false,
1281
+ error: e
1282
+ };
1283
+ }
1284
+ };
1285
+ const pick = (input) => {
1286
+ const list = typeof members === "function" ? members(input) : members;
1287
+ if (typeof members === "function") checkMembers(id, list);
1288
+ const start = now();
1289
+ const traces = [];
1290
+ const outputs = /* @__PURE__ */ new Map();
1291
+ const ran = [];
1292
+ let firstError;
1293
+ let cut = 0;
1294
+ let outOfTime = false;
1295
+ for (let i = 0; i < list.length; i++) {
1296
+ const m = list[i];
1297
+ if (maxMembers !== void 0 && i >= maxMembers) {
1298
+ traces.push({
1299
+ id: m.id,
1300
+ status: "cut"
1301
+ });
1302
+ cut++;
1303
+ continue;
1304
+ }
1305
+ if (i > 0 && budgetMs !== void 0 && (outOfTime || now() - start >= budgetMs)) {
1306
+ outOfTime = true;
1307
+ traces.push({
1308
+ id: m.id,
1309
+ status: "cut"
1310
+ });
1311
+ cut++;
1312
+ continue;
1313
+ }
1314
+ const t0 = now();
1315
+ let result;
1316
+ let ms;
1317
+ try {
1318
+ const output = m.run(input);
1319
+ ms = now() - t0;
1320
+ result = evaluate(m, input, output);
1321
+ } catch (e) {
1322
+ ms = now() - t0;
1323
+ result = {
1324
+ ok: false,
1325
+ error: e
1326
+ };
1327
+ }
1328
+ if (result.ok) {
1329
+ traces.push({
1330
+ id: m.id,
1331
+ status: "ran",
1332
+ values: result.values,
1333
+ ms,
1334
+ outputHash: result.hash
1335
+ });
1336
+ outputs.set(m.id, result.output);
1337
+ ran.push({
1338
+ id: m.id,
1339
+ values: result.values
1340
+ });
1341
+ } else {
1342
+ traces.push({
1343
+ id: m.id,
1344
+ status: "error",
1345
+ ms,
1346
+ error: messageOf(result.error)
1347
+ });
1348
+ firstError ??= { error: result.error };
1349
+ }
1350
+ }
1351
+ if (ran.length === 0) throw firstError.error;
1352
+ const { winner, kept } = selectBest(ran, levels);
1353
+ const winnerIndex = traces.findIndex((t) => t.id === winner);
1354
+ const base = traces[0];
1355
+ const trace = {
1356
+ members: traces,
1357
+ winner,
1358
+ winnerIndex,
1359
+ picked: winnerIndex > 0 && (base.status !== "ran" || traces[winnerIndex].outputHash !== base.outputHash),
1360
+ totalMs: now() - start,
1361
+ ...budgetMs !== void 0 ? { budgetMs } : {},
1362
+ ...maxMembers !== void 0 ? { maxMembers } : {},
1363
+ cut,
1364
+ deterministic: !outOfTime,
1365
+ kept
1366
+ };
1367
+ return {
1368
+ output: outputs.get(winner),
1369
+ winner,
1370
+ trace
1371
+ };
1372
+ };
1373
+ return {
1374
+ id,
1375
+ pick,
1376
+ run: (input) => pick(input).output
1377
+ };
1378
+ }
1379
+ export { DEFAULT_TOLERANCE, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, JOINT_MEASURES, JOINT_MOVED_MIN, RECORD_EPS, RECORD_SCALE, SIDES, applyPack, boundsOf, canonicalJson, checkInput, compareMeasure, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, documentHash, edgeStep, exercised, fnv1a32, grow, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, packHard, pickMoves, placedRect, portfolio, randomInt, rectInRegion, regionHitsRect, selectBest, sha256Hex, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide };
1143
1380
 
1144
1381
  //# sourceMappingURL=index.js.map