(*  File: algdt_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: algdt_utils.ml,v 1.48 2006/01/17 00:23:37 mottl Exp $ *)

open Algdt_types
open Utils

(* Miscellaneous general definitions *)

let init_tp = 0
let init_prod_el = TpVal init_tp
let init_prod_els = [| init_prod_el |]
let dummy_init_tps = [| init_tp |]

let dummy_fdsum = FDAtom 0
let dummy_fdsums = [| dummy_fdsum |]
let dummy_tp = 0

let dummy_var =
  {
    samples = [||];
    tp = dummy_tp;
    histo = [||];
  }

let empty_fspec = [|[|[||]|]|]

let get_n_fdsums ispec fspec =
  match ispec.(init_tp) with
  | Prod _ -> Array.length fspec.(init_tp).(0)
  | _ -> 1

let get_init_tps ispec fspec =
  match ispec.(init_tp) with
  | Prod _ -> fspec.(init_tp).(0)
  | _ -> dummy_init_tps

(* XXX implementation dependent! *)
(* let fdsum_cnstr = function FDAtom cnstr | FDStrct (cnstr, _) -> cnstr *)
external fdsum_cnstr : fdsum -> cnstr = "%field0"

let histo_is_redundant histo n_samples =
  let rec loop cnstr =
    let freq = histo.(cnstr) in
    freq = n_samples || freq = 0 && cnstr > 0 && loop (cnstr - 1) in
  loop (Array.length histo - 1)

let many_fdsums_of_vars vars =
  let var_samples = Array.map (fun var -> var.samples) vars in
  let n_vars = Array.length var_samples in
  if n_vars = 0 then [||]
  else
    let var_samples0 = var_samples.(0) in
    let n_vars_1 = n_vars - 1 in
    let acti sample_ix fdsum =
      let fdsums = Array.make n_vars fdsum in
      for var_ix = 1 to n_vars_1 do
        fdsums.(var_ix) <- var_samples.(var_ix).(sample_ix)
      done;
      fdsums in
    Array.mapi acti var_samples0


(* Conversion: undecorated -> decorated *)

let make_deco_dsum_cnv fspec ispec =
  let rec cnv_fdsum tp = function
    | FDAtom cnstr -> DAtom (tp, cnstr)
    | FDStrct (cnstr, fdsums) -> cnv_fdstrct tp cnstr fdsums
  and cnv_fdstrct tp cnstr fdsums =
    let dprod_els =
      let len = Array.length fdsums in
      if len = 1 then
        let sub_tp = fspec.(tp).(cnstr).(0) in
        match fdsums.(0) with
        | FDAtom sub_cnstr -> TpVal (DAtom (sub_tp, sub_cnstr))
        | FDStrct (sub_cnstr, sub_fdsums) ->
            TpVal (cnv_fdstrct sub_tp sub_cnstr sub_fdsums)
      else
        match ispec.(tp) with
        | Prod _ -> failwith "cnv_fdstrct: expected Sums"
        | Sums sums ->
            match sums.(cnstr) with
            | IAtom -> failwith "cnv_fdstrct: expected IStrct"
            | IStrct (ProdEls prod_els) ->
                ProdEls (fst (cnv_prod_els prod_els 0 fdsums))
            | IStrct (TpVal sub_tp) ->
                match ispec.(sub_tp) with
                | Prod prod_el -> fst (cnv_prod_el_loop 0 fdsums prod_el)
                | Sums _ -> failwith "cnv_fdstrct: expected Prod" in
    DStrct ((tp, cnstr), dprod_els)
  and cnv_prod_els prod_els ix fdsums =
    let dprod_el, new_ix = cnv_prod_el_loop ix fdsums prod_els.(0) in
    let dprod_els = Array.make (Array.length prod_els) dprod_el in
    let colli i ix prod_el =
      let dprod_el, new_ix = cnv_prod_el_loop ix fdsums prod_el in
      dprod_els.(i) <- dprod_el;
      new_ix in
    dprod_els, array_fold_left1i colli new_ix prod_els
  and cnv_prod_el_loop ix fdsums = function
    | ProdEls prod_els ->
        let dprod_els, new_ix = cnv_prod_els prod_els ix fdsums in
        ProdEls dprod_els, new_ix
    | TpVal tp ->
        match ispec.(tp) with
        | Prod sub_prod_el -> cnv_prod_el_loop ix fdsums sub_prod_el
        | Sums sums -> TpVal (cnv_fdsum tp fdsums.(ix)), ix + 1 in
  cnv_fdsum, fun prod_els fdsums -> fst (cnv_prod_els prod_els 0 fdsums)


(* Calculation of histograms and contingency tables *)

let make_histo var = Array.make (Array.length var.histo) 0

let calc_cntg_tbls { histo = dhisto; samples = dsamples } cvars =
  let make_cntg_tbl _ = Array.map make_histo cvars in
  let cntg_tbls = Array.init (Array.length dhisto) make_cntg_tbl in
  let fill_cntg_tbls sample_ix dsample =
    let dcnstr = fdsum_cnstr dsample in
    let cntg_tbl = cntg_tbls.(dcnstr) in
    let cnt_var ix var =
      let ccnstr = fdsum_cnstr var.samples.(sample_ix) in
      let ccnstr_cnts = cntg_tbl.(ix) in
      ccnstr_cnts.(ccnstr) <- ccnstr_cnts.(ccnstr) + 1 in
    Array.iteri cnt_var cvars in
  Array.iteri fill_cntg_tbls dsamples;
  cntg_tbls

let sample_histo fspec tp samples =
  let histo = Array.make (Array.length fspec.(tp)) 0 in
  let act sample =
    let cnstr = fdsum_cnstr sample in
    histo.(cnstr) <- histo.(cnstr) + 1 in
  Array.iter act samples;
  histo

let make_var fspec tp samples =
  { samples = samples; tp = tp; histo = sample_histo fspec tp samples }

let make_vars fspec tps many_samples =
  Array.mapi (fun i -> make_var fspec tps.(i)) many_samples


(* Split variables on some given variable without adding subvariables. *)
let split_vars { histo = histo; samples = samples } fspec vars =
  let cnv_histo freq =
    let cnv_vars { tp = tp } =
      {
        samples = Array.make freq dummy_fdsum;
        tp = tp;
        histo = Array.make (Array.length fspec.(tp)) 0;
      } in
    Array.map cnv_vars vars in
  let many_vars = Array.map cnv_histo histo in
  let dst_ixs = Array.make (Array.length histo) 0 in
  let n_vars_1 = Array.length vars - 1 in
  let acti_samples src_ix src_sample =
    let src_cnstr = fdsum_cnstr src_sample in
    let dst_ix = dst_ixs.(src_cnstr) in
    dst_ixs.(src_cnstr) <- dst_ix + 1;
    let dst_vars = many_vars.(src_cnstr) in
    for var_ix = 0 to n_vars_1 do
      let dst_var = dst_vars.(var_ix) in
      let dst_sample = vars.(var_ix).samples.(src_ix) in
      dst_var.samples.(dst_ix) <- dst_sample;
      let dst_histo = dst_var.histo in
      let dst_cnstr = fdsum_cnstr dst_sample in
      dst_histo.(dst_cnstr) <- dst_histo.(dst_cnstr) + 1
    done in
  Array.iteri acti_samples samples;
  many_vars


(* Split variables on some variable and add possible subvariables *)
(* (left, X (x, y), right) -> (left, x, y, right) *)
(* Ignore branches with less than or equal [min_freq] samples *)

let split_with_sub_vars min_freq fspec vars var_ix =
  let n_vars_1 = Array.length vars - 1 in
  let { samples = samples; tp = tp; histo = histo } = vars.(var_ix) in
  let var_ix_1 = var_ix - 1 in
  let var_ix1 = var_ix + 1 in
  let subs_tps = fspec.(tp) in

  let n_splits = Array.length histo in
  let split_vars = Array.make n_splits [||] in

  for split_cnstr = 0 to n_splits - 1 do
    let freq = histo.(split_cnstr) in
    let sub_tps = subs_tps.(split_cnstr) in
    let n_subs = Array.length sub_tps in
    let n_new_vars = n_vars_1 + n_subs in
    let new_vars = Array.make n_new_vars dummy_var in
    for i = 0 to var_ix_1 do
      let cur_var = vars.(i) in
      new_vars.(i) <-
        {
          samples = Array.make freq dummy_fdsum;
          tp = cur_var.tp;
          histo = Array.make (Array.length cur_var.histo) 0;
        }
    done;
    let n_subs_1 = n_subs - 1 in
    for i = 0 to n_subs_1 do
      let tp = sub_tps.(i) in
      new_vars.(var_ix + i) <-
        {
          samples = Array.make freq dummy_fdsum;
          tp = tp;
          histo = Array.make (Array.length fspec.(tp)) 0;
        }
    done;
    for i = var_ix1 to n_vars_1 do
      let cur_var = vars.(i) in
      new_vars.(i + n_subs_1) <-
        {
          samples = Array.make freq dummy_fdsum;
          tp = cur_var.tp;
          histo = Array.make (Array.length cur_var.histo) 0;
        }
    done;
    split_vars.(split_cnstr) <- new_vars
  done;

  let split_ixs = Array.make n_splits 0 in

  for sample_ix = 0 to Array.length samples - 1 do
    match samples.(sample_ix) with
    | FDAtom split_cnstr when histo.(split_cnstr) > min_freq ->
        let new_sample_ix = split_ixs.(split_cnstr) in
        split_ixs.(split_cnstr) <- new_sample_ix + 1;
        let new_vars = split_vars.(split_cnstr) in
        for i = 0 to var_ix_1 do
          let { histo = new_histo } as new_var = new_vars.(i) in
          let sample = vars.(i).samples.(sample_ix) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done;
        for i = var_ix1 to n_vars_1 do
          let { histo = new_histo } as new_var = new_vars.(i - 1) in
          let sample = vars.(i).samples.(sample_ix) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done
    | FDStrct (split_cnstr, subs) when histo.(split_cnstr) > min_freq ->
        let new_sample_ix = split_ixs.(split_cnstr) in
        split_ixs.(split_cnstr) <- new_sample_ix + 1;
        let new_vars = split_vars.(split_cnstr) in
        let n_subs = Array.length subs in
        for i = 0 to var_ix_1 do
          let { histo = new_histo } as new_var = new_vars.(i) in
          let sample = vars.(i).samples.(sample_ix) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done;
        let n_subs_1 = n_subs - 1 in
        for i = 0 to n_subs_1 do
          let { histo = new_histo } as new_var = new_vars.(var_ix + i) in
          let sample = subs.(i) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done;
        for i = var_ix1 to n_vars_1 do
          let { histo = new_histo } as new_var = new_vars.(i + n_subs_1) in
          let sample = vars.(i).samples.(sample_ix) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done
    | _ -> ()
  done;
  split_vars


(* Split variables on some variable and a constructor into two sets and
   add possible subvariables to the constructor case *)
(* (left, X (x, y), right) -> (left, x, y, right) *)

let split_cnstr_with_sub_vars fspec vars var_ix cnstr =
  let n_vars_1 = Array.length vars - 1 in
  let { samples = samples; tp = tp; histo = histo } = vars.(var_ix) in
  let n_samples = Array.length samples in
  let var_ix1 = var_ix + 1 in
  let var_ix_1 = var_ix - 1 in

  let freq = histo.(cnstr) in
  let other_freq = n_samples - freq in
  let sub_tps = fspec.(tp).(cnstr) in
  let n_subs = Array.length sub_tps in
  let n_new_vars = n_vars_1 + n_subs in
  let new_vars = Array.make n_new_vars dummy_var in
  let other_vars = Array.make n_new_vars dummy_var in

  for i = 0 to var_ix_1 do
    let { tp = cur_tp; histo = cur_histo } = vars.(i) in
    let n_cur_histo = Array.length cur_histo in
    new_vars.(i) <-
      {
        samples = Array.make freq dummy_fdsum;
        tp = cur_tp;
        histo = Array.make n_cur_histo 0;
      };
    other_vars.(i) <-
      {
        samples = Array.make other_freq dummy_fdsum;
        tp = cur_tp;
        histo = Array.make n_cur_histo 0;
      }
  done;
  let n_subs_1 = n_subs - 1 in
  for i = var_ix1 to n_vars_1 do
    let { tp = cur_tp; histo = cur_histo } = vars.(i) in
    let n_cur_histo = Array.length cur_histo in
    new_vars.(i + n_subs_1) <-
      {
        samples = Array.make freq dummy_fdsum;
        tp = cur_tp;
        histo = Array.make n_cur_histo 0;
      };
    other_vars.(i - 1) <-
      {
        samples = Array.make other_freq dummy_fdsum;
        tp = cur_tp;
        histo = Array.make n_cur_histo 0;
      }
  done;

  let new_sample_ix_ref = ref 0 in
  let other_sample_ix_ref = ref 0 in

  if n_subs > 0 then (
    for i = 0 to n_subs_1 do
      let tp = sub_tps.(i) in
      new_vars.(var_ix + i) <-
        {
          samples = Array.make freq dummy_fdsum;
          tp = tp;
          histo = Array.make (Array.length fspec.(tp)) 0;
        }
    done;
    for sample_ix = 0 to n_samples - 1 do
      match samples.(sample_ix) with
      | FDStrct (split_cnstr, subs) when split_cnstr = cnstr ->
          let new_sample_ix = !new_sample_ix_ref in
          new_sample_ix_ref := new_sample_ix + 1;
          for i = 0 to var_ix_1 do
            let { histo = new_histo } as new_var = new_vars.(i) in
            let sample = vars.(i).samples.(sample_ix) in
            new_var.samples.(new_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            new_histo.(cnstr) <- new_histo.(cnstr) + 1
          done;
          for i = 0 to n_subs_1 do
            let { histo = new_histo } as new_var = new_vars.(var_ix + i) in
            let sample = subs.(i) in
            new_var.samples.(new_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            new_histo.(cnstr) <- new_histo.(cnstr) + 1
          done;
          for i = var_ix1 to n_vars_1 do
            let { histo = new_histo } as new_var = new_vars.(i + n_subs_1) in
            let sample = vars.(i).samples.(sample_ix) in
            new_var.samples.(new_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            new_histo.(cnstr) <- new_histo.(cnstr) + 1
          done
      | _ ->
          let other_sample_ix = !other_sample_ix_ref in
          other_sample_ix_ref := other_sample_ix + 1;
          for i = 0 to var_ix_1 do
            let { histo = other_histo } as other_var = other_vars.(i) in
            let sample = vars.(i).samples.(sample_ix) in
            other_var.samples.(other_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            other_histo.(cnstr) <- other_histo.(cnstr) + 1
          done;
          for i = var_ix1 to n_vars_1 do
            let { histo = other_histo } as other_var = other_vars.(i - 1) in
            let sample = vars.(i).samples.(sample_ix) in
            other_var.samples.(other_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            other_histo.(cnstr) <- other_histo.(cnstr) + 1
          done
    done)
  else
    for sample_ix = 0 to n_samples - 1 do
      match samples.(sample_ix) with
      | FDAtom split_cnstr when split_cnstr = cnstr ->
          let new_sample_ix = !new_sample_ix_ref in
          new_sample_ix_ref := new_sample_ix + 1;
          for i = 0 to var_ix_1 do
            let { histo = new_histo } as new_var = new_vars.(i) in
            let sample = vars.(i).samples.(sample_ix) in
            new_var.samples.(new_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            new_histo.(cnstr) <- new_histo.(cnstr) + 1
          done;
          for i = var_ix1 to n_vars_1 do
            let { histo = new_histo } as new_var = new_vars.(i - 1) in
            let sample = vars.(i).samples.(sample_ix) in
            new_var.samples.(new_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            new_histo.(cnstr) <- new_histo.(cnstr) + 1
          done
      | _ ->
          let other_sample_ix = !other_sample_ix_ref in
          other_sample_ix_ref := other_sample_ix + 1;
          for i = 0 to var_ix_1 do
            let { histo = other_histo } as other_var = other_vars.(i) in
            let sample = vars.(i).samples.(sample_ix) in
            other_var.samples.(other_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            other_histo.(cnstr) <- other_histo.(cnstr) + 1
          done;
          for i = var_ix1 to n_vars_1 do
            let { histo = other_histo } as other_var = other_vars.(i - 1) in
            let sample = vars.(i).samples.(sample_ix) in
            other_var.samples.(other_sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            other_histo.(cnstr) <- other_histo.(cnstr) + 1
          done
    done;

  new_vars, other_vars


(* Shave variable with a given constructor and add possible subvariables *)
(* (left, X (x, y), right) -> (left, x, y, right) *)

let shave_with_sub_vars fspec vars var_ix cnstr =
  let n_vars_1 = Array.length vars - 1 in
  let { samples = samples; tp = tp; histo = histo } = vars.(var_ix) in
  let freq = histo.(cnstr) in
  let sub_tps = fspec.(tp).(cnstr) in
  let n_subs = Array.length sub_tps in
  let n_new_vars = n_vars_1 + n_subs in
  let new_vars = Array.make n_new_vars dummy_var in
  Array.blit vars 0 new_vars 0 var_ix;
  Array.blit vars (var_ix + 1) new_vars (var_ix + n_subs) (n_vars_1 - var_ix);
  if n_subs > 0 then (
    let n_subs_1 = n_subs - 1 in
    for i = 0 to n_subs_1 do
      let tp = sub_tps.(i) in
      new_vars.(var_ix + i) <-
        {
          samples = Array.make freq dummy_fdsum;
          tp = tp;
          histo = Array.make (Array.length fspec.(tp)) 0;
        }
    done;
    for sample_ix = 0 to Array.length samples - 1 do
      match samples.(sample_ix) with
      | FDStrct (split_cnstr, subs) when split_cnstr = cnstr ->
          for i = 0 to n_subs_1 do
            let { histo = new_histo } as new_var = new_vars.(var_ix + i) in
            let sample = subs.(i) in
            new_var.samples.(sample_ix) <- sample;
            let cnstr = fdsum_cnstr sample in
            new_histo.(cnstr) <- new_histo.(cnstr) + 1
          done
      | _ -> failwith "shave_with_sub_vars: constructor not unique"
    done);
  new_vars


(* Shave variable without adding subvariables *)

let atomic_shave_vars vars var_ix =
  let n_vars_1 = Array.length vars - 1 in
  let new_vars = Array.make n_vars_1 dummy_var in
  Array.blit vars 0 new_vars 0 var_ix;
  Array.blit vars (var_ix + 1) new_vars var_ix (n_vars_1 - var_ix);
  new_vars


(* Split variable on maybe constructors, adding subvariables as required *)

let maybe_split_with_sub_vars fspec vars var_ix maybes =
  let n_vars_1 = Array.length vars - 1 in
  let { samples = samples; tp = tp; histo = histo } = vars.(var_ix) in
  let var_ix_1 = var_ix - 1 in
  let var_ix1 = var_ix + 1 in
  let subs_tps = fspec.(tp) in

  let n_splits = Array.length histo in
  let split_vars = Array.make n_splits [||] in
  let freq_none_ref = ref 0 in

  for split_cnstr = 0 to n_splits - 1 do
    let freq = histo.(split_cnstr) in
    match maybes.(split_cnstr) with
    | None -> freq_none_ref := !freq_none_ref + freq
    | Some _ ->
        let sub_tps = subs_tps.(split_cnstr) in
        let n_subs = Array.length sub_tps in
        let n_new_vars = n_vars_1 + n_subs in
        let new_vars = Array.make n_new_vars dummy_var in
        for i = 0 to var_ix_1 do
          let cur_var = vars.(i) in
          new_vars.(i) <-
            {
              samples = Array.make freq dummy_fdsum;
              tp = cur_var.tp;
              histo = Array.make (Array.length cur_var.histo) 0;
            }
        done;
        let n_subs_1 = n_subs - 1 in
        for i = 0 to n_subs_1 do
          let tp = sub_tps.(i) in
          new_vars.(var_ix + i) <-
            {
              samples = Array.make freq dummy_fdsum;
              tp = tp;
              histo = Array.make (Array.length fspec.(tp)) 0;
            }
        done;
        for i = var_ix1 to n_vars_1 do
          let cur_var = vars.(i) in
          new_vars.(i + n_subs_1) <-
            {
              samples = Array.make freq dummy_fdsum;
              tp = cur_var.tp;
              histo = Array.make (Array.length cur_var.histo) 0;
            }
        done;
        split_vars.(split_cnstr) <- new_vars
  done;

  let freq_none = !freq_none_ref in
  let none_vars = Array.make n_vars_1 dummy_var in
  for i = 0 to var_ix_1 do
    let cur_var = vars.(i) in
    none_vars.(i) <-
      {
        samples = Array.make freq_none dummy_fdsum;
        tp = cur_var.tp;
        histo = Array.make (Array.length cur_var.histo) 0;
      }
  done;
  for i = var_ix1 to n_vars_1 do
    let cur_var = vars.(i) in
    none_vars.(i - 1) <-
      {
        samples = Array.make freq_none dummy_fdsum;
        tp = cur_var.tp;
        histo = Array.make (Array.length cur_var.histo) 0;
      }
  done;

  let split_ixs = Array.make n_splits 0 in
  let none_ix_ref = ref 0 in

  for sample_ix = 0 to Array.length samples - 1 do
    match samples.(sample_ix) with
    | FDAtom split_cnstr
    | FDStrct (split_cnstr, _) when maybes.(split_cnstr) = None ->
        let none_sample_ix = !none_ix_ref in
        none_ix_ref := none_sample_ix + 1;
        for i = 0 to var_ix_1 do
          let { histo = none_histo } as none_var = none_vars.(i) in
          let sample = vars.(i).samples.(sample_ix) in
          none_var.samples.(none_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          none_histo.(cnstr) <- none_histo.(cnstr) + 1
        done;
        for i = var_ix1 to n_vars_1 do
          let { histo = none_histo } as none_var = none_vars.(i - 1) in
          let sample = vars.(i).samples.(sample_ix) in
          none_var.samples.(none_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          none_histo.(cnstr) <- none_histo.(cnstr) + 1
        done
    | FDAtom split_cnstr ->
        let new_sample_ix = split_ixs.(split_cnstr) in
        split_ixs.(split_cnstr) <- new_sample_ix + 1;
        let new_vars = split_vars.(split_cnstr) in
        for i = 0 to var_ix_1 do
          let { histo = new_histo } as new_var = new_vars.(i) in
          let sample = vars.(i).samples.(sample_ix) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done;
        for i = var_ix1 to n_vars_1 do
          let { histo = new_histo } as new_var = new_vars.(i - 1) in
          let sample = vars.(i).samples.(sample_ix) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done
    | FDStrct (split_cnstr, subs) ->
        let new_sample_ix = split_ixs.(split_cnstr) in
        split_ixs.(split_cnstr) <- new_sample_ix + 1;
        let new_vars = split_vars.(split_cnstr) in
        let n_subs = Array.length subs in
        for i = 0 to var_ix_1 do
          let { histo = new_histo } as new_var = new_vars.(i) in
          let sample = vars.(i).samples.(sample_ix) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done;
        let n_subs_1 = n_subs - 1 in
        for i = 0 to n_subs_1 do
          let { histo = new_histo } as new_var = new_vars.(var_ix + i) in
          let sample = subs.(i) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done;
        for i = var_ix1 to n_vars_1 do
          let { histo = new_histo } as new_var = new_vars.(i + n_subs_1) in
          let sample = vars.(i).samples.(sample_ix) in
          new_var.samples.(new_sample_ix) <- sample;
          let cnstr = fdsum_cnstr sample in
          new_histo.(cnstr) <- new_histo.(cnstr) + 1
        done
  done;
  !none_ix_ref, split_vars, none_vars


(* Maybe find unique constructor *)

let maybe_get_unique histo n =
  let rec loop cnstr =
    let freq = histo.(cnstr) in
    if freq > 0 then
      if freq < n then None
      else Some cnstr
    else
      if cnstr > 0 then loop (cnstr - 1)
      else None in
  loop (Array.length histo - 1)


(* Calculation of shave information for domains with redundant constructors *)

type shave_info = ShInfo of pos_infos * vars
and pos_infos = (var_ix * cnstr * subpos_info) list
and subpos_info = (int * shave_info) option

let calc_shave_info fspec vars =
  let n_vars = Array.length vars in
  if n_vars = 0 then ShInfo ([], vars), 0
  else
    let n_samples = Array.length vars.(0).samples in
    let rec loop_var (ShInfo (pos_infos, vars) as sh_info) n_sh_vars var_ix =
      if var_ix < 0 then sh_info, n_sh_vars
      else
        let var = vars.(var_ix) in
        match maybe_get_unique var.histo n_samples with
        | None -> loop_var sh_info (n_sh_vars + 1) (var_ix - 1)
        | Some cnstr ->
            let sub_tps = fspec.(var.tp).(cnstr) in
            let n_subs = Array.length sub_tps in
            if n_subs = 0 then
              let new_sh_info =
                ShInfo ((var_ix, cnstr, None) :: pos_infos, vars) in
              loop_var new_sh_info n_sh_vars (var_ix - 1)
            else (
              let fst_tp = sub_tps.(0) in
              let fst_var =
                {
                  samples = Array.make n_samples dummy_fdsum;
                  tp = fst_tp;
                  histo = Array.make (Array.length fspec.(fst_tp)) 0;
                } in
              let sub_vars = Array.make n_subs fst_var in
              let n_subs_1 = n_subs - 1 in
              for i = 1 to n_subs_1 do
                sub_vars.(i) <-
                  let tp = sub_tps.(i) in
                  {
                    samples = Array.make n_samples dummy_fdsum;
                    tp = tp;
                    histo = Array.make (Array.length fspec.(tp)) 0;
                  }
              done;
              let samples = var.samples in
              for sample_ix = 0 to n_samples - 1 do
                match samples.(sample_ix) with
                | FDStrct (_, subs) ->
                    for i = 0 to n_subs_1 do
                      let sub_var = sub_vars.(i) in
                      let sub = subs.(i) in
                      sub_var.samples.(sample_ix) <- sub;
                      let subhisto = sub_var.histo in
                      let subcnstr = fdsum_cnstr sub in
                      subhisto.(subcnstr) <- subhisto.(subcnstr) + 1
                    done
                | _ -> assert false (* impossible *)
              done;
              let new_sh_info, n_sh_sub_vars =
                loop_var (ShInfo ([], sub_vars)) 0 n_subs_1 in
              let new_pos_info =
                var_ix, cnstr, Some (n_sh_sub_vars, new_sh_info) in
              let new_sh_info = ShInfo (new_pos_info :: pos_infos, vars) in
              loop_var new_sh_info (n_sh_vars + n_sh_sub_vars) (var_ix - 1)) in
    loop_var (ShInfo ([], vars)) 0 (n_vars - 1)


(* Compute variables from pos-infos *)

let vars_of_pos_infos n_sh_vars vars pos_infos =
  if pos_infos = [] then vars
  else (
    let sh_vars = Array.make n_sh_vars dummy_var in
    let rec loop vars lix sh_var_ix = function
      | [] ->
          let n_last = Array.length vars - lix in
          Array.blit vars lix sh_vars sh_var_ix n_last;
          sh_var_ix + n_last
      | (sh_ix, _, sub_sh_info) :: rest ->
          let n_between = sh_ix - lix in
          Array.blit vars lix sh_vars sh_var_ix n_between;
          let next_sh_var_ix = sh_var_ix + n_between in
          let next_lix = sh_ix + 1 in
          match sub_sh_info with
          | None | Some (0, _) -> loop vars next_lix next_sh_var_ix rest
          | Some (_, ShInfo (sub_infos, sub_vars)) ->
              let next_sh_var_ix = loop sub_vars 0 next_sh_var_ix sub_infos in
              loop vars next_lix next_sh_var_ix rest in
    ignore (loop vars 0 0 pos_infos);
    sh_vars)
