(*  File: entropy_utils.ml

    AIFAD - Automated Induction of Functions over Algebraic Datatypes

    Author: Markus Mottl
    email:  markus.mottl@gmail.com
    WWW:    http://www.ocaml.info

    Copyright (C) 2002  Austrian Research Institute for Artificial Intelligence
    Copyright (C) 2003- Markus Mottl

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*)

(* $Id: entropy_utils.ml,v 1.14 2006/01/17 00:23:37 mottl Exp $ *)

let log_2 = log 2.0
let log2 n = log n /. log_2

let icont p = -. log2 p

let calc_probs_icont ps = Array.map icont ps

(* Frequency-based multinomial estimation *)

let calc_entropy histo n =
  if n = 0 then 0.0
  else
    let rec loop sum ix =
      if ix < 0 then sum
      else
        let freq = histo.(ix) in
        if freq = 0 then loop sum (ix - 1)
        else
          let ffreq = float freq in
          loop (sum +. ffreq *. log ffreq) (ix - 1) in
    let sum = loop 0.0 (Array.length histo - 1) in
    let f_n = float n in
    log2 f_n -. sum /. f_n /. log_2

let calc_prob histo cnstr n = float histo.(cnstr) /. float n

let calc_probs histo n =
  let f_n = float n in
  let cnv freq = float freq /. f_n in
  Array.map cnv histo

let calc_unique_prob _ _ = 1.0

let calc_unique_entropy n_cnstrs freq = -. log2 (calc_unique_prob n_cnstrs freq)


(* Ristad's Natural Law of Succession *)

let histo_coll_count_full cnt freq = if freq = 0 then cnt else cnt + 1
let histo_count_full histo = Array.fold_left histo_coll_count_full 0 histo

let laplace_prob n k ni = float (ni + 1) /. float (n + k)

let calc_prob histo cnstr n =
  let k = Array.length histo in
  let q = histo_count_full histo in
  let ni = histo.(cnstr) in
  if q = k then laplace_prob n k ni
  else
    let f_n = float n in
    let t = f_n *. f_n +. float (n + q + q) in
    if ni = 0 then float (q * (q + 1)) /. float (k - q) /. t
    else float (ni + 1) *. float (n + 1 - q) /. t

let calc_probs histo n =
  let k = Array.length histo in
  let q = histo_count_full histo in
  if q = k then Array.map (laplace_prob n k) histo
  else
    let f_n = float n in
    let tl = f_n *. f_n +. float (n + q + q) in
    let t = float (n + 1 - q) /. tl in
    let zero_prob = float (q * (q + 1)) /. float (k - q) /. tl in
    let cnv ni = if ni = 0 then zero_prob else float (ni + 1) *. t in
    Array.map cnv histo

let calc_entropy histo n =
  if n = 0 then 0.0
  else
    let k = Array.length histo in
    let q = histo_count_full histo in
    let q_n = q + n in
    if k = q then
      let rec loop acc ix =
        let ffreq_1 = float (histo.(ix) + 1) in
        let new_acc = acc +. ffreq_1 *. log2 ffreq_1 in
        if ix = 0 then new_acc
        else loop new_acc (ix - 1) in
      let n_k = float (n + k) in
      (float q_n *. log2 n_k -. loop 0.0 (k - 1)) /. n_k
    else
      let rec loop acc ix =
        let freq = histo.(ix) in
        if freq = 0 then
          if ix = 0 then acc
          else loop acc (ix - 1)
        else
          let ffreq_1 = float (freq + 1) in
          let new_acc = acc +. ffreq_1 *. log2 ffreq_1 in
          if ix = 0 then new_acc
          else loop new_acc (ix - 1) in
      let f_n = float n in
      let tl = f_n *. f_n +. float (q_n + q) in
      let qq1 = float (q * (q + 1)) in
      let ni0 = qq1 *. log2 (qq1 /. float (k - q) /. tl) in
      let tu = float (n + 1 - q) in
      -. (ni0 +. tu *. loop (float q_n *. log2 (tu /. tl)) (k - 1)) /. tl

let calc_unique_prob n_cnstrs n =
  if n_cnstrs = 1 then 1.0
  else
    let f_n = float n in
    let f_n2 = f_n *. f_n in
    let f_n2_n = f_n2 +. f_n in
    f_n2_n /. (f_n2_n +. 2.0)

let calc_unique_entropy n_cnstrs freq = -. log2 (calc_unique_prob n_cnstrs freq)
