@yipe/dice 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![License: MIT](https://img.shields.io/badge/License-MIT-007ec6.svg)](LICENSE)
3
3
  ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178c6?logo=typescript)
4
4
  ![Size](https://img.shields.io/bundlephobia/minzip/@yipe/dice?logo=npm&logoColor=fff&label=Size)
5
- ![Dice](https://dprcalc.com/api/roll)
5
+ ![Dice](https://dprcalc.com/api/roll.svg)
6
6
 
7
7
  ![Last Commit](https://img.shields.io/github/last-commit/yipe/dice?logo=github)
8
8
  ![Dependencies](https://img.shields.io/librariesio/release/npm/@yipe/dice?logo=npm)
@@ -33,8 +33,6 @@ console.log("DPR:", attack.mean());
33
33
 
34
34
  ## 🚀 Installation
35
35
 
36
- (COMING SOON!)
37
-
38
36
  ```bash
39
37
  # Install with npm or yarn
40
38
  npm install @yipe/dice
package/dist/index.cjs CHANGED
@@ -47,20 +47,19 @@ var LRUCache = class {
47
47
  };
48
48
 
49
49
  // src/types.ts
50
- var COMPUTATIONAL_EPS = 1e-40;
51
- var TEST_EPS = 1e-10;
50
+ var EPS = 1e-12;
52
51
 
53
52
  // src/query.ts
54
53
  var _DiceQuery = class _DiceQuery {
55
- constructor(singles, combined) {
54
+ constructor(singles, combined, eps = EPS) {
56
55
  __publicField(this, "singles");
57
56
  __publicField(this, "combined");
58
57
  this.singles = Array.isArray(singles) ? singles : [singles];
59
- const c = combined ?? PMF.convolveMany(this.singles);
60
- this.combined = Math.abs(c.mass() - 1) <= 1e-12 ? c : c.normalize();
61
58
  if (this.singles.some((s) => s === void 0)) {
62
59
  throw new Error("DiceQuery contains undefined singles");
63
60
  }
61
+ const c = combined ?? PMF.convolveMany(this.singles);
62
+ this.combined = Math.abs(c.mass() - 1) <= eps ? c : c.normalize();
64
63
  }
65
64
  /**
66
65
  * Returns the expected damage across all possible outcomes.
@@ -358,23 +357,16 @@ var _DiceQuery = class _DiceQuery {
358
357
  * - "How much damage comes from miss effects (like save-for-half spells)?"
359
358
  */
360
359
  expectedDamageFrom(labels) {
361
- if (typeof labels === "string") {
362
- return this._expectedDamageFromSingle(labels);
363
- }
364
- let totalExpectedDamage = 0;
365
- for (const [, probabilityBin] of this.combined) {
366
- for (const label of labels) {
367
- totalExpectedDamage += probabilityBin.attr?.[label] || 0;
360
+ const wanted = Array.isArray(labels) ? labels : [labels];
361
+ let total = 0;
362
+ for (const single of this.singles) {
363
+ for (const [dmg, bin] of single) {
364
+ let p = 0;
365
+ for (const label of wanted) p += bin.count[label] ?? 0;
366
+ total += dmg * p;
368
367
  }
369
368
  }
370
- return totalExpectedDamage;
371
- }
372
- _expectedDamageFromSingle(label) {
373
- let totalExpectedDamage = 0;
374
- for (const [, probabilityBin] of this.combined) {
375
- totalExpectedDamage += probabilityBin.attr?.[label] || 0;
376
- }
377
- return totalExpectedDamage;
369
+ return total;
378
370
  }
379
371
  /**
380
372
  * Returns damage statistics for scenarios where AT LEAST ONE attack results in
@@ -597,7 +589,7 @@ var _DiceQuery = class _DiceQuery {
597
589
  * query.toStackedChartData(['hit', 'crit'])
598
590
  * // → {labels: [0, 6, 12, ...], datasets: [{label: 'hit', data: [0, 0.03, ...]}, ...]}
599
591
  */
600
- toStackedChartData(labels = [], epsilon = 1e-12) {
592
+ toStackedChartData(labels = [], epsilon = EPS) {
601
593
  const damageValues = this.combined.support();
602
594
  damageValues.map((dmg) => {
603
595
  const bin = this.combined.map.get(dmg);
@@ -1229,7 +1221,7 @@ var _DiceQuery = class _DiceQuery {
1229
1221
  *
1230
1222
  * Returns tuple: [pFirstNonSubset, pFirstSubset, pAnySuccess, pNone]
1231
1223
  */
1232
- firstSuccessSplit(successOutcome, subsetOutcome, eps = COMPUTATIONAL_EPS) {
1224
+ firstSuccessSplit(successOutcome, subsetOutcome, eps = EPS) {
1233
1225
  const pmfs = this.singles;
1234
1226
  if (!pmfs.length) {
1235
1227
  throw new Error("firstSuccessSplitFromPMFs: pmfs must be non-empty");
@@ -1278,15 +1270,14 @@ __publicField(_DiceQuery, "DEFAULT_OUTCOMES", [
1278
1270
  "missNone"
1279
1271
  ]);
1280
1272
  var DiceQuery = _DiceQuery;
1281
- var EPSILON = COMPUTATIONAL_EPS;
1282
1273
  var pmfCache = new LRUCache(1e3);
1283
1274
  var _PMF = class _PMF {
1284
- constructor(map = /* @__PURE__ */ new Map(), epsilon = EPSILON, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvidence = true) {
1275
+ constructor(map = /* @__PURE__ */ new Map(), epsilon = EPS, normalized = false, identifier = `anon#${_PMF.__anonIdCounter++}`, _preservedProvenance = true) {
1285
1276
  this.map = map;
1286
1277
  this.epsilon = epsilon;
1287
1278
  this.normalized = normalized;
1288
1279
  this.identifier = identifier;
1289
- this._preservedProvidence = _preservedProvidence;
1280
+ this._preservedProvenance = _preservedProvenance;
1290
1281
  // Cached computed values
1291
1282
  __publicField(this, "_support");
1292
1283
  __publicField(this, "_min");
@@ -1296,15 +1287,18 @@ var _PMF = class _PMF {
1296
1287
  __publicField(this, "_variance");
1297
1288
  __publicField(this, "_stdev");
1298
1289
  }
1299
- static empty(epsilon = EPSILON, identifier = "empty") {
1290
+ static empty(epsilon = EPS, identifier = "empty") {
1300
1291
  return new _PMF(/* @__PURE__ */ new Map(), epsilon, false, identifier);
1301
1292
  }
1302
1293
  // This has a single bin at value 0, mass of 1
1303
- static zero(epsilon = EPSILON) {
1294
+ static zero(epsilon = EPS) {
1304
1295
  const m = /* @__PURE__ */ new Map();
1305
1296
  m.set(0, { p: 1, count: { miss: 1 }, attr: {} });
1306
1297
  return new _PMF(m, epsilon, false, "zero");
1307
1298
  }
1299
+ static delta(value, epsilon = EPS) {
1300
+ return _PMF.fromMap(/* @__PURE__ */ new Map([[value, 1]]), epsilon);
1301
+ }
1308
1302
  // This creates a single bin at value 0, but with weight 0.
1309
1303
  static emptyMass() {
1310
1304
  return _PMF.zero().scaleMass(0);
@@ -1407,7 +1401,7 @@ var _PMF = class _PMF {
1407
1401
  * @param eps Optional tolerance for floating point rounding
1408
1402
  * @returns A single PMF representing the exclusive mixture of all options.
1409
1403
  */
1410
- static exclusive(options, eps = 1e-12) {
1404
+ static exclusive(options, eps = EPS) {
1411
1405
  const items = options.map(
1412
1406
  (o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o
1413
1407
  );
@@ -1437,8 +1431,7 @@ var _PMF = class _PMF {
1437
1431
  * [pCrit, critPMF],
1438
1432
  * ]);
1439
1433
  */
1440
- static mixN(weights) {
1441
- const eps = 1e-12;
1434
+ static mixN(weights, eps = EPS) {
1442
1435
  const filtered = weights.filter(([w]) => w > eps);
1443
1436
  if (filtered.length === 0) {
1444
1437
  return _PMF.emptyMass();
@@ -1460,17 +1453,23 @@ var _PMF = class _PMF {
1460
1453
  // This is a convenience method for when we use power
1461
1454
  // TODO: It can be smarter in the future, and we can also add it to query
1462
1455
  // That way statistics operations on invalid PMFs can throw an error
1463
- // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses providence?
1464
- setPreservedProvidence(preserved) {
1465
- if (!this._preservedProvidence && preserved) {
1456
+ // TODO… how can we detect if manually merging two queries' combined PMFs, as that loses provenance?
1457
+ setPreservedProvenance(preserved) {
1458
+ if (!this._preservedProvenance && preserved) {
1466
1459
  throw new Error(
1467
- "Preserved providence is already set to false, cannot fix that"
1460
+ "Preserved provenance is already set to false, cannot fix that"
1468
1461
  );
1469
1462
  }
1470
- this._preservedProvidence = preserved;
1463
+ this._preservedProvenance = preserved;
1471
1464
  }
1472
- preservedProvidence() {
1473
- return this._preservedProvidence;
1465
+ preservedProvenance() {
1466
+ return this._preservedProvenance;
1467
+ }
1468
+ getPowerCacheKey(n, eps) {
1469
+ const id = this.identifier;
1470
+ let key = `${id}`;
1471
+ for (let i = 1; i < n; i++) key += `+${id}`;
1472
+ return `${key}@${eps}`;
1474
1473
  }
1475
1474
  /**
1476
1475
  * Efficiently computes this PMF convolved with itself `n` times.
@@ -1488,7 +1487,7 @@ var _PMF = class _PMF {
1488
1487
  }
1489
1488
  if (n === 1) return this;
1490
1489
  const epsilon = eps ?? this.epsilon;
1491
- const key = getPowerCacheKey(this, n, epsilon);
1490
+ const key = this.getPowerCacheKey(n, epsilon);
1492
1491
  {
1493
1492
  const cached = pmfCache?.get(key);
1494
1493
  if (cached) return cached;
@@ -1505,7 +1504,7 @@ var _PMF = class _PMF {
1505
1504
  base = base.convolve(base, epsilon);
1506
1505
  }
1507
1506
  }
1508
- result.setPreservedProvidence(false);
1507
+ result.setPreservedProvenance(false);
1509
1508
  {
1510
1509
  pmfCache?.set(key, result);
1511
1510
  }
@@ -1534,7 +1533,7 @@ var _PMF = class _PMF {
1534
1533
  outcomeMass(outcome) {
1535
1534
  let totalProbabilityMass = 0;
1536
1535
  for (const { p, count } of this.map.values()) {
1537
- totalProbabilityMass += p * count[outcome];
1536
+ totalProbabilityMass += p * (count[outcome] ?? 0);
1538
1537
  }
1539
1538
  return totalProbabilityMass;
1540
1539
  }
@@ -1797,7 +1796,7 @@ var _PMF = class _PMF {
1797
1796
  }
1798
1797
  convolve(other, eps, raw = false) {
1799
1798
  const epsilon = eps ?? this.epsilon;
1800
- const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= 1e-12 ? x : x.normalize();
1799
+ const norm = (x) => raw ? x : Math.abs(x.mass() - 1) <= epsilon ? x : x.normalize();
1801
1800
  const A0 = norm(this);
1802
1801
  const B0 = norm(other);
1803
1802
  const [A, B] = A0.identifier <= B0.identifier ? [A0, B0] : [B0, A0];
@@ -1832,14 +1831,13 @@ var _PMF = class _PMF {
1832
1831
  epsilon,
1833
1832
  !raw,
1834
1833
  `${A.identifier}${raw ? "*" : "+"}${B.identifier}`
1835
- // ← canonical order
1836
1834
  );
1837
1835
  const mExp = (raw ? A.mass() : 1) * (raw ? B.mass() : 1);
1838
1836
  const mGot = result.mass();
1839
- if (mExp !== 0 && Math.abs(mGot - mExp) > 1e-12) {
1837
+ if (mExp !== 0 && Math.abs(mGot - mExp) > epsilon) {
1840
1838
  result = result.scaleMass(mExp / mGot);
1841
1839
  }
1842
- if (!raw && Math.abs(result.mass() - 1) > 1e-12)
1840
+ if (!raw && Math.abs(result.mass() - 1) > epsilon)
1843
1841
  result = result.normalize();
1844
1842
  pmfCache?.set(cacheKey, result);
1845
1843
  return result;
@@ -1887,7 +1885,7 @@ var _PMF = class _PMF {
1887
1885
  * - Similar PMF lists share common prefixes (A+B, (A+B)+C, etc.)
1888
1886
  * - Order-independent cache keys work better with consistent build patterns
1889
1887
  */
1890
- static convolveMany(pmfList, eps = EPSILON) {
1888
+ static convolveMany(pmfList, eps = EPS) {
1891
1889
  if (pmfList.length === 0) return _PMF.empty(eps);
1892
1890
  if (pmfList.length === 1) return pmfList[0];
1893
1891
  return _PMF.reduceConvolveLeft(pmfList, eps);
@@ -1902,7 +1900,7 @@ var _PMF = class _PMF {
1902
1900
  static fromJSON(jsonData) {
1903
1901
  return new _PMF(
1904
1902
  new Map(jsonData.bins),
1905
- EPSILON,
1903
+ EPS,
1906
1904
  !!jsonData.normalized,
1907
1905
  jsonData.identifier || "fromJSON"
1908
1906
  );
@@ -2048,6 +2046,21 @@ var _PMF = class _PMF {
2048
2046
  }
2049
2047
  return false;
2050
2048
  }
2049
+ tailProbGE(t) {
2050
+ let s = 0;
2051
+ for (const [x, rec] of this) {
2052
+ const p = typeof rec === "number" ? rec : rec.p;
2053
+ if (p > 0 && x >= t) s += p;
2054
+ }
2055
+ return s;
2056
+ }
2057
+ tailProbGT(t) {
2058
+ let s = 0;
2059
+ for (const [x, rec] of this) {
2060
+ if (x > t) s += rec.p;
2061
+ }
2062
+ return s;
2063
+ }
2051
2064
  /**
2052
2065
  * Returns a new PMF containing only bins where the specified outcome has non-zero probability.
2053
2066
  * This creates a marginal distribution for the given outcome type, with probabilities
@@ -2107,6 +2120,66 @@ var _PMF = class _PMF {
2107
2120
  const pNone = 1 - pSpecificSuccess - pGeneralSuccess;
2108
2121
  return { pSpecificSuccess, pGeneralSuccess, pNone, pAny };
2109
2122
  }
2123
+ mapValues(f, eps = EPS, opts) {
2124
+ const rounding = opts?.rounding ?? "none";
2125
+ const preserveCounts = opts?.preserveCounts ?? true;
2126
+ const round = (x) => rounding === "floor" ? Math.floor(x) : rounding === "ceil" ? Math.ceil(x) : rounding === "round" ? Math.round(x) : x;
2127
+ const probs = /* @__PURE__ */ new Map();
2128
+ const counts = /* @__PURE__ */ new Map();
2129
+ for (const [v, rec] of this) {
2130
+ if (Math.abs(rec.p) < eps) continue;
2131
+ const u = round(f(v));
2132
+ probs.set(u, (probs.get(u) ?? 0) + rec.p);
2133
+ if (preserveCounts) {
2134
+ const rec2 = this.map.get(v);
2135
+ const src = typeof rec2 === "number" ? void 0 : rec2?.count;
2136
+ if (src) {
2137
+ const dest = counts.get(u) ?? {};
2138
+ for (const k in src) {
2139
+ dest[k] = (dest[k] ?? 0) + src[k];
2140
+ }
2141
+ counts.set(u, dest);
2142
+ }
2143
+ }
2144
+ }
2145
+ const internal = /* @__PURE__ */ new Map();
2146
+ for (const [u, p] of probs) {
2147
+ internal.set(u, { p, count: counts.get(u) ?? {} });
2148
+ }
2149
+ return _PMF.fromMap(
2150
+ new Map(Array.from(internal, ([u, b]) => [u, b.p])),
2151
+ eps
2152
+ );
2153
+ }
2154
+ static fromMap(m, eps = EPS, { requireIntegerValues = true } = {}) {
2155
+ const filtered = [];
2156
+ for (const [v, p] of m) {
2157
+ if (!Number.isFinite(v) || !Number.isFinite(p)) continue;
2158
+ if (p <= 0 || Math.abs(p) < eps) continue;
2159
+ if (requireIntegerValues && !Number.isInteger(v)) {
2160
+ throw new Error(`fromMap: non-integer outcome ${v}`);
2161
+ }
2162
+ filtered.push([v, p]);
2163
+ }
2164
+ if (filtered.length === 0) {
2165
+ throw new Error("fromMap: empty or invalid input map");
2166
+ }
2167
+ let sum = 0;
2168
+ let c = 0;
2169
+ for (const [, p] of filtered) {
2170
+ const y = p - c;
2171
+ const t = sum + y;
2172
+ c = t - sum - y;
2173
+ sum = t;
2174
+ }
2175
+ if (sum <= 0) throw new Error("pmfFromMap: probabilities sum to 0");
2176
+ filtered.sort((a, b) => a[0] - b[0]);
2177
+ const internal = /* @__PURE__ */ new Map();
2178
+ for (const [v, p] of filtered) {
2179
+ internal.set(v, { p: p / sum, count: {} });
2180
+ }
2181
+ return new _PMF(internal, eps);
2182
+ }
2110
2183
  query() {
2111
2184
  return new DiceQuery(this);
2112
2185
  }
@@ -2114,12 +2187,127 @@ var _PMF = class _PMF {
2114
2187
  // Unique ID generator for anonymous PMFs to avoid cache key collisions
2115
2188
  __publicField(_PMF, "__anonIdCounter", 1);
2116
2189
  var PMF = _PMF;
2117
- function getPowerCacheKey(base, n, eps) {
2118
- const id = base.identifier;
2119
- let key = `${id}`;
2120
- for (let i = 1; i < n; i++) key += `+${id}`;
2121
- return `${key}@${eps}`;
2122
- }
2190
+
2191
+ // src/mixture.ts
2192
+ var Mixture = class _Mixture {
2193
+ constructor(eps = EPS) {
2194
+ __publicField(this, "totals", /* @__PURE__ */ new Map());
2195
+ // raw mass per outcome (pre-normalization)
2196
+ __publicField(this, "labelMass", /* @__PURE__ */ new Map());
2197
+ // raw mass per outcome per label
2198
+ __publicField(this, "eps");
2199
+ this.eps = Number.isFinite(eps) ? eps : EPS;
2200
+ }
2201
+ /** Remove all accumulated state. */
2202
+ clear() {
2203
+ this.totals.clear();
2204
+ this.labelMass.clear();
2205
+ return this;
2206
+ }
2207
+ /** Number of distinct outcome values currently accumulated. */
2208
+ size() {
2209
+ return this.totals.size;
2210
+ }
2211
+ /** Whether a label was ever added. */
2212
+ hasLabel(label) {
2213
+ for (const bag of this.labelMass.values()) if (bag[label]) return true;
2214
+ return false;
2215
+ }
2216
+ /**
2217
+ * Add a labeled component with a mixture weight.
2218
+ * Weight can be any positive finite number. Very small contributions are pruned by eps.
2219
+ */
2220
+ add(label, pmf, weight = 1) {
2221
+ if (!Number.isFinite(weight) || weight <= 0) return this;
2222
+ for (const [v, binOrNumber] of pmf) {
2223
+ const isNumber = typeof binOrNumber === "number";
2224
+ const p = isNumber ? binOrNumber : binOrNumber?.p ?? 0;
2225
+ if (p <= 0) continue;
2226
+ const add = weight * p;
2227
+ if (!Number.isFinite(add) || Math.abs(add) < this.eps) continue;
2228
+ this.totals.set(v, (this.totals.get(v) ?? 0) + add);
2229
+ const bag = this.labelMass.get(v) ?? {};
2230
+ bag[label] = (bag[label] ?? 0) + add;
2231
+ this.labelMass.set(v, bag);
2232
+ }
2233
+ return this;
2234
+ }
2235
+ buildPMF(eps = EPS) {
2236
+ let grand = 0;
2237
+ let c = 0;
2238
+ for (const m of this.totals.values()) {
2239
+ const y = m - c;
2240
+ const t = grand + y;
2241
+ c = t - grand - y;
2242
+ grand = t;
2243
+ }
2244
+ if (!(grand > 0)) throw new Error("LabeledMixture: zero total mass");
2245
+ const internal = /* @__PURE__ */ new Map();
2246
+ for (const [v, m] of this.totals) {
2247
+ if (m <= 0 || Math.abs(m) < this.eps) continue;
2248
+ const count = this.labelMass.get(v) ?? {};
2249
+ internal.set(v, { p: m / grand, count });
2250
+ }
2251
+ return new PMF(internal, eps);
2252
+ }
2253
+ /**
2254
+ * Produce normalized *per-label* PMFs (labels independent).
2255
+ * These are unlabeled PMFs built from the raw mass of that label alone.
2256
+ */
2257
+ byOutcome() {
2258
+ const labels = /* @__PURE__ */ new Set();
2259
+ for (const bag of this.labelMass.values()) {
2260
+ for (const k of Object.keys(bag)) labels.add(k);
2261
+ }
2262
+ const out = {};
2263
+ for (const label of labels) {
2264
+ const m = /* @__PURE__ */ new Map();
2265
+ for (const [v, bag] of this.labelMass) {
2266
+ const w = bag[label];
2267
+ if (w && Math.abs(w) >= this.eps) m.set(v, w);
2268
+ }
2269
+ if (m.size > 0) out[label] = PMF.fromMap(m, this.eps);
2270
+ }
2271
+ return out;
2272
+ }
2273
+ /**
2274
+ * Mixture weights per label, normalized to sum to 1 over labels that appeared.
2275
+ * Uses raw mass before per-outcome normalization.
2276
+ */
2277
+ weights() {
2278
+ const res = {};
2279
+ for (const [, bag] of this.labelMass) {
2280
+ for (const [lab, w] of Object.entries(bag)) {
2281
+ if (!Number.isFinite(w) || w <= 0) continue;
2282
+ res[lab] = (res[lab] ?? 0) + w;
2283
+ }
2284
+ }
2285
+ let total = 0;
2286
+ let c = 0;
2287
+ for (const v of Object.values(res)) {
2288
+ const y = v - c;
2289
+ const t = total + y;
2290
+ c = t - total - y;
2291
+ total = t;
2292
+ }
2293
+ if (total > 0) {
2294
+ for (const k in res) res[k] = res[k] / total;
2295
+ }
2296
+ return res;
2297
+ }
2298
+ toJSON() {
2299
+ return {
2300
+ totals: Array.from(this.totals.entries()).sort((a, b) => a[0] - b[0]),
2301
+ labels: Array.from(this.labelMass.entries()).sort((a, b) => a[0] - b[0]),
2302
+ eps: this.eps
2303
+ };
2304
+ }
2305
+ static mix(items, eps = EPS) {
2306
+ const mix = new _Mixture(eps);
2307
+ for (const [lab, pmf, w] of items) mix.add(lab, pmf, w);
2308
+ return mix.buildPMF();
2309
+ }
2310
+ };
2123
2311
 
2124
2312
  // src/dice.ts
2125
2313
  var Dice = class _Dice {
@@ -2435,7 +2623,7 @@ var Dice = class _Dice {
2435
2623
  * Convert dice to PMF using OutcomeType labels directly from damage distribution.
2436
2624
  * This is much cleaner than the original complex distribution conversion.
2437
2625
  */
2438
- toPMF(numEpsilon = COMPUTATIONAL_EPS) {
2626
+ toPMF(numEpsilon = EPS) {
2439
2627
  const total = this.total();
2440
2628
  if (total === 0) return PMF.empty(numEpsilon);
2441
2629
  this.ensureHitDistribution();
@@ -2614,7 +2802,7 @@ function parseExpression(arr, n) {
2614
2802
  }
2615
2803
  critNorm = crit.total();
2616
2804
  crit = op.call(crit, parseBinaryArgument(arg, arr, n));
2617
- critNorm = critNorm ? crit.total() / critNorm : 1;
2805
+ critNorm = crit && critNorm ? crit.total() / critNorm : 1;
2618
2806
  }
2619
2807
  let save;
2620
2808
  let saveNorm = 1;
@@ -2629,7 +2817,7 @@ function parseExpression(arr, n) {
2629
2817
  saveNorm = save.total();
2630
2818
  finalResult = finalResult.deleteFace(min);
2631
2819
  save = op.call(save, parseBinaryArgument(arg, arr, n));
2632
- saveNorm = saveNorm ? save.total() / saveNorm : 1;
2820
+ saveNorm = save && saveNorm ? save.total() / saveNorm : 1;
2633
2821
  }
2634
2822
  let pc;
2635
2823
  let pcNorm = 1;
@@ -2642,7 +2830,7 @@ function parseExpression(arr, n) {
2642
2830
  const missBefore = pc.total();
2643
2831
  finalResult = finalResult.deleteFace(min);
2644
2832
  pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2);
2645
- const missAfter = pc.total();
2833
+ const missAfter = pc ? pc.total() : 0;
2646
2834
  pcNorm = missBefore ? missAfter / missBefore : 1;
2647
2835
  }
2648
2836
  let miss;
@@ -2658,7 +2846,7 @@ function parseExpression(arr, n) {
2658
2846
  missNorm = miss.total();
2659
2847
  finalResult = finalResult.deleteFace(min);
2660
2848
  miss = op.call(miss, parseBinaryArgument(arg, arr, n));
2661
- missNorm = missNorm ? miss.total() / missNorm : 1;
2849
+ missNorm = miss && missNorm ? miss.total() / missNorm : 1;
2662
2850
  }
2663
2851
  let norm = finalResult.total();
2664
2852
  finalResult = op.call(finalResult, arg);
@@ -2948,16 +3136,15 @@ function parseOperation(s) {
2948
3136
  return;
2949
3137
  }
2950
3138
 
2951
- exports.COMPUTATIONAL_EPS = COMPUTATIONAL_EPS;
2952
- exports.Dice = Dice;
2953
3139
  exports.DiceQuery = DiceQuery;
3140
+ exports.EPS = EPS;
2954
3141
  exports.LRUCache = LRUCache;
3142
+ exports.Mixture = Mixture;
2955
3143
  exports.PMF = PMF;
2956
- exports.TEST_EPS = TEST_EPS;
2957
3144
  exports.clearParserCache = clearParserCache;
2958
3145
  exports.getCachingEnabled = getCachingEnabled;
2959
- exports.getPowerCacheKey = getPowerCacheKey;
2960
3146
  exports.parse = parse;
3147
+ exports.pmfCache = pmfCache;
2961
3148
  exports.setCachingEnabled = setCachingEnabled;
2962
3149
  //# sourceMappingURL=index.cjs.map
2963
3150
  //# sourceMappingURL=index.cjs.map