package touist
Source file satSolve.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153open Types.Ast open Pprint open Minisat let minisat_clauses_of_cnf ast = let num_lit = ref 1 in let fresh_lit () = let lit = !num_lit in (incr num_lit; Minisat.Lit.make lit) in let clauses,lit_to_str,_ = Cnf.clauses_of_cnf Minisat.Lit.neg fresh_lit ast in clauses,lit_to_str let clauses_to_solver ?(verbose=false) (clauses:Lit.t list list) : Minisat.t option = let solver = Minisat.create () in if verbose then set_verbose solver 10; let rec add_clauses solver (l:Lit.t list list) : Minisat.t option = match l with | [] -> Some solver (* -> the formula did not become unsat while adding *) | cur::next -> let a = Array.of_list cur in if Minisat.Raw.add_clause_a solver a then add_clauses solver next else None in add_clauses solver clauses (** for printing the Minisat.value type *) let string_of_value = function | V_true -> "1" | V_false -> "0" | _ -> "?" (** A container for remembering a model *) module Model = struct type t = (Minisat.Lit.t * Minisat.value) list let compare l1 l2 = Pervasives.compare l1 l2 (* [dump] gives a string under the form (0,1)(1,2)(1,3)(0,4)... *) let dump l = List.fold_left (fun acc x -> match x with l,v -> ("("^(string_of_value v) ^ "," ^ (Lit.to_string l) ^ ")" ^ acc)) "" l (* [pprint] gives a string under the form 1 prop(1,2,9) O prop(1,4,2)... *) let pprint ?(sep="\n") table model = List.fold_left (fun acc (n,v) -> let str = (string_of_value v)^" "^(Hashtbl.find table n) in match acc with "" -> str | _ -> str ^ sep ^ acc) "" model end (** A set that contains all the models already found. *) module ModelSet = struct include Set.Make(Model) let dump models = fold (fun m acc -> (Model.dump m) ^ "\n" ^ acc) models "" let pprint table models = fold (fun m acc -> (Model.pprint table m) ^ "=====\n" ^ acc) models "" end (** [get_model] retrieves the valuations from the current state of the solver and put them into a Model.t. [discard] must return true if the literal that is mapped to the given proposition name shouldn't be in the model. *) let get_model solver (table:(Lit.t,string) Hashtbl.t) (discard:string->bool): Model.t = Hashtbl.fold (fun lit name acc -> if not (discard name) then (lit,Minisat.value solver lit)::acc else acc) table [] (* [string_of_clause] dumps the clause in its literal-number form: e.g., 1 -5 3 9 -2 -7 *) let string_of_clause (clause:Lit.t list) : string = List.fold_left (fun acc lit -> (Lit.to_string lit) ^" "^ acc) "" clause let string_of_clauses = List.fold_left (fun acc v -> (if acc="" then "" else acc^"\n")^(string_of_clause v)) "" (* 1. Prevent current model from reappearing ========================================= We must prevent the current model to reappear in future models; to do so, we add a clause that take the negation of the valuations E.g: with the model a=1, b=0 we must add the clause -a or b. [counter_clause] will produce a list of literals that corresponds to this clause. [counter_current_model] then adds the clause to the problem. IMPORTANT: When adding the counter-clause, the problem can become unsat. [counter_current_model] returns false if the added clause makes the formula unsat right away. *) (* 2. Avoid duplicates caused by fake literals (of the form '&6') ============================================================== Our issue here: the models contain fake '&12' literals. We don't want to see these fake literals in our models; we also want to remove the duplicate models linked to these fake literals. To avoid those duplicates, we store the models (without the fake literals) in a set. To use this function, you need a ModelSet ref already initialized, e.g. with let models = ref ModelSet.empty *) (* 3. Fetch the models =================== Basically, we (1) compute a model with Minisat.solve (2) check if we already saw this model (duplicates because of &23 literals) (3) prevent the same model from reappearing in adding the clause where all lits are the negation of the valuation in the model (4) go on with (1) *) let solve_clauses ?(verbose=false) ?(print: (Lit.t,string) Hashtbl.t -> Model.t -> int -> unit = fun t m i -> ()) ?(continue: Model.t -> int -> bool = fun _ _ -> true) (clauses,table : Lit.t list list * (Lit.t,string) Hashtbl.t) : (ModelSet.t) = let counter_current_model solver (table:(Lit.t,string) Hashtbl.t) : bool = let counter_clause (l:Lit.t) _ acc = match Minisat.value solver l with | V_true -> (Minisat.Lit.neg l)::acc | V_false -> l::acc | _ -> acc in let counter_clause = Hashtbl.fold counter_clause table [] in Minisat.Raw.add_clause_a solver (Array.of_list counter_clause) in (* already_unsat means that during the adding of clauses, the formula was found unsat. So we don't even need to solve it. *) let models = ModelSet.empty in match clauses_to_solver ~verbose clauses with | None -> models | Some solver -> (* searching for duplicate is slow on ModelSet. For checking a model hasn't appeared already, I use a way faster Hashtbl, ass it won't check on every single literal but compute a hash of the model) *) let models_hash = (Hashtbl.create 100) in let rec solve_loop i = (* i is the model counter *) if not (Minisat.Raw.simplify solver) || not (Minisat.Raw.solve solver [||]) then models else let model = get_model solver table Cnf.is_dummy (* is_dummy removes &1 lits *) and has_next_model = counter_current_model solver table in let is_duplicate = Hashtbl.mem models_hash model in match is_duplicate,has_next_model with | true,false -> models (* is duplicate and no next model *) | true,true -> solve_loop i (* is duplicate but has next *) | false,true -> (* both not duplicate and has next *) let models = ModelSet.add model models in (print table model i; Hashtbl.add models_hash model (); if continue model i then solve_loop (i+1) else models) | false, false -> (* is not duplicate and no next model *) let models = ModelSet.add model models in (print table model i; models) in solve_loop 1 let print_dimacs ?(debug_dimacs=false) (clauses,tbl) ?(out_table) (out:out_channel) = (match out_table with | None -> () | Some out_table -> (* Display the mapping table. *) tbl |> Cnf.print_table (Minisat.Lit.to_int) out_table ~prefix:(if out = out_table then "c " else "")); (* Display the dimacs' preamble line. *) Printf.fprintf out "p cnf %d %d\n" (Hashtbl.length tbl) (List.length clauses); (* Display the dimacs clauses. *) let print_lit l = if debug_dimacs then (if Minisat.Lit.sign l then "" else "-") ^ (Minisat.Lit.abs l |> Hashtbl.find tbl) else Minisat.Lit.to_string l in clauses |> Cnf.print_clauses out print_lit;