@lapxo/obligations 0.4.0 → 0.4.2
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 +32 -2
- package/TARGET.bound +151 -30
- package/dist/lattice/obligatory.js +29 -1
- package/dist/measures/capacity.d.ts +324 -0
- package/dist/measures/capacity.js +184 -0
- package/dist/measures/coincidence.d.ts +111 -0
- package/dist/measures/coincidence.js +81 -0
- package/dist/measures/confidence.d.ts +73 -0
- package/dist/measures/confidence.js +52 -0
- package/dist/measures/error-events.js +23 -4
- package/dist/measures/fitting.d.ts +1 -1
- package/dist/measures/index.d.ts +8 -0
- package/dist/measures/index.js +4 -0
- package/dist/measures/unanimity-fit.d.ts +156 -0
- package/dist/measures/unanimity-fit.js +133 -0
- package/docs/img/cell.svg +28 -0
- package/docs/img/logo-fork.svg +6 -0
- package/docs/img/logo.svg +6 -0
- package/docs/img/price.svg +28 -0
- package/docs/img/thermometer.svg +28 -0
- package/docs/img/withdrawal.svg +28 -0
- package/docs/learn/in-phase.md +46 -0
- package/docs/learn/price.md +52 -0
- package/docs/learn/signature.md +50 -0
- package/docs/learn/thermometer.md +58 -0
- package/docs/learn/withdrawal.md +51 -0
- package/docs/measures.md +64 -0
- package/docs/paper.md +233 -0
- package/docs/reference.md +495 -0
- package/package.json +1 -1
- package/receipts.bound +138 -70
- package/samples/measures/capacity.json +71 -0
- package/samples/measures/coincidence.json +624 -0
- package/samples/measures/common-cause-capacity.json +48 -0
- package/samples/measures/confidence/union-bound.json +54 -0
- package/samples/measures/confidence.json +70 -0
- package/samples/measures/decisions.json +66 -0
- package/samples/measures/t12/common-cause.json +32 -0
- package/samples/measures/tolerant-count.json +144 -0
- package/samples/measures/unanimity-fit.json +166 -0
- package/samples/no/installed-artifact.json +19 -3
- package/samples/yes/installed-artifact.json +42 -6
- package/vectors/measures/capacity.cli.json +97 -0
- package/vectors/measures/coincidence.cli.json +97 -0
- package/vectors/measures/common-cause-capacity.cli.json +97 -0
- package/vectors/measures/confidence/expected.json +26 -0
- package/vectors/measures/confidence.cli.json +97 -0
- package/vectors/measures/decisions.cli.json +97 -0
- package/vectors/measures/descent.cli.json +19 -70
- package/vectors/measures/implementation-042.json +86 -0
- package/vectors/measures/implementation.json +82 -0
- package/vectors/measures/installed-artifact.cli.json +8 -8
- package/vectors/measures/m3.cli.json +19 -70
- package/vectors/measures/observations.cli.json +19 -70
- package/vectors/measures/spectrum.cli.json +18 -69
- package/vectors/measures/t12/expected.json +35 -0
- package/vectors/measures/tolerant-count.cli.json +97 -0
- package/vectors/measures/unanimity-fit.cli.json +97 -0
- package/vectors/measures/unanimity.cli.json +19 -70
- package/samples/measures/m3-reader.mjs +0 -732
- package/samples/measures/reader.mjs +0 -424
|
@@ -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,73 @@
|
|
|
1
|
+
import { type RationalInput } from './rational.ts';
|
|
2
|
+
export type DeclaredFailure = Readonly<{
|
|
3
|
+
id: string;
|
|
4
|
+
p: RationalInput;
|
|
5
|
+
justification: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function unionBoundConfidence(failures: readonly DeclaredFailure[]): {
|
|
8
|
+
model: "declared-failure-union-bound@1";
|
|
9
|
+
status: "conditional-bound";
|
|
10
|
+
unit: "probability";
|
|
11
|
+
conditions: readonly ["common-probability-space", "each-failure-probability-justified"];
|
|
12
|
+
declarations: {
|
|
13
|
+
id: string;
|
|
14
|
+
p: Readonly<{
|
|
15
|
+
numerator: string;
|
|
16
|
+
denominator: string;
|
|
17
|
+
}>;
|
|
18
|
+
justification: string;
|
|
19
|
+
}[];
|
|
20
|
+
failureSum: Readonly<{
|
|
21
|
+
numerator: string;
|
|
22
|
+
denominator: string;
|
|
23
|
+
}>;
|
|
24
|
+
value: Readonly<{
|
|
25
|
+
numerator: string;
|
|
26
|
+
denominator: string;
|
|
27
|
+
}>;
|
|
28
|
+
trivial: boolean;
|
|
29
|
+
meaning: "lower bound for all declared events succeeding; references are not verified";
|
|
30
|
+
};
|
|
31
|
+
export declare function tolerantCountConfidence(failures: readonly DeclaredFailure[], k: number): {
|
|
32
|
+
model: "declared-failure-count-bound@1";
|
|
33
|
+
allowedFailures: number;
|
|
34
|
+
totalDeclarations: number;
|
|
35
|
+
exclusionOrder: string[];
|
|
36
|
+
candidates: {
|
|
37
|
+
excludedCount: number;
|
|
38
|
+
threshold: number;
|
|
39
|
+
remainingFailureSum: Readonly<{
|
|
40
|
+
numerator: string;
|
|
41
|
+
denominator: string;
|
|
42
|
+
}>;
|
|
43
|
+
failureUpperBound: Readonly<{
|
|
44
|
+
numerator: string;
|
|
45
|
+
denominator: string;
|
|
46
|
+
}>;
|
|
47
|
+
}[];
|
|
48
|
+
failureUpperBound: Readonly<{
|
|
49
|
+
numerator: string;
|
|
50
|
+
denominator: string;
|
|
51
|
+
}>;
|
|
52
|
+
value: Readonly<{
|
|
53
|
+
numerator: string;
|
|
54
|
+
denominator: string;
|
|
55
|
+
}>;
|
|
56
|
+
trivial: boolean;
|
|
57
|
+
meaning: "lower bound for at most k declared failures; references are not verified";
|
|
58
|
+
status: "conditional-bound";
|
|
59
|
+
unit: "probability";
|
|
60
|
+
conditions: readonly ["common-probability-space", "each-failure-probability-justified"];
|
|
61
|
+
declarations: {
|
|
62
|
+
id: string;
|
|
63
|
+
p: Readonly<{
|
|
64
|
+
numerator: string;
|
|
65
|
+
denominator: string;
|
|
66
|
+
}>;
|
|
67
|
+
justification: string;
|
|
68
|
+
}[];
|
|
69
|
+
failureSum: Readonly<{
|
|
70
|
+
numerator: string;
|
|
71
|
+
denominator: string;
|
|
72
|
+
}>;
|
|
73
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { add, sub, div, q, rational, zero, one, pack, probability } from "./rational.js";
|
|
2
|
+
const refuse = (cause) => { throw new Error('REFUSE·confidence ' + cause); };
|
|
3
|
+
export function unionBoundConfidence(failures) {
|
|
4
|
+
if (!Array.isArray(failures))
|
|
5
|
+
refuse('declared failures required');
|
|
6
|
+
let sum = zero;
|
|
7
|
+
const names = new Set();
|
|
8
|
+
const declarations = Array.from(failures, failure => {
|
|
9
|
+
if (!failure || typeof failure.id !== 'string' || failure.id.trim() === '')
|
|
10
|
+
refuse('failure identity required');
|
|
11
|
+
if (names.has(failure.id))
|
|
12
|
+
refuse('duplicate failure identity');
|
|
13
|
+
names.add(failure.id);
|
|
14
|
+
if (typeof failure.justification !== 'string' || failure.justification.trim() === '')
|
|
15
|
+
refuse('justification reference required');
|
|
16
|
+
if (failure.p === undefined || failure.p === null)
|
|
17
|
+
refuse('declared failure probability required');
|
|
18
|
+
const p = probability(failure.p);
|
|
19
|
+
sum = add(sum, p);
|
|
20
|
+
return { id: failure.id, p: pack(p), justification: failure.justification };
|
|
21
|
+
});
|
|
22
|
+
const trivial = sum[0] >= sum[1];
|
|
23
|
+
return { model: 'declared-failure-union-bound@1', status: 'conditional-bound',
|
|
24
|
+
unit: 'probability', conditions: ['common-probability-space', 'each-failure-probability-justified'],
|
|
25
|
+
declarations, failureSum: pack(sum), value: pack(trivial ? zero : sub(one, sum)), trivial,
|
|
26
|
+
meaning: 'lower bound for all declared events succeeding; references are not verified' };
|
|
27
|
+
}
|
|
28
|
+
export function tolerantCountConfidence(failures, k) {
|
|
29
|
+
const base = unionBoundConfidence(failures), m = base.declarations.length;
|
|
30
|
+
if (!Number.isSafeInteger(k) || k < 0 || k > m)
|
|
31
|
+
refuse('allowed failure count must be an integer in 0..m');
|
|
32
|
+
const ordered = base.declarations.map(entry => ({ entry, p: probability(entry.p) })).sort((a, b) => {
|
|
33
|
+
const difference = a.p[0] * b.p[1] - b.p[0] * a.p[1];
|
|
34
|
+
return difference > 0n ? -1 : difference < 0n ? 1 : 0;
|
|
35
|
+
});
|
|
36
|
+
let remaining = rational(base.failureSum), upper = k === m ? zero : one;
|
|
37
|
+
const candidates = [];
|
|
38
|
+
if (k < m)
|
|
39
|
+
for (let j = 0; j <= k; j++) {
|
|
40
|
+
const threshold = k + 1 - j, ratio = div(remaining, q(BigInt(threshold)));
|
|
41
|
+
const capped = ratio[0] >= ratio[1] ? one : ratio;
|
|
42
|
+
if (capped[0] * upper[1] < upper[0] * capped[1])
|
|
43
|
+
upper = capped;
|
|
44
|
+
candidates.push({ excludedCount: j, threshold, remainingFailureSum: pack(remaining), failureUpperBound: pack(capped) });
|
|
45
|
+
if (j < k)
|
|
46
|
+
remaining = sub(remaining, ordered[j].p);
|
|
47
|
+
}
|
|
48
|
+
return { ...base, model: 'declared-failure-count-bound@1', allowedFailures: k,
|
|
49
|
+
totalDeclarations: m, exclusionOrder: ordered.map(({ entry }) => entry.id), candidates,
|
|
50
|
+
failureUpperBound: pack(upper), value: pack(sub(one, upper)), trivial: upper[0] === upper[1],
|
|
51
|
+
meaning: 'lower bound for at most k declared failures; references are not verified' };
|
|
52
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export function errorMatrix(rows, policy, requested) {
|
|
2
|
-
if (!Number.isSafeInteger(policy.minCells) || policy.minCells < 2)
|
|
2
|
+
if (!policy || !Number.isSafeInteger(policy.minCells) || policy.minCells < 2)
|
|
3
3
|
throw new Error('REFUSE·measure invalid minimum cells');
|
|
4
|
+
if (!Array.isArray(rows) || (requested !== undefined && !Array.isArray(requested)))
|
|
5
|
+
throw new Error('REFUSE·measure invalid event population');
|
|
4
6
|
const cells = new Map();
|
|
5
7
|
for (const row of rows) {
|
|
6
|
-
if (!row.event || !row.origin || (row.error !== 0 && row.error !== 1))
|
|
8
|
+
if (!row || typeof row.event !== 'string' || !row.event || typeof row.origin !== 'string' || !row.origin || (row.error !== 0 && row.error !== 1))
|
|
7
9
|
throw new Error('REFUSE·measure invalid error event');
|
|
8
10
|
const at = cells.get(row.event) ?? new Map();
|
|
9
11
|
if (at.has(row.origin) && at.get(row.origin) !== row.error)
|
|
@@ -12,7 +14,7 @@ export function errorMatrix(rows, policy, requested) {
|
|
|
12
14
|
cells.set(row.event, at);
|
|
13
15
|
}
|
|
14
16
|
const origins = [...new Set(requested ?? rows.map(row => row.origin))].sort();
|
|
15
|
-
if (origins.some(origin => !origin))
|
|
17
|
+
if (origins.some(origin => typeof origin !== 'string' || !origin))
|
|
16
18
|
throw new Error('REFUSE·measure invalid origin');
|
|
17
19
|
const events = [...cells.keys()].filter(event => origins.some(origin => cells.get(event).has(origin))).sort();
|
|
18
20
|
if (events.length < policy.minCells || !origins.length)
|
|
@@ -22,10 +24,15 @@ export function errorMatrix(rows, policy, requested) {
|
|
|
22
24
|
return { status: 'measured', origins, events, matrix: events.map(event => origins.map(origin => cells.get(event).get(origin))) };
|
|
23
25
|
}
|
|
24
26
|
export function covariance(reading) {
|
|
27
|
+
if (!reading || (reading.status !== 'measured' && reading.status !== 'unmeasured'))
|
|
28
|
+
throw new Error('REFUSE·measure invalid error matrix reading');
|
|
25
29
|
if (reading.status === 'unmeasured')
|
|
26
30
|
return reading;
|
|
31
|
+
if (!Array.isArray(reading.events) || !Array.isArray(reading.origins) || !Array.isArray(reading.matrix))
|
|
32
|
+
throw new Error('REFUSE·measure invalid error matrix');
|
|
27
33
|
const n = reading.events.length, m = reading.origins.length;
|
|
28
|
-
if (!n || !m || reading.
|
|
34
|
+
if (!n || !m || reading.events.some(event => typeof event !== 'string' || !event) || reading.origins.some(origin => typeof origin !== 'string' || !origin) ||
|
|
35
|
+
reading.matrix.length !== n || reading.matrix.some(row => !Array.isArray(row) || row.length !== m || row.some(x => x !== 0 && x !== 1)) || new Set(reading.events).size !== n || new Set(reading.origins).size !== m)
|
|
29
36
|
throw new Error('REFUSE·measure invalid error matrix');
|
|
30
37
|
const mean = reading.origins.map((_, i) => reading.matrix.reduce((sum, row) => sum + row[i], 0) / n);
|
|
31
38
|
const matrix = Array.from({ length: m }, (_, i) => Array.from({ length: m }, (_, j) => reading.matrix.reduce((sum, row) => sum + (row[i] - mean[i]) * (row[j] - mean[j]), 0) / n));
|
|
@@ -33,8 +40,20 @@ export function covariance(reading) {
|
|
|
33
40
|
}
|
|
34
41
|
export const errorCovariance = (rows, policy) => covariance(errorMatrix(rows, policy));
|
|
35
42
|
export function participationRatio(reading) {
|
|
43
|
+
if (!reading || (reading.status !== 'measured' && reading.status !== 'unmeasured'))
|
|
44
|
+
throw new Error('REFUSE·measure invalid covariance reading');
|
|
36
45
|
if (reading.status === 'unmeasured')
|
|
37
46
|
return reading;
|
|
47
|
+
const n = reading.origins?.length;
|
|
48
|
+
if (!n || !Array.isArray(reading.matrix) || reading.matrix.length !== n || reading.matrix.some(row => !Array.isArray(row) || row.length !== n || row.some(value => !Number.isFinite(value))))
|
|
49
|
+
throw new Error('REFUSE·measure invalid covariance matrix');
|
|
50
|
+
for (let i = 0; i < n; i++) {
|
|
51
|
+
if (reading.matrix[i][i] < 0)
|
|
52
|
+
throw new Error('REFUSE·measure negative covariance diagonal');
|
|
53
|
+
for (let j = 0; j < n; j++)
|
|
54
|
+
if (reading.matrix[i][j] !== reading.matrix[j][i])
|
|
55
|
+
throw new Error('REFUSE·measure nonsymmetric covariance');
|
|
56
|
+
}
|
|
38
57
|
const trace = reading.matrix.reduce((sum, row, i) => sum + row[i], 0);
|
|
39
58
|
const square = reading.matrix.reduce((sum, row) => sum + row.reduce((n, value) => n + value * value, 0), 0);
|
|
40
59
|
if (square === 0)
|
|
@@ -165,7 +165,7 @@ export declare function fitM3(rows: readonly ErrorEvent[], structure: FitStructu
|
|
|
165
165
|
procedure: "constrained-log-least-squares/active-set@1";
|
|
166
166
|
} | {
|
|
167
167
|
bootstrap: {
|
|
168
|
-
status: "
|
|
168
|
+
status: "measured" | "unmeasured";
|
|
169
169
|
cause: string | undefined;
|
|
170
170
|
words: number;
|
|
171
171
|
replicates: ({
|
package/dist/measures/index.d.ts
CHANGED
|
@@ -16,3 +16,11 @@ export { experimentM3 } from './m3-experiment.ts';
|
|
|
16
16
|
export type { M3ExperimentPolicy } from './m3-experiment.ts';
|
|
17
17
|
export { fitM3 } from './fitting.ts';
|
|
18
18
|
export type { FitStructure, FitPolicy } from './fitting.ts';
|
|
19
|
+
export { commonCauseCapacityBound, capacityBound, capacityP0, mutualInformation, unanimityNoFactor, decisionInformation } from './capacity.ts';
|
|
20
|
+
export type { CapacityModel, LogarithmPolicy, CapacityPolicy, DecisionRule } from './capacity.ts';
|
|
21
|
+
export { fitUnanimityModel } from './unanimity-fit.ts';
|
|
22
|
+
export type { UnanimityCount, UnanimityGroup, UnanimityCurve, UnanimityFitPolicy } from './unanimity-fit.ts';
|
|
23
|
+
export { coincidenceExcess } from './coincidence.ts';
|
|
24
|
+
export type { ValueReading, TruthReading, CoincidenceReference, CoincidencePolicy } from './coincidence.ts';
|
|
25
|
+
export { unionBoundConfidence, tolerantCountConfidence } from './confidence.ts';
|
|
26
|
+
export type { DeclaredFailure } from './confidence.ts';
|
package/dist/measures/index.js
CHANGED
|
@@ -7,3 +7,7 @@ export { roundHalfEven } from "./rational.js";
|
|
|
7
7
|
export { identifyM3 } from "./identification.js";
|
|
8
8
|
export { experimentM3 } from "./m3-experiment.js";
|
|
9
9
|
export { fitM3 } from "./fitting.js";
|
|
10
|
+
export { commonCauseCapacityBound, capacityBound, capacityP0, mutualInformation, unanimityNoFactor, decisionInformation } from "./capacity.js";
|
|
11
|
+
export { fitUnanimityModel } from "./unanimity-fit.js";
|
|
12
|
+
export { coincidenceExcess } from "./coincidence.js";
|
|
13
|
+
export { unionBoundConfidence, tolerantCountConfidence } from "./confidence.js";
|
|
@@ -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>
|