diff --git a/src/core/CCRandom.ml b/src/core/CCRandom.ml index 709a3e2e..a5a37f5c 100644 --- a/src/core/CCRandom.ml +++ b/src/core/CCRandom.ml @@ -192,3 +192,31 @@ let (<*>) f g st = f st (g st) let __default_state = Random.State.make_self_init () let run ?(st=__default_state) g = g st + +let uniformity_test ?(size_hint=10) k rng st = + let histogram = Hashtbl.create size_hint in + let add x = let n = try Hashtbl.find histogram x with Not_found -> 0 in + Hashtbl.replace histogram x (n + 1) in + let () = + for _i = 0 to ( k - 1 ) do + add @@ rng st + done in + let cardinal = float_of_int @@ Hashtbl.length histogram in + let kf = float_of_int k in + (* average number of points assuming an uniform distribution *) + let average = kf /. cardinal in + (* The number of points is a sum of random variables with binomial distribution *) + let p = 1. /. cardinal in + (* The variance of a binomial distribution with average p is *) + let variance = p *. (1. -. p ) in + (* Central limit theorem: a confidence interval of 4σ provides a false positive rate + of 0.00634% *) + let confidence = 4. in + let std = confidence *. ( sqrt @@ kf *. variance ) in + let predicate _key n acc = + acc && abs_float (average -. float_of_int n) < std in + Hashtbl.fold predicate histogram true + +(*$T split_list + run ( uniformity_test 50_000 (split_list 10 ~len:3) ) +*) diff --git a/src/core/CCRandom.mli b/src/core/CCRandom.mli index 2c1ebcc7..be3dcc0a 100644 --- a/src/core/CCRandom.mli +++ b/src/core/CCRandom.mli @@ -153,3 +153,11 @@ val (<*>) : ('a -> 'b) t -> 'a t -> 'b t val run : ?st:state -> 'a t -> 'a (** Using a random state (possibly the one in argument) run a generator *) + +(** {6 Random generator testing } *) + +val uniformity_test : ?size_hint:int -> int -> 'a t -> bool t +(** [uniformity_test k rng] tests the uniformity of the random generator [rng] using + [k] samples. + @since 0.15 +*)