| Copyright | (c) 2026 Jared Tobin |
|---|---|
| License | MIT |
| Maintainer | Jared Tobin <jared@ppad.tech> |
| Safe Haskell | Safe-Inferred |
| Language | Haskell2010 |
Numeric.Eproc.Bernoulli.TwoSided
Description
Two-sided Bernoulli rate anytime-valid test. Companion to Numeric.Eproc.Bernoulli, which handles the one-sided case; reach for this module when you want to test
H_0: E[x_t | F_{t-1}] = p_0 for all tagainst the negation. The canonical case is the sign test at
p_0 = 1/2.
This is exactly the two-sided bounded-mean test on [0, 1] with
null mean p_0, so the module is a thin newtype wrapper over
Numeric.Eproc.Bounded (much as Numeric.Eproc.Paired is a
wrapper for the paired difference case). See the Bounded module
for the mathematical detail: convex-hedge combination of two
per-direction e-processes, threshold log(2 / alpha), latched
rejection, etc.
Example
Sign test at p_0 = 1/2 with a downward shift:
>>>let Right cfg = config 0.5 1.0e-3 Newton>>>let s0 = initial cfg>>>let xs = take 500 (cycle [False, False, False, True])>>>decide cfg (foldl' (update cfg) s0 xs)Reject
Synopsis
- data Config
- data State
- data Verdict
- data ConfigError
- data Bettor
- config :: Double -> Double -> Bettor -> Either ConfigError Config
- initial :: Config -> State
- update :: Config -> State -> Bool -> State
- decide :: Config -> State -> Verdict
- log_wealth :: State -> Double
- log_wealth_sup :: State -> Double
- log_evalue :: State -> Double
- log_evalue_sup :: State -> Double
- p_value :: State -> Double
- samples :: State -> Int
Test configuration and state
Test outcome at the current sample count.
Reject means the wealth process has ever crossed the
rejection threshold, so H_0 is rejected at level alpha.
Once a state has rejected it stays rejected, even if subsequent
observations drive the current wealth back below threshold;
this is the supremum-style guarantee that Ville's inequality
actually delivers. 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).
Instances
data ConfigError Source #
Reasons that a test-configuration smart constructor can reject
its inputs. Returned by config,
configInterval,
config,
config,
config, and
config.
Constructors
| InvalidAlpha !Double | significance level outside |
| InvalidBounds !Double !Double | sample bounds violate |
| InvalidNullMean !Double !Double !Double | null mean outside |
| InvalidNullInterval !Double !Double !Double !Double | null-interval endpoints violate |
| InvalidBaselineRate !Double | baseline rate outside |
| InvalidComponentCount !Int | component count not positive |
| InvalidGridSize !Int | grid size below |
Instances
| Show ConfigError Source # | |
Defined in Numeric.Eproc.Common Methods showsPrec :: Int -> ConfigError -> ShowS # show :: ConfigError -> String # showList :: [ConfigError] -> ShowS # | |
| Eq ConfigError Source # | |
Defined in Numeric.Eproc.Common | |
Bettor strategies
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.
All three bettors enforce a safe-bet ceiling lambda_max
derived from the test's admissible-observation range by clipping
lambda to [0, lambda_max]; this keeps the per-step wealth
factor nonnegative.
Fixedbets the supplied constantlambda(clipped to[0, lambda_max]). The wager does not respond to observed data; this strategy is useful only as a baseline.Adaptiveis the aGRAPA (approximate growth-rate adaptive predictable plug-in) bettor of Waudby-Smith & Ramdas (2024). It tracks the empirical meanmuand variancesigma^2of centred observations and bets the Kelly-optimal plug-inlambda* = mu / (sigma^2 + mu^2)clipped to[0, lambda_max]. Fast to compute and competitive in practice.Newtonis the online Newton step (ONS) bettor of Waudby-Smith & Ramdas (2024, Algorithm 2). The per-step log-wealth loss-log(1 + lambda * z)is convex inlambda; ONS performs one Newton step per observation, accumulating squared gradients to scale the update by a fixed learning rate2 / (2 - log 3). Achieves logarithmic regret against the best constant bet in hindsight and is in practice the strongest of the three bettors under most signal regimes.
One deliberate deviation from WSR: Algorithm 2 seeds the
squared-gradient accumulator at 1, which presumes
observations scaled to [0, 1]. On raw-scale data that
constant is dimensionally wrong -- negligible when
z^2 >> 1, paralysing when z^2 << 1 -- so the accumulator
here is instead seeded near zero, making the update
scale-adaptive. The trade is bold early play: the first
nonzero observation typically drives the bet straight to
the lambda_max ceiling, annealing back toward the Kelly
point as gradients accumulate. Validity is unaffected --
predictability and clipping are all it needs -- and regret
stays logarithmic with a somewhat larger constant. The
visible effect is higher-variance early wealth: a supremum
modestly above its floor is expected even under H_0.
Construction
Arguments
| :: Double | baseline rate |
| -> Double | significance level |
| -> Bettor | bettor strategy |
| -> Either ConfigError Config |
Build a Config for the two-sided Bernoulli rate test.
Returns Left with a ConfigError on inputs that would leave
the mathematical regime: p_0 outside (0, 1) (or non-finite),
or alpha outside (0, 1) (or non-finite).
>>>let Right cfg = config 0.5 1.0e-3 Newton
initial :: Config -> State Source #
The initial State for a fresh streaming test.
>>>let s0 = initial cfg
Streaming
update :: Config -> State -> Bool -> State Source #
Fold one observation into the running State. Equivalent to
feeding the numeric 1/0 encoding of the observation into
the underlying bounded-mean test.
>>>let s1 = update cfg s0 True
Inspection
log_wealth :: State -> Double Source #
The current log(K^+_t + K^-_t) of the underlying bounded-mean
test. Not monotone; bounded above by log_wealth_sup. Starts
at log 2.
>>>log_wealth s00.6931471805599453
log_wealth_sup :: State -> Double Source #
The supremum-so-far of log(K^+_t + K^-_t) from the underlying
bounded-mean test. Monotone nondecreasing; decide rejects
exactly when it crosses log(2 / alpha). Starts at log 2.
>>>log_wealth_sup s00.6931471805599453
log_evalue :: State -> Double Source #
The current log e-value of the underlying bounded-mean test:
log_wealth minus log 2, normalized so a fresh state sits at
0. Not monotone; bounded above by log_evalue_sup.
>>>log_evalue s00.0
log_evalue_sup :: State -> Double Source #
The supremum-so-far of the log e-value: log_wealth_sup minus
log 2. Monotone nondecreasing, starting at 0; decide
rejects exactly when it crosses log(1 / alpha).
>>>log_evalue_sup s00.0