commit d9ce56dd4e90a053e8c8e67b1fa96d272ab42724
parent 1998344fea37369058a401bec35870a1347043de
Author: Jared Tobin <jared@jtobin.io>
Date: Thu, 2 Jul 2026 16:46:15 -0230
docs: refresh CHANGELOG and README for the unreleased work
- CHANGELOG unreleased entry now mentions the perf work
(log1p + log_sum_exp fast-path) alongside the new
Bernoulli.TwoSided module, and notes that TwoSided is a
newtype wrapper over Bounded.
- README performance section refreshed with current
LLVM-enabled numbers, including a nod to the new Bernoulli
and Bernoulli.TwoSided bench groups.
- README GHCi example refreshed: 'log_wealth s10' had drifted
because the two-sided combination switched from Bonferroni
max to convex hedge and 'log_wealth' now reports
sup log(K^+ + K^-), starting at log 2.
Diffstat:
2 files changed, 31 insertions(+), 23 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -2,11 +2,19 @@
- unreleased
* New module 'Numeric.Eproc.Bernoulli.TwoSided' providing a
- two-sided Bernoulli rate test (H_0: p = p_0) via the same
- convex-hedge construction 'Bounded' uses. Canonical use is the
- sign test at p_0 = 1/2. Same 'config' / 'initial' / 'update' /
- 'decide' / 'log_wealth' / 'samples' shape as the sibling
- one-sided 'Numeric.Eproc.Bernoulli'.
+ two-sided Bernoulli rate test (H_0: p = p_0). Canonical use is
+ the sign test at p_0 = 1/2. Implemented as a newtype wrapper
+ over 'Numeric.Eproc.Bounded' (following the pattern
+ 'Numeric.Eproc.Paired' uses), with the same 'config' /
+ 'initial' / 'update' / 'decide' / 'log_wealth' / 'samples'
+ shape as the sibling one-sided 'Numeric.Eproc.Bernoulli'.
+
+ * Per-step performance improvements to 'Bounded.update' (and
+ hence 'Paired.update' and 'Bernoulli.TwoSided.update' via
+ wrapping): log1p replaces log-of-(1+x) for the wealth factor,
+ and log_sum_exp is skipped when a cheap upper bound guarantees
+ the running max-log-sum can't change. Under H_0 workloads the
+ fold is ~40% faster on two-sided tests.
- 0.2.1 (2026-07-02)
* Two-sided bounded-mean tests now reject faster, or at least never
diff --git a/README.md b/README.md
@@ -28,14 +28,14 @@ A sample GHCi session:
> -- inspect (supremum-so-far) log-wealth and stopping decision at any
> -- point
> Bounded.log_wealth s10
- 0.4054651081081644
+ 0.916290731874155
> Bounded.decide cfg s10
Continue
>
> -- with enough evidence, the hypothesis is rejected
> let s300 = foldl' (Bounded.update cfg) s0 (concat (replicate 30 xs))
> Bounded.log_wealth s300
- 51.142711428622924
+ 51.14271142862292
> Bounded.decide cfg s300
Reject
```
@@ -55,30 +55,30 @@ Current benchmark figures on an M4 Silicon MacBook Air look like (use
```
benchmarking Bounded.update (one step)/newton
- time 13.05 ns (12.95 ns .. 13.17 ns)
- 1.000 R² (0.999 R² .. 1.000 R²)
- mean 13.03 ns (12.95 ns .. 13.15 ns)
- std dev 314.0 ps (248.3 ps .. 422.3 ps)
+ time 13.96 ns (13.88 ns .. 14.04 ns)
benchmarking Bounded.update (1000-sample fold)/fixed
- time 4.840 μs (4.819 μs .. 4.867 μs)
- 1.000 R² (1.000 R² .. 1.000 R²)
- mean 4.828 μs (4.817 μs .. 4.847 μs)
- std dev 44.90 ns (30.94 ns .. 61.54 ns)
+ time 7.951 μs (7.944 μs .. 7.959 μs)
benchmarking Bounded.update (1000-sample fold)/adaptive
- time 15.67 μs (15.66 μs .. 15.69 μs)
- 1.000 R² (1.000 R² .. 1.000 R²)
- mean 15.67 μs (15.65 μs .. 15.69 μs)
- std dev 63.74 ns (55.65 ns .. 75.07 ns)
+ time 12.69 μs (12.68 μs .. 12.71 μs)
benchmarking Bounded.update (1000-sample fold)/newton
- time 14.43 μs (14.42 μs .. 14.44 μs)
- 1.000 R² (1.000 R² .. 1.000 R²)
- mean 14.43 μs (14.42 μs .. 14.44 μs)
- std dev 46.74 ns (34.00 ns .. 64.63 ns)
+ time 14.61 μs (14.57 μs .. 14.64 μs)
+
+ benchmarking Bernoulli.update (1000-sample fold)/newton
+ time 14.64 μs (14.63 μs .. 14.65 μs)
+
+ benchmarking Bernoulli.TwoSided.update (1000-sample fold)/newton
+ time 14.83 μs (14.81 μs .. 14.84 μs)
```
+The `Paired` and `Bernoulli.TwoSided` modules are thin newtype
+wrappers over `Bounded`, and inline through with no measurable
+overhead. See the criterion suite for the full breakdown across
+`Fixed` / `Adaptive` / `Newton` bettors and per-step / fold
+workloads.
+
You should compile with the `llvm` flag for maximum performance.
## Development