eproc

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit e93692ea1a08ac732c15618f7c5e8fa9383478ef
parent 3b66da8d75aef92493c251a3b2e8039c86ad91fb
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri,  3 Jul 2026 14:18:28 -0230

api: log_evalue, log_evalue_sup, and p_value accessors

Every test module now exposes its evidence in calibrated form: the
log e-value (log-wealth normalized so a fresh state sits at zero,
uniform across one-sided and hedged modules), its running supremum,
and the anytime-valid p-value derived from that supremum. decide
remains equivalent to comparing p_value against alpha.

Diffstat:
Mlib/Numeric/Eproc/Bernoulli.hs | 44++++++++++++++++++++++++++++++++++++++++++++
Mlib/Numeric/Eproc/Bernoulli/TwoSided.hs | 35+++++++++++++++++++++++++++++++++++
Mlib/Numeric/Eproc/Bounded.hs | 44++++++++++++++++++++++++++++++++++++++++++++
Mlib/Numeric/Eproc/Paired.hs | 36++++++++++++++++++++++++++++++++++++
4 files changed, 159 insertions(+), 0 deletions(-)

diff --git a/lib/Numeric/Eproc/Bernoulli.hs b/lib/Numeric/Eproc/Bernoulli.hs @@ -73,6 +73,9 @@ module Numeric.Eproc.Bernoulli ( -- * Inspection , log_wealth , log_wealth_sup + , log_evalue + , log_evalue_sup + , p_value , samples ) where @@ -255,6 +258,47 @@ log_wealth_sup :: State -> Double log_wealth_sup = st_sup_log_w {-# INLINE log_wealth_sup #-} +-- | The current log e-value. For this one-sided test the single +-- wealth process is itself the e-process (a fresh state already +-- sits at wealth @1@), so this coincides with 'log_wealth'; the +-- accessor exists so that e-values read uniformly across test +-- modules regardless of their internal hedging, e.g. when +-- convex-combining several e-processes. Not monotone; bounded +-- above by 'log_evalue_sup'. +-- +-- >>> log_evalue s0 +-- 0.0 +log_evalue :: State -> Double +log_evalue = st_log_w +{-# INLINE log_evalue #-} + +-- | The supremum-so-far of the log e-value; coincides with +-- 'log_wealth_sup' for this one-sided test. Monotone +-- nondecreasing, starting at @0@; 'decide' rejects exactly when +-- it crosses @log(1 \/ alpha)@. +-- +-- >>> log_evalue_sup s0 +-- 0.0 +log_evalue_sup :: State -> Double +log_evalue_sup = st_sup_log_w +{-# INLINE log_evalue_sup #-} + +-- | The anytime-valid p-value: the reciprocal of the largest +-- e-value attained so far, @min 1 (exp (negate (log_evalue_sup +-- s)))@. +-- +-- Monotone nonincreasing in the sample count, and valid under +-- optional stopping: 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 +p_value :: State -> Double +p_value s = min 1 (exp (negate (log_evalue_sup s))) +{-# INLINE p_value #-} + -- | The number of samples consumed so far. -- -- >>> samples s0 diff --git a/lib/Numeric/Eproc/Bernoulli/TwoSided.hs b/lib/Numeric/Eproc/Bernoulli/TwoSided.hs @@ -55,6 +55,9 @@ module Numeric.Eproc.Bernoulli.TwoSided ( -- * Inspection , log_wealth , log_wealth_sup + , log_evalue + , log_evalue_sup + , p_value , samples ) where @@ -142,6 +145,38 @@ log_wealth_sup :: State -> Double log_wealth_sup (State s) = Bounded.log_wealth_sup s {-# INLINE log_wealth_sup #-} +-- | 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 :: State -> Double +log_evalue (State s) = Bounded.log_evalue s +{-# INLINE log_evalue #-} + +-- | 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 +log_evalue_sup :: State -> Double +log_evalue_sup (State s) = Bounded.log_evalue_sup s +{-# INLINE log_evalue_sup #-} + +-- | 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 +p_value :: State -> Double +p_value (State s) = Bounded.p_value s +{-# INLINE p_value #-} + -- | The number of samples consumed so far. -- -- >>> samples s0 diff --git a/lib/Numeric/Eproc/Bounded.hs b/lib/Numeric/Eproc/Bounded.hs @@ -84,6 +84,9 @@ module Numeric.Eproc.Bounded ( -- * Inspection , log_wealth , log_wealth_sup + , log_evalue + , log_evalue_sup + , p_value , samples ) where @@ -311,6 +314,47 @@ log_wealth_sup :: State -> Double log_wealth_sup State{..} = st_sup_log_sum {-# INLINE log_wealth_sup #-} +-- | The current log e-value of the convex-hedge e-process: the log +-- of @(K^+_t + K^-_t) \/ 2@, i.e. 'log_wealth' minus @log 2@. +-- +-- Unlike 'log_wealth', this is normalized so that a fresh state +-- sits at @0@ (e-value @1@): it is directly comparable across +-- test modules regardless of their internal hedging, and is the +-- form to use when convex-combining several e-processes. Not +-- monotone; bounded above by 'log_evalue_sup'. +-- +-- >>> log_evalue s0 +-- 0.0 +log_evalue :: State -> Double +log_evalue s = log_wealth s - log2_dbl +{-# INLINE log_evalue #-} + +-- | 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 +log_evalue_sup :: State -> Double +log_evalue_sup s = log_wealth_sup s - log2_dbl +{-# INLINE log_evalue_sup #-} + +-- | The anytime-valid p-value: the reciprocal of the largest +-- e-value attained so far, @min 1 (exp (negate (log_evalue_sup +-- s)))@. +-- +-- Monotone nonincreasing in the sample count, and valid under +-- optional stopping: 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 +p_value :: State -> Double +p_value s = min 1 (exp (negate (log_evalue_sup s))) +{-# INLINE p_value #-} + -- | The number of samples consumed so far. -- -- >>> samples s0 diff --git a/lib/Numeric/Eproc/Paired.hs b/lib/Numeric/Eproc/Paired.hs @@ -64,6 +64,9 @@ module Numeric.Eproc.Paired ( -- * Inspection , log_wealth , log_wealth_sup + , log_evalue + , log_evalue_sup + , p_value , samples ) where @@ -165,6 +168,39 @@ log_wealth_sup :: State -> Double log_wealth_sup (State s) = Bounded.log_wealth_sup s {-# INLINE log_wealth_sup #-} +-- | The current log e-value of the underlying bounded-mean test on +-- the differences: '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 :: State -> Double +log_evalue (State s) = Bounded.log_evalue s +{-# INLINE log_evalue #-} + +-- | 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 +log_evalue_sup :: State -> Double +log_evalue_sup (State s) = Bounded.log_evalue_sup s +{-# INLINE log_evalue_sup #-} + +-- | 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 +p_value :: State -> Double +p_value (State s) = Bounded.p_value s +{-# INLINE p_value #-} + -- | The number of paired observations consumed so far. -- -- >>> samples s0