@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,67 @@
1
+ function valid({ c, p, n }) {
2
+ if (!Number.isFinite(c) || c < 0 || c > 1 || !Number.isFinite(p) || p < 0 || p > 1 || !Number.isSafeInteger(n) || n < 2)
3
+ throw new Error('REFUSE·measure invalid unanimity model');
4
+ }
5
+ function logSum(a, b) { const high = Math.max(a, b); return high === -Infinity ? high : high + Math.log(Math.exp(a - high) + Math.exp(b - high)); }
6
+ export function unanimityFloor(model, k) {
7
+ valid(model);
8
+ if (!Number.isSafeInteger(k) || k < 1)
9
+ throw new Error('REFUSE·measure invalid witness count');
10
+ const { c, p, n } = model;
11
+ if (c === 1 || p === 1)
12
+ return { status: 'measured', value: 1 };
13
+ if (p === 0)
14
+ return { status: 'measured', value: c };
15
+ const logPrivate = Math.log1p(-c) + k * Math.log(p) + (1 - k) * Math.log(n - 1);
16
+ const logA = logSum(c === 0 ? -Infinity : Math.log(c), logPrivate), logB = Math.log1p(-c) + k * Math.log1p(-p);
17
+ const odds = logA - logB;
18
+ const value = odds >= 0 ? 1 / (1 + Math.exp(-odds)) : Math.exp(odds) / (1 + Math.exp(odds));
19
+ return { status: 'measured', value };
20
+ }
21
+ export function unanimityThreshold(model, searchLimit) {
22
+ valid(model);
23
+ if (!Number.isSafeInteger(searchLimit) || searchLimit < 1)
24
+ throw new Error('REFUSE·measure invalid search budget');
25
+ const { c, p, n } = model, q = p / (n - 1), coefficient = 1 - p - q;
26
+ if (c === 1 || p === 0 || p === 1 || coefficient <= 0)
27
+ return { status: 'measured', value: 1 };
28
+ if (c === 0)
29
+ return { status: 'unmeasured', cause: 'no finite nondecreasing threshold for this model' };
30
+ const right = Math.log(p) + Math.log(c), base = Math.log(coefficient) + Math.log1p(-c) + Math.log(n - 1);
31
+ for (let k = 1; k <= searchLimit; k++)
32
+ if (base + k * Math.log(q) <= right)
33
+ return { status: 'measured', value: k };
34
+ return { status: 'unmeasured', cause: 'threshold outside declared search budget' };
35
+ }
36
+ export function descentCount(graph, cell) {
37
+ const nodes = new Map();
38
+ for (const node of graph) {
39
+ if (!node.scope || nodes.has(node.scope))
40
+ throw new Error('REFUSE·measure duplicate or absent graph scope');
41
+ nodes.set(node.scope, [...new Set(node.restsOn)]);
42
+ }
43
+ if (!nodes.has(cell) || [...nodes.values()].some(parents => parents.some(parent => !nodes.has(parent))))
44
+ return { status: 'unmeasured', cause: 'incomplete declared descent graph' };
45
+ const seen = new Map(), visiting = new Set();
46
+ function reaches(scope) {
47
+ if (visiting.has(scope))
48
+ throw new Error('REFUSE·measure cyclic descent at ' + scope);
49
+ if (seen.has(scope))
50
+ return seen.get(scope);
51
+ visiting.add(scope);
52
+ let result = false;
53
+ for (const parent of nodes.get(scope)) {
54
+ const nested = reaches(parent);
55
+ result = result || parent === cell || nested;
56
+ }
57
+ visiting.delete(scope);
58
+ seen.set(scope, result);
59
+ return result;
60
+ }
61
+ let count = 0;
62
+ for (const scope of nodes.keys())
63
+ if (reaches(scope) && scope !== cell)
64
+ count++;
65
+ return { status: 'measured', value: count };
66
+ }
67
+ export const modelRequired = (measure) => ({ status: 'unmeasured', cause: 'declared model required for ' + measure });
@@ -0,0 +1,19 @@
1
+ export type Rational = Readonly<{
2
+ numerator: string;
3
+ denominator: string;
4
+ }>;
5
+ export type RationalInput = string | Rational;
6
+ export type Q = readonly [bigint, bigint];
7
+ export declare const zero: Q, one: Q;
8
+ export declare const refuse: (cause: string) => never;
9
+ export declare function q(a: bigint, b?: bigint): Q;
10
+ export declare function rational(input: RationalInput): Q;
11
+ export declare const add: (a: Q, b: Q) => Q;
12
+ export declare const sub: (a: Q, b: Q) => Q;
13
+ export declare const mul: (a: Q, b: Q) => Q;
14
+ export declare const div: (a: Q, b: Q) => Q;
15
+ export declare const pow: (a: Q, n: number) => Q;
16
+ export declare const equal: (a: Q, b: Q) => boolean;
17
+ export declare const pack: (a: Q) => Rational;
18
+ export declare function probability(input: RationalInput): Q;
19
+ export declare function roundHalfEven(input: RationalInput, digits: number): string;
@@ -0,0 +1,53 @@
1
+ export const zero = [0n, 1n], one = [1n, 1n];
2
+ export const refuse = (cause) => { throw new Error('REFUSE·model/M3 ' + cause); };
3
+ const gcd = (a, b) => { a = a < 0n ? -a : a; while (b) {
4
+ const r = a % b;
5
+ a = b;
6
+ b = r;
7
+ } return a; };
8
+ export function q(a, b = 1n) {
9
+ if (b === 0n)
10
+ refuse('zero rational denominator');
11
+ if (b < 0n) {
12
+ a = -a;
13
+ b = -b;
14
+ }
15
+ const d = gcd(a, b);
16
+ return [a / d, b / d];
17
+ }
18
+ export function rational(input) {
19
+ if (typeof input === 'string') {
20
+ if (/^-?\d+\/\d+$/.test(input)) {
21
+ const [a, b] = input.split('/');
22
+ return q(BigInt(a), BigInt(b));
23
+ }
24
+ if (!/^-?\d+(\.\d+)?$/.test(input))
25
+ refuse('invalid exact probability');
26
+ const sign = input.startsWith('-') ? -1n : 1n, text = input.replace(/^-/, '');
27
+ const [whole, part = ''] = text.split('.');
28
+ return q(sign * BigInt(whole + part), 10n ** BigInt(part.length));
29
+ }
30
+ if (!input || typeof input.numerator !== 'string' || typeof input.denominator !== 'string' || !/^[-]?\d+$/.test(input.numerator) || !/^\d+$/.test(input.denominator))
31
+ refuse('invalid rational');
32
+ return q(BigInt(input.numerator), BigInt(input.denominator));
33
+ }
34
+ export const add = (a, b) => q(a[0] * b[1] + b[0] * a[1], a[1] * b[1]);
35
+ export const sub = (a, b) => q(a[0] * b[1] - b[0] * a[1], a[1] * b[1]);
36
+ export const mul = (a, b) => q(a[0] * b[0], a[1] * b[1]);
37
+ export const div = (a, b) => q(a[0] * b[1], a[1] * b[0]);
38
+ export const pow = (a, n) => q(a[0] ** BigInt(n), a[1] ** BigInt(n));
39
+ export const equal = (a, b) => a[0] === b[0] && a[1] === b[1];
40
+ export const pack = (a) => ({ numerator: String(a[0]), denominator: String(a[1]) });
41
+ export function probability(input) { const a = rational(input); if (a[0] < 0n || a[0] > a[1])
42
+ refuse('probability outside 0..1'); return a; }
43
+ export function roundHalfEven(input, digits) {
44
+ if (!Number.isSafeInteger(digits) || digits < 0)
45
+ refuse('invalid decimal digits');
46
+ const [a, b] = rational(input), scale = 10n ** BigInt(digits), absolute = a < 0n ? -a : a;
47
+ let integer = absolute * scale / b;
48
+ const remainder = absolute * scale % b;
49
+ if (2n * remainder > b || (2n * remainder === b && integer % 2n === 1n))
50
+ integer++;
51
+ const text = String(integer).padStart(digits + 1, '0'), prefix = a < 0n && integer !== 0n ? '-' : '';
52
+ return prefix + (digits ? text.slice(0, -digits) + '.' + text.slice(-digits) : text);
53
+ }
@@ -0,0 +1,22 @@
1
+ import type { Covariance, Scalar, Unmeasured } from './error-events.ts';
2
+ export type NumericalPolicy = Readonly<{
3
+ absolute: number;
4
+ relative: number;
5
+ maxRotations: number;
6
+ }>;
7
+ export type Spectrum = Readonly<{
8
+ status: 'measured';
9
+ origins: readonly string[];
10
+ values: readonly number[];
11
+ vectors: readonly (readonly number[])[];
12
+ tolerance: number;
13
+ residual: number;
14
+ }>;
15
+ export declare function spectrum(reading: Covariance | Unmeasured, policy: NumericalPolicy): Spectrum | Unmeasured;
16
+ export declare function commonMass(reading: Spectrum | Unmeasured): Scalar;
17
+ export declare function loadings(reading: Spectrum | Unmeasured): Readonly<{
18
+ status: 'measured';
19
+ origins: readonly string[];
20
+ values: readonly number[];
21
+ }> | Unmeasured;
22
+ export declare function commonMassOf(reading: Spectrum | Unmeasured, origins: readonly string[], tau: number): Scalar;
@@ -0,0 +1,84 @@
1
+ const refuse = (cause) => { throw new Error('REFUSE·measure ' + cause); };
2
+ export function spectrum(reading, policy) {
3
+ if (reading.status === 'unmeasured')
4
+ return reading;
5
+ if (!Number.isFinite(policy.absolute) || policy.absolute <= 0 || !Number.isFinite(policy.relative) || policy.relative < 0 || !Number.isSafeInteger(policy.maxRotations) || policy.maxRotations < 1)
6
+ refuse('invalid numerical policy');
7
+ const n = reading.origins.length, original = reading.matrix;
8
+ if (!n || original.length !== n || original.some(row => row.length !== n || row.some(x => !Number.isFinite(x))))
9
+ refuse('invalid covariance matrix');
10
+ const scale = Math.max(...original.flat().map(Math.abs)), tolerance = policy.absolute + policy.relative * scale;
11
+ for (let i = 0; i < n; i++)
12
+ for (let j = 0; j < n; j++)
13
+ if (original[i][j] !== original[j][i])
14
+ refuse('nonsymmetric covariance');
15
+ const a = original.map(row => [...row]), v = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => i === j ? 1 : 0));
16
+ for (let step = 0; step < policy.maxRotations; step++) {
17
+ let p = 0, q = 0, largest = 0;
18
+ for (let i = 0; i < n; i++)
19
+ for (let j = i + 1; j < n; j++)
20
+ if (Math.abs(a[i][j]) > largest) {
21
+ largest = Math.abs(a[i][j]);
22
+ p = i;
23
+ q = j;
24
+ }
25
+ if (largest <= tolerance)
26
+ break;
27
+ const theta = .5 * Math.atan2(2 * a[p][q], a[q][q] - a[p][p]), c = Math.cos(theta), s = Math.sin(theta);
28
+ const pp = a[p][p], qq = a[q][q], pq = a[p][q];
29
+ for (let k = 0; k < n; k++)
30
+ if (k !== p && k !== q) {
31
+ const x = a[k][p], y = a[k][q];
32
+ a[k][p] = a[p][k] = c * x - s * y;
33
+ a[k][q] = a[q][k] = s * x + c * y;
34
+ }
35
+ a[p][p] = c * c * pp - 2 * s * c * pq + s * s * qq;
36
+ a[q][q] = s * s * pp + 2 * s * c * pq + c * c * qq;
37
+ a[p][q] = a[q][p] = 0;
38
+ for (let k = 0; k < n; k++) {
39
+ const x = v[k][p], y = v[k][q];
40
+ v[k][p] = c * x - s * y;
41
+ v[k][q] = s * x + c * y;
42
+ }
43
+ }
44
+ const order = Array.from({ length: n }, (_, i) => i).sort((i, j) => a[j][j] - a[i][i] || i - j);
45
+ const raw = order.map(i => a[i][i]);
46
+ if (raw.some(x => x < -tolerance))
47
+ refuse('covariance is not positive semidefinite');
48
+ const values = raw.map(x => Math.max(0, x)), vectors = order.map(i => v.map(row => row[i]));
49
+ const residual = Math.max(...vectors.map((vector, i) => Math.hypot(...original.map((row, j) => row.reduce((sum, x, k) => sum + x * vector[k], 0) - values[i] * vector[j]))));
50
+ if (residual > n * tolerance)
51
+ return { status: 'unmeasured', cause: 'eigensolver did not converge within declared budget' };
52
+ return { status: 'measured', origins: reading.origins, values, vectors, tolerance, residual };
53
+ }
54
+ export function commonMass(reading) {
55
+ if (reading.status === 'unmeasured')
56
+ return reading;
57
+ const total = reading.values.reduce((sum, x) => sum + x, 0);
58
+ return total <= reading.tolerance ? { status: 'unmeasured', cause: 'no resolved variation' } : { status: 'measured', value: reading.values[0] / total };
59
+ }
60
+ export function loadings(reading) {
61
+ if (reading.status === 'unmeasured')
62
+ return reading;
63
+ if (reading.values[0] <= reading.tolerance || (reading.values.length > 1 && reading.values[0] - reading.values[1] <= 2 * reading.tolerance))
64
+ return { status: 'unmeasured', cause: 'top eigenspace is unresolved or repeated' };
65
+ return { status: 'measured', origins: reading.origins, values: reading.vectors[0].map(Math.abs) };
66
+ }
67
+ export function commonMassOf(reading, origins, tau) {
68
+ if (reading.status === 'unmeasured')
69
+ return reading;
70
+ if (!Number.isFinite(tau) || tau <= 0 || tau > 1)
71
+ refuse('invalid spectral threshold');
72
+ const selected = [...new Set(origins)].map(origin => reading.origins.indexOf(origin));
73
+ if (!selected.length || selected.some(i => i < 0))
74
+ return { status: 'unmeasured', cause: 'subset not observed' };
75
+ const energy = reading.values.map((value, k) => value * selected.reduce((sum, i) => sum + reading.vectors[k][i] ** 2, 0));
76
+ const total = energy.reduce((sum, x) => sum + x, 0);
77
+ if (total <= reading.tolerance)
78
+ return { status: 'unmeasured', cause: 'no resolved subset variation' };
79
+ const cutoff = tau * reading.values[0];
80
+ if (reading.values.some(value => Math.abs(value - cutoff) <= 2 * reading.tolerance) && tau !== 1)
81
+ return { status: 'unmeasured', cause: 'spectral threshold unresolved at tolerance' };
82
+ const common = energy.reduce((sum, x, k) => sum + (reading.values[k] >= cutoff - 2 * reading.tolerance ? x : 0), 0);
83
+ return { status: 'measured', value: common / total };
84
+ }
@@ -0,0 +1,156 @@
1
+ import { type RationalInput } from './rational.ts';
2
+ export type UnanimityCount = Readonly<{
3
+ k: number;
4
+ unanimous: number;
5
+ trials: number;
6
+ }>;
7
+ export type UnanimityGroup = Readonly<{
8
+ id: string;
9
+ outcomes: readonly Readonly<{
10
+ k: number;
11
+ unanimous: boolean;
12
+ }>[];
13
+ }>;
14
+ export type UnanimityCurve = Readonly<{
15
+ n: number;
16
+ lineage: string;
17
+ counts: readonly UnanimityCount[];
18
+ groups?: readonly UnanimityGroup[];
19
+ }>;
20
+ export type UnanimityFitPolicy = Readonly<{
21
+ procedure: 'exact-c-slices-on-declared-p-grid@1';
22
+ p: readonly RationalInput[];
23
+ tolerance: RationalInput;
24
+ budget: string;
25
+ bootstrap: Readonly<{
26
+ B: number;
27
+ seed: number;
28
+ prng: 'xorshift32';
29
+ randomWords: number;
30
+ }>;
31
+ }>;
32
+ export declare function fitUnanimityModel(curve: UnanimityCurve, policy: UnanimityFitPolicy): {
33
+ status: "unmeasured";
34
+ cause: string;
35
+ required: string;
36
+ model: "single-dominant-factor@1";
37
+ lineage: string;
38
+ cardinality: number;
39
+ policy: Readonly<{
40
+ procedure: "exact-c-slices-on-declared-p-grid@1";
41
+ p: readonly RationalInput[];
42
+ tolerance: RationalInput;
43
+ budget: string;
44
+ bootstrap: Readonly<{
45
+ B: number;
46
+ seed: number;
47
+ prng: "xorshift32";
48
+ randomWords: number;
49
+ }>;
50
+ }>;
51
+ meaning: "compatible with (c,p) under the declared model, on the declared p slices only";
52
+ workUnit: "rational-bit-and-group-bound";
53
+ } | {
54
+ compatible: {
55
+ p: Readonly<{
56
+ numerator: string;
57
+ denominator: string;
58
+ }>;
59
+ c: {
60
+ lo: Readonly<{
61
+ numerator: string;
62
+ denominator: string;
63
+ }>;
64
+ hi: Readonly<{
65
+ numerator: string;
66
+ denominator: string;
67
+ }>;
68
+ };
69
+ residuals: {
70
+ k: number;
71
+ at: "slice-midpoint";
72
+ predicted: Readonly<{
73
+ numerator: string;
74
+ denominator: string;
75
+ }>;
76
+ observed: Readonly<{
77
+ numerator: string;
78
+ denominator: string;
79
+ }>;
80
+ residual: Readonly<{
81
+ numerator: string;
82
+ denominator: string;
83
+ }>;
84
+ }[];
85
+ }[];
86
+ identification: {
87
+ status: "unmeasured";
88
+ cause: string;
89
+ diagnostics: string[];
90
+ };
91
+ uncertainty: {
92
+ status: "unmeasured";
93
+ cause: string;
94
+ words: number;
95
+ procedure?: undefined;
96
+ replicas?: undefined;
97
+ interpretation?: undefined;
98
+ } | {
99
+ status: "measured";
100
+ procedure: "paired-group-bootstrap-regions@1";
101
+ replicas: {
102
+ p: Readonly<{
103
+ numerator: string;
104
+ denominator: string;
105
+ }>;
106
+ c: {
107
+ lo: Readonly<{
108
+ numerator: string;
109
+ denominator: string;
110
+ }>;
111
+ hi: Readonly<{
112
+ numerator: string;
113
+ denominator: string;
114
+ }>;
115
+ };
116
+ residuals: {
117
+ k: number;
118
+ at: "slice-midpoint";
119
+ predicted: Readonly<{
120
+ numerator: string;
121
+ denominator: string;
122
+ }>;
123
+ observed: Readonly<{
124
+ numerator: string;
125
+ denominator: string;
126
+ }>;
127
+ residual: Readonly<{
128
+ numerator: string;
129
+ denominator: string;
130
+ }>;
131
+ }[];
132
+ }[][];
133
+ words: number;
134
+ interpretation: "empirical replicate regions; not a guaranteed confidence set";
135
+ cause?: undefined;
136
+ };
137
+ cause?: string | undefined;
138
+ status: "measured" | "unmeasured";
139
+ model: "single-dominant-factor@1";
140
+ lineage: string;
141
+ cardinality: number;
142
+ policy: Readonly<{
143
+ procedure: "exact-c-slices-on-declared-p-grid@1";
144
+ p: readonly RationalInput[];
145
+ tolerance: RationalInput;
146
+ budget: string;
147
+ bootstrap: Readonly<{
148
+ B: number;
149
+ seed: number;
150
+ prng: "xorshift32";
151
+ randomWords: number;
152
+ }>;
153
+ }>;
154
+ meaning: "compatible with (c,p) under the declared model, on the declared p slices only";
155
+ workUnit: "rational-bit-and-group-bound";
156
+ };
@@ -0,0 +1,133 @@
1
+ import { add, sub, mul, div, q, pow, zero, one, pack, probability } from "./rational.js";
2
+ const refuse = (cause) => { throw new Error('REFUSE·unanimity-fit ' + cause); };
3
+ const compare = (a, b) => a[0] * b[1] < b[0] * a[1] ? -1 : a[0] * b[1] > b[0] * a[1] ? 1 : 0;
4
+ function privateRate(k, n, p) {
5
+ return add(pow(sub(one, p), k), div(pow(p, k), pow(q(BigInt(n - 1)), k - 1)));
6
+ }
7
+ function slices(counts, n, grid, tolerance) {
8
+ return grid.flatMap(p => {
9
+ let lo = zero, hi = one;
10
+ for (const row of counts) {
11
+ const observed = q(BigInt(row.unanimous), BigInt(row.trials)), a = privateRate(row.k, n, p), b = sub(one, a);
12
+ if (b[0] === 0n) {
13
+ if (compare(observed, sub(one, tolerance)) < 0)
14
+ return [];
15
+ continue;
16
+ }
17
+ const lower = div(sub(sub(observed, tolerance), a), b), upper = div(sub(add(observed, tolerance), a), b);
18
+ if (compare(lower, lo) > 0)
19
+ lo = lower;
20
+ if (compare(upper, hi) < 0)
21
+ hi = upper;
22
+ if (compare(lo, hi) > 0)
23
+ return [];
24
+ }
25
+ const midpoint = div(add(lo, hi), q(2n));
26
+ return [{ p: pack(p), c: { lo: pack(lo), hi: pack(hi) }, residuals: counts.map(row => {
27
+ const predicted = add(midpoint, mul(sub(one, midpoint), privateRate(row.k, n, p)));
28
+ return { k: row.k, at: 'slice-midpoint', predicted: pack(predicted),
29
+ observed: pack(q(BigInt(row.unanimous), BigInt(row.trials))),
30
+ residual: pack(sub(q(BigInt(row.unanimous), BigInt(row.trials)), predicted)) };
31
+ }) }];
32
+ });
33
+ }
34
+ export function fitUnanimityModel(curve, policy) {
35
+ if (!curve || !Number.isSafeInteger(curve.n) || curve.n < 2 || typeof curve.lineage !== 'string' || !curve.lineage ||
36
+ !Array.isArray(curve.counts) || !curve.counts.length)
37
+ refuse('one declared lineage, cardinality and counts required');
38
+ if (curve.counts.some(row => !row || !Number.isSafeInteger(row.k) || row.k < 1 || !Number.isSafeInteger(row.trials) || row.trials < 1 ||
39
+ !Number.isSafeInteger(row.unanimous) || row.unanimous < 0 || row.unanimous > row.trials))
40
+ refuse('invalid size counts');
41
+ const counts = [...curve.counts].sort((a, b) => a.k - b.k);
42
+ if (new Set(counts.map(row => row.k)).size !== counts.length)
43
+ refuse('invalid or duplicate size counts');
44
+ if (!policy || policy.procedure !== 'exact-c-slices-on-declared-p-grid@1' || !Array.isArray(policy.p) || !policy.p.length ||
45
+ typeof policy.budget !== 'string' || !/^\d+$/.test(policy.budget))
46
+ refuse('declared fitting procedure and budget required');
47
+ const grid = policy.p.map(probability).sort(compare), tolerance = probability(policy.tolerance), bootstrap = policy.bootstrap;
48
+ if (new Set(grid.map(p => p.join('/'))).size !== grid.length)
49
+ refuse('duplicate p slice');
50
+ if (!bootstrap || bootstrap.prng !== 'xorshift32' || !Number.isInteger(bootstrap.seed) || bootstrap.seed < 1 || bootstrap.seed > 0xffffffff ||
51
+ !Number.isSafeInteger(bootstrap.B) || bootstrap.B < 0 || !Number.isSafeInteger(bootstrap.randomWords) || bootstrap.randomWords < 0)
52
+ refuse('invalid bootstrap declaration');
53
+ const work = grid.reduce((sum, p) => sum + counts.reduce((total, row) => total + BigInt(row.k) *
54
+ (BigInt(p[1].toString(2).length) + BigInt(BigInt(curve.n - 1).toString(2).length) + 2n), 0n), 0n) * (BigInt(bootstrap.B) + 1n) +
55
+ BigInt(curve.groups?.length ?? 0) * (BigInt(counts.length) + 1n) * (BigInt(bootstrap.B) + 1n);
56
+ const metadata = { model: 'single-dominant-factor@1', lineage: curve.lineage, cardinality: curve.n, policy,
57
+ meaning: 'compatible with (c,p) under the declared model, on the declared p slices only',
58
+ workUnit: 'rational-bit-and-group-bound' };
59
+ if (work > BigInt(policy.budget))
60
+ return { ...metadata, status: 'unmeasured', cause: 'unanimity-fit/budget', required: String(work) };
61
+ const compatible = slices(counts, curve.n, grid, tolerance);
62
+ let groups;
63
+ if (curve.groups !== undefined) {
64
+ if (!Array.isArray(curve.groups) || !curve.groups.length || curve.groups.some(group => !group) || new Set(curve.groups.map(group => group.id)).size !== curve.groups.length)
65
+ refuse('invalid or duplicate sampling groups');
66
+ for (const group of curve.groups) {
67
+ if (!group || typeof group.id !== 'string' || !group.id || !Array.isArray(group.outcomes) || group.outcomes.length !== counts.length ||
68
+ group.outcomes.some((item) => !item || typeof item.unanimous !== 'boolean' || !counts.some(row => row.k === item.k)) ||
69
+ new Set(group.outcomes.map((item) => item.k)).size !== counts.length)
70
+ refuse('complete paired size outcomes required');
71
+ }
72
+ groups = [...curve.groups].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
73
+ for (const row of counts)
74
+ if (row.trials !== groups.length || row.unanimous !== groups.filter(group => group.outcomes.find(item => item.k === row.k).unanimous).length)
75
+ refuse('group outcomes do not reproduce counts');
76
+ }
77
+ const causes = [];
78
+ if (counts.filter(row => row.k > 1).length < 2)
79
+ causes.push('too few informative sizes');
80
+ if (counts.every(row => row.unanimous === row.trials))
81
+ causes.push('perfect unanimity cannot distinguish p=0 from c=1');
82
+ if (curve.n === 2 && compatible.some(slice => compatible.some(other => compare(add([BigInt(slice.p.numerator), BigInt(slice.p.denominator)], [BigInt(other.p.numerator), BigInt(other.p.denominator)]), one) === 0 &&
83
+ slice.p.numerator + '/' + slice.p.denominator !== other.p.numerator + '/' + other.p.denominator)))
84
+ causes.push('binary p versus 1-p ambiguity');
85
+ if (!compatible.length)
86
+ causes.push('no compatible parameter slice under the declared tolerance');
87
+ const replicas = [], wordsLimit = bootstrap.randomWords;
88
+ let x = bootstrap.seed >>> 0, words = 0;
89
+ const exhausted = Symbol('unanimity-fit/random-words');
90
+ function index(N) {
91
+ const limit = Math.floor(4294967296 / N) * N;
92
+ for (;;) {
93
+ if (words >= wordsLimit)
94
+ throw exhausted;
95
+ words++;
96
+ x ^= x << 13;
97
+ x ^= x >>> 17;
98
+ x ^= x << 5;
99
+ const value = x >>> 0;
100
+ if (value < limit)
101
+ return value % N;
102
+ }
103
+ }
104
+ let uncertaintyCause;
105
+ if (!groups || bootstrap.B === 0)
106
+ uncertaintyCause = groups ? 'bootstrap not requested' : 'sampling groups not handed; size dependence unknown';
107
+ else {
108
+ if (groups.length > 4294967296)
109
+ refuse('sampling population exceeds declared PRNG index range');
110
+ try {
111
+ for (let b = 0; b < bootstrap.B; b++) {
112
+ const hits = counts.map(() => 0);
113
+ for (let i = 0; i < groups.length; i++) {
114
+ const group = groups[index(groups.length)];
115
+ counts.forEach((row, j) => { if (group.outcomes.find(item => item.k === row.k).unanimous)
116
+ hits[j]++; });
117
+ }
118
+ replicas.push(slices(counts.map((row, i) => ({ ...row, unanimous: hits[i] })), curve.n, grid, tolerance));
119
+ }
120
+ }
121
+ catch (error) {
122
+ if (error !== exhausted)
123
+ throw error;
124
+ uncertaintyCause = 'unanimity-fit/random-words';
125
+ }
126
+ }
127
+ return { ...metadata, status: causes.length ? 'unmeasured' : 'measured',
128
+ ...(causes.length ? { cause: causes.join('; ') } : {}), compatible,
129
+ identification: { status: 'unmeasured', cause: 'finite slices do not certify continuous parameter identification', diagnostics: causes },
130
+ uncertainty: uncertaintyCause ? { status: 'unmeasured', cause: uncertaintyCause, words } :
131
+ { status: 'measured', procedure: 'paired-group-bootstrap-regions@1', replicas, words,
132
+ interpretation: 'empirical replicate regions; not a guaranteed confidence set' } };
133
+ }
@@ -0,0 +1,28 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 178" width="640" height="178" role="img" aria-labelledby="title desc">
2
+ <title id="title">A cell</title>
3
+ <desc id="desc">Two origins on one scale: a claims 2 to 9 and b claims 5 to 12; what they hold together runs from a floor of 5 to a ceiling of 9.</desc>
4
+ <style>
5
+ text{font:14px ui-sans-serif,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;fill:#59636e}
6
+ .claim{fill:#8c959f}.held{fill:#2da44e}.pole{fill:#2da44e;font-weight:600}.axis,.tick{stroke:#8c959f;stroke-width:1}.touch{stroke:#2da44e;stroke-width:1;stroke-dasharray:3 3}
7
+ @media (prefers-color-scheme:dark){text{fill:#9198a1}}
8
+ </style>
9
+ <line class="touch" x1="269.3" y1="12" x2="269.3" y2="126"/>
10
+ <line class="touch" x1="442.7" y1="12" x2="442.7" y2="126"/>
11
+ <text x="82" y="32" text-anchor="end">a</text>
12
+ <rect class="claim" x="139.3" y="18" width="303.4" height="18" rx="4"/>
13
+ <text x="82" y="66" text-anchor="end">b</text>
14
+ <rect class="claim" x="269.3" y="52" width="303.4" height="18" rx="4"/>
15
+ <text x="82" y="100" text-anchor="end">held</text>
16
+ <rect class="held" x="269.3" y="86" width="173.4" height="18" rx="4"/>
17
+ <line class="axis" x1="96" y1="126" x2="616" y2="126"/>
18
+ <line class="tick" x1="139.3" y1="126" x2="139.3" y2="131"/>
19
+ <text x="139.3" y="148" text-anchor="middle">2</text>
20
+ <line class="tick" x1="269.3" y1="126" x2="269.3" y2="131"/>
21
+ <text x="269.3" y="148" text-anchor="middle">5</text>
22
+ <line class="tick" x1="442.7" y1="126" x2="442.7" y2="131"/>
23
+ <text x="442.7" y="148" text-anchor="middle">9</text>
24
+ <line class="tick" x1="572.7" y1="126" x2="572.7" y2="131"/>
25
+ <text x="572.7" y="148" text-anchor="middle">12</text>
26
+ <text class="pole" x="269.3" y="168" text-anchor="middle">floor</text>
27
+ <text class="pole" x="442.7" y="168" text-anchor="middle">ceiling</text>
28
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-labelledby="title">
2
+ <title id="title">2 origins and the bond where they touch</title>
3
+ <path d="M32 17.58 A17 17 0 0 1 32 46.42 A17 17 0 0 1 32 17.58 Z" fill="#cf222e"/>
4
+ <path d="M32 17.58 A17 17 0 1 0 32 46.42" fill="none" stroke="#8c959f" stroke-width="4" stroke-linecap="round"/>
5
+ <path d="M32 17.58 A17 17 0 1 1 32 46.42" fill="none" stroke="#8c959f" stroke-width="4" stroke-linecap="round"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-labelledby="title">
2
+ <title id="title">2 origins and the bond where they touch</title>
3
+ <path d="M32 17.58 A17 17 0 0 1 32 46.42 A17 17 0 0 1 32 17.58 Z" fill="#2da44e"/>
4
+ <path d="M32 17.58 A17 17 0 1 0 32 46.42" fill="none" stroke="#8c959f" stroke-width="4" stroke-linecap="round"/>
5
+ <path d="M32 17.58 A17 17 0 1 1 32 46.42" fill="none" stroke="#8c959f" stroke-width="4" stroke-linecap="round"/>
6
+ </svg>
@@ -0,0 +1,28 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 178" width="640" height="178" role="img" aria-labelledby="title desc">
2
+ <title id="title">A cell</title>
3
+ <desc id="desc">Two origins on one scale: seller claims 2 to 9 and buyer claims 5 to 12; what they hold together runs from a floor of 5 to a ceiling of 9.</desc>
4
+ <style>
5
+ text{font:14px ui-sans-serif,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;fill:#59636e}
6
+ .claim{fill:#8c959f}.held{fill:#2da44e}.pole{fill:#2da44e;font-weight:600}.axis,.tick{stroke:#8c959f;stroke-width:1}.touch{stroke:#2da44e;stroke-width:1;stroke-dasharray:3 3}
7
+ @media (prefers-color-scheme:dark){text{fill:#9198a1}}
8
+ </style>
9
+ <line class="touch" x1="269.3" y1="12" x2="269.3" y2="126"/>
10
+ <line class="touch" x1="442.7" y1="12" x2="442.7" y2="126"/>
11
+ <text x="82" y="32" text-anchor="end">seller</text>
12
+ <rect class="claim" x="139.3" y="18" width="303.4" height="18" rx="4"/>
13
+ <text x="82" y="66" text-anchor="end">buyer</text>
14
+ <rect class="claim" x="269.3" y="52" width="303.4" height="18" rx="4"/>
15
+ <text x="82" y="100" text-anchor="end">held</text>
16
+ <rect class="held" x="269.3" y="86" width="173.4" height="18" rx="4"/>
17
+ <line class="axis" x1="96" y1="126" x2="616" y2="126"/>
18
+ <line class="tick" x1="139.3" y1="126" x2="139.3" y2="131"/>
19
+ <text x="139.3" y="148" text-anchor="middle">2</text>
20
+ <line class="tick" x1="269.3" y1="126" x2="269.3" y2="131"/>
21
+ <text x="269.3" y="148" text-anchor="middle">5</text>
22
+ <line class="tick" x1="442.7" y1="126" x2="442.7" y2="131"/>
23
+ <text x="442.7" y="148" text-anchor="middle">9</text>
24
+ <line class="tick" x1="572.7" y1="126" x2="572.7" y2="131"/>
25
+ <text x="572.7" y="148" text-anchor="middle">12</text>
26
+ <text class="pole" x="269.3" y="168" text-anchor="middle">floor</text>
27
+ <text class="pole" x="442.7" y="168" text-anchor="middle">ceiling</text>
28
+ </svg>
@@ -0,0 +1,28 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 228" width="640" height="228" role="img" aria-labelledby="title desc">
2
+ <title id="title">A cell</title>
3
+ <desc id="desc">Four origins on one scale: a claims 19.8 to 20.2, b claims 19.9 to 20.3, c claims 19.7 to 20.1 and d claims 24 to 24.4; their claims do not meet.</desc>
4
+ <style>
5
+ text{font:14px ui-sans-serif,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;fill:#59636e}
6
+ .claim{fill:#8c959f}.held{fill:#2da44e}.pole{fill:#2da44e;font-weight:600}.axis,.tick{stroke:#8c959f;stroke-width:1}.touch{stroke:#2da44e;stroke-width:1;stroke-dasharray:3 3}
7
+ @media (prefers-color-scheme:dark){text{fill:#9198a1}}
8
+ </style>
9
+ <text x="82" y="32" text-anchor="end">a</text>
10
+ <rect class="claim" x="181.4" y="18" width="31" height="18" rx="4"/>
11
+ <text x="82" y="66" text-anchor="end">b</text>
12
+ <rect class="claim" x="189.1" y="52" width="31.1" height="18" rx="4"/>
13
+ <text x="82" y="100" text-anchor="end">c</text>
14
+ <rect class="claim" x="173.6" y="86" width="31.1" height="18" rx="4"/>
15
+ <text x="82" y="134" text-anchor="end">d</text>
16
+ <rect class="claim" x="507.3" y="120" width="31.1" height="18" rx="4"/>
17
+ <text x="82" y="168" text-anchor="end">held</text>
18
+ <text x="96" y="168">their claims do not meet</text>
19
+ <line class="axis" x1="96" y1="194" x2="616" y2="194"/>
20
+ <line class="tick" x1="173.6" y1="194" x2="173.6" y2="199"/>
21
+ <text x="173.6" y="216" text-anchor="middle">19.7</text>
22
+ <line class="tick" x1="204.7" y1="194" x2="204.7" y2="199"/>
23
+ <text x="204.7" y="216" text-anchor="middle">20.1</text>
24
+ <line class="tick" x1="507.3" y1="194" x2="507.3" y2="199"/>
25
+ <text x="507.3" y="216" text-anchor="middle">24</text>
26
+ <line class="tick" x1="538.4" y1="194" x2="538.4" y2="199"/>
27
+ <text x="538.4" y="216" text-anchor="middle">24.4</text>
28
+ </svg>