src/HOL/Tools/sat_solver.ML
changeset 17493 cf8713d880b1
parent 16915 bca4c3b1afca
child 17494 e70600834f44
equal deleted inserted replaced
17492:714c95aab8bc 17493:cf8713d880b1
     9 signature SAT_SOLVER =
     9 signature SAT_SOLVER =
    10 sig
    10 sig
    11 	exception NOT_CONFIGURED
    11 	exception NOT_CONFIGURED
    12 
    12 
    13 	type assignment = int -> bool option
    13 	type assignment = int -> bool option
       
    14 	type proof      = int list Inttab.table * int
    14 	datatype result = SATISFIABLE of assignment
    15 	datatype result = SATISFIABLE of assignment
    15 	                | UNSATISFIABLE
    16 	                | UNSATISFIABLE of proof option
    16 	                | UNKNOWN
    17 	                | UNKNOWN
    17 	type solver     = PropLogic.prop_formula -> result
    18 	type solver     = PropLogic.prop_formula -> result
    18 
    19 
    19 	(* auxiliary functions to create external SAT solvers *)
    20 	(* auxiliary functions to create external SAT solvers *)
    20 	val write_dimacs_cnf_file : Path.T -> PropLogic.prop_formula -> unit
    21 	val write_dimacs_cnf_file : Path.T -> PropLogic.prop_formula -> unit
    48 (* ------------------------------------------------------------------------- *)
    49 (* ------------------------------------------------------------------------- *)
    49 
    50 
    50 	type assignment = int -> bool option;
    51 	type assignment = int -> bool option;
    51 
    52 
    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 (* ------------------------------------------------------------------------- *)
    53 (* return type of SAT solvers: if the result is 'SATISFIABLE', a satisfying  *)
    71 (* return type of SAT solvers: if the result is 'SATISFIABLE', a satisfying  *)
    54 (*      assignment must be returned as well                                  *)
    72 (*      assignment must be returned as well; if the result is                *)
       
    73 (*      'UNSATISFIABLE', a proof of unsatisfiability may be returned         *)
    55 (* ------------------------------------------------------------------------- *)
    74 (* ------------------------------------------------------------------------- *)
    56 
    75 
    57 	datatype result = SATISFIABLE of assignment
    76 	datatype result = SATISFIABLE of assignment
    58 	                | UNSATISFIABLE
    77 	                | UNSATISFIABLE of proof option
    59 	                | UNKNOWN;
    78 	                | UNKNOWN;
    60 
    79 
    61 (* ------------------------------------------------------------------------- *)
    80 (* ------------------------------------------------------------------------- *)
    62 (* type of SAT solvers: given a propositional formula, a satisfying          *)
    81 (* type of SAT solvers: given a propositional formula, a satisfying          *)
    63 (*      assignment may be returned                                           *)
    82 (*      assignment may be returned                                           *)
   115 		val number_of_vars    = maxidx fm'
   134 		val number_of_vars    = maxidx fm'
   116 		val number_of_clauses = cnf_number_of_clauses fm'
   135 		val number_of_clauses = cnf_number_of_clauses fm'
   117 	in
   136 	in
   118 		File.write path
   137 		File.write path
   119 			("c This file was generated by SatSolver.write_dimacs_cnf_file\n" ^
   138 			("c This file was generated by SatSolver.write_dimacs_cnf_file\n" ^
   120 			 "c (c) Tjark Weber\n" ^
       
   121 			 "p cnf " ^ string_of_int number_of_vars ^ " " ^ string_of_int number_of_clauses ^ "\n");
   139 			 "p cnf " ^ string_of_int number_of_vars ^ " " ^ string_of_int number_of_clauses ^ "\n");
   122 		File.append_list path
   140 		File.append_list path
   123 			(cnf_string fm');
   141 			(cnf_string fm');
   124 		File.append path
   142 		File.append path
   125 			" 0\n"
   143 			" 0\n"
   171 		end
   189 		end
   172 		val number_of_vars = Int.max (maxidx fm, 1)
   190 		val number_of_vars = Int.max (maxidx fm, 1)
   173 	in
   191 	in
   174 		File.write path
   192 		File.write path
   175 			("c This file was generated by SatSolver.write_dimacs_sat_file\n" ^
   193 			("c This file was generated by SatSolver.write_dimacs_sat_file\n" ^
   176 			 "c (c) Tjark Weber\n" ^
       
   177 			 "p sat " ^ string_of_int number_of_vars ^ "\n" ^
   194 			 "p sat " ^ string_of_int number_of_vars ^ "\n" ^
   178 			 "(");
   195 			 "(");
   179 		File.append_list path
   196 		File.append_list path
   180 			(sat_string fm);
   197 			(sat_string fm);
   181 		File.append path
   198 		File.append path
   235 			UNKNOWN
   252 			UNKNOWN
   236 		  | parse_lines (line::lines) =
   253 		  | parse_lines (line::lines) =
   237 			if is_substring satisfiable line then
   254 			if is_substring satisfiable line then
   238 				SATISFIABLE (parse_assignment [] lines)
   255 				SATISFIABLE (parse_assignment [] lines)
   239 			else if is_substring unsatisfiable line then
   256 			else if is_substring unsatisfiable line then
   240 				UNSATISFIABLE
   257 				UNSATISFIABLE NONE
   241 			else
   258 			else
   242 				parse_lines lines
   259 				parse_lines lines
   243 	in
   260 	in
   244 		(parse_lines o (List.filter (fn l => l <> "")) o split_lines o File.read) path
   261 		(parse_lines o (List.filter (fn l => l <> "")) o split_lines o File.read) path
   245 	end;
   262 	end;
   392 			if PropLogic.eval (assignment_from_list xs) fm then
   409 			if PropLogic.eval (assignment_from_list xs) fm then
   393 				SatSolver.SATISFIABLE (SOME o (assignment_from_list xs))
   410 				SatSolver.SATISFIABLE (SOME o (assignment_from_list xs))
   394 			else
   411 			else
   395 				(case next_list xs indices of
   412 				(case next_list xs indices of
   396 				  SOME xs' => solver_loop xs'
   413 				  SOME xs' => solver_loop xs'
   397 				| NONE     => SatSolver.UNSATISFIABLE)
   414 				| NONE     => SatSolver.UNSATISFIABLE NONE)
   398 	in
   415 	in
   399 		(* start with the "first" assignment (all variables are interpreted as 'false') *)
   416 		(* start with the "first" assignment (all variables are interpreted as 'false') *)
   400 		solver_loop []
   417 		solver_loop []
   401 	end
   418 	end
   402 in
   419 in
   414 		open PropLogic
   431 		open PropLogic
   415 	in
   432 	in
   416 		fun dpll_solver fm =
   433 		fun dpll_solver fm =
   417 		let
   434 		let
   418 			(* We could use 'PropLogic.defcnf fm' instead of 'PropLogic.nnf fm' *)
   435 			(* We could use 'PropLogic.defcnf fm' instead of 'PropLogic.nnf fm' *)
   419 			(* but that sometimes introduces more boolean variables than we can *)
   436 			(* but that sometimes leads to worse performance due to the         *)
   420 			(* handle efficiently.                                              *)
   437 			(* introduction of additional variables.                            *)
   421 			val fm' = PropLogic.nnf fm
   438 			val fm' = PropLogic.nnf fm
   422 			(* int list *)
   439 			(* int list *)
   423 			val indices = PropLogic.indices fm'
   440 			val indices = PropLogic.indices fm'
   424 			(* int list -> int -> prop_formula *)
   441 			(* int list -> int -> prop_formula *)
   425 			fun partial_var_eval []      i = BoolVar i
   442 			fun partial_var_eval []      i = BoolVar i
   483 				else assignment_from_list xs i
   500 				else assignment_from_list xs i
   484 		in
   501 		in
   485 			(* initially, no variable is interpreted yet *)
   502 			(* initially, no variable is interpreted yet *)
   486 			case dpll [] fm' of
   503 			case dpll [] fm' of
   487 			  SOME assignment => SatSolver.SATISFIABLE (assignment_from_list assignment)
   504 			  SOME assignment => SatSolver.SATISFIABLE (assignment_from_list assignment)
   488 			| NONE            => SatSolver.UNSATISFIABLE
   505 			| NONE            => SatSolver.UNSATISFIABLE NONE
   489 		end
   506 		end
   490 	end  (* local *)
   507 	end  (* local *)
   491 in
   508 in
   492 	SatSolver.add_solver ("dpll", dpll_solver)
   509 	SatSolver.add_solver ("dpll", dpll_solver)
   493 end;
   510 end;
   525 
   542 
   526 (* ------------------------------------------------------------------------- *)
   543 (* ------------------------------------------------------------------------- *)
   527 (* zChaff (http://www.princeton.edu/~chaff/zchaff.html)                      *)
   544 (* zChaff (http://www.princeton.edu/~chaff/zchaff.html)                      *)
   528 (* ------------------------------------------------------------------------- *)
   545 (* ------------------------------------------------------------------------- *)
   529 
   546 
       
   547 (* ------------------------------------------------------------------------- *)
       
   548 (* 'zchaff_with_proofs' applies the "zchaff" prover to a formula, and if     *)
       
   549 (* zChaff finds that the formula is unsatisfiable, a proof of this is read   *)
       
   550 (* from a file "resolve_trace" that was generated by zChaff.  See the code   *)
       
   551 (* below for the expected format of the "resolve_trace" file.  Aside from    *)
       
   552 (* some basic syntactic checks, no verification of the proof is performed.   *)
       
   553 (* ------------------------------------------------------------------------- *)
       
   554 
       
   555 (* add "zchaff_with_proofs" _before_ "zchaff" to the available solvers, so   *)
       
   556 (* that the latter is preferred by the "auto" solver                         *)
       
   557 
       
   558 let
       
   559 	exception INVALID_PROOF of string
       
   560 	fun zchaff_with_proofs fm =
       
   561 	case SatSolver.invoke_solver "zchaff" fm of
       
   562 	  SatSolver.UNSATISFIABLE NONE =>
       
   563 		(let
       
   564 			(* string list *)
       
   565 			val proof_lines = ((split_lines o File.read) (Path.unpack "resolve_trace"))
       
   566 				handle _ => raise INVALID_PROOF "Could not read file \"resolve_trace\""
       
   567 			val _ = try File.rm (Path.unpack "resolve_trace")
       
   568 			(* PropLogic.prop_formula -> int *)
       
   569 			fun cnf_number_of_clauses (PropLogic.And (fm1, fm2)) = cnf_number_of_clauses fm1 + cnf_number_of_clauses fm2
       
   570 			  | cnf_number_of_clauses _                          = 1
       
   571 			(* string -> int *)
       
   572 			fun int_from_string s = (
       
   573 				case Int.fromString s of
       
   574 				  SOME i => i
       
   575 				| NONE   => raise INVALID_PROOF ("File format error: number expected (" ^ quote s ^ " encountered).")
       
   576 			)
       
   577 			(* parse the "resolve_trace" file *)
       
   578 			(* int ref *)
       
   579 			val clause_offset = ref ~1
       
   580 			(* string list -> proof -> proof *)
       
   581 			fun process_tokens [] proof =
       
   582 				proof
       
   583 			  | process_tokens (tok::toks) (clause_table, conflict_id) =
       
   584 				if tok="CL:" then (
       
   585 					case toks of
       
   586 					  id::sep::ids =>
       
   587 						let
       
   588 							val _   = if !clause_offset = ~1 then () else raise INVALID_PROOF ("File format error: \"CL:\" disallowed after \"VAR:\".")
       
   589 							val cid = int_from_string id
       
   590 							val _   = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).")
       
   591 							val rs  = map int_from_string ids
       
   592 						in
       
   593 							(* update clause table *)
       
   594 							(Inttab.update_new ((cid, rs), clause_table), conflict_id)
       
   595 								handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once.")
       
   596 						end
       
   597 					| _ =>
       
   598 						raise INVALID_PROOF "File format error: \"CL:\" followed by an insufficient number of tokens."
       
   599 				) else if tok="VAR:" then (
       
   600 					case toks of
       
   601 					  id::levsep::levid::valsep::valid::antesep::anteid::litsep::lits =>
       
   602 						let
       
   603 							(* set 'clause_offset' to the largest used clause ID *)
       
   604 							val _   = if !clause_offset = ~1 then clause_offset :=
       
   605 								(case Inttab.max_key clause_table of
       
   606 								  SOME id => id
       
   607 								| NONE    => cnf_number_of_clauses (PropLogic.defcnf fm))
       
   608 								else
       
   609 									()
       
   610 							val vid = int_from_string id
       
   611 							val _   = if levsep = "L:" then () else raise INVALID_PROOF ("File format error: \"L:\" expected (" ^ quote levsep ^ " encountered).")
       
   612 							val _   = int_from_string levid
       
   613 							val _   = if valsep = "V:" then () else raise INVALID_PROOF ("File format error: \"V:\" expected (" ^ quote valsep ^ " encountered).")
       
   614 							val _   = int_from_string valid
       
   615 							val _   = if antesep = "A:" then () else raise INVALID_PROOF ("File format error: \"A:\" expected (" ^ quote antesep ^ " encountered).")
       
   616 							val aid = int_from_string anteid
       
   617 							val _   = if litsep = "Lits:" then () else raise INVALID_PROOF ("File format error: \"Lits:\" expected (" ^ quote litsep ^ " encountered).")
       
   618 							val ls  = map int_from_string lits
       
   619 							(* convert the data provided by zChaff to our resolution-style proof format *)
       
   620 							(* each "VAR:" line defines a unit clause, the resolvents are implicitly    *)
       
   621 							(* given by the literals in the antecedent clause                           *)
       
   622 							(* we use the sum of '!clause_offset' and the variable ID as clause ID for the unit clause *)
       
   623 							val cid = !clause_offset + vid
       
   624 							(* the low bit of each literal gives its sign (positive/negative), therefore  *)
       
   625 							(* we have to divide each literal by 2 to obtain the proper variable ID; then *)
       
   626 							(* we add '!clause_offset' to obtain the ID of the corresponding unit clause  *)
       
   627 							val vids = filter (not_equal vid) (map (fn l => l div 2) ls)
       
   628 							val rs   = aid :: map (fn v => !clause_offset + v) vids
       
   629 						in
       
   630 							(* update clause table *)
       
   631 							(Inttab.update_new ((cid, rs), clause_table), conflict_id)
       
   632 								handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ string_of_int cid ^ " (antecedent for variable " ^ id ^ ") defined more than once.")
       
   633 						end
       
   634 					| _ =>
       
   635 						raise INVALID_PROOF "File format error: \"VAR:\" followed by an insufficient number of tokens."
       
   636 				) else if tok="CONF:" then (
       
   637 					case toks of
       
   638 					  id::sep::ids =>
       
   639 						let
       
   640 							val cid = int_from_string id
       
   641 							val _   = if sep = "==" then () else raise INVALID_PROOF ("File format error: \"==\" expected (" ^ quote sep ^ " encountered).")
       
   642 							val _   = map int_from_string ids
       
   643 							val _   = if conflict_id = ~1 then () else raise INVALID_PROOF "File format error: more than one conflicting clause specified."
       
   644 						in
       
   645 							(* update conflict id *)
       
   646 							(clause_table, cid)
       
   647 						end
       
   648 					| _ =>
       
   649 						raise INVALID_PROOF "File format error: \"CONF:\" followed by an insufficient number of tokens."
       
   650 				) else
       
   651 					raise INVALID_PROOF ("File format error: unknown token " ^ quote tok ^ " encountered.")
       
   652 			(* string list -> proof -> proof *)
       
   653 			fun process_lines [] proof =
       
   654 				proof
       
   655 			  | process_lines (l::ls) proof =
       
   656 				process_lines ls (process_tokens (String.tokens (fn c => c = #" " orelse c = #"\t") l) proof)
       
   657 			(* proof *)
       
   658 			val (clause_table, conflict_id) = process_lines proof_lines (Inttab.empty, ~1)
       
   659 			val _                           = if conflict_id <> ~1 then () else raise INVALID_PROOF "File format error: no conflicting clause specified."
       
   660 		in
       
   661 			SatSolver.UNSATISFIABLE (SOME (clause_table, conflict_id))
       
   662 		end handle INVALID_PROOF reason => (warning reason; SatSolver.UNSATISFIABLE NONE))
       
   663 	| result =>
       
   664 		result
       
   665 in
       
   666 	SatSolver.add_solver ("zchaff_with_proofs", zchaff_with_proofs)
       
   667 end;
       
   668 
   530 let
   669 let
   531 	fun zchaff fm =
   670 	fun zchaff fm =
   532 	let
   671 	let
   533 		val _          = if (getenv "ZCHAFF_HOME") = "" then raise SatSolver.NOT_CONFIGURED else ()
   672 		val _          = if (getenv "ZCHAFF_HOME") = "" then raise SatSolver.NOT_CONFIGURED else ()
   534 		val _          = if (getenv "ZCHAFF_VERSION") <> "2004.5.13" andalso
   673 		val _          = if (getenv "ZCHAFF_VERSION") <> "2004.5.13" andalso