@cavelang/fusion 0.35.0 → 0.36.1

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 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 }` from any numeric claim
28
- carrying `+/-` uncertainty (attribute or metric payload); claims without a
29
- usable estimate and zero-confidence estimates are skipped. Invalid zero,
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. The spec's worked example is a test
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;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,EAAE,gBAAgB,SAAS,MAAM,EAAE,KAAG,MACtB,CAAA;AAErE;;;;;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,EAM7C,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,MAAM,EAAE,KAAG,MACT,CAAA"}
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"}
@@ -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) => conditionConfs.reduce((product, conf) => product * conf, claimConf);
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
- const total = hypotheses.reduce((sum, hypothesis) => sum + hypothesis.conf, 0);
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) => confs.reduce((sum, conf) => sum + conf, 0) - 1;
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,CAClG,cAAc,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,GAAG,IAAI,EAAE,SAAS,CAAC,CAAA;AAErE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,UAAwB,EACuB,EAAE;IACjD,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,CAAC,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAC9E,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,CAChE,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAA"}
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"}
@@ -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, σ, and confidence weight p ∈ (0, 1]. */
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. Estimates with `conf` 0 or a
40
- * non-positive σ contribute nothing and are skipped.
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;
@@ -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;AAEnD,uEAAuE;AACvE,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;AAED,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;AAED;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAI,WAAW,SAAS,QAAQ,EAAE,KAAG,SAAS,GAAG,SAyBjE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,KAAK,CAAC,CAAC,KAAG,SAAS,GAAG,QAuBvD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,SAAS,KAAK,CAAC,CAAC,EAAE,KAAG,SAAS,GAAG,SAI/D,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. Estimates with `conf` 0 or a
44
- * non-positive σ contribute nothing and are skipped.
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.forEach(estimate => Uncertainty.validateSigma(estimate.sigma));
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 totalWeight = 0;
58
- let weightedSum = 0;
59
- for (const [index, estimate] of usable.entries()) {
60
- const conf = estimate.conf ?? 1;
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 weight = conf / (sigma * sigma);
64
- totalWeight += weight;
65
- weightedSum += weight * estimate.mean * factor;
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: weightedSum / totalWeight,
69
- sigma: 1 / Math.sqrt(totalWeight),
70
- precision: totalWeight,
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?.num === undefined) {
233
+ if (value === undefined)
85
234
  return undefined;
86
- }
87
- const sigma = Claim.sigmaOf(claim);
88
- if (sigma === undefined || !(sigma > 0)) {
235
+ const mean = value.num;
236
+ if (mean === undefined)
89
237
  return undefined;
90
- }
91
- const factor = conversionFactor(claim.delta?.unit, value.unit);
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([value.unit, claim.delta?.unit]);
249
+ throw new FusionUnitError([unit, deltaUnit]);
94
250
  }
95
- return {
96
- mean: value.num,
251
+ const estimate = {
252
+ mean,
97
253
  sigma: sigma * factor,
98
- ...value.unit === undefined ? {} : { unit: value.unit },
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) => fuse(claims.flatMap(claim => {
108
- const estimate = estimateOf(claim);
109
- return estimate === undefined ? [] : [estimate];
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
@@ -1 +1 @@
1
- {"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/fuse.ts"],"names":[],"mappings":"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"}
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;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAYlD,MAAM,gBAAgB,GAAG,CAAC,QAAkB,EAAQ,EAAE;IACpD,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACzC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IACnE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,8BAA8B,CAAC,CAAA;IACzF,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACrE,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAA;IACrE,CAAC;AACH,CAAC,CAAA;AAWD,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,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACtF,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAChF,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,qGAAqG;AACrG,MAAM,YAAY,GAAG,CAAC,KAAiD,EAAE,MAAc,EAAU,EAAE;IACjG,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7C,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,KAAK,GAAG,CAAC,KAAa,EAAU,EAAE;QACtC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QACjC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,CAAA;QAC/C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC,CAAA;QAC1C,MAAM,SAAS,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAA;QAC9F,OAAO,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACpD,CAAC,CAAA;IACD,IAAI,SAAS,GAAG,EAAE,CAAA;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC/B,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,KAAK,CAAA;IAC/C,CAAC;IACD,IAAI,SAAS,KAAK,EAAE;QAAE,OAAO,CAAC,CAAA;IAC9B,MAAM,IAAI,GAAG,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACpC,IAAI,SAAS,GAAG,EAAE;QAAE,SAAS,GAAG,CAAC,SAAS,CAAA;IAC1C,sEAAsE;IACtE,IAAI,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAA;IACxC,uCAAuC;IACvC,yEAAyE;IACzE,IAAI,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAC5E,IAAI,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,WAAW;QAAE,QAAQ,EAAE,CAAA;IAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAA;IAC5C,IAAI,KAAK,GAAG,CAAC;QAAE,SAAS,KAAK,MAAM,CAAC,CAAC,KAAK,CAAC,CAAA;;QACtC,WAAW,KAAK,MAAM,CAAC,KAAK,CAAC,CAAA;IAClC,IAAI,OAAO,GAAG,SAAS,GAAG,WAAW,CAAA;IACrC,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,CAAA;IACzC,IAAI,SAAS,GAAG,EAAE,GAAG,WAAW,IAAI,CAAC,SAAS,GAAG,EAAE,KAAK,WAAW,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC;QAAE,OAAO,EAAE,CAAA;IACxG,OAAO,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,CAAA;AAC5C,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,SAA8B,EAAyB,EAAE;IAC5E,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACjG,KAAK,MAAM,QAAQ,IAAI,SAAS;QAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAC5D,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,aAAa,GAAG,CAAC,CAAA;IACrB,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,UAAU,GAAG,QAAQ,CAAA;IACzB,IAAI,WAAW,GAAG,CAAC,QAAQ,CAAA;IAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;QAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAE,CAAA;QAC9B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAA;QACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAA;QACnC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,UAAU,CAAC,wDAAwD,CAAC,CAAA;QAChF,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,CAAA;QACpD,MAAM,UAAU,GAAG,cAAc,GAAG,KAAK,CAAA;QACzC,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,UAAU,CAAC,CAAA;QACnD,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;QAC3C,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;QACvC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;QACzC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,CAAA;IACpD,CAAC,CAAC,CAAA;IACF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,UAAU,CAAC,yDAAyD,CAAC,CAAA;IACjF,CAAC;IACD,wEAAwE;IACxE,wEAAwE;IACxE,MAAM,OAAO,GAAG,CAAC,QAA+B,EAAU,EAAE;QAC1D,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,GAAG,aAAa,CAAA;QAClD,OAAO,QAAQ,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;YACrE,CAAC,CAAC,QAAQ,CAAC,cAAc,GAAG,WAAW;YACvC,CAAC,CAAC,QAAQ,CAAC,UAAU,GAAG,aAAa,CAAA;IACzC,CAAC,CAAA;IACD,uEAAuE;IACvE,uEAAuE;IACvE,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,IAAI,gBAAgB,GAAG,CAAC,CAAA;IACxB,KAAK,MAAM,QAAQ,IAAI,MAAM,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACrC,MAAM,IAAI,GAAG,WAAW,GAAG,MAAM,CAAA;QACjC,gBAAgB,IAAI,WAAW,IAAI,MAAM;YACvC,CAAC,CAAC,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,WAAW,CAAA;QACjE,WAAW,GAAG,IAAI,CAAA;IACpB,CAAC;IACD,WAAW,IAAI,gBAAgB,CAAA;IAC/B,MAAM,aAAa,GAAG,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC5D,MAAM,SAAS,GAAG,aAAa,GAAG,aAAa,CAAA;IAC/C,MAAM,KAAK,GAAG,CAAC,GAAG,aAAa,CAAA;IAC/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAC7F,MAAM,IAAI,UAAU,CAAC,yDAAyD,CAAC,CAAA;IACjF,CAAC;IACD,4EAA4E;IAC5E,4EAA4E;IAC5E,wDAAwD;IACxD,IAAI,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC;QACvC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAA;IACtF,CAAC;IACD,2EAA2E;IAC3E,mEAAmE;IACnE,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,cAAc,GAAG,CAAC,CAAA;IACtB,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,SAAS,GAAG,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IACtE,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,IAAI,mBAAmB,GAAG,CAAC,CAAA;IAC3B,IAAI,gBAAgB,GAAG,KAAK,CAAA;IAC5B,KAAK,MAAM,QAAQ,IAAI,MAAM,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;QAC/B,MAAM,YAAY,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAAA;QAClE,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI;YAAE,gBAAgB,GAAG,IAAI,CAAA;QACtG,MAAM,aAAa,GAAG,SAAS,GAAG,YAAY,CAAA;QAC9C,mBAAmB,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YAClE,CAAC,CAAC,CAAC,SAAS,GAAG,aAAa,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,aAAa,CAAC,GAAG,SAAS,CAAA;QAC3F,SAAS,GAAG,aAAa,CAAA;QACzB,MAAM,YAAY,GAAG,YAAY,GAAG,WAAW,CAAA;QAC/C,MAAM,IAAI,GAAG,IAAI,GAAG,YAAY,CAAA;QAChC,0EAA0E;QAC1E,cAAc,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACxD,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;QAC/D,IAAI,GAAG,IAAI,CAAA;IACb,CAAC;IACD,IAAI,IAAI,cAAc,CAAA;IACtB,0EAA0E;IAC1E,2EAA2E;IAC3E,MAAM,kBAAkB,GAAG,SAAS,GAAG,mBAAmB,CAAA;IAC1D,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,IAAI,GAAG,kBAAkB,GAAG,WAAW,CAAA;IAChF,8EAA8E;IAC9E,4EAA4E;IAC5E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9F,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC1C,IAAI,EAAE,QAAQ,CAAC,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC;SAC1D,CAAC,CAAC,EAAE,WAAW,CAAC,CAAA;IACnB,CAAC;IACD,IAAI,IAAI,SAAS,CAAA;IACjB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,0EAA0E;QAC1E,+DAA+D;QAC/D,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,CAAC,GAAG;YACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,GAAG,WAAW,CAAA;QACtE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,GAAG,OAAO,CAAA;IACxD,CAAC;IACD,2EAA2E;IAC3E,yEAAyE;IACzE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAA;IACxD,OAAO;QACL,IAAI;QACJ,KAAK;QACL,SAAS;QACT,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,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAA;IACtB,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;IACzB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAA;IAC3B,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC7C,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;IAC5D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAA;IACvB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAA;IAC5B,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAChD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,eAAe,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAA;IAC9C,CAAC;IACD,MAAM,QAAQ,GAAa;QACzB,IAAI;QACJ,KAAK,EAAE,KAAK,GAAG,MAAM;QACrB,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;QACrC,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAA;IACD,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAC1B,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAA0B,EAAyB,EAAE;IAC9E,MAAM,SAAS,GAAe,EAAE,CAAA;IAChC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;QAClC,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACtD,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,CAAA;AACxB,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ /** Shared numeric boundary for probabilities accepted by the fusion API. */
2
+ export declare const validateConfidence: (conf: number) => void;
3
+ //# sourceMappingURL=validate.d.ts.map
@@ -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.35.0",
3
+ "version": "0.36.1",
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": "^22.18.0 || ^24.0.0 || ^26.0.0"
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.35.0"
46
+ "@cavelang/core": "0.36.1"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsc -b",
@@ -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
- conditionConfs.reduce((product, conf) => product * conf, claimConf)
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
- const total = hypotheses.reduce((sum, hypothesis) => sum + hypothesis.conf, 0)
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.reduce((sum, conf) => sum + conf, 0) - 1
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, σ, and confidence weight p ∈ (0, 1]. */
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. Estimates with `conf` 0 or a
67
- * non-positive σ contribute nothing and are skipped.
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.forEach(estimate => Uncertainty.validateSigma(estimate.sigma))
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 totalWeight = 0
80
- let weightedSum = 0
81
- for (const [index, estimate] of usable.entries()) {
82
- const conf = estimate.conf ?? 1
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 weight = conf / (sigma * sigma)
86
- totalWeight += weight
87
- weightedSum += weight * estimate.mean * factor
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: weightedSum / totalWeight,
91
- sigma: 1 / Math.sqrt(totalWeight),
92
- precision: totalWeight,
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?.num === undefined) {
109
- return undefined
110
- }
111
- const sigma = Claim.sigmaOf(claim)
112
- if (sigma === undefined || !(sigma > 0)) {
113
- return undefined
114
- }
115
- const factor = conversionFactor(claim.delta?.unit, value.unit)
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([value.unit, claim.delta?.unit])
261
+ throw new FusionUnitError([unit, deltaUnit])
118
262
  }
119
- return {
120
- mean: value.num,
263
+ const estimate: Estimate = {
264
+ mean,
121
265
  sigma: sigma * factor,
122
- ...value.unit === undefined ? {} : { unit: value.unit },
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
- fuse(claims.flatMap(claim => {
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
- return estimate === undefined ? [] : [estimate]
136
- }))
282
+ if (estimate !== undefined) estimates.push(estimate)
283
+ }
284
+ return fuse(estimates)
285
+ }
@@ -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
+ }