ppad-eproc-0.5.1: Anytime-valid sequential testing via e-processes.
Copyright(c) 2026 Jared Tobin
LicenseMIT
MaintainerJared Tobin <jared@ppad.tech>
Safe HaskellSafe-Inferred
LanguageHaskell2010

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 t

against 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

Test configuration and state

data Config Source #

Two-sided Bernoulli rate test configuration. Build with config. Wraps a Config on [0, 1] with null mean p_0.

data State Source #

Streaming test state. Construct with initial and fold observations through update.

data Verdict Source #

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).

Constructors

Reject 
Continue 

Instances

Instances details
Show Verdict Source # 
Instance details

Defined in Numeric.Eproc.Common

Eq Verdict Source # 
Instance details

Defined in Numeric.Eproc.Common

Methods

(==) :: Verdict -> Verdict -> Bool #

(/=) :: Verdict -> Verdict -> Bool #

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 (0, 1)

InvalidBounds !Double !Double

sample bounds violate lo < hi

InvalidNullMean !Double !Double !Double

null mean outside (lo, hi) (strict, to avoid div-by-zero in the safe-bet ceilings)

InvalidNullInterval !Double !Double !Double !Double

null-interval endpoints violate lo < m_lo <= m_hi < hi (strict at the sample bounds, to avoid div-by-zero in the safe-bet ceilings)

InvalidBaselineRate !Double

baseline rate outside (0, 1)

InvalidComponentCount !Int

component count not positive

InvalidGridSize !Int

grid size below 1

Instances

Instances details
Show ConfigError Source # 
Instance details

Defined in Numeric.Eproc.Common

Eq ConfigError Source # 
Instance details

Defined in Numeric.Eproc.Common

Bettor strategies

data Bettor Source #

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.

  • Fixed bets the supplied constant lambda (clipped to [0, lambda_max]). 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 of Waudby-Smith & Ramdas (2024, Algorithm 2). 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 by a fixed learning rate 2 / (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.

Constructors

Fixed !Double 
Adaptive 
Newton 

Instances

Instances details
Show Bettor Source # 
Instance details

Defined in Numeric.Eproc.Common

Eq Bettor Source # 
Instance details

Defined in Numeric.Eproc.Common

Methods

(==) :: Bettor -> Bettor -> Bool #

(/=) :: Bettor -> Bettor -> Bool #

Construction

config Source #

Arguments

:: Double

baseline rate p_0, in (0, 1)

-> Double

significance level alpha, in (0, 1)

-> 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

decide :: Config -> State -> Verdict Source #

Compute the current Verdict from the running State.

>>> decide cfg s0
Continue

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 s0
0.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 s0
0.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 s0
0.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 s0
0.0

p_value :: State -> Double Source #

The anytime-valid p-value: the reciprocal of the largest e-value attained so far. Monotone nonincreasing; under H_0, P(exists t: p_t <= alpha) <= alpha for every alpha simultaneously. decide returns Reject exactly when this value has reached the configured alpha or below.

>>> p_value s0
1.0

samples :: State -> Int Source #

The number of samples consumed so far.

>>> samples s0
0