packages feed

ppad-eproc 0.2.2 → 0.3.0

raw patch · 8 files changed

+126/−38 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Numeric.Eproc.Bernoulli: log_wealth_sup :: State -> Double
+ Numeric.Eproc.Bernoulli.TwoSided: log_wealth_sup :: State -> Double
+ Numeric.Eproc.Bounded: log_wealth_sup :: State -> Double
+ Numeric.Eproc.Paired: log_wealth_sup :: State -> Double

Files

CHANGELOG view
@@ -1,5 +1,14 @@ # Changelog +- 0.3.0 (2026-07-02)+  * Introduces a breaking API change: 'log_wealth' now returns the+    current log-wealth, whereas the supremum-thus-far statistic is+    exposed as 'log_wealth_sup'.++- 0.2.2 (2026-07-02)+  * Adds a Numeric.Eproc.Bernoulli.TwoSided module for a two-sided+    Bernoulli rate test.+ - 0.2.2 (2026-07-02)   * Adds a Numeric.Eproc.Bernoulli.TwoSided module for a two-sided     Bernoulli rate test.
lib/Numeric/Eproc/Bernoulli.hs view
@@ -72,6 +72,7 @@    -- * Inspection   , log_wealth+  , log_wealth_sup   , samples   ) where @@ -117,7 +118,7 @@ data State = State {     st_n         :: {-# UNPACK #-} !Int     -- ^ sample count   , st_log_w     :: {-# UNPACK #-} !Double  -- ^ running log-wealth-  , st_max_log_w :: {-# UNPACK #-} !Double  -- ^ sup log-wealth so far+  , st_sup_log_w :: {-# UNPACK #-} !Double  -- ^ sup log-wealth so far   , st_bet       :: !BetState               -- ^ bettor state   } @@ -169,7 +170,7 @@ initial Config{..} = State {     st_n         = 0   , st_log_w     = 0-  , st_max_log_w = 0+  , st_sup_log_w = 0   , st_bet       = init_bet cfg_bettor   } {-# INLINE initial #-}@@ -202,9 +203,9 @@       !z      = xd - cfg_p0       !lam    = bet_lambda cfg_bettor cfg_lam_max st_bet       !logw'  = st_log_w + log1p (lam * z)-      !maxw'  = max st_max_log_w logw'+      !supw'  = max st_sup_log_w logw'       !s'     = step_bet cfg_bettor cfg_lam_max st_bet z-  in  State (st_n + 1) logw' maxw' s'+  in  State (st_n + 1) logw' supw' s' {-# INLINE update #-}  -- | Compute the current 'Verdict' from the running 'State'.@@ -221,12 +222,26 @@ --   Continue decide :: Config -> State -> Verdict decide Config{..} State{..}-  | st_max_log_w >= cfg_log_thresh = Reject+  | st_sup_log_w >= cfg_log_thresh = Reject   | otherwise                      = Continue {-# INLINE decide #-}  -- inspection ----------------------------------------------------------------- +-- | The current running log-wealth @log W_n@ at the present sample+--   count.+--+--   Unlike 'log_wealth_sup' this is not monotone: adverse+--   observations decrease it. It is bounded above by+--   'log_wealth_sup', which is what 'decide' tests against the+--   rejection threshold.+--+--   >>> log_wealth s0+--   0.0+log_wealth :: State -> Double+log_wealth = st_log_w+{-# INLINE log_wealth #-}+ -- | The supremum-so-far log-wealth, across all sample counts up to --   the current one. --@@ -234,11 +249,11 @@ --   nondecreasing in the sample count, and 'decide' rejects exactly --   when it crosses @log(1 \/ alpha)@. -----   >>> log_wealth s0+--   >>> log_wealth_sup s0 --   0.0-log_wealth :: State -> Double-log_wealth = st_max_log_w-{-# INLINE log_wealth #-}+log_wealth_sup :: State -> Double+log_wealth_sup = st_sup_log_w+{-# INLINE log_wealth_sup #-}  -- | The number of samples consumed so far. --
lib/Numeric/Eproc/Bernoulli/TwoSided.hs view
@@ -54,6 +54,7 @@    -- * Inspection   , log_wealth+  , log_wealth_sup   , samples   ) where @@ -121,14 +122,25 @@  -- inspection ----------------------------------------------------------------- --- | The supremum-so-far of @log(K^+_t + K^-_t)@ from the underlying---   bounded-mean test. Starts at @log 2@.+-- | 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 :: State -> Double log_wealth (State s) = Bounded.log_wealth s {-# INLINE log_wealth #-}++-- | 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_wealth_sup :: State -> Double+log_wealth_sup (State s) = Bounded.log_wealth_sup s+{-# INLINE log_wealth_sup #-}  -- | The number of samples consumed so far. --
lib/Numeric/Eproc/Bounded.hs view
@@ -83,6 +83,7 @@    -- * Inspection   , log_wealth+  , log_wealth_sup   , samples   ) where @@ -126,7 +127,7 @@ -- --   The two log-wealth fields track the running log-wealth of the --   positive- and negative-direction e-processes separately; the---   /max log-sum/ field latches the supremum so far of+--   /sup log-sum/ field latches the supremum so far of --   @log(K^+_t + K^-_t)@, which is the test statistic the --   convex-hedge construction actually monitors. The per-direction --   bettor states carry whatever the chosen 'Bettor' needs (running@@ -135,7 +136,7 @@     st_n           :: {-# UNPACK #-} !Int     -- ^ sample count   , st_log_w_pos   :: {-# UNPACK #-} !Double  -- ^ log-wealth, pos   , st_log_w_neg   :: {-# UNPACK #-} !Double  -- ^ log-wealth, neg-  , st_max_log_sum :: {-# UNPACK #-} !Double  -- ^ sup log(K^+ + K^-)+  , st_sup_log_sum :: {-# UNPACK #-} !Double  -- ^ sup log(K^+ + K^-)   , st_bet_pos     :: !BetState               -- ^ bettor state, pos   , st_bet_neg     :: !BetState               -- ^ bettor state, neg   }@@ -198,7 +199,7 @@ -- | The initial 'State' for a fresh streaming test. -- --   Both per-direction log-wealths start at @0@ (i.e., @K = 1@);---   the max-log-sum starts at @log 2@ (since @K^+_0 + K^-_0 = 2@);+--   the sup-log-sum starts at @log 2@ (since @K^+_0 + K^-_0 = 2@); --   both bettors start in the per-strategy initial state --   appropriate for the 'Bettor' chosen in the 'Config'. --@@ -210,7 +211,7 @@         st_n           = 0       , st_log_w_pos   = 0       , st_log_w_neg   = 0-      , st_max_log_sum = log2_dbl+      , st_sup_log_sum = log2_dbl       , st_bet_pos     = s0       , st_bet_neg     = s0       }@@ -250,13 +251,13 @@       -- already sits at or below the running max: no update can       -- move it. Under H_0 (calibration) this is the common case.       !cheap_ub = max logw_p logw_n + log2_dbl-      !max_sum-        | cheap_ub <= st_max_log_sum = st_max_log_sum+      !sup_sum+        | cheap_ub <= st_sup_log_sum = st_sup_log_sum         | otherwise                  =-            max st_max_log_sum (log_sum_exp logw_p logw_n)+            max st_sup_log_sum (log_sum_exp logw_p logw_n)       !sp      = step_bet cfg_bettor cfg_lam_max_pos st_bet_pos z       !sn      = step_bet cfg_bettor cfg_lam_max_neg st_bet_neg (negate z)-  in  State (st_n + 1) logw_p logw_n max_sum sp sn+  in  State (st_n + 1) logw_p logw_n sup_sum sp sn {-# INLINE update #-}  -- | Compute the current 'Verdict' from the running 'State'.@@ -274,12 +275,28 @@ --   Continue decide :: Config -> State -> Verdict decide Config{..} State{..}-  | st_max_log_sum >= cfg_log_thresh = Reject+  | st_sup_log_sum >= cfg_log_thresh = Reject   | otherwise                        = Continue {-# INLINE decide #-}  -- inspection ----------------------------------------------------------------- +-- | The current @log(K^+_t + K^-_t)@ -- the running log-wealth of+--   the convex-hedge combination at the present sample count.+--+--   Unlike 'log_wealth_sup' this is not monotone: adverse+--   observations decrease it. It is bounded above by+--   'log_wealth_sup', which is what 'decide' tests against the+--   rejection threshold.+--+--   Starts at @log 2@ (since @K^+_0 + K^-_0 = 2@).+--+--   >>> log_wealth s0+--   0.6931471805599453+log_wealth :: State -> Double+log_wealth State{..} = log_sum_exp st_log_w_pos st_log_w_neg+{-# INLINE log_wealth #-}+ -- | The supremum-so-far of @log(K^+_t + K^-_t)@, taken across all --   sample counts up to the current one. This is the test statistic --   the convex-hedge construction actually monitors: it is monotone@@ -288,11 +305,11 @@ -- --   Starts at @log 2@ (since @K^+_0 + K^-_0 = 2@). -----   >>> log_wealth s0+--   >>> log_wealth_sup s0 --   0.6931471805599453-log_wealth :: State -> Double-log_wealth State{..} = st_max_log_sum-{-# INLINE log_wealth #-}+log_wealth_sup :: State -> Double+log_wealth_sup State{..} = st_sup_log_sum+{-# INLINE log_wealth_sup #-}  -- | The number of samples consumed so far. --
lib/Numeric/Eproc/Common.hs view
@@ -132,7 +132,7 @@ {-# INLINE log_sum_exp #-}  -- | @log 2@ as a shared constant. Used both as the initial value of---   the two-sided running max-log-sum (since @K^+_0 + K^-_0 = 2@) and+--   the two-sided running sup-log-sum (since @K^+_0 + K^-_0 = 2@) and --   as the tight upper-bound slack in the fast-path skip inside --   'Numeric.Eproc.Bounded.update' / --   'Numeric.Eproc.Bernoulli.TwoSided.update'.
lib/Numeric/Eproc/Paired.hs view
@@ -63,6 +63,7 @@    -- * Inspection   , log_wealth+  , log_wealth_sup   , samples   ) where @@ -143,14 +144,26 @@  -- inspection ----------------------------------------------------------------- --- | The supremum-so-far log-wealth of the underlying bounded-mean---   test on the differences.+-- | The current @log(K^+_t + K^-_t)@ of the underlying bounded-mean+--   test on the differences. Not monotone; bounded above by+--   'log_wealth_sup'. Starts at @log 2@. -- --   >>> log_wealth s0---   0.0+--   0.6931471805599453 log_wealth :: State -> Double log_wealth (State s) = Bounded.log_wealth s {-# INLINE log_wealth #-}++-- | The supremum-so-far of @log(K^+_t + K^-_t)@ from the underlying+--   bounded-mean test on the differences. Monotone nondecreasing;+--   'decide' rejects exactly when it crosses @log(2 \/ alpha)@.+--   Starts at @log 2@.+--+--   >>> log_wealth_sup s0+--   0.6931471805599453+log_wealth_sup :: State -> Double+log_wealth_sup (State s) = Bounded.log_wealth_sup s+{-# INLINE log_wealth_sup #-}  -- | The number of paired observations consumed so far. --
ppad-eproc.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               ppad-eproc-version:            0.2.2+version:            0.3.0 synopsis:           Anytime-valid sequential testing via e-processes. license:            MIT license-file:       LICENSE
test/Main.hs view
@@ -517,14 +517,15 @@       QC.forAll (QC.listOf unit_double) $ \xs ->         let cfg = ok (Bounded.config 0.5 0.0 1.0 1.0e-3 b)             st  = foldl' (Bounded.update cfg) (Bounded.initial cfg) xs-        in  finite (Bounded.log_wealth st)+        in  finite (Bounded.log_wealth st) &&+            finite (Bounded.log_wealth_sup st)    , QC.testProperty "Bernoulli: log_wealth finite after any admissible stream" $       QC.forAll arb_bettor $ \b ->       QC.forAll QC.arbitrary $ \xs ->         let cfg = ok (Bern.config 0.05 1.0e-3 b)             st  = foldl' (Bern.update cfg) (Bern.initial cfg) (xs :: [Bool])-        in  finite (Bern.log_wealth st)+        in  finite (Bern.log_wealth st) && finite (Bern.log_wealth_sup st)    , QC.testProperty "Bounded: Fixed with arbitrary lambda is safe" $       QC.forAll (QC.choose (-1000, 1000)) $ \lam ->@@ -540,22 +541,36 @@             st  = foldl' (Bern.update cfg) (Bern.initial cfg) (xs :: [Bool])         in  finite (Bern.log_wealth st) -  , QC.testProperty "Bounded: log_wealth is monotone nondecreasing" $+  , QC.testProperty "Bounded: log_wealth_sup is monotone nondecreasing" $       QC.forAll arb_bettor $ \b ->       QC.forAll (QC.listOf unit_double) $ \xs ->         let cfg  = ok (Bounded.config 0.5 0.0 1.0 1.0e-3 b)             sts  = scanl (Bounded.update cfg) (Bounded.initial cfg) xs-            lws  = map Bounded.log_wealth sts+            lws  = map Bounded.log_wealth_sup sts         in  and (zipWith (<=) lws (drop 1 lws)) -  , QC.testProperty "Bernoulli: log_wealth is monotone nondecreasing" $+  , QC.testProperty "Bernoulli: log_wealth_sup is monotone nondecreasing" $       QC.forAll arb_bettor $ \b ->       QC.forAll QC.arbitrary $ \xs ->         let cfg  = ok (Bern.config 0.05 1.0e-3 b)             sts  = scanl (Bern.update cfg) (Bern.initial cfg) (xs :: [Bool])-            lws  = map Bern.log_wealth sts+            lws  = map Bern.log_wealth_sup sts         in  and (zipWith (<=) lws (drop 1 lws)) +  , QC.testProperty "Bounded: log_wealth bounded above by log_wealth_sup" $+      QC.forAll arb_bettor $ \b ->+      QC.forAll (QC.listOf unit_double) $ \xs ->+        let cfg  = ok (Bounded.config 0.5 0.0 1.0 1.0e-3 b)+            sts  = scanl (Bounded.update cfg) (Bounded.initial cfg) xs+        in  all (\s -> Bounded.log_wealth s <= Bounded.log_wealth_sup s) sts++  , QC.testProperty "Bernoulli: log_wealth bounded above by log_wealth_sup" $+      QC.forAll arb_bettor $ \b ->+      QC.forAll QC.arbitrary $ \xs ->+        let cfg  = ok (Bern.config 0.05 1.0e-3 b)+            sts  = scanl (Bern.update cfg) (Bern.initial cfg) (xs :: [Bool])+        in  all (\s -> Bern.log_wealth s <= Bern.log_wealth_sup s) sts+   , QC.testProperty "Bounded: rejection is latched" $       QC.forAll arb_bettor $ \b ->       QC.forAll (QC.listOf unit_double) $ \xs ->@@ -577,7 +592,7 @@       QC.forAll QC.arbitrary $ \xs ->         let cfg = ok (BernTS.config 0.5 1.0e-3 b)             st  = foldl' (BernTS.update cfg) (BernTS.initial cfg) (xs :: [Bool])-        in  finite (BernTS.log_wealth st)+        in  finite (BernTS.log_wealth st) && finite (BernTS.log_wealth_sup st)    , QC.testProperty "BernTS: Fixed with arbitrary lambda is safe" $       QC.forAll (QC.choose (-1000, 1000)) $ \lam ->@@ -586,13 +601,20 @@             st  = foldl' (BernTS.update cfg) (BernTS.initial cfg) (xs :: [Bool])         in  finite (BernTS.log_wealth st) -  , QC.testProperty "BernTS: log_wealth is monotone nondecreasing" $+  , QC.testProperty "BernTS: log_wealth_sup is monotone nondecreasing" $       QC.forAll arb_bettor $ \b ->       QC.forAll QC.arbitrary $ \xs ->         let cfg  = ok (BernTS.config 0.5 1.0e-3 b)             sts  = scanl (BernTS.update cfg) (BernTS.initial cfg) (xs :: [Bool])-            lws  = map BernTS.log_wealth sts+            lws  = map BernTS.log_wealth_sup sts         in  and (zipWith (<=) lws (drop 1 lws))++  , QC.testProperty "BernTS: log_wealth bounded above by log_wealth_sup" $+      QC.forAll arb_bettor $ \b ->+      QC.forAll QC.arbitrary $ \xs ->+        let cfg  = ok (BernTS.config 0.5 1.0e-3 b)+            sts  = scanl (BernTS.update cfg) (BernTS.initial cfg) (xs :: [Bool])+        in  all (\s -> BernTS.log_wealth s <= BernTS.log_wealth_sup s) sts    , QC.testProperty "BernTS: rejection is latched" $       QC.forAll arb_bettor $ \b ->