src/HOL/Tools/sat_solver.ML
author webertj
Thu, 21 May 2009 15:23:32 +0100
changeset 31219 034f23104635
parent 30275 381ce8d88cb8
child 32740 9dd0a2f83429
permissions -rw-r--r--
write_dimacs_{sat,cnf}_file now write the DIMACS file on the fly, without building it in memory first
     1 (*  Title:      HOL/Tools/sat_solver.ML
     2     Author:     Tjark Weber
     3     Copyright   2004-2009
     4 
     5 Interface to external SAT solvers, and (simple) built-in SAT solvers.
     6 *)
     7 
     8 signature SAT_SOLVER =
     9 sig
    10   exception NOT_CONFIGURED
    11 
    12   type assignment = int -> bool option
    13   type proof      = int list Inttab.table * int
    14   datatype result = SATISFIABLE of assignment
    15                   | UNSATISFIABLE of proof option
    16                   | UNKNOWN
    17   type solver     = PropLogic.prop_formula -> result
    18 
    19   (* auxiliary functions to create external SAT solvers *)
    20   val write_dimacs_cnf_file : Path.T -> PropLogic.prop_formula -> unit
    21   val write_dimacs_sat_file : Path.T -> PropLogic.prop_formula -> unit
    22   val read_std_result_file  : Path.T -> string * string * string -> result
    23   val make_external_solver  : string -> (PropLogic.prop_formula -> unit) ->
    24                                 (unit -> result) -> solver
    25 
    26   val read_dimacs_cnf_file : Path.T -> PropLogic.prop_formula
    27 
    28   (* generic solver interface *)
    29   val solvers       : (string * solver) list ref
    30   val add_solver    : string * solver -> unit
    31   val invoke_solver : string -> solver  (* exception Option *)
    32 end;
    33 
    34 structure SatSolver : SAT_SOLVER =
    35 struct
    36 
    37   open PropLogic;
    38 
    39 (* ------------------------------------------------------------------------- *)
    40 (* should be raised by an external SAT solver to indicate that the solver is *)
    41 (* not configured properly                                                   *)
    42 (* ------------------------------------------------------------------------- *)
    43 
    44   exception NOT_CONFIGURED;
    45 
    46 (* ------------------------------------------------------------------------- *)
    47 (* type of partial (satisfying) assignments: 'a i = NONE' means that 'a' is  *)
    48 (*      a satisfying assignment regardless of the value of variable 'i'      *)
    49 (* ------------------------------------------------------------------------- *)
    50 
    51   type assignment = int -> bool option;
    52 
    53 (* ------------------------------------------------------------------------- *)
    54 (* a proof of unsatisfiability, to be interpreted as follows: each integer   *)
    55 (*      is a clause ID, each list 'xs' stored under the key 'x' in the table *)
    56 (*      contains the IDs of clauses that must be resolved (in the given      *)
    57 (*      order) to obtain the new clause 'x'.  Each list 'xs' must be         *)
    58 (*      non-empty, and the literal to be resolved upon must always be unique *)
    59 (*      (e.g. "A | ~B" must not be resolved with "~A | B").  Circular        *)
    60 (*      dependencies of clauses are not allowed.  (At least) one of the      *)
    61 (*      clauses in the table must be the empty clause (i.e. contain no       *)
    62 (*      literals); its ID is given by the second component of the proof.     *)
    63 (*      The clauses of the original problem passed to the SAT solver have    *)
    64 (*      consecutive IDs starting with 0.  Clause IDs must be non-negative,   *)
    65 (*      but do not need to be consecutive.                                   *)
    66 (* ------------------------------------------------------------------------- *)
    67 
    68   type proof = int list Inttab.table * int;
    69 
    70 (* ------------------------------------------------------------------------- *)
    71 (* return type of SAT solvers: if the result is 'SATISFIABLE', a satisfying  *)
    72 (*      assignment must be returned as well; if the result is                *)
    73 (*      'UNSATISFIABLE', a proof of unsatisfiability may be returned         *)
    74 (* ------------------------------------------------------------------------- *)
    75 
    76   datatype result = SATISFIABLE of assignment
    77                   | UNSATISFIABLE of proof option
    78                   | UNKNOWN;
    79 
    80 (* ------------------------------------------------------------------------- *)
    81 (* type of SAT solvers: given a propositional formula, a satisfying          *)
    82 (*      assignment may be returned                                           *)
    83 (* ------------------------------------------------------------------------- *)
    84 
    85   type solver = prop_formula -> result;
    86 
    87 (* ------------------------------------------------------------------------- *)
    88 (* write_dimacs_cnf_file: serializes a formula 'fm' of propositional logic   *)
    89 (*      to a file in DIMACS CNF format (see "Satisfiability Suggested        *)
    90 (*      Format", May 8 1993, Section 2.1)                                    *)
    91 (* Note: 'fm' must not contain a variable index less than 1.                 *)
    92 (* Note: 'fm' must be given in CNF.                                          *)
    93 (* ------------------------------------------------------------------------- *)
    94 
    95   (* Path.T -> prop_formula -> unit *)
    96 
    97   fun write_dimacs_cnf_file path fm =
    98   let
    99     (* prop_formula -> prop_formula *)
   100     fun cnf_True_False_elim True =
   101       Or (BoolVar 1, Not (BoolVar 1))
   102       | cnf_True_False_elim False =
   103       And (BoolVar 1, Not (BoolVar 1))
   104       | cnf_True_False_elim fm =
   105       fm  (* since 'fm' is in CNF, either 'fm'='True'/'False',
   106              or 'fm' does not contain 'True'/'False' at all *)
   107     (* prop_formula -> int *)
   108     fun cnf_number_of_clauses (And (fm1, fm2)) =
   109       (cnf_number_of_clauses fm1) + (cnf_number_of_clauses fm2)
   110       | cnf_number_of_clauses _ =
   111       1
   112     (* TextIO.outstream -> unit *)
   113     fun write_cnf_file out =
   114     let
   115       (* prop_formula -> unit *)
   116       fun write_formula True =
   117           error "formula is not in CNF"
   118         | write_formula False =
   119           error "formula is not in CNF"
   120         | write_formula (BoolVar i) =
   121           (i>=1 orelse error "formula contains a variable index less than 1";
   122            TextIO.output (out, string_of_int i))
   123         | write_formula (Not (BoolVar i)) =
   124           (TextIO.output (out, "-");
   125            write_formula (BoolVar i))
   126         | write_formula (Not _) =
   127           error "formula is not in CNF"
   128         | write_formula (Or (fm1, fm2)) =
   129           (write_formula fm1;
   130            TextIO.output (out, " ");
   131            write_formula fm2)
   132         | write_formula (And (fm1, fm2)) =
   133           (write_formula fm1;
   134            TextIO.output (out, " 0\n");
   135            write_formula fm2)
   136       val fm'               = cnf_True_False_elim fm
   137       val number_of_vars    = maxidx fm'
   138       val number_of_clauses = cnf_number_of_clauses fm'
   139     in
   140       TextIO.output (out, "c This file was generated by SatSolver.write_dimacs_cnf_file\n");
   141       TextIO.output (out, "p cnf " ^ string_of_int number_of_vars ^ " " ^
   142                             string_of_int number_of_clauses ^ "\n");
   143       write_formula fm';
   144       TextIO.output (out, " 0\n")
   145     end
   146   in
   147     File.open_output write_cnf_file path
   148   end;
   149 
   150 (* ------------------------------------------------------------------------- *)
   151 (* write_dimacs_sat_file: serializes a formula 'fm' of propositional logic   *)
   152 (*      to a file in DIMACS SAT format (see "Satisfiability Suggested        *)
   153 (*      Format", May 8 1993, Section 2.2)                                    *)
   154 (* Note: 'fm' must not contain a variable index less than 1.                 *)
   155 (* ------------------------------------------------------------------------- *)
   156 
   157   (* Path.T -> prop_formula -> unit *)
   158 
   159   fun write_dimacs_sat_file path fm =
   160   let
   161     (* TextIO.outstream -> unit *)
   162     fun write_sat_file out =
   163     let
   164       (* prop_formula -> unit *)
   165       fun write_formula True =
   166           TextIO.output (out, "*()")
   167         | write_formula False =
   168           TextIO.output (out, "+()")
   169         | write_formula (BoolVar i) =
   170           (i>=1 orelse error "formula contains a variable index less than 1";
   171            TextIO.output (out, string_of_int i))
   172         | write_formula (Not (BoolVar i)) =
   173           (TextIO.output (out, "-");
   174            write_formula (BoolVar i))
   175         | write_formula (Not fm) =
   176           (TextIO.output (out, "-(");
   177            write_formula fm;
   178            TextIO.output (out, ")"))
   179         | write_formula (Or (fm1, fm2)) =
   180           (TextIO.output (out, "+(");
   181            write_formula_or fm1;
   182            TextIO.output (out, " ");
   183            write_formula_or fm2;
   184            TextIO.output (out, ")"))
   185         | write_formula (And (fm1, fm2)) =
   186           (TextIO.output (out, "*(");
   187            write_formula_and fm1;
   188            TextIO.output (out, " ");
   189            write_formula_and fm2;
   190            TextIO.output (out, ")"))
   191       (* optimization to make use of n-ary disjunction/conjunction *)
   192       and write_formula_or (Or (fm1, fm2)) =
   193           (write_formula_or fm1;
   194            TextIO.output (out, " ");
   195            write_formula_or fm2)
   196         | write_formula_or fm =
   197           write_formula fm
   198       and write_formula_and (And (fm1, fm2)) =
   199           (write_formula_and fm1;
   200            TextIO.output (out, " ");
   201            write_formula_and fm2)
   202         | write_formula_and fm =
   203           write_formula fm
   204       val number_of_vars = Int.max (maxidx fm, 1)
   205     in
   206       TextIO.output (out, "c This file was generated by SatSolver.write_dimacs_sat_file\n");
   207       TextIO.output (out, "p sat " ^ string_of_int number_of_vars ^ "\n");
   208       TextIO.output (out, "(");
   209       write_formula fm;
   210       TextIO.output (out, ")\n")
   211     end
   212   in
   213     File.open_output write_sat_file path
   214   end;
   215 
   216 (* ------------------------------------------------------------------------- *)
   217 (* read_std_result_file: scans a SAT solver's output file for a satisfying   *)
   218 (*      variable assignment.  Returns the assignment, or 'UNSATISFIABLE' if  *)
   219 (*      the file contains 'unsatisfiable', or 'UNKNOWN' if the file contains *)
   220 (*      neither 'satisfiable' nor 'unsatisfiable'.  Empty lines are ignored. *)
   221 (*      The assignment must be given in one or more lines immediately after  *)
   222 (*      the line that contains 'satisfiable'.  These lines must begin with   *)
   223 (*      'assignment_prefix'.  Variables must be separated by " ".  Non-      *)
   224 (*      integer strings are ignored.  If variable i is contained in the      *)
   225 (*      assignment, then i is interpreted as 'true'.  If ~i is contained in  *)
   226 (*      the assignment, then i is interpreted as 'false'.  Otherwise the     *)
   227 (*      value of i is taken to be unspecified.                               *)
   228 (* ------------------------------------------------------------------------- *)
   229 
   230   (* Path.T -> string * string * string -> result *)
   231 
   232   fun read_std_result_file path (satisfiable, assignment_prefix, unsatisfiable) =
   233   let
   234     (* string -> int list *)
   235     fun int_list_from_string s =
   236       List.mapPartial Int.fromString (space_explode " " s)
   237     (* int list -> assignment *)
   238     fun assignment_from_list [] i =
   239       NONE  (* the SAT solver didn't provide a value for this variable *)
   240       | assignment_from_list (x::xs) i =
   241       if x=i then (SOME true)
   242       else if x=(~i) then (SOME false)
   243       else assignment_from_list xs i
   244     (* int list -> string list -> assignment *)
   245     fun parse_assignment xs [] =
   246       assignment_from_list xs
   247       | parse_assignment xs (line::lines) =
   248       if String.isPrefix assignment_prefix line then
   249         parse_assignment (xs @ int_list_from_string line) lines
   250       else
   251         assignment_from_list xs
   252     (* string -> string -> bool *)
   253     fun is_substring needle haystack =
   254     let
   255       val length1 = String.size needle
   256       val length2 = String.size haystack
   257     in
   258       if length2 < length1 then
   259         false
   260       else if needle = String.substring (haystack, 0, length1) then
   261         true
   262       else is_substring needle (String.substring (haystack, 1, length2-1))
   263     end
   264     (* string list -> result *)
   265     fun parse_lines [] =
   266       UNKNOWN
   267       | parse_lines (line::lines) =
   268       if is_substring unsatisfiable line then
   269         UNSATISFIABLE NONE
   270       else if is_substring satisfiable line then
   271         SATISFIABLE (parse_assignment [] lines)
   272       else
   273         parse_lines lines
   274   in
   275     (parse_lines o (List.filter (fn l => l <> "")) o split_lines o File.read) path
   276   end;
   277 
   278 (* ------------------------------------------------------------------------- *)
   279 (* make_external_solver: call 'writefn', execute 'cmd', call 'readfn'        *)
   280 (* ------------------------------------------------------------------------- *)
   281 
   282   (* string -> (PropLogic.prop_formula -> unit) -> (unit -> result) -> solver *)
   283 
   284   fun make_external_solver cmd writefn readfn fm =
   285     (writefn fm; system cmd; readfn ());
   286 
   287 (* ------------------------------------------------------------------------- *)
   288 (* read_dimacs_cnf_file: returns a propositional formula that corresponds to *)
   289 (*      a SAT problem given in DIMACS CNF format                             *)
   290 (* ------------------------------------------------------------------------- *)
   291 
   292   (* Path.T -> PropLogic.prop_formula *)
   293 
   294   fun read_dimacs_cnf_file path =
   295   let
   296     (* string list -> string list *)
   297     fun filter_preamble [] =
   298       error "problem line not found in DIMACS CNF file"
   299       | filter_preamble (line::lines) =
   300       if String.isPrefix "c " line orelse line = "c" then
   301         (* ignore comments *)
   302         filter_preamble lines
   303       else if String.isPrefix "p " line then
   304         (* ignore the problem line (which must be the last line of the preamble) *)
   305         (* Ignoring the problem line implies that if the file contains more clauses *)
   306         (* or variables than specified in its preamble, we will accept it anyway.   *)
   307         lines
   308       else
   309         error "preamble in DIMACS CNF file contains a line that does not begin with \"c \" or \"p \""
   310     (* string -> int *)
   311     fun int_from_string s =
   312       case Int.fromString s of
   313         SOME i => i
   314       | NONE   => error ("token " ^ quote s ^ "in DIMACS CNF file is not a number")
   315     (* int list -> int list list *)
   316     fun clauses xs =
   317       let
   318         val (xs1, xs2) = take_prefix (fn i => i <> 0) xs
   319       in
   320         case xs2 of
   321           []      => [xs1]
   322         | (0::[]) => [xs1]
   323         | (0::tl) => xs1 :: clauses tl
   324         | _       => sys_error "this cannot happen"
   325       end
   326     (* int -> PropLogic.prop_formula *)
   327     fun literal_from_int i =
   328       (i<>0 orelse error "variable index in DIMACS CNF file is 0";
   329       if i>0 then
   330         PropLogic.BoolVar i
   331       else
   332         PropLogic.Not (PropLogic.BoolVar (~i)))
   333     (* PropLogic.prop_formula list -> PropLogic.prop_formula *)
   334     fun disjunction [] =
   335       error "empty clause in DIMACS CNF file"
   336       | disjunction (x::xs) =
   337       (case xs of
   338         [] => x
   339       | _  => PropLogic.Or (x, disjunction xs))
   340     (* PropLogic.prop_formula list -> PropLogic.prop_formula *)
   341     fun conjunction [] =
   342       error "no clause in DIMACS CNF file"
   343       | conjunction (x::xs) =
   344       (case xs of
   345         [] => x
   346       | _  => PropLogic.And (x, conjunction xs))
   347   in
   348     (conjunction
   349     o (map disjunction)
   350     o (map (map literal_from_int))
   351     o clauses
   352     o (map int_from_string)
   353     o List.concat
   354     o (map (String.fields (fn c => c mem [#" ", #"\t", #"\n"])))
   355     o filter_preamble
   356     o (List.filter (fn l => l <> ""))
   357     o split_lines
   358     o File.read)
   359       path
   360   end;
   361 
   362 (* ------------------------------------------------------------------------- *)
   363 (* solvers: a (reference to a) table of all registered SAT solvers           *)
   364 (* ------------------------------------------------------------------------- *)
   365 
   366   val solvers = ref ([] : (string * solver) list);
   367 
   368 (* ------------------------------------------------------------------------- *)
   369 (* add_solver: updates 'solvers' by adding a new solver                      *)
   370 (* ------------------------------------------------------------------------- *)
   371 
   372   (* string * solver -> unit *)
   373 
   374     fun add_solver (name, new_solver) =
   375       let
   376         val the_solvers = !solvers;
   377         val _ = if AList.defined (op =) the_solvers name
   378           then warning ("SAT solver " ^ quote name ^ " was defined before")
   379           else ();
   380       in solvers := AList.update (op =) (name, new_solver) the_solvers end;
   381 
   382 (* ------------------------------------------------------------------------- *)
   383 (* invoke_solver: returns the solver associated with the given 'name'        *)
   384 (* Note: If no solver is associated with 'name', exception 'Option' will be  *)
   385 (*       raised.                                                             *)
   386 (* ------------------------------------------------------------------------- *)
   387 
   388   (* string -> solver *)
   389 
   390   fun invoke_solver name =
   391     (the o AList.lookup (op =) (!solvers)) name;
   392 
   393 end;  (* SatSolver *)
   394 
   395 
   396 (* ------------------------------------------------------------------------- *)
   397 (* Predefined SAT solvers                                                    *)
   398 (* ------------------------------------------------------------------------- *)
   399 
   400 (* ------------------------------------------------------------------------- *)
   401 (* Internal SAT solver, available as 'SatSolver.invoke_solver "enumerate"'   *)
   402 (* -- simply enumerates assignments until a satisfying assignment is found   *)
   403 (* ------------------------------------------------------------------------- *)
   404 
   405 let
   406   fun enum_solver fm =
   407   let
   408     (* int list *)
   409     val indices = PropLogic.indices fm
   410     (* int list -> int list -> int list option *)
   411     (* binary increment: list 'xs' of current bits, list 'ys' of all bits (lower bits first) *)
   412     fun next_list _ ([]:int list) =
   413       NONE
   414       | next_list [] (y::ys) =
   415       SOME [y]
   416       | next_list (x::xs) (y::ys) =
   417       if x=y then
   418         (* reset the bit, continue *)
   419         next_list xs ys
   420       else
   421         (* set the lowest bit that wasn't set before, keep the higher bits *)
   422         SOME (y::x::xs)
   423     (* int list -> int -> bool *)
   424     fun assignment_from_list xs i =
   425       i mem xs
   426     (* int list -> SatSolver.result *)
   427     fun solver_loop xs =
   428       if PropLogic.eval (assignment_from_list xs) fm then
   429         SatSolver.SATISFIABLE (SOME o (assignment_from_list xs))
   430       else
   431         (case next_list xs indices of
   432           SOME xs' => solver_loop xs'
   433         | NONE     => SatSolver.UNSATISFIABLE NONE)
   434   in
   435     (* start with the "first" assignment (all variables are interpreted as 'false') *)
   436     solver_loop []
   437   end
   438 in
   439   SatSolver.add_solver ("enumerate", enum_solver)
   440 end;
   441 
   442 (* ------------------------------------------------------------------------- *)
   443 (* Internal SAT solver, available as 'SatSolver.invoke_solver "dpll"' -- a   *)
   444 (* simple implementation of the DPLL algorithm (cf. L. Zhang, S. Malik: "The *)
   445 (* Quest for Efficient Boolean Satisfiability Solvers", July 2002, Fig. 1).  *)
   446 (* ------------------------------------------------------------------------- *)
   447 
   448 let
   449   local
   450     open PropLogic
   451   in
   452     fun dpll_solver fm =
   453     let
   454       (* We could use 'PropLogic.defcnf fm' instead of 'PropLogic.nnf fm' *)
   455       (* but that sometimes leads to worse performance due to the         *)
   456       (* introduction of additional variables.                            *)
   457       val fm' = PropLogic.nnf fm
   458       (* int list *)
   459       val indices = PropLogic.indices fm'
   460       (* int list -> int -> prop_formula *)
   461       fun partial_var_eval []      i = BoolVar i
   462         | partial_var_eval (x::xs) i = if x=i then True else if x=(~i) then False else partial_var_eval xs i
   463       (* int list -> prop_formula -> prop_formula *)
   464       fun partial_eval xs True             = True
   465         | partial_eval xs False            = False
   466         | partial_eval xs (BoolVar i)      = partial_var_eval xs i
   467         | partial_eval xs (Not fm)         = SNot (partial_eval xs fm)
   468         | partial_eval xs (Or (fm1, fm2))  = SOr (partial_eval xs fm1, partial_eval xs fm2)
   469         | partial_eval xs (And (fm1, fm2)) = SAnd (partial_eval xs fm1, partial_eval xs fm2)
   470       (* prop_formula -> int list *)
   471       fun forced_vars True              = []
   472         | forced_vars False             = []
   473         | forced_vars (BoolVar i)       = [i]
   474         | forced_vars (Not (BoolVar i)) = [~i]
   475         | forced_vars (Or (fm1, fm2))   = (forced_vars fm1) inter_int (forced_vars fm2)
   476         | forced_vars (And (fm1, fm2))  = (forced_vars fm1) union_int (forced_vars fm2)
   477         (* Above, i *and* ~i may be forced.  In this case the first occurrence takes   *)
   478         (* precedence, and the next partial evaluation of the formula returns 'False'. *)
   479         | forced_vars _                 = error "formula is not in negation normal form"
   480       (* int list -> prop_formula -> (int list * prop_formula) *)
   481       fun eval_and_force xs fm =
   482       let
   483         val fm' = partial_eval xs fm
   484         val xs' = forced_vars fm'
   485       in
   486         if null xs' then
   487           (xs, fm')
   488         else
   489           eval_and_force (xs@xs') fm'  (* xs and xs' should be distinct, so '@' here should have *)
   490                                        (* the same effect as 'union_int'                         *)
   491       end
   492       (* int list -> int option *)
   493       fun fresh_var xs =
   494         Library.find_first (fn i => not (i mem_int xs) andalso not ((~i) mem_int xs)) indices
   495       (* int list -> prop_formula -> int list option *)
   496       (* partial assignment 'xs' *)
   497       fun dpll xs fm =
   498       let
   499         val (xs', fm') = eval_and_force xs fm
   500       in
   501         case fm' of
   502           True  => SOME xs'
   503         | False => NONE
   504         | _     =>
   505           let
   506             val x = valOf (fresh_var xs')  (* a fresh variable must exist since 'fm' did not evaluate to 'True'/'False' *)
   507           in
   508             case dpll (x::xs') fm' of  (* passing fm' rather than fm should save some simplification work *)
   509               SOME xs'' => SOME xs''
   510             | NONE      => dpll ((~x)::xs') fm'  (* now try interpreting 'x' as 'False' *)
   511           end
   512       end
   513       (* int list -> assignment *)
   514       fun assignment_from_list [] i =
   515         NONE  (* the DPLL procedure didn't provide a value for this variable *)
   516         | assignment_from_list (x::xs) i =
   517         if x=i then (SOME true)
   518         else if x=(~i) then (SOME false)
   519         else assignment_from_list xs i
   520     in
   521       (* initially, no variable is interpreted yet *)
   522       case dpll [] fm' of
   523         SOME assignment => SatSolver.SATISFIABLE (assignment_from_list assignment)
   524       | NONE            => SatSolver.UNSATISFIABLE NONE
   525     end
   526   end  (* local *)
   527 in
   528   SatSolver.add_solver ("dpll", dpll_solver)
   529 end;
   530 
   531 (* ------------------------------------------------------------------------- *)
   532 (* Internal SAT solver, available as 'SatSolver.invoke_solver "auto"': uses  *)
   533 (* the last installed solver (other than "auto" itself) that does not raise  *)
   534 (* 'NOT_CONFIGURED'.  (However, the solver may return 'UNKNOWN'.)            *)
   535 (* ------------------------------------------------------------------------- *)
   536 
   537 let
   538   fun auto_solver fm =
   539   let
   540     fun loop [] =
   541       SatSolver.UNKNOWN
   542       | loop ((name, solver)::solvers) =
   543       if name="auto" then
   544         (* do not call solver "auto" from within "auto" *)
   545         loop solvers
   546       else (
   547         (if name="dpll" orelse name="enumerate" then
   548           warning ("Using SAT solver " ^ quote name ^ "; for better performance, consider using an external solver.")
   549         else
   550           tracing ("Using SAT solver " ^ quote name ^ "."));
   551         (* apply 'solver' to 'fm' *)
   552         solver fm
   553           handle SatSolver.NOT_CONFIGURED => loop solvers
   554       )
   555   in
   556     loop (!SatSolver.solvers)
   557   end
   558 in
   559   SatSolver.add_solver ("auto", auto_solver)
   560 end;
   561 
   562 (* ------------------------------------------------------------------------- *)
   563 (* MiniSat 1.14                                                              *)
   564 (* (http://www.cs.chalmers.se/Cs/Research/FormalMethods/MiniSat/)            *)
   565 (* ------------------------------------------------------------------------- *)
   566 
   567 (* ------------------------------------------------------------------------- *)
   568 (* "minisat_with_proofs" requires a modified version of MiniSat 1.14 by John *)
   569 (* Matthews, which can output ASCII proof traces.  Replaying binary proof    *)
   570 (* traces generated by MiniSat-p_v1.14 has _not_ been implemented.           *)
   571 (* ------------------------------------------------------------------------- *)
   572 
   573 (* add "minisat_with_proofs" _before_ "minisat" to the available solvers, so *)
   574 (* that the latter is preferred by the "auto" solver                         *)
   575 
   576 (* There is a complication that is dealt with in the code below: MiniSat     *)
   577 (* introduces IDs for original clauses in the proof trace.  It does not (in  *)
   578 (* general) follow the convention that the original clauses are numbered     *)
   579 (* from 0 to n-1 (where n is the number of clauses in the formula).          *)
   580 
   581 let
   582   exception INVALID_PROOF of string
   583   fun minisat_with_proofs fm =
   584   let
   585     val _          = if (getenv "MINISAT_HOME") = "" then raise SatSolver.NOT_CONFIGURED else ()
   586     val serial_str = serial_string ()
   587     val inpath     = File.tmp_path (Path.explode ("isabelle" ^ serial_str ^ ".cnf"))
   588     val outpath    = File.tmp_path (Path.explode ("result" ^ serial_str))
   589     val proofpath  = File.tmp_path (Path.explode ("result" ^ serial_str ^ ".prf"))
   590     val cmd        = (getenv "MINISAT_HOME") ^ "/minisat " ^ (Path.implode inpath) ^ " -r " ^ (Path.implode outpath) ^ " -t " ^ (Path.implode proofpath) ^ "> /dev/null"
   591     fun writefn fm = SatSolver.write_dimacs_cnf_file inpath fm
   592     fun readfn ()  = SatSolver.read_std_result_file outpath ("SAT", "", "UNSAT")
   593     val _          = if File.exists inpath then warning ("overwriting existing file " ^ quote (Path.implode inpath)) else ()
   594     val _          = if File.exists outpath then warning ("overwriting existing file " ^ quote (Path.implode outpath)) else ()
   595     val cnf        = PropLogic.defcnf fm
   596     val result     = SatSolver.make_external_solver cmd writefn readfn cnf
   597     val _          = try File.rm inpath
   598     val _          = try File.rm outpath
   599   in  case result of
   600     SatSolver.UNSATISFIABLE NONE =>
   601     (let
   602       (* string list *)
   603       val proof_lines = (split_lines o File.read) proofpath
   604         handle IO.Io _ => raise INVALID_PROOF "Could not read file \"result.prf\""
   605       (* representation of clauses as ordered lists of literals (with duplicates removed) *)
   606       (* prop_formula -> int list *)
   607       fun clause_to_lit_list (PropLogic.Or (fm1, fm2)) =
   608         OrdList.union int_ord (clause_to_lit_list fm1) (clause_to_lit_list fm2)
   609         | clause_to_lit_list (PropLogic.BoolVar i) =
   610         [i]
   611         | clause_to_lit_list (PropLogic.Not (PropLogic.BoolVar i)) =
   612         [~i]
   613         | clause_to_lit_list _ =
   614         raise INVALID_PROOF "Error: invalid clause in CNF formula."
   615       (* prop_formula -> int *)
   616       fun cnf_number_of_clauses (PropLogic.And (fm1, fm2)) =
   617         cnf_number_of_clauses fm1 + cnf_number_of_clauses fm2
   618         | cnf_number_of_clauses _ =
   619         1
   620       val number_of_clauses = cnf_number_of_clauses cnf
   621       (* int list array *)
   622       val clauses = Array.array (number_of_clauses, [])
   623       (* initialize the 'clauses' array *)
   624       (* prop_formula * int -> int *)
   625       fun init_array (PropLogic.And (fm1, fm2), n) =
   626         init_array (fm2, init_array (fm1, n))
   627         | init_array (fm, n) =
   628         (Array.update (clauses, n, clause_to_lit_list fm); n+1)
   629       val _ = init_array (cnf, 0)
   630       (* optimization for the common case where MiniSat "R"s clauses in their *)
   631       (* original order:                                                      *)
   632       val last_ref_clause = ref (number_of_clauses - 1)
   633       (* search the 'clauses' array for the given list of literals 'lits', *)
   634       (* starting at index '!last_ref_clause + 1'                          *)
   635       (* int list -> int option *)
   636       fun original_clause_id lits =
   637       let
   638         fun original_clause_id_from index =
   639           if index = number_of_clauses then
   640             (* search from beginning again *)
   641             original_clause_id_from 0
   642           (* both 'lits' and the list of literals used in 'clauses' are sorted, so *)
   643           (* testing for equality should suffice -- barring duplicate literals     *)
   644           else if Array.sub (clauses, index) = lits then (
   645             (* success *)
   646             last_ref_clause := index;
   647             SOME index
   648           ) else if index = !last_ref_clause then
   649             (* failure *)
   650             NONE
   651           else
   652             (* continue search *)
   653             original_clause_id_from (index + 1)
   654       in
   655         original_clause_id_from (!last_ref_clause + 1)
   656       end
   657       (* string -> int *)
   658       fun int_from_string s = (
   659         case Int.fromString s of
   660           SOME i => i
   661         | NONE   => raise INVALID_PROOF ("File format error: number expected (" ^ quote s ^ " encountered).")
   662       )
   663       (* parse the proof file *)
   664       val clause_table  = ref (Inttab.empty : int list Inttab.table)
   665       val empty_id      = ref ~1
   666       (* contains a mapping from clause IDs as used by MiniSat to clause IDs in *)
   667       (* our proof format, where original clauses are numbered starting from 0  *)
   668       val clause_id_map = ref (Inttab.empty : int Inttab.table)
   669       fun sat_to_proof id = (
   670         case Inttab.lookup (!clause_id_map) id of
   671           SOME id' => id'
   672         | NONE     => raise INVALID_PROOF ("Clause ID " ^ Int.toString id ^ " used, but not defined.")
   673       )
   674       val next_id = ref (number_of_clauses - 1)
   675       (* string list -> unit *)
   676       fun process_tokens [] =
   677         ()
   678         | process_tokens (tok::toks) =
   679         if tok="R" then (
   680           case toks of
   681             id::sep::lits =>
   682             let
   683               val _        = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"R\" disallowed after \"X\"."
   684               val cid      = int_from_string id
   685               val _        = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).")
   686               val ls       = sort int_ord (map int_from_string lits)
   687               val proof_id = case original_clause_id ls of
   688                                SOME orig_id => orig_id
   689                              | NONE         => raise INVALID_PROOF ("Original clause (new ID is " ^ id ^ ") not found.")
   690             in
   691               (* extend the mapping of clause IDs with this newly defined ID *)
   692               clause_id_map := Inttab.update_new (cid, proof_id) (!clause_id_map)
   693                 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once (in \"R\").")
   694               (* the proof itself doesn't change *)
   695             end
   696           | _ =>
   697             raise INVALID_PROOF "File format error: \"R\" followed by an insufficient number of tokens."
   698         ) else if tok="C" then (
   699           case toks of
   700             id::sep::ids =>
   701             let
   702               val _        = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"C\" disallowed after \"X\"."
   703               val cid      = int_from_string id
   704               val _        = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).")
   705               (* ignore the pivot literals in MiniSat's trace *)
   706               fun unevens []             = raise INVALID_PROOF "File format error: \"C\" followed by an even number of IDs."
   707                 | unevens (x :: [])      = x :: []
   708                 | unevens (x :: _ :: xs) = x :: unevens xs
   709               val rs       = (map sat_to_proof o unevens o map int_from_string) ids
   710               (* extend the mapping of clause IDs with this newly defined ID *)
   711               val proof_id = inc next_id
   712               val _        = clause_id_map := Inttab.update_new (cid, proof_id) (!clause_id_map)
   713                                handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once (in \"C\").")
   714             in
   715               (* update clause table *)
   716               clause_table := Inttab.update_new (proof_id, rs) (!clause_table)
   717                 handle Inttab.DUP _ => raise INVALID_PROOF ("Error: internal ID for clause " ^ id ^ " already used.")
   718             end
   719           | _ =>
   720             raise INVALID_PROOF "File format error: \"C\" followed by an insufficient number of tokens."
   721         ) else if tok="D" then (
   722           case toks of
   723             [id] =>
   724             let
   725               val _ = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"D\" disallowed after \"X\"."
   726               val _ = sat_to_proof (int_from_string id)
   727             in
   728               (* simply ignore "D" *)
   729               ()
   730             end
   731           | _ =>
   732             raise INVALID_PROOF "File format error: \"D\" followed by an illegal number of tokens."
   733         ) else if tok="X" then (
   734           case toks of
   735             [id1, id2] =>
   736             let
   737               val _            = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: more than one end-of-proof statement."
   738               val _            = sat_to_proof (int_from_string id1)
   739               val new_empty_id = sat_to_proof (int_from_string id2)
   740             in
   741               (* update conflict id *)
   742               empty_id := new_empty_id
   743             end
   744           | _ =>
   745             raise INVALID_PROOF "File format error: \"X\" followed by an illegal number of tokens."
   746         ) else
   747           raise INVALID_PROOF ("File format error: unknown token " ^ quote tok ^ " encountered.")
   748       (* string list -> unit *)
   749       fun process_lines [] =
   750         ()
   751         | process_lines (l::ls) = (
   752           process_tokens (String.tokens (fn c => c = #" " orelse c = #"\t") l);
   753           process_lines ls
   754         )
   755       (* proof *)
   756       val _ = process_lines proof_lines
   757       val _ = if !empty_id <> ~1 then () else raise INVALID_PROOF "File format error: no conflicting clause specified."
   758     in
   759       SatSolver.UNSATISFIABLE (SOME (!clause_table, !empty_id))
   760     end handle INVALID_PROOF reason => (warning reason; SatSolver.UNSATISFIABLE NONE))
   761   | result =>
   762     result
   763   end
   764 in
   765   SatSolver.add_solver ("minisat_with_proofs", minisat_with_proofs)
   766 end;
   767 
   768 let
   769   fun minisat fm =
   770   let
   771     val _          = if (getenv "MINISAT_HOME") = "" then raise SatSolver.NOT_CONFIGURED else ()
   772     val serial_str = serial_string ()
   773     val inpath     = File.tmp_path (Path.explode ("isabelle" ^ serial_str ^ ".cnf"))
   774     val outpath    = File.tmp_path (Path.explode ("result" ^ serial_str))
   775     val cmd        = (getenv "MINISAT_HOME") ^ "/minisat " ^ (Path.implode inpath) ^ " -r " ^ (Path.implode outpath) ^ " > /dev/null"
   776     fun writefn fm = SatSolver.write_dimacs_cnf_file inpath (PropLogic.defcnf fm)
   777     fun readfn ()  = SatSolver.read_std_result_file outpath ("SAT", "", "UNSAT")
   778     val _          = if File.exists inpath then warning ("overwriting existing file " ^ quote (Path.implode inpath)) else ()
   779     val _          = if File.exists outpath then warning ("overwriting existing file " ^ quote (Path.implode outpath)) else ()
   780     val result     = SatSolver.make_external_solver cmd writefn readfn fm
   781     val _          = try File.rm inpath
   782     val _          = try File.rm outpath
   783   in
   784     result
   785   end
   786 in
   787   SatSolver.add_solver ("minisat", minisat)
   788 end;
   789 
   790 (* ------------------------------------------------------------------------- *)
   791 (* zChaff (http://www.princeton.edu/~chaff/zchaff.html)                      *)
   792 (* ------------------------------------------------------------------------- *)
   793 
   794 (* ------------------------------------------------------------------------- *)
   795 (* 'zchaff_with_proofs' applies the "zchaff" prover to a formula, and if     *)
   796 (* zChaff finds that the formula is unsatisfiable, a proof of this is read   *)
   797 (* from a file "resolve_trace" that was generated by zChaff.  See the code   *)
   798 (* below for the expected format of the "resolve_trace" file.  Aside from    *)
   799 (* some basic syntactic checks, no verification of the proof is performed.   *)
   800 (* ------------------------------------------------------------------------- *)
   801 
   802 (* add "zchaff_with_proofs" _before_ "zchaff" to the available solvers, so   *)
   803 (* that the latter is preferred by the "auto" solver                         *)
   804 
   805 let
   806   exception INVALID_PROOF of string
   807   fun zchaff_with_proofs fm =
   808   case SatSolver.invoke_solver "zchaff" fm of
   809     SatSolver.UNSATISFIABLE NONE =>
   810     (let
   811       (* string list *)
   812       val proof_lines = ((split_lines o File.read) (Path.explode "resolve_trace"))
   813         handle IO.Io _ => raise INVALID_PROOF "Could not read file \"resolve_trace\""
   814       (* PropLogic.prop_formula -> int *)
   815       fun cnf_number_of_clauses (PropLogic.And (fm1, fm2)) = cnf_number_of_clauses fm1 + cnf_number_of_clauses fm2
   816         | cnf_number_of_clauses _                          = 1
   817       (* string -> int *)
   818       fun int_from_string s = (
   819         case Int.fromString s of
   820           SOME i => i
   821         | NONE   => raise INVALID_PROOF ("File format error: number expected (" ^ quote s ^ " encountered).")
   822       )
   823       (* parse the "resolve_trace" file *)
   824       val clause_offset = ref ~1
   825       val clause_table  = ref (Inttab.empty : int list Inttab.table)
   826       val empty_id      = ref ~1
   827       (* string list -> unit *)
   828       fun process_tokens [] =
   829         ()
   830         | process_tokens (tok::toks) =
   831         if tok="CL:" then (
   832           case toks of
   833             id::sep::ids =>
   834             let
   835               val _   = if !clause_offset = ~1 then () else raise INVALID_PROOF ("File format error: \"CL:\" disallowed after \"VAR:\".")
   836               val _   = if !empty_id = ~1 then () else raise INVALID_PROOF ("File format error: \"CL:\" disallowed after \"CONF:\".")
   837               val cid = int_from_string id
   838               val _   = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).")
   839               val rs  = map int_from_string ids
   840             in
   841               (* update clause table *)
   842               clause_table := Inttab.update_new (cid, rs) (!clause_table)
   843                 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once.")
   844             end
   845           | _ =>
   846             raise INVALID_PROOF "File format error: \"CL:\" followed by an insufficient number of tokens."
   847         ) else if tok="VAR:" then (
   848           case toks of
   849             id::levsep::levid::valsep::valid::antesep::anteid::litsep::lits =>
   850             let
   851               val _   = if !empty_id = ~1 then () else raise INVALID_PROOF ("File format error: \"VAR:\" disallowed after \"CONF:\".")
   852               (* set 'clause_offset' to the largest used clause ID *)
   853               val _   = if !clause_offset = ~1 then clause_offset :=
   854                 (case Inttab.max_key (!clause_table) of
   855                   SOME id => id
   856                 | NONE    => cnf_number_of_clauses (PropLogic.defcnf fm) - 1  (* the first clause ID is 0, not 1 *))
   857                 else
   858                   ()
   859               val vid = int_from_string id
   860               val _   = if levsep = "L:" then () else raise INVALID_PROOF ("File format error: \"L:\" expected (" ^ quote levsep ^ " encountered).")
   861               val _   = int_from_string levid
   862               val _   = if valsep = "V:" then () else raise INVALID_PROOF ("File format error: \"V:\" expected (" ^ quote valsep ^ " encountered).")
   863               val _   = int_from_string valid
   864               val _   = if antesep = "A:" then () else raise INVALID_PROOF ("File format error: \"A:\" expected (" ^ quote antesep ^ " encountered).")
   865               val aid = int_from_string anteid
   866               val _   = if litsep = "Lits:" then () else raise INVALID_PROOF ("File format error: \"Lits:\" expected (" ^ quote litsep ^ " encountered).")
   867               val ls  = map int_from_string lits
   868               (* convert the data provided by zChaff to our resolution-style proof format *)
   869               (* each "VAR:" line defines a unit clause, the resolvents are implicitly    *)
   870               (* given by the literals in the antecedent clause                           *)
   871               (* we use the sum of '!clause_offset' and the variable ID as clause ID for the unit clause *)
   872               val cid = !clause_offset + vid
   873               (* the low bit of each literal gives its sign (positive/negative), therefore  *)
   874               (* we have to divide each literal by 2 to obtain the proper variable ID; then *)
   875               (* we add '!clause_offset' to obtain the ID of the corresponding unit clause  *)
   876               val vids = filter (not_equal vid) (map (fn l => l div 2) ls)
   877               val rs   = aid :: map (fn v => !clause_offset + v) vids
   878             in
   879               (* update clause table *)
   880               clause_table := Inttab.update_new (cid, rs) (!clause_table)
   881                 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ string_of_int cid ^ " (derived from antecedent for variable " ^ id ^ ") already defined.")
   882             end
   883           | _ =>
   884             raise INVALID_PROOF "File format error: \"VAR:\" followed by an insufficient number of tokens."
   885         ) else if tok="CONF:" then (
   886           case toks of
   887             id::sep::ids =>
   888             let
   889               val _   = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: more than one conflicting clause specified."
   890               val cid = int_from_string id
   891               val _   = if sep = "==" then () else raise INVALID_PROOF ("File format error: \"==\" expected (" ^ quote sep ^ " encountered).")
   892               val ls  = map int_from_string ids
   893               (* the conflict clause must be resolved with the unit clauses *)
   894               (* for its literals to obtain the empty clause                *)
   895               val vids         = map (fn l => l div 2) ls
   896               val rs           = cid :: map (fn v => !clause_offset + v) vids
   897               val new_empty_id = getOpt (Inttab.max_key (!clause_table), !clause_offset) + 1
   898             in
   899               (* update clause table and conflict id *)
   900               clause_table := Inttab.update_new (new_empty_id, rs) (!clause_table)
   901                 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ string_of_int new_empty_id ^ " (empty clause derived from clause " ^ id ^ ") already defined.");
   902               empty_id     := new_empty_id
   903             end
   904           | _ =>
   905             raise INVALID_PROOF "File format error: \"CONF:\" followed by an insufficient number of tokens."
   906         ) else
   907           raise INVALID_PROOF ("File format error: unknown token " ^ quote tok ^ " encountered.")
   908       (* string list -> unit *)
   909       fun process_lines [] =
   910         ()
   911         | process_lines (l::ls) = (
   912           process_tokens (String.tokens (fn c => c = #" " orelse c = #"\t") l);
   913           process_lines ls
   914         )
   915       (* proof *)
   916       val _ = process_lines proof_lines
   917       val _ = if !empty_id <> ~1 then () else raise INVALID_PROOF "File format error: no conflicting clause specified."
   918     in
   919       SatSolver.UNSATISFIABLE (SOME (!clause_table, !empty_id))
   920     end handle INVALID_PROOF reason => (warning reason; SatSolver.UNSATISFIABLE NONE))
   921   | result =>
   922     result
   923 in
   924   SatSolver.add_solver ("zchaff_with_proofs", zchaff_with_proofs)
   925 end;
   926 
   927 let
   928   fun zchaff fm =
   929   let
   930     val _          = if (getenv "ZCHAFF_HOME") = "" then raise SatSolver.NOT_CONFIGURED else ()
   931     val serial_str = serial_string ()
   932     val inpath     = File.tmp_path (Path.explode ("isabelle" ^ serial_str ^ ".cnf"))
   933     val outpath    = File.tmp_path (Path.explode ("result" ^ serial_str))
   934     val cmd        = (getenv "ZCHAFF_HOME") ^ "/zchaff " ^ (Path.implode inpath) ^ " > " ^ (Path.implode outpath)
   935     fun writefn fm = SatSolver.write_dimacs_cnf_file inpath (PropLogic.defcnf fm)
   936     fun readfn ()  = SatSolver.read_std_result_file outpath ("Instance Satisfiable", "", "Instance Unsatisfiable")
   937     val _          = if File.exists inpath then warning ("overwriting existing file " ^ quote (Path.implode inpath)) else ()
   938     val _          = if File.exists outpath then warning ("overwriting existing file " ^ quote (Path.implode outpath)) else ()
   939     val result     = SatSolver.make_external_solver cmd writefn readfn fm
   940     val _          = try File.rm inpath
   941     val _          = try File.rm outpath
   942   in
   943     result
   944   end
   945 in
   946   SatSolver.add_solver ("zchaff", zchaff)
   947 end;
   948 
   949 (* ------------------------------------------------------------------------- *)
   950 (* BerkMin 561 (http://eigold.tripod.com/BerkMin.html)                       *)
   951 (* ------------------------------------------------------------------------- *)
   952 
   953 let
   954   fun berkmin fm =
   955   let
   956     val _          = if (getenv "BERKMIN_HOME") = "" then raise SatSolver.NOT_CONFIGURED else ()
   957     val serial_str = serial_string ()
   958     val inpath     = File.tmp_path (Path.explode ("isabelle" ^ serial_str ^ ".cnf"))
   959     val outpath    = File.tmp_path (Path.explode ("result" ^ serial_str))
   960     val exec       = getenv "BERKMIN_EXE"
   961     val cmd        = (getenv "BERKMIN_HOME") ^ "/" ^ (if exec = "" then "BerkMin561" else exec) ^ " " ^ (Path.implode inpath) ^ " > " ^ (Path.implode outpath)
   962     fun writefn fm = SatSolver.write_dimacs_cnf_file inpath (PropLogic.defcnf fm)
   963     fun readfn ()  = SatSolver.read_std_result_file outpath ("Satisfiable          !!", "solution =", "UNSATISFIABLE          !!")
   964     val _          = if File.exists inpath then warning ("overwriting existing file " ^ quote (Path.implode inpath)) else ()
   965     val _          = if File.exists outpath then warning ("overwriting existing file " ^ quote (Path.implode outpath)) else ()
   966     val result     = SatSolver.make_external_solver cmd writefn readfn fm
   967     val _          = try File.rm inpath
   968     val _          = try File.rm outpath
   969   in
   970     result
   971   end
   972 in
   973   SatSolver.add_solver ("berkmin", berkmin)
   974 end;
   975 
   976 (* ------------------------------------------------------------------------- *)
   977 (* Jerusat 1.3 (http://www.cs.tau.ac.il/~ale1/)                              *)
   978 (* ------------------------------------------------------------------------- *)
   979 
   980 let
   981   fun jerusat fm =
   982   let
   983     val _          = if (getenv "JERUSAT_HOME") = "" then raise SatSolver.NOT_CONFIGURED else ()
   984     val serial_str = serial_string ()
   985     val inpath     = File.tmp_path (Path.explode ("isabelle" ^ serial_str ^ ".cnf"))
   986     val outpath    = File.tmp_path (Path.explode ("result" ^ serial_str))
   987     val cmd        = (getenv "JERUSAT_HOME") ^ "/Jerusat1.3 " ^ (Path.implode inpath) ^ " > " ^ (Path.implode outpath)
   988     fun writefn fm = SatSolver.write_dimacs_cnf_file inpath (PropLogic.defcnf fm)
   989     fun readfn ()  = SatSolver.read_std_result_file outpath ("s SATISFIABLE", "v ", "s UNSATISFIABLE")
   990     val _          = if File.exists inpath then warning ("overwriting existing file " ^ quote (Path.implode inpath)) else ()
   991     val _          = if File.exists outpath then warning ("overwriting existing file " ^ quote (Path.implode outpath)) else ()
   992     val result     = SatSolver.make_external_solver cmd writefn readfn fm
   993     val _          = try File.rm inpath
   994     val _          = try File.rm outpath
   995   in
   996     result
   997   end
   998 in
   999   SatSolver.add_solver ("jerusat", jerusat)
  1000 end;
  1001 
  1002 (* ------------------------------------------------------------------------- *)
  1003 (* Add code for other SAT solvers below.                                     *)
  1004 (* ------------------------------------------------------------------------- *)
  1005 
  1006 (*
  1007 let
  1008   fun mysolver fm = ...
  1009 in
  1010   SatSolver.add_solver ("myname", (fn fm => if mysolver_is_configured then mysolver fm else raise SatSolver.NOT_CONFIGURED));  -- register the solver
  1011 end;
  1012 
  1013 -- the solver is now available as SatSolver.invoke_solver "myname"
  1014 *)