@lapxo/obligations 0.4.1 → 0.4.3

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 (40) hide show
  1. package/README.md +2 -2
  2. package/TARGET.bound +97 -10
  3. package/dist/lattice/obligatory.js +29 -1
  4. package/dist/measures/capacity.d.ts +22 -0
  5. package/dist/measures/capacity.js +16 -0
  6. package/dist/measures/confidence.d.ts +73 -0
  7. package/dist/measures/confidence.js +52 -0
  8. package/dist/measures/index.d.ts +9 -1
  9. package/dist/measures/index.js +5 -1
  10. package/dist/measures/mix-loss.d.ts +187 -0
  11. package/dist/measures/mix-loss.js +101 -0
  12. package/dist/measures/phase-defect.d.ts +40 -0
  13. package/dist/measures/phase-defect.js +33 -0
  14. package/dist/measures/tolerant-subsets.d.ts +79 -0
  15. package/dist/measures/tolerant-subsets.js +60 -0
  16. package/docs/measures.md +25 -1
  17. package/package.json +1 -1
  18. package/receipts.bound +304 -107
  19. package/samples/measures/common-cause-capacity.json +48 -0
  20. package/samples/measures/confidence/union-bound.json +54 -0
  21. package/samples/measures/confidence.json +70 -0
  22. package/samples/measures/l7-tolerant-k.json +90 -0
  23. package/samples/measures/l8-phase-defect.json +19 -0
  24. package/samples/measures/l9-mix-loss.json +36 -0
  25. package/samples/measures/t12/common-cause.json +32 -0
  26. package/samples/measures/tolerant-count.json +144 -0
  27. package/samples/no/installed-artifact.json +102 -5
  28. package/samples/yes/installed-artifact.json +36 -49
  29. package/vectors/laws/implementation-043.json +98 -0
  30. package/vectors/laws/l7-tolerant-k.json +253 -0
  31. package/vectors/laws/l8-phase-defect.json +214 -0
  32. package/vectors/laws/l9-mix-loss.json +130 -0
  33. package/vectors/laws/policy.json +22 -0
  34. package/vectors/measures/common-cause-capacity.cli.json +97 -0
  35. package/vectors/measures/confidence/expected.json +26 -0
  36. package/vectors/measures/confidence.cli.json +97 -0
  37. package/vectors/measures/implementation-042.json +86 -0
  38. package/vectors/measures/installed-artifact.cli.json +12 -28
  39. package/vectors/measures/t12/expected.json +35 -0
  40. package/vectors/measures/tolerant-count.cli.json +97 -0
@@ -18,6 +18,7 @@ export function restOf(c, world = new Map()) {
18
18
  const out = [];
19
19
  const held = new Set();
20
20
  const edge = [...c.restsOn];
21
+ const above = new Map();
21
22
  for (let i = 0; i < edge.length; i += 1) {
22
23
  const name = edge[i];
23
24
  if (name === own)
@@ -26,7 +27,34 @@ export function restOf(c, world = new Map()) {
26
27
  continue;
27
28
  held.add(name);
28
29
  out.push(name);
29
- edge.push(...(world.get(name)?.restsOn ?? []));
30
+ const rests = world.get(name)?.restsOn ?? [];
31
+ above.set(name, rests);
32
+ for (const ancestor of rests)
33
+ edge.push(ancestor);
34
+ }
35
+ const status = new Map();
36
+ for (const name of out) {
37
+ if (status.has(name))
38
+ continue;
39
+ status.set(name, 'visiting');
40
+ const path = [{ name, next: 0 }];
41
+ while (path.length) {
42
+ const head = path[path.length - 1];
43
+ const rests = above.get(head.name);
44
+ if (head.next === rests.length) {
45
+ status.set(head.name, 'done');
46
+ path.pop();
47
+ continue;
48
+ }
49
+ const ancestor = rests[head.next++];
50
+ const seen = status.get(ancestor);
51
+ if (seen === 'visiting')
52
+ throw new UnitError('rest-cycle', `obligations: REFUSE·rest ${own} reaches a cycle at ${ancestor}: an order of rest with a cycle has no fold to reach`, { at: own });
53
+ if (seen === undefined) {
54
+ status.set(ancestor, 'visiting');
55
+ path.push({ name: ancestor, next: 0 });
56
+ }
57
+ }
30
58
  }
31
59
  return out;
32
60
  }
@@ -28,6 +28,28 @@ export declare function capacityBound(n: number, c: RationalInput, policy: Logar
28
28
  value: number;
29
29
  meaning: "upper bound for every k; achievability depends on the private channel";
30
30
  };
31
+ export declare function commonCauseCapacityBound(n: number, c: RationalInput, accuracy: RationalInput, policy: LogarithmPolicy): {
32
+ model: "single-covering-symmetric-cause@1";
33
+ parameters: {
34
+ n: number;
35
+ c: Readonly<{
36
+ numerator: string;
37
+ denominator: string;
38
+ }>;
39
+ q: Readonly<{
40
+ numerator: string;
41
+ denominator: string;
42
+ }>;
43
+ };
44
+ policy: Readonly<{
45
+ logarithm: "binary64-log2";
46
+ }>;
47
+ status: "measured";
48
+ unit: "bits";
49
+ conditions: readonly ["uniform-truth", "cause-indicator-independent-of-truth", "active-cause-copies-one-symmetric-channel", "inactive-private-channel-unrestricted"];
50
+ value: number;
51
+ meaning: "upper bound after revealing cause indicator; copies do not add; no achievability claim";
52
+ };
31
53
  export declare function capacityP0(n: number, c: RationalInput, policy: LogarithmPolicy): {
32
54
  value: number;
33
55
  meaning: "mutual information at p=0 for every positive k";
@@ -29,6 +29,22 @@ export function capacityBound(n, c, policy) {
29
29
  status: 'measured', unit: 'bits', value: Math.log2(n) - numeric(factor) * Math.log2(n - 1),
30
30
  meaning: 'upper bound for every k; achievability depends on the private channel' };
31
31
  }
32
+ export function commonCauseCapacityBound(n, c, accuracy, policy) {
33
+ cardinality(n);
34
+ logPolicy(policy);
35
+ if (accuracy === undefined || accuracy === null)
36
+ refuse('common-cause accuracy q required');
37
+ const factor = probability(c), correct = probability(accuracy), wrong = sub(one, correct);
38
+ const uniform = correct[0] * BigInt(n) === correct[1];
39
+ const channel = uniform ? 0 : correct[0] === 0n ? Math.log1p(1 / (n - 1)) / Math.LN2 :
40
+ Math.max(0, Math.log2(n) - entropy([correct, wrong]) - numeric(wrong) * Math.log2(n - 1));
41
+ return { model: 'single-covering-symmetric-cause@1',
42
+ parameters: { n, c: pack(factor), q: pack(correct) }, policy, status: 'measured', unit: 'bits',
43
+ conditions: ['uniform-truth', 'cause-indicator-independent-of-truth',
44
+ 'active-cause-copies-one-symmetric-channel', 'inactive-private-channel-unrestricted'],
45
+ value: numeric(sub(one, factor)) * Math.log2(n) + numeric(factor) * channel,
46
+ meaning: 'upper bound after revealing cause indicator; copies do not add; no achievability claim' };
47
+ }
32
48
  export function capacityP0(n, c, policy) {
33
49
  const bound = capacityBound(n, c, policy), factor = probability(c);
34
50
  return { ...bound, value: bound.value - entropy([factor, sub(one, factor)]), meaning: 'mutual information at p=0 for every positive k' };
@@ -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
+ }
@@ -16,9 +16,17 @@ 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 { capacityBound, capacityP0, mutualInformation, unanimityNoFactor, decisionInformation } from './capacity.ts';
19
+ export { commonCauseCapacityBound, capacityBound, capacityP0, mutualInformation, unanimityNoFactor, decisionInformation } from './capacity.ts';
20
20
  export type { CapacityModel, LogarithmPolicy, CapacityPolicy, DecisionRule } from './capacity.ts';
21
21
  export { fitUnanimityModel } from './unanimity-fit.ts';
22
22
  export type { UnanimityCount, UnanimityGroup, UnanimityCurve, UnanimityFitPolicy } from './unanimity-fit.ts';
23
23
  export { coincidenceExcess } from './coincidence.ts';
24
24
  export type { ValueReading, TruthReading, CoincidenceReference, CoincidencePolicy } from './coincidence.ts';
25
+ export { unionBoundConfidence, tolerantCountConfidence } from './confidence.ts';
26
+ export type { DeclaredFailure } from './confidence.ts';
27
+ export { tolerantSubsetConfidence, independentCountConfidence } from './tolerant-subsets.ts';
28
+ export type { JointIndependenceDeclaration } from './tolerant-subsets.ts';
29
+ export { phaseDefect, phaseDefectExtrema } from './phase-defect.ts';
30
+ export type { PhaseDefectInterpretation } from './phase-defect.ts';
31
+ export { mixProjectionLoss } from './mix-loss.ts';
32
+ export type { NamedConfiguration, ConfigurationPrior, MixLossModel, MixLossPolicy } from './mix-loss.ts';
@@ -7,6 +7,10 @@ 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 { capacityBound, capacityP0, mutualInformation, unanimityNoFactor, decisionInformation } from "./capacity.js";
10
+ export { commonCauseCapacityBound, capacityBound, capacityP0, mutualInformation, unanimityNoFactor, decisionInformation } from "./capacity.js";
11
11
  export { fitUnanimityModel } from "./unanimity-fit.js";
12
12
  export { coincidenceExcess } from "./coincidence.js";
13
+ export { unionBoundConfidence, tolerantCountConfidence } from "./confidence.js";
14
+ export { tolerantSubsetConfidence, independentCountConfidence } from "./tolerant-subsets.js";
15
+ export { phaseDefect, phaseDefectExtrema } from "./phase-defect.js";
16
+ export { mixProjectionLoss } from "./mix-loss.js";
@@ -0,0 +1,187 @@
1
+ import { type RationalInput } from './rational.ts';
2
+ export type NamedConfiguration = Readonly<{
3
+ id: string;
4
+ values: readonly string[];
5
+ }>;
6
+ export type ConfigurationPrior = Readonly<{
7
+ kind: 'uniform';
8
+ }> | Readonly<{
9
+ kind: 'explicit';
10
+ probabilities: readonly Readonly<{
11
+ configuration: string;
12
+ p: RationalInput;
13
+ }>[];
14
+ }>;
15
+ export type MixLossModel = Readonly<{
16
+ factors: readonly Readonly<{
17
+ id: string;
18
+ weight: RationalInput;
19
+ }>[];
20
+ alphabet: readonly string[];
21
+ configurations: readonly NamedConfiguration[];
22
+ prior?: ConfigurationPrior;
23
+ }>;
24
+ export type MixLossPolicy = Readonly<{
25
+ logarithm: 'binary64-log2';
26
+ budget: string;
27
+ }>;
28
+ export declare function mixProjectionLoss(model: MixLossModel, policy: MixLossPolicy): {
29
+ status: "unmeasured";
30
+ cause: "mix-loss/prior-required";
31
+ parameters: {
32
+ m: number;
33
+ factors: {
34
+ id: string;
35
+ weight: Readonly<{
36
+ numerator: string;
37
+ denominator: string;
38
+ }>;
39
+ }[];
40
+ alphabet: string[];
41
+ configurations: {
42
+ id: string;
43
+ values: string[];
44
+ }[];
45
+ };
46
+ policy: Readonly<{
47
+ logarithm: "binary64-log2";
48
+ budget: string;
49
+ }>;
50
+ required?: undefined;
51
+ workUnit?: undefined;
52
+ model?: undefined;
53
+ prior?: undefined;
54
+ unit?: undefined;
55
+ approximation?: undefined;
56
+ configurationBits?: undefined;
57
+ mixBits?: undefined;
58
+ lossBits?: undefined;
59
+ entropyDifferenceBits?: undefined;
60
+ forkLossBits?: undefined;
61
+ mixInjectiveOnConfigurations?: undefined;
62
+ mixInjectiveOnPositiveSupport?: undefined;
63
+ configurations?: undefined;
64
+ outputs?: undefined;
65
+ meaning?: undefined;
66
+ } | {
67
+ status: "unmeasured";
68
+ cause: "mix-loss/work-budget";
69
+ parameters: {
70
+ m: number;
71
+ factors: {
72
+ id: string;
73
+ weight: Readonly<{
74
+ numerator: string;
75
+ denominator: string;
76
+ }>;
77
+ }[];
78
+ alphabet: string[];
79
+ configurations: {
80
+ id: string;
81
+ values: string[];
82
+ }[];
83
+ };
84
+ policy: Readonly<{
85
+ logarithm: "binary64-log2";
86
+ budget: string;
87
+ }>;
88
+ required: string;
89
+ workUnit: "declared-configurations";
90
+ model?: undefined;
91
+ prior?: undefined;
92
+ unit?: undefined;
93
+ approximation?: undefined;
94
+ configurationBits?: undefined;
95
+ mixBits?: undefined;
96
+ lossBits?: undefined;
97
+ entropyDifferenceBits?: undefined;
98
+ forkLossBits?: undefined;
99
+ mixInjectiveOnConfigurations?: undefined;
100
+ mixInjectiveOnPositiveSupport?: undefined;
101
+ configurations?: undefined;
102
+ outputs?: undefined;
103
+ meaning?: undefined;
104
+ } | {
105
+ status: "measured";
106
+ model: "declared-weighted-mix-projection@1";
107
+ parameters: {
108
+ m: number;
109
+ factors: {
110
+ id: string;
111
+ weight: Readonly<{
112
+ numerator: string;
113
+ denominator: string;
114
+ }>;
115
+ }[];
116
+ alphabet: string[];
117
+ configurations: {
118
+ id: string;
119
+ values: string[];
120
+ }[];
121
+ };
122
+ prior: {
123
+ kind: "uniform" | "explicit";
124
+ probabilities: {
125
+ configuration: string;
126
+ p: Readonly<{
127
+ numerator: string;
128
+ denominator: string;
129
+ }>;
130
+ }[];
131
+ };
132
+ policy: Readonly<{
133
+ logarithm: "binary64-log2";
134
+ budget: string;
135
+ }>;
136
+ unit: "bits";
137
+ approximation: "binary64-log2";
138
+ configurationBits: number;
139
+ mixBits: number;
140
+ lossBits: number;
141
+ entropyDifferenceBits: number;
142
+ forkLossBits: number;
143
+ mixInjectiveOnConfigurations: boolean;
144
+ mixInjectiveOnPositiveSupport: boolean;
145
+ configurations: {
146
+ id: string;
147
+ probability: Readonly<{
148
+ numerator: string;
149
+ denominator: string;
150
+ }>;
151
+ mix: Readonly<{
152
+ kind: "value";
153
+ value: string;
154
+ }> | {
155
+ kind: "tie";
156
+ };
157
+ fork: {
158
+ factor: string;
159
+ value: string;
160
+ }[];
161
+ }[];
162
+ outputs: {
163
+ output: Readonly<{
164
+ kind: "value";
165
+ value: string;
166
+ }> | {
167
+ kind: "tie";
168
+ };
169
+ probability: Readonly<{
170
+ numerator: string;
171
+ denominator: string;
172
+ }>;
173
+ configurations: string[];
174
+ posterior: {
175
+ configuration: string;
176
+ p: Readonly<{
177
+ numerator: string;
178
+ denominator: string;
179
+ }>;
180
+ }[];
181
+ conditionalBits: number | null;
182
+ }[];
183
+ meaning: "conditional entropy of the declared configuration given its deterministic mix output; named fork is injective";
184
+ cause?: undefined;
185
+ required?: undefined;
186
+ workUnit?: undefined;
187
+ };
@@ -0,0 +1,101 @@
1
+ import { add, div, one, pack, probability, rational, zero } from "./rational.js";
2
+ const refuse = (why) => { throw Error('REFUSE·mix-loss ' + why); };
3
+ const exact = (input, asProbability = false) => {
4
+ try {
5
+ return asProbability ? probability(input) : rational(input);
6
+ }
7
+ catch {
8
+ return refuse('invalid ' + (asProbability ? 'prior probability' : 'factor weight'));
9
+ }
10
+ };
11
+ const logInteger = (n) => { const shift = Math.max(0, n.toString(2).length - 53); return Math.log2(Number(n >> BigInt(shift))) + shift; };
12
+ const logQ = (p) => logInteger(p[0]) - logInteger(p[1]);
13
+ const numeric = (p) => p[0] === 0n ? 0 : 2 ** logQ(p);
14
+ const entropy = (ps) => ps.reduce((sum, p) => p[0] === 0n ? sum : sum - numeric(p) * logQ(p), 0);
15
+ const uniqueNames = (names, what) => {
16
+ if (!Array.isArray(names) || !names.length || names.some(n => typeof n !== 'string' || !n.trim()) || new Set(names).size !== names.length)
17
+ refuse('distinct nonempty ' + what + ' required');
18
+ };
19
+ export function mixProjectionLoss(model, policy) {
20
+ if (!policy || policy.logarithm !== 'binary64-log2' || typeof policy.budget !== 'string' || !/^\d+$/.test(policy.budget))
21
+ refuse('logarithm and work budget required');
22
+ if (!model || !Array.isArray(model.factors) || !Array.isArray(model.alphabet) || !Array.isArray(model.configurations))
23
+ refuse('declared factors, alphabet and configuration support required');
24
+ uniqueNames(model.factors.map(f => f?.id), 'factor IDs');
25
+ uniqueNames(model.alphabet, 'alphabet values');
26
+ uniqueNames(model.configurations.map(c => c?.id), 'configuration IDs');
27
+ const alphabet = new Set(model.alphabet);
28
+ const patterns = new Set();
29
+ for (const c of model.configurations) {
30
+ if (!Array.isArray(c.values) || c.values.length !== model.factors.length || c.values.some(v => !alphabet.has(v)))
31
+ refuse('configuration outside declared factor/alphabet domain');
32
+ const pattern = JSON.stringify(c.values);
33
+ if (patterns.has(pattern))
34
+ refuse('duplicate named-factor configuration');
35
+ patterns.add(pattern);
36
+ }
37
+ const weights = model.factors.map(f => exact(f.weight));
38
+ if (weights.some(w => w[0] < 0n) || weights.every(w => w[0] === 0n))
39
+ refuse('nonnegative weights with positive total required');
40
+ const parameters = { m: model.factors.length, factors: model.factors.map((f, i) => ({ id: f.id, weight: pack(weights[i]) })),
41
+ alphabet: [...model.alphabet], configurations: model.configurations.map(c => ({ id: c.id, values: [...c.values] })) };
42
+ if (!model.prior)
43
+ return { status: 'unmeasured', cause: 'mix-loss/prior-required', parameters, policy };
44
+ const required = BigInt(model.configurations.length);
45
+ if (required > BigInt(policy.budget))
46
+ return { status: 'unmeasured', cause: 'mix-loss/work-budget',
47
+ parameters, policy, required: String(required), workUnit: 'declared-configurations' };
48
+ let prior;
49
+ if (model.prior.kind === 'uniform')
50
+ prior = model.configurations.map(() => div(one, [BigInt(model.configurations.length), 1n]));
51
+ else if (model.prior.kind === 'explicit') {
52
+ const entries = model.prior.probabilities;
53
+ if (!Array.isArray(entries) || entries.length !== model.configurations.length || entries.some(e => !e || typeof e.configuration !== 'string') ||
54
+ new Set(entries.map(e => e.configuration)).size !== entries.length)
55
+ refuse('one prior probability per configuration required');
56
+ const byId = new Map(entries.map(e => [e.configuration, exact(e.p, true)]));
57
+ if (model.configurations.some(c => !byId.has(c.id)))
58
+ refuse('prior support differs from declared configurations');
59
+ prior = model.configurations.map(c => byId.get(c.id));
60
+ const total = prior.reduce(add, zero);
61
+ if (total[0] !== total[1])
62
+ refuse('prior probabilities must sum exactly to one');
63
+ }
64
+ else
65
+ return refuse('declared prior kind required');
66
+ const fibers = new Map();
67
+ const configurations = model.configurations.map((c, i) => {
68
+ const totals = new Map(model.alphabet.map(v => [v, zero]));
69
+ c.values.forEach((v, j) => totals.set(v, add(totals.get(v), weights[j])));
70
+ let best = zero;
71
+ for (const total of totals.values())
72
+ if (total[0] * best[1] > best[0] * total[1])
73
+ best = total;
74
+ const winners = model.alphabet.filter(v => { const t = totals.get(v); return t[0] * best[1] === best[0] * t[1]; });
75
+ const output = winners.length === 1 ? { kind: 'value', value: winners[0] } : { kind: 'tie', values: winners };
76
+ const key = output.kind === 'tie' ? 'tie' : JSON.stringify(['value', output.value]);
77
+ const fiber = fibers.get(key) ?? { output: output.kind === 'tie' ? { kind: 'tie', values: [] } : output, indices: [] };
78
+ fiber.indices.push(i);
79
+ fibers.set(key, fiber);
80
+ return { id: c.id, probability: pack(prior[i]), mix: output.kind === 'tie' ? { kind: 'tie' } : output,
81
+ fork: model.factors.map((f, j) => ({ factor: f.id, value: c.values[j] })) };
82
+ });
83
+ const outputs = [...fibers.values()].map(f => {
84
+ const mass = f.indices.map(i => prior[i]).reduce(add, zero);
85
+ const posterior = mass[0] === 0n ? [] : f.indices.map(i => ({ configuration: model.configurations[i].id, p: div(prior[i], mass) }));
86
+ return { output: f.output.kind === 'tie' ? { kind: 'tie' } : f.output, probability: pack(mass),
87
+ configurations: f.indices.map(i => model.configurations[i].id),
88
+ posterior: posterior.map(p => ({ configuration: p.configuration, p: pack(p.p) })),
89
+ conditionalBits: mass[0] === 0n ? null : entropy(posterior.map(p => p.p)), mass };
90
+ });
91
+ const configurationBits = entropy(prior), mixBits = entropy(outputs.map(o => o.mass));
92
+ const lossBits = outputs.reduce((sum, o) => sum + (o.conditionalBits === null ? 0 : numeric(o.mass) * o.conditionalBits), 0);
93
+ return { status: 'measured', model: 'declared-weighted-mix-projection@1', parameters,
94
+ prior: { kind: model.prior.kind, probabilities: model.configurations.map((c, i) => ({ configuration: c.id, p: pack(prior[i]) })) },
95
+ policy, unit: 'bits', approximation: 'binary64-log2', configurationBits, mixBits, lossBits,
96
+ entropyDifferenceBits: configurationBits - mixBits, forkLossBits: 0,
97
+ mixInjectiveOnConfigurations: [...fibers.values()].every(f => f.indices.length === 1),
98
+ mixInjectiveOnPositiveSupport: [...fibers.values()].every(f => f.indices.filter(i => prior[i][0] > 0n).length <= 1),
99
+ configurations, outputs: outputs.map(({ mass, ...o }) => o),
100
+ meaning: 'conditional entropy of the declared configuration given its deterministic mix output; named fork is injective' };
101
+ }
@@ -0,0 +1,40 @@
1
+ import type { LogarithmPolicy } from './capacity.ts';
2
+ export type PhaseDefectInterpretation = 'phase-defect' | 'information';
3
+ export declare function phaseDefect(n: number, p: number, s: number, policy: LogarithmPolicy, interpretation?: PhaseDefectInterpretation): {
4
+ model: "F4-integer-phase-defect@1";
5
+ parameters: {
6
+ n: number;
7
+ p: number;
8
+ s: number;
9
+ };
10
+ policy: Readonly<{
11
+ logarithm: "binary64-log2";
12
+ }>;
13
+ status: "measured";
14
+ meaning: "phase defect; information interpretation open";
15
+ regime: "nonnegative" | "may-be-negative";
16
+ value: number;
17
+ };
18
+ export declare function phaseDefectExtrema(n: number, p: number, policy: LogarithmPolicy): {
19
+ model: "F4-integer-phase-defect@1";
20
+ parameters: {
21
+ n: number;
22
+ p: number;
23
+ };
24
+ policy: Readonly<{
25
+ logarithm: "binary64-log2";
26
+ }>;
27
+ status: "measured";
28
+ minimum: {
29
+ value: number;
30
+ shifts: number[];
31
+ };
32
+ maximum: {
33
+ value: number;
34
+ shifts: number[];
35
+ };
36
+ nonzeroMaximum: {
37
+ value: number;
38
+ shifts: number[];
39
+ } | null;
40
+ };
@@ -0,0 +1,33 @@
1
+ const refuse = (cause) => { throw new Error('REFUSE·phase-defect ' + cause); };
2
+ function domain(n, p, policy) {
3
+ if (!Number.isSafeInteger(n) || n < 1 || !Number.isSafeInteger(p) || p < 1 || n % p !== 0)
4
+ refuse('positive integer divisor domain required');
5
+ if (!policy || policy.logarithm !== 'binary64-log2')
6
+ refuse('logarithm policy required');
7
+ }
8
+ function shift(p, s) {
9
+ if (!Number.isSafeInteger(s) || s < 0 || s >= p)
10
+ refuse('integer shift domain required');
11
+ }
12
+ function binaryEntropy(t) {
13
+ return t === 0 || t === 1 ? 0 : -t * Math.log2(t) - (1 - t) * Math.log2(1 - t);
14
+ }
15
+ export function phaseDefect(n, p, s, policy, interpretation = 'phase-defect') {
16
+ domain(n, p, policy);
17
+ shift(p, s);
18
+ if (interpretation !== 'phase-defect')
19
+ refuse('information interpretation unsupported');
20
+ return { model: 'F4-integer-phase-defect@1', parameters: { n, p, s }, policy,
21
+ status: 'measured', meaning: 'phase defect; information interpretation open',
22
+ regime: n / p >= 2 ? 'nonnegative' : 'may-be-negative',
23
+ value: Math.log2(n / p) - binaryEntropy(s / p) };
24
+ }
25
+ export function phaseDefectExtrema(n, p, policy) {
26
+ domain(n, p, policy);
27
+ const middle = Math.floor(p / 2), minimumShifts = p === 1 ? [0] : p % 2 === 0 ? [middle] : [middle, middle + 1];
28
+ return { model: 'F4-integer-phase-defect@1', parameters: { n, p }, policy,
29
+ status: 'measured',
30
+ minimum: { value: Math.log2(n / p) - binaryEntropy(middle / p), shifts: minimumShifts },
31
+ maximum: { value: Math.log2(n / p), shifts: [0] },
32
+ nonzeroMaximum: p === 1 ? null : { value: Math.log2(n / p) - binaryEntropy(1 / p), shifts: p === 2 ? [1] : [1, p - 1] } };
33
+ }