(* O(i) uncached
   O(1) cached *)
let log_binom_coeff n p i = 
  if p = 0. or p = 1. then
    if p = 0. then if i = 0 then 0. else Log_float.zero
    else if i = n then 0. else Log_float.zero
  else
    let fn =float n
    and fi = float i in
    let ret = fi *. log p +. (fn -. fi) *. log (1. -. p) in
    ret +. Choose.cached_log_choose n i

(* return log of tail probability mass 
   O(i + n^.5) uncached 
   O(n^.5) cached
*)
let rec log_binom_upper_bound n p i sum = 
  if i < 0 then sum
  else 
    let ret = Log_float.add (log_binom_coeff n p i) sum in
    if ret -. sum > Precision.too_tiny then log_binom_upper_bound n p (i-1) ret
    else ret

(* return log of tail probability mass 
   O(i + n^.5) uncached 
   O(n^.5) cached
*)	
let rec log_binom_lower_bound n p i sum  = 
  if i > n then sum
  else 
    let ret = Log_float.add (log_binom_coeff n p i) sum in
    if ret -. sum > Precision.too_tiny then log_binom_lower_bound n p (i+1) ret
    else ret
      
module Cache_Map = Map.Make(struct type t = int * float * int let compare = compare end)
let upper_bound_cache = ref Cache_Map.empty
let lower_bound_cache = ref Cache_Map.empty

let store_cache _ = 
  let oc = open_out_bin "/tmp/binomial_cache" in
  output_value oc !upper_bound_cache; 
  output_value oc !lower_bound_cache; 
  close_out oc

let restore_cache _ = 
  try
    let ic = open_in_bin "/tmp/binomial_cache" in
    upper_bound_cache := input_value ic;
    lower_bound_cache := input_value ic;
    close_in ic
  with Sys_error _ -> ()

(* return log of tail probability mass 
   O(1) cached
   O(n^.5) uncached binomial
   O(i + n^.5) uncached
*)	
let cached_log_binom_upper_bound n i p =
  try
    Cache_Map.find (n,p,i) !upper_bound_cache
  with 
    Not_found -> 
      let ret = log_binom_upper_bound n p i Log_float.zero in
      upper_bound_cache := Cache_Map.add (n,p,i) ret !upper_bound_cache;
      ret

(* return log of tail probability mass 
   O(1) cached
   O(n^.5) uncached binomial
   O(i + n^.5) uncached
*)	
let cached_log_binom_lower_bound n i p =
  try
    Cache_Map.find (n,p,i) !lower_bound_cache
  with 
    Not_found -> 
      let ret = log_binom_lower_bound n p i Log_float.zero in
      lower_bound_cache := Cache_Map.add (n,p,i) ret !lower_bound_cache;
      ret

(*
   O(1) cached
   O(n^.5) uncached binomial
   O(i + n^.5) uncached   
*)
let binom_lower size n i p =
  let cumulative = cached_log_binom_lower_bound n i p in
  let ret = min (cumulative +. size) 0. in
  ret

(*
   O(1) cached
   O(n^.5) uncached binomial
   O(i + n^.5) uncached
*)
let binom_upper size n i p =
  let cumulative = cached_log_binom_upper_bound n i p in
  let ret = min (cumulative +. size) 0. in
  ret
