@takk/bayesoutputgate 1.0.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/CHANGELOG.md +92 -0
- package/LICENSE +190 -0
- package/NOTICE +45 -0
- package/README.md +403 -0
- package/SECURITY.md +98 -0
- package/SPEC.md +467 -0
- package/dist/adapter/index.cjs +411 -0
- package/dist/adapter/index.d.cts +29 -0
- package/dist/adapter/index.d.ts +29 -0
- package/dist/adapter/index.js +404 -0
- package/dist/audit/index.cjs +82 -0
- package/dist/audit/index.d.cts +40 -0
- package/dist/audit/index.d.ts +40 -0
- package/dist/audit/index.js +77 -0
- package/dist/bayesfactor/index.cjs +152 -0
- package/dist/bayesfactor/index.d.cts +15 -0
- package/dist/bayesfactor/index.d.ts +15 -0
- package/dist/bayesfactor/index.js +149 -0
- package/dist/beta/index.cjs +180 -0
- package/dist/beta/index.d.cts +45 -0
- package/dist/beta/index.d.ts +45 -0
- package/dist/beta/index.js +178 -0
- package/dist/calibration/index.cjs +339 -0
- package/dist/calibration/index.d.cts +53 -0
- package/dist/calibration/index.d.ts +53 -0
- package/dist/calibration/index.js +333 -0
- package/dist/cli/index.cjs +968 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +966 -0
- package/dist/dimensions/index.cjs +106 -0
- package/dist/dimensions/index.d.cts +33 -0
- package/dist/dimensions/index.d.ts +33 -0
- package/dist/dimensions/index.js +104 -0
- package/dist/edge/index.cjs +1141 -0
- package/dist/edge/index.d.cts +12 -0
- package/dist/edge/index.d.ts +12 -0
- package/dist/edge/index.js +1109 -0
- package/dist/gate/index.cjs +803 -0
- package/dist/gate/index.d.cts +77 -0
- package/dist/gate/index.d.ts +77 -0
- package/dist/gate/index.js +799 -0
- package/dist/hypothesis/index.cjs +268 -0
- package/dist/hypothesis/index.d.cts +38 -0
- package/dist/hypothesis/index.d.ts +38 -0
- package/dist/hypothesis/index.js +266 -0
- package/dist/index.cjs +1141 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +1109 -0
- package/dist/likelihood/index.cjs +137 -0
- package/dist/likelihood/index.d.cts +23 -0
- package/dist/likelihood/index.d.ts +23 -0
- package/dist/likelihood/index.js +132 -0
- package/dist/node/index.cjs +1282 -0
- package/dist/node/index.d.cts +24 -0
- package/dist/node/index.d.ts +24 -0
- package/dist/node/index.js +1246 -0
- package/dist/policy/index.cjs +88 -0
- package/dist/policy/index.d.cts +11 -0
- package/dist/policy/index.d.ts +11 -0
- package/dist/policy/index.js +85 -0
- package/dist/types-bMjn1j4e.d.cts +159 -0
- package/dist/types-bMjn1j4e.d.ts +159 -0
- package/package.json +142 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var BayesOutputGateError = class _BayesOutputGateError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "BayesOutputGateError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
Object.setPrototypeOf(this, _BayesOutputGateError.prototype);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function invariant(condition, code, message) {
|
|
14
|
+
if (!condition) {
|
|
15
|
+
throw new BayesOutputGateError(code, message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/mathspecial.ts
|
|
20
|
+
var LN_SQRT_2PI = 0.9189385332046728;
|
|
21
|
+
var LANCZOS_G = 7;
|
|
22
|
+
var LANCZOS_COEFFICIENTS = [
|
|
23
|
+
0.9999999999998099,
|
|
24
|
+
676.5203681218851,
|
|
25
|
+
-1259.1392167224028,
|
|
26
|
+
771.3234287776531,
|
|
27
|
+
-176.6150291621406,
|
|
28
|
+
12.507343278686905,
|
|
29
|
+
-0.13857109526572012,
|
|
30
|
+
9984369578019572e-21,
|
|
31
|
+
15056327351493116e-23
|
|
32
|
+
];
|
|
33
|
+
function lgamma(x) {
|
|
34
|
+
if (!Number.isFinite(x) || x <= 0) {
|
|
35
|
+
throw new BayesOutputGateError(
|
|
36
|
+
"NUMERIC",
|
|
37
|
+
`lgamma requires a positive finite argument, got ${x}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const z = x - 1;
|
|
41
|
+
let acc = LANCZOS_COEFFICIENTS[0];
|
|
42
|
+
for (let i = 1; i < LANCZOS_COEFFICIENTS.length; i++) {
|
|
43
|
+
acc += LANCZOS_COEFFICIENTS[i] / (z + i);
|
|
44
|
+
}
|
|
45
|
+
const t = z + LANCZOS_G + 0.5;
|
|
46
|
+
return LN_SQRT_2PI + (z + 0.5) * Math.log(t) - t + Math.log(acc);
|
|
47
|
+
}
|
|
48
|
+
function lbeta(a, b) {
|
|
49
|
+
return lgamma(a) + lgamma(b) - lgamma(a + b);
|
|
50
|
+
}
|
|
51
|
+
function betaLogDensity(x, a, b) {
|
|
52
|
+
if (a <= 0 || b <= 0) {
|
|
53
|
+
throw new BayesOutputGateError(
|
|
54
|
+
"NUMERIC",
|
|
55
|
+
`Beta shape parameters must be positive, got a=${a}, b=${b}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (x < 0 || x > 1 || !Number.isFinite(x)) {
|
|
59
|
+
return Number.NEGATIVE_INFINITY;
|
|
60
|
+
}
|
|
61
|
+
if (x === 0) {
|
|
62
|
+
if (a < 1) return Number.POSITIVE_INFINITY;
|
|
63
|
+
if (a > 1) return Number.NEGATIVE_INFINITY;
|
|
64
|
+
return -lbeta(a, b) + (b - 1) * Math.log(1);
|
|
65
|
+
}
|
|
66
|
+
if (x === 1) {
|
|
67
|
+
if (b < 1) return Number.POSITIVE_INFINITY;
|
|
68
|
+
if (b > 1) return Number.NEGATIVE_INFINITY;
|
|
69
|
+
return -lbeta(a, b);
|
|
70
|
+
}
|
|
71
|
+
return (a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - lbeta(a, b);
|
|
72
|
+
}
|
|
73
|
+
function clamp(x, lo, hi) {
|
|
74
|
+
if (x < lo) return lo;
|
|
75
|
+
if (x > hi) return hi;
|
|
76
|
+
return x;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/bayesfactor/index.ts
|
|
80
|
+
var LN_3 = Math.log(3);
|
|
81
|
+
var LN_10 = Math.log(10);
|
|
82
|
+
var LN_100 = Math.log(100);
|
|
83
|
+
var DENSITY_EPSILON = 1e-6;
|
|
84
|
+
function jeffreysStrength(logBayesFactor) {
|
|
85
|
+
const x = logBayesFactor;
|
|
86
|
+
if (x >= LN_100) return "decisive-high";
|
|
87
|
+
if (x >= LN_10) return "strong-high";
|
|
88
|
+
if (x >= LN_3) return "substantial-high";
|
|
89
|
+
if (x > -LN_3) return "inconclusive";
|
|
90
|
+
if (x > -LN_10) return "substantial-low";
|
|
91
|
+
if (x > -LN_100) return "strong-low";
|
|
92
|
+
return "decisive-low";
|
|
93
|
+
}
|
|
94
|
+
function bayesFactor(scores, models) {
|
|
95
|
+
invariant(models.length > 0, "INVALID_CONFIG", "at least one dimension model is required");
|
|
96
|
+
const map = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const s of scores) {
|
|
98
|
+
invariant(
|
|
99
|
+
Number.isFinite(s.value) && s.value >= 0 && s.value <= 1,
|
|
100
|
+
"INVALID_SCORE",
|
|
101
|
+
`score for "${s.dimension}" must be a finite number in [0, 1], got ${s.value}`
|
|
102
|
+
);
|
|
103
|
+
invariant(
|
|
104
|
+
!map.has(s.dimension),
|
|
105
|
+
"INVALID_SCORE",
|
|
106
|
+
`duplicate score for dimension "${s.dimension}"`
|
|
107
|
+
);
|
|
108
|
+
map.set(s.dimension, s.value);
|
|
109
|
+
}
|
|
110
|
+
const contributions = [];
|
|
111
|
+
let logBayesFactor = 0;
|
|
112
|
+
for (const model of models) {
|
|
113
|
+
const value = map.get(model.dimension);
|
|
114
|
+
if (value === void 0) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
invariant(
|
|
118
|
+
Number.isFinite(model.weight) && model.weight >= 0,
|
|
119
|
+
"INVALID_CONFIG",
|
|
120
|
+
`weight for "${model.dimension}" must be a non-negative finite number, got ${model.weight}`
|
|
121
|
+
);
|
|
122
|
+
const x = clamp(value, DENSITY_EPSILON, 1 - DENSITY_EPSILON);
|
|
123
|
+
const logHigh = betaLogDensity(x, model.high.a, model.high.b);
|
|
124
|
+
const logLow = betaLogDensity(x, model.low.a, model.low.b);
|
|
125
|
+
const weighted = model.weight * (logHigh - logLow);
|
|
126
|
+
logBayesFactor += weighted;
|
|
127
|
+
contributions.push({
|
|
128
|
+
dimension: model.dimension,
|
|
129
|
+
score: value,
|
|
130
|
+
logBayesFactor: weighted,
|
|
131
|
+
weight: model.weight
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
invariant(
|
|
135
|
+
contributions.length > 0,
|
|
136
|
+
"INVALID_SCORE",
|
|
137
|
+
"no scored dimension matched a configured dimension model"
|
|
138
|
+
);
|
|
139
|
+
invariant(Number.isFinite(logBayesFactor), "NUMERIC", "combined log-Bayes-Factor is not finite");
|
|
140
|
+
const strength = jeffreysStrength(logBayesFactor);
|
|
141
|
+
const favored = strength.endsWith("high") ? "high" : strength.endsWith("low") ? "low" : "inconclusive";
|
|
142
|
+
return {
|
|
143
|
+
logBayesFactor,
|
|
144
|
+
bayesFactor: Math.exp(logBayesFactor),
|
|
145
|
+
favored,
|
|
146
|
+
strength,
|
|
147
|
+
contributions
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
exports.bayesFactor = bayesFactor;
|
|
152
|
+
exports.jeffreysStrength = jeffreysStrength;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { S as ScoreVector, e as DimensionModel, a as BayesFactorResult, E as EvidenceStrength } from '../types-bMjn1j4e.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Map a natural-log Bayes Factor to a Jeffreys-scale strength category (Jeffreys 1961, the symmetric
|
|
5
|
+
* boundaries 3, 10, 100). Positive favors high quality, negative favors low quality.
|
|
6
|
+
*/
|
|
7
|
+
declare function jeffreysStrength(logBayesFactor: number): EvidenceStrength;
|
|
8
|
+
/**
|
|
9
|
+
* Weigh an output's scores against the high-quality and low-quality hypotheses. The combined
|
|
10
|
+
* log-Bayes-Factor is the weighted sum of per-dimension log-density differences. Only dimensions
|
|
11
|
+
* that have both a score and a model contribute.
|
|
12
|
+
*/
|
|
13
|
+
declare function bayesFactor(scores: ScoreVector, models: readonly DimensionModel[]): BayesFactorResult;
|
|
14
|
+
|
|
15
|
+
export { bayesFactor, jeffreysStrength };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { S as ScoreVector, e as DimensionModel, a as BayesFactorResult, E as EvidenceStrength } from '../types-bMjn1j4e.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Map a natural-log Bayes Factor to a Jeffreys-scale strength category (Jeffreys 1961, the symmetric
|
|
5
|
+
* boundaries 3, 10, 100). Positive favors high quality, negative favors low quality.
|
|
6
|
+
*/
|
|
7
|
+
declare function jeffreysStrength(logBayesFactor: number): EvidenceStrength;
|
|
8
|
+
/**
|
|
9
|
+
* Weigh an output's scores against the high-quality and low-quality hypotheses. The combined
|
|
10
|
+
* log-Bayes-Factor is the weighted sum of per-dimension log-density differences. Only dimensions
|
|
11
|
+
* that have both a score and a model contribute.
|
|
12
|
+
*/
|
|
13
|
+
declare function bayesFactor(scores: ScoreVector, models: readonly DimensionModel[]): BayesFactorResult;
|
|
14
|
+
|
|
15
|
+
export { bayesFactor, jeffreysStrength };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var BayesOutputGateError = class _BayesOutputGateError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "BayesOutputGateError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
Object.setPrototypeOf(this, _BayesOutputGateError.prototype);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
function invariant(condition, code, message) {
|
|
12
|
+
if (!condition) {
|
|
13
|
+
throw new BayesOutputGateError(code, message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/mathspecial.ts
|
|
18
|
+
var LN_SQRT_2PI = 0.9189385332046728;
|
|
19
|
+
var LANCZOS_G = 7;
|
|
20
|
+
var LANCZOS_COEFFICIENTS = [
|
|
21
|
+
0.9999999999998099,
|
|
22
|
+
676.5203681218851,
|
|
23
|
+
-1259.1392167224028,
|
|
24
|
+
771.3234287776531,
|
|
25
|
+
-176.6150291621406,
|
|
26
|
+
12.507343278686905,
|
|
27
|
+
-0.13857109526572012,
|
|
28
|
+
9984369578019572e-21,
|
|
29
|
+
15056327351493116e-23
|
|
30
|
+
];
|
|
31
|
+
function lgamma(x) {
|
|
32
|
+
if (!Number.isFinite(x) || x <= 0) {
|
|
33
|
+
throw new BayesOutputGateError(
|
|
34
|
+
"NUMERIC",
|
|
35
|
+
`lgamma requires a positive finite argument, got ${x}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const z = x - 1;
|
|
39
|
+
let acc = LANCZOS_COEFFICIENTS[0];
|
|
40
|
+
for (let i = 1; i < LANCZOS_COEFFICIENTS.length; i++) {
|
|
41
|
+
acc += LANCZOS_COEFFICIENTS[i] / (z + i);
|
|
42
|
+
}
|
|
43
|
+
const t = z + LANCZOS_G + 0.5;
|
|
44
|
+
return LN_SQRT_2PI + (z + 0.5) * Math.log(t) - t + Math.log(acc);
|
|
45
|
+
}
|
|
46
|
+
function lbeta(a, b) {
|
|
47
|
+
return lgamma(a) + lgamma(b) - lgamma(a + b);
|
|
48
|
+
}
|
|
49
|
+
function betaLogDensity(x, a, b) {
|
|
50
|
+
if (a <= 0 || b <= 0) {
|
|
51
|
+
throw new BayesOutputGateError(
|
|
52
|
+
"NUMERIC",
|
|
53
|
+
`Beta shape parameters must be positive, got a=${a}, b=${b}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (x < 0 || x > 1 || !Number.isFinite(x)) {
|
|
57
|
+
return Number.NEGATIVE_INFINITY;
|
|
58
|
+
}
|
|
59
|
+
if (x === 0) {
|
|
60
|
+
if (a < 1) return Number.POSITIVE_INFINITY;
|
|
61
|
+
if (a > 1) return Number.NEGATIVE_INFINITY;
|
|
62
|
+
return -lbeta(a, b) + (b - 1) * Math.log(1);
|
|
63
|
+
}
|
|
64
|
+
if (x === 1) {
|
|
65
|
+
if (b < 1) return Number.POSITIVE_INFINITY;
|
|
66
|
+
if (b > 1) return Number.NEGATIVE_INFINITY;
|
|
67
|
+
return -lbeta(a, b);
|
|
68
|
+
}
|
|
69
|
+
return (a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - lbeta(a, b);
|
|
70
|
+
}
|
|
71
|
+
function clamp(x, lo, hi) {
|
|
72
|
+
if (x < lo) return lo;
|
|
73
|
+
if (x > hi) return hi;
|
|
74
|
+
return x;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/bayesfactor/index.ts
|
|
78
|
+
var LN_3 = Math.log(3);
|
|
79
|
+
var LN_10 = Math.log(10);
|
|
80
|
+
var LN_100 = Math.log(100);
|
|
81
|
+
var DENSITY_EPSILON = 1e-6;
|
|
82
|
+
function jeffreysStrength(logBayesFactor) {
|
|
83
|
+
const x = logBayesFactor;
|
|
84
|
+
if (x >= LN_100) return "decisive-high";
|
|
85
|
+
if (x >= LN_10) return "strong-high";
|
|
86
|
+
if (x >= LN_3) return "substantial-high";
|
|
87
|
+
if (x > -LN_3) return "inconclusive";
|
|
88
|
+
if (x > -LN_10) return "substantial-low";
|
|
89
|
+
if (x > -LN_100) return "strong-low";
|
|
90
|
+
return "decisive-low";
|
|
91
|
+
}
|
|
92
|
+
function bayesFactor(scores, models) {
|
|
93
|
+
invariant(models.length > 0, "INVALID_CONFIG", "at least one dimension model is required");
|
|
94
|
+
const map = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const s of scores) {
|
|
96
|
+
invariant(
|
|
97
|
+
Number.isFinite(s.value) && s.value >= 0 && s.value <= 1,
|
|
98
|
+
"INVALID_SCORE",
|
|
99
|
+
`score for "${s.dimension}" must be a finite number in [0, 1], got ${s.value}`
|
|
100
|
+
);
|
|
101
|
+
invariant(
|
|
102
|
+
!map.has(s.dimension),
|
|
103
|
+
"INVALID_SCORE",
|
|
104
|
+
`duplicate score for dimension "${s.dimension}"`
|
|
105
|
+
);
|
|
106
|
+
map.set(s.dimension, s.value);
|
|
107
|
+
}
|
|
108
|
+
const contributions = [];
|
|
109
|
+
let logBayesFactor = 0;
|
|
110
|
+
for (const model of models) {
|
|
111
|
+
const value = map.get(model.dimension);
|
|
112
|
+
if (value === void 0) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
invariant(
|
|
116
|
+
Number.isFinite(model.weight) && model.weight >= 0,
|
|
117
|
+
"INVALID_CONFIG",
|
|
118
|
+
`weight for "${model.dimension}" must be a non-negative finite number, got ${model.weight}`
|
|
119
|
+
);
|
|
120
|
+
const x = clamp(value, DENSITY_EPSILON, 1 - DENSITY_EPSILON);
|
|
121
|
+
const logHigh = betaLogDensity(x, model.high.a, model.high.b);
|
|
122
|
+
const logLow = betaLogDensity(x, model.low.a, model.low.b);
|
|
123
|
+
const weighted = model.weight * (logHigh - logLow);
|
|
124
|
+
logBayesFactor += weighted;
|
|
125
|
+
contributions.push({
|
|
126
|
+
dimension: model.dimension,
|
|
127
|
+
score: value,
|
|
128
|
+
logBayesFactor: weighted,
|
|
129
|
+
weight: model.weight
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
invariant(
|
|
133
|
+
contributions.length > 0,
|
|
134
|
+
"INVALID_SCORE",
|
|
135
|
+
"no scored dimension matched a configured dimension model"
|
|
136
|
+
);
|
|
137
|
+
invariant(Number.isFinite(logBayesFactor), "NUMERIC", "combined log-Bayes-Factor is not finite");
|
|
138
|
+
const strength = jeffreysStrength(logBayesFactor);
|
|
139
|
+
const favored = strength.endsWith("high") ? "high" : strength.endsWith("low") ? "low" : "inconclusive";
|
|
140
|
+
return {
|
|
141
|
+
logBayesFactor,
|
|
142
|
+
bayesFactor: Math.exp(logBayesFactor),
|
|
143
|
+
favored,
|
|
144
|
+
strength,
|
|
145
|
+
contributions
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { bayesFactor, jeffreysStrength };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var BayesOutputGateError = class _BayesOutputGateError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "BayesOutputGateError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
Object.setPrototypeOf(this, _BayesOutputGateError.prototype);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function invariant(condition, code, message) {
|
|
14
|
+
if (!condition) {
|
|
15
|
+
throw new BayesOutputGateError(code, message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/mathspecial.ts
|
|
20
|
+
var LN_SQRT_2PI = 0.9189385332046728;
|
|
21
|
+
var LANCZOS_G = 7;
|
|
22
|
+
var LANCZOS_COEFFICIENTS = [
|
|
23
|
+
0.9999999999998099,
|
|
24
|
+
676.5203681218851,
|
|
25
|
+
-1259.1392167224028,
|
|
26
|
+
771.3234287776531,
|
|
27
|
+
-176.6150291621406,
|
|
28
|
+
12.507343278686905,
|
|
29
|
+
-0.13857109526572012,
|
|
30
|
+
9984369578019572e-21,
|
|
31
|
+
15056327351493116e-23
|
|
32
|
+
];
|
|
33
|
+
function lgamma(x) {
|
|
34
|
+
if (!Number.isFinite(x) || x <= 0) {
|
|
35
|
+
throw new BayesOutputGateError(
|
|
36
|
+
"NUMERIC",
|
|
37
|
+
`lgamma requires a positive finite argument, got ${x}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const z = x - 1;
|
|
41
|
+
let acc = LANCZOS_COEFFICIENTS[0];
|
|
42
|
+
for (let i = 1; i < LANCZOS_COEFFICIENTS.length; i++) {
|
|
43
|
+
acc += LANCZOS_COEFFICIENTS[i] / (z + i);
|
|
44
|
+
}
|
|
45
|
+
const t = z + LANCZOS_G + 0.5;
|
|
46
|
+
return LN_SQRT_2PI + (z + 0.5) * Math.log(t) - t + Math.log(acc);
|
|
47
|
+
}
|
|
48
|
+
function lbeta(a, b) {
|
|
49
|
+
return lgamma(a) + lgamma(b) - lgamma(a + b);
|
|
50
|
+
}
|
|
51
|
+
function betaLogDensity(x, a, b) {
|
|
52
|
+
if (a <= 0 || b <= 0) {
|
|
53
|
+
throw new BayesOutputGateError(
|
|
54
|
+
"NUMERIC",
|
|
55
|
+
`Beta shape parameters must be positive, got a=${a}, b=${b}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (x < 0 || x > 1 || !Number.isFinite(x)) {
|
|
59
|
+
return Number.NEGATIVE_INFINITY;
|
|
60
|
+
}
|
|
61
|
+
if (x === 0) {
|
|
62
|
+
if (a < 1) return Number.POSITIVE_INFINITY;
|
|
63
|
+
if (a > 1) return Number.NEGATIVE_INFINITY;
|
|
64
|
+
return -lbeta(a, b) + (b - 1) * Math.log(1);
|
|
65
|
+
}
|
|
66
|
+
if (x === 1) {
|
|
67
|
+
if (b < 1) return Number.POSITIVE_INFINITY;
|
|
68
|
+
if (b > 1) return Number.NEGATIVE_INFINITY;
|
|
69
|
+
return -lbeta(a, b);
|
|
70
|
+
}
|
|
71
|
+
return (a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - lbeta(a, b);
|
|
72
|
+
}
|
|
73
|
+
function clamp(x, lo, hi) {
|
|
74
|
+
if (x < lo) return lo;
|
|
75
|
+
if (x > hi) return hi;
|
|
76
|
+
return x;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/beta/index.ts
|
|
80
|
+
var VARIANCE_FLOOR = 1e-9;
|
|
81
|
+
var CONCENTRATION_FLOOR = 1e-3;
|
|
82
|
+
var BetaModel = class _BetaModel {
|
|
83
|
+
priorA;
|
|
84
|
+
priorB;
|
|
85
|
+
count = 0;
|
|
86
|
+
sum = 0;
|
|
87
|
+
sumSquares = 0;
|
|
88
|
+
constructor(options = {}) {
|
|
89
|
+
const prior = options.prior ?? { a: 1, b: 1 };
|
|
90
|
+
invariant(
|
|
91
|
+
prior.a > 0 && prior.b > 0 && Number.isFinite(prior.a) && Number.isFinite(prior.b),
|
|
92
|
+
"INVALID_CONFIG",
|
|
93
|
+
`prior Beta parameters must be positive and finite, got a=${prior.a}, b=${prior.b}`
|
|
94
|
+
);
|
|
95
|
+
this.priorA = prior.a;
|
|
96
|
+
this.priorB = prior.b;
|
|
97
|
+
}
|
|
98
|
+
/** Build a model from labeled scores in one pass. */
|
|
99
|
+
static fromSamples(samples, options = {}) {
|
|
100
|
+
const model = new _BetaModel(options);
|
|
101
|
+
for (const s of samples) {
|
|
102
|
+
model.observe(s);
|
|
103
|
+
}
|
|
104
|
+
return model;
|
|
105
|
+
}
|
|
106
|
+
/** Restore a model from a snapshot. */
|
|
107
|
+
static fromSnapshot(snapshot) {
|
|
108
|
+
const model = new _BetaModel({ prior: snapshot.prior });
|
|
109
|
+
invariant(
|
|
110
|
+
snapshot.count >= 0 && Number.isFinite(snapshot.sum) && Number.isFinite(snapshot.sumSquares),
|
|
111
|
+
"INVALID_SNAPSHOT",
|
|
112
|
+
"snapshot fields must be finite and non-negative"
|
|
113
|
+
);
|
|
114
|
+
model.count = snapshot.count;
|
|
115
|
+
model.sum = snapshot.sum;
|
|
116
|
+
model.sumSquares = snapshot.sumSquares;
|
|
117
|
+
return model;
|
|
118
|
+
}
|
|
119
|
+
/** Number of real observations folded into this model so far. */
|
|
120
|
+
get observations() {
|
|
121
|
+
return this.count;
|
|
122
|
+
}
|
|
123
|
+
/** Fold a single score in [0, 1] into the model, updating its calibration online. */
|
|
124
|
+
observe(score) {
|
|
125
|
+
invariant(
|
|
126
|
+
Number.isFinite(score) && score >= 0 && score <= 1,
|
|
127
|
+
"INVALID_SCORE",
|
|
128
|
+
`score must be a finite number in [0, 1], got ${score}`
|
|
129
|
+
);
|
|
130
|
+
this.count += 1;
|
|
131
|
+
this.sum += score;
|
|
132
|
+
this.sumSquares += score * score;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The fitted Beta parameters. The prior is mixed in as `priorA + priorB` pseudo-observations with
|
|
137
|
+
* the prior's own mean and variance, then a and b come from method of moments on the blend, so a
|
|
138
|
+
* cold model returns its prior and a warm model is data-driven.
|
|
139
|
+
*/
|
|
140
|
+
params() {
|
|
141
|
+
const priorStrength = this.priorA + this.priorB;
|
|
142
|
+
const priorMean = this.priorA / priorStrength;
|
|
143
|
+
const priorVariance = priorMean * (1 - priorMean) / (priorStrength + 1);
|
|
144
|
+
const totalCount = this.count + priorStrength;
|
|
145
|
+
const totalSum = this.sum + priorStrength * priorMean;
|
|
146
|
+
const totalSumSquares = this.sumSquares + priorStrength * (priorVariance + priorMean * priorMean);
|
|
147
|
+
const mean = clamp(totalSum / totalCount, VARIANCE_FLOOR, 1 - VARIANCE_FLOOR);
|
|
148
|
+
const rawVariance = totalSumSquares / totalCount - mean * mean;
|
|
149
|
+
const maxVariance = mean * (1 - mean);
|
|
150
|
+
const variance = clamp(rawVariance, VARIANCE_FLOOR, maxVariance * (1 - VARIANCE_FLOOR));
|
|
151
|
+
const concentration = Math.max(mean * (1 - mean) / variance - 1, CONCENTRATION_FLOOR);
|
|
152
|
+
const a = Math.max(mean * concentration, CONCENTRATION_FLOOR);
|
|
153
|
+
const b = Math.max((1 - mean) * concentration, CONCENTRATION_FLOOR);
|
|
154
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) {
|
|
155
|
+
throw new BayesOutputGateError("NUMERIC", "Beta fit produced non-finite parameters");
|
|
156
|
+
}
|
|
157
|
+
return { a, b };
|
|
158
|
+
}
|
|
159
|
+
/** Mean of the fitted Beta. */
|
|
160
|
+
mean() {
|
|
161
|
+
const { a, b } = this.params();
|
|
162
|
+
return a / (a + b);
|
|
163
|
+
}
|
|
164
|
+
/** Log-density of an observed score under the fitted Beta. */
|
|
165
|
+
logDensity(score) {
|
|
166
|
+
const { a, b } = this.params();
|
|
167
|
+
return betaLogDensity(score, a, b);
|
|
168
|
+
}
|
|
169
|
+
/** A serializable snapshot of the current state. */
|
|
170
|
+
snapshot() {
|
|
171
|
+
return {
|
|
172
|
+
count: this.count,
|
|
173
|
+
sum: this.sum,
|
|
174
|
+
sumSquares: this.sumSquares,
|
|
175
|
+
prior: { a: this.priorA, b: this.priorB }
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
exports.BetaModel = BetaModel;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { b as BetaParams } from '../types-bMjn1j4e.cjs';
|
|
2
|
+
|
|
3
|
+
/** Options controlling the regularizing prior of a {@link BetaModel}. */
|
|
4
|
+
interface BetaModelOptions {
|
|
5
|
+
/** Prior Beta acting as a regularizer. Defaults to the uniform Beta(1, 1). */
|
|
6
|
+
readonly prior?: BetaParams;
|
|
7
|
+
}
|
|
8
|
+
/** A serializable snapshot of a {@link BetaModel}, for persistence across processes. */
|
|
9
|
+
interface BetaModelSnapshot {
|
|
10
|
+
readonly count: number;
|
|
11
|
+
readonly sum: number;
|
|
12
|
+
readonly sumSquares: number;
|
|
13
|
+
readonly prior: BetaParams;
|
|
14
|
+
}
|
|
15
|
+
/** A calibrated, online-updatable Beta model of a score under one hypothesis. */
|
|
16
|
+
declare class BetaModel {
|
|
17
|
+
private readonly priorA;
|
|
18
|
+
private readonly priorB;
|
|
19
|
+
private count;
|
|
20
|
+
private sum;
|
|
21
|
+
private sumSquares;
|
|
22
|
+
constructor(options?: BetaModelOptions);
|
|
23
|
+
/** Build a model from labeled scores in one pass. */
|
|
24
|
+
static fromSamples(samples: Iterable<number>, options?: BetaModelOptions): BetaModel;
|
|
25
|
+
/** Restore a model from a snapshot. */
|
|
26
|
+
static fromSnapshot(snapshot: BetaModelSnapshot): BetaModel;
|
|
27
|
+
/** Number of real observations folded into this model so far. */
|
|
28
|
+
get observations(): number;
|
|
29
|
+
/** Fold a single score in [0, 1] into the model, updating its calibration online. */
|
|
30
|
+
observe(score: number): this;
|
|
31
|
+
/**
|
|
32
|
+
* The fitted Beta parameters. The prior is mixed in as `priorA + priorB` pseudo-observations with
|
|
33
|
+
* the prior's own mean and variance, then a and b come from method of moments on the blend, so a
|
|
34
|
+
* cold model returns its prior and a warm model is data-driven.
|
|
35
|
+
*/
|
|
36
|
+
params(): BetaParams;
|
|
37
|
+
/** Mean of the fitted Beta. */
|
|
38
|
+
mean(): number;
|
|
39
|
+
/** Log-density of an observed score under the fitted Beta. */
|
|
40
|
+
logDensity(score: number): number;
|
|
41
|
+
/** A serializable snapshot of the current state. */
|
|
42
|
+
snapshot(): BetaModelSnapshot;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { BetaModel, type BetaModelOptions, type BetaModelSnapshot };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { b as BetaParams } from '../types-bMjn1j4e.js';
|
|
2
|
+
|
|
3
|
+
/** Options controlling the regularizing prior of a {@link BetaModel}. */
|
|
4
|
+
interface BetaModelOptions {
|
|
5
|
+
/** Prior Beta acting as a regularizer. Defaults to the uniform Beta(1, 1). */
|
|
6
|
+
readonly prior?: BetaParams;
|
|
7
|
+
}
|
|
8
|
+
/** A serializable snapshot of a {@link BetaModel}, for persistence across processes. */
|
|
9
|
+
interface BetaModelSnapshot {
|
|
10
|
+
readonly count: number;
|
|
11
|
+
readonly sum: number;
|
|
12
|
+
readonly sumSquares: number;
|
|
13
|
+
readonly prior: BetaParams;
|
|
14
|
+
}
|
|
15
|
+
/** A calibrated, online-updatable Beta model of a score under one hypothesis. */
|
|
16
|
+
declare class BetaModel {
|
|
17
|
+
private readonly priorA;
|
|
18
|
+
private readonly priorB;
|
|
19
|
+
private count;
|
|
20
|
+
private sum;
|
|
21
|
+
private sumSquares;
|
|
22
|
+
constructor(options?: BetaModelOptions);
|
|
23
|
+
/** Build a model from labeled scores in one pass. */
|
|
24
|
+
static fromSamples(samples: Iterable<number>, options?: BetaModelOptions): BetaModel;
|
|
25
|
+
/** Restore a model from a snapshot. */
|
|
26
|
+
static fromSnapshot(snapshot: BetaModelSnapshot): BetaModel;
|
|
27
|
+
/** Number of real observations folded into this model so far. */
|
|
28
|
+
get observations(): number;
|
|
29
|
+
/** Fold a single score in [0, 1] into the model, updating its calibration online. */
|
|
30
|
+
observe(score: number): this;
|
|
31
|
+
/**
|
|
32
|
+
* The fitted Beta parameters. The prior is mixed in as `priorA + priorB` pseudo-observations with
|
|
33
|
+
* the prior's own mean and variance, then a and b come from method of moments on the blend, so a
|
|
34
|
+
* cold model returns its prior and a warm model is data-driven.
|
|
35
|
+
*/
|
|
36
|
+
params(): BetaParams;
|
|
37
|
+
/** Mean of the fitted Beta. */
|
|
38
|
+
mean(): number;
|
|
39
|
+
/** Log-density of an observed score under the fitted Beta. */
|
|
40
|
+
logDensity(score: number): number;
|
|
41
|
+
/** A serializable snapshot of the current state. */
|
|
42
|
+
snapshot(): BetaModelSnapshot;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { BetaModel, type BetaModelOptions, type BetaModelSnapshot };
|