@mark1russell7/rigor-bench 0.2.19 → 0.2.20

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
@@ -37,6 +37,7 @@ v.comparison; // the cases that gain and dro
37
37
  | `armOrder(arms, round)` | The order of any count of arms in a round. Each pair of arms has each order within 1 after each count of rounds. |
38
38
  | `checkPairedTime(record)` | The record, or a `RangeError` for a time that is not a finite number above 0. |
39
39
  | `caseRatios(records)` | The ratio of each case. It refuses 2 records of one round, and an order that did not turn. |
40
+ | `orderSize(records)` | The count of records per first arm, the order effect, and the amount that unequal counts can move a mean. This size is a guide, because a verdict reads a median. |
40
41
  | `aaBand(aa, options)` | The band of the A/A arm, symmetric on a log scale. The kind `median` (the default) holds the A/A median, and the kind `case` holds one A/A case. It needs 5 cases or more. |
41
42
  | `timeVerdict(candidate, aa, options)` | The outcome of the rule `difference` (the default) or `band`. |
42
43
  | `verdictMargin(options)` | The margin of the rule `difference` on a log scale: the larger of `margin` and the noise of the pooled band. An interval in `[exp(-margin), exp(margin)]` is `same`. |
package/build/fresh.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as PairedTime, t as Arm } from "./paired.js";
1
+ import { i as PairedTime, t as Arm } from "./paired.js";
2
2
  /** The computer of a run. The `cpu` is the model of the first CPU. */
3
3
  export interface BenchEnv {
4
4
  readonly cpu: string;
package/build/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as caseRatios, i as armOrder, n as CaseRatio, o as checkPairedTime, r as PairedTime, s as firstArm, t as Arm } from "./paired.js";
1
+ import { a as armOrder, c as firstArm, i as PairedTime, l as orderSize, n as CaseRatio, o as caseRatios, r as OrderSize, s as checkPairedTime, t as Arm } from "./paired.js";
2
2
  import { BootstrapOptions, Comparison as Comparison$1, Interval, TwoSamplePermutationOptions } from "@mark1russell7/rigor-statistics";
3
3
  /** One value of one build on one case. The value is finite and 0 or more. */
4
4
  interface BudgetRound {
@@ -1357,5 +1357,5 @@ interface Rounds {
1357
1357
  * keeps the work.
1358
1358
  */
1359
1359
  export declare function timeRounds(arms: Readonly<Record<Arm, () => number>>, options: RoundsOptions): Rounds;
1360
- export { type Arm, type ArmModels, type ArmPair, type Balanced, type Band, type BandKind, type BandOptions, type BudgetLook, type BudgetLooksOptions, type BudgetLooksVerdict, type BudgetMore, type BudgetOptions, type BudgetOutcome, type BudgetResult, type BudgetRound, type BudgetVerdict, type CaseBudget, type CaseGate, type CaseInterval, type CaseLimit, type CaseRatio, type CaseTail, type CaseTailLimit, type Comparison, type ControlFactors, type ControlOptions, type ControlRead, type ControlRow, type EntryCount, type Gate, type GateCaseOutcome, type GateJobRead, type GateOutcome, type GroupCounts, type GroupFactor, type GuardRead, type JobLimitVerdict, type JobRecords, type JobVerdict, type JointClaim, type JointControl, type JointMeasure, type JointVerdict, type Ladder, type LadderDimension, type LadderOptions, type LadderVerdict, type LimitOnModel, type LimitOptions, type LimitOutcome, type LimitVerdict, type MeasureCheck, type MeasureCheckOptions, type MeasureChecks, type MeasureFinding, type MeasureFindingKind, type MeasureRow, type MeasureRule, type MeasuredFactor, type MedianBudget, type ModelLimits, type ModelRead, type ModelRow, type ModelStatus, type ModelTest, type ModelTestOptions, type NoControl, type NoFactor, type NoiseCheck, type NotSlowerOptions, type NotSlowerRead, type NotSlowerVerdict, type PairedTime, type PartnerRead, type PoolFacts, type PoolOptions, type PooledJob, type PooledLimitVerdict, type PooledRecords, type PooledVerdict, type Power, type PowerOptions, type QuantileBudget, type RateBudget, type Rounds, type RoundsOptions, type RunPairs, type Rung, type RungBudget, type RungEvidence, type RungRecord, type RungVerdict, type SpreadRead, type TailFalseFails, type TailGainCase, type TailLimit, type TailLimitOptions, type TailLimitVerdict, type TailRuleOptions, type TimeOutcome, type TimeVerdict, type VerdictOptions, type VerdictRule, type VisibleShift, type WinClaim, type WorstBudget, armOrder, caseRatios, checkPairedTime, firstArm };
1360
+ export { type Arm, type ArmModels, type ArmPair, type Balanced, type Band, type BandKind, type BandOptions, type BudgetLook, type BudgetLooksOptions, type BudgetLooksVerdict, type BudgetMore, type BudgetOptions, type BudgetOutcome, type BudgetResult, type BudgetRound, type BudgetVerdict, type CaseBudget, type CaseGate, type CaseInterval, type CaseLimit, type CaseRatio, type CaseTail, type CaseTailLimit, type Comparison, type ControlFactors, type ControlOptions, type ControlRead, type ControlRow, type EntryCount, type Gate, type GateCaseOutcome, type GateJobRead, type GateOutcome, type GroupCounts, type GroupFactor, type GuardRead, type JobLimitVerdict, type JobRecords, type JobVerdict, type JointClaim, type JointControl, type JointMeasure, type JointVerdict, type Ladder, type LadderDimension, type LadderOptions, type LadderVerdict, type LimitOnModel, type LimitOptions, type LimitOutcome, type LimitVerdict, type MeasureCheck, type MeasureCheckOptions, type MeasureChecks, type MeasureFinding, type MeasureFindingKind, type MeasureRow, type MeasureRule, type MeasuredFactor, type MedianBudget, type ModelLimits, type ModelRead, type ModelRow, type ModelStatus, type ModelTest, type ModelTestOptions, type NoControl, type NoFactor, type NoiseCheck, type NotSlowerOptions, type NotSlowerRead, type NotSlowerVerdict, type OrderSize, type PairedTime, type PartnerRead, type PoolFacts, type PoolOptions, type PooledJob, type PooledLimitVerdict, type PooledRecords, type PooledVerdict, type Power, type PowerOptions, type QuantileBudget, type RateBudget, type Rounds, type RoundsOptions, type RunPairs, type Rung, type RungBudget, type RungEvidence, type RungRecord, type RungVerdict, type SpreadRead, type TailFalseFails, type TailGainCase, type TailLimit, type TailLimitOptions, type TailLimitVerdict, type TailRuleOptions, type TimeOutcome, type TimeVerdict, type VerdictOptions, type VerdictRule, type VisibleShift, type WinClaim, type WorstBudget, armOrder, caseRatios, checkPairedTime, firstArm, orderSize };
1361
1361
  //# sourceMappingURL=index.d.ts.map
package/build/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as firstArm, n as caseRatios, r as checkPairedTime, t as armOrder } from "./paired.js";
1
+ import { a as orderSize, i as firstArm, n as caseRatios, r as checkPairedTime, t as armOrder } from "./paired.js";
2
2
  import { bootstrapMedian, bootstrapTwo, compareRatios, holm, median, medianInterval, partialConjunction, quantile, quantileInterval, quantileSamples, signTest, signTestPower, twoSamplePermutation, wilson } from "@mark1russell7/rigor-statistics";
3
3
  import { random } from "@mark1russell7/rigor-generators";
4
4
  // src/budget.ts
@@ -2337,6 +2337,6 @@ function timeRounds(arms, options) {
2337
2337
  sink
2338
2338
  };
2339
2339
  }
2340
- export { BUDGET_LOOKS, CEILING_ONLY, GATE_LOOKS, LADDER_FORMAT, UNKNOWN_MODEL, aaBand, aaSpread, armModels, armOrder, armVerdicts, balanceOrders, budgetLookSpend, budgetLooksVerdict, budgetVerdict, caseIntervals, caseRatios, caseTails, checkLadder, checkMeasures, checkPairedTime, controlFactor, firstArm, gateOf, jointClaim, ladderVerdict, limitOnModel, limitVerdict, modelRowText, modelTest, notSlowerVerdict, poolRecords, pooledBand, pooledLimitVerdict, pooledVerdict, rungVerdict, tailFalseFails, tailLimitVerdict, timeRounds, timeVerdict, verdictMargin, verdictPower, visibleShift, winClaim };
2340
+ export { BUDGET_LOOKS, CEILING_ONLY, GATE_LOOKS, LADDER_FORMAT, UNKNOWN_MODEL, aaBand, aaSpread, armModels, armOrder, armVerdicts, balanceOrders, budgetLookSpend, budgetLooksVerdict, budgetVerdict, caseIntervals, caseRatios, caseTails, checkLadder, checkMeasures, checkPairedTime, controlFactor, firstArm, gateOf, jointClaim, ladderVerdict, limitOnModel, limitVerdict, modelRowText, modelTest, notSlowerVerdict, orderSize, poolRecords, pooledBand, pooledLimitVerdict, pooledVerdict, rungVerdict, tailFalseFails, tailLimitVerdict, timeRounds, timeVerdict, verdictMargin, verdictPower, visibleShift, winClaim };
2341
2341
 
2342
2342
  //# sourceMappingURL=index.js.map
package/build/paired.d.ts CHANGED
@@ -42,5 +42,26 @@ interface CaseRatio {
42
42
  * It also refuses a case whose two orders differ in count by more than 1, because that order did not turn each round.
43
43
  */
44
44
  declare function caseRatios(records: readonly PairedTime[]): CaseRatio[];
45
- export { caseRatios as a, armOrder as i, CaseRatio as n, checkPairedTime as o, PairedTime as r, firstArm as s, Arm as t };
45
+ /** The size of the order effect in paired records. It is a guide: this size holds for a mean, and a verdict reads a median. */
46
+ interface OrderSize {
47
+ /** The count of records with arm `a` first. */
48
+ readonly aFirst: number;
49
+ /** The count of records with arm `b` first. */
50
+ readonly bFirst: number;
51
+ /** The count of cases with records in both orders. The effect reads only these cases. */
52
+ readonly cases: number;
53
+ /** The median over those cases of the mean `log(b / a)` with `b` first, less the mean with `a` first. With no such case, it is `null`. */
54
+ readonly effect: number | null;
55
+ /** The largest `|n1 - n2| / (n1 + n2)` of a case, where `n1` and `n2` are its counts in each order. */
56
+ readonly split: number;
57
+ /** The value `split * |effect| / 2`: the amount that the unequal counts can move the mean of a case on a log scale. With no effect, it is `null`. */
58
+ readonly size: number | null;
59
+ }
60
+ /**
61
+ * This function gives the order effect of paired records, and the amount that unequal counts of the two orders can move a ratio.
62
+ * With `n1` and `n2` rounds in each order, the mean of `log(b / a)` moves by `(n1 - n2) / (n1 + n2) * effect / 2`.
63
+ * It examines each record, and it refuses no record for its order.
64
+ */
65
+ declare function orderSize(records: readonly PairedTime[]): OrderSize;
66
+ export { armOrder as a, firstArm as c, PairedTime as i, orderSize as l, CaseRatio as n, caseRatios as o, OrderSize as r, checkPairedTime as s, Arm as t };
46
67
  //# sourceMappingURL=paired.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paired.d.ts","names":[],"sources":["../src/paired.ts"],"mappings":";KAOY;;UAGK;;WAEN;;WAEA;WACA,OAAO;;WAEP;;WAEA;;;;;;iBAOK,SAAS,eAAe,kBAAa;;;;;;;;;;iBAerC,SAAS,GAAG,eAAe,KAAK,yBAAyB;;iBASzD,gBAAgB,QAAQ,aAAa;;UAapC;WACN;;WAEA;WACA;;;;;;;iBAQK,WAAW,kBAAkB,eAAe"}
1
+ {"version":3,"file":"paired.d.ts","names":[],"sources":["../src/paired.ts"],"mappings":";KAOY;;UAGK;;WAEN;;WAEA;WACA,OAAO;;WAEP;;WAEA;;;;;;iBAOK,SAAS,eAAe,kBAAa;;;;;;;;;;iBAerC,SAAS,GAAG,eAAe,KAAK,yBAAyB;;iBASzD,gBAAgB,QAAQ,aAAa;;UAapC;WACN;;WAEA;WACA;;;;;;;iBAQK,WAAW,kBAAkB,eAAe;;UAoB3C;;WAEN;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;iBAQK,UAAU,kBAAkB,eAAe"}
package/build/paired.js CHANGED
@@ -65,6 +65,39 @@ function caseRatios(records) {
65
65
  };
66
66
  });
67
67
  }
68
- export { firstArm as i, caseRatios as n, checkPairedTime as r, armOrder as t };
68
+ /**
69
+ * This function gives the order effect of paired records, and the amount that unequal counts of the two orders can move a ratio.
70
+ * With `n1` and `n2` rounds in each order, the mean of `log(b / a)` moves by `(n1 - n2) / (n1 + n2) * effect / 2`.
71
+ * It examines each record, and it refuses no record for its order.
72
+ */
73
+ function orderSize(records) {
74
+ const cases = /* @__PURE__ */ new Map();
75
+ for (const record of records) {
76
+ checkPairedTime(record);
77
+ const list = cases.get(record.case) ?? [];
78
+ list.push(record);
79
+ cases.set(record.case, list);
80
+ }
81
+ const mean = (list) => list.reduce((sum, r) => sum + Math.log(r.b / r.a), 0) / list.length;
82
+ const effects = [];
83
+ let split = 0;
84
+ for (const list of cases.values()) {
85
+ const bFirst = list.filter((r) => r.first === "b");
86
+ const aFirst = list.filter((r) => r.first === "a");
87
+ split = Math.max(split, Math.abs(bFirst.length - aFirst.length) / list.length);
88
+ if (bFirst.length > 0 && aFirst.length > 0) effects.push(mean(bFirst) - mean(aFirst));
89
+ }
90
+ const effect = effects.length === 0 ? null : median(effects);
91
+ const bFirst = records.filter((r) => r.first === "b").length;
92
+ return {
93
+ aFirst: records.length - bFirst,
94
+ bFirst,
95
+ cases: effects.length,
96
+ effect,
97
+ split,
98
+ size: effect === null ? null : split * Math.abs(effect) / 2
99
+ };
100
+ }
101
+ export { orderSize as a, firstArm as i, caseRatios as n, checkPairedTime as r, armOrder as t };
69
102
 
70
103
  //# sourceMappingURL=paired.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paired.js","names":[],"sources":["../src/paired.ts"],"sourcesContent":["/**\n * The paired-time record of a bench. Each round runs the two arms of a pair on one case, one after the other, in a\n * fresh process. The order turns each round, so a warm cache or a slow start does not always help the same arm.\n */\nimport { median } from \"@mark1russell7/rigor-statistics\";\n\n/** The two arms of a pair. Arm `a` is the baseline, and arm `b` is the candidate. */\nexport type Arm = \"a\" | \"b\";\n\n/** One round of one pair on one case: the times of the two arms in milliseconds, and the arm that ran first. */\nexport interface PairedTime {\n /** The name of the case: one input of the bench. */\n readonly case: string;\n /** The number of the round, a whole number of 0 or more. */\n readonly round: number;\n readonly first: Arm;\n /** The time of the baseline arm, in milliseconds. */\n readonly a: number;\n /** The time of the candidate arm, in milliseconds. */\n readonly b: number;\n}\n\n/**\n * This function gives the arm that runs first in a round. The order turns each round. A case with an odd `offset`\n * starts with arm `b`, so half of the cases start with each arm.\n */\nexport function firstArm(round: number, offset = 0): Arm {\n if (!Number.isSafeInteger(round) || round < 0) throw new RangeError(`firstArm: the round is a whole number of 0 or more, not ${round}.`);\n if (!Number.isSafeInteger(offset)) throw new RangeError(`firstArm: the offset is a whole number, not ${offset}.`);\n return (((round + offset) % 2) + 2) % 2 === 0 ? \"a\" : \"b\";\n}\n\n/**\n * This function gives the order of three or more arms in one round. Each odd round is the reverse of the round before it,\n * and each even round turns the arms by one more place. So each pair of arms has each order one time in each two rounds,\n * at each count of arms. After any count of rounds, the two orders of a pair differ by 1 or less, and `caseRatios` accepts\n * the records.\n *\n * A plain turn of the arms does not give this. With 3 arms, each arm comes before the next arm in 2 of 3 rounds. Over 7\n * rounds, a pair can have one order 5 times, and `caseRatios` refuses those records.\n */\nexport function armOrder<T>(arms: readonly T[], round: number): readonly T[] {\n if (arms.length === 0) throw new RangeError(\"armOrder: there is no arm.\");\n if (!Number.isSafeInteger(round) || round < 0) throw new RangeError(`armOrder: the round is a whole number of 0 or more, not ${round}.`);\n const shift = Math.floor(round / 2) % arms.length;\n const turned = [...arms.slice(shift), ...arms.slice(0, shift)];\n return round % 2 === 0 ? turned : turned.toReversed();\n}\n\n/** This function examines one record. A time that is not a finite number above 0 is an error. */\nexport function checkPairedTime(record: PairedTime): PairedTime {\n const where = `the record of the case ${JSON.stringify(record.case)}, round ${record.round}`;\n if (typeof record.case !== \"string\" || record.case === \"\") throw new RangeError(\"checkPairedTime: the case is a string that is not empty.\");\n if (!Number.isSafeInteger(record.round) || record.round < 0) throw new RangeError(`checkPairedTime: in ${where}, the round is a whole number of 0 or more.`);\n if (record.first !== \"a\" && record.first !== \"b\") throw new RangeError(`checkPairedTime: in ${where}, the first arm is \"a\" or \"b\", not ${String(record.first)}.`);\n for (const arm of [\"a\", \"b\"] as const) {\n const ms = record[arm];\n if (!(typeof ms === \"number\" && Number.isFinite(ms) && ms > 0)) throw new RangeError(`checkPairedTime: in ${where}, the time of arm ${arm} is a finite number above 0, not ${String(ms)}.`);\n }\n return record;\n}\n\n/** The ratio of one case: the candidate time over the baseline time, over the rounds of the case. */\nexport interface CaseRatio {\n readonly case: string;\n /** The value `exp(median(log(b / a)))` over the rounds. For an odd count of rounds, it is the median of the ratios. */\n readonly ratio: number;\n readonly rounds: number;\n}\n\n/**\n * This function gives the ratio of each case, in the order of the first record of each case.\n * It examines each record. It refuses a case with 2 records of one round.\n * It also refuses a case whose two orders differ in count by more than 1, because that order did not turn each round.\n */\nexport function caseRatios(records: readonly PairedTime[]): CaseRatio[] {\n const cases = new Map<string, PairedTime[]>();\n for (const record of records) {\n checkPairedTime(record);\n const list = cases.get(record.case) ?? [];\n list.push(record);\n cases.set(record.case, list);\n }\n return [...cases].map(([name, list]) => {\n const rounds = new Set(list.map((r) => r.round));\n if (rounds.size !== list.length) throw new RangeError(`caseRatios: the case ${JSON.stringify(name)} has 2 records of one round.`);\n const aFirst = list.filter((r) => r.first === \"a\").length;\n if (Math.abs(2 * aFirst - list.length) > 1) {\n throw new RangeError(`caseRatios: the case ${JSON.stringify(name)} starts with arm a in ${aFirst} of ${list.length} rounds. The order turns each round.`);\n }\n return { case: name, ratio: Math.exp(median(list.map((r) => Math.log(r.b / r.a)))), rounds: list.length };\n });\n}\n"],"mappings":";;;;;;;;;;AA0BA,SAAgB,SAAS,OAAe,SAAS,GAAQ;CACvD,IAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,GAAG,MAAM,IAAI,WAAW,2DAA2D,MAAM,EAAE;CACvI,IAAI,CAAC,OAAO,cAAc,MAAM,GAAG,MAAM,IAAI,WAAW,+CAA+C,OAAO,EAAE;CAChH,SAAU,QAAQ,UAAU,IAAK,KAAK,MAAM,IAAI,MAAM;AACxD;;;;;;;;;;AAWA,SAAgB,SAAY,MAAoB,OAA6B;CAC3E,IAAI,KAAK,WAAW,GAAG,MAAM,IAAI,WAAW,4BAA4B;CACxE,IAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,GAAG,MAAM,IAAI,WAAW,2DAA2D,MAAM,EAAE;CACvI,MAAM,QAAQ,KAAK,MAAM,QAAQ,CAAC,IAAI,KAAK;CAC3C,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,KAAK,GAAG,GAAG,KAAK,MAAM,GAAG,KAAK,CAAC;CAC7D,OAAO,QAAQ,MAAM,IAAI,SAAS,OAAO,WAAW;AACtD;;AAGA,SAAgB,gBAAgB,QAAgC;CAC9D,MAAM,QAAQ,0BAA0B,KAAK,UAAU,OAAO,IAAI,EAAE,UAAU,OAAO;CACrF,IAAI,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS,IAAI,MAAM,IAAI,WAAW,0DAA0D;CAC1I,IAAI,CAAC,OAAO,cAAc,OAAO,KAAK,KAAK,OAAO,QAAQ,GAAG,MAAM,IAAI,WAAW,uBAAuB,MAAM,4CAA4C;CAC3J,IAAI,OAAO,UAAU,OAAO,OAAO,UAAU,KAAK,MAAM,IAAI,WAAW,uBAAuB,MAAM,qCAAqC,OAAO,OAAO,KAAK,EAAE,EAAE;CAChK,KAAK,MAAM,OAAO,CAAC,KAAK,GAAG,GAAY;EACrC,MAAM,KAAK,OAAO;EAClB,IAAI,EAAE,OAAO,OAAO,YAAY,OAAO,SAAS,EAAE,KAAK,KAAK,IAAI,MAAM,IAAI,WAAW,uBAAuB,MAAM,oBAAoB,IAAI,mCAAmC,OAAO,EAAE,EAAE,EAAE;CAC5L;CACA,OAAO;AACT;;;;;;AAeA,SAAgB,WAAW,SAA6C;CACtE,MAAM,wBAAQ,IAAI,IAA0B;CAC5C,KAAK,MAAM,UAAU,SAAS;EAC5B,gBAAgB,MAAM;EACtB,MAAM,OAAO,MAAM,IAAI,OAAO,IAAI,KAAK,CAAC;EACxC,KAAK,KAAK,MAAM;EAChB,MAAM,IAAI,OAAO,MAAM,IAAI;CAC7B;CACA,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,UAAU;EAEtC,IAAI,IADe,IAAI,KAAK,KAAK,MAAM,EAAE,KAAK,CACrC,CAAC,CAAC,SAAS,KAAK,QAAQ,MAAM,IAAI,WAAW,wBAAwB,KAAK,UAAU,IAAI,EAAE,6BAA6B;EAChI,MAAM,SAAS,KAAK,QAAQ,MAAM,EAAE,UAAU,GAAG,CAAC,CAAC;EACnD,IAAI,KAAK,IAAI,IAAI,SAAS,KAAK,MAAM,IAAI,GACvC,MAAM,IAAI,WAAW,wBAAwB,KAAK,UAAU,IAAI,EAAE,wBAAwB,OAAO,MAAM,KAAK,OAAO,qCAAqC;EAE1J,OAAO;GAAE,MAAM;GAAM,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,MAAM,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;GAAG,QAAQ,KAAK;EAAO;CAC1G,CAAC;AACH"}
1
+ {"version":3,"file":"paired.js","names":[],"sources":["../src/paired.ts"],"sourcesContent":["/**\n * The paired-time record of a bench. Each round runs the two arms of a pair on one case, one after the other, in a\n * fresh process. The order turns each round, so a warm cache or a slow start does not always help the same arm.\n */\nimport { median } from \"@mark1russell7/rigor-statistics\";\n\n/** The two arms of a pair. Arm `a` is the baseline, and arm `b` is the candidate. */\nexport type Arm = \"a\" | \"b\";\n\n/** One round of one pair on one case: the times of the two arms in milliseconds, and the arm that ran first. */\nexport interface PairedTime {\n /** The name of the case: one input of the bench. */\n readonly case: string;\n /** The number of the round, a whole number of 0 or more. */\n readonly round: number;\n readonly first: Arm;\n /** The time of the baseline arm, in milliseconds. */\n readonly a: number;\n /** The time of the candidate arm, in milliseconds. */\n readonly b: number;\n}\n\n/**\n * This function gives the arm that runs first in a round. The order turns each round. A case with an odd `offset`\n * starts with arm `b`, so half of the cases start with each arm.\n */\nexport function firstArm(round: number, offset = 0): Arm {\n if (!Number.isSafeInteger(round) || round < 0) throw new RangeError(`firstArm: the round is a whole number of 0 or more, not ${round}.`);\n if (!Number.isSafeInteger(offset)) throw new RangeError(`firstArm: the offset is a whole number, not ${offset}.`);\n return (((round + offset) % 2) + 2) % 2 === 0 ? \"a\" : \"b\";\n}\n\n/**\n * This function gives the order of three or more arms in one round. Each odd round is the reverse of the round before it,\n * and each even round turns the arms by one more place. So each pair of arms has each order one time in each two rounds,\n * at each count of arms. After any count of rounds, the two orders of a pair differ by 1 or less, and `caseRatios` accepts\n * the records.\n *\n * A plain turn of the arms does not give this. With 3 arms, each arm comes before the next arm in 2 of 3 rounds. Over 7\n * rounds, a pair can have one order 5 times, and `caseRatios` refuses those records.\n */\nexport function armOrder<T>(arms: readonly T[], round: number): readonly T[] {\n if (arms.length === 0) throw new RangeError(\"armOrder: there is no arm.\");\n if (!Number.isSafeInteger(round) || round < 0) throw new RangeError(`armOrder: the round is a whole number of 0 or more, not ${round}.`);\n const shift = Math.floor(round / 2) % arms.length;\n const turned = [...arms.slice(shift), ...arms.slice(0, shift)];\n return round % 2 === 0 ? turned : turned.toReversed();\n}\n\n/** This function examines one record. A time that is not a finite number above 0 is an error. */\nexport function checkPairedTime(record: PairedTime): PairedTime {\n const where = `the record of the case ${JSON.stringify(record.case)}, round ${record.round}`;\n if (typeof record.case !== \"string\" || record.case === \"\") throw new RangeError(\"checkPairedTime: the case is a string that is not empty.\");\n if (!Number.isSafeInteger(record.round) || record.round < 0) throw new RangeError(`checkPairedTime: in ${where}, the round is a whole number of 0 or more.`);\n if (record.first !== \"a\" && record.first !== \"b\") throw new RangeError(`checkPairedTime: in ${where}, the first arm is \"a\" or \"b\", not ${String(record.first)}.`);\n for (const arm of [\"a\", \"b\"] as const) {\n const ms = record[arm];\n if (!(typeof ms === \"number\" && Number.isFinite(ms) && ms > 0)) throw new RangeError(`checkPairedTime: in ${where}, the time of arm ${arm} is a finite number above 0, not ${String(ms)}.`);\n }\n return record;\n}\n\n/** The ratio of one case: the candidate time over the baseline time, over the rounds of the case. */\nexport interface CaseRatio {\n readonly case: string;\n /** The value `exp(median(log(b / a)))` over the rounds. For an odd count of rounds, it is the median of the ratios. */\n readonly ratio: number;\n readonly rounds: number;\n}\n\n/**\n * This function gives the ratio of each case, in the order of the first record of each case.\n * It examines each record. It refuses a case with 2 records of one round.\n * It also refuses a case whose two orders differ in count by more than 1, because that order did not turn each round.\n */\nexport function caseRatios(records: readonly PairedTime[]): CaseRatio[] {\n const cases = new Map<string, PairedTime[]>();\n for (const record of records) {\n checkPairedTime(record);\n const list = cases.get(record.case) ?? [];\n list.push(record);\n cases.set(record.case, list);\n }\n return [...cases].map(([name, list]) => {\n const rounds = new Set(list.map((r) => r.round));\n if (rounds.size !== list.length) throw new RangeError(`caseRatios: the case ${JSON.stringify(name)} has 2 records of one round.`);\n const aFirst = list.filter((r) => r.first === \"a\").length;\n if (Math.abs(2 * aFirst - list.length) > 1) {\n throw new RangeError(`caseRatios: the case ${JSON.stringify(name)} starts with arm a in ${aFirst} of ${list.length} rounds. The order turns each round.`);\n }\n return { case: name, ratio: Math.exp(median(list.map((r) => Math.log(r.b / r.a)))), rounds: list.length };\n });\n}\n\n/** The size of the order effect in paired records. It is a guide: this size holds for a mean, and a verdict reads a median. */\nexport interface OrderSize {\n /** The count of records with arm `a` first. */\n readonly aFirst: number;\n /** The count of records with arm `b` first. */\n readonly bFirst: number;\n /** The count of cases with records in both orders. The effect reads only these cases. */\n readonly cases: number;\n /** The median over those cases of the mean `log(b / a)` with `b` first, less the mean with `a` first. With no such case, it is `null`. */\n readonly effect: number | null;\n /** The largest `|n1 - n2| / (n1 + n2)` of a case, where `n1` and `n2` are its counts in each order. */\n readonly split: number;\n /** The value `split * |effect| / 2`: the amount that the unequal counts can move the mean of a case on a log scale. With no effect, it is `null`. */\n readonly size: number | null;\n}\n\n/**\n * This function gives the order effect of paired records, and the amount that unequal counts of the two orders can move a ratio.\n * With `n1` and `n2` rounds in each order, the mean of `log(b / a)` moves by `(n1 - n2) / (n1 + n2) * effect / 2`.\n * It examines each record, and it refuses no record for its order.\n */\nexport function orderSize(records: readonly PairedTime[]): OrderSize {\n const cases = new Map<string, PairedTime[]>();\n for (const record of records) {\n checkPairedTime(record);\n const list = cases.get(record.case) ?? [];\n list.push(record);\n cases.set(record.case, list);\n }\n const mean = (list: readonly PairedTime[]): number => list.reduce((sum, r) => sum + Math.log(r.b / r.a), 0) / list.length;\n const effects: number[] = [];\n let split = 0;\n for (const list of cases.values()) {\n const bFirst = list.filter((r) => r.first === \"b\");\n const aFirst = list.filter((r) => r.first === \"a\");\n split = Math.max(split, Math.abs(bFirst.length - aFirst.length) / list.length);\n if (bFirst.length > 0 && aFirst.length > 0) effects.push(mean(bFirst) - mean(aFirst));\n }\n const effect = effects.length === 0 ? null : median(effects);\n const bFirst = records.filter((r) => r.first === \"b\").length;\n return { aFirst: records.length - bFirst, bFirst, cases: effects.length, effect, split, size: effect === null ? null : (split * Math.abs(effect)) / 2 };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/rigor-bench",
4
- "version": "0.2.19",
4
+ "version": "0.2.20",
5
5
  "description": "The paired-time records of a bench: the order that turns each round, the ratio of each case, and the time verdict against the band of an A/A arm.",
6
6
  "keywords": [
7
7
  "benchmark",