@lapxo/obligations 0.3.5 → 0.4.1

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.
Files changed (71) hide show
  1. package/README.md +21 -126
  2. package/TARGET.bound +622 -507
  3. package/dist/measures/capacity.d.ts +302 -0
  4. package/dist/measures/capacity.js +168 -0
  5. package/dist/measures/coincidence.d.ts +111 -0
  6. package/dist/measures/coincidence.js +81 -0
  7. package/dist/measures/error-events.d.ts +27 -0
  8. package/dist/measures/error-events.js +62 -0
  9. package/dist/measures/experiments.d.ts +15 -0
  10. package/dist/measures/experiments.js +53 -0
  11. package/dist/measures/fitting.d.ts +243 -0
  12. package/dist/measures/fitting.js +115 -0
  13. package/dist/measures/identification.d.ts +156 -0
  14. package/dist/measures/identification.js +107 -0
  15. package/dist/measures/index.d.ts +24 -0
  16. package/dist/measures/index.js +12 -0
  17. package/dist/measures/m3-experiment.d.ts +69 -0
  18. package/dist/measures/m3-experiment.js +70 -0
  19. package/dist/measures/m3.d.ts +139 -0
  20. package/dist/measures/m3.js +181 -0
  21. package/dist/measures/model.d.ts +14 -0
  22. package/dist/measures/model.js +67 -0
  23. package/dist/measures/rational.d.ts +19 -0
  24. package/dist/measures/rational.js +53 -0
  25. package/dist/measures/spectrum.d.ts +22 -0
  26. package/dist/measures/spectrum.js +84 -0
  27. package/dist/measures/unanimity-fit.d.ts +156 -0
  28. package/dist/measures/unanimity-fit.js +133 -0
  29. package/docs/img/cell.svg +28 -0
  30. package/docs/img/logo-fork.svg +6 -0
  31. package/docs/img/logo.svg +6 -0
  32. package/docs/img/price.svg +28 -0
  33. package/docs/img/thermometer.svg +28 -0
  34. package/docs/img/withdrawal.svg +28 -0
  35. package/docs/learn/in-phase.md +46 -0
  36. package/docs/learn/price.md +52 -0
  37. package/docs/learn/signature.md +50 -0
  38. package/docs/learn/thermometer.md +58 -0
  39. package/docs/learn/withdrawal.md +51 -0
  40. package/docs/measures.md +197 -0
  41. package/docs/paper.md +233 -0
  42. package/docs/reference.md +495 -0
  43. package/package.json +1 -93
  44. package/receipts.bound +118 -0
  45. package/samples/measures/capacity.json +71 -0
  46. package/samples/measures/coincidence.json +624 -0
  47. package/samples/measures/decisions.json +66 -0
  48. package/samples/measures/descent.json +92 -0
  49. package/samples/measures/m3.json +3126 -0
  50. package/samples/measures/observations.json +212 -0
  51. package/samples/measures/spectrum.json +6530 -0
  52. package/samples/measures/unanimity-fit.json +166 -0
  53. package/samples/measures/unanimity.json +76 -0
  54. package/samples/no/installed-artifact.json +314 -0
  55. package/samples/yes/algebra-is-closed.json +17 -0
  56. package/samples/yes/field-uses-price-and-debit.json +9 -0
  57. package/samples/yes/installed-artifact.json +424 -0
  58. package/samples/yes/three-theories-one-algebra.json +5 -0
  59. package/src/field/motion.ts +59 -0
  60. package/src/lattice/index.ts +79 -0
  61. package/vectors/measures/capacity.cli.json +97 -0
  62. package/vectors/measures/coincidence.cli.json +97 -0
  63. package/vectors/measures/decisions.cli.json +97 -0
  64. package/vectors/measures/descent.cli.json +97 -0
  65. package/vectors/measures/implementation.json +82 -0
  66. package/vectors/measures/installed-artifact.cli.json +63 -0
  67. package/vectors/measures/m3.cli.json +97 -0
  68. package/vectors/measures/observations.cli.json +97 -0
  69. package/vectors/measures/spectrum.cli.json +97 -0
  70. package/vectors/measures/unanimity-fit.cli.json +97 -0
  71. package/vectors/measures/unanimity.cli.json +97 -0
@@ -0,0 +1,302 @@
1
+ import { type RationalInput } from './rational.ts';
2
+ export type CapacityModel = Readonly<{
3
+ n: number;
4
+ c: RationalInput;
5
+ p: RationalInput;
6
+ }>;
7
+ export type LogarithmPolicy = Readonly<{
8
+ logarithm: 'binary64-log2';
9
+ }>;
10
+ export type CapacityPolicy = LogarithmPolicy & Readonly<{
11
+ budget: string;
12
+ }>;
13
+ export type DecisionRule = 'plurality-uniform-ties@1' | 'posterior-map-uniform-ties@1';
14
+ export declare function capacityBound(n: number, c: RationalInput, policy: LogarithmPolicy): {
15
+ model: "single-dominant-factor@1";
16
+ parameters: {
17
+ n: number;
18
+ c: Readonly<{
19
+ numerator: string;
20
+ denominator: string;
21
+ }>;
22
+ };
23
+ policy: Readonly<{
24
+ logarithm: "binary64-log2";
25
+ }>;
26
+ status: "measured";
27
+ unit: "bits";
28
+ value: number;
29
+ meaning: "upper bound for every k; achievability depends on the private channel";
30
+ };
31
+ export declare function capacityP0(n: number, c: RationalInput, policy: LogarithmPolicy): {
32
+ value: number;
33
+ meaning: "mutual information at p=0 for every positive k";
34
+ model: "single-dominant-factor@1";
35
+ parameters: {
36
+ n: number;
37
+ c: Readonly<{
38
+ numerator: string;
39
+ denominator: string;
40
+ }>;
41
+ };
42
+ policy: Readonly<{
43
+ logarithm: "binary64-log2";
44
+ }>;
45
+ status: "measured";
46
+ unit: "bits";
47
+ };
48
+ export declare function unanimityNoFactor(k: number, n: number, p: RationalInput, policy: Readonly<{
49
+ budget: string;
50
+ }>): {
51
+ status: "unmeasured";
52
+ cause: string;
53
+ required: string;
54
+ workUnit: "rational-bit-bound";
55
+ model?: undefined;
56
+ parameters?: undefined;
57
+ policy?: undefined;
58
+ value?: undefined;
59
+ } | {
60
+ status: "measured";
61
+ model: "single-dominant-factor@1";
62
+ parameters: {
63
+ k: number;
64
+ n: number;
65
+ p: Readonly<{
66
+ numerator: string;
67
+ denominator: string;
68
+ }>;
69
+ };
70
+ policy: Readonly<{
71
+ budget: string;
72
+ }>;
73
+ workUnit: "rational-bit-bound";
74
+ value: Readonly<{
75
+ numerator: string;
76
+ denominator: string;
77
+ }>;
78
+ cause?: undefined;
79
+ required?: undefined;
80
+ };
81
+ export declare function mutualInformation(k: number, model: CapacityModel, policy: CapacityPolicy): {
82
+ status: "measured";
83
+ unit: "bits";
84
+ value: number;
85
+ probabilitySum: Readonly<{
86
+ numerator: string;
87
+ denominator: string;
88
+ }>;
89
+ work: string;
90
+ loss: {
91
+ exclusion: number;
92
+ privateUncertainty: number;
93
+ factorAmbiguity: number;
94
+ };
95
+ exactConditionalEntropy: {
96
+ mass: Readonly<{
97
+ numerator: string;
98
+ denominator: string;
99
+ }>;
100
+ posterior: Readonly<{
101
+ numerator: string;
102
+ denominator: string;
103
+ }>;
104
+ }[];
105
+ model: "single-dominant-factor@1";
106
+ parameters: {
107
+ k: number;
108
+ n: number;
109
+ c: Readonly<{
110
+ numerator: string;
111
+ denominator: string;
112
+ }>;
113
+ p: Readonly<{
114
+ numerator: string;
115
+ denominator: string;
116
+ }>;
117
+ };
118
+ policy: CapacityPolicy;
119
+ conditions: readonly ["uniform-truth", "factor-independent-of-truth", "uniform-shared-wrong-value", "independent-homogeneous-private-channel"];
120
+ } | {
121
+ status: "unmeasured";
122
+ cause: string;
123
+ requiredAtLeast: string;
124
+ model: "single-dominant-factor@1";
125
+ parameters: {
126
+ k: number;
127
+ n: number;
128
+ c: Readonly<{
129
+ numerator: string;
130
+ denominator: string;
131
+ }>;
132
+ p: Readonly<{
133
+ numerator: string;
134
+ denominator: string;
135
+ }>;
136
+ };
137
+ policy: CapacityPolicy;
138
+ conditions: readonly ["uniform-truth", "factor-independent-of-truth", "uniform-shared-wrong-value", "independent-homogeneous-private-channel"];
139
+ } | {
140
+ decision: {
141
+ rule: DecisionRule | undefined;
142
+ accuracy: Readonly<{
143
+ numerator: string;
144
+ denominator: string;
145
+ }>;
146
+ information: number;
147
+ joint: Readonly<{
148
+ numerator: string;
149
+ denominator: string;
150
+ }>[][];
151
+ tiePolicy: "uniform-over-maximizers";
152
+ };
153
+ status: "measured";
154
+ unit: "bits";
155
+ value: number;
156
+ probabilitySum: Readonly<{
157
+ numerator: string;
158
+ denominator: string;
159
+ }>;
160
+ work: string;
161
+ loss: {
162
+ exclusion: number;
163
+ privateUncertainty: number;
164
+ factorAmbiguity: number;
165
+ };
166
+ exactConditionalEntropy: {
167
+ mass: Readonly<{
168
+ numerator: string;
169
+ denominator: string;
170
+ }>;
171
+ posterior: Readonly<{
172
+ numerator: string;
173
+ denominator: string;
174
+ }>;
175
+ }[];
176
+ model: "single-dominant-factor@1";
177
+ parameters: {
178
+ k: number;
179
+ n: number;
180
+ c: Readonly<{
181
+ numerator: string;
182
+ denominator: string;
183
+ }>;
184
+ p: Readonly<{
185
+ numerator: string;
186
+ denominator: string;
187
+ }>;
188
+ };
189
+ policy: CapacityPolicy;
190
+ conditions: readonly ["uniform-truth", "factor-independent-of-truth", "uniform-shared-wrong-value", "independent-homogeneous-private-channel"];
191
+ };
192
+ export declare function decisionInformation(k: number, model: CapacityModel, rule: DecisionRule, policy: CapacityPolicy): {
193
+ status: "measured";
194
+ unit: "bits";
195
+ value: number;
196
+ probabilitySum: Readonly<{
197
+ numerator: string;
198
+ denominator: string;
199
+ }>;
200
+ work: string;
201
+ loss: {
202
+ exclusion: number;
203
+ privateUncertainty: number;
204
+ factorAmbiguity: number;
205
+ };
206
+ exactConditionalEntropy: {
207
+ mass: Readonly<{
208
+ numerator: string;
209
+ denominator: string;
210
+ }>;
211
+ posterior: Readonly<{
212
+ numerator: string;
213
+ denominator: string;
214
+ }>;
215
+ }[];
216
+ model: "single-dominant-factor@1";
217
+ parameters: {
218
+ k: number;
219
+ n: number;
220
+ c: Readonly<{
221
+ numerator: string;
222
+ denominator: string;
223
+ }>;
224
+ p: Readonly<{
225
+ numerator: string;
226
+ denominator: string;
227
+ }>;
228
+ };
229
+ policy: CapacityPolicy;
230
+ conditions: readonly ["uniform-truth", "factor-independent-of-truth", "uniform-shared-wrong-value", "independent-homogeneous-private-channel"];
231
+ } | {
232
+ status: "unmeasured";
233
+ cause: string;
234
+ requiredAtLeast: string;
235
+ model: "single-dominant-factor@1";
236
+ parameters: {
237
+ k: number;
238
+ n: number;
239
+ c: Readonly<{
240
+ numerator: string;
241
+ denominator: string;
242
+ }>;
243
+ p: Readonly<{
244
+ numerator: string;
245
+ denominator: string;
246
+ }>;
247
+ };
248
+ policy: CapacityPolicy;
249
+ conditions: readonly ["uniform-truth", "factor-independent-of-truth", "uniform-shared-wrong-value", "independent-homogeneous-private-channel"];
250
+ } | {
251
+ decision: {
252
+ rule: DecisionRule | undefined;
253
+ accuracy: Readonly<{
254
+ numerator: string;
255
+ denominator: string;
256
+ }>;
257
+ information: number;
258
+ joint: Readonly<{
259
+ numerator: string;
260
+ denominator: string;
261
+ }>[][];
262
+ tiePolicy: "uniform-over-maximizers";
263
+ };
264
+ status: "measured";
265
+ unit: "bits";
266
+ value: number;
267
+ probabilitySum: Readonly<{
268
+ numerator: string;
269
+ denominator: string;
270
+ }>;
271
+ work: string;
272
+ loss: {
273
+ exclusion: number;
274
+ privateUncertainty: number;
275
+ factorAmbiguity: number;
276
+ };
277
+ exactConditionalEntropy: {
278
+ mass: Readonly<{
279
+ numerator: string;
280
+ denominator: string;
281
+ }>;
282
+ posterior: Readonly<{
283
+ numerator: string;
284
+ denominator: string;
285
+ }>;
286
+ }[];
287
+ model: "single-dominant-factor@1";
288
+ parameters: {
289
+ k: number;
290
+ n: number;
291
+ c: Readonly<{
292
+ numerator: string;
293
+ denominator: string;
294
+ }>;
295
+ p: Readonly<{
296
+ numerator: string;
297
+ denominator: string;
298
+ }>;
299
+ };
300
+ policy: CapacityPolicy;
301
+ conditions: readonly ["uniform-truth", "factor-independent-of-truth", "uniform-shared-wrong-value", "independent-homogeneous-private-channel"];
302
+ };
@@ -0,0 +1,168 @@
1
+ import { add, sub, mul, div, q, pow, zero, one, pack, probability } from "./rational.js";
2
+ const refuse = (cause) => { throw new Error('REFUSE·capacity ' + cause); };
3
+ function cardinality(n) { if (!Number.isSafeInteger(n) || n < 2)
4
+ refuse('invalid cardinality'); }
5
+ function logPolicy(policy) { if (!policy || policy.logarithm !== 'binary64-log2')
6
+ refuse('logarithm policy required'); }
7
+ function logInteger(value) {
8
+ const bits = value.toString(2).length, shift = Math.max(0, bits - 53);
9
+ return Math.log2(Number(value >> BigInt(shift))) + shift;
10
+ }
11
+ function logQ(value) { return logInteger(value[0]) - logInteger(value[1]); }
12
+ function numeric(value) { return value[0] === 0n ? 0 : 2 ** logQ(value); }
13
+ function entropy(probabilities) {
14
+ let value = 0, correction = 0;
15
+ for (const p of probabilities) {
16
+ if (p[0] === 0n)
17
+ continue;
18
+ const term = -numeric(p) * logQ(p) - correction, next = value + term;
19
+ correction = (next - value) - term;
20
+ value = next;
21
+ }
22
+ return value;
23
+ }
24
+ export function capacityBound(n, c, policy) {
25
+ cardinality(n);
26
+ logPolicy(policy);
27
+ const factor = probability(c);
28
+ return { model: 'single-dominant-factor@1', parameters: { n, c: pack(factor) }, policy,
29
+ status: 'measured', unit: 'bits', value: Math.log2(n) - numeric(factor) * Math.log2(n - 1),
30
+ meaning: 'upper bound for every k; achievability depends on the private channel' };
31
+ }
32
+ export function capacityP0(n, c, policy) {
33
+ const bound = capacityBound(n, c, policy), factor = probability(c);
34
+ return { ...bound, value: bound.value - entropy([factor, sub(one, factor)]), meaning: 'mutual information at p=0 for every positive k' };
35
+ }
36
+ export function unanimityNoFactor(k, n, p, policy) {
37
+ cardinality(n);
38
+ if (!Number.isSafeInteger(k) || k < 1)
39
+ refuse('invalid witness count');
40
+ if (!policy || typeof policy.budget !== 'string' || !/^\d+$/.test(policy.budget))
41
+ refuse('power budget required');
42
+ const error = probability(p);
43
+ const required = BigInt(k) * (BigInt(error[1].toString(2).length) + BigInt(BigInt(n - 1).toString(2).length) + 2n);
44
+ if (required > BigInt(policy.budget))
45
+ return { status: 'unmeasured', cause: 'capacity/power-budget', required: String(required), workUnit: 'rational-bit-bound' };
46
+ return { status: 'measured', model: 'single-dominant-factor@1', parameters: { k, n, p: pack(error) },
47
+ policy, workUnit: 'rational-bit-bound', value: pack(add(pow(sub(one, error), k), div(pow(error, k), pow(q(BigInt(n - 1)), k - 1)))) };
48
+ }
49
+ function term(terms, mass, posterior) {
50
+ if (mass[0] === 0n)
51
+ return;
52
+ const key = posterior.join('/'), previous = terms.get(key);
53
+ terms.set(key, { mass: previous ? add(previous.mass, mass) : mass, posterior });
54
+ }
55
+ function evaluateEntropy(terms) {
56
+ let sum = 0, correction = 0;
57
+ for (const item of terms.values()) {
58
+ const contribution = -numeric(item.mass) * logQ(item.posterior) - correction, next = sum + contribution;
59
+ correction = (next - sum) - contribution;
60
+ sum = next;
61
+ }
62
+ return sum;
63
+ }
64
+ function prepared(k, model, policy) {
65
+ if (!model)
66
+ refuse('model required');
67
+ cardinality(model.n);
68
+ logPolicy(policy);
69
+ if (!Number.isSafeInteger(k) || k < 1)
70
+ refuse('invalid witness count');
71
+ if (typeof policy.budget !== 'string' || !/^\d+$/.test(policy.budget))
72
+ refuse('invalid enumeration budget');
73
+ const c = probability(model.c), p = probability(model.p), cap = BigInt(policy.budget);
74
+ let work = 1n;
75
+ for (let i = 0; i <= k; i++) {
76
+ work *= BigInt(model.n);
77
+ if (work > cap)
78
+ break;
79
+ }
80
+ return { c, p, cap, work };
81
+ }
82
+ export function mutualInformation(k, model, policy) {
83
+ return evaluateCapacity(k, model, policy);
84
+ }
85
+ export function decisionInformation(k, model, rule, policy) {
86
+ if (rule !== 'plurality-uniform-ties@1' && rule !== 'posterior-map-uniform-ties@1')
87
+ refuse('unknown decision rule');
88
+ return evaluateCapacity(k, model, policy, rule);
89
+ }
90
+ function evaluateCapacity(k, model, policy, rule) {
91
+ const { c, p, cap, work } = prepared(k, model, policy), n = model.n;
92
+ const metadata = { model: 'single-dominant-factor@1', parameters: { k, n, c: pack(c), p: pack(p) }, policy,
93
+ conditions: ['uniform-truth', 'factor-independent-of-truth', 'uniform-shared-wrong-value',
94
+ 'independent-homogeneous-private-channel'] };
95
+ if (work > cap)
96
+ return { ...metadata, status: 'unmeasured', cause: 'capacity/budget', requiredAtLeast: String(work) };
97
+ const terms = new Map(), privateTerms = new Map();
98
+ const decisions = rule ? Array.from({ length: n }, () => Array.from({ length: n }, () => zero)) : undefined;
99
+ let total = zero, accuracy = zero;
100
+ const counts = Array(n).fill(0);
101
+ function visit(depth) {
102
+ if (depth < k) {
103
+ for (let value = 0; value < n; value++) {
104
+ counts[value]++;
105
+ visit(depth + 1);
106
+ counts[value]--;
107
+ }
108
+ return;
109
+ }
110
+ const unanimous = counts.findIndex(count => count === k), privateMass = [], joint = [];
111
+ for (let t = 0; t < n; t++) {
112
+ const channel = mul(pow(sub(one, p), counts[t]), pow(div(p, q(BigInt(n - 1))), k - counts[t]));
113
+ const independent = div(channel, q(BigInt(n)));
114
+ privateMass.push(independent);
115
+ const forced = unanimous >= 0 && unanimous !== t ? div(c, q(BigInt(n) * BigInt(n - 1))) : zero;
116
+ joint.push(add(mul(sub(one, c), independent), forced));
117
+ }
118
+ const mass = joint.reduce(add, zero), privateTotal = privateMass.reduce(add, zero);
119
+ total = add(total, mass);
120
+ joint.forEach(value => { if (value[0] !== 0n)
121
+ term(terms, value, div(value, mass)); });
122
+ privateMass.forEach(value => { if (value[0] !== 0n)
123
+ term(privateTerms, value, div(value, privateTotal)); });
124
+ if (!decisions || mass[0] === 0n)
125
+ return;
126
+ const winners = [];
127
+ if (rule === 'plurality-uniform-ties@1') {
128
+ const largest = Math.max(...counts);
129
+ counts.forEach((count, i) => { if (count === largest)
130
+ winners.push(i); });
131
+ }
132
+ else {
133
+ let largest = zero;
134
+ for (const value of joint)
135
+ if (value[0] * largest[1] > largest[0] * value[1])
136
+ largest = value;
137
+ joint.forEach((value, i) => { if (value[0] * largest[1] === largest[0] * value[1])
138
+ winners.push(i); });
139
+ }
140
+ for (let t = 0; t < n; t++)
141
+ for (const answer of winners) {
142
+ const mass = div(joint[t], q(BigInt(winners.length)));
143
+ decisions[t][answer] = add(decisions[t][answer], mass);
144
+ if (answer === t)
145
+ accuracy = add(accuracy, mass);
146
+ }
147
+ }
148
+ visit(0);
149
+ const conditional = evaluateEntropy(terms), privateConditional = evaluateEntropy(privateTerms);
150
+ const result = { ...metadata, status: 'measured', unit: 'bits',
151
+ value: Math.log2(n) - conditional, probabilitySum: pack(total), work: String(work),
152
+ loss: { exclusion: numeric(c) * Math.log2(n - 1), privateUncertainty: numeric(sub(one, c)) * privateConditional,
153
+ factorAmbiguity: conditional - numeric(c) * Math.log2(n - 1) - numeric(sub(one, c)) * privateConditional },
154
+ exactConditionalEntropy: [...terms.values()].map(({ mass, posterior }) => ({ mass: pack(mass), posterior: pack(posterior) })) };
155
+ if (!decisions)
156
+ return result;
157
+ const decisionTerms = new Map();
158
+ for (let answer = 0; answer < n; answer++) {
159
+ const mass = decisions.reduce((sum, row) => add(sum, row[answer]), zero);
160
+ if (mass[0] === 0n)
161
+ continue;
162
+ for (const row of decisions)
163
+ if (row[answer][0] !== 0n)
164
+ term(decisionTerms, row[answer], div(row[answer], mass));
165
+ }
166
+ return { ...result, decision: { rule, accuracy: pack(accuracy), information: Math.log2(n) - evaluateEntropy(decisionTerms),
167
+ joint: decisions.map(row => row.map(pack)), tiePolicy: 'uniform-over-maximizers' } };
168
+ }
@@ -0,0 +1,111 @@
1
+ import { type RationalInput } from './rational.ts';
2
+ export type ValueReading = Readonly<{
3
+ event: string;
4
+ origin: string;
5
+ value: string;
6
+ }>;
7
+ export type TruthReading = Readonly<{
8
+ event: string;
9
+ value: string;
10
+ kind: 'truth';
11
+ }>;
12
+ export type CoincidenceReference = Readonly<{
13
+ model: 'independent-uniform-wrong@1';
14
+ values: readonly string[];
15
+ }> & (Readonly<{
16
+ marginals: 'handed';
17
+ errors: readonly Readonly<{
18
+ origin: string;
19
+ probability: RationalInput;
20
+ }>[];
21
+ }> | Readonly<{
22
+ marginals: 'estimated-by-truth';
23
+ }>);
24
+ export type CoincidencePolicy = Readonly<{
25
+ minEvents: number;
26
+ minEventsPerTruth: number;
27
+ budget: string;
28
+ }>;
29
+ export declare function coincidenceExcess(rows: readonly ValueReading[], truth: readonly TruthReading[], selection: readonly string[], reference: CoincidenceReference, policy: CoincidencePolicy): {
30
+ status: "unmeasured";
31
+ cause: string;
32
+ required: string;
33
+ reference: CoincidenceReference;
34
+ policy: Readonly<{
35
+ minEvents: number;
36
+ minEventsPerTruth: number;
37
+ budget: string;
38
+ }>;
39
+ selection: string[];
40
+ order: number;
41
+ interpretation: "coincidence relative to the declared reference; not causal identification";
42
+ } | {
43
+ status: "unmeasured";
44
+ cause: string;
45
+ events: number;
46
+ reference: CoincidenceReference;
47
+ policy: Readonly<{
48
+ minEvents: number;
49
+ minEventsPerTruth: number;
50
+ budget: string;
51
+ }>;
52
+ selection: string[];
53
+ order: number;
54
+ interpretation: "coincidence relative to the declared reference; not causal identification";
55
+ } | {
56
+ status: "measured";
57
+ events: number;
58
+ falseCoincidences: number;
59
+ observed: Readonly<{
60
+ numerator: string;
61
+ denominator: string;
62
+ }>;
63
+ expected: Readonly<{
64
+ numerator: string;
65
+ denominator: string;
66
+ }>;
67
+ delta: Readonly<{
68
+ numerator: string;
69
+ denominator: string;
70
+ }>;
71
+ ratio: {
72
+ status: "unmeasured";
73
+ cause: string;
74
+ value?: undefined;
75
+ } | {
76
+ status: "measured";
77
+ value: Readonly<{
78
+ numerator: string;
79
+ denominator: string;
80
+ }>;
81
+ cause?: undefined;
82
+ };
83
+ strata: {
84
+ truth: string;
85
+ events: number;
86
+ marginals: {
87
+ origin: string;
88
+ error: Readonly<{
89
+ numerator: string;
90
+ denominator: string;
91
+ }>;
92
+ }[];
93
+ reference: Readonly<{
94
+ numerator: string;
95
+ denominator: string;
96
+ }>;
97
+ }[];
98
+ uncertainty: {
99
+ status: "unmeasured";
100
+ cause: string;
101
+ };
102
+ reference: CoincidenceReference;
103
+ policy: Readonly<{
104
+ minEvents: number;
105
+ minEventsPerTruth: number;
106
+ budget: string;
107
+ }>;
108
+ selection: string[];
109
+ order: number;
110
+ interpretation: "coincidence relative to the declared reference; not causal identification";
111
+ };
@@ -0,0 +1,81 @@
1
+ import { add, sub, mul, div, pow, q, zero, one, pack, probability } from "./rational.js";
2
+ const refuse = (cause) => { throw new Error('REFUSE·coincidence ' + cause); };
3
+ const named = (value) => typeof value === 'string' && value.length > 0;
4
+ export function coincidenceExcess(rows, truth, selection, reference, policy) {
5
+ if (!Array.isArray(rows) || !Array.isArray(truth) || !Array.isArray(selection) || !selection.length ||
6
+ selection.some(id => !named(id)) || new Set(selection).size !== selection.length)
7
+ refuse('invalid selection or rows');
8
+ if (!reference || reference.model !== 'independent-uniform-wrong@1' || !Array.isArray(reference.values) ||
9
+ reference.values.length < 2 || reference.values.some(value => !named(value)) || new Set(reference.values).size !== reference.values.length)
10
+ refuse('invalid declared reference');
11
+ if (!policy || !Number.isSafeInteger(policy.minEvents) || policy.minEvents < 1 ||
12
+ !Number.isSafeInteger(policy.minEventsPerTruth) || policy.minEventsPerTruth < 1 || typeof policy.budget !== 'string' || !/^\d+$/.test(policy.budget))
13
+ refuse('invalid observation policy');
14
+ const required = BigInt(rows.length) * (BigInt(selection.length) + 1n) + BigInt(truth.length) + BigInt(reference.values.length) + BigInt(selection.length);
15
+ const metadata = { reference, policy, selection: [...selection].sort(), order: selection.length,
16
+ interpretation: 'coincidence relative to the declared reference; not causal identification' };
17
+ if (required > BigInt(policy.budget))
18
+ return { ...metadata, status: 'unmeasured', cause: 'coincidence/budget', required: String(required) };
19
+ const selected = new Set(selection), alphabet = new Set(reference.values), truthByEvent = new Map();
20
+ for (const row of truth) {
21
+ if (!row || row.kind !== 'truth' || !named(row.event) || !alphabet.has(row.value))
22
+ refuse('invalid truth; probes require a separate declared contract');
23
+ if (truthByEvent.has(row.event) && truthByEvent.get(row.event) !== row.value)
24
+ refuse('conflicting truth ' + row.event);
25
+ truthByEvent.set(row.event, row.value);
26
+ }
27
+ const events = new Map();
28
+ for (const row of rows) {
29
+ if (!row || !named(row.event) || !named(row.origin) || !alphabet.has(row.value))
30
+ refuse('invalid value reading');
31
+ if (!selected.has(row.origin))
32
+ continue;
33
+ const values = events.get(row.event) ?? new Map();
34
+ if (values.has(row.origin) && values.get(row.origin) !== row.value)
35
+ refuse('conflicting value ' + row.event + '/' + row.origin);
36
+ values.set(row.origin, row.value);
37
+ events.set(row.event, values);
38
+ }
39
+ const ordered = [...events.keys()].sort();
40
+ if (ordered.length < policy.minEvents)
41
+ return { ...metadata, status: 'unmeasured', cause: 'insufficient observed events', events: ordered.length };
42
+ if (ordered.some(event => events.get(event).size !== selection.length || !truthByEvent.has(event)))
43
+ return { ...metadata, status: 'unmeasured', cause: 'incomplete shared events or truth', events: ordered.length };
44
+ const origins = [...selection].sort(), groups = new Map();
45
+ let falseCoincidences = 0;
46
+ for (const event of ordered) {
47
+ const t = truthByEvent.get(event), values = origins.map(id => events.get(event).get(id));
48
+ if (values.every(value => value === values[0]) && values[0] !== t)
49
+ falseCoincidences++;
50
+ const group = groups.get(t) ?? { events: 0, errors: origins.map(() => 0) };
51
+ group.events++;
52
+ values.forEach((value, i) => { if (value !== t)
53
+ group.errors[i]++; });
54
+ groups.set(t, group);
55
+ }
56
+ let handed = [];
57
+ if (reference.marginals === 'handed') {
58
+ if (!Array.isArray(reference.errors) || reference.errors.length !== origins.length || reference.errors.some(row => !row) ||
59
+ new Set(reference.errors.map(row => row.origin)).size !== origins.length ||
60
+ reference.errors.some(row => !row || !selected.has(row.origin)))
61
+ refuse('marginals must name exactly the selection');
62
+ handed = origins.map(id => probability(reference.errors.find(row => row.origin === id).probability));
63
+ }
64
+ else if (reference.marginals !== 'estimated-by-truth')
65
+ refuse('unknown marginal procedure');
66
+ if (reference.marginals === 'estimated-by-truth' && [...groups.values()].some(group => group.events < policy.minEventsPerTruth))
67
+ return { ...metadata, status: 'unmeasured', cause: 'insufficient events per observed truth', events: ordered.length };
68
+ let expected = zero;
69
+ const strata = [...groups].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([value, group]) => {
70
+ const errors = reference.marginals === 'handed' ? handed : group.errors.map(count => q(BigInt(count), BigInt(group.events)));
71
+ const baseline = div(errors.reduce(mul, one), pow(q(BigInt(reference.values.length - 1)), origins.length - 1));
72
+ expected = add(expected, mul(q(BigInt(group.events), BigInt(ordered.length)), baseline));
73
+ return { truth: value, events: group.events, marginals: origins.map((origin, i) => ({ origin, error: pack(errors[i]) })), reference: pack(baseline) };
74
+ });
75
+ const observed = q(BigInt(falseCoincidences), BigInt(ordered.length));
76
+ return { ...metadata, status: 'measured', events: ordered.length, falseCoincidences, observed: pack(observed),
77
+ expected: pack(expected), delta: pack(sub(observed, expected)),
78
+ ratio: expected[0] === 0n ? { status: 'unmeasured', cause: 'zero reference mass' } :
79
+ { status: 'measured', value: pack(div(observed, expected)) }, strata,
80
+ uncertainty: { status: 'unmeasured', cause: 'descriptive population only; no sampling design handed' } };
81
+ }
@@ -0,0 +1,27 @@
1
+ export type ErrorEvent = Readonly<{
2
+ event: string;
3
+ origin: string;
4
+ error: 0 | 1;
5
+ }>;
6
+ export type Unmeasured = Readonly<{
7
+ status: 'unmeasured';
8
+ cause: string;
9
+ }>;
10
+ export type ErrorMatrix = Readonly<{
11
+ status: 'measured';
12
+ origins: readonly string[];
13
+ events: readonly string[];
14
+ matrix: readonly (readonly number[])[];
15
+ }>;
16
+ export type Covariance = ErrorMatrix;
17
+ export type Scalar = Readonly<{
18
+ status: 'measured';
19
+ value: number;
20
+ }> | Unmeasured;
21
+ export type ObservationPolicy = Readonly<{
22
+ minCells: number;
23
+ }>;
24
+ export declare function errorMatrix(rows: readonly ErrorEvent[], policy: ObservationPolicy, requested?: readonly string[]): ErrorMatrix | Unmeasured;
25
+ export declare function covariance(reading: ErrorMatrix | Unmeasured): Covariance | Unmeasured;
26
+ export declare const errorCovariance: (rows: readonly ErrorEvent[], policy: ObservationPolicy) => Covariance | Unmeasured;
27
+ export declare function participationRatio(reading: Covariance | Unmeasured): Scalar;