module BDDType
  use mach.peano.Peano
  use mach.int.Int63

  type bdd =
    | Bottom
    | Top
    | N int63 bdd bdd Peano.t (* N (var, true_branch, false_branch, id) *)

  type affectation = int63 -> bool

  (* Test whenever provided bdd has a node as root *)
  let predicate is_node (input : bdd) : bool =
    match input with
      | Bottom | Top -> false
      | N _ _ _ _ -> true
    end

  (* Test whenever provided bdd is a leaf *)
  let predicate is_leaf (input : bdd) : bool = not (is_node input)

  (* Return the `true_branch` of provided bdd *)
  let function true_branch (input : bdd) : bdd =
    requires { is_node input }
    match input with
      | N _ branch _ _ -> branch
      | _ -> absurd
    end

  (* Return the `false_branch` of provided bdd *)
  let function false_branch (input : bdd) : bdd =
    requires { is_node input }
    match input with
      | N _ _ branch _ -> branch
      | _ -> absurd
    end

  (* Return the id of variable of provided bdd *)
  let function var (input : bdd) : int63 =
    requires { is_node input }
    match input with
      | N var_id _ _ _ -> var_id
      | _ -> absurd
    end

  (* Return the id of provided bdd *)
  let function id (input : bdd) : Peano.t =
    match input with
      | Bottom -> Peano.zero
      | Top -> Peano.one
      | N _ _ _ node_id -> node_id
    end

  (* Equality of rank 0 *)
  let predicate eq0 (t1 : bdd) (t2 : bdd) : bool =
    eq (id t1) (id t2)

  (* Equality of rank 1 *)
  let predicate eq1 (t1 : bdd) (t2 : bdd) : bool =
    match t1, t2 with
      | Top, Top | Bottom, Bottom -> True
      | N var_1 true_1 false_1 _, N var_2 true_2 false_2 _ ->
          var_1 = var_2 && eq0 true_1 true_2 && eq0 false_1 false_2
      | _ -> False
    end

  (* Tuple equality over the BDD *)
  let predicate tuple_eq0 (t1 t2 : (bdd, bdd)) : bool =
    let l1, r1 = t1 in
    let l2, r2 = t2 in
    eq0 l1 l2 && eq0 r1 r2

  (* Value of a BDD with a given affectation *)
  let rec function value (f : affectation) (input : bdd) : bool =
    variant { input }
    match input with
      | Top -> True
      | Bottom -> False
      | N var_id true_b false_b _ ->
           if f var_id then value f true_b else value f false_b
    end

  (* Convert a Boolean to its corresponding leaf *)
  let bool_to_leaf (b : bool) : bdd = if b then Top else Bottom
    ensures { forall f : affectation. value f result = b }
    ensures { is_leaf result }

end

module Hashtbl
  use map.Map

  type key

  val predicate eq key key : bool

  type t 'a = abstract {
    mutable contents: map key 'a ;
    mutable domain: map key bool
  }
  invariant {
    (      (* All similar key have the same status in the table's domain *)
      forall u v : key. eq u v -> domain u = domain v
    ) /\ ( (* If two key are similar and one is in the table,
              they map to the same value *)
      forall u v : key. eq u v /\ domain u ->
          Map.([]) contents u =  Map.([]) contents v
    )
  }
  by { contents = (fun _ -> any 'a) ; domain = (fun _ -> false) }

  function is_in (h: t 'a) (k: key) : bool =
    h.domain k

  function val_of (h : t 'a) (k : key) : 'a = Map.([]) h.contents k

  val create (_u : unit) : t 'a
   ensures { forall k : key. is_in result k = false }

  val put (h: t 'a) (k: key) (v: 'a) : unit
    writes { h }
    ensures { forall k' : key. eq k k' -> is_in h k' /\ val_of h k' = v }
    ensures { forall k' : key. not eq k k' -> val_of h k' = val_of (old h) k'
                /\ is_in h k' = is_in (old h) k' }

  exception NotFound

  val find (h: t 'a) (k: key) : 'a
    ensures { is_in h k /\ val_of h k = result }
    raises  { NotFound -> not is_in h k }

  val mem (h: t 'a) (k: key) : bool
    ensures { result = is_in h k }
end

module HashConsingHashtbl
  use BDDType
  clone export Hashtbl with type key = bdd, val eq = eq1
end

module BDDAssociationMap
  use BDDType
  clone export Hashtbl with type key = bdd, val eq = eq0
end

module BDDTupleAssociationMap
  use BDDType

  clone export Hashtbl with type key = (bdd, bdd), val eq = tuple_eq0
end

module BDD
  use int.Int
  use mach.peano.Peano
  use mach.int.Int63
  use export BDDType

  use HashConsingHashtbl as HCMap

  (* A value is mem_tbl in the table if it is in the table and it is
     linked to itself *)
  predicate mem_tbl (tbl : HCMap.t bdd) (k : bdd) =
    HCMap.is_in tbl k /\ HCMap.val_of tbl k = k

  (* A value is well-formed (ie a ROBDD) if its mem_tbl in the hash-consign
     table or it's a leaf *)
  predicate well_formed (tbl : HCMap.t bdd) (k : bdd) =
    is_leaf k \/ mem_tbl tbl k

  type hctable = { tbl : HCMap.t bdd; mutable next_id : Peano.t }

  (* Top and Bottom are not in the Table *)
  invariant { not HCMap.is_in tbl Top /\ not HCMap.is_in tbl Bottom }

  (* Each BDD mem_tbl in table is Ordered [True Branch] *)
  invariant { forall u : bdd. mem_tbl tbl u -> is_node (true_branch u) ->
                var u < var (true_branch u) }

  (* Each BDD mem_tbl in table is Ordered [False Branch] *)
  invariant { forall u : bdd. mem_tbl tbl u -> is_node (false_branch u) ->
                var u < var (false_branch u) }

  (* No BDD mem_tbl in table has the same branch twice *)
  invariant { forall u : bdd. mem_tbl tbl u -> true_branch u <> false_branch u }

  (* Each BDD in the domain is in the same equivalence-class of eq1 to its
     associated value *)
  invariant { forall u : bdd. HCMap.is_in tbl u -> eq1 u (HCMap.val_of tbl u) }

  (* Each BDD mem_tbl in table has a unique id *)
  invariant { forall u v : bdd. mem_tbl tbl u /\ mem_tbl tbl v /\ u <> v ->
                (id u).v <> (id v).v }

  (* Each BDD mem_tbl in table has a id lower than next_id *)
  invariant { forall u : bdd. mem_tbl tbl u -> (id u).v < next_id.v }

  (* Transitivity of true_branch for the BDD mem_tbl in table *)
  invariant { forall u : bdd. mem_tbl tbl u ->
                well_formed tbl (true_branch u) }

  (* Transitivity of false_branch for the BDD mem_tbl in table *)
  invariant { forall u : bdd. mem_tbl tbl u ->
                well_formed tbl (false_branch u) }

  (* Next_id begin at 2 *)
  invariant { next_id >= 2 }

  (* Each BDD mem_tbl in table have an id greater than 2 *)
  invariant { forall u : bdd. mem_tbl tbl u -> id u >= 2 }

  by { tbl = HCMap.create (); next_id = Peano.succ Peano.one }

  (* Each BDD is now uniquily present in the table *)
  lemma bdd_is_unique :
    forall hc : hctable, u v : bdd. mem_tbl hc.tbl u /\ mem_tbl hc.tbl v ->
        var u = var v -> true_branch u = true_branch v ->
          false_branch u = false_branch v ->
            u = v

  (* All BDD well-formed in the 'old_hc' Hash-consign table are still
     well-formed in the newer one *)
  predicate still_well_formed (old_hc new_hc : hctable) =
    forall u : bdd. well_formed old_hc.tbl u -> well_formed new_hc.tbl u

  (* With the hash-consign, for well-formed BDD, eq1 is the same as the
     logical equality *)
  lemma eq1_match_logic :
    forall hc : hctable, u v : bdd. well_formed hc.tbl u ->
      well_formed hc.tbl v -> u = v <-> eq1 u v

  (* With the hash-consign, for well-formed BDD, eq0 is the same as the
     logical equality *)
  lemma eq0_match_logic :
    forall hc : hctable, u v : bdd. well_formed hc.tbl u ->
      well_formed hc.tbl v -> u = v <-> eq0 u v

  (* Create an empty hctable *)
  let create_hctable () : hctable =
    { tbl = HCMap.create () ; next_id = Peano.succ Peano.one }

  (* From two bdd and a variable, we create a new BDD in the table *)
  let create_node (hc : hctable) (v : int63) (t : bdd) (f : bdd) : bdd =
    (* If t is a node, var t > v *)
    requires { is_node t -> var t > v }

    (* If f is a node, var f > v *)
    requires { is_node f -> var f > v }

    (* t is well-formed in the hash-consign table *)
    requires { well_formed hc.tbl t }

    (* f is well-formed in the hash-consign table *)
    requires { well_formed hc.tbl f }

    (* The old well-formed bdd are still well-formed *)
    ensures { still_well_formed (old hc) hc }

    (* All well-formed BDD in the current hash-consign table where well-formed
       in the old hash-consign table or they are the result *)
    ensures { forall u : bdd. u <> result ->
                well_formed (old hc).tbl u = well_formed hc.tbl u }

    (* The result is well-formed *)
    ensures { well_formed hc.tbl result }

    (* If t <> f, var result = v *)
    ensures { t <> f -> var result = v }

    (* If t = f and is_node t, var result = var t *)
    ensures { t = f -> is_node t -> var t = var result }

    (* If result is a node, var result <= v *)
    ensures { is_node result -> var result >= v }

    (* Interpretation of a ROBDD *)
    ensures { forall a : affectation.
                value a result = if a v then value a t else value a f }

    (* result was either well_formed in the old hash-table and the next_id
       hasn't change or result have as id the old next_id *)
    ensures { well_formed (old hc).tbl result /\ hc.next_id = old hc.next_id \/
                id result = (old hc).next_id = hc.next_id - 1 }

    if eq0 t f then
      t
    else
      let bdd_candidate = N v t f Peano.zero in
      try
        HCMap.find hc.tbl bdd_candidate
      with HCMap.NotFound ->
        let new_node = N v t f hc.next_id in
        HCMap.put hc.tbl bdd_candidate new_node;
        hc.next_id <- Peano.succ hc.next_id;
        new_node
      end
end

module Not
  use BDD
  use int.Int
  use BDDAssociationMap as MemoMap

  (* Test if 'not_bdd' is the correct value of the 'orig_bdd' bdd by the
     not operation *)
  predicate is_not_bdd (not_bdd : bdd) (orig_bdd : bdd) =
    forall f : affectation. value f not_bdd = not value f orig_bdd

  type memoMap = { hc : hctable; m : MemoMap.t bdd }

  (* Top and Bottom are not in the memoisation table *)
  invariant { not MemoMap.is_in m Top /\ not MemoMap.is_in m Bottom }

  (* All BDD in the Memoisation Table are in the HashConsign Table *)
  invariant { forall u : bdd. MemoMap.is_in m u -> id u < hc.next_id }

  (* All BDD in the Memoisation Table have their value in the
     HashConsign Table *)
  invariant { forall u : bdd. MemoMap.is_in m u ->
                well_formed hc.tbl (MemoMap.val_of m u) }

  (* All values are images of the not operation of their key *)
  invariant { forall u : bdd. mem_tbl hc.tbl u -> MemoMap.is_in m u ->
                is_not_bdd (MemoMap.val_of m u) u }

  (* The variable of the keys are <= than the variable of the value *)
  invariant { forall u : bdd. mem_tbl hc.tbl u ->
                MemoMap.is_in m u ->
                  is_node (MemoMap.val_of m u) ->
                    var u <= var (MemoMap.val_of m u) }

  by { hc = create_hctable (); m = MemoMap.create () }

  let init_memo_map (hc : hctable) : memoMap =
    { hc = hc ; m = MemoMap.create () }

  (* Memoised unary operation *)
  let rec apply (map : memoMap) (b : bdd) : bdd =
    (* The input BDD must be well-formed. *)
    requires { well_formed map.hc.tbl b }

    (* The result is well-formed *)
    ensures { well_formed map.hc.tbl result }

    (* If the input is a node, then it is in the table *)
    ensures { is_node b -> MemoMap.is_in map.m b }

    (* If the input is a node, then its associated value in the table is
       the result *)
    ensures { is_node b -> MemoMap.val_of map.m b = result }

    (* If the result is a node, then the input must be a node and var result
       is >= than var b *)
    ensures { is_node result -> var b <= var result /\ is_node b }

    (* All the old well-formed bdd in map.hc are still well-formed in map.hc *)
    ensures { still_well_formed (old map.hc) map.hc }

    (* All old BDD in the table are concerved and their value too *)
    ensures { forall u : bdd. MemoMap.is_in (old map).m u ->
                MemoMap.is_in map.m u /\
                  MemoMap.val_of (old map).m u = MemoMap.val_of map.m u }

    (* No changes have been made to the memo_table if the input is a leaf *)
    ensures { is_leaf b -> forall u : bdd.
                MemoMap.is_in (old map).m u = MemoMap.is_in map.m u }

    (* The result is the result of the not operation *)
    ensures { is_not_bdd result b }

    (*  The next_id of the hash-consign table can only increase *)
    ensures { map.hc.next_id >= (old map).hc.next_id }

    variant { b }
    match b with
      | Bottom -> Top
      | Top -> Bottom
      | _ ->
        try
          MemoMap.find map.m b
        with MemoMap.NotFound ->
            let new_tb = apply map (true_branch b) in
            let new_fb = apply map (false_branch b) in
            let new_value = create_node map.hc (var b) new_tb new_fb
            in
            MemoMap.put map.m b new_value;
            new_value
        end
    end
end

module Size
  use BDD
  use int.Int
  use mach.peano.Peano
  use set.Fset
  use ref.Ref
  use BDDAssociationMap as HashMap

  (* Subnodes of b *)
  function subnodes (b : bdd) : fset bdd =
    match b with
      | Top | Bottom -> empty
      | N _ true_bdd false_bdd _ ->
              add b (union (subnodes true_bdd) (subnodes false_bdd))
    end

  (* If the true branch of a node u is a node,
     then it is in the subnodes of u *)
  lemma true_branch_in_sub_nodes:
    forall u : bdd. is_node u /\ is_node (true_branch u) ->
      mem (true_branch u) (subnodes u)

  (* If the false branch of a node u is a node,
     then it is in the subnodes of u *)
  lemma false_branch_in_sub_nodes:
    forall u : bdd. is_node u /\ is_node (false_branch u) ->
      mem (false_branch u) (subnodes u)

  (* If u is a subnode of v and true_branch u is a node,
     then true_branch u is a subnode of v *)
  lemma sub_nodes_with_true_branch:
    forall u v [@induction] : bdd. mem u (subnodes v) ->
      is_node (true_branch u) -> mem (true_branch u) (subnodes v)

  (* If u is a subnode of v and false_branch u is a node,
     then false_branch u is a subnode of v *)
  lemma sub_nodes_with_false_branch:
    forall u v [@induction] : bdd. mem u (subnodes v) ->
      is_node (false_branch u) -> mem (false_branch u) (subnodes v)

  (* u is in its subnodes *)
  lemma bdd_in_their_subnodes:
    forall u : bdd. is_node u -> mem u (subnodes u)

  (* All the variable of b are in [0; n [ *)
  predicate var_bounded_bdd (n : int) (b : bdd) =
    forall u : bdd. mem u (subnodes b) -> n > var u >= 0

  (* Top and Bottom are not in the subnodes of any bdd *)
  let rec lemma top_bottom_not_in_subnodes (b : bdd) =
    ensures { not mem Top (subnodes b) }
    ensures { not mem Bottom (subnodes b) }
    match b with
      | Top | Bottom -> ()
      | N _ t f _ -> top_bottom_not_in_subnodes t; top_bottom_not_in_subnodes f
    end

  (* Compute the number of Nodes in a well-formed BDD *)
  let size (ghost hc : hctable) (in_bdd : bdd) : Peano.t =
    (* in_bdd is well-formed *)
    requires { well_formed hc.tbl in_bdd }

    (* The result is the cardinal of the subnodes of in_bdd *)
    ensures { result = cardinal (subnodes in_bdd) }

    let m = HashMap.create () in
    let ghost ref s = empty in
    let ref cpt = Peano.zero in

    let rec loop (b : bdd) =
      (* b is a well-formed bdd *)
      requires { mem_tbl hc.tbl b }

      (* b must be a subnode of in_bdd *)
      requires { mem b (subnodes in_bdd) }

      (* The hash-table m has the same domain as the ghost set s *)
      requires { forall u : bdd. mem u s <->
                  mem_tbl hc.tbl u /\ HashMap.is_in m u }

      (* cpt is the cardinal of the ghost set s *)
      requires { cpt = cardinal s }

      (* All the nodes in the ghost set s are subnodes of in_bdd *)
      requires { forall u : bdd. mem u s -> mem u (subnodes in_bdd) }

      (* The hash-table m still has the same domain as the ghost set s *)
      ensures { forall u : bdd. mem u s <->
                  mem_tbl hc.tbl u /\ HashMap.is_in m u }

      (* All the subnodes of b are now in the ghost set s *)
      ensures { forall u : bdd. mem u (subnodes b) -> mem u s }

      (* cpt is still the cardinal of the ghost set s *)
      ensures { cpt = cardinal s }

      (* The ghost set s is only getting bigger *)
      ensures { forall u : bdd. mem u (old s) -> mem u s }

      (* The ghost set s is contained in the subnodes of in_bdd *)
      ensures { forall u : bdd. mem u s -> mem u (subnodes in_bdd) }

      variant { b }
      if not HashMap.mem m b then (
        cpt <- Peano.succ cpt;
        HashMap.put m b ();
        s <- Fset.add b s
      );
      if is_node (true_branch b) then
        loop (true_branch b);
      if is_node (false_branch b) then
        loop (false_branch b)
    in
    if is_node in_bdd then
      loop in_bdd;
    assert { s == subnodes in_bdd };
    cpt
end

module BinOp
  use BDD
  use int.Int
  use mach.int.Int63
  use int.MinMax
  use BDDTupleAssociationMap as MemoMap

  (* Binary boolean function to customise *)
  val function bool_op (b1 b2 : bool) : bool

  (* Test if 'res' is the correct value of the 'left' and 'right'
     bdd by the bool operation *)
  predicate is_result (res left right : bdd) =
    forall f : affectation. value f res = bool_op (value f left) (value f right)

  type memoMap = { hc : hctable; m : MemoMap.t bdd }

  (* Top and Bottom are not in the memoisation table *)
  invariant { not MemoMap.is_in m (Top, Top)       /\
              not MemoMap.is_in m (Bottom, Top)    /\
              not MemoMap.is_in m (Bottom, Bottom) /\
              not MemoMap.is_in m (Top, Bottom) }

  (* All BDD in the Memoisation Table have their value in the
     HashConsign Table *)
  invariant { forall u v : bdd. MemoMap.is_in m (u, v) ->
                well_formed hc.tbl (MemoMap.val_of m (u, v)) }

  (* All values are images of the not operation of their key *)
  invariant { forall u v : bdd. well_formed hc.tbl u ->
                well_formed hc.tbl v -> MemoMap.is_in m (u, v) ->
                  is_result (MemoMap.val_of m (u, v)) u v }

  (* The variable of the keys are <= than the variable of the value (if the value is a node) *)
  invariant { forall u v : bdd. well_formed hc.tbl u -> well_formed hc.tbl v -> MemoMap.is_in m (u, v)
                                    -> is_node (MemoMap.val_of m (u, v)) -> is_node u -> is_leaf v
                                           -> var u <= var (MemoMap.val_of m (u, v)) }

  invariant { forall u v : bdd. well_formed hc.tbl u -> well_formed hc.tbl v -> MemoMap.is_in m (u, v)
                                    -> is_node (MemoMap.val_of m (u, v)) -> is_leaf u -> is_node v
                                           -> var v <= var (MemoMap.val_of m (u, v)) }

  invariant { forall u v : bdd. well_formed hc.tbl u -> well_formed hc.tbl v -> MemoMap.is_in m (u, v)
                                    -> is_node (MemoMap.val_of m (u, v)) -> is_node u -> is_node v
                                           -> min (var u) (var v) <= var (MemoMap.val_of m (u, v)) }

  (* The bdd in the keys have their id < than the next id of the hash-consign table *)
  invariant { forall u v : bdd. MemoMap.is_in m (u, v) -> id u < hc.next_id /\ id v < hc.next_id }

  by { hc = create_hctable (); m = MemoMap.create () }

  let init_memo_map (hc : hctable) : memoMap =
    { hc = hc ; m = MemoMap.create () }

  (* Memoised binary operation *)
  let rec apply (map : memoMap) (l r : bdd) : bdd =
    (* Input BDDs must be well-formed. *)
    requires { well_formed map.hc.tbl l /\ well_formed map.hc.tbl r }

    (* The result is well-formed *)
    ensures { well_formed map.hc.tbl result }

    (* If the result is a node, then one of the input bdd is a node *)
    ensures { not is_node result \/ is_node l \/ is_node r }

    (* If one of the input bdd is a node, then the tuple is in the table *)
    ensures { is_node l \/ is_node r -> MemoMap.is_in map.m (l, r) }

    (* If the tuple is in the table, then its associated value in the table is the result *)
    ensures { is_node l \/ is_node r -> MemoMap.val_of map.m (l, r) = result }

    (* If the result if a node and the l is the only node of the input, then the var_id of the result is >= than var_id l *)
    ensures { is_node result -> is_node l -> is_leaf r -> var l <= var result }

    (* If the result if a node and the r is the only node of the input, then the var_id of the result is >= than var_id r *)
    ensures { is_node result -> is_leaf l -> is_node r -> var r <= var result }

    (* If the result if a node and (l, r) are two nodes, then the var_id of the result is >= than min of the var of r and l *)
    ensures { is_node result -> is_node l -> is_node r -> min (var l) (var r) <= var result }

    (* All the old well-formed bdd in map.hc are still well-formed in map.hc *)
    ensures { still_well_formed (old map.hc) map.hc }

    (* All old BDD in the table are concerved and their value too *)
    ensures { forall u : (bdd, bdd). MemoMap.is_in (old map).m u -> MemoMap.is_in map.m u /\ MemoMap.val_of (old map).m u = MemoMap.val_of map.m u }

    (* No changes have been made to the memo_table if the input is a tuple of leaves *)
    ensures { is_leaf l /\ is_leaf r -> forall u : (bdd, bdd). MemoMap.is_in (old map).m u = MemoMap.is_in map.m u }

    (* The result is the result of the bool operation *)
    ensures { is_result result l r }

    variant { l, r }
    if is_leaf l && is_leaf r then
      match l, r with
        | Top,    Top    -> bool_to_leaf (bool_op true true)
        | Top,    Bottom -> bool_to_leaf (bool_op true false)
        | Bottom, Top    -> bool_to_leaf (bool_op false true)
        | Bottom, Bottom -> bool_to_leaf (bool_op false false)
        | _ -> absurd
      end
    else
        try
          MemoMap.find map.m (l, r)
        with MemoMap.NotFound ->
          let result =
            match (is_leaf l, is_leaf r) with
              | True , True  -> (* is_leaf l /\ is_leaf r *)
                  absurd
              | True , False -> (* is_leaf l /\ is_node r *)
                  create_node map.hc (var r)
                                     (apply map l (true_branch r))
                                     (apply map l (false_branch r))
              | False, True  -> (* is_node l /\ is_leaf r *)
                  create_node map.hc (var l)
                                     (apply map (true_branch l) r)
                                     (apply map (false_branch l) r)
              | False, False -> (* is_node l /\ is_node r *)
                  if var l = var r then
                    (* same variable for l and r *)
                    create_node map.hc (var l)
                                       (apply map (true_branch l) (true_branch r))
                                       (apply map (false_branch l) (false_branch r))
                  else if var l > var r then
                    (* var l comme after var r *)
                    create_node map.hc (var r)
                                       (apply map l (true_branch r))
                                       (apply map l (false_branch r))
                  else
                    (* var r comme after var l *)
                    create_node map.hc (var l)
                                       (apply map (true_branch l) r)
                                       (apply map (false_branch l) r)
            end
          in
          MemoMap.put map.m (l, r) result;
          result
        end
end

module And
  let function bool_op (a b : bool) : bool = a && b
  clone export BinOp with val bool_op = bool_op
end

module Or
  let function bool_op (a b : bool) : bool = a || b
  clone export BinOp with val bool_op = bool_op
end

module Implies
  let function bool_op (a b : bool) : bool = not a || b
  clone export BinOp with val bool_op = bool_op
end

module Sat
  use BDD
  use Size
  use seq.Seq
  use set.Fset
  use mach.int.Int63
  use int.Int

  let rec lemma value_match (hc : hctable) (u v : affectation) (b : bdd) =
    requires { well_formed hc.tbl b }
    requires { is_node b -> forall y : int63. y >= var b -> u y  = v y }
    ensures { value u b = value v b }
    variant  { b }
    if is_leaf b then ()
    else
      if u (var b) then
        value_match hc u v (true_branch b)
      else
        value_match hc u v (false_branch b)

  predicate sat (b : bdd) =
    exists f : affectation. value f b = True

  lemma top_is_sat:
    sat Top
    by value (fun _ -> False) Top = True

  let rec lemma nodes_are_sat (hc : hctable) (b : bdd) : affectation =
    requires { mem_tbl hc.tbl b }
    ensures {
    value result b = True }
    variant { b }
    match b with
      | N v Bottom f _ ->
          match f with
            | Top -> (fun _ -> False)
            | Bottom -> absurd
            | N _ _ _ _ ->  let u = nodes_are_sat hc f in (fun x -> if x = v then False else u x)
          end
      | N v t _ _ ->
          match t with
            | Top -> (fun _ -> True)
            | Bottom -> absurd
            | N _ _ _ _ -> let u = nodes_are_sat hc t in (fun x -> if x = v then True else u x)
          end
      | _ -> absurd
    end

  let predicate is_sat (ghost hc : hctable) (b : bdd) =
    requires { well_formed hc.tbl b }
    ensures { result <-> sat b }
    match b with
      | Bottom -> False
      | Top | N _ _ _ _ -> True
    end
end

module AnySat
  use BDD
  use Size
  use Sat
  use mach.int.Int63
  use int.Int
  use seq.Seq
  use set.Fset
  use mach.array.Array63

  (* Predicate to get the value of a BDD with an array *)
  let rec ghost predicate array_value (sol : array bool) (b : bdd) =
    requires { var_bounded_bdd sol.length b }
    variant { b }
    match b with
      | Top -> True
      | Bottom -> False
      | N v t f _ -> if sol[v] then array_value sol t else array_value sol f
    end

  let rec lemma array_value_same_eval (b : bdd) (sol : array bool) (affect : affectation) =
    requires { var_bounded_bdd sol.length b }
    requires { forall x : int63. 0 <= x < sol.length -> sol[x] = affect x }
    ensures { array_value sol b <-> value affect b }
    variant { b }
    match b with
      | Top | Bottom -> ()
      | N v t f _ -> if sol[v] then array_value_same_eval t sol affect else array_value_same_eval f sol affect
    end

  (* Give an affectation for each var in [0; n[ that satisfy the bdd. *)
  let any_sat (ghost hc : hctable) (n : int63) (b : bdd) : array bool =
    (* Well-formed bdd *)
    requires { well_formed hc.tbl b }

    (* All the variable are in [0; n [ *)
    requires { var_bounded_bdd n b }

    (* n >=0 *)
    requires { n >= 0 }

    (* If b is sat, the result is an affectation that satisfy the bdd *)
    ensures { sat b -> array_value result b = True }

    let sol = Array63.make n False in
    let rec _loop (u : bdd) =
      (* Well-formed BDD *)
      requires { well_formed hc.tbl u }

      (* All the variable are in [0; n [ *)
      requires { var_bounded_bdd n u }

      (* u is a subnodes of b or u is Top *)
      requires { u = Top \/ mem u (subnodes b) }

      (* All the cell between (var u) and n are set to False
         (No modification of the array is this range has been made) *)
      requires { forall x : int. is_node u -> n > x >= var u -> sol[x] = False }

      (* This build a correct affectation that satisfy u *)
      ensures { array_value sol u }

      (* If u is a node, all the modification before our variable are kept *)
      ensures { forall x : int63. is_node u -> 0 <= x < var u -> (old sol)[x] = sol[x] }

      (* We do not modify the array if u = Top *)
      ensures { u = Top -> old sol = sol }

      variant { u }
      match u with
        | Top -> ()
        | Bottom -> absurd
        | N v t Bottom _ -> sol[v] <- True; _loop t
        | N _ _ f _ -> _loop f
      end
    in
    if is_node b then _loop b;
    sol
end

module CountSat
  use BDD
  use Size
  use mach.int.Int63
  use set.Fset
  use seq.Seq
  use int.Int
  use int.Power

  (* Predicate to get the value of a BDD with a sequence *)
  let rec ghost predicate seq_value (sol : seq bool) (b : bdd) =
    requires { var_bounded_bdd sol.length b }
    variant { b }
    match b with
      | Top -> True
      | Bottom -> False
      | N v t f _ -> if sol[Int63.to_int v] then seq_value sol t else seq_value sol f
    end

  (* seq_value and value give the same output for similar affectation *)
  let rec lemma array_value_same_eval (b : bdd) (sol : seq bool) (affect : affectation) =
    requires { var_bounded_bdd sol.length b }
    requires { forall x : int63. 0 <= x < sol.length -> sol[x] = affect x }
    ensures { seq_value sol b <-> value affect b }
    variant { b }
    match b with
      | Top | Bottom -> ()
      | N v t f _ -> if sol[Int63.to_int v] then array_value_same_eval t sol affect else array_value_same_eval f sol affect
    end

  (* build all the interpretation satifying a BDD *)
  let rec function all_sat (ghost hc : hctable) (nb_var : int63) (v_id : int63) (ghost a : seq bool) (b : bdd) : (card : int, ghost set : fset (seq bool)) =
    (* v_id is in [0; nb_var] *)
    requires { nb_var >= v_id >= 0 }

    (* All variable are < than nb_var *)
    requires { var_bounded_bdd nb_var b }

    (* b is well-formed *)
    requires { well_formed hc.tbl b }

    (* If b is a node, var b >= v_id *)
    requires { is_node b -> v_id <= var b }

    (* a is of length max_var *)
    requires { a.length = nb_var }

    (* The size of all the sequences in the result is max_var - v_id *)
    ensures { forall u : seq bool. mem u set -> u.length = nb_var }

    (* All sequences in the result satisfies b *)
    ensures { forall u : seq bool. mem u set -> seq_value u b = True }

    (* All sequences have the same begining as a *)
    ensures { forall u : seq bool. mem u set -> forall i : int. 0 <= i < v_id -> a[i] = u[i] }

    (* If u is a sequences satifying b then u is in result *)
    ensures { forall u : seq bool. u.length = nb_var -> seq_value u b = True -> (forall i : int. 0 <= i < v_id -> a[i] = u[i]) -> mem u set }

    (* card is tha cardinal of the set built *)
    ensures { card = cardinal set }

    (* If b is Bottom, no affectation can satisfy it *)
    ensures { b = Bottom -> set = Fset.empty /\ card = 0 }

    (* If b is top, there is 2^(nb_var - v_id) affectation satisfying it (which begining is False) *)
    ensures { b = Top -> card = power 2 (nb_var - v_id) }

    variant { nb_var - v_id }
    match b with
      | Bottom -> let ghost res = Fset.empty in 0, res
      | Top ->
          if nb_var = v_id then
            let ghost res = Fset.singleton a in
            1, res
          else
            let b_true = set a (Int63.to_int v_id) True in
            let b_false = set a (Int63.to_int v_id) False in
            let true_card, true_set = all_sat hc nb_var (v_id + 1) b_true b in
            let false_card, false_set = all_sat hc nb_var (v_id + 1) b_false b in
            assert { Fset.disjoint true_set false_set };
            assert { true_card = false_card };
            let ghost res = Fset.union true_set false_set in
            true_card + false_card, res
      | N v t f _ ->
          let b_true = set a (Int63.to_int v_id) True in
          let b_false = set a (Int63.to_int v_id) False in
          if v = v_id then
            let true_card, true_set = all_sat hc nb_var (v_id + 1) b_true t in
            let false_card, false_set = all_sat hc nb_var (v_id + 1) b_false f in
            assert { Fset.disjoint true_set false_set };
            let ghost res = Fset.union true_set false_set in
            true_card + false_card, res
          else
            let true_card, true_set = all_sat hc nb_var (v_id + 1) b_true b in
            let false_card, false_set = all_sat hc nb_var (v_id + 1) b_false b in
            assert { Fset.disjoint true_set false_set };
            let ghost res = Fset.union true_set false_set in
            true_card + false_card, res
    end

  let function count_sat (ghost hc : hctable) (nb_var : int63) (b : bdd) : (card : int, ghost set : fset (seq bool)) =
    (* v_id is in [0; nb_var] *)
    requires { nb_var >= 0 }

    (* All variable are < than nb_var *)
    requires { var_bounded_bdd nb_var b }

    (* b is well-formed *)
    requires { well_formed hc.tbl b }

    (* If u is a sequences satifying b then u is in result *)
    ensures { forall u : seq bool. u.length = nb_var /\ seq_value u b = True <-> mem u set }

    (* card is tha cardinal of the set built *)
    ensures { card = cardinal set }

    all_sat hc nb_var 0 (Seq.create (Int63.to_int nb_var) (fun _ -> False)) b

end

Generated by why3doc 1.6.0