@lapxo/obligations 0.3.4 → 0.4.0
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 +12 -135
- package/TARGET.bound +574 -503
- package/dist/measures/error-events.d.ts +27 -0
- package/dist/measures/error-events.js +43 -0
- package/dist/measures/experiments.d.ts +15 -0
- package/dist/measures/experiments.js +53 -0
- package/dist/measures/fitting.d.ts +243 -0
- package/dist/measures/fitting.js +115 -0
- package/dist/measures/identification.d.ts +156 -0
- package/dist/measures/identification.js +107 -0
- package/dist/measures/index.d.ts +18 -0
- package/dist/measures/index.js +9 -0
- package/dist/measures/m3-experiment.d.ts +69 -0
- package/dist/measures/m3-experiment.js +70 -0
- package/dist/measures/m3.d.ts +139 -0
- package/dist/measures/m3.js +181 -0
- package/dist/measures/model.d.ts +14 -0
- package/dist/measures/model.js +67 -0
- package/dist/measures/rational.d.ts +19 -0
- package/dist/measures/rational.js +53 -0
- package/dist/measures/spectrum.d.ts +22 -0
- package/dist/measures/spectrum.js +84 -0
- package/docs/measures.md +149 -0
- package/package.json +1 -93
- package/receipts.bound +75 -0
- package/samples/measures/descent.json +92 -0
- package/samples/measures/m3-reader.mjs +732 -0
- package/samples/measures/m3.json +3126 -0
- package/samples/measures/observations.json +212 -0
- package/samples/measures/reader.mjs +424 -0
- package/samples/measures/spectrum.json +6530 -0
- package/samples/measures/unanimity.json +76 -0
- package/samples/no/installed-artifact.json +302 -0
- package/samples/yes/algebra-is-closed.json +17 -0
- package/samples/yes/field-uses-price-and-debit.json +9 -0
- package/samples/yes/installed-artifact.json +397 -0
- package/samples/yes/three-theories-one-algebra.json +5 -0
- package/src/field/motion.ts +59 -0
- package/src/lattice/index.ts +79 -0
- package/vectors/measures/descent.cli.json +148 -0
- package/vectors/measures/installed-artifact.cli.json +43 -0
- package/vectors/measures/m3.cli.json +148 -0
- package/vectors/measures/observations.cli.json +148 -0
- package/vectors/measures/spectrum.cli.json +148 -0
- package/vectors/measures/unanimity.cli.json +148 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { add, sub, mul, div, q, zero, one, equal, pack, probability, refuse } from "./rational.js";
|
|
2
|
+
const key = (a, b) => JSON.stringify([a, b].sort());
|
|
3
|
+
const gcd = (a, b) => b ? gcd(b, a % b) : a;
|
|
4
|
+
const integerPower = (a, n) => n < 0n ? q(a[1] ** (-n), a[0] ** (-n)) : q(a[0] ** n, a[1] ** n);
|
|
5
|
+
function exactRoot(a, n) {
|
|
6
|
+
if (a === 0n || a === 1n || n === 1n)
|
|
7
|
+
return a;
|
|
8
|
+
if (n > BigInt(a.toString(2).length))
|
|
9
|
+
return undefined;
|
|
10
|
+
let low = 1n, high = a;
|
|
11
|
+
while (low <= high) {
|
|
12
|
+
const middle = (low + high) / 2n, p = middle ** n;
|
|
13
|
+
if (p === a)
|
|
14
|
+
return middle;
|
|
15
|
+
if (p < a)
|
|
16
|
+
low = middle + 1n;
|
|
17
|
+
else
|
|
18
|
+
high = middle - 1n;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function monomial(bases, powers) {
|
|
23
|
+
let degree = 1n;
|
|
24
|
+
for (const p of powers)
|
|
25
|
+
degree = degree / gcd(degree, p[1]) * p[1];
|
|
26
|
+
let radicand = one;
|
|
27
|
+
for (let i = 0; i < bases.length; i++)
|
|
28
|
+
radicand = mul(radicand, integerPower(bases[i], powers[i][0] * (degree / powers[i][1])));
|
|
29
|
+
const a = exactRoot(radicand[0], degree), b = exactRoot(radicand[1], degree);
|
|
30
|
+
return { radicand: pack(radicand), degree: String(degree), rational: a === undefined || b === undefined ? undefined : pack(q(a, b)) };
|
|
31
|
+
}
|
|
32
|
+
export function identifyM3(input, policy) {
|
|
33
|
+
if (!input || !Array.isArray(input.witnesses) || !Array.isArray(input.factors) || !Array.isArray(input.pairs) || !policy || !/^\d+$/.test(policy.budget))
|
|
34
|
+
refuse('invalid identification inputs');
|
|
35
|
+
const ids = input.witnesses.map(w => w?.id);
|
|
36
|
+
if (!ids.length || ids.some(id => typeof id !== 'string' || !id) || new Set(ids).size !== ids.length)
|
|
37
|
+
refuse('invalid identification witnesses');
|
|
38
|
+
if (input.factors.some(f => !f || typeof f.id !== 'string' || !f.id || !Array.isArray(f.members) || new Set(f.members).size !== f.members.length || f.members.some(id => !ids.includes(id))) || new Set(input.factors.map(f => f.id)).size !== input.factors.length)
|
|
39
|
+
refuse('invalid declared sharing structure');
|
|
40
|
+
const s = input.witnesses.map(w => probability(w.correct));
|
|
41
|
+
const shared = input.factors.filter(f => f.members.length > 1), rows = input.pairs.length, columns = shared.length;
|
|
42
|
+
const metadata = { model: 'M/3', parameters: input, conditions: ['population-moments', 'declared-sharing-sets', 'positive-correctness', 'full-column-rank', 'singleton-private-unresolved'], policy };
|
|
43
|
+
if (s.some(p => p[0] === 0n))
|
|
44
|
+
return { ...metadata, status: 'unmeasured', cause: 'zero correctness; unidentified' };
|
|
45
|
+
const seen = new Set(), bases = [], matrix = [];
|
|
46
|
+
for (const pair of input.pairs) {
|
|
47
|
+
if (!pair || pair.a === pair.b || !ids.includes(pair.a) || !ids.includes(pair.b) || seen.has(key(pair.a, pair.b)))
|
|
48
|
+
refuse('invalid identification pair');
|
|
49
|
+
seen.add(key(pair.a, pair.b));
|
|
50
|
+
const a = s[ids.indexOf(pair.a)], b = s[ids.indexOf(pair.b)], joint = probability(pair.correctTogether);
|
|
51
|
+
if (joint[0] === 0n || sub(joint, a)[0] > 0n || sub(joint, b)[0] > 0n || sub(joint, sub(add(a, b), one))[0] < 0n)
|
|
52
|
+
return { ...metadata, status: 'unmeasured', cause: 'model/M misfit' };
|
|
53
|
+
bases.push(div(joint, mul(a, b)));
|
|
54
|
+
matrix.push(shared.map(f => f.members.includes(pair.a) && f.members.includes(pair.b) ? one : zero));
|
|
55
|
+
}
|
|
56
|
+
const work = BigInt(rows) * BigInt(columns) * BigInt(rows + columns + 1);
|
|
57
|
+
if (work > BigInt(policy.budget))
|
|
58
|
+
return { ...metadata, status: 'unmeasured', cause: 'identification/budget', required: String(work) };
|
|
59
|
+
const transform = matrix.map((_, i) => matrix.map((__, j) => i === j ? one : zero));
|
|
60
|
+
let rank = 0;
|
|
61
|
+
const pivots = [];
|
|
62
|
+
for (let column = 0; column < columns; column++) {
|
|
63
|
+
const pivot = matrix.findIndex((row, i) => i >= rank && row[column][0] !== 0n);
|
|
64
|
+
if (pivot < 0)
|
|
65
|
+
continue;
|
|
66
|
+
[matrix[rank], matrix[pivot]] = [matrix[pivot], matrix[rank]];
|
|
67
|
+
[transform[rank], transform[pivot]] = [transform[pivot], transform[rank]];
|
|
68
|
+
const divisor = matrix[rank][column];
|
|
69
|
+
matrix[rank] = matrix[rank].map(v => div(v, divisor));
|
|
70
|
+
transform[rank] = transform[rank].map(v => div(v, divisor));
|
|
71
|
+
for (let i = 0; i < rows; i++)
|
|
72
|
+
if (i !== rank) {
|
|
73
|
+
const amount = matrix[i][column];
|
|
74
|
+
matrix[i] = matrix[i].map((v, j) => sub(v, mul(amount, matrix[rank][j])));
|
|
75
|
+
transform[i] = transform[i].map((v, j) => sub(v, mul(amount, transform[rank][j])));
|
|
76
|
+
}
|
|
77
|
+
pivots.push(column);
|
|
78
|
+
rank++;
|
|
79
|
+
}
|
|
80
|
+
if (rank !== columns)
|
|
81
|
+
return { ...metadata, status: 'unmeasured', cause: 'rank-deficient declared sharing system', rank, columns };
|
|
82
|
+
for (let i = rank; i < rows; i++) {
|
|
83
|
+
const residual = monomial(bases, transform[i]);
|
|
84
|
+
if (!equal(q(BigInt(residual.radicand.numerator), BigInt(residual.radicand.denominator)), one))
|
|
85
|
+
return { ...metadata, status: 'unmeasured', cause: 'model/M misfit', rank, columns };
|
|
86
|
+
}
|
|
87
|
+
const factors = shared.map((factor, j) => {
|
|
88
|
+
const powers = transform[pivots.indexOf(j)], inverseSurvival = monomial(bases, powers);
|
|
89
|
+
const radicand = q(BigInt(inverseSurvival.radicand.numerator), BigInt(inverseSurvival.radicand.denominator));
|
|
90
|
+
if (sub(radicand, one)[0] < 0n)
|
|
91
|
+
return { id: factor.id, misfit: true };
|
|
92
|
+
const survival = monomial(bases, powers.map(p => q(-p[0], p[1])));
|
|
93
|
+
return { id: factor.id, survival, probability: survival.rational ? pack(sub(one, q(BigInt(survival.rational.numerator), BigInt(survival.rational.denominator)))) : undefined };
|
|
94
|
+
});
|
|
95
|
+
if (factors.some(f => 'misfit' in f))
|
|
96
|
+
return { ...metadata, status: 'unmeasured', cause: 'model/M misfit', rank, columns };
|
|
97
|
+
const residualSurvival = input.witnesses.map((w, i) => {
|
|
98
|
+
const powers = bases.map(() => zero);
|
|
99
|
+
shared.forEach((f, j) => { if (f.members.includes(w.id))
|
|
100
|
+
transform[pivots.indexOf(j)].forEach((p, k) => { powers[k] = add(powers[k], p); }); });
|
|
101
|
+
const r = monomial([s[i], ...bases], [one, ...powers]);
|
|
102
|
+
return { witness: w.id, r, privateSingletonDecomposition: 'unresolved' };
|
|
103
|
+
});
|
|
104
|
+
if (residualSurvival.some(w => sub(q(BigInt(w.r.radicand.numerator), BigInt(w.r.radicand.denominator)), one)[0] > 0n))
|
|
105
|
+
return { ...metadata, status: 'unmeasured', cause: 'model/M misfit', rank, columns };
|
|
106
|
+
return { ...metadata, status: 'identified', rank, columns, factors, residualSurvival, representation: 'positive exact rational roots' };
|
|
107
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { errorMatrix, covariance, errorCovariance, participationRatio } from './error-events.ts';
|
|
2
|
+
export type { ErrorEvent, ErrorMatrix, Covariance, Unmeasured, Scalar, ObservationPolicy } from './error-events.ts';
|
|
3
|
+
export { spectrum, commonMass, loadings, commonMassOf } from './spectrum.ts';
|
|
4
|
+
export type { Spectrum, NumericalPolicy } from './spectrum.ts';
|
|
5
|
+
export { unanimityFloor, unanimityThreshold, descentCount, modelRequired } from './model.ts';
|
|
6
|
+
export type { UnanimityModel, Descent } from './model.ts';
|
|
7
|
+
export { dissentExperiment, interestExperiment } from './experiments.ts';
|
|
8
|
+
export type { ExperimentPolicy, ConditionalRate } from './experiments.ts';
|
|
9
|
+
export { evaluateM3, probeUpdate, dissentCertificate } from './m3.ts';
|
|
10
|
+
export type { Witness, Factor, ModelM3, EvaluationPolicy, Conditional } from './m3.ts';
|
|
11
|
+
export { roundHalfEven } from './rational.ts';
|
|
12
|
+
export type { Rational, RationalInput } from './rational.ts';
|
|
13
|
+
export { identifyM3 } from './identification.ts';
|
|
14
|
+
export type { IdentificationInput, IdentificationPolicy } from './identification.ts';
|
|
15
|
+
export { experimentM3 } from './m3-experiment.ts';
|
|
16
|
+
export type { M3ExperimentPolicy } from './m3-experiment.ts';
|
|
17
|
+
export { fitM3 } from './fitting.ts';
|
|
18
|
+
export type { FitStructure, FitPolicy } from './fitting.ts';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { errorMatrix, covariance, errorCovariance, participationRatio } from "./error-events.js";
|
|
2
|
+
export { spectrum, commonMass, loadings, commonMassOf } from "./spectrum.js";
|
|
3
|
+
export { unanimityFloor, unanimityThreshold, descentCount, modelRequired } from "./model.js";
|
|
4
|
+
export { dissentExperiment, interestExperiment } from "./experiments.js";
|
|
5
|
+
export { evaluateM3, probeUpdate, dissentCertificate } from "./m3.js";
|
|
6
|
+
export { roundHalfEven } from "./rational.js";
|
|
7
|
+
export { identifyM3 } from "./identification.js";
|
|
8
|
+
export { experimentM3 } from "./m3-experiment.js";
|
|
9
|
+
export { fitM3 } from "./fitting.js";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ModelM3 } from './m3.ts';
|
|
2
|
+
export type M3ExperimentPolicy = Readonly<{
|
|
3
|
+
prng: 'xorshift32';
|
|
4
|
+
seed: number;
|
|
5
|
+
trials: number;
|
|
6
|
+
randomWords: number;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function experimentM3(model: ModelM3, policy: M3ExperimentPolicy): {
|
|
9
|
+
status: "unmeasured";
|
|
10
|
+
cause: string;
|
|
11
|
+
words: number;
|
|
12
|
+
model: "M/3";
|
|
13
|
+
parameters: Readonly<{
|
|
14
|
+
cell: string;
|
|
15
|
+
cardinality: number;
|
|
16
|
+
witnesses: readonly import("./m3.ts").Witness[];
|
|
17
|
+
factors: readonly import("./m3.ts").Factor[];
|
|
18
|
+
selection: readonly string[];
|
|
19
|
+
}>;
|
|
20
|
+
policy: Readonly<{
|
|
21
|
+
prng: "xorshift32";
|
|
22
|
+
seed: number;
|
|
23
|
+
trials: number;
|
|
24
|
+
randomWords: number;
|
|
25
|
+
}>;
|
|
26
|
+
conditions: readonly ["controlled-simulation", "declared-xorshift32-stream", "exact-rational-event-sampling", "not-a-law"];
|
|
27
|
+
} | {
|
|
28
|
+
status: "measured";
|
|
29
|
+
words: number;
|
|
30
|
+
counts: {
|
|
31
|
+
true: number;
|
|
32
|
+
false: number;
|
|
33
|
+
fired: number;
|
|
34
|
+
dissent: number;
|
|
35
|
+
majorityTrue: number;
|
|
36
|
+
majorityFalse: number;
|
|
37
|
+
majorityNone: number;
|
|
38
|
+
};
|
|
39
|
+
unanimity: {
|
|
40
|
+
numerator: number;
|
|
41
|
+
denominator: number;
|
|
42
|
+
rate: number | "unmeasured";
|
|
43
|
+
};
|
|
44
|
+
falseMajorityGivenDissent: {
|
|
45
|
+
numerator: number;
|
|
46
|
+
denominator: number;
|
|
47
|
+
rate: number | "unmeasured";
|
|
48
|
+
};
|
|
49
|
+
falseMajorityGivenMajorityAndDissent: {
|
|
50
|
+
numerator: number;
|
|
51
|
+
denominator: number;
|
|
52
|
+
rate: number | "unmeasured";
|
|
53
|
+
};
|
|
54
|
+
model: "M/3";
|
|
55
|
+
parameters: Readonly<{
|
|
56
|
+
cell: string;
|
|
57
|
+
cardinality: number;
|
|
58
|
+
witnesses: readonly import("./m3.ts").Witness[];
|
|
59
|
+
factors: readonly import("./m3.ts").Factor[];
|
|
60
|
+
selection: readonly string[];
|
|
61
|
+
}>;
|
|
62
|
+
policy: Readonly<{
|
|
63
|
+
prng: "xorshift32";
|
|
64
|
+
seed: number;
|
|
65
|
+
trials: number;
|
|
66
|
+
randomWords: number;
|
|
67
|
+
}>;
|
|
68
|
+
conditions: readonly ["controlled-simulation", "declared-xorshift32-stream", "exact-rational-event-sampling", "not-a-law"];
|
|
69
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { validateM3 } from "./m3.js";
|
|
2
|
+
import { probability, refuse } from "./rational.js";
|
|
3
|
+
export function experimentM3(model, policy) {
|
|
4
|
+
validateM3(model);
|
|
5
|
+
if (!policy || policy.prng !== 'xorshift32' || !Number.isInteger(policy.seed) || policy.seed <= 0 || policy.seed > 0xffffffff || !Number.isSafeInteger(policy.trials) || policy.trials < 1 || !Number.isSafeInteger(policy.randomWords) || policy.randomWords < 0)
|
|
6
|
+
refuse('invalid experiment policy');
|
|
7
|
+
const metadata = { model: 'M/3', parameters: model, policy, conditions: ['controlled-simulation', 'declared-xorshift32-stream', 'exact-rational-event-sampling', 'not-a-law'] };
|
|
8
|
+
let x = policy.seed >>> 0, words = 0;
|
|
9
|
+
const exhausted = Symbol('experiment/random-words');
|
|
10
|
+
const word = () => { if (words === policy.randomWords)
|
|
11
|
+
throw exhausted; words++; x ^= x << 13; x ^= x >>> 17; x ^= x << 5; return BigInt(x >>> 0); };
|
|
12
|
+
const below = (n) => { if (n === 1n)
|
|
13
|
+
return 0n; const bits = BigInt((n - 1n).toString(2).length), mask = (1n << bits) - 1n; for (;;) {
|
|
14
|
+
let r = 0n;
|
|
15
|
+
for (let b = 0n; b < bits; b += 32n)
|
|
16
|
+
r = (r << 32n) | word();
|
|
17
|
+
r &= mask;
|
|
18
|
+
if (r < n)
|
|
19
|
+
return r;
|
|
20
|
+
} };
|
|
21
|
+
const bernoulli = (p) => p[0] === 0n ? false : p[0] === p[1] ? true : below(p[1]) < p[0];
|
|
22
|
+
const factors = [...model.factors].filter(f => f.members.some(id => model.selection.includes(id))).sort((a, b) => a.index - b.index);
|
|
23
|
+
const c = factors.map(f => probability(f.probability)), p = model.selection.map(id => probability(model.witnesses.find(w => w.id === id).privateError)), k = model.selection.length;
|
|
24
|
+
const counts = { true: 0, false: 0, fired: 0, dissent: 0, majorityTrue: 0, majorityFalse: 0, majorityNone: 0 };
|
|
25
|
+
try {
|
|
26
|
+
for (let trial = 0; trial < policy.trials; trial++) {
|
|
27
|
+
const governors = model.selection.map(() => -1);
|
|
28
|
+
let fired = false;
|
|
29
|
+
factors.forEach((f, i) => { if (bernoulli(c[i])) {
|
|
30
|
+
fired = true;
|
|
31
|
+
model.selection.forEach((id, j) => { if (governors[j] === -1 && f.members.includes(id))
|
|
32
|
+
governors[j] = i; });
|
|
33
|
+
} });
|
|
34
|
+
const labels = new Map(), values = new Map();
|
|
35
|
+
const wrong = () => Number(below(BigInt(model.cardinality - 1))) + 1;
|
|
36
|
+
governors.forEach((g, i) => { let value = 0; if (g !== -1) {
|
|
37
|
+
if (!labels.has(g))
|
|
38
|
+
labels.set(g, wrong());
|
|
39
|
+
value = labels.get(g);
|
|
40
|
+
}
|
|
41
|
+
else if (bernoulli(p[i]))
|
|
42
|
+
value = wrong(); values.set(value, (values.get(value) ?? 0) + 1); });
|
|
43
|
+
if (values.size === 1) {
|
|
44
|
+
if (values.has(0))
|
|
45
|
+
counts.true++;
|
|
46
|
+
else {
|
|
47
|
+
counts.false++;
|
|
48
|
+
if (fired)
|
|
49
|
+
counts.fired++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
counts.dissent++;
|
|
54
|
+
if (2 * (values.get(0) ?? 0) > k)
|
|
55
|
+
counts.majorityTrue++;
|
|
56
|
+
else if ([...values].some(([value, count]) => value !== 0 && 2 * count > k))
|
|
57
|
+
counts.majorityFalse++;
|
|
58
|
+
else
|
|
59
|
+
counts.majorityNone++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error !== exhausted)
|
|
65
|
+
throw error;
|
|
66
|
+
return { ...metadata, status: 'unmeasured', cause: 'experiment/random-words', words };
|
|
67
|
+
}
|
|
68
|
+
const conditional = (numerator, denominator) => ({ numerator, denominator, rate: denominator ? numerator / denominator : 'unmeasured' });
|
|
69
|
+
return { ...metadata, status: 'measured', words, counts, unanimity: conditional(counts.false, counts.true + counts.false), falseMajorityGivenDissent: conditional(counts.majorityFalse, counts.dissent), falseMajorityGivenMajorityAndDissent: conditional(counts.majorityFalse, counts.majorityTrue + counts.majorityFalse) };
|
|
70
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { type Q, type Rational, type RationalInput } from './rational.ts';
|
|
2
|
+
export type Witness = Readonly<{
|
|
3
|
+
id: string;
|
|
4
|
+
privateError?: RationalInput;
|
|
5
|
+
}>;
|
|
6
|
+
export type Factor = Readonly<{
|
|
7
|
+
id: string;
|
|
8
|
+
index: number;
|
|
9
|
+
members: readonly string[];
|
|
10
|
+
probability: RationalInput;
|
|
11
|
+
}>;
|
|
12
|
+
export type ModelM3 = Readonly<{
|
|
13
|
+
cell: string;
|
|
14
|
+
cardinality: number;
|
|
15
|
+
witnesses: readonly Witness[];
|
|
16
|
+
factors: readonly Factor[];
|
|
17
|
+
selection: readonly string[];
|
|
18
|
+
}>;
|
|
19
|
+
export type EvaluationPolicy = Readonly<{
|
|
20
|
+
budget: string;
|
|
21
|
+
}>;
|
|
22
|
+
export type Conditional = Readonly<{
|
|
23
|
+
status: 'measured';
|
|
24
|
+
value: Rational;
|
|
25
|
+
numerator: Rational;
|
|
26
|
+
denominator: Rational;
|
|
27
|
+
}> | Readonly<{
|
|
28
|
+
status: 'unmeasured';
|
|
29
|
+
cause: string;
|
|
30
|
+
numerator: Rational;
|
|
31
|
+
denominator: Rational;
|
|
32
|
+
}>;
|
|
33
|
+
export declare const conditions: readonly ["ordered-governance", "independent-factor-indicators", "independent-private-indicators", "independent-uniform-wrong-labels", "handed-parameters"];
|
|
34
|
+
type Prepared = Readonly<{
|
|
35
|
+
witnesses: readonly Witness[];
|
|
36
|
+
factors: readonly Factor[];
|
|
37
|
+
private: readonly Q[];
|
|
38
|
+
rates: readonly Q[];
|
|
39
|
+
}>;
|
|
40
|
+
export declare function validateM3(model: ModelM3): Prepared;
|
|
41
|
+
export declare function conditional(a: Q, b: Q, cause: string): Conditional;
|
|
42
|
+
export declare function evaluateM3(model: ModelM3, policy: EvaluationPolicy): {
|
|
43
|
+
status: "unmeasured";
|
|
44
|
+
cause: string;
|
|
45
|
+
requiredAtLeast: string;
|
|
46
|
+
model: "M/3";
|
|
47
|
+
parameters: Readonly<{
|
|
48
|
+
cell: string;
|
|
49
|
+
cardinality: number;
|
|
50
|
+
witnesses: readonly Witness[];
|
|
51
|
+
factors: readonly Factor[];
|
|
52
|
+
selection: readonly string[];
|
|
53
|
+
}>;
|
|
54
|
+
conditions: readonly ["ordered-governance", "independent-factor-indicators", "independent-private-indicators", "independent-uniform-wrong-labels", "handed-parameters"];
|
|
55
|
+
policy: Readonly<{
|
|
56
|
+
budget: string;
|
|
57
|
+
}>;
|
|
58
|
+
} | {
|
|
59
|
+
status: "measured";
|
|
60
|
+
work: string;
|
|
61
|
+
probabilities: {
|
|
62
|
+
true: Readonly<{
|
|
63
|
+
numerator: string;
|
|
64
|
+
denominator: string;
|
|
65
|
+
}>;
|
|
66
|
+
false: Readonly<{
|
|
67
|
+
numerator: string;
|
|
68
|
+
denominator: string;
|
|
69
|
+
}>;
|
|
70
|
+
fired: Readonly<{
|
|
71
|
+
numerator: string;
|
|
72
|
+
denominator: string;
|
|
73
|
+
}>;
|
|
74
|
+
dissent: Readonly<{
|
|
75
|
+
numerator: string;
|
|
76
|
+
denominator: string;
|
|
77
|
+
}>;
|
|
78
|
+
majorityTrue: Readonly<{
|
|
79
|
+
numerator: string;
|
|
80
|
+
denominator: string;
|
|
81
|
+
}>;
|
|
82
|
+
majorityFalse: Readonly<{
|
|
83
|
+
numerator: string;
|
|
84
|
+
denominator: string;
|
|
85
|
+
}>;
|
|
86
|
+
majorityNone: Readonly<{
|
|
87
|
+
numerator: string;
|
|
88
|
+
denominator: string;
|
|
89
|
+
}>;
|
|
90
|
+
};
|
|
91
|
+
quantities: {
|
|
92
|
+
phi: Conditional;
|
|
93
|
+
D: Conditional;
|
|
94
|
+
reliability: Conditional;
|
|
95
|
+
falseMajorityGivenDissent: Conditional;
|
|
96
|
+
falseMajorityGivenMajorityAndDissent: Conditional;
|
|
97
|
+
};
|
|
98
|
+
reliabilityLabel: string;
|
|
99
|
+
model: "M/3";
|
|
100
|
+
parameters: Readonly<{
|
|
101
|
+
cell: string;
|
|
102
|
+
cardinality: number;
|
|
103
|
+
witnesses: readonly Witness[];
|
|
104
|
+
factors: readonly Factor[];
|
|
105
|
+
selection: readonly string[];
|
|
106
|
+
}>;
|
|
107
|
+
conditions: readonly ["ordered-governance", "independent-factor-indicators", "independent-private-indicators", "independent-uniform-wrong-labels", "handed-parameters"];
|
|
108
|
+
policy: Readonly<{
|
|
109
|
+
budget: string;
|
|
110
|
+
}>;
|
|
111
|
+
};
|
|
112
|
+
export declare function probeUpdate(phi: RationalInput, error: RationalInput, cardinality: number): {
|
|
113
|
+
model: "M/3";
|
|
114
|
+
conditions: readonly ["independent-private-only-probe", "independent-uniform-wrong-value"];
|
|
115
|
+
parameters: {
|
|
116
|
+
phi: RationalInput;
|
|
117
|
+
error: RationalInput;
|
|
118
|
+
cardinality: number;
|
|
119
|
+
};
|
|
120
|
+
result: Conditional;
|
|
121
|
+
};
|
|
122
|
+
export declare function dissentCertificate(model: ModelM3, fired: readonly string[]): {
|
|
123
|
+
model: "M/3";
|
|
124
|
+
parameters: {
|
|
125
|
+
model: Readonly<{
|
|
126
|
+
cell: string;
|
|
127
|
+
cardinality: number;
|
|
128
|
+
witnesses: readonly Witness[];
|
|
129
|
+
factors: readonly Factor[];
|
|
130
|
+
selection: readonly string[];
|
|
131
|
+
}>;
|
|
132
|
+
fired: readonly string[];
|
|
133
|
+
};
|
|
134
|
+
conditions: readonly ["ordered-governance", "independent-factor-indicators", "independent-private-indicators", "independent-uniform-wrong-labels", "handed-parameters"];
|
|
135
|
+
unanimityForced: boolean;
|
|
136
|
+
coveringFactors: string[];
|
|
137
|
+
meaning: "observed dissent excludes this configuration only";
|
|
138
|
+
};
|
|
139
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { add, sub, mul, div, q, zero, one, equal, pack, probability, refuse } from "./rational.js";
|
|
2
|
+
export const conditions = Object.freeze(['ordered-governance', 'independent-factor-indicators', 'independent-private-indicators', 'independent-uniform-wrong-labels', 'handed-parameters']);
|
|
3
|
+
const named = (s) => typeof s === 'string' && s.length > 0;
|
|
4
|
+
export function validateM3(model) {
|
|
5
|
+
if (!model || !Array.isArray(model.witnesses) || !Array.isArray(model.factors) || !Array.isArray(model.selection) || model.witnesses.some(w => !w) || model.factors.some(f => !f || !Array.isArray(f.members)))
|
|
6
|
+
refuse('invalid model records');
|
|
7
|
+
if (!named(model.cell) || !Number.isSafeInteger(model.cardinality) || model.cardinality < 2)
|
|
8
|
+
refuse('invalid cell or cardinality');
|
|
9
|
+
const ids = model.witnesses.map(w => w.id);
|
|
10
|
+
if (ids.some(id => !named(id)) || new Set(ids).size !== ids.length)
|
|
11
|
+
refuse('duplicate or absent witness');
|
|
12
|
+
if (!model.selection.length || new Set(model.selection).size !== model.selection.length || model.selection.some(id => !ids.includes(id)))
|
|
13
|
+
refuse('invalid selection');
|
|
14
|
+
const factors = [...model.factors].sort((a, b) => a.index - b.index);
|
|
15
|
+
if (new Set(factors.map(f => f.id)).size !== factors.length || new Set(factors.map(f => f.index)).size !== factors.length)
|
|
16
|
+
refuse('duplicate factor identity or index');
|
|
17
|
+
for (const f of factors)
|
|
18
|
+
if (!named(f.id) || !Number.isSafeInteger(f.index) || new Set(f.members).size !== f.members.length || f.members.some(id => !ids.includes(id)))
|
|
19
|
+
refuse('invalid sharing set or order');
|
|
20
|
+
const required = new Set([...model.selection, ...factors.filter(f => f.members.some(id => model.selection.includes(id))).flatMap(f => f.members)]);
|
|
21
|
+
const allPrivate = model.witnesses.map(w => { if (w.privateError === undefined) {
|
|
22
|
+
if (required.has(w.id))
|
|
23
|
+
refuse('private probability absent for ' + w.id);
|
|
24
|
+
return zero;
|
|
25
|
+
} return probability(w.privateError); });
|
|
26
|
+
const allRates = factors.map(f => probability(f.probability));
|
|
27
|
+
const selected = model.selection.map(id => model.witnesses[ids.indexOf(id)]);
|
|
28
|
+
const touching = factors.filter(f => f.members.some(id => model.selection.includes(id)));
|
|
29
|
+
return { witnesses: selected, factors: touching, private: selected.map(w => allPrivate[ids.indexOf(w.id)]), rates: touching.map(f => allRates[factors.indexOf(f)]) };
|
|
30
|
+
}
|
|
31
|
+
function limit(policy) { if (!policy || !/^\d+$/.test(policy.budget))
|
|
32
|
+
refuse('invalid evaluator/budget'); return BigInt(policy.budget); }
|
|
33
|
+
function bells(n, cap) {
|
|
34
|
+
let row = [1n];
|
|
35
|
+
const result = [1n];
|
|
36
|
+
for (let i = 1; i <= n; i++) {
|
|
37
|
+
const next = [row[row.length - 1]];
|
|
38
|
+
for (let j = 1; j <= i; j++)
|
|
39
|
+
next.push(next[j - 1] + row[j - 1]);
|
|
40
|
+
row = next;
|
|
41
|
+
result.push(row[0]);
|
|
42
|
+
if (row[0] > cap)
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function* configurations(input, masses = true) {
|
|
48
|
+
const ids = input.witnesses.map(w => w.id), governors = ids.map(() => -1);
|
|
49
|
+
function* walk(i, mass, fired) {
|
|
50
|
+
if (i === input.factors.length) {
|
|
51
|
+
yield { governors: [...governors], mass, fired };
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
yield* walk(i + 1, masses ? mul(mass, sub(one, input.rates[i])) : one, fired);
|
|
55
|
+
const changed = [];
|
|
56
|
+
for (let j = 0; j < ids.length; j++)
|
|
57
|
+
if (governors[j] === -1 && input.factors[i].members.includes(ids[j])) {
|
|
58
|
+
governors[j] = i;
|
|
59
|
+
changed.push(j);
|
|
60
|
+
}
|
|
61
|
+
yield* walk(i + 1, masses ? mul(mass, input.rates[i]) : one, true);
|
|
62
|
+
for (const j of changed)
|
|
63
|
+
governors[j] = -1;
|
|
64
|
+
}
|
|
65
|
+
yield* walk(0, one, false);
|
|
66
|
+
}
|
|
67
|
+
function countWork(input, cap) {
|
|
68
|
+
const k = input.witnesses.length, bell = bells(k + 1, cap);
|
|
69
|
+
if (bell.length <= k + 1 || bell[k + 1] > cap)
|
|
70
|
+
return cap + 1n;
|
|
71
|
+
let factorConfigurations = 1n;
|
|
72
|
+
for (let i = 0; i < input.factors.length; i++) {
|
|
73
|
+
factorConfigurations *= 2n;
|
|
74
|
+
if (factorConfigurations > cap)
|
|
75
|
+
return cap + 1n;
|
|
76
|
+
}
|
|
77
|
+
let total = 0n;
|
|
78
|
+
for (const c of configurations(input, false)) {
|
|
79
|
+
const free = c.governors.filter(g => g === -1).length, groups = new Set(c.governors.filter(g => g !== -1)).size;
|
|
80
|
+
let choose = 1n;
|
|
81
|
+
for (let r = 0; r <= free; r++) {
|
|
82
|
+
total += choose * bell[groups + r];
|
|
83
|
+
if (total > cap)
|
|
84
|
+
return cap + 1n;
|
|
85
|
+
choose = choose * BigInt(free - r) / BigInt(r + 1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return total;
|
|
89
|
+
}
|
|
90
|
+
export function conditional(a, b, cause) { return b[0] === 0n ? { status: 'unmeasured', cause, numerator: pack(a), denominator: pack(b) } : { status: 'measured', value: pack(div(a, b)), numerator: pack(a), denominator: pack(b) }; }
|
|
91
|
+
export function evaluateM3(model, policy) {
|
|
92
|
+
const input = validateM3(model), cap = limit(policy), work = countWork(input, cap);
|
|
93
|
+
const metadata = { model: 'M/3', parameters: model, conditions, policy };
|
|
94
|
+
if (work > cap)
|
|
95
|
+
return { ...metadata, status: 'unmeasured', cause: 'evaluator/budget', requiredAtLeast: String(work) };
|
|
96
|
+
const k = input.witnesses.length, wrong = BigInt(model.cardinality - 1);
|
|
97
|
+
let truth = zero, falsehood = zero, fired = zero, majorityTrue = zero, majorityFalse = zero, majorityNone = zero;
|
|
98
|
+
for (const configuration of configurations(input)) {
|
|
99
|
+
if (configuration.mass[0] === 0n)
|
|
100
|
+
continue;
|
|
101
|
+
const grouped = new Map();
|
|
102
|
+
configuration.governors.forEach(g => { if (g !== -1)
|
|
103
|
+
grouped.set(g, (grouped.get(g) ?? 0) + 1); });
|
|
104
|
+
const weights = [...grouped.values()], free = configuration.governors.flatMap((g, i) => g === -1 ? [i] : []);
|
|
105
|
+
function privatePatterns(i, mass, correct) {
|
|
106
|
+
if (mass[0] === 0n)
|
|
107
|
+
return;
|
|
108
|
+
if (i < free.length) {
|
|
109
|
+
const p = input.private[free[i]];
|
|
110
|
+
privatePatterns(i + 1, mul(mass, sub(one, p)), correct + 1);
|
|
111
|
+
weights.push(1);
|
|
112
|
+
privatePatterns(i + 1, mul(mass, p), correct);
|
|
113
|
+
weights.pop();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const L = weights.length, blocks = [];
|
|
117
|
+
function partitions(label) {
|
|
118
|
+
if (label < L) {
|
|
119
|
+
for (let j = 0; j < blocks.length; j++) {
|
|
120
|
+
blocks[j] += weights[label];
|
|
121
|
+
partitions(label + 1);
|
|
122
|
+
blocks[j] -= weights[label];
|
|
123
|
+
}
|
|
124
|
+
blocks.push(weights[label]);
|
|
125
|
+
partitions(label + 1);
|
|
126
|
+
blocks.pop();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const b = blocks.length;
|
|
130
|
+
if (BigInt(b) > wrong)
|
|
131
|
+
return;
|
|
132
|
+
let assignments = 1n;
|
|
133
|
+
for (let j = 0; j < b; j++)
|
|
134
|
+
assignments *= wrong - BigInt(j);
|
|
135
|
+
const probabilityMass = mul(mass, q(assignments, wrong ** BigInt(L)));
|
|
136
|
+
if (correct === k) {
|
|
137
|
+
truth = add(truth, probabilityMass);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (correct === 0 && b === 1) {
|
|
141
|
+
falsehood = add(falsehood, probabilityMass);
|
|
142
|
+
if (configuration.fired)
|
|
143
|
+
fired = add(fired, probabilityMass);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (2 * correct > k)
|
|
147
|
+
majorityTrue = add(majorityTrue, probabilityMass);
|
|
148
|
+
else if (blocks.some(weight => 2 * weight > k))
|
|
149
|
+
majorityFalse = add(majorityFalse, probabilityMass);
|
|
150
|
+
else
|
|
151
|
+
majorityNone = add(majorityNone, probabilityMass);
|
|
152
|
+
}
|
|
153
|
+
partitions(0);
|
|
154
|
+
}
|
|
155
|
+
privatePatterns(0, configuration.mass, 0);
|
|
156
|
+
}
|
|
157
|
+
const dissent = sub(sub(one, truth), falsehood), majorityMass = add(majorityTrue, majorityFalse), unanimity = add(truth, falsehood);
|
|
158
|
+
let closedTruth = one;
|
|
159
|
+
for (const p of input.private)
|
|
160
|
+
closedTruth = mul(closedTruth, sub(one, p));
|
|
161
|
+
for (const c of input.rates)
|
|
162
|
+
closedTruth = mul(closedTruth, sub(one, c));
|
|
163
|
+
if (!equal(closedTruth, truth) || !equal(dissent, add(majorityMass, majorityNone)))
|
|
164
|
+
refuse('partition mass identity');
|
|
165
|
+
return { ...metadata, status: 'measured', work: String(work), probabilities: { true: pack(truth), false: pack(falsehood), fired: pack(fired), dissent: pack(dissent), majorityTrue: pack(majorityTrue), majorityFalse: pack(majorityFalse), majorityNone: pack(majorityNone) }, quantities: { phi: conditional(falsehood, unanimity, 'no unanimity mass'), D: conditional(fired, unanimity, 'no unanimity mass'), reliability: conditional(truth, unanimity, 'no unanimity mass'), falseMajorityGivenDissent: conditional(majorityFalse, dissent, 'no dissent mass'), falseMajorityGivenMajorityAndDissent: conditional(majorityFalse, majorityMass, 'no majority mass') }, reliabilityLabel: 'P(correct | unanimous) under M/3 with handed parameters' };
|
|
166
|
+
}
|
|
167
|
+
export function probeUpdate(phi, error, cardinality) {
|
|
168
|
+
if (!Number.isSafeInteger(cardinality) || cardinality < 2)
|
|
169
|
+
refuse('invalid probe cardinality');
|
|
170
|
+
const f = probability(phi), p = probability(error), a = div(mul(f, p), q(BigInt(cardinality - 1))), b = mul(sub(one, f), sub(one, p));
|
|
171
|
+
return { model: 'M/3', conditions: ['independent-private-only-probe', 'independent-uniform-wrong-value'], parameters: { phi, error, cardinality }, result: conditional(a, add(a, b), 'no extended unanimity mass') };
|
|
172
|
+
}
|
|
173
|
+
export function dissentCertificate(model, fired) {
|
|
174
|
+
validateM3(model);
|
|
175
|
+
if (!Array.isArray(fired) || new Set(fired).size !== fired.length || fired.some(id => !model.factors.some(f => f.id === id)))
|
|
176
|
+
refuse('invalid fired configuration');
|
|
177
|
+
const selected = new Set(model.selection), active = model.factors.filter(f => fired.includes(f.id) && f.members.some(id => selected.has(id)));
|
|
178
|
+
const covering = active.filter(f => model.selection.every(id => f.members.includes(id)));
|
|
179
|
+
const witnesses = covering.filter(f => !active.some(earlier => earlier.index < f.index && !model.selection.every(id => earlier.members.includes(id)))).map(f => f.id);
|
|
180
|
+
return { model: 'M/3', parameters: { model, fired }, conditions, unanimityForced: witnesses.length > 0, coveringFactors: witnesses, meaning: 'observed dissent excludes this configuration only' };
|
|
181
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Scalar, Unmeasured } from './error-events.ts';
|
|
2
|
+
export type UnanimityModel = Readonly<{
|
|
3
|
+
c: number;
|
|
4
|
+
p: number;
|
|
5
|
+
n: number;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function unanimityFloor(model: UnanimityModel, k: number): Scalar;
|
|
8
|
+
export declare function unanimityThreshold(model: UnanimityModel, searchLimit: number): Scalar;
|
|
9
|
+
export type Descent = Readonly<{
|
|
10
|
+
scope: string;
|
|
11
|
+
restsOn: readonly string[];
|
|
12
|
+
}>;
|
|
13
|
+
export declare function descentCount(graph: readonly Descent[], cell: string): Scalar;
|
|
14
|
+
export declare const modelRequired: (measure: string) => Unmeasured;
|