@cavelang/fusion 0.34.0 → 0.36.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 +144 -5
- package/dist/src/conditional.d.ts.map +1 -1
- package/dist/src/conditional.js +32 -3
- package/dist/src/conditional.js.map +1 -1
- package/dist/src/fuse.d.ts +4 -3
- package/dist/src/fuse.d.ts.map +1 -1
- package/dist/src/fuse.js +192 -29
- package/dist/src/fuse.js.map +1 -1
- package/dist/src/validate.d.ts +3 -0
- package/dist/src/validate.d.ts.map +1 -0
- package/dist/src/validate.js +7 -0
- package/dist/src/validate.js.map +1 -0
- package/package.json +3 -3
- package/src/conditional.ts +34 -5
- package/src/fuse.ts +181 -32
- package/src/validate.ts +6 -0
package/README.md
CHANGED
|
@@ -24,18 +24,103 @@ For normally distributed estimates with `+/- Δ` at kσ: σ = Δ/k, precision
|
|
|
24
24
|
- posterior mean μ = Σ wᵢxᵢ / Σ wᵢ
|
|
25
25
|
- posterior σ = 1 / √(Σ wᵢ)
|
|
26
26
|
|
|
27
|
-
`estimateOf(claim)` extracts `{ mean, sigma, conf }`
|
|
28
|
-
carrying `+/-` uncertainty (attribute or metric payload)
|
|
29
|
-
|
|
27
|
+
`estimateOf(claim)` extracts `{ mean, sigma, conf }` plus the optional `unit`
|
|
28
|
+
from a numeric claim carrying `+/-` uncertainty (attribute or metric payload).
|
|
29
|
+
It returns `undefined` without a numeric payload or uncertainty. `fuseClaims`
|
|
30
|
+
omits those claims, and fusion skips zero-confidence estimates after validation.
|
|
31
|
+
Extraction validates the returned estimate's finite mean and confidence in
|
|
32
|
+
`[0, 1]`, including structurally constructed claims that bypass `Claim.of`.
|
|
33
|
+
Invalid values throw `RangeError` even at zero confidence; a valid
|
|
34
|
+
zero-confidence claim still extracts an estimate, which `fuse` subsequently skips.
|
|
35
|
+
These helpers operate on the supplied occurrences: they do not resolve store
|
|
36
|
+
history, deduplicate claims or group source contexts. Supplying an old estimate
|
|
37
|
+
beside its zero-confidence retraction still includes the old estimate. Repeating
|
|
38
|
+
an estimate four times contributes four times its precision and halves its
|
|
39
|
+
posterior sigma. Select current evidence for the intended snapshot and quantity,
|
|
40
|
+
and account for repeated or correlated observations before calling `fuseClaims`.
|
|
41
|
+
Invalid zero,
|
|
30
42
|
negative, or non-finite σ values throw the same typed core uncertainty error
|
|
31
|
-
used by parsed and constructed claims.
|
|
43
|
+
used by parsed and constructed claims. `estimateOf` also validates sigma after
|
|
44
|
+
converting the uncertainty unit to the value's unit: overflow or underflow
|
|
45
|
+
throws `Uncertainty.InvalidUncertaintyError` instead of returning an infinite
|
|
46
|
+
or zero spread. Finite, positive converted spreads remain valid, including
|
|
47
|
+
representable subnormal values. The spec's worked example is a test
|
|
32
48
|
case verbatim.
|
|
33
49
|
|
|
50
|
+
`estimateOf` captures the payload's numeric value and unit and the uncertainty's
|
|
51
|
+
magnitude and unit once. Sigma calculation and unit conversion use those captured
|
|
52
|
+
fields, so changing getters cannot mix separate uncertainty observations.
|
|
53
|
+
|
|
54
|
+
`fuse` captures each estimate's mean, sigma, confidence and unit once before
|
|
55
|
+
validation and calculation. `noisyAndIndependent` validates and multiplies each
|
|
56
|
+
condition value in one pass, still validating later conditions after a zero.
|
|
57
|
+
`normalizeHypotheses` captures each confidence and its enumerable metadata before
|
|
58
|
+
normalizing. Getter-backed inputs therefore cannot substitute an unvalidated
|
|
59
|
+
second value during calculation. Inherited confidence fields and enumerable
|
|
60
|
+
symbol metadata retain their normal meaning; normalization returns fresh objects.
|
|
61
|
+
|
|
62
|
+
All confidence inputs to fusion and the conditional/hypothesis helpers must be
|
|
63
|
+
finite probabilities in `[0, 1]`; invalid values throw `RangeError`. Estimate
|
|
64
|
+
means must also be finite. Every estimate is validated before zero-confidence
|
|
65
|
+
entries are skipped. Omitted or `undefined` estimate confidence defaults to 1;
|
|
66
|
+
explicit `null` is invalid. A supplied estimate unit must be a string, otherwise
|
|
67
|
+
validation throws `TypeError`, even on a zero-confidence estimate. Arbitrary
|
|
68
|
+
string units retain the exact-match rules below.
|
|
69
|
+
Fusion normalizes weights and means to avoid intermediate
|
|
70
|
+
overflow and uses compensated summation for both total weight and posterior mean
|
|
71
|
+
contributions. This retains the combined precision of many weak estimates beside
|
|
72
|
+
a strong estimate and smaller mean contributions beside large opposing estimates.
|
|
73
|
+
Subnormal means are scaled by a power of two before weighting, and a finite
|
|
74
|
+
compensated weighted sum is divided only once. This avoids erasing representable
|
|
75
|
+
tiny averages through premature rounding, including equal means with unequal
|
|
76
|
+
weights and small residuals after cancellation. If the unnormalized weighted
|
|
77
|
+
sum overflows, or subnormal weighted products contribute to a subnormal provisional
|
|
78
|
+
mean, a fallback sums binary64 mean and normalized-weight products as
|
|
79
|
+
exact integers and rounds their quotient once, with halfway cases rounded to
|
|
80
|
+
even. This preserves subnormal residuals even when large estimates cancel and
|
|
81
|
+
individual weighted contributions would round to zero. It also corrects early
|
|
82
|
+
rounding of nonzero subnormal products after cancellation, even when the sum
|
|
83
|
+
never overflows. Other finite sums retain the compensated floating-point path. The fallback does not make unit
|
|
84
|
+
conversion, weight calculation or the entire fusion procedure exact.
|
|
85
|
+
Its integer operands come from finite binary64 means and normalized weights,
|
|
86
|
+
so their widths are bounded by binary64 exponents rather than arbitrary-length
|
|
87
|
+
numeric text. Summing more terms can increase the accumulator width; traversal,
|
|
88
|
+
temporary storage and exact arithmetic still cost more as the estimate count
|
|
89
|
+
grows. These pure synchronous helpers impose no estimate-count limit,
|
|
90
|
+
cancellation check or wall-clock deadline. Callers managing large evidence sets
|
|
91
|
+
must bound that work before calling them.
|
|
92
|
+
The returned mean is bounded to the smallest and largest converted means with
|
|
93
|
+
positive weight, preventing roundoff from extrapolating beyond the evidence.
|
|
94
|
+
In particular, identical converted means retain that exact mean regardless of
|
|
95
|
+
their relative weights.
|
|
96
|
+
After validating all estimates and calculating the posterior precision and spread,
|
|
97
|
+
fusion returns an identical converted mean directly. Mixed signed zeros keep the
|
|
98
|
+
general path. This avoids unnecessary weighted sums and exact fallback work,
|
|
99
|
+
while retaining invalid-input and unrepresentable-precision errors.
|
|
100
|
+
When an individual root precision becomes subnormal or underflows during its
|
|
101
|
+
initial division, normalization uses its separate confidence and spread factors
|
|
102
|
+
when their combined denominator is finite. A representable
|
|
103
|
+
mean contribution can therefore survive even when that unnormalized root
|
|
104
|
+
precision cannot be represented on its own.
|
|
105
|
+
Converted estimates and the posterior's positive precision and spread
|
|
106
|
+
must fit finite JavaScript numbers, otherwise it throws `RangeError` rather than
|
|
107
|
+
returning `NaN`, infinity, or zero precision. This is a numeric representation
|
|
108
|
+
limit, not evidence that an estimate has zero confidence.
|
|
109
|
+
|
|
110
|
+
Claim, estimate, condition and hypothesis arrays must be dense. A missing array slot
|
|
111
|
+
is invalid evidence and throws, including when a preceding probability is zero.
|
|
112
|
+
Empty arrays retain their usual meanings: no conditions leave claim confidence
|
|
113
|
+
unchanged, fusion and normalization return `undefined`, and an empty hypothesis
|
|
114
|
+
set has gap `-1`.
|
|
115
|
+
|
|
34
116
|
Fusion preserves units at the library boundary. Missing units combine only
|
|
35
117
|
with missing units, arbitrary units combine only by exact equality, and the
|
|
36
|
-
fixed-duration units `ms`, `s`, `min`, and `h` convert to the first estimate's
|
|
118
|
+
fixed-duration units `ms`, `s`, `min`, and `h` convert to the first positive-confidence estimate's
|
|
37
119
|
unit before weighting. Every other mixture throws a typed `FusionUnitError`;
|
|
38
120
|
adapters surface that same failure instead of maintaining separate checks.
|
|
121
|
+
Reordering usable estimates can therefore change the output unit while retaining
|
|
122
|
+
the same physical mean, spread and precision after conversion. A zero-confidence
|
|
123
|
+
estimate does not select the output unit.
|
|
39
124
|
|
|
40
125
|
## Conditional confidence (§10.2)
|
|
41
126
|
|
|
@@ -51,8 +136,62 @@ explicit in the query engine, never silently assumed.
|
|
|
51
136
|
itself stays manual — new confidences are appended as new claims, which is
|
|
52
137
|
the append-only §9 discipline, not a computation.
|
|
53
138
|
|
|
139
|
+
Both helpers use compensated summation so many small probabilities alongside a
|
|
140
|
+
dominant hypothesis contribute to the total instead of disappearing one by one
|
|
141
|
+
through rounding. `hypothesisGap` subtracts the unit baseline before combining
|
|
142
|
+
the summation correction, preserving representable excesses and deficits that
|
|
143
|
+
would disappear if the total were first rounded near one. For example,
|
|
144
|
+
`hypothesisGap([1, 2 ** -54])` returns `2 ** -54` rather than zero. Results still
|
|
145
|
+
use JavaScript floating-point precision.
|
|
146
|
+
Normalization returns new records with updated confidences, preserving the
|
|
147
|
+
other enumerable fields and leaving the input records untouched. Empty and
|
|
148
|
+
all-zero sets return `undefined`; equal positive probabilities normalize equally,
|
|
149
|
+
including the smallest positive representable values.
|
|
150
|
+
|
|
151
|
+
## Fallback cost trial
|
|
152
|
+
|
|
153
|
+
Run `node scripts/fusion-fallback-bench.mjs` from the repository root. The trial
|
|
154
|
+
uses 128, 1,024 and 8,192 estimates, two untimed warmups and nine single-call
|
|
155
|
+
samples per fixture. Input construction and correctness assertions are outside
|
|
156
|
+
the timer. All samples, runtime details and source hashes are retained in
|
|
157
|
+
[the fusion fallback trial](../../benchmarks/fusion-fallback-review.json).
|
|
158
|
+
Sequential macOS arm64 runs gave these median milliseconds for 8,192 estimates:
|
|
159
|
+
|
|
160
|
+
| Fixture | Node 24 before | Node 24 after | Node 26 before | Node 26 after |
|
|
161
|
+
|---|---:|---:|---:|---:|
|
|
162
|
+
| Identical mean 42 | 0.923 | 0.969 | 0.847 | 0.858 |
|
|
163
|
+
| Identical MAX_VALUE | 13.770 | 1.184 | 14.013 | 0.895 |
|
|
164
|
+
| Overflow cancellation with tiny residual | 4.635 | 5.144 | 4.473 | 4.715 |
|
|
165
|
+
| Finite cancellation with tiny residual | 4.377 | 4.551 | 3.802 | 5.204 |
|
|
166
|
+
|
|
167
|
+
“Before” already includes the exact overflow and subnormal-product fallbacks;
|
|
168
|
+
“after” adds the identical-mean shortcut. Both preserve each fixture's expected
|
|
169
|
+
mean, precision and spread. The extreme identical-mean case benefits because it
|
|
170
|
+
previously entered the exact fallback. Ordinary identical means show no material
|
|
171
|
+
improvement at this size, and the unchanged cancellation paths vary between
|
|
172
|
+
runs. These fixed-order samples are bounded measurements, not a workload cap,
|
|
173
|
+
statistical confidence interval or browser performance result. The integer
|
|
174
|
+
fallback still costs more than ordinary fusion and scales with input count.
|
|
175
|
+
|
|
54
176
|
## Tests
|
|
55
177
|
|
|
178
|
+
The fusion cancellation tests compare 768 deterministic ordered cases with an
|
|
179
|
+
independent integer-weight oracle. Powers-of-two uncertainty and confidence
|
|
180
|
+
make scaled weights exact small integers; opposite large means cancel
|
|
181
|
+
analytically, leaving a rational number of `MIN_VALUE` units. The oracle rounds
|
|
182
|
+
that rational directly, without using the fallback's binary64 decomposition.
|
|
183
|
+
Cases vary signed tiny means, input order, uncertainty, confidence, cancelling
|
|
184
|
+
pair count, and large magnitudes of 1 or MAX_VALUE. They check mean and precision.
|
|
185
|
+
This validates a bounded cancellation family, not arbitrary floating-point
|
|
186
|
+
inputs, unit-conversion rounding or a general exact-fusion guarantee.
|
|
187
|
+
|
|
188
|
+
The hypothesis-gap tests compare 768 ordered near-unit calculations against
|
|
189
|
+
BigInt sums of exactly representable binary fractions. The fixtures include
|
|
190
|
+
positive, negative and zero gaps and three input orders; they independently
|
|
191
|
+
check the cancellation boundary without using the floating-point summation
|
|
192
|
+
implementation as the expected result. This is bounded numerical coverage,
|
|
193
|
+
not a claim of correctly rounded results for every possible probability set.
|
|
194
|
+
|
|
56
195
|
```
|
|
57
196
|
pnpm --filter @cavelang/fusion test
|
|
58
197
|
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conditional.d.ts","sourceRoot":"","sources":["../../src/conditional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"conditional.d.ts","sourceRoot":"","sources":["../../src/conditional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmBH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,EAAE,gBAAgB,SAAS,MAAM,EAAE,KAAG,MAQ1F,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACrE,YAAY,SAAS,CAAC,EAAE,KACvB,SAAS,GAAG,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAU7C,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,MAAM,EAAE,KAAG,MAExD,CAAA"}
|
package/dist/src/conditional.js
CHANGED
|
@@ -10,11 +10,34 @@
|
|
|
10
10
|
* silently assumed for all domains (spec §10.2); hence the deliberately
|
|
11
11
|
* loud function name.
|
|
12
12
|
*/
|
|
13
|
+
import { validateConfidence } from "./validate.js";
|
|
14
|
+
/** Neumaier summation retains small probabilities beside a dominant value. */
|
|
15
|
+
const confidenceTotal = (values, confidence, baseline = 0) => {
|
|
16
|
+
let total = 0;
|
|
17
|
+
let correction = 0;
|
|
18
|
+
for (const value of values) {
|
|
19
|
+
const conf = confidence(value);
|
|
20
|
+
validateConfidence(conf);
|
|
21
|
+
const next = total + conf;
|
|
22
|
+
correction += total >= conf ? (total - next) + conf : (conf - next) + total;
|
|
23
|
+
total = next;
|
|
24
|
+
}
|
|
25
|
+
// Subtract the baseline before rounding away a small correction near one.
|
|
26
|
+
return (total - baseline) + correction;
|
|
27
|
+
};
|
|
13
28
|
/**
|
|
14
29
|
* Noisy-AND under an explicit independence assumption:
|
|
15
30
|
* `noisyAndIndependent(0.8, [0.6])` → 0.48 (spec §10.2's example).
|
|
16
31
|
*/
|
|
17
|
-
export const noisyAndIndependent = (claimConf, conditionConfs) =>
|
|
32
|
+
export const noisyAndIndependent = (claimConf, conditionConfs) => {
|
|
33
|
+
validateConfidence(claimConf);
|
|
34
|
+
let product = claimConf;
|
|
35
|
+
for (const conf of conditionConfs) {
|
|
36
|
+
validateConfidence(conf);
|
|
37
|
+
product *= conf;
|
|
38
|
+
}
|
|
39
|
+
return product;
|
|
40
|
+
};
|
|
18
41
|
/**
|
|
19
42
|
* Normalizes an exhaustive hypothesis set so confidences sum to 1
|
|
20
43
|
* (spec §10.3: "confidences of an exhaustive hypothesis set should sum to
|
|
@@ -22,7 +45,11 @@ export const noisyAndIndependent = (claimConf, conditionConfs) => conditionConfs
|
|
|
22
45
|
* @returns `undefined` when every confidence is 0.
|
|
23
46
|
*/
|
|
24
47
|
export const normalizeHypotheses = (hypotheses) => {
|
|
25
|
-
|
|
48
|
+
hypotheses = Array.from(hypotheses, hypothesis => {
|
|
49
|
+
const { conf, ...metadata } = hypothesis;
|
|
50
|
+
return { ...metadata, conf };
|
|
51
|
+
});
|
|
52
|
+
const total = confidenceTotal(hypotheses, hypothesis => hypothesis.conf);
|
|
26
53
|
if (!(total > 0)) {
|
|
27
54
|
return undefined;
|
|
28
55
|
}
|
|
@@ -32,5 +59,7 @@ export const normalizeHypotheses = (hypotheses) => {
|
|
|
32
59
|
* @returns how far a hypothesis set is from exhaustive (Σ conf − 1);
|
|
33
60
|
* ≈0 for a well-formed set, negative when probability mass is missing.
|
|
34
61
|
*/
|
|
35
|
-
export const hypothesisGap = (confs) =>
|
|
62
|
+
export const hypothesisGap = (confs) => {
|
|
63
|
+
return confidenceTotal(confs, conf => conf, 1);
|
|
64
|
+
};
|
|
36
65
|
//# sourceMappingURL=conditional.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conditional.js","sourceRoot":"","sources":["../../src/conditional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAE,cAAiC,EAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"conditional.js","sourceRoot":"","sources":["../../src/conditional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAElD,8EAA8E;AAC9E,MAAM,eAAe,GAAG,CAAI,MAAoB,EAAE,UAAgC,EAAE,QAAQ,GAAG,CAAC,EAAU,EAAE;IAC1G,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;QAC9B,kBAAkB,CAAC,IAAI,CAAC,CAAA;QACxB,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,CAAA;QACzB,UAAU,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,KAAK,CAAA;QAC3E,KAAK,GAAG,IAAI,CAAA;IACd,CAAC;IACD,0EAA0E;IAC1E,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,UAAU,CAAA;AACxC,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAE,cAAiC,EAAU,EAAE;IAClG,kBAAkB,CAAC,SAAS,CAAC,CAAA;IAC7B,IAAI,OAAO,GAAG,SAAS,CAAA;IACvB,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,kBAAkB,CAAC,IAAI,CAAC,CAAA;QACxB,OAAO,IAAI,IAAI,CAAA;IACjB,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,UAAwB,EACuB,EAAE;IACjD,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE;QAC/C,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,GAAG,UAAU,CAAA;QACxC,OAAO,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAO,CAAA;IACnC,CAAC,CAAC,CAAA;IACF,MAAM,KAAK,GAAG,eAAe,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IACxE,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAA;AACzF,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAwB,EAAU,EAAE;IAChE,OAAO,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;AAChD,CAAC,CAAA"}
|
package/dist/src/fuse.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* CAVE itself only stores claims and metadata.
|
|
14
14
|
*/
|
|
15
15
|
import { Claim } from '@cavelang/core';
|
|
16
|
-
/** One numeric estimate: mean, σ,
|
|
16
|
+
/** One numeric estimate: finite mean, positive finite σ, confidence p ∈ [0, 1]. */
|
|
17
17
|
export type Estimate = {
|
|
18
18
|
readonly mean: number;
|
|
19
19
|
readonly sigma: number;
|
|
@@ -36,8 +36,9 @@ export declare class FusionUnitError extends Error {
|
|
|
36
36
|
constructor(units: readonly (string | undefined)[]);
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* Fuses estimates into a posterior.
|
|
40
|
-
*
|
|
39
|
+
* Fuses finite estimates into a posterior. Zero-confidence estimates are
|
|
40
|
+
* skipped after validating every input; invalid inputs and unrepresentable
|
|
41
|
+
* posteriors throw instead of returning NaN or infinity.
|
|
41
42
|
* @returns `undefined` when no estimate carries usable weight.
|
|
42
43
|
*/
|
|
43
44
|
export declare const fuse: (estimates: readonly Estimate[]) => undefined | Posterior;
|
package/dist/src/fuse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fuse.d.ts","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,EAAe,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"fuse.d.ts","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,EAAe,MAAM,gBAAgB,CAAA;AAGnD,mFAAmF;AACnF,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAWD,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qCAAqC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAiBD,gFAAgF;AAChF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAA;gBAEnC,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE;CAOnD;AAuCD;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,WAAW,SAAS,QAAQ,EAAE,KAAG,SAAS,GAAG,SAsHjE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,KAAK,CAAC,CAAC,KAAG,SAAS,GAAG,QA4BvD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,SAAS,KAAK,CAAC,CAAC,EAAE,KAAG,SAAS,GAAG,SAOnE,CAAA"}
|
package/dist/src/fuse.js
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
* CAVE itself only stores claims and metadata.
|
|
14
14
|
*/
|
|
15
15
|
import { Claim, Uncertainty } from '@cavelang/core';
|
|
16
|
+
import { validateConfidence } from "./validate.js";
|
|
17
|
+
const validateEstimate = (estimate) => {
|
|
18
|
+
Uncertainty.validateSigma(estimate.sigma);
|
|
19
|
+
validateConfidence(estimate.conf === undefined ? 1 : estimate.conf);
|
|
20
|
+
if (!Number.isFinite(estimate.mean))
|
|
21
|
+
throw new RangeError('estimate mean must be finite');
|
|
22
|
+
if (estimate.unit !== undefined && typeof estimate.unit !== 'string') {
|
|
23
|
+
throw new TypeError('estimate unit must be a string when supplied');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
16
26
|
const durationScale = {
|
|
17
27
|
ms: 0.001,
|
|
18
28
|
s: 1,
|
|
@@ -24,8 +34,8 @@ const conversionFactor = (from, to) => {
|
|
|
24
34
|
return 1;
|
|
25
35
|
if (from === undefined || to === undefined)
|
|
26
36
|
return undefined;
|
|
27
|
-
const fromScale = durationScale[from];
|
|
28
|
-
const toScale = durationScale[to];
|
|
37
|
+
const fromScale = Object.hasOwn(durationScale, from) ? durationScale[from] : undefined;
|
|
38
|
+
const toScale = Object.hasOwn(durationScale, to) ? durationScale[to] : undefined;
|
|
29
39
|
return fromScale === undefined || toScale === undefined ? undefined : fromScale / toScale;
|
|
30
40
|
};
|
|
31
41
|
/** Typed boundary failure for estimates that do not share a compatible unit. */
|
|
@@ -39,13 +49,58 @@ export class FusionUnitError extends Error {
|
|
|
39
49
|
this.units = unique;
|
|
40
50
|
}
|
|
41
51
|
}
|
|
52
|
+
/** Sum finite weighted binary64 terms exactly, then round once. Rare overflow/underflow fallback. */
|
|
53
|
+
const exactAverage = (terms, weight) => {
|
|
54
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
55
|
+
// Every finite double is an integer multiple of 2^-1074. Using that common
|
|
56
|
+
// scale preserves tiny residuals even when large partial sums overflow.
|
|
57
|
+
const units = (value) => {
|
|
58
|
+
view.setFloat64(0, value);
|
|
59
|
+
const bits = view.getBigUint64(0);
|
|
60
|
+
const exponent = Number((bits >> 52n) & 0x7ffn);
|
|
61
|
+
const fraction = bits & ((1n << 52n) - 1n);
|
|
62
|
+
const magnitude = exponent === 0 ? fraction : ((1n << 52n) | fraction) << BigInt(exponent - 1);
|
|
63
|
+
return bits >> 63n === 0n ? magnitude : -magnitude;
|
|
64
|
+
};
|
|
65
|
+
let numerator = 0n;
|
|
66
|
+
for (const term of terms) {
|
|
67
|
+
const ratio = units(term.ratio);
|
|
68
|
+
numerator += units(term.mean) * ratio * ratio;
|
|
69
|
+
}
|
|
70
|
+
if (numerator === 0n)
|
|
71
|
+
return 0;
|
|
72
|
+
const sign = numerator < 0n ? -1 : 1;
|
|
73
|
+
if (numerator < 0n)
|
|
74
|
+
numerator = -numerator;
|
|
75
|
+
// Three factors in each numerator term versus one in the denominator.
|
|
76
|
+
let denominator = units(weight) << 2148n;
|
|
77
|
+
// Find the quotient's binary exponent,
|
|
78
|
+
// then round its 53-bit significand (or subnormal integer) ties to even.
|
|
79
|
+
let exponent = numerator.toString(2).length - denominator.toString(2).length;
|
|
80
|
+
if (exponent >= 0 ? numerator < (denominator << BigInt(exponent)) :
|
|
81
|
+
(numerator << BigInt(-exponent)) < denominator)
|
|
82
|
+
exponent--;
|
|
83
|
+
const shift = Math.max(-1074, exponent - 52);
|
|
84
|
+
if (shift < 0)
|
|
85
|
+
numerator <<= BigInt(-shift);
|
|
86
|
+
else
|
|
87
|
+
denominator <<= BigInt(shift);
|
|
88
|
+
let rounded = numerator / denominator;
|
|
89
|
+
const remainder = numerator % denominator;
|
|
90
|
+
if (remainder * 2n > denominator || (remainder * 2n === denominator && (rounded & 1n) !== 0n))
|
|
91
|
+
rounded++;
|
|
92
|
+
return sign * Number(rounded) * 2 ** shift;
|
|
93
|
+
};
|
|
42
94
|
/**
|
|
43
|
-
* Fuses estimates into a posterior.
|
|
44
|
-
*
|
|
95
|
+
* Fuses finite estimates into a posterior. Zero-confidence estimates are
|
|
96
|
+
* skipped after validating every input; invalid inputs and unrepresentable
|
|
97
|
+
* posteriors throw instead of returning NaN or infinity.
|
|
45
98
|
* @returns `undefined` when no estimate carries usable weight.
|
|
46
99
|
*/
|
|
47
100
|
export const fuse = (estimates) => {
|
|
48
|
-
estimates.
|
|
101
|
+
estimates = Array.from(estimates, ({ mean, sigma, conf, unit }) => ({ mean, sigma, conf, unit }));
|
|
102
|
+
for (const estimate of estimates)
|
|
103
|
+
validateEstimate(estimate);
|
|
49
104
|
const usable = estimates.filter(estimate => (estimate.conf ?? 1) > 0);
|
|
50
105
|
if (usable.length === 0)
|
|
51
106
|
return undefined;
|
|
@@ -54,20 +109,114 @@ export const fuse = (estimates) => {
|
|
|
54
109
|
if (factors.some(factor => factor === undefined)) {
|
|
55
110
|
throw new FusionUnitError(usable.map(estimate => estimate.unit));
|
|
56
111
|
}
|
|
57
|
-
let
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
|
|
112
|
+
let maxRootWeight = 0;
|
|
113
|
+
let maxMean = 0;
|
|
114
|
+
let lowestMean = Infinity;
|
|
115
|
+
let highestMean = -Infinity;
|
|
116
|
+
const scaled = usable.map((estimate, index) => {
|
|
61
117
|
const factor = factors[index];
|
|
62
118
|
const sigma = estimate.sigma * factor;
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
119
|
+
const mean = estimate.mean * factor;
|
|
120
|
+
if (!Number.isFinite(mean) || !Number.isFinite(sigma) || sigma <= 0) {
|
|
121
|
+
throw new RangeError('converted estimate is outside the finite numeric range');
|
|
122
|
+
}
|
|
123
|
+
const rootConfidence = Math.sqrt(estimate.conf ?? 1);
|
|
124
|
+
const rootWeight = rootConfidence / sigma;
|
|
125
|
+
maxRootWeight = Math.max(maxRootWeight, rootWeight);
|
|
126
|
+
maxMean = Math.max(maxMean, Math.abs(mean));
|
|
127
|
+
lowestMean = Math.min(lowestMean, mean);
|
|
128
|
+
highestMean = Math.max(highestMean, mean);
|
|
129
|
+
return { mean, rootWeight, rootConfidence, sigma };
|
|
130
|
+
});
|
|
131
|
+
if (!Number.isFinite(maxRootWeight) || maxRootWeight === 0) {
|
|
132
|
+
throw new RangeError('posterior precision is outside the finite numeric range');
|
|
133
|
+
}
|
|
134
|
+
// A root weight may underflow even though its ratio to a small maximum,
|
|
135
|
+
// and the resulting weighted mean, are representable. Keep its factors.
|
|
136
|
+
const ratioOf = (estimate) => {
|
|
137
|
+
const denominator = estimate.sigma * maxRootWeight;
|
|
138
|
+
return estimate.rootWeight < 2 ** -1022 && Number.isFinite(denominator)
|
|
139
|
+
? estimate.rootConfidence / denominator
|
|
140
|
+
: estimate.rootWeight / maxRootWeight;
|
|
141
|
+
};
|
|
142
|
+
// Normalize before squaring or multiplying by means. Direct σ² and w*x
|
|
143
|
+
// intermediates can overflow even when the posterior is representable.
|
|
144
|
+
let totalWeight = 0;
|
|
145
|
+
let weightCorrection = 0;
|
|
146
|
+
for (const estimate of scaled) {
|
|
147
|
+
const weight = ratioOf(estimate) ** 2;
|
|
148
|
+
const next = totalWeight + weight;
|
|
149
|
+
weightCorrection += totalWeight >= weight
|
|
150
|
+
? (totalWeight - next) + weight : (weight - next) + totalWeight;
|
|
151
|
+
totalWeight = next;
|
|
152
|
+
}
|
|
153
|
+
totalWeight += weightCorrection;
|
|
154
|
+
const rootPrecision = maxRootWeight * Math.sqrt(totalWeight);
|
|
155
|
+
const precision = rootPrecision * rootPrecision;
|
|
156
|
+
const sigma = 1 / rootPrecision;
|
|
157
|
+
if (!Number.isFinite(precision) || precision === 0 || !Number.isFinite(sigma) || sigma === 0) {
|
|
158
|
+
throw new RangeError('posterior precision is outside the finite numeric range');
|
|
159
|
+
}
|
|
160
|
+
// A convex average of identical converted means is that mean. Precision and
|
|
161
|
+
// input validation above still apply; Object.is keeps mixed signed zeros on
|
|
162
|
+
// the general path with its existing rounding behavior.
|
|
163
|
+
if (Object.is(lowestMean, highestMean)) {
|
|
164
|
+
return { mean: lowestMean, sigma, precision, ...unit === undefined ? {} : { unit } };
|
|
66
165
|
}
|
|
166
|
+
// Multiply each root-weight factor separately: squaring a tiny ratio first
|
|
167
|
+
// could erase a representable contribution from a very large mean.
|
|
168
|
+
let mean = 0;
|
|
169
|
+
let meanCorrection = 0;
|
|
170
|
+
// A power-of-two scale preserves subnormal inputs exactly while preventing
|
|
171
|
+
// their weighted products from rounding away before they can be added.
|
|
172
|
+
const meanScale = maxMean > 0 && maxMean < 2 ** -1022 ? 2 ** -1022 : 1;
|
|
173
|
+
let numerator = 0;
|
|
174
|
+
let numeratorCorrection = 0;
|
|
175
|
+
let subnormalProduct = false;
|
|
176
|
+
for (const estimate of scaled) {
|
|
177
|
+
const ratio = ratioOf(estimate);
|
|
178
|
+
const weightedMean = ((estimate.mean / meanScale) * ratio) * ratio;
|
|
179
|
+
if (estimate.mean !== 0 && ratio !== 0 && Math.abs(weightedMean) < 2 ** -1022)
|
|
180
|
+
subnormalProduct = true;
|
|
181
|
+
const numeratorNext = numerator + weightedMean;
|
|
182
|
+
numeratorCorrection += Math.abs(numerator) >= Math.abs(weightedMean)
|
|
183
|
+
? (numerator - numeratorNext) + weightedMean : (weightedMean - numeratorNext) + numerator;
|
|
184
|
+
numerator = numeratorNext;
|
|
185
|
+
const contribution = weightedMean / totalWeight;
|
|
186
|
+
const next = mean + contribution;
|
|
187
|
+
// Keep small contributions when large positive and negative means cancel.
|
|
188
|
+
meanCorrection += Math.abs(mean) >= Math.abs(contribution)
|
|
189
|
+
? (mean - next) + contribution : (contribution - next) + mean;
|
|
190
|
+
mean = next;
|
|
191
|
+
}
|
|
192
|
+
mean += meanCorrection;
|
|
193
|
+
// Divide once when possible: separate divisions can erase subnormal terms
|
|
194
|
+
// that would survive their combined average, including after cancellation.
|
|
195
|
+
const correctedNumerator = numerator + numeratorCorrection;
|
|
196
|
+
if (Number.isFinite(correctedNumerator))
|
|
197
|
+
mean = correctedNumerator / totalWeight;
|
|
198
|
+
// A finite sum cannot recover contributions already rounded during weighting.
|
|
199
|
+
// Recompute only when those subnormal products may determine a tiny result.
|
|
200
|
+
if (!Number.isFinite(correctedNumerator) || (subnormalProduct && Math.abs(mean) < 2 ** -1022)) {
|
|
201
|
+
mean = exactAverage(scaled.map(estimate => ({
|
|
202
|
+
mean: estimate.mean / meanScale, ratio: ratioOf(estimate)
|
|
203
|
+
})), totalWeight);
|
|
204
|
+
}
|
|
205
|
+
mean *= meanScale;
|
|
206
|
+
if (!Number.isFinite(mean)) {
|
|
207
|
+
// Near Number.MAX_VALUE, rounded convex contributions can sum just beyond
|
|
208
|
+
// the finite range. Rescale that boundary case before summing.
|
|
209
|
+
const normalized = scaled.reduce((sum, estimate) => sum +
|
|
210
|
+
ratioOf(estimate) ** 2 * (estimate.mean / maxMean), 0) / totalWeight;
|
|
211
|
+
mean = Math.max(-1, Math.min(1, normalized)) * maxMean;
|
|
212
|
+
}
|
|
213
|
+
// Positive weights form a convex average. Bound floating-point roundoff to
|
|
214
|
+
// the converted input interval, including identical and subnormal means.
|
|
215
|
+
mean = Math.max(lowestMean, Math.min(highestMean, mean));
|
|
67
216
|
return {
|
|
68
|
-
mean
|
|
69
|
-
sigma
|
|
70
|
-
precision
|
|
217
|
+
mean,
|
|
218
|
+
sigma,
|
|
219
|
+
precision,
|
|
71
220
|
...unit === undefined ? {} : { unit }
|
|
72
221
|
};
|
|
73
222
|
};
|
|
@@ -81,31 +230,45 @@ export const estimateOf = (claim) => {
|
|
|
81
230
|
const value = payload.kind === 'attribute' ? payload.value :
|
|
82
231
|
payload.kind === 'metric' ? payload.value :
|
|
83
232
|
undefined;
|
|
84
|
-
if (value
|
|
233
|
+
if (value === undefined)
|
|
85
234
|
return undefined;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (sigma === undefined || !(sigma > 0)) {
|
|
235
|
+
const mean = value.num;
|
|
236
|
+
if (mean === undefined)
|
|
89
237
|
return undefined;
|
|
90
|
-
|
|
91
|
-
|
|
238
|
+
const delta = claim.delta;
|
|
239
|
+
if (delta === undefined)
|
|
240
|
+
return undefined;
|
|
241
|
+
const magnitude = delta.num;
|
|
242
|
+
if (magnitude === undefined)
|
|
243
|
+
return undefined;
|
|
244
|
+
const sigma = Uncertainty.sigma(magnitude, claim.sigmaLevel);
|
|
245
|
+
const unit = value.unit;
|
|
246
|
+
const deltaUnit = delta.unit;
|
|
247
|
+
const factor = conversionFactor(deltaUnit, unit);
|
|
92
248
|
if (factor === undefined) {
|
|
93
|
-
throw new FusionUnitError([
|
|
249
|
+
throw new FusionUnitError([unit, deltaUnit]);
|
|
94
250
|
}
|
|
95
|
-
|
|
96
|
-
mean
|
|
251
|
+
const estimate = {
|
|
252
|
+
mean,
|
|
97
253
|
sigma: sigma * factor,
|
|
98
|
-
...
|
|
254
|
+
...unit === undefined ? {} : { unit },
|
|
99
255
|
conf: claim.conf
|
|
100
256
|
};
|
|
257
|
+
validateEstimate(estimate);
|
|
258
|
+
return estimate;
|
|
101
259
|
};
|
|
102
260
|
/**
|
|
103
261
|
* Fuses the numeric estimates found in `claims` (spec §10.1's worked
|
|
104
262
|
* example lives in the tests). Claims without usable numeric uncertainty
|
|
105
263
|
* are ignored.
|
|
106
264
|
*/
|
|
107
|
-
export const fuseClaims = (claims) =>
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
265
|
+
export const fuseClaims = (claims) => {
|
|
266
|
+
const estimates = [];
|
|
267
|
+
for (const claim of claims) {
|
|
268
|
+
const estimate = estimateOf(claim);
|
|
269
|
+
if (estimate !== undefined)
|
|
270
|
+
estimates.push(estimate);
|
|
271
|
+
}
|
|
272
|
+
return fuse(estimates);
|
|
273
|
+
};
|
|
111
274
|
//# sourceMappingURL=fuse.js.map
|
package/dist/src/fuse.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAqBnD,MAAM,aAAa,GAAqC;IACtD,EAAE,EAAE,KAAK;IACT,CAAC,EAAE,CAAC;IACJ,GAAG,EAAE,EAAE;IACP,CAAC,EAAE,KAAK;CACT,CAAA;AAED,MAAM,gBAAgB,GAAG,CAAC,IAAwB,EAAE,EAAsB,EAAsB,EAAE;IAChG,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,CAAC,CAAA;IACzB,IAAI,IAAI,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC5D,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACrC,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,CAAC,CAAA;IACjC,OAAO,SAAS,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,GAAG,OAAO,CAAA;AAC3F,CAAC,CAAA;AAED,gFAAgF;AAChF,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,KAAK,CAAiC;IAE/C,YAAY,KAAsC;QAChD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QACnF,KAAK,CAAC,2BAA2B;YAC/B,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAA;QACjG,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;QAC7B,IAAI,CAAC,KAAK,GAAG,MAAM,CAAA;IACrB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,SAA8B,EAAyB,EAAE;IAC5E,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;IACxE,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACrE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,IAAI,CAAA;IAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IAC7E,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;IAClE,CAAC;IACD,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAA;QAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAE,CAAA;QAC9B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAA;QACrC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAA;QACrC,WAAW,IAAI,MAAM,CAAA;QACrB,WAAW,IAAI,MAAM,GAAG,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAA;IAChD,CAAC;IACD,OAAO;QACL,IAAI,EAAE,WAAW,GAAG,WAAW;QAC/B,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;QACjC,SAAS,EAAE,WAAW;QACtB,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;KACtC,CAAA;AACH,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAc,EAAwB,EAAE;IACjE,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;IAC7B,MAAM,KAAK,GACT,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC3C,SAAS,CAAA;IACX,IAAI,KAAK,EAAE,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAClC,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACxC,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IAC9D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;IAC5D,CAAC;IACD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,GAAG;QACf,KAAK,EAAE,KAAK,GAAG,MAAM;QACrB,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;QACvD,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAA;AACH,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAA0B,EAAyB,EAAE,CAC9E,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;IAC1B,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAClC,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;AACjD,CAAC,CAAC,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,IAIjD,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Shared numeric boundary for probabilities accepted by the fusion API. */
|
|
2
|
+
export const validateConfidence = (conf) => {
|
|
3
|
+
if (!Number.isFinite(conf) || conf < 0 || conf > 1) {
|
|
4
|
+
throw new RangeError('confidence must be finite and between 0 and 1');
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/validate.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAQ,EAAE;IACvD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,UAAU,CAAC,+CAA+C,CAAC,CAAA;IACvE,CAAC;AACH,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavelang/fusion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CAVE probabilistic layer — Bayesian fusion of numeric estimates, noisy-AND conditional confidence, hypothesis helpers.",
|
|
6
6
|
"keywords": [
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"directory": "packages/fusion"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
|
-
"node": "^
|
|
24
|
+
"node": "^24.16.0 || ^26.1.0"
|
|
25
25
|
},
|
|
26
26
|
"exports": {
|
|
27
27
|
".": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@cavelang/core": "0.
|
|
46
|
+
"@cavelang/core": "0.36.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc -b",
|
package/src/conditional.ts
CHANGED
|
@@ -11,12 +11,36 @@
|
|
|
11
11
|
* loud function name.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { validateConfidence } from './validate.ts'
|
|
15
|
+
|
|
16
|
+
/** Neumaier summation retains small probabilities beside a dominant value. */
|
|
17
|
+
const confidenceTotal = <T>(values: readonly T[], confidence: (value: T) => number, baseline = 0): number => {
|
|
18
|
+
let total = 0
|
|
19
|
+
let correction = 0
|
|
20
|
+
for (const value of values) {
|
|
21
|
+
const conf = confidence(value)
|
|
22
|
+
validateConfidence(conf)
|
|
23
|
+
const next = total + conf
|
|
24
|
+
correction += total >= conf ? (total - next) + conf : (conf - next) + total
|
|
25
|
+
total = next
|
|
26
|
+
}
|
|
27
|
+
// Subtract the baseline before rounding away a small correction near one.
|
|
28
|
+
return (total - baseline) + correction
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
/**
|
|
15
32
|
* Noisy-AND under an explicit independence assumption:
|
|
16
33
|
* `noisyAndIndependent(0.8, [0.6])` → 0.48 (spec §10.2's example).
|
|
17
34
|
*/
|
|
18
|
-
export const noisyAndIndependent = (claimConf: number, conditionConfs: readonly number[]): number =>
|
|
19
|
-
|
|
35
|
+
export const noisyAndIndependent = (claimConf: number, conditionConfs: readonly number[]): number => {
|
|
36
|
+
validateConfidence(claimConf)
|
|
37
|
+
let product = claimConf
|
|
38
|
+
for (const conf of conditionConfs) {
|
|
39
|
+
validateConfidence(conf)
|
|
40
|
+
product *= conf
|
|
41
|
+
}
|
|
42
|
+
return product
|
|
43
|
+
}
|
|
20
44
|
|
|
21
45
|
/**
|
|
22
46
|
* Normalizes an exhaustive hypothesis set so confidences sum to 1
|
|
@@ -27,7 +51,11 @@ export const noisyAndIndependent = (claimConf: number, conditionConfs: readonly
|
|
|
27
51
|
export const normalizeHypotheses = <T extends { readonly conf: number }>(
|
|
28
52
|
hypotheses: readonly T[]
|
|
29
53
|
): undefined | (T & { readonly conf: number })[] => {
|
|
30
|
-
|
|
54
|
+
hypotheses = Array.from(hypotheses, hypothesis => {
|
|
55
|
+
const { conf, ...metadata } = hypothesis
|
|
56
|
+
return { ...metadata, conf } as T
|
|
57
|
+
})
|
|
58
|
+
const total = confidenceTotal(hypotheses, hypothesis => hypothesis.conf)
|
|
31
59
|
if (!(total > 0)) {
|
|
32
60
|
return undefined
|
|
33
61
|
}
|
|
@@ -38,5 +66,6 @@ export const normalizeHypotheses = <T extends { readonly conf: number }>(
|
|
|
38
66
|
* @returns how far a hypothesis set is from exhaustive (Σ conf − 1);
|
|
39
67
|
* ≈0 for a well-formed set, negative when probability mass is missing.
|
|
40
68
|
*/
|
|
41
|
-
export const hypothesisGap = (confs: readonly number[]): number =>
|
|
42
|
-
confs
|
|
69
|
+
export const hypothesisGap = (confs: readonly number[]): number => {
|
|
70
|
+
return confidenceTotal(confs, conf => conf, 1)
|
|
71
|
+
}
|
package/src/fuse.ts
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { Claim, Uncertainty } from '@cavelang/core'
|
|
17
|
+
import { validateConfidence } from './validate.ts'
|
|
17
18
|
|
|
18
|
-
/** One numeric estimate: mean, σ,
|
|
19
|
+
/** One numeric estimate: finite mean, positive finite σ, confidence p ∈ [0, 1]. */
|
|
19
20
|
export type Estimate = {
|
|
20
21
|
readonly mean: number
|
|
21
22
|
readonly sigma: number
|
|
@@ -25,6 +26,15 @@ export type Estimate = {
|
|
|
25
26
|
readonly conf?: number
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
const validateEstimate = (estimate: Estimate): void => {
|
|
30
|
+
Uncertainty.validateSigma(estimate.sigma)
|
|
31
|
+
validateConfidence(estimate.conf === undefined ? 1 : estimate.conf)
|
|
32
|
+
if (!Number.isFinite(estimate.mean)) throw new RangeError('estimate mean must be finite')
|
|
33
|
+
if (estimate.unit !== undefined && typeof estimate.unit !== 'string') {
|
|
34
|
+
throw new TypeError('estimate unit must be a string when supplied')
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
export type Posterior = {
|
|
29
39
|
readonly mean: number
|
|
30
40
|
readonly sigma: number
|
|
@@ -44,8 +54,8 @@ const durationScale: Readonly<Record<string, number>> = {
|
|
|
44
54
|
const conversionFactor = (from: undefined | string, to: undefined | string): undefined | number => {
|
|
45
55
|
if (from === to) return 1
|
|
46
56
|
if (from === undefined || to === undefined) return undefined
|
|
47
|
-
const fromScale = durationScale[from]
|
|
48
|
-
const toScale = durationScale[to]
|
|
57
|
+
const fromScale = Object.hasOwn(durationScale, from) ? durationScale[from] : undefined
|
|
58
|
+
const toScale = Object.hasOwn(durationScale, to) ? durationScale[to] : undefined
|
|
49
59
|
return fromScale === undefined || toScale === undefined ? undefined : fromScale / toScale
|
|
50
60
|
}
|
|
51
61
|
|
|
@@ -62,13 +72,52 @@ export class FusionUnitError extends Error {
|
|
|
62
72
|
}
|
|
63
73
|
}
|
|
64
74
|
|
|
75
|
+
/** Sum finite weighted binary64 terms exactly, then round once. Rare overflow/underflow fallback. */
|
|
76
|
+
const exactAverage = (terms: readonly { mean: number, ratio: number }[], weight: number): number => {
|
|
77
|
+
const view = new DataView(new ArrayBuffer(8))
|
|
78
|
+
// Every finite double is an integer multiple of 2^-1074. Using that common
|
|
79
|
+
// scale preserves tiny residuals even when large partial sums overflow.
|
|
80
|
+
const units = (value: number): bigint => {
|
|
81
|
+
view.setFloat64(0, value)
|
|
82
|
+
const bits = view.getBigUint64(0)
|
|
83
|
+
const exponent = Number((bits >> 52n) & 0x7ffn)
|
|
84
|
+
const fraction = bits & ((1n << 52n) - 1n)
|
|
85
|
+
const magnitude = exponent === 0 ? fraction : ((1n << 52n) | fraction) << BigInt(exponent - 1)
|
|
86
|
+
return bits >> 63n === 0n ? magnitude : -magnitude
|
|
87
|
+
}
|
|
88
|
+
let numerator = 0n
|
|
89
|
+
for (const term of terms) {
|
|
90
|
+
const ratio = units(term.ratio)
|
|
91
|
+
numerator += units(term.mean) * ratio * ratio
|
|
92
|
+
}
|
|
93
|
+
if (numerator === 0n) return 0
|
|
94
|
+
const sign = numerator < 0n ? -1 : 1
|
|
95
|
+
if (numerator < 0n) numerator = -numerator
|
|
96
|
+
// Three factors in each numerator term versus one in the denominator.
|
|
97
|
+
let denominator = units(weight) << 2148n
|
|
98
|
+
// Find the quotient's binary exponent,
|
|
99
|
+
// then round its 53-bit significand (or subnormal integer) ties to even.
|
|
100
|
+
let exponent = numerator.toString(2).length - denominator.toString(2).length
|
|
101
|
+
if (exponent >= 0 ? numerator < (denominator << BigInt(exponent)) :
|
|
102
|
+
(numerator << BigInt(-exponent)) < denominator) exponent--
|
|
103
|
+
const shift = Math.max(-1074, exponent - 52)
|
|
104
|
+
if (shift < 0) numerator <<= BigInt(-shift)
|
|
105
|
+
else denominator <<= BigInt(shift)
|
|
106
|
+
let rounded = numerator / denominator
|
|
107
|
+
const remainder = numerator % denominator
|
|
108
|
+
if (remainder * 2n > denominator || (remainder * 2n === denominator && (rounded & 1n) !== 0n)) rounded++
|
|
109
|
+
return sign * Number(rounded) * 2 ** shift
|
|
110
|
+
}
|
|
111
|
+
|
|
65
112
|
/**
|
|
66
|
-
* Fuses estimates into a posterior.
|
|
67
|
-
*
|
|
113
|
+
* Fuses finite estimates into a posterior. Zero-confidence estimates are
|
|
114
|
+
* skipped after validating every input; invalid inputs and unrepresentable
|
|
115
|
+
* posteriors throw instead of returning NaN or infinity.
|
|
68
116
|
* @returns `undefined` when no estimate carries usable weight.
|
|
69
117
|
*/
|
|
70
118
|
export const fuse = (estimates: readonly Estimate[]): undefined | Posterior => {
|
|
71
|
-
estimates.
|
|
119
|
+
estimates = Array.from(estimates, ({ mean, sigma, conf, unit }) => ({ mean, sigma, conf, unit }))
|
|
120
|
+
for (const estimate of estimates) validateEstimate(estimate)
|
|
72
121
|
const usable = estimates.filter(estimate => (estimate.conf ?? 1) > 0)
|
|
73
122
|
if (usable.length === 0) return undefined
|
|
74
123
|
const unit = usable[0]!.unit
|
|
@@ -76,20 +125,112 @@ export const fuse = (estimates: readonly Estimate[]): undefined | Posterior => {
|
|
|
76
125
|
if (factors.some(factor => factor === undefined)) {
|
|
77
126
|
throw new FusionUnitError(usable.map(estimate => estimate.unit))
|
|
78
127
|
}
|
|
79
|
-
let
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
|
|
128
|
+
let maxRootWeight = 0
|
|
129
|
+
let maxMean = 0
|
|
130
|
+
let lowestMean = Infinity
|
|
131
|
+
let highestMean = -Infinity
|
|
132
|
+
const scaled = usable.map((estimate, index) => {
|
|
83
133
|
const factor = factors[index]!
|
|
84
134
|
const sigma = estimate.sigma * factor
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
135
|
+
const mean = estimate.mean * factor
|
|
136
|
+
if (!Number.isFinite(mean) || !Number.isFinite(sigma) || sigma <= 0) {
|
|
137
|
+
throw new RangeError('converted estimate is outside the finite numeric range')
|
|
138
|
+
}
|
|
139
|
+
const rootConfidence = Math.sqrt(estimate.conf ?? 1)
|
|
140
|
+
const rootWeight = rootConfidence / sigma
|
|
141
|
+
maxRootWeight = Math.max(maxRootWeight, rootWeight)
|
|
142
|
+
maxMean = Math.max(maxMean, Math.abs(mean))
|
|
143
|
+
lowestMean = Math.min(lowestMean, mean)
|
|
144
|
+
highestMean = Math.max(highestMean, mean)
|
|
145
|
+
return { mean, rootWeight, rootConfidence, sigma }
|
|
146
|
+
})
|
|
147
|
+
if (!Number.isFinite(maxRootWeight) || maxRootWeight === 0) {
|
|
148
|
+
throw new RangeError('posterior precision is outside the finite numeric range')
|
|
149
|
+
}
|
|
150
|
+
// A root weight may underflow even though its ratio to a small maximum,
|
|
151
|
+
// and the resulting weighted mean, are representable. Keep its factors.
|
|
152
|
+
const ratioOf = (estimate: typeof scaled[number]): number => {
|
|
153
|
+
const denominator = estimate.sigma * maxRootWeight
|
|
154
|
+
return estimate.rootWeight < 2 ** -1022 && Number.isFinite(denominator)
|
|
155
|
+
? estimate.rootConfidence / denominator
|
|
156
|
+
: estimate.rootWeight / maxRootWeight
|
|
157
|
+
}
|
|
158
|
+
// Normalize before squaring or multiplying by means. Direct σ² and w*x
|
|
159
|
+
// intermediates can overflow even when the posterior is representable.
|
|
160
|
+
let totalWeight = 0
|
|
161
|
+
let weightCorrection = 0
|
|
162
|
+
for (const estimate of scaled) {
|
|
163
|
+
const weight = ratioOf(estimate) ** 2
|
|
164
|
+
const next = totalWeight + weight
|
|
165
|
+
weightCorrection += totalWeight >= weight
|
|
166
|
+
? (totalWeight - next) + weight : (weight - next) + totalWeight
|
|
167
|
+
totalWeight = next
|
|
88
168
|
}
|
|
169
|
+
totalWeight += weightCorrection
|
|
170
|
+
const rootPrecision = maxRootWeight * Math.sqrt(totalWeight)
|
|
171
|
+
const precision = rootPrecision * rootPrecision
|
|
172
|
+
const sigma = 1 / rootPrecision
|
|
173
|
+
if (!Number.isFinite(precision) || precision === 0 || !Number.isFinite(sigma) || sigma === 0) {
|
|
174
|
+
throw new RangeError('posterior precision is outside the finite numeric range')
|
|
175
|
+
}
|
|
176
|
+
// A convex average of identical converted means is that mean. Precision and
|
|
177
|
+
// input validation above still apply; Object.is keeps mixed signed zeros on
|
|
178
|
+
// the general path with its existing rounding behavior.
|
|
179
|
+
if (Object.is(lowestMean, highestMean)) {
|
|
180
|
+
return { mean: lowestMean, sigma, precision, ...unit === undefined ? {} : { unit } }
|
|
181
|
+
}
|
|
182
|
+
// Multiply each root-weight factor separately: squaring a tiny ratio first
|
|
183
|
+
// could erase a representable contribution from a very large mean.
|
|
184
|
+
let mean = 0
|
|
185
|
+
let meanCorrection = 0
|
|
186
|
+
// A power-of-two scale preserves subnormal inputs exactly while preventing
|
|
187
|
+
// their weighted products from rounding away before they can be added.
|
|
188
|
+
const meanScale = maxMean > 0 && maxMean < 2 ** -1022 ? 2 ** -1022 : 1
|
|
189
|
+
let numerator = 0
|
|
190
|
+
let numeratorCorrection = 0
|
|
191
|
+
let subnormalProduct = false
|
|
192
|
+
for (const estimate of scaled) {
|
|
193
|
+
const ratio = ratioOf(estimate)
|
|
194
|
+
const weightedMean = ((estimate.mean / meanScale) * ratio) * ratio
|
|
195
|
+
if (estimate.mean !== 0 && ratio !== 0 && Math.abs(weightedMean) < 2 ** -1022) subnormalProduct = true
|
|
196
|
+
const numeratorNext = numerator + weightedMean
|
|
197
|
+
numeratorCorrection += Math.abs(numerator) >= Math.abs(weightedMean)
|
|
198
|
+
? (numerator - numeratorNext) + weightedMean : (weightedMean - numeratorNext) + numerator
|
|
199
|
+
numerator = numeratorNext
|
|
200
|
+
const contribution = weightedMean / totalWeight
|
|
201
|
+
const next = mean + contribution
|
|
202
|
+
// Keep small contributions when large positive and negative means cancel.
|
|
203
|
+
meanCorrection += Math.abs(mean) >= Math.abs(contribution)
|
|
204
|
+
? (mean - next) + contribution : (contribution - next) + mean
|
|
205
|
+
mean = next
|
|
206
|
+
}
|
|
207
|
+
mean += meanCorrection
|
|
208
|
+
// Divide once when possible: separate divisions can erase subnormal terms
|
|
209
|
+
// that would survive their combined average, including after cancellation.
|
|
210
|
+
const correctedNumerator = numerator + numeratorCorrection
|
|
211
|
+
if (Number.isFinite(correctedNumerator)) mean = correctedNumerator / totalWeight
|
|
212
|
+
// A finite sum cannot recover contributions already rounded during weighting.
|
|
213
|
+
// Recompute only when those subnormal products may determine a tiny result.
|
|
214
|
+
if (!Number.isFinite(correctedNumerator) || (subnormalProduct && Math.abs(mean) < 2 ** -1022)) {
|
|
215
|
+
mean = exactAverage(scaled.map(estimate => ({
|
|
216
|
+
mean: estimate.mean / meanScale, ratio: ratioOf(estimate)
|
|
217
|
+
})), totalWeight)
|
|
218
|
+
}
|
|
219
|
+
mean *= meanScale
|
|
220
|
+
if (!Number.isFinite(mean)) {
|
|
221
|
+
// Near Number.MAX_VALUE, rounded convex contributions can sum just beyond
|
|
222
|
+
// the finite range. Rescale that boundary case before summing.
|
|
223
|
+
const normalized = scaled.reduce((sum, estimate) => sum +
|
|
224
|
+
ratioOf(estimate) ** 2 * (estimate.mean / maxMean), 0) / totalWeight
|
|
225
|
+
mean = Math.max(-1, Math.min(1, normalized)) * maxMean
|
|
226
|
+
}
|
|
227
|
+
// Positive weights form a convex average. Bound floating-point roundoff to
|
|
228
|
+
// the converted input interval, including identical and subnormal means.
|
|
229
|
+
mean = Math.max(lowestMean, Math.min(highestMean, mean))
|
|
89
230
|
return {
|
|
90
|
-
mean
|
|
91
|
-
sigma
|
|
92
|
-
precision
|
|
231
|
+
mean,
|
|
232
|
+
sigma,
|
|
233
|
+
precision,
|
|
93
234
|
...unit === undefined ? {} : { unit }
|
|
94
235
|
}
|
|
95
236
|
}
|
|
@@ -105,23 +246,28 @@ export const estimateOf = (claim: Claim.t): undefined | Estimate => {
|
|
|
105
246
|
payload.kind === 'attribute' ? payload.value :
|
|
106
247
|
payload.kind === 'metric' ? payload.value :
|
|
107
248
|
undefined
|
|
108
|
-
if (value
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
249
|
+
if (value === undefined) return undefined
|
|
250
|
+
const mean = value.num
|
|
251
|
+
if (mean === undefined) return undefined
|
|
252
|
+
const delta = claim.delta
|
|
253
|
+
if (delta === undefined) return undefined
|
|
254
|
+
const magnitude = delta.num
|
|
255
|
+
if (magnitude === undefined) return undefined
|
|
256
|
+
const sigma = Uncertainty.sigma(magnitude, claim.sigmaLevel)
|
|
257
|
+
const unit = value.unit
|
|
258
|
+
const deltaUnit = delta.unit
|
|
259
|
+
const factor = conversionFactor(deltaUnit, unit)
|
|
116
260
|
if (factor === undefined) {
|
|
117
|
-
throw new FusionUnitError([
|
|
261
|
+
throw new FusionUnitError([unit, deltaUnit])
|
|
118
262
|
}
|
|
119
|
-
|
|
120
|
-
mean
|
|
263
|
+
const estimate: Estimate = {
|
|
264
|
+
mean,
|
|
121
265
|
sigma: sigma * factor,
|
|
122
|
-
...
|
|
266
|
+
...unit === undefined ? {} : { unit },
|
|
123
267
|
conf: claim.conf
|
|
124
268
|
}
|
|
269
|
+
validateEstimate(estimate)
|
|
270
|
+
return estimate
|
|
125
271
|
}
|
|
126
272
|
|
|
127
273
|
/**
|
|
@@ -129,8 +275,11 @@ export const estimateOf = (claim: Claim.t): undefined | Estimate => {
|
|
|
129
275
|
* example lives in the tests). Claims without usable numeric uncertainty
|
|
130
276
|
* are ignored.
|
|
131
277
|
*/
|
|
132
|
-
export const fuseClaims = (claims: readonly Claim.t[]): undefined | Posterior =>
|
|
133
|
-
|
|
278
|
+
export const fuseClaims = (claims: readonly Claim.t[]): undefined | Posterior => {
|
|
279
|
+
const estimates: Estimate[] = []
|
|
280
|
+
for (const claim of claims) {
|
|
134
281
|
const estimate = estimateOf(claim)
|
|
135
|
-
|
|
136
|
-
}
|
|
282
|
+
if (estimate !== undefined) estimates.push(estimate)
|
|
283
|
+
}
|
|
284
|
+
return fuse(estimates)
|
|
285
|
+
}
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Shared numeric boundary for probabilities accepted by the fusion API. */
|
|
2
|
+
export const validateConfidence = (conf: number): void => {
|
|
3
|
+
if (!Number.isFinite(conf) || conf < 0 || conf > 1) {
|
|
4
|
+
throw new RangeError('confidence must be finite and between 0 and 1')
|
|
5
|
+
}
|
|
6
|
+
}
|