commit 2a3196374cc7b58d3cd7ad1fd774a5cd914469aa
parent dea08bd4741aa02cc1c0039090dca018c5aeedbd
Author: Jared Tobin <jared@jtobin.io>
Date: Thu, 4 Jun 2026 17:01:51 -0230
extract Bettor and Verdict into Numeric.Eproc.Common
Both types are now defined once in Numeric.Eproc.Common and
re-exported from each test module (Bounded, Paired, Bernoulli).
Single source of truth for the shared vocabulary; callers can
still import any test module and get the full surface without a
second import.
Diffstat:
5 files changed, 82 insertions(+), 95 deletions(-)
diff --git a/lib/Numeric/Eproc/Bernoulli.hs b/lib/Numeric/Eproc/Bernoulli.hs
@@ -62,53 +62,13 @@ module Numeric.Eproc.Bernoulli (
, samples
) where
--- types ----------------------------------------------------------------------
+import Numeric.Eproc.Common (Bettor(..), Verdict(..))
--- | A predictable bettor.
---
--- A bettor describes how, given the history of centred observations
--- @z_t = x_t - p_0@, the next predictable bet @lambda_t@ is chosen.
--- Predictability -- that is, @lambda_t@ depends only on data
--- observed strictly before step @t@ -- is what makes the resulting
--- wealth process a nonnegative supermartingale under @H_0@.
---
--- For 'Adaptive' and 'Newton', a safe-bet ceiling @lambda_max@ is
--- derived from the baseline rate @p_0@ supplied to 'config' -- bets
--- get clipped to @[0, lambda_max]@ so that the wealth factor
--- @1 + lambda * (x - p_0)@ stays nonnegative for both @x = 0@ and
--- @x = 1@.
---
--- * 'Fixed' always bets the supplied constant @lambda@. The wager
--- does not respond to observed data; this strategy is useful only
--- as a baseline.
---
--- * 'Adaptive' is the Bernoulli analogue of the aGRAPA bettor of
--- Waudby-Smith & Ramdas (2024). It tracks the empirical mean
--- @mu@ and variance @sigma^2@ of centred observations and bets
--- the Kelly-optimal plug-in @lambda* = mu \/ (sigma^2 + mu^2)@
--- clipped to @[0, lambda_max]@.
---
--- * 'Newton' is the online Newton step (ONS) bettor. The per-step
--- log-wealth loss @-log(1 + lambda * z)@ is convex in @lambda@;
--- ONS performs one Newton step per observation, accumulating
--- squared gradients to scale the update.
-data Bettor =
- Fixed {-# UNPACK #-} !Double
- | Adaptive
- | Newton
- deriving (Eq, Show)
+-- types ----------------------------------------------------------------------
--- | Test outcome at the current sample count.
---
--- 'Reject' means the wealth process has crossed the threshold, so
--- @H_0@ is rejected at level @alpha@. 'Continue' means there is
--- not yet enough evidence; collect more samples (or stop and
--- report no rejection -- the type-I error guarantee holds for
--- /any/ stopping rule).
-data Verdict =
- Reject
- | Continue
- deriving (Eq, Show)
+-- here, the centred observation @z_t@ referenced in
+-- "Numeric.Eproc.Common" is @x_t - p_0@; the safe-bet ceiling
+-- @lambda_max@ is derived from @p_0@ (see 'config').
-- bettor state. one constructor per 'Bettor' alternative; the
-- constructor used in a given 'State' matches the 'Bettor' chosen in
diff --git a/lib/Numeric/Eproc/Bounded.hs b/lib/Numeric/Eproc/Bounded.hs
@@ -60,58 +60,14 @@ module Numeric.Eproc.Bounded (
) where
import GHC.Exts (Double(D#))
+import Numeric.Eproc.Common (Bettor(..), Verdict(..))
-- types ----------------------------------------------------------------------
--- | A predictable bettor.
---
--- A bettor describes how, given the history of centred observations
--- @z_t = x_t - m@, the next predictable bet @lambda_t@ is chosen.
--- Predictability -- that is, @lambda_t@ depends only on data
--- observed strictly before step @t@ -- is what makes the resulting
--- wealth process a nonnegative supermartingale under @H_0@.
---
--- For 'Adaptive' and 'Newton', a per-direction safe-bet ceiling
--- @lambda_max@ is derived from the sample bounds supplied to
--- 'config' -- bets get clipped to @[0, lambda_max]@ so that the
--- wealth factor @1 + lambda * z@ stays nonnegative for every
--- admissible observation.
---
--- * 'Fixed' always bets the supplied constant @lambda@. The wager
--- does not respond to observed data; this strategy is useful only
--- as a baseline.
---
--- * 'Adaptive' is the aGRAPA (approximate growth-rate adaptive
--- predictable plug-in) bettor of Waudby-Smith & Ramdas (2024).
--- It tracks the empirical mean @mu@ and variance @sigma^2@ of
--- centred observations and bets the Kelly-optimal plug-in
--- @lambda* = mu \/ (sigma^2 + mu^2)@ clipped to
--- @[0, lambda_max]@. Fast to compute and competitive in practice.
---
--- * 'Newton' is the online Newton step (ONS) bettor. The per-step
--- log-wealth loss @-log(1 + lambda * z)@ is convex in @lambda@;
--- ONS performs one Newton step per observation, accumulating
--- squared gradients to scale the update. Achieves logarithmic
--- regret against the best constant bet in hindsight and is in
--- practice the strongest of the three bettors under most signal
--- regimes.
-data Bettor =
- Fixed {-# UNPACK #-} !Double
- | Adaptive
- | Newton
- deriving (Eq, Show)
-
--- | Test outcome at the current sample count.
---
--- 'Reject' means the wealth process has crossed the Bonferroni
--- threshold, so @H_0@ is rejected at level @alpha@. 'Continue'
--- means there is not yet enough evidence; collect more samples (or
--- stop and report no rejection -- the type-I error guarantee holds
--- for /any/ stopping rule).
-data Verdict =
- Reject
- | Continue
- deriving (Eq, Show)
+-- here, the centred observation @z_t@ referenced in
+-- "Numeric.Eproc.Common" is @x_t - m@; the per-direction safe-bet
+-- ceilings @lambda_max@ are derived from the sample bounds (see
+-- 'config').
-- per-direction bettor state. one constructor per 'Bettor' alternative;
-- the constructor used in a given 'State' matches the 'Bettor' chosen
diff --git a/lib/Numeric/Eproc/Common.hs b/lib/Numeric/Eproc/Common.hs
@@ -0,0 +1,70 @@
+{-# OPTIONS_HADDOCK prune #-}
+
+-- |
+-- Module: Numeric.Eproc.Common
+-- Copyright: (c) 2026 Jared Tobin
+-- License: MIT
+-- Maintainer: Jared Tobin <jared@ppad.tech>
+--
+-- Shared vocabulary for the eproc tests: the predictable bettor
+-- strategies and the test verdict type. Re-exported from each test
+-- module ("Numeric.Eproc.Bounded", "Numeric.Eproc.Paired",
+-- "Numeric.Eproc.Bernoulli"); import this module directly only if
+-- you need the types without picking a particular test.
+
+module Numeric.Eproc.Common (
+ Bettor(..)
+ , Verdict(..)
+ ) where
+
+-- | A predictable bettor.
+--
+-- A bettor describes how, given the history of centred
+-- observations @z_t@ (each test module specifies its own centring;
+-- see the per-module documentation), the next predictable bet
+-- @lambda_t@ is chosen. Predictability -- that is, @lambda_t@
+-- depends only on data observed strictly before step @t@ -- is
+-- what makes the resulting wealth process a nonnegative
+-- supermartingale under @H_0@.
+--
+-- For 'Adaptive' and 'Newton', a safe-bet ceiling @lambda_max@
+-- derived from the test's admissible-observation range is enforced
+-- by clipping @lambda@ to @[0, lambda_max]@, so the wealth factor
+-- stays nonnegative.
+--
+-- * 'Fixed' always bets the supplied constant @lambda@. The wager
+-- does not respond to observed data; this strategy is useful
+-- only as a baseline.
+--
+-- * 'Adaptive' is the aGRAPA (approximate growth-rate adaptive
+-- predictable plug-in) bettor of Waudby-Smith & Ramdas (2024).
+-- It tracks the empirical mean @mu@ and variance @sigma^2@ of
+-- centred observations and bets the Kelly-optimal plug-in
+-- @lambda* = mu \/ (sigma^2 + mu^2)@ clipped to
+-- @[0, lambda_max]@. Fast to compute and competitive in
+-- practice.
+--
+-- * 'Newton' is the online Newton step (ONS) bettor. The per-step
+-- log-wealth loss @-log(1 + lambda * z)@ is convex in @lambda@;
+-- ONS performs one Newton step per observation, accumulating
+-- squared gradients to scale the update. Achieves logarithmic
+-- regret against the best constant bet in hindsight and is in
+-- practice the strongest of the three bettors under most signal
+-- regimes.
+data Bettor =
+ Fixed {-# UNPACK #-} !Double
+ | Adaptive
+ | Newton
+ deriving (Eq, Show)
+
+-- | Test outcome at the current sample count.
+--
+-- 'Reject' means the wealth process has crossed the rejection
+-- threshold, so @H_0@ is rejected at level @alpha@. 'Continue'
+-- means there is not yet enough evidence; collect more samples
+-- (or stop and report no rejection -- the type-I error guarantee
+-- holds for /any/ stopping rule).
+data Verdict =
+ Reject
+ | Continue
+ deriving (Eq, Show)
diff --git a/lib/Numeric/Eproc/Paired.hs b/lib/Numeric/Eproc/Paired.hs
@@ -58,7 +58,7 @@ module Numeric.Eproc.Paired (
) where
import qualified Numeric.Eproc.Bounded as Bounded
-import Numeric.Eproc.Bounded (Verdict(..), Bettor(..))
+import Numeric.Eproc.Common (Bettor(..), Verdict(..))
-- types ----------------------------------------------------------------------
diff --git a/ppad-eproc.cabal b/ppad-eproc.cabal
@@ -36,6 +36,7 @@ library
exposed-modules:
Numeric.Eproc.Bernoulli
Numeric.Eproc.Bounded
+ Numeric.Eproc.Common
Numeric.Eproc.Paired
build-depends:
base >= 4.9 && < 5